[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"?>
|
||||
<openerp>
|
||||
<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">
|
||||
<field name="name">ir.ui.menu.tree</field>
|
||||
<field name="model">ir.ui.menu</field>
|
||||
|
|
|
@ -236,15 +236,18 @@ class ir_mail_server(osv.osv):
|
|||
return connection
|
||||
|
||||
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.
|
||||
|
||||
:param string email_from: sender email address
|
||||
: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 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
|
||||
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 object_id: optional tracking identifier, to be included in the message-id for
|
||||
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'),
|
||||
must match the format of the ``body`` parameter. Default is '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
|
||||
containing the bytes of the attachment
|
||||
: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''
|
||||
|
||||
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()
|
||||
|
||||
if not message_id:
|
||||
|
@ -304,13 +309,21 @@ class ir_mail_server(osv.osv):
|
|||
for key, value in headers.iteritems():
|
||||
msg[ustr(key).encode('utf-8')] = encode_header(value)
|
||||
|
||||
if html2text and subtype == 'html':
|
||||
# Always provide alternative text body if possible.
|
||||
if subtype == 'html' and not body_alternative and html2text:
|
||||
# Always provide alternative text body ourselves if possible.
|
||||
text_utf8 = tools.html2text(email_body_utf8.decode('utf-8')).encode('utf-8')
|
||||
alternative_part = MIMEMultipart(_subtype="alternative")
|
||||
alternative_part.attach(MIMEText(text_utf8, _charset='utf-8', _subtype='plain'))
|
||||
alternative_part.attach(email_text_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:
|
||||
msg.attach(email_text_part)
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
import re
|
||||
import time
|
||||
import netsvc
|
||||
from osv import fields, osv
|
||||
|
@ -26,6 +27,8 @@ import tools
|
|||
from tools.misc import currency
|
||||
from tools.translate import _
|
||||
|
||||
CURRENCY_DISPLAY_PATTERN = re.compile(r'(\w+)\s*(?:\((.*)\))?')
|
||||
|
||||
class res_currency(osv.osv):
|
||||
def _current_rate(self, cr, uid, ids, name, arg, context=None):
|
||||
if context is None:
|
||||
|
@ -68,6 +71,8 @@ class res_currency(osv.osv):
|
|||
_defaults = {
|
||||
'active': lambda *a: 1,
|
||||
'position' : 'after',
|
||||
'rounding': 0.01,
|
||||
'accuracy': 4,
|
||||
}
|
||||
_sql_constraints = [
|
||||
# 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
|
||||
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):
|
||||
if not ids:
|
||||
return []
|
||||
|
|
|
@ -185,7 +185,7 @@ class res_partner(osv.osv):
|
|||
def name_get(self, cr, uid, ids, context={}):
|
||||
if not len(ids):
|
||||
return []
|
||||
if context.get('show_ref', False):
|
||||
if context and context.get('show_ref'):
|
||||
rec_name = 'ref'
|
||||
else:
|
||||
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):
|
||||
if not args:
|
||||
args=[]
|
||||
if not context:
|
||||
context={}
|
||||
if name:
|
||||
ids = self.search(cr, uid, [('ref', '=', name)] + args, limit=limit, context=context)
|
||||
if not ids:
|
||||
|
@ -312,6 +310,8 @@ class res_partner_address(osv.osv):
|
|||
}
|
||||
|
||||
def name_get(self, cr, user, ids, context={}):
|
||||
if context is None:
|
||||
context = {}
|
||||
if not len(ids):
|
||||
return []
|
||||
res = []
|
||||
|
|
|
@ -304,7 +304,8 @@ class browse_record(object):
|
|||
self._cr = cr
|
||||
self._uid = uid
|
||||
self._id = id
|
||||
self._table = table
|
||||
self._table = table # deprecated, use _model!
|
||||
self._model = table
|
||||
self._table_name = self._table._name
|
||||
self.__logger = logging.getLogger(
|
||||
'osv.browse_record.' + self._table_name)
|
||||
|
@ -816,13 +817,16 @@ class BaseModel(object):
|
|||
raise TypeError('_name is mandatory in case of multiple inheritance')
|
||||
|
||||
for parent_name in ((type(parent_names)==list) and parent_names or [parent_names]):
|
||||
parent_class = pool.get(parent_name).__class__
|
||||
if not pool.get(parent_name):
|
||||
parent_model = 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'
|
||||
'You may need to add a dependency on the parent class\' module.' % (name, parent_name))
|
||||
parent_class = parent_model.__class__
|
||||
nattr = {}
|
||||
for s in attributes:
|
||||
new = copy.copy(getattr(pool.get(parent_name), s, {}))
|
||||
new = copy.copy(getattr(parent_model, s, {}))
|
||||
if s == '_columns':
|
||||
# Don't _inherit custom fields.
|
||||
for c in new.keys():
|
||||
|
@ -850,6 +854,8 @@ class BaseModel(object):
|
|||
new.extend(cls.__dict__.get(s, []))
|
||||
nattr[s] = new
|
||||
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.__init__(pool, cr)
|
||||
return obj
|
||||
|
@ -1045,6 +1051,7 @@ class BaseModel(object):
|
|||
'name': n,
|
||||
'model': self._name,
|
||||
'res_id': r['id'],
|
||||
'module': '__export__',
|
||||
})
|
||||
r = n
|
||||
else:
|
||||
|
@ -4612,17 +4619,21 @@ class BaseModel(object):
|
|||
return False
|
||||
return True
|
||||
|
||||
def _get_xml_ids(self, cr, uid, ids, *args, **kwargs):
|
||||
"""Find out the XML ID(s) of any database record.
|
||||
def _get_external_ids(self, cr, uid, ids, *args, **kwargs):
|
||||
"""Retrieve the External ID(s) of any database record.
|
||||
|
||||
**Synopsis**: ``_get_xml_ids(cr, uid, ids) -> { 'id': ['module.xml_id'] }``
|
||||
|
||||
:return: map of ids to the list of their fully qualified XML IDs
|
||||
(empty list when there's none).
|
||||
:return: map of ids to the list of their fully qualified External IDs
|
||||
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')
|
||||
data_ids = model_data_obj.search(cr, uid, [('model', '=', self._name), ('res_id', 'in', ids)])
|
||||
data_results = model_data_obj.read(cr, uid, data_ids, ['module', 'name', 'res_id'])
|
||||
ir_model_data = self.pool.get('ir.model.data')
|
||||
data_ids = ir_model_data.search(cr, uid, [('model', '=', self._name), ('res_id', 'in', ids)])
|
||||
data_results = ir_model_data.read(cr, uid, data_ids, ['module', 'name', 'res_id'])
|
||||
result = {}
|
||||
for id in ids:
|
||||
# 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)
|
||||
return result
|
||||
|
||||
def get_xml_id(self, cr, uid, ids, *args, **kwargs):
|
||||
"""Find out the XML ID of any database record, if there
|
||||
def get_external_id(self, cr, uid, ids, *args, **kwargs):
|
||||
"""Retrieve the External ID of any database record, if there
|
||||
is one. This method works as a possible implementation
|
||||
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).
|
||||
|
||||
**Synopsis**: ``get_xml_id(cr, uid, ids) -> { 'id': 'module.xml_id' }``
|
||||
|
||||
:return: map of ids to their fully qualified XML ID,
|
||||
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)
|
||||
for k, v in results.items():
|
||||
for k, v in results.iteritems():
|
||||
if results[k]:
|
||||
results[k] = v[0]
|
||||
else:
|
||||
results[k] = ''
|
||||
return results
|
||||
|
||||
# backwards compatibility
|
||||
get_xml_id = get_external_id
|
||||
_get_xml_ids = _get_external_ids
|
||||
|
||||
# Transience
|
||||
def is_transient(self):
|
||||
""" 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',
|
||||
# New in Python 2.7 - http://bugs.python.org/issue4715 :
|
||||
'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))
|
||||
|
||||
_logger = logging.getLogger('safe_eval')
|
||||
|
|
|
@ -340,6 +340,7 @@ class YamlInterpreter(object):
|
|||
return record_dict
|
||||
|
||||
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.model:
|
||||
model_name = node.model
|
||||
|
@ -377,7 +378,10 @@ class YamlInterpreter(object):
|
|||
if column._type in ("many2many", "one2many"):
|
||||
value = [(6, 0, elements)]
|
||||
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":
|
||||
value = self.get_id(expression)
|
||||
elif column._type == "one2many":
|
||||
|
|
|
@ -60,7 +60,7 @@ def _eval_expr(cr, ident, workitem, action):
|
|||
|
||||
def execute_action(cr, ident, workitem, activity):
|
||||
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)
|
||||
return result
|
||||
|
||||
|
|
Loading…
Reference in New Issue