[MERGE]: Merge with lp:openobject-server

bzr revid: rpa@tinyerp.com-20100924053725-hq7yztizail9m3ul
bzr revid: rpa@tinyerp.com-20100927052905-mnf9z0ee9oeakf67
This commit is contained in:
rpa (Open ERP)
2010-09-27 10:59:05 +05:30
18 changed files with 168 additions and 115 deletions
+24 -12
View File
@@ -757,16 +757,21 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, **kwargs):
return has_updates
def _check_module_names(cr, module_names):
mod_names = set(module_names)
if 'base' in mod_names:
# ignore dummy 'all' module
if 'all' in mod_names:
mod_names.remove('all')
if mod_names:
cr.execute("SELECT count(id) AS count FROM ir_module_module WHERE name in %s", (tuple(mod_names),))
if cr.dictfetchone()['count'] != len(mod_names):
# find out what module name(s) are incorrect:
cr.execute("SELECT name FROM ir_module_module")
incorrect_names = mod_names.difference([x['name'] for x in cr.dictfetchall()])
logging.getLogger('init').warning('invalid module names, ignored: %s', ", ".join(incorrect_names))
def load_modules(db, force_demo=False, status=None, update_module=False):
def check_module_name(cr, mods, state):
for mod in mods:
id = modobj.search(cr, 1, ['&', ('state', '=', state), ('name', '=', mod)])
if id:
getattr(modobj, states[state])(cr, 1, id)
elif mod != 'all':
logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: invalid module name!' % (mod))
if not status:
status = {}
cr = db.cursor()
@@ -795,16 +800,23 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
if update_module:
modobj = pool.get('ir.module.module')
states = {'installed': 'button_upgrade', 'uninstalled': 'button_install'}
logger.notifyChannel('init', netsvc.LOG_INFO, 'updating modules list')
if ('base' in tools.config['init']) or ('base' in tools.config['update']):
modobj.update_list(cr, 1)
_check_module_names(cr, itertools.chain(tools.config['init'].keys(), tools.config['update'].keys()))
mods = [k for k in tools.config['init'] if tools.config['init'][k]]
check_module_name(cr, mods, 'uninstalled')
if mods:
ids = modobj.search(cr, 1, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
if ids:
modobj.button_install(cr, 1, ids)
mods = [k for k in tools.config['update'] if tools.config['update'][k]]
check_module_name(cr, mods, 'installed')
if mods:
ids = modobj.search(cr, 1, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
if ids:
modobj.button_upgrade(cr, 1, ids)
cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
+5 -5
View File
@@ -1906,8 +1906,10 @@
<tree editable="bottom" string="Config Wizard Steps">
<field name="sequence"/>
<field name="action_id"/>
<field name="restart"/>
<field name="state" readonly="1"/>
<button name="action_launch" states="open" string="Launch" type="object" icon="gtk-execute" help="Launch Configuration Wizard"/>
<button name="action_open" states="cancel,skip,done" string="Set as todo" type="object" icon="gtk-convert"/>
<button name="action_launch" states="open" string="Launch this wizard" type="object" icon="gtk-execute"/>
</tree>
</field>
</record>
@@ -1927,10 +1929,8 @@
<field name="groups_id" nolabel="1" colspan="4"/>
<group colspan="4" col="4">
<field name="state" colspan="2" readonly="1"/>
<button name="action_launch" states="open" string="Launch" type="object" icon="gtk-execute" help="Launch Configuration Wizard"/>
<button name="action_open" states="cancel,skip,done"
string="Set as Todo" type="object"
icon="gtk-convert"/>
<button name="action_launch" states="open" string="Launch" type="object" icon="gtk-execute" help="Launch this wizard"/>
<button name="action_open" states="cancel,skip,done" string="Set as todo" type="object" icon="gtk-convert"/>
</group>
</form>
</field>
+4 -4
View File
@@ -753,7 +753,7 @@ act_window_close()
TODO_STATES = [('open', 'To Do'),
('done', 'Done'),
('skip','Skipped'),
('cancel','Cancel')]
('cancel','Cancelled')]
class ir_actions_todo(osv.osv):
_name = 'ir.actions.todo'
@@ -769,9 +769,9 @@ class ir_actions_todo(osv.osv):
'note':fields.text('Text', translate=True),
}
_defaults={
'state': lambda *a: 'open',
'sequence': lambda *a: 10,
'restart': lambda *a: 'always',
'state': 'open',
'sequence': 10,
'restart': 'onskip',
}
_order="sequence,id"
+2 -2
View File
@@ -105,8 +105,8 @@ class ir_attachment(osv.osv):
'datas': fields.binary('Data'),
'datas_fname': fields.char('Filename',size=256),
'description': fields.text('Description'),
'res_name': fields.function(_name_get_resname, type='char',
string='Resource Name', method=True),
'res_name': fields.function(_name_get_resname, type='char', size=128,
string='Resource Name', method=True, store=True),
'res_model': fields.char('Resource Object',size=64, readonly=True,
help="The database object this attachment will be attached to"),
'res_id': fields.integer('Resource ID', readonly=True,
+2 -2
View File
@@ -88,7 +88,7 @@ class report_custom(osv.osv):
c_f[i] = []
tmp = report.fields_child0[i]
for j in range(3):
c_f[i].append((not isinstance(eval('tmp.field_child'+str(j)), browse_null) and eval('tmp.field_child'+str(j)+'.ttype')) or None)
c_f[i].append((not isinstance(eval('tmp.field_child'+str(j),{},{'tmp':tmp}), browse_null) and eval('tmp.field_child'+str(j)+'.ttype',{},{'tmp':tmp})) or None)
if not reduce(lambda x,y : x or y, map(lambda x: x in ['integer', 'float'], c_f[1])):
raise osv.except_osv(_('Invalid operation'), _('Second field should be figures'))
@@ -101,7 +101,7 @@ class report_custom(osv.osv):
c_f[i] = []
tmp = report.fields_child0[i]
for j in range(3):
c_f[i].append((not isinstance(eval('tmp.field_child'+str(j)), browse_null) and eval('tmp.field_child'+str(j)+'.ttype')) or None)
c_f[i].append((not isinstance(eval('tmp.field_child'+str(j),{},{'tmp':tmp}), browse_null) and eval('tmp.field_child'+str(j)+'.ttype',{},{'tmp':tmp})) or None)
if i == 0:
pass
+2 -2
View File
@@ -162,9 +162,9 @@
<field name="installed_version"/>
<field name="latest_version"/>
<field name="state"/>
<button name="button_install" states="uninstalled" string="Schedule for Installation" icon="terp-gtk-jump-to-ltr" type="object" help="Schedule for Installation"/>
<button name="button_install" states="uninstalled" string="Schedule for Installation" icon="terp-gtk-jump-to-ltr" type="object"/>
<button name="button_install_cancel" states="to install" string="Cancel Install" icon="gtk-cancel" type="object"/>
<button name="button_upgrade" states="installed" string="Schedule Upgrade" icon="terp-gtk-go-back-rtl" type="object" help="Schedule Upgrade"/>
<button name="button_upgrade" states="installed" string="Schedule Upgrade" icon="terp-gtk-go-back-rtl" type="object"/>
<button name="button_uninstall" states="installed" string="Uninstall (beta)" icon="terp-dialog-close" type="object"/>
<button name="button_uninstall_cancel" states="to remove" string="Cancel Uninstall" icon="gtk-cancel" type="object"/>
<button name="button_upgrade_cancel" states="to upgrade" string="Cancel Upgrade" icon="gtk-cancel" type="object"/>
@@ -3,27 +3,24 @@
<data>
<record id="view_base_language_install" model="ir.ui.view">
<field name="name">Install Language</field>
<field name="name">Load Official Translation</field>
<field name="model">base.language.install</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="System Upgrade">
<group colspan="2" col="4">
<separator string="System Upgrade" colspan="6"/>
<newline/>
<label align="0.0" string="Choose a language to install:" colspan="4"/>
<field name="lang" colspan="4"/>
<label align="0.0" string="Note that this operation may take a few minutes." colspan="4"/>
<separator string="" colspan="6"/>
<button special="cancel" string="Cancel" icon="gtk-cancel"/>
<button name="lang_install" string="Start installation" type="object" icon="gtk-ok"/>
</group>
</form>
<form string="Load Official Translation">
<separator string="Load an Official Translation" colspan="4"/>
<group colspan="4"><field name="lang"/></group>
<label string="" colspan="4"/>
<label string="Please be patient, as this operation may take a few minutes..." colspan="4"/>
<separator string="" colspan="4"/>
<button special="cancel" string="Cancel" icon="gtk-cancel"/>
<button name="lang_install" string="Load" type="object" icon="gtk-ok"/>
</form>
</field>
</record>
<record id="action_view_base_language_install" model="ir.actions.act_window">
<field name="name">Install Language</field>
<field name="name">Load Official Translation</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">base.language.install</field>
<field name="view_type">form</field>
@@ -38,23 +35,21 @@
parent="menu_translation"/>
<record id="view_base_language_install_msg" model="ir.ui.view">
<field name="name">Install Language Message</field>
<field name="name">Official Translation Loaded</field>
<field name="model">base.module.import.msg</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Language file loaded.">
<group colspan="2" col="4">
<separator string="Installation Done" colspan="4"/>
<label align="0.0" string="The selected language has been successfully installed.\nYou must change the preferences of the user and open a new menu to view changes." colspan="4"/>
<separator string="" colspan="4"/>
<button special="cancel" string="OK" icon="gtk-ok"/>
</group>
<form string="Official Translation Loaded">
<separator string="Official Translation Loaded" colspan="4"/>
<label string="The selected language has been successfully installed.\nYou must change the preferences of the user and open a new menu to view the changes." colspan="4"/>
<separator string="" colspan="4"/>
<button special="cancel" string="OK" icon="gtk-ok"/>
</form>
</field>
</record>
<record id="action_view_base_language_install_msg" model="ir.actions.act_window">
<field name="name">Install Language Message</field>
<field name="name">Official Translation Loaded</field>
<field name="res_model">base.module.import.msg</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
+4 -4
View File
@@ -20,8 +20,9 @@
##############################################################################
from osv import osv,fields
from operator import attrgetter
from tools.misc import attrgetter
import time
# -------------------------------------------------------------------------
# Properties
# -------------------------------------------------------------------------
@@ -31,7 +32,6 @@ class ir_property(osv.osv):
def _models_field_get(self, cr, uid, field_key, field_value, context=None):
get = attrgetter(field_key, field_value)
obj = self.pool.get('ir.model.fields')
ids = obj.search(cr, uid, [('view_load','=',1)], context=context)
res = set()
@@ -40,7 +40,7 @@ class ir_property(osv.osv):
return res
def _models_get(self, cr, uid, context=None):
return self._models_field_get(cr, uid, 'model', 'name', context)
return self._models_field_get(cr, uid, 'model', 'model_id.name', context)
def _models_get2(self, cr, uid, context=None):
return self._models_field_get(cr, uid, 'relation', 'relation', context)
@@ -50,7 +50,7 @@ class ir_property(osv.osv):
'name': fields.char('Name', size=128, select=1),
'res_id': fields.reference('Resource', selection=_models_get, size=128,
help="If not set, act as default property", select=1),
help="If not set, acts as a default value for new resources", select=1),
'company_id': fields.many2one('res.company', 'Company', select=1),
'fields_id': fields.many2one('ir.model.fields', 'Field', ondelete='cascade', required=True, select=1),
+1 -1
View File
@@ -5,7 +5,7 @@
<assert
model="res.currency.rate"
search="[('currency_id', '=', ref('INR'))]"
count="3"
count="1"
string="Rate entries for Indian rupee">
<test expr="True"/>
</assert>
+3 -3
View File
@@ -6,15 +6,15 @@
<!-- model="res.currency"-->
<!-- id="INR"-->
<!-- string="Indian rupee rate in 2009">-->
<!-- <!-- date specified in data element's context -->-->
<!-- date specified in data element's context -->
<!-- <test expr="str(rate)">65.8287</test>-->
<!-- </assert>-->
<!-- </assert> -->
<assert
model="res.currency"
id="INR"
string="Indian rupee rate in 2002"
context="{'date': '2010-06-01'}">
<test expr="str(rate)">58.8287</test>
<test expr="str(rate)">59.9739</test>
</assert>
</data>
</openerp>
+10 -2
View File
@@ -348,11 +348,19 @@ class Agent(object):
current_thread.dbname = dbname # hack hack
cls._logger.notifyChannel('timers', LOG_DEBUG, "Run %s.%s(*%r, **%r)" % (function.im_class.__name__, function.func_name, args, kwargs))
delattr(current_thread, 'dbname')
threading.Thread(target=function, args=args, kwargs=kwargs).start()
task_thread = threading.Thread(target=function, name='netsvc.Agent.task', args=args, kwargs=kwargs)
# force non-daemon task threads (the runner thread must be daemon, and this property is inherited by default)
task_thread.setDaemon(False)
task_thread.start()
time.sleep(1)
time.sleep(60)
threading.Thread(target=Agent.runner).start()
agent_runner = threading.Thread(target=Agent.runner, name="netsvc.Agent.runner")
# the agent runner is a typical daemon thread, that will never quit and must be
# terminated when the main process exits - with no consequence (the processing
# threads it spawns are not marked daemon)
agent_runner.setDaemon(True)
agent_runner.start()
import traceback
+57 -41
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
##############################################################################
#
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
@@ -16,7 +16,7 @@
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
@@ -27,16 +27,19 @@ OpenERP is an ERP+CRM program for small and medium businesses.
The whole source code is distributed under the terms of the
GNU Public Licence.
(c) 2003-TODAY, Fabien Pinckaers - Tiny sprl
(c) 2003-TODAY, Fabien Pinckaers - OpenERP s.a.
"""
#----------------------------------------------------------
# python imports
#----------------------------------------------------------
import sys
import logging
import os
import signal
import pwd
import signal
import sys
import threading
import traceback
import release
__author__ = release.author
@@ -52,23 +55,22 @@ if pwd.getpwuid(os.getuid())[0] == 'root' :
# get logger
#----------------------------------------------------------
import netsvc
logger = netsvc.Logger()
logger = logging.getLogger('server')
#-----------------------------------------------------------------------
# import the tools module so that the commandline parameters are parsed
#-----------------------------------------------------------------------
import tools
logger.notifyChannel("server", netsvc.LOG_INFO, "version - %s" % release.version )
logger.info("OpenERP version - %s", release.version)
for name, value in [('addons_path', tools.config['addons_path']),
('database hostname', tools.config['db_host'] or 'localhost'),
('database port', tools.config['db_port'] or '5432'),
('database user', tools.config['db_user'])]:
logger.notifyChannel("server", netsvc.LOG_INFO, "%s - %s" % ( name, value ))
logger.info("%s - %s", name, value)
# Don't allow if the connection to PostgreSQL done by postgres user
if tools.config['db_user'] == 'postgres':
logger.notifyChannel("server", netsvc.LOG_ERROR, "%s" % ("Attempted to connect database with postgres user. This is a security flaws, aborting."))
logger.error("Connecting to the database as 'postgres' user is forbidden, as it present major security issues. Shutting down.")
sys.exit(1)
import time
@@ -76,7 +78,7 @@ import time
#----------------------------------------------------------
# init net service
#----------------------------------------------------------
logger.notifyChannel("objects", netsvc.LOG_INFO, 'initialising distributed objects services')
logger.info('initialising distributed objects services')
#---------------------------------------------------------------
# connect to the database and initialize it with base if needed
@@ -117,8 +119,7 @@ if tools.config['db_name']:
for dbname in tools.config['db_name'].split(','):
db,pool = pooler.get_db_and_pool(dbname, update_module=tools.config['init'] or tools.config['update'], pooljobs=False)
if tools.config["test_file"]:
logger.notifyChannel("init", netsvc.LOG_INFO,
'loading test file %s' % (tools.config["test_file"],))
logger.info('loading test file %s', tools.config["test_file"])
cr = db.cursor()
tools.convert_yaml_import(cr, 'base', file(tools.config["test_file"]), {}, 'test', True)
cr.rollback()
@@ -134,16 +135,14 @@ if tools.config["translate_out"]:
msg = "language %s" % (tools.config["language"],)
else:
msg = "new language"
logger.notifyChannel("init", netsvc.LOG_INFO,
'writing translation file for %s to %s' % (msg,
tools.config["translate_out"]))
logger.info('writing translation file for %s to %s', msg, tools.config["translate_out"])
fileformat = os.path.splitext(tools.config["translate_out"])[-1][1:].lower()
buf = file(tools.config["translate_out"], "w")
tools.trans_export(tools.config["language"], tools.config["translate_modules"], buf, fileformat)
buf.close()
logger.notifyChannel("init", netsvc.LOG_INFO, 'translation file written successfully')
logger.info('translation file written successfully')
sys.exit(0)
if tools.config["translate_in"]:
@@ -169,30 +168,23 @@ SIGNALS = dict(
[(getattr(signal, sign), sign) for sign in LST_SIGNALS]
)
def handler(signum, _):
netsvc.quit_signals_received = 0
def handler(signum, frame):
"""
:param signum: the signal number
:param _:
:param frame: the interrupted stack frame or None
"""
netsvc.Agent.quit()
netsvc.Server.quitAll()
if tools.config['pidfile']:
os.unlink(tools.config['pidfile'])
logger.notifyChannel('shutdown', netsvc.LOG_INFO,
"Shutdown Server! - %s" % ( SIGNALS[signum], ))
logger.shutdown()
sys.exit(0)
netsvc.quit_signals_received += 1
if netsvc.quit_signals_received > 1:
sys.stderr.write("Forced shutdown.\n")
os._exit(0)
for signum in SIGNALS:
signal.signal(signum, handler)
import threading
import traceback
def dumpstacks(signum, _):
def dumpstacks(signum, frame):
# code from http://stackoverflow.com/questions/132058/getting-stack-trace-from-a-running-python-application#answer-2569696
id2name = dict([(th.ident, th.name) for th in threading.enumerate()])
# modified for python 2.5 compatibility
thread_map = dict(threading._active, **threading._limbo)
id2name = dict([(threadId, thread.getName()) for threadId, thread in thread_map.items()])
code = []
for threadId, stack in sys._current_frames().items():
code.append("\n# Thread: %s(%d)" % (id2name[threadId], threadId))
@@ -200,25 +192,49 @@ def dumpstacks(signum, _):
code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
if line:
code.append(" %s" % (line.strip()))
logging.getLogger('dumpstacks').info("\n".join(code))
logger.notifyChannel("dumpstacks", netsvc.LOG_INFO, "\n".join(code))
for signum in SIGNALS:
signal.signal(signum, handler)
if os.name == 'posix':
signal.signal(signal.SIGQUIT, dumpstacks)
def quit():
netsvc.Agent.quit()
netsvc.Server.quitAll()
if tools.config['pidfile']:
os.unlink(tools.config['pidfile'])
logger = logging.getLogger('shutdown')
logger.info("Initiating OpenERP Server shutdown")
logger.info("Hit CTRL-C again or send a second signal to immediately terminate the server...")
logging.shutdown()
# manually join() all threads before calling sys.exit() to allow a second signal
# to trigger _force_quit() in case some non-daemon threads won't exit cleanly.
# threading.Thread.join() should not mask signals (at least in python 2.5)
for thread in threading.enumerate():
if thread != threading.currentThread() and not thread.isDaemon():
while thread.isAlive():
# need a busyloop here as thread.join() masks signals
# and would present the forced shutdown
thread.join(0.05)
time.sleep(0.05)
sys.exit(0)
if tools.config['pidfile']:
fd = open(tools.config['pidfile'], 'w')
pidtext = "%d" % (os.getpid())
fd.write(pidtext)
fd.close()
netsvc.Server.startAll()
logger.notifyChannel("web-services", netsvc.LOG_INFO,
'the server is running, waiting for connections...')
logger.info('OpenERP server is running, waiting for connections...')
while True:
while netsvc.quit_signals_received == 0:
time.sleep(60)
quit()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+1 -1
View File
@@ -777,7 +777,7 @@ class related(function):
if not ids: return {}
relation = obj._name
if self._type in ('one2many', 'many2many'):
res = {}.fromkeys(ids, [])
res = dict([(i, []) for i in ids])
else:
res = {}.fromkeys(ids, False)
+3
View File
@@ -2275,6 +2275,9 @@ class orm(orm_template):
del r['id']
if groupby and fget[groupby]['type'] == 'many2one':
data_ids = self.search(cr, uid, [('id', 'in', alldata.keys())], order=groupby, context=context)
# the IDS of the records that has groupby field value = False or ''
# should be added too
data_ids += filter(lambda x:x not in data_ids, alldata.keys())
data = self.read(cr, uid, data_ids, groupby and [groupby] or ['id'], context=context)
# restore order of the search as read() uses the default _order (this is only for groups, so the size of data_read shoud be small):
data.sort(lambda x,y: cmp(data_ids.index(x['id']), data_ids.index(y['id'])))
+4 -4
View File
@@ -73,7 +73,7 @@ class report_custom(report_int):
for cond in conditions:
if cond and cond[0]:
c = cond[0]
temp = c[0](eval('obj.'+c[1]))
temp = c[0](eval('obj.'+c[1],{'obj': obj}))
if not eval('\''+temp+'\''+' '+c[2]+' '+'\''+str(c[3])+'\''):
tobreak = True
if tobreak:
@@ -87,7 +87,7 @@ class report_custom(report_int):
row_canvas[i]=False
elif len(fields[i])==1:
if not isinstance(obj, browse_null):
row.append(str(eval('obj.'+fields[i][0])))
row.append(str(eval('obj.'+fields[i][0],{'obj': obj})))
else:
row.append(None)
else:
@@ -107,7 +107,7 @@ class report_custom(report_int):
else:
key = levels.keys()
for l in key:
objs = eval('obj.'+l)
objs = eval('obj.'+l,{'obj': obj})
if not isinstance(objs, browse_record_list) and type(objs) <> type([]):
objs = [objs]
field_new = []
@@ -192,7 +192,7 @@ class report_custom(report_int):
def build_tree(obj, level, depth):
res = self._row_get(cr, uid,[obj], new_fields, new_cond)
level.append(depth)
new_obj = eval('obj.'+report['field_parent'][1])
new_obj = eval('obj.'+report['field_parent'][1],{'obj': obj})
if not isinstance(new_obj, list) :
new_obj = [new_obj]
for o in new_obj:
+1 -1
View File
@@ -137,7 +137,7 @@ class BaseHttpDaemon(threading.Thread, netsvc.Server):
_RealProto = '??'
def __init__(self, interface, port, handler):
threading.Thread.__init__(self)
threading.Thread.__init__(self, name='%sDaemon-%d'%(self._RealProto, port))
netsvc.Server.__init__(self)
self.__port = port
self.__interface = interface
+1 -1
View File
@@ -102,7 +102,7 @@ class TinySocketClientThread(threading.Thread, netsvc.OpenERPDispatcher):
class TinySocketServerThread(threading.Thread,netsvc.Server):
def __init__(self, interface, port, secure=False):
threading.Thread.__init__(self, name="Net-RPC socket")
threading.Thread.__init__(self, name="NetRPCDaemon-%d"%port)
netsvc.Server.__init__(self)
self.__port = port
self.__interface = interface
+26 -7
View File
@@ -419,6 +419,13 @@ def _email_send(smtp_from, smtp_to_list, message, openobject_id=None, ssl=False,
:return: True if the mail was delivered successfully to the smtp,
else False (+ exception logged)
"""
class WriteToLogger(object):
def __init__(self):
self.logger = netsvc.Logger()
def write(self, s):
self.logger.notifyChannel('email_send', netsvc.LOG_DEBUG, s)
if openobject_id:
message['Message-Id'] = generate_tracking_message_id(openobject_id)
@@ -534,13 +541,6 @@ def email_send(email_from, email_to, subject, body, email_cc=None, email_bcc=Non
part.add_header('Content-Disposition', 'attachment; filename="%s"' % (fname,))
msg.attach(part)
class WriteToLogger(object):
def __init__(self):
self.logger = netsvc.Logger()
def write(self, s):
self.logger.notifyChannel('email_send', netsvc.LOG_DEBUG, s)
return _email_send(email_from, flatten([email_to, email_cc, email_bcc]), msg, openobject_id=openobject_id, ssl=ssl, debug=debug)
#----------------------------------------------------------
@@ -1406,5 +1406,24 @@ def upload_data(email, data, type='SURVEY'):
a = upload_data_thread(email, data, type)
a.start()
return True
# port of python 2.6's attrgetter with support for dotted notation
def resolve_attr(obj, attr):
for name in attr.split("."):
obj = getattr(obj, name)
return obj
def attrgetter(*items):
if len(items) == 1:
attr = items[0]
def g(obj):
return resolve_attr(obj, attr)
else:
def g(obj):
return tuple(resolve_attr(obj, attr) for attr in items)
return g
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: