[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:
committed by
Julien Castiaux
parent
2fc46a064d
commit
59c59cd8a0
@@ -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
|
||||
|
||||
|
@@ -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
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user