From e2180e5a2e273156a36224cfec04b347298bb09e Mon Sep 17 00:00:00 2001 From: XavierDo Date: Thu, 24 Jan 2019 09:25:29 +0000 Subject: [PATCH] [FIX] fields: setting an integer field invalidates its inverse one2many In particular, creating a `mail.message` automatically invalidates the corresponding `record.message_ids`. closes odoo/odoo#30737 --- odoo/addons/test_new_api/ir.model.access.csv | 2 ++ odoo/addons/test_new_api/models.py | 24 +++++++++++++ .../test_new_api/tests/test_one2many.py | 34 +++++++++++++++++++ odoo/fields.py | 23 ++++++++----- 4 files changed, 75 insertions(+), 8 deletions(-) diff --git a/odoo/addons/test_new_api/ir.model.access.csv b/odoo/addons/test_new_api/ir.model.access.csv index 82fc03ab8ac..b44f6e248bd 100644 --- a/odoo/addons/test_new_api/ir.model.access.csv +++ b/odoo/addons/test_new_api/ir.model.access.csv @@ -29,3 +29,5 @@ access_test_new_api_monetary_custom,access_test_new_api_monetary_custom,model_te access_test_new_api_monetary_inherits,access_test_new_api_monetary_inherits,model_test_new_api_monetary_inherits,,1,1,1,1 access_test_new_api_field_with_caps,access_test_new_api_field_with_caps,model_test_new_api_field_with_caps,,1,1,1,1 access_test_new_api_req_m2o,access_test_new_api_req_m2o,model_test_new_api_req_m2o,,1,1,1,1 +access_test_new_api_attachment,access_test_new_api_attachment,model_test_new_api_attachment,,1,1,1,1 +access_test_new_api_attachment_host,access_test_new_api_attachment_host,model_test_new_api_attachment_host,,1,1,1,1 diff --git a/odoo/addons/test_new_api/models.py b/odoo/addons/test_new_api/models.py index 7649498762f..23bd632ea59 100644 --- a/odoo/addons/test_new_api/models.py +++ b/odoo/addons/test_new_api/models.py @@ -524,3 +524,27 @@ class RequiredM2O(models.Model): foo = fields.Many2one('res.currency', required=True, ondelete='cascade') bar = fields.Many2one('res.country', required=True) + + +class Attachment(models.Model): + _name = 'test_new_api.attachment' + _description = 'Attachment' + + res_model = fields.Char(required=True) + res_id = fields.Integer(required=True) + name = fields.Char(compute='_compute_name', compute_sudo=True, store=True) + + @api.depends('res_model', 'res_id') + def _compute_name(self): + for rec in self: + rec.name = self.env[rec.res_model].browse(rec.res_id).display_name + + +class AttachmentHost(models.Model): + _name = 'test_new_api.attachment.host' + _description = 'Attachment Host' + + attachment_ids = fields.One2many( + 'test_new_api.attachment', 'res_id', auto_join=True, + domain=lambda self: [('res_model', '=', self._name)], + ) diff --git a/odoo/addons/test_new_api/tests/test_one2many.py b/odoo/addons/test_new_api/tests/test_one2many.py index 62de71c07fc..6662e5cacbf 100644 --- a/odoo/addons/test_new_api/tests/test_one2many.py +++ b/odoo/addons/test_new_api/tests/test_one2many.py @@ -149,3 +149,37 @@ class One2manyCase(TransactionCase): self.assertEqual(model1.pArTneR_321_id, p1) self.assertTrue(model2.exists()) self.assertEqual(model2.pArTneR_321_id, p1) + + def test_cache_invalidation(self): + """ Cache invalidation for one2many with integer inverse. """ + record = self.env['test_new_api.attachment.host'].create({}) + with self.assertQueryCount(2): + self.assertFalse(record.attachment_ids, "inconsistent cache") + + # creating attachment must compute name and invalidate attachment_ids + attachment = self.env['test_new_api.attachment'].create({ + 'res_model': record._name, + 'res_id': record.id, + }) + with self.assertQueryCount(1): + self.assertEqual(attachment.name, record.display_name, + "field should be computed") + with self.assertQueryCount(2): + self.assertEqual(record.attachment_ids, attachment, "inconsistent cache") + + # creating a host should not attempt to recompute attachment.name + with self.assertQueryCount(1): + record = self.env['test_new_api.attachment.host'].create({}) + with self.assertQueryCount(0): + # field res_id should not have been invalidated + attachment.res_id + with self.assertQueryCount(2): + self.assertFalse(record.attachment_ids, "inconsistent cache") + + # writing on res_id must recompute name and invalidate attachment_ids + attachment.res_id = record.id + with self.assertQueryCount(1): + self.assertEqual(attachment.name, record.display_name, + "field should be recomputed") + with self.assertQueryCount(2): + self.assertEqual(record.attachment_ids, attachment, "inconsistent cache") diff --git a/odoo/fields.py b/odoo/fields.py index 99dfe94bacf..0311440f26f 100644 --- a/odoo/fields.py +++ b/odoo/fields.py @@ -697,10 +697,13 @@ class Field(MetaField('DummyField', (object,), {})): # 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 + [field.name] - result.append((inv_model, inv_field, inv_path)) + # limitation: setting the inverse of an integer field F does not + # trigger the recomputation of fields that depend on F + if field.relational: + 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 + [field.name] + result.append((inv_model, inv_field, inv_path)) if not field.store and field not in seen: result += field.resolve_deps(model, path, seen) @@ -2198,6 +2201,11 @@ class _RelationalMulti(_Relational): def _update(self, records, value): """ Update the cached value of ``self`` for ``records`` with ``value``. """ + if not isinstance(records, BaseModel): + # the inverse of self is a non-relational field; do not update in + # this case, as we do not know whether the records are the ones that + # value makes reference to (via a res_model/res_id pair) + return cache = records.env.cache for record in records: special = cache.get_special(record, self) @@ -2377,12 +2385,11 @@ class One2many(_RelationalMulti): # link self to its inverse field and vice-versa comodel = model.env[self.comodel_name] invf = comodel._fields[self.inverse_name] - # In some rare cases, a ``One2many`` field can link to ``Int`` field - # (res_model/res_id pattern). Only inverse the field if this is - # a ``Many2one`` field. if isinstance(invf, Many2one): + # setting one2many fields only invalidates many2one inverses; + # integer inverses (res_model/res_id pairs) are not supported model._field_inverses.add(self, invf) - comodel._field_inverses.add(invf, self) + comodel._field_inverses.add(invf, self) _description_relation_field = property(attrgetter('inverse_name'))