[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
This commit is contained in:
@@ -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
|
||||
|
||||
|
@@ -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)],
|
||||
)
|
||||
|
||||
@@ -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")
|
||||
|
||||
+15
-8
@@ -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'))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user