[FIX] ir_model: cascade delete custom o2m and related fields

Before this revision:

* Install a module, for this example we'll use `project`
* Create a o2m field or a related field whose inverse is a field from
the `project` module.
* Uninstall the `project` module.

Result -> BOOM! The registry can't be reloaded because it can't find the
inverse field of our custom field, ayayay!!

After this revision, this shouldn't break the registry anymore because
we create two m2o fields which will trigger cascade deletes for both o2m
and related fields upon the deletion of the inverse field, these fields
are both computed stored fields and they're not shown to the end user,
this is all done "under the rug".

Task-ID: 58808
This commit is contained in:
Adrian Torres
2018-03-26 14:58:08 +02:00
committed by GitHub
parent a7530a376c
commit 4265026ee2
2 changed files with 60 additions and 0 deletions
+17
View File
@@ -308,6 +308,8 @@ class IrModelFields(models.Model):
relation = fields.Char(string='Object Relation',
help="For relationship fields, the technical name of the target model")
relation_field = fields.Char(help="For one2many fields, the field on the target model that implement the opposite many2one relationship")
relation_field_id = fields.Many2one('ir.model.fields', compute='_compute_relation_field_id',
store=True, ondelete='cascade', string='Relation field')
model_id = fields.Many2one('ir.model', string='Model', required=True, index=True, ondelete='cascade',
help="The model this field belongs to")
field_description = fields.Char(string='Field Label', default='', required=True, translate=True)
@@ -319,6 +321,8 @@ class IrModelFields(models.Model):
"For example: [('blue','Blue'),('yellow','Yellow')]")
copy = fields.Boolean(string='Copied', help="Whether the value is copied when duplicating a record.")
related = fields.Char(string='Related Field', help="The corresponding related field, if any. This must be a dot-separated list of field names.")
related_field_id = fields.Many2one('ir.model.fields', compute='_compute_related_field_id',
store=True, string="Related field", ondelete='cascade')
required = fields.Boolean()
readonly = fields.Boolean()
index = fields.Boolean(string='Indexed')
@@ -346,6 +350,19 @@ class IrModelFields(models.Model):
" name, partner_id.name")
store = fields.Boolean(string='Stored', default=True, help="Whether the value is stored in the database.")
@api.depends('relation', 'relation_field')
def _compute_relation_field_id(self):
for rec in self:
if rec.state == 'manual' and rec.relation_field:
rec.relation_field_id = self._get(rec.relation, rec.relation_field)
@api.depends('related')
def _compute_related_field_id(self):
for rec in self:
if rec.state == 'manual' and rec.related:
field = rec._related_field()
rec.related_field_id = self._get(field.model_name, field.name)
@api.depends()
def _in_modules(self):
installed_modules = self.env['ir.module.module'].search([('state', '=', 'installed')])
+43
View File
@@ -230,6 +230,7 @@ class TestActionBindings(common.TransactionCase):
class TestCustomFields(common.TransactionCase):
MODEL = 'res.partner'
COMODEL = 'res.users'
def setUp(self):
# check that the registry is properly reset
@@ -273,6 +274,48 @@ class TestCustomFields(common.TransactionCase):
with self.assertRaises(ValidationError):
self.create_field('foo')
def test_create_custom_o2m(self):
""" try creating a custom o2m and then deleting its m2o inverse """
model = self.env['ir.model'].search([('model', '=', self.MODEL)])
comodel = self.env['ir.model'].search([('model', '=', self.COMODEL)])
m2o_field = self.env['ir.model.fields'].create({
'model_id': comodel.id,
'name': 'x_my_m2o',
'field_description': 'my_m2o',
'ttype': 'many2one',
'relation': self.MODEL,
})
o2m_field = self.env['ir.model.fields'].create({
'model_id': model.id,
'name': 'x_my_o2m',
'field_description': 'my_o2m',
'ttype': 'one2many',
'relation': self.COMODEL,
'relation_field': m2o_field.name,
})
m2o_field.unlink()
self.assertFalse(o2m_field.exists())
def test_create_custom_related(self):
""" try creating a custom related then deleting its inverse """
comodel = self.env['ir.model'].search([('model', '=', self.COMODEL)])
field = self.create_field('x_my_char')
related_field = self.env['ir.model.fields'].create({
'model_id': comodel.id,
'name': 'x_oh_boy',
'field_description': 'x_oh_boy',
'ttype': 'char',
'related': 'partner_id.x_my_char',
})
field.unlink()
self.assertFalse(related_field.exists())
def test_rename_custom(self):
""" custom field names must be start with 'x_' """
field = self.create_field('x_foo')