summaryrefslogtreecommitdiff
path: root/odoo
diff options
context:
space:
mode:
authorRaphael Collet <rco@odoo.com>2021-01-12 09:45:11 +0000
committerRaphael Collet <rco@odoo.com>2021-01-12 14:03:27 +0000
commit25760348859f594abb796b5bc7b28f63e298cd53 (patch)
tree19b44da9232340d56edc8ef29be5bd03b600bd0b /odoo
parent9dd7e037239ff7b124d36f176fe4c66091e2cc4e (diff)
[FIX] core: invoke constraint methods once per write
Consider a constraint method with two fields: a normal field, and a field with an inverse method. When calling write() with both fields, the constraint method should be invoked once. closes odoo/odoo#64411 X-original-commit: 3b39d7efa94484b3bbf04fce756394a7e424f625 Signed-off-by: Raphael Collet (rco) <rco@openerp.com> Co-authored-by: Thibault Delavallée <tde@odoo.com>
Diffstat (limited to 'odoo')
-rw-r--r--odoo/addons/test_new_api/models/test_new_api.py6
-rw-r--r--odoo/addons/test_new_api/tests/test_new_fields.py43
-rw-r--r--odoo/models.py13
3 files changed, 47 insertions, 15 deletions
diff --git a/odoo/addons/test_new_api/models/test_new_api.py b/odoo/addons/test_new_api/models/test_new_api.py
index b02ef005015..df13ab303f9 100644
--- a/odoo/addons/test_new_api/models/test_new_api.py
+++ b/odoo/addons/test_new_api/models/test_new_api.py
@@ -400,6 +400,7 @@ class ComputeInverse(models.Model):
foo = fields.Char()
bar = fields.Char(compute='_compute_bar', inverse='_inverse_bar', store=True)
+ baz = fields.Char()
@api.depends('foo')
def _compute_bar(self):
@@ -412,6 +413,11 @@ class ComputeInverse(models.Model):
for record in self:
record.foo = record.bar
+ @api.constrains('bar', 'baz')
+ def _check_constraint(self):
+ if self._context.get('log_constraint'):
+ self._context.get('log', []).append('constraint')
+
class MultiComputeInverse(models.Model):
""" Model with the same inverse method for several fields. """
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 f77bcc261b0..45a7f8a4c35 100644
--- a/odoo/addons/test_new_api/tests/test_new_fields.py
+++ b/odoo/addons/test_new_api/tests/test_new_fields.py
@@ -553,7 +553,6 @@ class TestFields(TransactionCaseWithUserDemo):
self.assertEqual(foo1.name, 'Bar')
self.assertEqual(foo2.name, 'Bar')
-
# create/write on 'foo' should only invoke the compute method
log = []
model = self.env['test_new_api.compute.inverse'].with_context(log=log)
@@ -629,16 +628,6 @@ class TestFields(TransactionCaseWithUserDemo):
with self.assertRaises(AccessError):
foo.with_user(user).display_name = 'Forbidden'
- def test_13_inverse_access(self):
- """ test access rights on inverse fields """
- foo = self.env['test_new_api.category'].create({'name': 'Foo'})
- user = self.env['res.users'].create({'name': 'Foo', 'login': 'foo'})
- self.assertFalse(user.has_group('base.group_system'))
- # add group on non-stored inverse field
- self.patch(type(foo).display_name, 'groups', 'base.group_system')
- with self.assertRaises(AccessError):
- foo.with_user(user).display_name = 'Forbidden'
-
def test_14_search(self):
""" test search on computed fields """
discussion = self.env.ref('test_new_api.discussion_0')
@@ -683,6 +672,38 @@ class TestFields(TransactionCaseWithUserDemo):
discussion.name = "X"
discussion.flush()
+ def test_15_constraint_inverse(self):
+ """ test constraint method on normal field and field with inverse """
+ log = []
+ model = self.env['test_new_api.compute.inverse'].with_context(log=log, log_constraint=True)
+
+ # create/write with normal field only
+ log.clear()
+ record = model.create({'baz': 'Hi'})
+ self.assertCountEqual(log, ['constraint'])
+
+ log.clear()
+ record.write({'baz': 'Ho'})
+ self.assertCountEqual(log, ['constraint'])
+
+ # create/write with inverse field only
+ log.clear()
+ record = model.create({'bar': 'Hi'})
+ self.assertCountEqual(log, ['inverse', 'constraint'])
+
+ log.clear()
+ record.write({'bar': 'Ho'})
+ self.assertCountEqual(log, ['inverse', 'constraint'])
+
+ # create/write with both fields only
+ log.clear()
+ record = model.create({'bar': 'Hi', 'baz': 'Hi'})
+ self.assertCountEqual(log, ['inverse', 'constraint'])
+
+ log.clear()
+ record.write({'bar': 'Ho', 'baz': 'Ho'})
+ self.assertCountEqual(log, ['inverse', 'constraint'])
+
def test_16_compute_unassigned(self):
model = self.env['test_new_api.compute.unassigned']
diff --git a/odoo/models.py b/odoo/models.py
index be36edcdc79..d9adffc1bbe 100644
--- a/odoo/models.py
+++ b/odoo/models.py
@@ -1244,10 +1244,15 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})):
yield dbid, xid, converted, dict(extras, record=stream.index)
- def _validate_fields(self, field_names):
+ def _validate_fields(self, field_names, excluded_names=()):
+ """ Invoke the constraint methods for which at least one field name is
+ in ``field_names`` and none is in ``excluded_names``.
+ """
field_names = set(field_names)
+ excluded_names = set(excluded_names)
for check in self._constraint_methods:
- if not field_names.isdisjoint(check._constrains):
+ if (not field_names.isdisjoint(check._constrains)
+ and excluded_names.isdisjoint(check._constrains)):
check(self)
@api.model
@@ -3680,7 +3685,7 @@ Fields:
# validate non-inversed fields first
inverse_fields = [f.name for fs in determine_inverses.values() for f in fs]
- real_recs._validate_fields(set(vals) - set(inverse_fields))
+ real_recs._validate_fields(vals, inverse_fields)
for fields in determine_inverses.values():
# inverse records that are not being computed
@@ -3895,7 +3900,7 @@ Fields:
# check Python constraints for non-stored inversed fields
for data in data_list:
- data['record']._validate_fields(set(data['inversed']) - set(data['stored']))
+ data['record']._validate_fields(data['inversed'], data['stored'])
if self._check_company_auto:
records._check_company()