[FIX] field: compute transitive dependencies over non-stored fields

In other words, when a field F depends on a non-stored field G, it also depends
on G's dependencies.  This guarantees that whenever a dependency of G is
modified, F will be invalidated and marked to recompute (if necessary).

The transitive closure of dependencies is not computed over stored fields.
Anyway stored fields already trigger the recomputation of their dependent
fields during their recomputation.  The performance impact on the loading of a
registry is negligible (less than 1%), and the increase of recomputation
triggers is small (less than 10%).

(cherry picked from commit 3fbd86bcbe)

opw-2033493

closes odoo/odoo#35636

Signed-off-by: Raphael Collet (rco) <rco@openerp.com>
This commit is contained in:
Raphael Collet
2019-08-12 09:18:31 +00:00
committed by Julien Castiaux
parent 2fc46a064d
commit 59c59cd8a0
4 changed files with 32 additions and 4 deletions
@@ -18,3 +18,4 @@ access_test_new_api_company_attr,access_test_new_api_company_attr,model_test_new
access_test_new_api_sparse,access_test_new_api_sparse,model_test_new_api_sparse,,1,1,1,1
access_test_new_api_compute_inverse,access_test_new_api_compute_inverse,model_test_new_api_compute_inverse,,1,1,1,1
access_test_new_api_recursive,access_test_new_api_recursive,model_test_new_api_recursive,,1,1,1,1
access_test_new_api_cascade,access_test_new_api_cascade,model_test_new_api_cascade,,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
18 access_test_new_api_sparse access_test_new_api_sparse model_test_new_api_sparse 1 1 1 1
19 access_test_new_api_compute_inverse access_test_new_api_compute_inverse model_test_new_api_compute_inverse 1 1 1 1
20 access_test_new_api_recursive access_test_new_api_recursive model_test_new_api_recursive 1 1 1 1
21 access_test_new_api_cascade access_test_new_api_cascade model_test_new_api_cascade 1 1 1 1
+18
View File
@@ -376,3 +376,21 @@ class ComputeRecursive(models.Model):
rec.display_name = rec.parent.display_name + " / " + rec.name
else:
rec.display_name = rec.name
class ComputeCascade(models.Model):
_name = 'test_new_api.cascade'
foo = fields.Char()
bar = fields.Char(compute='_compute_bar') # depends on foo
baz = fields.Char(compute='_compute_baz', store=True) # depends on bar
@api.depends('foo')
def _compute_bar(self):
for record in self:
record.bar = "[%s]" % (record.foo or "")
@api.depends('bar')
def _compute_baz(self):
for record in self:
record.baz = "<%s>" % (record.bar or "")
@@ -223,6 +223,11 @@ class TestFields(common.TransactionCase):
double_size = message.double_size
self.assertEqual(double_size, message.size)
record = self.env['test_new_api.cascade'].create({'foo': "Hi"})
self.assertEqual(record.baz, "<[Hi]>")
record.foo = "Ho"
self.assertEqual(record.baz, "<[Ho]>")
def test_13_inverse(self):
""" test inverse computation of fields """
Category = self.env['test_new_api.category']
+8 -4
View File
@@ -721,7 +721,7 @@ class Field(object):
# on ``path``. See method ``modified`` below for details.
#
def resolve_deps(self, model):
def resolve_deps(self, model, path0=[], seen=frozenset()):
""" Return the dependencies of ``self`` as tuples ``(model, field, path)``,
where ``path`` is an optional list of field names.
"""
@@ -732,11 +732,12 @@ class Field(object):
for dotnames in self.depends:
if dotnames == self.name:
_logger.warning("Field %s depends on itself; please fix its decorator @api.depends().", self)
model, path = model0, dotnames.split('.')
for i, fname in enumerate(path):
model, path = model0, path0
for fname in dotnames.split('.'):
field = model._fields[fname]
result.append((model, field, path[:i]))
result.append((model, field, path))
model = model0.env.get(field.comodel_name)
path = None if path is None else path + [fname]
# add self's model dependencies
for mname, fnames in model0._depends.iteritems():
@@ -746,11 +747,14 @@ class Field(object):
result.append((model, field, None))
# add indirect dependencies from the dependencies found above
seen = seen.union([self])
for model, field, path in list(result):
for inv_field in model._field_inverses[field]:
inv_model = model0.env[inv_field.model_name]
inv_path = None if path is None else path + [field.name]
result.append((inv_model, inv_field, inv_path))
if not field.store and field not in seen:
result += field.resolve_deps(model, path, seen)
return result