diff --git a/addons/google_drive/models/google_drive.py b/addons/google_drive/models/google_drive.py index 58191842aaa..a4dbfa2b98b 100644 --- a/addons/google_drive/models/google_drive.py +++ b/addons/google_drive/models/google_drive.py @@ -176,7 +176,7 @@ class GoogleDrive(models.Model): return config_values name = fields.Char('Template Name', required=True) - model_id = fields.Many2one('ir.model', 'Model', ondelete='set null', required=True) + model_id = fields.Many2one('ir.model', 'Model', required=True) model = fields.Char('Related Model', related='model_id.model', readonly=True) filter_id = fields.Many2one('ir.filters', 'Filter', domain="[('model_id', '=', model)]") google_drive_template_url = fields.Char('Template URL', required=True) diff --git a/odoo/addons/test_new_api/ir.model.access.csv b/odoo/addons/test_new_api/ir.model.access.csv index 0de3565ed6e..82fc03ab8ac 100644 --- a/odoo/addons/test_new_api/ir.model.access.csv +++ b/odoo/addons/test_new_api/ir.model.access.csv @@ -28,3 +28,4 @@ access_test_new_api_monetary_related,access_test_new_api_monetary_related,model_ access_test_new_api_monetary_custom,access_test_new_api_monetary_custom,model_test_new_api_monetary_custom,,1,1,1,1 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 diff --git a/odoo/addons/test_new_api/models.py b/odoo/addons/test_new_api/models.py index 7efa9321316..7649498762f 100644 --- a/odoo/addons/test_new_api/models.py +++ b/odoo/addons/test_new_api/models.py @@ -516,3 +516,11 @@ class FieldWithCaps(models.Model): _description = 'Model with field defined with capital letters' pArTneR_321_id = fields.Many2one('res.partner') + + +class RequiredM2O(models.Model): + _name = 'test_new_api.req_m2o' + _description = 'Required Many2one' + + foo = fields.Many2one('res.currency', required=True, ondelete='cascade') + bar = fields.Many2one('res.country', required=True) 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 d68493f5586..3403037aea5 100644 --- a/odoo/addons/test_new_api/tests/test_new_fields.py +++ b/odoo/addons/test_new_api/tests/test_new_fields.py @@ -1447,3 +1447,25 @@ class TestParentStore(common.TransactionCase): """ Move multiple nodes to create a cycle. """ with self.assertRaises(UserError): self.cats(1, 3).write({'parent': self.cats(9).id}) + + +class TestRequiredMany2one(common.TransactionCase): + + def test_explicit_ondelete(self): + field = self.env['test_new_api.req_m2o']._fields['foo'] + self.assertEqual(field.ondelete, 'cascade') + + def test_implicit_ondelete(self): + field = self.env['test_new_api.req_m2o']._fields['bar'] + self.assertEqual(field.ondelete, 'restrict') + + def test_explicit_set_null(self): + Model = self.env['test_new_api.req_m2o'] + 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 5d1701447a4..99dfe94bacf 100644 --- a/odoo/fields.py +++ b/odoo/fields.py @@ -2052,7 +2052,7 @@ class Many2one(_Relational): type = 'many2one' column_type = ('int4', 'int4') _slots = { - 'ondelete': 'set null', # what to do when value is deleted + 'ondelete': None, # what to do when value is deleted 'auto_join': False, # whether joins are generated upon search 'delegate': False, # whether self implements delegation } @@ -2066,6 +2066,22 @@ class Many2one(_Relational): if not self.delegate: self.delegate = name in model._inherits.values() + def _setup_regular_base(self, model): + super()._setup_regular_base(model) + # 3 cases: + # 1) The ondelete attribute is not defined, we assign it a sensible default + # 2) The ondelete attribute is defined and its definition makes sense + # 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' + if self.ondelete == 'set null' and self.required: + raise ValueError( + "The m2o field %s of model %s is required but declares its ondelete policy " + "as being 'set null'. Only 'restrict' and 'cascade' make sense." + % (self.name, model._name) + ) + def update_db(self, model, columns): comodel = model.env[self.comodel_name] if not model.is_transient() and comodel.is_transient():