diff --git a/odoo/addons/base/i18n/base.pot b/odoo/addons/base/i18n/base.pot index 92884667f6d..d54274f4cc5 100644 --- a/odoo/addons/base/i18n/base.pot +++ b/odoo/addons/base/i18n/base.pot @@ -7370,6 +7370,12 @@ msgstr "" msgid "Field `%(field_name)s` does not exist" msgstr "" +#. module: base +#: code:addons/base/ir/ir_model.py:298 +#, python-format +msgid "Field names can only contain characters, digits and underscores (up to 63)." +msgstr "" + #. module: base #: model:ir.actions.act_window,name:base.action_model_fields #: model:ir.model,name:base.model_ir_model_fields diff --git a/odoo/addons/base/ir/ir_model.py b/odoo/addons/base/ir/ir_model.py index 3b2d33e6121..95a02198885 100644 --- a/odoo/addons/base/ir/ir_model.py +++ b/odoo/addons/base/ir/ir_model.py @@ -286,6 +286,17 @@ class IrModelFields(models.Model): raise UserError(_("The Selection Options expression is not a valid Pythonic expression." "Please provide an expression in the [('key','Label'), ...] format.")) + @api.constrains('name', 'state') + def _check_name(self): + for field in self: + if field.state == 'manual' and not field.name.startswith('x_'): + raise ValidationError(_("Custom fields must have a name that starts with 'x_' !")) + try: + models.check_pg_name(field.name) + except ValidationError: + msg = _("Field names can only contain characters, digits and underscores (up to 63).") + raise ValidationError(msg) + _sql_constraints = [ ('size_gt_zero', 'CHECK (size>=0)', 'Size of the field cannot be negative.'), ] @@ -492,9 +503,6 @@ class IrModelFields(models.Model): res = super(IrModelFields, self).create(vals) if vals.get('state', 'manual') == 'manual': - if not vals['name'].startswith('x_'): - raise UserError(_("Custom fields must have a name that starts with 'x_' !")) - if vals.get('relation') and not self.env['ir.model'].search([('model', '=', vals['relation'])]): raise UserError(_("Model %s does not exist!") % vals['relation']) @@ -559,10 +567,6 @@ class IrModelFields(models.Model): raise UserError(_('Can only rename one field at a time!')) if vals['name'] in obj._fields: raise UserError(_('Cannot rename field to %s, because that field already exists!') % vals['name']) - if vals.get('state', 'manual') == 'manual' and not vals['name'].startswith('x_'): - raise UserError(_('New field name must still start with x_ , because it is a custom field!')) - if '\'' in vals['name'] or '"' in vals['name'] or ';' in vals['name']: - raise ValueError('Invalid character in column name') column_rename = (obj._table, item.name, vals['name'], item.index) # We don't check the 'state', because it might come from the context diff --git a/odoo/addons/base/tests/test_ir_actions.py b/odoo/addons/base/tests/test_ir_actions.py index 00e57ac6ae0..a379181e657 100644 --- a/odoo/addons/base/tests/test_ir_actions.py +++ b/odoo/addons/base/tests/test_ir_actions.py @@ -368,3 +368,58 @@ class TestServerActions(TestServerActionsBase): self.action.write({ 'child_ids': [(6, 0, [self.action.id])] }) + + +class TestCustomFields(common.TransactionCase): + MODEL = 'res.partner' + + def setUp(self): + # use a test cursor instead of a real cursor + registry = odoo.registry() + registry.enter_test_mode() + fnames = set(registry[self.MODEL]._fields) + + @self.addCleanup + def callback(): + registry.leave_test_mode() + # the tests may have modified the registry, reset it + with registry.cursor() as cr: + registry.clear_manual_fields() + registry.setup_models(cr) + assert set(registry[self.MODEL]._fields) == fnames + + super(TestCustomFields, self).setUp() + + def create_field(self, name): + """ create a custom field and return it """ + model = self.env['ir.model'].search([('model', '=', self.MODEL)]) + field = self.env['ir.model.fields'].create({ + 'model_id': model.id, + 'name': name, + 'field_description': name, + 'ttype': 'char', + }) + self.assertIn(name, self.env[self.MODEL]._fields) + return field + + def test_create_custom(self): + """ custom field names must be start with 'x_' """ + with self.assertRaises(ValidationError): + self.create_field('foo') + + def test_rename_custom(self): + """ custom field names must be start with 'x_' """ + field = self.create_field('x_foo') + with self.assertRaises(ValidationError): + field.name = 'foo' + + def test_create_valid(self): + """ field names must be valid pg identifiers """ + with self.assertRaises(ValidationError): + self.create_field('x_foo bar') + + def test_rename_valid(self): + """ field names must be valid pg identifiers """ + field = self.create_field('x_foo') + with self.assertRaises(ValidationError): + field.name = 'x_foo bar'