diff --git a/odoo/fields.py b/odoo/fields.py index 37c57dcd5c4..84a1d83d7a1 100644 --- a/odoo/fields.py +++ b/odoo/fields.py @@ -1063,51 +1063,6 @@ class Field(MetaField('DummyField', (object,), {})): # Notification when fields are modified # - def modified(self, records): - """ Notify that field ``self`` has been modified on ``records``: prepare the - fields/records to recompute, and return a spec indicating what to - invalidate. - """ - # invalidate the fields that depend on self, and prepare recomputation - spec = [(self, records._ids)] - - # group triggers by model and path to reduce the number of calls to search() - bymodel = defaultdict(lambda: defaultdict(list)) - for field, path in records._field_triggers[self]: - bymodel[field.model_name][path].append(field) - - for model_name, bypath in pycompat.items(bymodel): - for path, fields in pycompat.items(bypath): - if path and any(field.compute and field.store for field in fields): - # process stored fields - stored = set(field for field in fields if field.compute and field.store) - fields = set(fields) - stored - if path == 'id': - target0 = records - else: - # don't move this line to function top, see log - env = records.env(user=SUPERUSER_ID, context={'active_test': False}) - target0 = env[model_name].search([(path, 'in', records.ids)]) - if target0: - for field in stored: - # discard records to not recompute for field - target = target0 - records.env.protected(field) - if not target: - continue - spec.append((field, target._ids)) - # recompute field on target in the environment of - # records, and as user admin if required - if field.compute_sudo: - target = target.with_env(records.env(user=SUPERUSER_ID)) - else: - target = target.with_env(records.env) - target._recompute_todo(field) - # process non-stored fields - for field in fields: - spec.append((field, None)) - - return spec - def modified_draft(self, records): """ Same as :meth:`modified`, but in draft mode. """ env = records.env @@ -1898,15 +1853,6 @@ class _Relational(Field): def null(self, record): return record.env[self.comodel_name] - def modified(self, records): - # Invalidate cache for inverse fields, too. Note that the recomputation - # of fields that depend on inverse fields is already covered by the - # triggers. - spec = super(_Relational, self).modified(records) - for invf in records._field_inverses[self]: - spec.append((invf, None)) - return spec - class Many2one(_Relational): """ The value of such a field is a recordset of size 0 (no diff --git a/odoo/models.py b/odoo/models.py index 2222555fd00..ca40b271748 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -4713,12 +4713,48 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): :param fnames: iterable of field names that have been modified on records ``self`` """ - # each field knows what to invalidate and recompute - spec = [] + # group triggers by (model, path) to minimize the calls to search() + invalids = [] + triggers = defaultdict(set) for fname in fnames: - spec += self._fields[fname].modified(self) + mfield = self._fields[fname] + # invalidate mfield on self, and its inverses fields + invalids.append((mfield, self._ids)) + for field in self._field_inverses[mfield]: + invalids.append((field, None)) + # group triggers by model and path to reduce the number of search() + for field, path in self._field_triggers[mfield]: + triggers[(field.model_name, path)].add(field) - self.env.invalidate(spec) + # process triggers, mark fields to be invalidated/recomputed + for model_path, fields in triggers.items(): + model_name, path = model_path + stored = {field for field in fields if field.compute and field.store} + # process stored fields + if path and stored: + # determine records of model_name linked by path to self + if path == 'id': + target0 = self + else: + env = self.env(user=SUPERUSER_ID, context={'active_test': False}) + target0 = env[model_name].search([(path, 'in', self.ids)]) + target0 = target0.with_env(self.env) + # prepare recomputation for each field on linked records + for field in stored: + # discard records to not recompute for field + target = target0 - self.env.protected(field) + if not target: + continue + invalids.append((field, target._ids)) + # mark field to be recomputed on target + if field.compute_sudo: + target = target.sudo() + target._recompute_todo(field) + # process non-stored fields + for field in (fields - stored): + invalids.append((field, None)) + + self.env.invalidate(invalids) def _recompute_check(self, field): """ If ``field`` must be recomputed on some record in ``self``, return the