From 6037f3a56d3992cf0a6ff5ea1cb374dcbac03ea0 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Wed, 23 Mar 2016 13:48:43 +0100 Subject: [PATCH] [REF] fields: change API and semantics of converter methods The cache data format has been split between a cache format and a record format (returned by accessing a field on a record). We have the following converters: ANY FORMAT | | convert_to_cache v CACHE FORMAT | | convert_to_record v RECORD FORMAT | | convert_to_read | convert_to_write | convert_to_onchange | convert_to_export | convert_to_display_name v OTHER FORMATS Modify the API of the converter methods to make them uniform (pass value and record), and adapt their documentation. --- .../wizard/event_edit_registration.py | 1 - addons/mail/wizard/mail_compose_message.py | 2 +- openerp/addons/base/ir/ir_values.py | 2 +- openerp/fields.py | 169 +++++++++--------- openerp/models.py | 22 +-- 5 files changed, 98 insertions(+), 98 deletions(-) diff --git a/addons/event_sale/wizard/event_edit_registration.py b/addons/event_sale/wizard/event_edit_registration.py index 98f6553f5a6..989737405b8 100644 --- a/addons/event_sale/wizard/event_edit_registration.py +++ b/addons/event_sale/wizard/event_edit_registration.py @@ -41,7 +41,6 @@ class SaleOrderEventRegistration(models.TransientModel): 'sale_order_line_id': so_line.id, }]) res['event_registration_ids'] = attendee_list - res = self._convert_to_cache(res, validate=False) res = self._convert_to_write(res) return res diff --git a/addons/mail/wizard/mail_compose_message.py b/addons/mail/wizard/mail_compose_message.py index 34d59736185..6537d6d1cf6 100644 --- a/addons/mail/wizard/mail_compose_message.py +++ b/addons/mail/wizard/mail_compose_message.py @@ -398,7 +398,7 @@ class MailComposer(models.TransientModel): # ORM handle the assignation of command list on new onchange (api.v8), # this force the complete replacement of x2many field with # command and is compatible with onchange api.v7 - values = self._convert_to_write(self._convert_to_cache(values)) + values = self._convert_to_write(values) return {'value': values} diff --git a/openerp/addons/base/ir/ir_values.py b/openerp/addons/base/ir/ir_values.py index 3af0f92249f..ce9f7328ede 100644 --- a/openerp/addons/base/ir/ir_values.py +++ b/openerp/addons/base/ir/ir_values.py @@ -402,7 +402,7 @@ class IrValues(models.Model): # FIXME: needs cleanup try: action_def = { - field: action._fields[field].convert_to_read(action[field]) + field: action._fields[field].convert_to_read(action[field], action) for field in fields } if action._name in ('ir.actions.report.xml', 'ir.actions.act_window'): diff --git a/openerp/fields.py b/openerp/fields.py index 4d59bad64bf..63e2f2c3df4 100644 --- a/openerp/fields.py +++ b/openerp/fields.py @@ -81,7 +81,7 @@ def default_new_to_old(field, value): """ Convert the new-API default ``value`` to the old API. """ if callable(value): from openerp import api - return api.model(lambda model: field.convert_to_write(value(model))) + return api.model(lambda model: field.convert_to_write(value(model), model)) else: return value @@ -758,57 +758,56 @@ class Field(object): # Conversion of values # - def null(self, env): - """ return the null value for this field in the given environment """ + def null(self, record): + """ Return the null value for this field in the record format. """ return False def convert_to_cache(self, value, record, validate=True): - """ convert ``value`` to the cache level in ``env``; ``value`` may come from - an assignment, or have the format of methods :meth:`BaseModel.read` - or :meth:`BaseModel.write` + """ Convert ``value`` to the cache format; ``value`` may come from an + assignment, or have the format of methods :meth:`BaseModel.read` or + :meth:`BaseModel.write`. - :param record: the target record for the assignment, or an empty recordset - - :param bool validate: when True, field-specific validation of - ``value`` will be performed + :param bool validate: when True, field-specific validation of ``value`` + will be performed """ return value - def convert_to_read(self, value, use_name_get=True): - """ convert ``value`` from the cache to a value as returned by method - :meth:`BaseModel.read` + def convert_to_record(self, value, record): + """ Convert ``value`` from the cache format to the record format. """ + return value - :param bool use_name_get: when True, value's diplay name will - be computed using :meth:`BaseModel.name_get`, if relevant - for the field + def convert_to_read(self, value, record, use_name_get=True): + """ Convert ``value`` from the record format to the format returned by + method :meth:`BaseModel.read`. + + :param bool use_name_get: when True, the value's display name will be + computed using :meth:`BaseModel.name_get`, if relevant for the field """ return False if value is None else value - def convert_to_write(self, value): - """ convert ``value`` from the cache to a valid value for method - :meth:`BaseModel.write`. + def convert_to_write(self, value, record): + """ Convert ``value`` from the record format to the format of method + :meth:`BaseModel.write`. """ - return self.convert_to_read(value) + return self.convert_to_read(value, record) - def convert_to_onchange(self, value, fnames=None): - """ convert ``value`` from the cache to a value as returned by method - :meth:`BaseModel.onchange`. + def convert_to_onchange(self, value, record, fnames=()): + """ Convert ``value`` from the record format to the format returned by + method :meth:`BaseModel.onchange`. - :param fnames: an optional collection of field names to convert - (for relational fields only) + :param fnames: an optional collection of field names to convert + (for relational fields only) """ - return self.convert_to_read(value) + return self.convert_to_read(value, record) - def convert_to_export(self, value, env): - """ convert ``value`` from the cache to a valid value for export. The - parameter ``env`` is given for managing translations. - """ + def convert_to_export(self, value, record): + """ Convert ``value`` from the record format to the export format. """ if not value: return '' - return value if env.context.get('export_raw_data') else ustr(value) + return value if record._context.get('export_raw_data') else ustr(value) - def convert_to_display_name(self, value, record=None): - """ convert ``value`` from the cache to a suitable display name. """ + def convert_to_display_name(self, value, record): + """ Convert ``value`` from the record format to a suitable display name. """ return ustr(value) ############################################################################ @@ -823,26 +822,22 @@ class Field(object): if not record: # null record -> return the null value for this field - return self.null(record.env) + return self.null(record) # only a single record may be accessed record.ensure_one() try: - return record._cache[self] + value = record._cache[self] except KeyError: - pass + # cache miss, determine value and retrieve it + if record.id: + self.determine_value(record) + else: + self.determine_draft_value(record) + value = record._cache[self] - # cache miss, retrieve value - if record.id: - # normal record -> read or compute value for this field - self.determine_value(record) - else: - # draft record -> compute the value or let it be null - self.determine_draft_value(record) - - # the result should be in cache now - return record._cache[self] + return self.convert_to_record(value, record) def __set__(self, record, value): """ set the value of field ``self`` on ``record`` """ @@ -872,7 +867,8 @@ class Field(object): else: # simply write to the database, and update cache - record.write({self.name: self.convert_to_write(value)}) + write_value = self.convert_to_write(self.convert_to_record(value, record), record) + record.write({self.name: write_value}) record._cache[self] = value ############################################################################ @@ -885,7 +881,8 @@ class Field(object): # initialize the fields to their corresponding null value in cache computed = records._field_computed[self] for field in computed: - records._cache[field] = field.null(records.env) + for record in records: + record._cache[field] = field.convert_to_cache(False, record, validate=False) records.env.computed[field].update(records._ids) if isinstance(self.compute, basestring): getattr(records, self.compute)() @@ -947,14 +944,15 @@ class Field(object): else: # this is a non-stored non-computed field - record._cache[self] = self.null(env) + record._cache[self] = self.convert_to_cache(False, record, validate=False) def determine_draft_value(self, record): """ Determine the value of ``self`` for the given draft ``record``. """ if self.compute: self._compute_value(record) else: - record._cache[self] = SpecialValue(self.null(record.env)) + null = self.convert_to_cache(False, record, validate=False) + record._cache[self] = SpecialValue(null) def determine_inverse(self, records): """ Given the value of ``self`` on ``records``, inverse the computation. """ @@ -1047,8 +1045,8 @@ class Boolean(Field): def convert_to_cache(self, value, record, validate=True): return bool(value) - def convert_to_export(self, value, env): - if env.context.get('export_raw_data'): + def convert_to_export(self, value, record): + if record._context.get('export_raw_data'): return value return ustr(value) @@ -1068,7 +1066,7 @@ class Integer(Field): return value.get('id', False) return int(value or 0) - def convert_to_read(self, value, use_name_get=True): + def convert_to_read(self, value, record, use_name_get=True): # Integer values greater than 2^31-1 are not supported in pure XMLRPC, # so we have to pass them as floats :-( if value and value > xmlrpclib.MAXINT: @@ -1079,9 +1077,9 @@ class Integer(Field): # special case, when an integer field is used as inverse for a one2many records._cache[self] = value.id or 0 - def convert_to_export(self, value, env): + def convert_to_export(self, value, record): if value or value == 0: - return value if env.context.get('export_raw_data') else ustr(value) + return value if record._context.get('export_raw_data') else ustr(value) return '' @@ -1125,9 +1123,9 @@ class Float(Field): digits = self.digits return float_round(value, precision_digits=digits[1]) if digits else value - def convert_to_export(self, value, env): + def convert_to_export(self, value, record): if value or value == 0.0: - return value if env.context.get('export_raw_data') else ustr(value) + return value if record._context.get('export_raw_data') else ustr(value) return '' @@ -1365,10 +1363,10 @@ class Date(Field): return value[:DATE_LENGTH] return self.to_string(value) - def convert_to_export(self, value, env): + def convert_to_export(self, value, record): if not value: return '' - return self.from_string(value) if env.context.get('export_raw_data') else ustr(value) + return self.from_string(value) if record._context.get('export_raw_data') else ustr(value) class Datetime(Field): @@ -1436,12 +1434,12 @@ class Datetime(Field): return value return self.to_string(value) - def convert_to_export(self, value, env): + def convert_to_export(self, value, record): if not value: return '' - return self.from_string(value) if env.context.get('export_raw_data') else ustr(value) + return self.from_string(value) if record._context.get('export_raw_data') else ustr(value) - def convert_to_display_name(self, value, record=None): + def convert_to_display_name(self, value, record): assert record, 'Record expected' return Datetime.to_string(Datetime.context_timestamp(record, Datetime.from_string(value))) @@ -1547,11 +1545,11 @@ class Selection(Field): return False raise ValueError("Wrong value for %s: %r" % (self, value)) - def convert_to_export(self, value, env): + def convert_to_export(self, value, record): if not isinstance(self.selection, list): # FIXME: this reproduces an existing buggy behavior! return value if value else '' - for item in self._description_selection(env): + for item in self._description_selection(record.env): if item[0] == value: return item[1] return False @@ -1582,13 +1580,13 @@ class Reference(Selection): return False raise ValueError("Wrong value for %s: %r" % (self, value)) - def convert_to_read(self, value, use_name_get=True): + def convert_to_read(self, value, record, use_name_get=True): return "%s,%s" % (value._name, value.id) if value else False - def convert_to_export(self, value, env): + def convert_to_export(self, value, record): return value.name_get()[0][1] if value else '' - def convert_to_display_name(self, value, record=None): + def convert_to_display_name(self, value, record): return ustr(value and value.display_name) @@ -1627,8 +1625,8 @@ class _Relational(Field): _column_domain = property(attrgetter('domain')) _column_context = property(attrgetter('context')) - def null(self, env): - return env[self.comodel_name] + def null(self, record): + return record.env[self.comodel_name] def modified(self, records): # Invalidate cache for inverse fields, too. Note that the recomputation @@ -1699,9 +1697,9 @@ class Many2one(_Relational): elif isinstance(value, dict): return record.env[self.comodel_name].new(value) else: - return self.null(record.env) + return record.env[self.comodel_name] - def convert_to_read(self, value, use_name_get=True): + def convert_to_read(self, value, record, use_name_get=True): if use_name_get and value: # evaluate name_get() as superuser, because the visibility of a # many2one field value (id and name) depends on the current record's @@ -1718,13 +1716,13 @@ class Many2one(_Relational): else: return value.id - def convert_to_write(self, value): + def convert_to_write(self, value, record): return value.id - def convert_to_export(self, value, env): + def convert_to_export(self, value, record): return value.name_get()[0][1] if value else '' - def convert_to_display_name(self, value, record=None): + def convert_to_display_name(self, value, record): return ustr(value.display_name) @@ -1790,13 +1788,13 @@ class _RelationalMulti(_Relational): # return result as a recordset return comodel.browse(list(ids)) elif not value: - return self.null(record.env) + return record.env[self.comodel_name] raise ValueError("Wrong value for %s: %s" % (self, value)) - def convert_to_read(self, value, use_name_get=True): + def convert_to_read(self, value, record, use_name_get=True): return value.ids - def convert_to_write(self, value): + def convert_to_write(self, value, record): # make result with new and existing records result = [(5,)] for record in value: @@ -1805,21 +1803,22 @@ class _RelationalMulti(_Relational): values = record._convert_to_write(values) result.append((0, 0, values)) elif record._is_dirty(): - values = {k: record._cache[k] for k in record._get_dirty()} + values = {name: record[name] for name in record._get_dirty()} values = record._convert_to_write(values) result.append((1, record.id, values)) else: result.append((4, record.id)) return result - def convert_to_onchange(self, value, fnames=None): + def convert_to_onchange(self, value, record, fnames=()): # return the recordset value as a list of commands; the commands may # give all fields values, the client is responsible for figuring out # which fields are actually dirty - fields = [(name, value._fields[name]) for name in (fnames or []) if name != 'id'] + converters = [(name, value._fields[name].convert_to_onchange) + for name in fnames if name != 'id'] result = [(5,)] for record in value: - vals = {name: field.convert_to_onchange(record[name]) for name, field in fields} + vals = {name: convert(record[name], record) for name, convert in converters} if not record.id: result.append((0, 0, vals)) elif vals: @@ -1828,10 +1827,10 @@ class _RelationalMulti(_Relational): result.append((4, record.id)) return result - def convert_to_export(self, value, env): + def convert_to_export(self, value, record): return ','.join(name for id, name in value.name_get()) if value else '' - def convert_to_display_name(self, value, record=None): + def convert_to_display_name(self, value, record): raise NotImplementedError() def _compute_related(self, records): @@ -1900,11 +1899,11 @@ class One2many(_RelationalMulti): _column_auto_join = property(attrgetter('auto_join')) _column_limit = property(attrgetter('limit')) - def convert_to_onchange(self, value, fnames=None): + def convert_to_onchange(self, value, record, fnames=()): if fnames: # do not serialize self's inverse field fnames = [name for name in fnames if name != self.inverse_name] - return super(One2many, self).convert_to_onchange(value, fnames) + return super(One2many, self).convert_to_onchange(value, record, fnames) class Many2many(_RelationalMulti): diff --git a/openerp/models.py b/openerp/models.py index 0f49f5f222e..6d88734a913 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -945,7 +945,7 @@ class BaseModel(object): # this part could be simpler, but it has to be done this way # in order to reproduce the former behavior if not isinstance(value, BaseModel): - current[i] = field.convert_to_export(value, self.env) + current[i] = field.convert_to_export(value, record) else: primary_done.append(name) @@ -1383,7 +1383,6 @@ class BaseModel(object): parent_fields[field.model_name].append(field.name) # convert default values to the right format - defaults = self._convert_to_cache(defaults, validate=False) defaults = self._convert_to_write(defaults) # add default values for inherited fields @@ -3393,7 +3392,7 @@ class BaseModel(object): try: values = {'id': record.id} for name, field in name_fields: - values[name] = field.convert_to_read(record[name], use_name_get) + values[name] = field.convert_to_read(record[name], record, use_name_get) result.append(values) except MissingError: pass @@ -5650,7 +5649,10 @@ class BaseModel(object): result = {} for name, value in values.iteritems(): if name in fields: - value = fields[name].convert_to_write(value) + field = fields[name] + value = field.convert_to_cache(value, self, validate=False) + value = field.convert_to_record(value, self) + value = field.convert_to_write(value, self) if not isinstance(value, NewId): result[name] = value return result @@ -5696,8 +5698,8 @@ class BaseModel(object): recs = self for name in name_seq.split('.'): field = recs._fields[name] - null = field.null(self.env) - recs = recs.mapped(lambda rec: rec._cache.get(field, null)) + null = field.convert_to_cache(False, self, validate=False) + recs = recs.mapped(lambda rec: field.convert_to_record(rec._cache.get(field, null), rec)) return recs def filtered(self, func): @@ -6111,9 +6113,9 @@ class BaseModel(object): def __init__(self, record): self._record = record def __getitem__(self, name): - field = self._record._fields[name] - value = self._record[name] - return field.convert_to_write(value) + record = self._record + field = record._fields[name] + return field.convert_to_write(record[name], record) def __getattr__(self, name): return self[name] @@ -6228,7 +6230,7 @@ class BaseModel(object): # collect values from dirty fields result['value'] = { - name: self._fields[name].convert_to_onchange(record[name], subfields.get(name)) + name: self._fields[name].convert_to_onchange(record[name], record, subfields[name]) for name in dirty }