From c5fa4771fcec91c06a26d5103126a1373f962f0b Mon Sep 17 00:00:00 2001 From: Xavier ALT Date: Wed, 11 Sep 2019 21:15:34 +0000 Subject: [PATCH] [FIX] fields: default to ondelete='cascade' for required m2o fields on transient TransientModel (wizards) can be annoying because if they have required many2one fields, those fields will default to `ondelete='restrict'`, preventing user to delete the comodel records without deleting the transient model records first. To improve such case, we default to `ondelete='cascade'` for required many2one field on a TransientModel (unless specified otherwise) closes odoo/odoo#36738 Signed-off-by: Raphael Collet (rco) --- odoo/addons/test_new_api/models.py | 8 +++++++ .../test_new_api/tests/test_new_fields.py | 22 +++++++++++++++++++ odoo/fields.py | 14 +++++++----- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/odoo/addons/test_new_api/models.py b/odoo/addons/test_new_api/models.py index c7188dec6cc..ca728b86691 100644 --- a/odoo/addons/test_new_api/models.py +++ b/odoo/addons/test_new_api/models.py @@ -594,6 +594,14 @@ class RequiredM2O(models.Model): bar = fields.Many2one('res.country', required=True) +class RequiredM2OTransient(models.TransientModel): + _name = 'test_new_api.req_m2o_transient' + _description = 'Transient Model with Required Many2one' + + foo = fields.Many2one('res.currency', required=True, ondelete='restrict') + bar = fields.Many2one('res.country', required=True) + + class Attachment(models.Model): _name = 'test_new_api.attachment' _description = 'Attachment' 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 c7f83be715b..845d5f4e960 100644 --- a/odoo/addons/test_new_api/tests/test_new_fields.py +++ b/odoo/addons/test_new_api/tests/test_new_fields.py @@ -1929,3 +1929,25 @@ class TestRequiredMany2one(common.TransactionCase): with self.assertRaises(ValueError): field._setup_regular_base(Model) + + +class TestRequiredMany2oneTransient(common.TransactionCase): + + def test_explicit_ondelete(self): + field = self.env['test_new_api.req_m2o_transient']._fields['foo'] + self.assertEqual(field.ondelete, 'restrict') + + def test_implicit_ondelete(self): + field = self.env['test_new_api.req_m2o_transient']._fields['bar'] + self.assertEqual(field.ondelete, 'cascade') + + def test_explicit_set_null(self): + Model = self.env['test_new_api.req_m2o_transient'] + field = Model._fields['foo'] + + # invalidate registry to redo the setup afterwards + self.registry.registry_invalidated = True + self.patch(field, 'ondelete', 'set null') + + with self.assertRaises(ValueError): + field._setup_regular_base(Model) diff --git a/odoo/fields.py b/odoo/fields.py index 2576fd770ac..f3167b3ce77 100644 --- a/odoo/fields.py +++ b/odoo/fields.py @@ -2306,7 +2306,14 @@ class Many2one(_Relational): # 3) The ondelete attribute is explicitly defined as 'set null' for a required m2o, # this is considered a programming error. if not self.ondelete: - self.ondelete = 'restrict' if self.required else 'set null' + comodel = model.env[self.comodel_name] + if model.is_transient() and not comodel.is_transient(): + # Many2one relations from TransientModel Model are annoying because + # they can block deletion due to foreign keys. So unless stated + # otherwise, we default them to ondelete='cascade'. + self.ondelete = 'cascade' if self.required else 'set null' + else: + self.ondelete = 'restrict' if self.required else 'set null' if self.ondelete == 'set null' and self.required: raise ValueError( "The m2o field %s of model %s is required but declares its ondelete policy " @@ -2318,11 +2325,6 @@ class Many2one(_Relational): comodel = model.env[self.comodel_name] if not model.is_transient() and comodel.is_transient(): raise ValueError('Many2one %s from Model to TransientModel is forbidden' % self) - if model.is_transient() and not comodel.is_transient(): - # Many2one relations from TransientModel Model are annoying because - # they can block deletion due to foreign keys. So unless stated - # otherwise, we default them to ondelete='cascade'. - self.ondelete = self.ondelete or 'cascade' return super(Many2one, self).update_db(model, columns) def update_db_column(self, model, column):