From 1616d47efa8d27560d1db23b8320b5e803dd405d Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Wed, 8 Dec 2021 15:14:59 +0000 Subject: [PATCH] [FIX] core: computed inversed fields partly assigned This fixes inconsistencies when dealing with fields that are computed and inversed by the same methods. Consider two fields F1, F2 with the same compute and inverse methods. Consider a record where we wrote on a dependency of the common compute method. At this point, both fields F1 and F2 are marked to be computed. Now let us write on F1 only. Here is what happens: - the write discards the computation of F1, but not F2 - the inverse method of F1 is called: - the method accesses F2 -> this calls the compute method, which assigns both F1 and F2 - the method accesses F1 -> the value of F1 has been replaced by the computation above The issue comes from a combination of factors: - the value of F2 must be determined by the computation; - the computation assigns both F1 and F2; - the computation is done while inversing F1 (and F2). The solution is to force the computation before actually writing on the fields and calling their inverse methods. Note that this is necessary only when part of the fields computed by a common method are updated. When all fields computed by a common method are updated, the computation will automatically be cancelled. closes odoo/odoo#81172 X-original-commit: fb4d6ee4ba8bcb5cd8030105ac57e9e02850bfc4 Signed-off-by: Raphael Collet --- .../test_new_api/tests/test_new_fields.py | 27 +++++++++++++++++++ odoo/models.py | 8 ++++++ 2 files changed, 35 insertions(+) diff --git a/odoo/addons/test_new_api/tests/test_new_fields.py b/odoo/addons/test_new_api/tests/test_new_fields.py index 48444cbdadb..ec96fbf351c 100644 --- a/odoo/addons/test_new_api/tests/test_new_fields.py +++ b/odoo/addons/test_new_api/tests/test_new_fields.py @@ -621,6 +621,33 @@ class TestFields(TransactionCaseWithUserDemo): self.assertEqual(record.bar3, 'C') self.assertCountEqual(log, ['compute']) + # corner case: write on a field that is marked to compute + log.clear() + # writing on 'foo' marks 'bar1', 'bar2', 'bar3' to compute + record.write({'foo': '1/2/3'}) + self.assertCountEqual(log, []) + # writing on 'bar3' must force the computation before updating + record.write({'bar3': 'X'}) + self.assertCountEqual(log, ['compute', 'inverse23']) + self.assertEqual(record.foo, '1/2/X') + self.assertEqual(record.bar1, '1') + self.assertEqual(record.bar2, '2') + self.assertEqual(record.bar3, 'X') + self.assertCountEqual(log, ['compute', 'inverse23']) + + log.clear() + # writing on 'foo' marks 'bar1', 'bar2', 'bar3' to compute + record.write({'foo': 'A/B/C'}) + self.assertCountEqual(log, []) + # writing on 'bar1', 'bar2', 'bar3' discards the computation + record.write({'bar1': 'X', 'bar2': 'Y', 'bar3': 'Z'}) + self.assertCountEqual(log, ['inverse1', 'inverse23']) + self.assertEqual(record.foo, 'X/Y/Z') + self.assertEqual(record.bar1, 'X') + self.assertEqual(record.bar2, 'Y') + self.assertEqual(record.bar3, 'Z') + self.assertCountEqual(log, ['inverse1', 'inverse23']) + def test_13_inverse_access(self): """ test access rights on inverse fields """ foo = self.env['test_new_api.category'].create({'name': 'Foo'}) diff --git a/odoo/models.py b/odoo/models.py index 31a6cacd9cd..944db478f18 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -3855,6 +3855,14 @@ Fields: if fname == 'company_id' or (field.relational and field.check_company): check_company = True + # force the computation of fields that are computed with some assigned + # fields, but are not assigned themselves + to_compute = [field.name + for field in protected + if field.compute and field.name not in vals] + if to_compute: + self.recompute(to_compute, self) + # protect fields being written against recomputation with env.protecting(protected, self): # Determine records depending on values. When modifying a relational