From 1ec7b28a09bead5b5f1f28b38ca4bf203ecd8b09 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Wed, 30 Mar 2016 11:10:32 +0200 Subject: [PATCH] [FIX] models, fields, api: do not mix the cache format with the record format --- openerp/api.py | 8 ++++++-- openerp/fields.py | 35 +++++++++++++++++++---------------- openerp/models.py | 13 +++++++++++-- 3 files changed, 36 insertions(+), 20 deletions(-) diff --git a/openerp/api.py b/openerp/api.py index 1707fc05e6a..0ec3342cd74 100644 --- a/openerp/api.py +++ b/openerp/api.py @@ -1036,6 +1036,8 @@ class Environment(object): def check_cache(self): """ Check the cache consistency. """ + from openerp.fields import SpecialValue + # make a full copy of the cache, and invalidate it cache_dump = dict( (field, dict(field_cache)) @@ -1051,9 +1053,11 @@ class Environment(object): for record in records: try: cached = field_dump[record.id] + cached = cached.get() if isinstance(cached, SpecialValue) else cached + value = field.convert_to_record(cached, record) fetched = record[field.name] - if fetched != cached: - info = {'cached': cached, 'fetched': fetched} + if fetched != value: + info = {'cached': value, 'fetched': fetched} invalids.append((field, record, info)) except (AccessError, MissingError): pass diff --git a/openerp/fields.py b/openerp/fields.py index 8f3d9cd10fc..9bb67944254 100644 --- a/openerp/fields.py +++ b/openerp/fields.py @@ -52,11 +52,12 @@ def copy_cache(records, env): if record not in done: done.add(record) target = record.with_env(env) - for name, value in record._cache.iteritems(): + for name in record._cache: + field = record._fields[name] + value = record[name] if isinstance(value, BaseModel): todo.update(value) - value = value.with_env(env) - target._cache[name] = value + target._cache[name] = field.convert_to_cache(value, target, validate=False) def resolve_mro(model, name, predicate): @@ -88,12 +89,9 @@ def default_new_to_old(field, value): def default_old_to_new(field, value): """ Convert the old-API default ``value`` to the new API. """ if callable(value): - return lambda model: field.convert_to_cache( - value(model._model, model._cr, model._uid, model._context), - model, validate=False, - ) + return lambda model: value(model._model, model._cr, model._uid, model._context) else: - return lambda model: field.convert_to_cache(value, model, validate=False) + return lambda model: value def default_old_to_old(field, value): """ Convert the old-API default ``value`` to the old API. """ @@ -860,7 +858,7 @@ class Field(object): record._cache[self] = value if env.in_onchange: for invf in record._field_inverses[self]: - invf._update(value, record) + invf._update(record[self.name], record) record._set_dirty(self.name) # determine more dependent fields, and invalidate them @@ -1685,8 +1683,11 @@ class Many2one(_Relational): _column_auto_join = property(attrgetter('auto_join')) def _update(self, records, value): - """ Update the cached value of ``self`` for ``records`` with ``value``. """ - records._cache[self] = value + """ Update the cached value of ``self`` for ``records`` with ``value``. + This is used to reflect the assignment ``value[name] = records``, where + ``name`` is the inverse field of ``self``. + """ + records._cache[self] = self.convert_to_cache(value, records, validate=False) def convert_to_cache(self, value, record, validate=True): if isinstance(value, (NoneType, int, long)): @@ -1741,8 +1742,9 @@ class UnionUpdate(SpecialValue): # in order to read the current field's value, remove self from cache del record._cache[field] # read the current field's value, and update it in cache only - record._cache[field] = new_value = record[field.name] | value - return new_value + value = field.convert_to_cache(record[field.name] | value, record, validate=False) + record._cache[field] = value + return value class _RelationalMulti(_Relational): @@ -1752,9 +1754,10 @@ class _RelationalMulti(_Relational): """ Update the cached value of ``self`` for ``records`` with ``value``. """ for record in records: if self in record._cache: - record._cache[self] = record[self.name] | value + value = self.convert_to_cache(record[self.name] | value, record, validate=False) else: - record._cache[self] = UnionUpdate(self, record, value) + value = UnionUpdate(self, record, value) + record._cache[self] = value def convert_to_cache(self, value, record, validate=True): if isinstance(value, BaseModel): @@ -1805,7 +1808,7 @@ class _RelationalMulti(_Relational): result = [(5,)] for record in value: if not record.id: - values = dict(record._cache) + values = {name: record[name] for name in record._cache} values = record._convert_to_write(values) result.append((0, 0, values)) elif record._is_dirty(): diff --git a/openerp/models.py b/openerp/models.py index 721cce01edc..a37195693d9 100644 --- a/openerp/models.py +++ b/openerp/models.py @@ -5665,6 +5665,15 @@ class BaseModel(object): if name in fields } + def _convert_to_record(self, values): + """ Convert the ``values`` dictionary from the cache format to the + record format. + """ + return { + name: self._fields[name].convert_to_record(value, self) + for name, value in values.iteritems() + } + def _convert_to_write(self, values): """ Convert the ``values`` dictionary into the format of :meth:`write`. """ fields = self._fields @@ -6099,7 +6108,7 @@ class BaseModel(object): return if res.get('value'): res['value'].pop('id', None) - self.update(self._convert_to_cache(res['value'], validate=False)) + self.update(res['value']) if res.get('domain'): result.setdefault('domain', {}).update(res['domain']) if res.get('warning'): @@ -6188,7 +6197,7 @@ class BaseModel(object): # create a new record with values, and attach ``self`` to it with env.do_in_onchange(): record = self.new(values) - values = dict(record._cache) + values = {name: record[name] for name in record._cache} # attach ``self`` with a different context (for cache consistency) record._origin = self.with_context(__onchange=True)