From d790650bfb703ba30a7abde8d4d3d111034fbbb5 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Fri, 1 Jun 2012 15:14:14 +0200 Subject: [PATCH] [IMP] uninstall: use dedicated table instead of overloading ir.model.data. bzr revid: vmt@openerp.com-20120601131414-880vmpctmjfxl84f --- openerp/addons/base/ir/ir_model.py | 26 +------ openerp/addons/base/ir/ir_model_constraint.py | 22 ++++-- openerp/addons/base/module/module.py | 3 + openerp/osv/orm.py | 5 +- openerp/tests/addons/test_uninstall/models.py | 7 ++ t.py | 78 +++++++++++++++++-- 6 files changed, 99 insertions(+), 42 deletions(-) diff --git a/openerp/addons/base/ir/ir_model.py b/openerp/addons/base/ir/ir_model.py index 421342cd625..174f52b1944 100644 --- a/openerp/addons/base/ir/ir_model.py +++ b/openerp/addons/base/ir/ir_model.py @@ -29,8 +29,7 @@ from openerp import netsvc, pooler, tools from openerp.tools.safe_eval import safe_eval as eval from openerp.tools import config from openerp.tools.translate import _ -from openerp.osv.orm import except_orm, browse_record, EXT_ID_PREFIX_FK, \ - EXT_ID_PREFIX_M2M_TABLE, EXT_ID_PREFIX_CONSTRAINT +from openerp.osv.orm import except_orm, browse_record, EXT_ID_PREFIX_M2M_TABLE _logger = logging.getLogger(__name__) @@ -857,8 +856,7 @@ class ir_model_data(osv.osv): model_obj = self.pool.get(model) name = tools.ustr(data.name) - if name.startswith(EXT_ID_PREFIX_FK) or name.startswith(EXT_ID_PREFIX_M2M_TABLE)\ - or name.startswith(EXT_ID_PREFIX_CONSTRAINT): + if name.startswith(EXT_ID_PREFIX_M2M_TABLE): # double-check we are really going to delete all the owners of this schema element cr.execute("""SELECT id from ir_model_data where name = %s and res_id IS NULL""", (data.name,)) external_ids = [x[0] for x in cr.fetchall()] @@ -866,16 +864,6 @@ class ir_model_data(osv.osv): # as installed modules have defined this element we must not delete it! continue - if name.startswith(EXT_ID_PREFIX_FK): - name = name[len(EXT_ID_PREFIX_FK):] - # test if FK exists on this table (it could be on a related m2m table, in which case we ignore it) - 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""", ('f', name, model_obj._table)) - if cr.fetchone(): - cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),) - _logger.info('Dropped FK CONSTRAINT %s@%s', name, model) - continue - if name.startswith(EXT_ID_PREFIX_M2M_TABLE): name = name[len(EXT_ID_PREFIX_M2M_TABLE):] cr.execute("SELECT 1 FROM information_schema.tables WHERE table_name=%s", (name,)) @@ -883,16 +871,6 @@ class ir_model_data(osv.osv): to_drop_table.append(name) continue - if name.startswith(EXT_ID_PREFIX_CONSTRAINT): - name = name[len(EXT_ID_PREFIX_CONSTRAINT):] - # test if constraint exists - 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, model_obj._table)) - if cr.fetchone(): - cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),) - _logger.info('Dropped CONSTRAINT %s@%s', name, model) - continue - pair_to_unlink = (model, res_id) if pair_to_unlink not in to_unlink: to_unlink.append(pair_to_unlink) diff --git a/openerp/addons/base/ir/ir_model_constraint.py b/openerp/addons/base/ir/ir_model_constraint.py index 790b598763a..620edb32363 100644 --- a/openerp/addons/base/ir/ir_model_constraint.py +++ b/openerp/addons/base/ir/ir_model_constraint.py @@ -1,8 +1,12 @@ +import logging import openerp +from openerp import SUPERUSER_ID from openerp.osv import fields from openerp.osv.orm import Model +_logger = logging.getLogger(__name__) + class ir_model_constraint(Model): """ This model tracks PostgreSQL foreign keys and constraints used by OpenERP @@ -33,7 +37,7 @@ class ir_model_constraint(Model): Delete PostgreSQL foreign keys and constraints tracked by this model. """ - if uid != 1 and not self.pool.get('ir.model.access').check_groups(cr, uid, "base.group_system"): + if uid != SUPERUSER_ID and not self.pool.get('ir.model.access').check_groups(cr, uid, "base.group_system"): raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module'))) context = dict(context or {}) @@ -41,10 +45,11 @@ class ir_model_constraint(Model): ids_set = set(ids) ids.sort() ids.reverse() + to_unlink = [] for data in self.browse(cr, uid, ids, context): - model = data.model + model = data.model.name model_obj = self.pool.get(model) - name = tools.ustr(data.name) + name = openerp.tools.ustr(data.name) typ = data.type # double-check we are really going to delete all the owners of this schema element @@ -52,20 +57,25 @@ class ir_model_constraint(Model): external_ids = [x[0] for x in cr.fetchall()] if (set(external_ids)-ids_set): # as installed modules have defined this element we must not delete it! - pass + continue - elif typ == 'f': + if typ == 'f': # test if FK exists on this table (it could be on a related m2m table, in which case we ignore it) 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""", ('f', name, model_obj._table)) if cr.fetchone(): + print '>>> ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name) cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),) _logger.info('Dropped FK CONSTRAINT %s@%s', name, model) - elif typ == 'u': + if typ == 'u': # test if constraint exists 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, model_obj._table)) if cr.fetchone(): + print '>>> ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name) cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),) _logger.info('Dropped CONSTRAINT %s@%s', name, model) + + to_unlink.append(data.id) + self.unlink(cr, uid, to_unlink, context) diff --git a/openerp/addons/base/module/module.py b/openerp/addons/base/module/module.py index e04e4e4a02b..371bfb1b93c 100644 --- a/openerp/addons/base/module/module.py +++ b/openerp/addons/base/module/module.py @@ -364,7 +364,10 @@ class module(osv.osv): including the deletion of all database structures created by the module: tables, columns, constraints, etc.""" ir_model_data = self.pool.get('ir.model.data') + ir_model_constraint = self.pool.get('ir.model.constraint') modules_to_remove = [m.name for m in self.browse(cr, uid, ids, context)] + constraint_ids = ir_model_constraint.search(cr, uid, [('module', 'in', modules_to_remove)]) + ir_model_constraint._module_data_uninstall(cr, uid, constraint_ids, context) data_ids = ir_model_data.search(cr, uid, [('module', 'in', modules_to_remove)]) ir_model_data._module_data_uninstall(cr, uid, data_ids, context) ir_model_data.unlink(cr, uid, data_ids, context) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index be55c1d305d..64c627849ac 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -71,9 +71,7 @@ _schema = logging.getLogger(__name__ + '.schema') from openerp.tools import SKIPPED_ELEMENT_TYPES # Prefixes for external IDs of schema elements -EXT_ID_PREFIX_FK = "_foreign_key_" EXT_ID_PREFIX_M2M_TABLE = "_m2m_rel_table_" -EXT_ID_PREFIX_CONSTRAINT = "_constraint_" regex_order = re.compile('^(([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I) regex_object_name = re.compile(r'^[a-z0-9_.]+$') @@ -3108,7 +3106,6 @@ class BaseModel(object): """ Create the foreign keys recorded by _auto_init. """ for t, k, r, d in self._foreign_keys: cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (t, k, r, d)) - self._make_ext_id(cr, "%s%s_%s_fkey" % (EXT_ID_PREFIX_FK, t, k)) self._save_constraint(cr, "%s_%s_fkey" % (t, k), 'f') cr.commit() del self._foreign_keys @@ -3234,7 +3231,7 @@ class BaseModel(object): for (key, con, _) in self._sql_constraints: conname = '%s_%s' % (self._table, key) - self._make_ext_id(cr, EXT_ID_PREFIX_CONSTRAINT + conname) + self._save_constraint(cr, conname, 'u') cr.execute("SELECT conname, pg_catalog.pg_get_constraintdef(oid, true) as condef FROM pg_constraint where conname=%s", (conname,)) existing_constraints = cr.dictfetchall() sql_actions = { diff --git a/openerp/tests/addons/test_uninstall/models.py b/openerp/tests/addons/test_uninstall/models.py index 645853cc2f8..335aedc941a 100644 --- a/openerp/tests/addons/test_uninstall/models.py +++ b/openerp/tests/addons/test_uninstall/models.py @@ -1,11 +1,18 @@ # -*- coding: utf-8 -*- import openerp +from openerp.osv import fields from openerp.osv.orm import Model class test_uninstall_model(Model): _name = 'test_uninstall.model' _columns = { + 'name': fields.char('Name', size=64), + 'ref': fields.many2one('res.users', string='User'), } + _sql_constraints = [ + ('name_uniq', 'unique (name)', 'Each name must be unique.') + ] + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/t.py b/t.py index f5497943a59..7fc33f7d281 100644 --- a/t.py +++ b/t.py @@ -1,18 +1,80 @@ import openerp +from openerp import SUPERUSER_ID from openerp.osv import fields from openerp.osv.orm import Model conf = openerp.tools.config +# TODO Exception handling (especially on cursors). + +def get_registry(database_name): + registry = openerp.modules.registry.RegistryManager.get(database_name) + return registry + +def reload_registry(database_name): + openerp.modules.registry.RegistryManager.new( + database_name, update_module=True) + +def search_registry(database_name, model_name, domain): + registry = get_registry(database_name) + cr = registry.db.cursor() + model = registry.get(model_name) + record_ids = model.search(cr, SUPERUSER_ID, + domain, {}) + cr.close() + return record_ids + +def install_module(database_name, module_name): + registry = get_registry(database_name) + ir_module_module = registry.get('ir.module.module') + cr = registry.db.cursor() + module_ids = ir_module_module.search(cr, SUPERUSER_ID, + [('name', '=', module_name)], {}) + assert len(module_ids) == 1 + ir_module_module.button_install(cr, SUPERUSER_ID, module_ids, {}) + cr.commit() + cr.close() + reload_registry(database_name) + +def uninstall_module(database_name, module_name): + registry = get_registry(database_name) + ir_module_module = registry.get('ir.module.module') + cr = registry.db.cursor() + module_ids = ir_module_module.search(cr, SUPERUSER_ID, + [('name', '=', module_name)], {}) + assert len(module_ids) == 1 + ir_module_module.button_uninstall(cr, SUPERUSER_ID, module_ids, {}) + cr.commit() + cr.close() + reload_registry(database_name) + if __name__ == '__main__': openerp.netsvc.init_logger() - conf['addons_path'] = './openerp/tests/addons' - conf['init'] = {'test_uninstall': 1} - registry = openerp.modules.registry.RegistryManager.new('xx', update_module=True) + conf['addons_path'] = './openerp/tests/addons,../../addons/trunk,../../web/trunk/addons' + + install_module('xx', 'test_uninstall') + registry = get_registry('xx') + assert registry.get('test_uninstall.model') + + assert search_registry('xx', 'ir.model.data', + [('module', '=', 'test_uninstall')]) + + assert search_registry('xx', 'ir.model.fields', + [('model', '=', 'test_uninstall.model')]) + + uninstall_module('xx', 'test_uninstall') + registry = get_registry('xx') + assert not registry.get('test_uninstall.model') + + assert not search_registry('xx', 'ir.model.data', + [('module', '=', 'test_uninstall')]) + + assert not search_registry('xx', 'ir.model.fields', + [('model', '=', 'test_uninstall.model')]) + ir_model_constraint = registry.get('ir.model.constraint') - print ir_model_constraint cr = registry.db.cursor() - ids = ir_model_constraint.search(cr, openerp.SUPERUSER_ID, [], {}) - print ir_model_constraint.browse(cr, openerp.SUPERUSER_ID, ids, {}) + ids = ir_model_constraint.search(cr, SUPERUSER_ID, [], {}) + #print ir_model_constraint.browse(cr, SUPERUSER_ID, ids, {}) cr.close() ##################################################################### @@ -33,8 +95,8 @@ MY_MODULE = { 'depends': ['base'], } -def create_virtual_module(db_name, module_name, info): - registry = openerp.modules.registry.RegistryManager.get(db_name) +def create_virtual_module(database_name, module_name, info): + registry = get_registry(database_name) cr = registry.db.cursor() cr.execute("""SELECT 1 FROM ir_module_module WHERE name=%s""", (module_name,))