[MERGE] various bugfixes/improvements (coming from EDI development)
- _original_module is now available on model/browse_records - context usage in res.partner.* - proper name_search() + default values for res.currency - active_model in wkf exec context - safe_eval allows try/except/finally - yaml_import: !ref {id: xml_id} works - ir_mail_server: support for alternative body/subtype - default value for web.base.url config parameter - consistency rename: Model.*get_xml_id* -> *get_external_id* bzr revid: odo@openerp.com-20111005100954-c8mbd4kz6kkqaj84
This commit is contained in:
commit
d13fb7fe7e
|
@ -1,6 +1,10 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<openerp>
|
<openerp>
|
||||||
<data noupdate="1">
|
<data noupdate="1">
|
||||||
|
<record id="parameter_web_base_url" model="ir.config_parameter">
|
||||||
|
<field name="key">web.base.url</field>
|
||||||
|
<field name="value">http://localhost:8069</field>
|
||||||
|
</record>
|
||||||
<record id="view_menu" model="ir.ui.view">
|
<record id="view_menu" model="ir.ui.view">
|
||||||
<field name="name">ir.ui.menu.tree</field>
|
<field name="name">ir.ui.menu.tree</field>
|
||||||
<field name="model">ir.ui.menu</field>
|
<field name="model">ir.ui.menu</field>
|
||||||
|
|
|
@ -236,15 +236,18 @@ class ir_mail_server(osv.osv):
|
||||||
return connection
|
return connection
|
||||||
|
|
||||||
def build_email(self, email_from, email_to, subject, body, email_cc=None, email_bcc=None, reply_to=False,
|
def build_email(self, email_from, email_to, subject, body, email_cc=None, email_bcc=None, reply_to=False,
|
||||||
attachments=None, message_id=None, references=None, object_id=False, subtype='plain', headers=None):
|
attachments=None, message_id=None, references=None, object_id=False, subtype='plain', headers=None,
|
||||||
|
body_alternative=None, subtype_alternative='plain'):
|
||||||
"""Constructs an RFC2822 email.message.Message object based on the keyword arguments passed, and returns it.
|
"""Constructs an RFC2822 email.message.Message object based on the keyword arguments passed, and returns it.
|
||||||
|
|
||||||
:param string email_from: sender email address
|
:param string email_from: sender email address
|
||||||
:param list email_to: list of recipient addresses (to be joined with commas)
|
:param list email_to: list of recipient addresses (to be joined with commas)
|
||||||
:param string subject: email subject (no pre-encoding/quoting necessary)
|
:param string subject: email subject (no pre-encoding/quoting necessary)
|
||||||
:param string body: email body, according to the ``subtype`` (by default, plaintext).
|
:param string body: email body, of the type ``subtype`` (by default, plaintext).
|
||||||
If html subtype is used, the message will be automatically converted
|
If html subtype is used, the message will be automatically converted
|
||||||
to plaintext and wrapped in multipart/alternative.
|
to plaintext and wrapped in multipart/alternative, unless an explicit
|
||||||
|
``body_alternative`` version is passed.
|
||||||
|
:param string body_alternative: optional alternative body, of the type specified in ``subtype_alternative``
|
||||||
:param string reply_to: optional value of Reply-To header
|
:param string reply_to: optional value of Reply-To header
|
||||||
:param string object_id: optional tracking identifier, to be included in the message-id for
|
:param string object_id: optional tracking identifier, to be included in the message-id for
|
||||||
recognizing replies. Suggested format for object-id is "res_id-model",
|
recognizing replies. Suggested format for object-id is "res_id-model",
|
||||||
|
@ -252,6 +255,8 @@ class ir_mail_server(osv.osv):
|
||||||
:param string subtype: optional mime subtype for the text body (usually 'plain' or 'html'),
|
:param string subtype: optional mime subtype for the text body (usually 'plain' or 'html'),
|
||||||
must match the format of the ``body`` parameter. Default is 'plain',
|
must match the format of the ``body`` parameter. Default is 'plain',
|
||||||
making the content part of the mail "text/plain".
|
making the content part of the mail "text/plain".
|
||||||
|
:param string subtype_alternative: optional mime subtype of ``body_alternative`` (usually 'plain'
|
||||||
|
or 'html'). Default is 'plain'.
|
||||||
:param list attachments: list of (filename, filecontents) pairs, where filecontents is a string
|
:param list attachments: list of (filename, filecontents) pairs, where filecontents is a string
|
||||||
containing the bytes of the attachment
|
containing the bytes of the attachment
|
||||||
:param list email_cc: optional list of string values for CC header (to be joined with commas)
|
:param list email_cc: optional list of string values for CC header (to be joined with commas)
|
||||||
|
@ -276,7 +281,7 @@ class ir_mail_server(osv.osv):
|
||||||
if not body: body = u''
|
if not body: body = u''
|
||||||
|
|
||||||
email_body_utf8 = ustr(body).encode('utf-8')
|
email_body_utf8 = ustr(body).encode('utf-8')
|
||||||
email_text_part = MIMEText(email_body_utf8 or '', _subtype=subtype, _charset='utf-8')
|
email_text_part = MIMEText(email_body_utf8, _subtype=subtype, _charset='utf-8')
|
||||||
msg = MIMEMultipart()
|
msg = MIMEMultipart()
|
||||||
|
|
||||||
if not message_id:
|
if not message_id:
|
||||||
|
@ -304,13 +309,21 @@ class ir_mail_server(osv.osv):
|
||||||
for key, value in headers.iteritems():
|
for key, value in headers.iteritems():
|
||||||
msg[ustr(key).encode('utf-8')] = encode_header(value)
|
msg[ustr(key).encode('utf-8')] = encode_header(value)
|
||||||
|
|
||||||
if html2text and subtype == 'html':
|
if subtype == 'html' and not body_alternative and html2text:
|
||||||
# Always provide alternative text body if possible.
|
# Always provide alternative text body ourselves if possible.
|
||||||
text_utf8 = tools.html2text(email_body_utf8.decode('utf-8')).encode('utf-8')
|
text_utf8 = tools.html2text(email_body_utf8.decode('utf-8')).encode('utf-8')
|
||||||
alternative_part = MIMEMultipart(_subtype="alternative")
|
alternative_part = MIMEMultipart(_subtype="alternative")
|
||||||
alternative_part.attach(MIMEText(text_utf8, _charset='utf-8', _subtype='plain'))
|
alternative_part.attach(MIMEText(text_utf8, _charset='utf-8', _subtype='plain'))
|
||||||
alternative_part.attach(email_text_part)
|
alternative_part.attach(email_text_part)
|
||||||
msg.attach(alternative_part)
|
msg.attach(alternative_part)
|
||||||
|
elif body_alternative:
|
||||||
|
# Include both alternatives, as specified, within a multipart/alternative part
|
||||||
|
alternative_part = MIMEMultipart(_subtype="alternative")
|
||||||
|
body_alternative_utf8 = ustr(body_alternative).encode('utf-8')
|
||||||
|
alternative_body_part = MIMEText(body_alternative_utf8, _subtype=subtype_alternative, _charset='utf-8')
|
||||||
|
alternative_part.attach(alternative_body_part)
|
||||||
|
alternative_part.attach(email_text_part)
|
||||||
|
msg.attach(alternative_part)
|
||||||
else:
|
else:
|
||||||
msg.attach(email_text_part)
|
msg.attach(email_text_part)
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
import re
|
||||||
import time
|
import time
|
||||||
import netsvc
|
import netsvc
|
||||||
from osv import fields, osv
|
from osv import fields, osv
|
||||||
|
@ -26,6 +27,8 @@ import tools
|
||||||
from tools.misc import currency
|
from tools.misc import currency
|
||||||
from tools.translate import _
|
from tools.translate import _
|
||||||
|
|
||||||
|
CURRENCY_DISPLAY_PATTERN = re.compile(r'(\w+)\s*(?:\((.*)\))?')
|
||||||
|
|
||||||
class res_currency(osv.osv):
|
class res_currency(osv.osv):
|
||||||
def _current_rate(self, cr, uid, ids, name, arg, context=None):
|
def _current_rate(self, cr, uid, ids, name, arg, context=None):
|
||||||
if context is None:
|
if context is None:
|
||||||
|
@ -68,6 +71,8 @@ class res_currency(osv.osv):
|
||||||
_defaults = {
|
_defaults = {
|
||||||
'active': lambda *a: 1,
|
'active': lambda *a: 1,
|
||||||
'position' : 'after',
|
'position' : 'after',
|
||||||
|
'rounding': 0.01,
|
||||||
|
'accuracy': 4,
|
||||||
}
|
}
|
||||||
_sql_constraints = [
|
_sql_constraints = [
|
||||||
# this constraint does not cover all cases due to SQL NULL handling for company_id,
|
# this constraint does not cover all cases due to SQL NULL handling for company_id,
|
||||||
|
@ -101,6 +106,18 @@ class res_currency(osv.osv):
|
||||||
r['date'] = currency_date
|
r['date'] = currency_date
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
|
||||||
|
if not args:
|
||||||
|
args = []
|
||||||
|
if name:
|
||||||
|
ids = self.search(cr, user, ([('name','=',name)] + args), limit=limit, context=context)
|
||||||
|
name_match = CURRENCY_DISPLAY_PATTERN.match(name)
|
||||||
|
if not ids and name_match:
|
||||||
|
ids = self.search(cr, user, [('name','=', name_match.group(1))] + args, limit=limit, context=context)
|
||||||
|
else:
|
||||||
|
ids = self.search(cr, user, args, limit=limit, context=context)
|
||||||
|
return self.name_get(cr, user, ids, context=context)
|
||||||
|
|
||||||
def name_get(self, cr, uid, ids, context=None):
|
def name_get(self, cr, uid, ids, context=None):
|
||||||
if not ids:
|
if not ids:
|
||||||
return []
|
return []
|
||||||
|
|
|
@ -185,7 +185,7 @@ class res_partner(osv.osv):
|
||||||
def name_get(self, cr, uid, ids, context={}):
|
def name_get(self, cr, uid, ids, context={}):
|
||||||
if not len(ids):
|
if not len(ids):
|
||||||
return []
|
return []
|
||||||
if context.get('show_ref', False):
|
if context and context.get('show_ref'):
|
||||||
rec_name = 'ref'
|
rec_name = 'ref'
|
||||||
else:
|
else:
|
||||||
rec_name = 'name'
|
rec_name = 'name'
|
||||||
|
@ -196,8 +196,6 @@ class res_partner(osv.osv):
|
||||||
def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100):
|
def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100):
|
||||||
if not args:
|
if not args:
|
||||||
args=[]
|
args=[]
|
||||||
if not context:
|
|
||||||
context={}
|
|
||||||
if name:
|
if name:
|
||||||
ids = self.search(cr, uid, [('ref', '=', name)] + args, limit=limit, context=context)
|
ids = self.search(cr, uid, [('ref', '=', name)] + args, limit=limit, context=context)
|
||||||
if not ids:
|
if not ids:
|
||||||
|
@ -312,6 +310,8 @@ class res_partner_address(osv.osv):
|
||||||
}
|
}
|
||||||
|
|
||||||
def name_get(self, cr, user, ids, context={}):
|
def name_get(self, cr, user, ids, context={}):
|
||||||
|
if context is None:
|
||||||
|
context = {}
|
||||||
if not len(ids):
|
if not len(ids):
|
||||||
return []
|
return []
|
||||||
res = []
|
res = []
|
||||||
|
|
|
@ -304,7 +304,8 @@ class browse_record(object):
|
||||||
self._cr = cr
|
self._cr = cr
|
||||||
self._uid = uid
|
self._uid = uid
|
||||||
self._id = id
|
self._id = id
|
||||||
self._table = table
|
self._table = table # deprecated, use _model!
|
||||||
|
self._model = table
|
||||||
self._table_name = self._table._name
|
self._table_name = self._table._name
|
||||||
self.__logger = logging.getLogger(
|
self.__logger = logging.getLogger(
|
||||||
'osv.browse_record.' + self._table_name)
|
'osv.browse_record.' + self._table_name)
|
||||||
|
@ -816,13 +817,16 @@ class BaseModel(object):
|
||||||
raise TypeError('_name is mandatory in case of multiple inheritance')
|
raise TypeError('_name is mandatory in case of multiple inheritance')
|
||||||
|
|
||||||
for parent_name in ((type(parent_names)==list) and parent_names or [parent_names]):
|
for parent_name in ((type(parent_names)==list) and parent_names or [parent_names]):
|
||||||
parent_class = pool.get(parent_name).__class__
|
parent_model = pool.get(parent_name)
|
||||||
if not pool.get(parent_name):
|
if not getattr(cls, '_original_module', None) and name == parent_model._name:
|
||||||
|
cls._original_module = parent_model._original_module
|
||||||
|
if not parent_model:
|
||||||
raise TypeError('The model "%s" specifies an unexisting parent class "%s"\n'
|
raise TypeError('The model "%s" specifies an unexisting parent class "%s"\n'
|
||||||
'You may need to add a dependency on the parent class\' module.' % (name, parent_name))
|
'You may need to add a dependency on the parent class\' module.' % (name, parent_name))
|
||||||
|
parent_class = parent_model.__class__
|
||||||
nattr = {}
|
nattr = {}
|
||||||
for s in attributes:
|
for s in attributes:
|
||||||
new = copy.copy(getattr(pool.get(parent_name), s, {}))
|
new = copy.copy(getattr(parent_model, s, {}))
|
||||||
if s == '_columns':
|
if s == '_columns':
|
||||||
# Don't _inherit custom fields.
|
# Don't _inherit custom fields.
|
||||||
for c in new.keys():
|
for c in new.keys():
|
||||||
|
@ -850,6 +854,8 @@ class BaseModel(object):
|
||||||
new.extend(cls.__dict__.get(s, []))
|
new.extend(cls.__dict__.get(s, []))
|
||||||
nattr[s] = new
|
nattr[s] = new
|
||||||
cls = type(name, (cls, parent_class), dict(nattr, _register=False))
|
cls = type(name, (cls, parent_class), dict(nattr, _register=False))
|
||||||
|
if not getattr(cls, '_original_module', None):
|
||||||
|
cls._original_module = cls._module
|
||||||
obj = object.__new__(cls)
|
obj = object.__new__(cls)
|
||||||
obj.__init__(pool, cr)
|
obj.__init__(pool, cr)
|
||||||
return obj
|
return obj
|
||||||
|
@ -1045,6 +1051,7 @@ class BaseModel(object):
|
||||||
'name': n,
|
'name': n,
|
||||||
'model': self._name,
|
'model': self._name,
|
||||||
'res_id': r['id'],
|
'res_id': r['id'],
|
||||||
|
'module': '__export__',
|
||||||
})
|
})
|
||||||
r = n
|
r = n
|
||||||
else:
|
else:
|
||||||
|
@ -4612,17 +4619,21 @@ class BaseModel(object):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _get_xml_ids(self, cr, uid, ids, *args, **kwargs):
|
def _get_external_ids(self, cr, uid, ids, *args, **kwargs):
|
||||||
"""Find out the XML ID(s) of any database record.
|
"""Retrieve the External ID(s) of any database record.
|
||||||
|
|
||||||
**Synopsis**: ``_get_xml_ids(cr, uid, ids) -> { 'id': ['module.xml_id'] }``
|
**Synopsis**: ``_get_xml_ids(cr, uid, ids) -> { 'id': ['module.xml_id'] }``
|
||||||
|
|
||||||
:return: map of ids to the list of their fully qualified XML IDs
|
:return: map of ids to the list of their fully qualified External IDs
|
||||||
(empty list when there's none).
|
in the form ``module.key``, or an empty list when there's no External
|
||||||
|
ID for a record, e.g.::
|
||||||
|
|
||||||
|
{ 'id': ['module.ext_id', 'module.ext_id_bis'],
|
||||||
|
'id2': [] }
|
||||||
"""
|
"""
|
||||||
model_data_obj = self.pool.get('ir.model.data')
|
ir_model_data = self.pool.get('ir.model.data')
|
||||||
data_ids = model_data_obj.search(cr, uid, [('model', '=', self._name), ('res_id', 'in', ids)])
|
data_ids = ir_model_data.search(cr, uid, [('model', '=', self._name), ('res_id', 'in', ids)])
|
||||||
data_results = model_data_obj.read(cr, uid, data_ids, ['module', 'name', 'res_id'])
|
data_results = ir_model_data.read(cr, uid, data_ids, ['module', 'name', 'res_id'])
|
||||||
result = {}
|
result = {}
|
||||||
for id in ids:
|
for id in ids:
|
||||||
# can't use dict.fromkeys() as the list would be shared!
|
# can't use dict.fromkeys() as the list would be shared!
|
||||||
|
@ -4631,29 +4642,35 @@ class BaseModel(object):
|
||||||
result[record['res_id']].append('%(module)s.%(name)s' % record)
|
result[record['res_id']].append('%(module)s.%(name)s' % record)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_xml_id(self, cr, uid, ids, *args, **kwargs):
|
def get_external_id(self, cr, uid, ids, *args, **kwargs):
|
||||||
"""Find out the XML ID of any database record, if there
|
"""Retrieve the External ID of any database record, if there
|
||||||
is one. This method works as a possible implementation
|
is one. This method works as a possible implementation
|
||||||
for a function field, to be able to add it to any
|
for a function field, to be able to add it to any
|
||||||
model object easily, referencing it as ``osv.osv.get_xml_id``.
|
model object easily, referencing it as ``Model.get_external_id``.
|
||||||
|
|
||||||
When multiple XML IDs exist for a record, only one
|
When multiple External IDs exist for a record, only one
|
||||||
of them is returned (randomly).
|
of them is returned (randomly).
|
||||||
|
|
||||||
**Synopsis**: ``get_xml_id(cr, uid, ids) -> { 'id': 'module.xml_id' }``
|
|
||||||
|
|
||||||
:return: map of ids to their fully qualified XML ID,
|
:return: map of ids to their fully qualified XML ID,
|
||||||
defaulting to an empty string when there's none
|
defaulting to an empty string when there's none
|
||||||
(to be usable as a function field).
|
(to be usable as a function field),
|
||||||
|
e.g.::
|
||||||
|
|
||||||
|
{ 'id': 'module.ext_id',
|
||||||
|
'id2': '' }
|
||||||
"""
|
"""
|
||||||
results = self._get_xml_ids(cr, uid, ids)
|
results = self._get_xml_ids(cr, uid, ids)
|
||||||
for k, v in results.items():
|
for k, v in results.iteritems():
|
||||||
if results[k]:
|
if results[k]:
|
||||||
results[k] = v[0]
|
results[k] = v[0]
|
||||||
else:
|
else:
|
||||||
results[k] = ''
|
results[k] = ''
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
# backwards compatibility
|
||||||
|
get_xml_id = get_external_id
|
||||||
|
_get_xml_ids = _get_external_ids
|
||||||
|
|
||||||
# Transience
|
# Transience
|
||||||
def is_transient(self):
|
def is_transient(self):
|
||||||
""" Return whether the model is transient.
|
""" Return whether the model is transient.
|
||||||
|
|
|
@ -64,7 +64,7 @@ _SAFE_OPCODES = _EXPR_OPCODES.union(set(opmap[x] for x in [
|
||||||
'MAKE_FUNCTION', 'SLICE+0', 'SLICE+1', 'SLICE+2', 'SLICE+3',
|
'MAKE_FUNCTION', 'SLICE+0', 'SLICE+1', 'SLICE+2', 'SLICE+3',
|
||||||
# New in Python 2.7 - http://bugs.python.org/issue4715 :
|
# New in Python 2.7 - http://bugs.python.org/issue4715 :
|
||||||
'JUMP_IF_FALSE_OR_POP', 'JUMP_IF_TRUE_OR_POP', 'POP_JUMP_IF_FALSE',
|
'JUMP_IF_FALSE_OR_POP', 'JUMP_IF_TRUE_OR_POP', 'POP_JUMP_IF_FALSE',
|
||||||
'POP_JUMP_IF_TRUE'
|
'POP_JUMP_IF_TRUE', 'SETUP_EXCEPT', 'END_FINALLY'
|
||||||
] if x in opmap))
|
] if x in opmap))
|
||||||
|
|
||||||
_logger = logging.getLogger('safe_eval')
|
_logger = logging.getLogger('safe_eval')
|
||||||
|
|
|
@ -340,6 +340,7 @@ class YamlInterpreter(object):
|
||||||
return record_dict
|
return record_dict
|
||||||
|
|
||||||
def process_ref(self, node, column=None):
|
def process_ref(self, node, column=None):
|
||||||
|
assert node.search or node.id, '!ref node should have a `search` attribute or `id` attribute'
|
||||||
if node.search:
|
if node.search:
|
||||||
if node.model:
|
if node.model:
|
||||||
model_name = node.model
|
model_name = node.model
|
||||||
|
@ -377,7 +378,10 @@ class YamlInterpreter(object):
|
||||||
if column._type in ("many2many", "one2many"):
|
if column._type in ("many2many", "one2many"):
|
||||||
value = [(6, 0, elements)]
|
value = [(6, 0, elements)]
|
||||||
else: # many2one
|
else: # many2one
|
||||||
value = self._get_first_result(elements)
|
if isinstance(elements, (list,tuple)):
|
||||||
|
value = self._get_first_result(elements)
|
||||||
|
else:
|
||||||
|
value = elements
|
||||||
elif column._type == "many2one":
|
elif column._type == "many2one":
|
||||||
value = self.get_id(expression)
|
value = self.get_id(expression)
|
||||||
elif column._type == "one2many":
|
elif column._type == "one2many":
|
||||||
|
|
|
@ -60,7 +60,7 @@ def _eval_expr(cr, ident, workitem, action):
|
||||||
|
|
||||||
def execute_action(cr, ident, workitem, activity):
|
def execute_action(cr, ident, workitem, activity):
|
||||||
obj = pooler.get_pool(cr.dbname).get('ir.actions.server')
|
obj = pooler.get_pool(cr.dbname).get('ir.actions.server')
|
||||||
ctx = {'active_id':ident[2], 'active_ids':[ident[2]]}
|
ctx = {'active_model':ident[1], 'active_id':ident[2], 'active_ids':[ident[2]]}
|
||||||
result = obj.run(cr, ident[0], [activity['action_id']], ctx)
|
result = obj.run(cr, ident[0], [activity['action_id']], ctx)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue