diff --git a/odoo/addons/base/models/ir_model.py b/odoo/addons/base/models/ir_model.py index bc9a993310e..a4eb9fa0983 100644 --- a/odoo/addons/base/models/ir_model.py +++ b/odoo/addons/base/models/ir_model.py @@ -1633,18 +1633,23 @@ class IrModelConstraint(models.Model): self._cr.execute( sql.SQL('ALTER TABLE {} DROP CONSTRAINT {}').format( sql.Identifier(table), - sql.Identifier(name) + sql.Identifier(name[:63]) )) _logger.info('Dropped FK CONSTRAINT %s@%s', name, data.model.model) if typ == 'u': + hname = tools.make_identifier(name) # test if constraint exists + # Since type='u' means any "other" constraint, to avoid issues we limit to + # 'c' -> check, 'u' -> unique, 'x' -> exclude constraints, effective leaving + # out 'p' -> primary key and 'f' -> foreign key, constraints. + # See: https://www.postgresql.org/docs/9.5/catalog-pg-constraint.html self._cr.execute("""SELECT 1 from pg_constraint cs JOIN pg_class cl ON (cs.conrelid = cl.oid) - WHERE cs.contype=%s and cs.conname=%s and cl.relname=%s""", - ('u', name, table)) + WHERE cs.contype in ('c', 'u', 'x') and cs.conname=%s and cl.relname=%s""", + (hname, table)) if self._cr.fetchone(): self._cr.execute(sql.SQL('ALTER TABLE {} DROP CONSTRAINT {}').format( - sql.Identifier(table), sql.Identifier(name))) + sql.Identifier(table), sql.Identifier(hname))) _logger.info('Dropped CONSTRAINT %s@%s', name, data.model.model) self.unlink() @@ -2286,9 +2291,9 @@ class IrModelData(models.Model): modules._remove_copied_views() # remove constraints - delete(self.env['ir.model.constraint'].browse(unique(constraint_ids))) constraints = self.env['ir.model.constraint'].search([('module', 'in', modules.ids)]) constraints._module_data_uninstall() + delete(self.env['ir.model.constraint'].browse(unique(constraint_ids))) # If we delete a selection field, and some of its values have ondelete='cascade', # we expect the records with that value to be deleted. If we delete the field first, diff --git a/odoo/addons/base/tests/__init__.py b/odoo/addons/base/tests/__init__.py index 3b4ea5cd30c..73f5b6e2526 100644 --- a/odoo/addons/base/tests/__init__.py +++ b/odoo/addons/base/tests/__init__.py @@ -41,7 +41,7 @@ from . import test_res_config from . import test_res_lang from . import test_search from . import test_translate -#import test_uninstall # loop +# from . import test_uninstall # loop from . import test_user_has_group from . import test_views from . import test_xmlrpc diff --git a/odoo/addons/base/tests/test_uninstall.py b/odoo/addons/base/tests/test_uninstall.py index 123fbf747dd..b6455f0670a 100644 --- a/odoo/addons/base/tests/test_uninstall.py +++ b/odoo/addons/base/tests/test_uninstall.py @@ -46,6 +46,17 @@ class TestUninstall(BaseCase): self.assertTrue(env['ir.model.data'].search([('module', '=', MODULE)])) self.assertTrue(env['ir.model.fields'].search([('model', '=', MODEL)])) + env.cr.execute( + r""" + SELECT conname + FROM pg_constraint + WHERE conrelid = 'res_users'::regclass + AND conname LIKE 'res\_users\_test\_uninstall\_res\_user\_%' + """ + ) + existing_constraints = [r[0] for r in env.cr.fetchall()] + self.assertTrue(len(existing_constraints) == 4, existing_constraints) + def test_02_uninstall(self): """ Check a few things showing the module is uninstalled. """ with environment() as env: @@ -59,6 +70,17 @@ class TestUninstall(BaseCase): self.assertFalse(env['ir.model.data'].search([('module', '=', MODULE)])) self.assertFalse(env['ir.model.fields'].search([('model', '=', MODEL)])) + env.cr.execute( + r""" + SELECT conname + FROM pg_constraint + WHERE conrelid = 'res_users'::regclass + AND conname LIKE 'res\_users\_test\_uninstall\_res\_user\_%' + """ + ) + remaining_constraints = [r[0] for r in env.cr.fetchall()] + self.assertFalse(remaining_constraints) + if __name__ == '__main__': unittest.main() diff --git a/odoo/addons/test_uninstall/models.py b/odoo/addons/test_uninstall/models.py index 052cecf14cf..247b01d4bdf 100644 --- a/odoo/addons/test_uninstall/models.py +++ b/odoo/addons/test_uninstall/models.py @@ -18,3 +18,13 @@ class test_uninstall_model(models.Model): _sql_constraints = [ ('name_uniq', 'unique (name)', 'Each name must be unique.'), ] + +class ResUsers(models.Model): + _inherit = 'res.users' + + _sql_constraints = [ + ('test_uninstall_res_user_unique_constraint', 'unique (password)', 'Test uninstall unique constraint'), + ('test_uninstall_res_user_check_constraint', 'check (true)', 'Test uninstall check constraint'), + ('test_uninstall_res_user_exclude_constraint', 'exclude (password with =)', 'Test uninstall exclude constraint'), + ('test_uninstall_res_user_exclude_constraint_looooooooooooong_name', 'exclude (password with =)', 'Test uninstall exclude constraint'), + ] diff --git a/odoo/models.py b/odoo/models.py index 4b7d0a2f237..e550dce0a46 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -795,15 +795,8 @@ class BaseModel(metaclass=MetaModel): for mname, fnames in base._depends.items(): depends.setdefault(mname, []).extend(fnames) - for constraint in base._sql_constraints: - constraint_key = constraint[0] - if len(cls._table) + len(constraint_key) + 1 > 63: - _logger.warning( - 'Constrains `%s` combined to model table will have more than 63 character ' - 'and could be truncated leading to unexpected results', - constraint_key - ) - cls._sql_constraints[constraint_key] = constraint + for cons in base._sql_constraints: + cls._sql_constraints[cons[0]] = cons cls._sql_constraints = list(cls._sql_constraints.values()) @@ -2852,6 +2845,11 @@ class BaseModel(metaclass=MetaModel): for (key, definition, message) in self._sql_constraints: conname = '%s_%s' % (self._table, key) + if len(conname) > 63: + hashed_conname = tools.make_identifier(conname) + _logger.info("Constraint name %r has more than 63 characters, internal PG identifier is %r", conname, hashed_conname) + conname = hashed_conname + current_definition = tools.constraint_definition(cr, self._table, conname) if current_definition == definition: continue