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 7d292000479..8ff42a51287 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 14795e7c868..a2b36650bec 100644 --- a/odoo/addons/test_new_api/tests/test_new_fields.py +++ b/odoo/addons/test_new_api/tests/test_new_fields.py @@ -556,7 +556,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) @@ -676,6 +675,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 8ee8b03610f..5aaff716a02 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -1257,10 +1257,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 @@ -3681,7 +3686,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 @@ -3896,7 +3901,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()