Skip to content
Browse files

[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 3fbd86b)


closes #35636

Signed-off-by: Raphael Collet (rco) <>
  • Loading branch information...
rco-odoo authored and Julien00859 committed Jan 4, 2018
1 parent 2fc46a0 commit 59c59cd8a04af18cedd5005222de6744a215d903
@@ -18,3 +18,4 @@ access_test_new_api_company_attr,access_test_new_api_company_attr,model_test_new
@@ -376,3 +376,21 @@ def _compute_display_name(self):
rec.display_name = rec.parent.display_name + " / " +
rec.display_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

def _compute_bar(self):
for record in self: = "[%s]" % ( or "")

def _compute_baz(self):
for record in self:
record.baz = "<%s>" % ( or "")
@@ -223,6 +223,11 @@ def test_12_cascade(self):
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]>") = "Ho"
self.assertEqual(record.baz, "<[Ho]>")

def test_13_inverse(self):
""" test inverse computation of fields """
Category = self.env['test_new_api.category']
@@ -721,7 +721,7 @@ def _inverse_sparse(self, records):
# 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 @@ def resolve_deps(self, model):
for dotnames in self.depends:
if dotnames ==
_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 @@ def resolve_deps(self, model):
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 + []
result.append((inv_model, inv_field, inv_path))
if not and field not in seen:
result += field.resolve_deps(model, path, seen)

return result

0 comments on commit 59c59cd

Please sign in to comment.
You can’t perform that action at this time.