diff --git a/odoo/addons/test_new_api/ir.model.access.csv b/odoo/addons/test_new_api/ir.model.access.csv index 66d10310f43..5a11d121179 100644 --- a/odoo/addons/test_new_api/ir.model.access.csv +++ b/odoo/addons/test_new_api/ir.model.access.csv @@ -23,6 +23,7 @@ access_test_new_api_compute_protected,access_test_new_api_compute_protected,mode access_test_new_api_multi_compute_inverse,access_test_new_api_multi_compute_inverse,model_test_new_api_multi_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 +access_test_new_api_compute_onchange,access_test_new_api_compute_onchange,model_test_new_api_compute_onchange,,1,1,1,1 access_test_new_api_binary_svg,access_test_new_api_binary_svg,model_test_new_api_binary_svg,,1,1,1,1 access_test_new_api_monetary_base,access_test_new_api_monetary_base,model_test_new_api_monetary_base,,1,1,1,1 access_test_new_api_monetary_related,access_test_new_api_monetary_related,model_test_new_api_monetary_related,,1,1,1,1 diff --git a/odoo/addons/test_new_api/models.py b/odoo/addons/test_new_api/models.py index 18bcbdb8c51..aa97b9f2281 100644 --- a/odoo/addons/test_new_api/models.py +++ b/odoo/addons/test_new_api/models.py @@ -373,8 +373,8 @@ class Related(models.Model): related_related_name = fields.Char(related='related_name', string='A related on a related on Name', readonly=False) message = fields.Many2one('test_new_api.message') - message_name = fields.Text(related="message.body", related_sudo=False, string='Message Body', readonly=False) - message_currency = fields.Many2one(related="message.author", string='Message Author', readonly=False) + message_name = fields.Text(related="message.body", related_sudo=False, string='Message Body') + message_currency = fields.Many2one(related="message.author", string='Message Author') class ComputeProtected(models.Model): _name = 'test_new_api.compute.protected' @@ -495,6 +495,27 @@ class ComputeCascade(models.Model): record.baz = "<%s>" % (record.bar or "") +class ComputeOnchange(models.Model): + _name = 'test_new_api.compute.onchange' + _description = "Compute method as an onchange" + + active = fields.Boolean() + foo = fields.Char() + bar = fields.Char(compute='_compute_bar', store=True) + baz = fields.Char(compute='_compute_baz', store=True, readonly=False) + + @api.depends('foo') + def _compute_bar(self): + for record in self: + record.bar = record.foo + + @api.depends('active', 'foo') + def _compute_baz(self): + for record in self: + if record.active: + record.baz = record.foo + + class ModelImage(models.Model): _name = 'test_new_api.model_image' _description = 'Test Image field' diff --git a/odoo/addons/test_new_api/tests/test_onchange.py b/odoo/addons/test_new_api/tests/test_onchange.py index b3a765ed7e9..2359acd78fe 100644 --- a/odoo/addons/test_new_api/tests/test_onchange.py +++ b/odoo/addons/test_new_api/tests/test_onchange.py @@ -522,3 +522,156 @@ class TestOnChange(common.TransactionCase): self.Message.onchange(values, 'discussion', field_onchange) self.assertFalse(called[0], "discussion.messages has been read") + + +class TestComputeOnchange(common.TransactionCase): + + def test_create(self): + model = self.env['test_new_api.compute.onchange'] + + # compute 'bar' and 'baz' + record = model.create({'active': True, 'foo': "foo"}) + self.assertEqual(record.bar, "foo") + self.assertEqual(record.baz, "foo") + + # compute 'bar' but not 'baz' + record = model.create({'active': True, 'foo': "foo", 'bar': "bar", 'baz': "baz"}) + self.assertEqual(record.bar, "foo") + self.assertEqual(record.baz, "baz") + + # compute 'bar' and 'baz', but do not change its value + record = model.create({'active': False, 'foo': "foo"}) + self.assertEqual(record.bar, "foo") + self.assertEqual(record.baz, False) + + # compute 'bar' but not 'baz' + record = model.create({'active': False, 'foo': "foo", 'bar': "bar", 'baz': "baz"}) + self.assertEqual(record.bar, "foo") + self.assertEqual(record.baz, "baz") + + def test_write(self): + model = self.env['test_new_api.compute.onchange'] + record = model.create({'active': True, 'foo': "foo"}) + self.assertEqual(record.bar, "foo") + self.assertEqual(record.baz, "foo") + + # recompute 'bar' and 'baz' + record.write({'foo': "foo1"}) + self.assertEqual(record.bar, "foo1") + self.assertEqual(record.baz, "foo1") + + # recompute 'bar' but not 'baz' + record.write({'foo': "foo2", 'bar': "bar2", 'baz': "baz2"}) + self.assertEqual(record.bar, "foo2") + self.assertEqual(record.baz, "baz2") + + # recompute 'bar' and 'baz', but do not change its value + record.write({'active': False, 'foo': "foo3"}) + self.assertEqual(record.bar, "foo3") + self.assertEqual(record.baz, "baz2") + + # recompute 'bar' but not 'baz' + record.write({'active': False, 'foo': "foo4", 'bar': "bar4", 'baz': "baz4"}) + self.assertEqual(record.bar, "foo4") + self.assertEqual(record.baz, "baz4") + + def test_set(self): + model = self.env['test_new_api.compute.onchange'] + record = model.create({'active': True, 'foo': "foo"}) + self.assertEqual(record.bar, "foo") + self.assertEqual(record.baz, "foo") + + # recompute 'bar' and 'baz' + record.foo = "foo1" + self.assertEqual(record.bar, "foo1") + self.assertEqual(record.baz, "foo1") + + # do not recompute 'baz' + record.baz = "baz2" + self.assertEqual(record.bar, "foo1") + self.assertEqual(record.baz, "baz2") + + # recompute 'baz', but do not change its value + record.active = False + self.assertEqual(record.bar, "foo1") + self.assertEqual(record.baz, "baz2") + + # recompute 'baz', but do not change its value + record.foo = "foo3" + self.assertEqual(record.bar, "foo3") + self.assertEqual(record.baz, "baz2") + + # do not recompute 'baz' + record.baz = "baz4" + self.assertEqual(record.bar, "foo3") + self.assertEqual(record.baz, "baz4") + + def test_set_new(self): + model = self.env['test_new_api.compute.onchange'] + record = model.new({'active': True, 'foo': "foo"}) + self.assertEqual(record.bar, "foo") + self.assertEqual(record.baz, "foo") + + # recompute 'bar' and 'baz' + record.foo = "foo1" + self.assertEqual(record.bar, "foo1") + self.assertEqual(record.baz, "foo1") + + # do not recompute 'baz' + record.baz = "baz2" + self.assertEqual(record.bar, "foo1") + self.assertEqual(record.baz, "baz2") + + # recompute 'baz', but do not change its value + record.active = False + self.assertEqual(record.bar, "foo1") + self.assertEqual(record.baz, "baz2") + + # recompute 'baz', but do not change its value + record.foo = "foo3" + self.assertEqual(record.bar, "foo3") + self.assertEqual(record.baz, "baz2") + + # do not recompute 'baz' + record.baz = "baz4" + self.assertEqual(record.bar, "foo3") + self.assertEqual(record.baz, "baz4") + + def test_onchange(self): + form = common.Form(self.env['test_new_api.compute.onchange']) + form.active = True + form.foo = "foo1" + self.assertEqual(form.bar, "foo1") + self.assertEqual(form.baz, "foo1") + form.baz = "baz2" + self.assertEqual(form.bar, "foo1") + self.assertEqual(form.baz, "baz2") + form.active = False + self.assertEqual(form.bar, "foo1") + self.assertEqual(form.baz, "baz2") + form.foo = "foo3" + self.assertEqual(form.bar, "foo3") + self.assertEqual(form.baz, "baz2") + form.active = True + self.assertEqual(form.bar, "foo3") + self.assertEqual(form.baz, "foo3") + + record = form.save() + self.assertEqual(record.bar, "foo3") + self.assertEqual(record.baz, "foo3") + + form = common.Form(record) + self.assertEqual(form.bar, "foo3") + self.assertEqual(form.baz, "foo3") + form.foo = "foo4" + self.assertEqual(form.bar, "foo4") + self.assertEqual(form.baz, "foo4") + form.baz = "baz5" + self.assertEqual(form.bar, "foo4") + self.assertEqual(form.baz, "baz5") + form.active = False + self.assertEqual(form.bar, "foo4") + self.assertEqual(form.baz, "baz5") + form.foo = "foo6" + self.assertEqual(form.bar, "foo6") + self.assertEqual(form.baz, "baz5") diff --git a/odoo/models.py b/odoo/models.py index 2914fc93685..f46b1a560f2 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -3315,7 +3315,8 @@ Fields: records_to_inverse[field] = self.filtered('id') if field.relational or self._field_inverses[field]: relational_names.append(fname) - protected.update(self._field_computed.get(field, [field])) + if field.compute and not field.readonly: + protected.update(self._field_computed.get(field, [field])) # protect fields being written against recomputation with env.protecting(protected, self): @@ -3516,11 +3517,8 @@ Fields: elif field.inverse: inversed[key] = val inversed_fields.add(field) - # ignore the protection of compute fields which do not have an - # inverse, otherwise their computation are not correctly - # performed, neither the fields which are computed in the same - # compute method (`test_validation_error`) - if not field.compute or field.inverse: + # protect non-readonly computed fields against (re)computation + if field.compute and not field.readonly: protected.update(self._field_computed.get(field, [field])) data_list.append(data) @@ -5452,9 +5450,7 @@ Fields: # Dont force the recomputation of compute fields which are # not stored as this is not really necessary. if field.compute and field.store: - records_to_invalidate = records.filtered(lambda r: not r.id) - self.env.add_to_compute(field, records - records_to_invalidate) - self.env.cache.invalidate([(field, records_to_invalidate._ids)]) + self.env.add_to_compute(field, records) else: self.env.cache.invalidate([(field, records._ids)]) # recursively trigger recomputation of field's dependents