summaryrefslogtreecommitdiff
path: root/odoo/models.py
diff options
context:
space:
mode:
authorRaphael Collet <rco@odoo.com>2017-07-06 17:16:03 +0200
committerRaphael Collet <rco@odoo.com>2017-07-10 16:18:33 +0200
commite25bcf41e2f312536a00ef8531b2f6c6e0334ebc (patch)
treecd4fed7e6ea124b024637c5327f0e73bce376148 /odoo/models.py
parentc47a41b8b4c568ec63692a027c4dc13c82f1f172 (diff)
[REF] models: group recomputation triggers to minimize the number of requests
When several fields are modified and trigger the same recomputations, the same SQL queries were made to determine the records to recompute, because each field was processed independently. In order to avoid duplicated SQL queries, process the triggers of the modified fields altogether. This reduces the number of queries by 15% to 25%, depending on the number of computed fields.
Diffstat (limited to 'odoo/models.py')
-rw-r--r--odoo/models.py46
1 files changed, 41 insertions, 5 deletions
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)
-
- self.env.invalidate(spec)
+ 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)
+
+ # 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