diff --git a/odoo/addons/base/i18n/base.pot b/odoo/addons/base/i18n/base.pot index 1c49c869758..5db22993064 100644 --- a/odoo/addons/base/i18n/base.pot +++ b/odoo/addons/base/i18n/base.pot @@ -5015,7 +5015,7 @@ msgstr "" #. module: base #: code:addons/base/ir/ir_model.py:549 #, python-format -msgid "Cannot rename field to %s, because that field already exists!" +msgid "Cannot rename/delete fields that are still present in views:" msgstr "" #. module: base diff --git a/odoo/addons/base/ir/ir_model.py b/odoo/addons/base/ir/ir_model.py index 564c3160ff2..8ccc3bd929c 100644 --- a/odoo/addons/base/ir/ir_model.py +++ b/odoo/addons/base/ir/ir_model.py @@ -9,6 +9,7 @@ from collections import defaultdict from odoo import api, fields, models, SUPERUSER_ID, tools, _ from odoo.exceptions import AccessError, UserError, ValidationError from odoo.modules.registry import Registry +from odoo.osv import expression from odoo.tools.safe_eval import safe_eval _logger = logging.getLogger(__name__) @@ -469,6 +470,21 @@ class IrModelFields(models.Model): msg = _("The field '%s' cannot be removed because the field '%s' depends on it.") raise UserError(msg % (field, model._field_inverses[field][0])) + # remove fields from registry, and check that views are not broken + fields = [self.env[record.model]._pop_field(record.name) for record in self] + try: + domain = expression.OR([('arch_db', 'like', record.name)] for record in self) + views = self.env['ir.ui.view'].search(domain) + views._check_xml() + except Exception: + raise UserError("\n".join([ + _("Cannot rename/delete fields that are still present in views:"), + ", ".join(map(str, fields)), + ])) + finally: + # the registry has been modified, restore it + self.pool.setup_models(self._cr) + @api.multi def unlink(self): if not self: diff --git a/odoo/addons/base/tests/test_ir_actions.py b/odoo/addons/base/tests/test_ir_actions.py index 31f4dfd2dbb..0663ca5a237 100644 --- a/odoo/addons/base/tests/test_ir_actions.py +++ b/odoo/addons/base/tests/test_ir_actions.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo.exceptions import ValidationError +from odoo.exceptions import UserError, ValidationError from odoo.tools import mute_logger import odoo.tests.common as common import odoo.workflow @@ -390,6 +390,9 @@ class TestCustomFields(common.TransactionCase): super(TestCustomFields, self).setUp() + # do not reload the registry after removing a field + self.env = self.env(context={'_force_unlink': True}) + def create_field(self, name): """ create a custom field and return it """ model = self.env['ir.model'].search([('model', '=', self.MODEL)]) @@ -402,6 +405,14 @@ class TestCustomFields(common.TransactionCase): self.assertIn(name, self.env[self.MODEL]._fields) return field + def create_view(self, name): + """ create a view with the given field name """ + return self.env['ir.ui.view'].create({ + 'name': 'yet another view', + 'model': self.MODEL, + 'arch': '' % name, + }) + def test_create_custom(self): """ custom field names must be start with 'x_' """ with self.assertRaises(ValidationError): @@ -436,3 +447,33 @@ class TestCustomFields(common.TransactionCase): field2 = self.create_field('x_bar') with self.assertRaises(ValidationError): field2.name = field1.name + + def test_remove_without_view(self): + """ try removing a custom field that does not occur in views """ + field = self.create_field('x_foo') + field.unlink() + + def test_rename_without_view(self): + """ try renaming a custom field that does not occur in views """ + field = self.create_field('x_foo') + field.name = 'x_bar' + + def test_remove_with_view(self): + """ try removing a custom field that occurs in a view """ + field = self.create_field('x_foo') + self.create_view('x_foo') + + # try to delete the field, this should fail but not modify the registry + with self.assertRaises(UserError): + field.unlink() + self.assertIn('x_foo', self.env[self.MODEL]._fields) + + def test_rename_with_view(self): + """ try renaming a custom field that occurs in a view """ + field = self.create_field('x_foo') + self.create_view('x_foo') + + # try to delete the field, this should fail but not modify the registry + with self.assertRaises(UserError): + field.name = 'x_bar' + self.assertIn('x_foo', self.env[self.MODEL]._fields)