From 5df4aa8ccad22430cc1467ea0820c2bb16aaf726 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Thu, 31 May 2012 18:01:40 +0200 Subject: [PATCH 01/13] [IMP] uninstall: started test module, added new ir_model_constraint table. bzr revid: vmt@openerp.com-20120531160140-tutjw76tp0u5hipo --- openerp/addons/base/base.sql | 19 +++++ openerp/addons/base/ir/__init__.py | 1 + openerp/addons/base/ir/ir_model_constraint.py | 71 +++++++++++++++++++ openerp/osv/orm.py | 18 +++++ .../tests/addons/test_uninstall/__init__.py | 3 + .../addons/test_uninstall/__openerp__.py | 15 ++++ openerp/tests/addons/test_uninstall/models.py | 11 +++ t.py | 66 +++++++++++++++++ 8 files changed, 204 insertions(+) create mode 100644 openerp/addons/base/ir/ir_model_constraint.py create mode 100644 openerp/tests/addons/test_uninstall/__init__.py create mode 100644 openerp/tests/addons/test_uninstall/__openerp__.py create mode 100644 openerp/tests/addons/test_uninstall/models.py create mode 100644 t.py diff --git a/openerp/addons/base/base.sql b/openerp/addons/base/base.sql index eb9966bddcd..a227007353c 100644 --- a/openerp/addons/base/base.sql +++ b/openerp/addons/base/base.sql @@ -347,6 +347,25 @@ CREATE TABLE ir_model_data ( res_id integer, primary key(id) ); +-- Records foreign keys and constraints +-- installed by a module (so they can be removed them when the module is +-- uninstalled).: +-- - for a foreign key: type 'f', +-- - for a constraint: type 'u'. +CREATE TABLE ir_model_constraint ( + id serial NOT NULL, + create_uid integer, + create_date timestamp without time zone, + write_date timestamp without time zone, + write_uid integer, + date_init timestamp without time zone, + date_update timestamp without time zone, + module integer NOT NULL references ir_module_module on delete restrict, + model integer NOT NULL references ir_model on delete restrict, + type character varying(1) NOT NULL, + name character varying(128) NOT NULL +); + --------------------------------- -- Users --------------------------------- diff --git a/openerp/addons/base/ir/__init__.py b/openerp/addons/base/ir/__init__.py index a2a4b999ed8..f4605d1615f 100644 --- a/openerp/addons/base/ir/__init__.py +++ b/openerp/addons/base/ir/__init__.py @@ -20,6 +20,7 @@ ############################################################################## import ir_model +import ir_model_constraint import ir_sequence import ir_needaction import ir_ui_menu diff --git a/openerp/addons/base/ir/ir_model_constraint.py b/openerp/addons/base/ir/ir_model_constraint.py new file mode 100644 index 00000000000..790b598763a --- /dev/null +++ b/openerp/addons/base/ir/ir_model_constraint.py @@ -0,0 +1,71 @@ + +import openerp +from openerp.osv import fields +from openerp.osv.orm import Model + +class ir_model_constraint(Model): + """ + This model tracks PostgreSQL foreign keys and constraints used by OpenERP + models. + """ + _name = 'ir.model.constraint' + _columns = { + 'name': fields.char('Constraint', required=True, size=128, select=1, + help="PostgreSQL constraint or foreign key name."), + 'model': fields.many2one('ir.model', string='Model Name', + required=True, select=1), + 'module': fields.many2one('ir.module.module', string='Module Name', + required=True, select=1), + 'type': fields.char('Constraint Type', required=True, size=1, select=1, + help="Type of the constraint: `f` for a foreign key, " + "`u` for other constraints."), + 'date_update': fields.datetime('Update Date'), + 'date_init': fields.datetime('Initialization Date') + } + + _sql_constraints = [ + ('module_name_uniq', 'unique(name, module)', + 'Constraints with the same name are unique per module.'), + ] + + def _module_data_uninstall(self, cr, uid, ids, context=None): + """ + 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"): + raise except_orm(_('Permission Denied'), (_('Administrator access is required to uninstall a module'))) + + context = dict(context or {}) + + ids_set = set(ids) + ids.sort() + ids.reverse() + for data in self.browse(cr, uid, ids, context): + model = data.model + model_obj = self.pool.get(model) + name = tools.ustr(data.name) + typ = data.type + + # double-check we are really going to delete all the owners of this schema element + cr.execute("""SELECT id from ir_model_constraint where name=%s""", (data.name,)) + 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 + + elif 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(): + cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),) + _logger.info('Dropped FK CONSTRAINT %s@%s', name, model) + + elif 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(): + cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),) + _logger.info('Dropped CONSTRAINT %s@%s', name, model) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index e99296a15e1..be55c1d305d 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -2744,6 +2744,23 @@ class BaseModel(object): _schema.debug("Table '%s': column '%s': dropped NOT NULL constraint", self._table, column['attname']) + def _save_constraint(self, cr, constraint_name, type): + assert type in ('f', 'u') + cr.execute(""" + SELECT 1 FROM ir_model_constraint, ir_module_module + WHERE ir_model_constraint.module=ir_module_module.id + AND ir_model_constraint.name=%s + AND ir_module_module.name=%s + """, (constraint_name, self._module)) + if not cr.rowcount: + cr.execute(""" + INSERT INTO ir_model_constraint + (name, date_init, date_update, module, model, type) + VALUES (%s, now() AT TIME ZONE 'UTC', now() AT TIME ZONE 'UTC', + (SELECT id FROM ir_module_module WHERE name=%s), + (SELECT id FROM ir_model WHERE model=%s), %s)""", + (constraint_name, self._module, self._name, type)) + # quick creation of ir.model.data entry to make uninstall of schema elements easier def _make_ext_id(self, cr, ext_id): cr.execute('SELECT 1 FROM ir_model_data WHERE name=%s AND module=%s', (ext_id, self._module)) @@ -3092,6 +3109,7 @@ class BaseModel(object): 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 diff --git a/openerp/tests/addons/test_uninstall/__init__.py b/openerp/tests/addons/test_uninstall/__init__.py new file mode 100644 index 00000000000..fe4487156b1 --- /dev/null +++ b/openerp/tests/addons/test_uninstall/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +import models +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/tests/addons/test_uninstall/__openerp__.py b/openerp/tests/addons/test_uninstall/__openerp__.py new file mode 100644 index 00000000000..d8776897df0 --- /dev/null +++ b/openerp/tests/addons/test_uninstall/__openerp__.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +{ + 'name': 'test-uninstall', + 'version': '0.1', + 'category': 'Tests', + 'description': """A module to test the uninstall feature.""", + 'author': 'OpenERP SA', + 'maintainer': 'OpenERP SA', + 'website': 'http://www.openerp.com', + 'depends': ['base'], + 'data': [], + 'installable': True, + 'auto_install': False, +} +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/tests/addons/test_uninstall/models.py b/openerp/tests/addons/test_uninstall/models.py new file mode 100644 index 00000000000..645853cc2f8 --- /dev/null +++ b/openerp/tests/addons/test_uninstall/models.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +import openerp +from openerp.osv.orm import Model + +class test_uninstall_model(Model): + _name = 'test_uninstall.model' + + _columns = { + } + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/t.py b/t.py new file mode 100644 index 00000000000..f5497943a59 --- /dev/null +++ b/t.py @@ -0,0 +1,66 @@ +import openerp +from openerp.osv import fields +from openerp.osv.orm import Model +conf = openerp.tools.config + +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) + 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, {}) + cr.close() + +##################################################################### + +# Nice idea, but won't work without some more change to the framework (which +# expects everything on disk, maybe we can craft a zip file...). + +MY_MODULE = { + 'author': 'Jean Beauvoir', + 'website': 'http://www.youtube.com/watch?v=FeO5DfdZi7Y', + 'name': 'FEEL THE HEAT', + 'description': "Cobra's theme", + 'web': False, + 'license': 'WTFPL', + 'application': False, + 'icon': False, + 'sequence': 100, + 'depends': ['base'], +} + +def create_virtual_module(db_name, module_name, info): + registry = openerp.modules.registry.RegistryManager.get(db_name) + cr = registry.db.cursor() + + cr.execute("""SELECT 1 FROM ir_module_module WHERE name=%s""", (module_name,)) + if cr.fetchone(): return + + category_id = openerp.modules.db.create_categories(cr, ['Tests']) + cr.execute('INSERT INTO ir_module_module \ + (author, website, name, shortdesc, description, \ + category_id, auto_install, state, certificate, web, license, application, icon, sequence) \ + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id', ( + info['author'], + info['website'], module_name, info['name'], + info['description'], category_id, + True, 'to install', False, + info['web'], + info['license'], + info['application'], info['icon'], + info['sequence'])) + module_id = cr.fetchone()[0] + cr.execute('INSERT INTO ir_model_data \ + (name,model,module, res_id, noupdate) VALUES (%s,%s,%s,%s,%s)', ( + 'module_' + module_name, 'ir.module.module', 'base', module_id, True)) + dependencies = info['depends'] + for d in dependencies: + cr.execute('INSERT INTO ir_module_module_dependency \ + (module_id,name) VALUES (%s, %s)', (module_id, d)) + + cr.commit() + cr.close() From d790650bfb703ba30a7abde8d4d3d111034fbbb5 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Fri, 1 Jun 2012 15:14:14 +0200 Subject: [PATCH 02/13] [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,)) From eadd5e0a0a6aa8babfbedce91f28f9701c2812ea Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Fri, 1 Jun 2012 17:34:49 +0200 Subject: [PATCH 03/13] [IMP] uninstall: use a dedicated table for many2many relationships instead of overlaoding ir.model.data. bzr revid: vmt@openerp.com-20120601153449-uj2sl7qjzz3s7xnj --- openerp/addons/base/base.sql | 20 ++++++++++++--- openerp/addons/base/ir/__init__.py | 1 + openerp/addons/base/ir/ir_model.py | 23 +---------------- openerp/addons/base/ir/ir_model_constraint.py | 2 -- openerp/addons/base/module/module.py | 3 +++ openerp/osv/orm.py | 25 +++++++++++-------- openerp/tests/addons/test_uninstall/models.py | 1 + 7 files changed, 38 insertions(+), 37 deletions(-) diff --git a/openerp/addons/base/base.sql b/openerp/addons/base/base.sql index a227007353c..0a8f50c24bf 100644 --- a/openerp/addons/base/base.sql +++ b/openerp/addons/base/base.sql @@ -347,9 +347,8 @@ CREATE TABLE ir_model_data ( res_id integer, primary key(id) ); --- Records foreign keys and constraints --- installed by a module (so they can be removed them when the module is --- uninstalled).: +-- Records foreign keys and constraints installed by a module (so they can be +-- removed them when the module is uninstalled): -- - for a foreign key: type 'f', -- - for a constraint: type 'u'. CREATE TABLE ir_model_constraint ( @@ -366,6 +365,21 @@ CREATE TABLE ir_model_constraint ( name character varying(128) NOT NULL ); +-- Records relation tables (i.e. implementing many2many) installed by a module +-- (so they can be removed them when the module is uninstalled). +CREATE TABLE ir_model_relation ( + id serial NOT NULL, + create_uid integer, + create_date timestamp without time zone, + write_date timestamp without time zone, + write_uid integer, + date_init timestamp without time zone, + date_update timestamp without time zone, + module integer NOT NULL references ir_module_module on delete restrict, + model integer NOT NULL references ir_model on delete restrict, + name character varying(128) NOT NULL +); + --------------------------------- -- Users --------------------------------- diff --git a/openerp/addons/base/ir/__init__.py b/openerp/addons/base/ir/__init__.py index f4605d1615f..bc4f3eecfbf 100644 --- a/openerp/addons/base/ir/__init__.py +++ b/openerp/addons/base/ir/__init__.py @@ -21,6 +21,7 @@ import ir_model import ir_model_constraint +import ir_model_relation import ir_sequence import ir_needaction import ir_ui_menu diff --git a/openerp/addons/base/ir/ir_model.py b/openerp/addons/base/ir/ir_model.py index 174f52b1944..231b19bb3f8 100644 --- a/openerp/addons/base/ir/ir_model.py +++ b/openerp/addons/base/ir/ir_model.py @@ -29,7 +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_M2M_TABLE +from openerp.osv.orm import except_orm, browse_record _logger = logging.getLogger(__name__) @@ -847,7 +847,6 @@ class ir_model_data(osv.osv): ids_set = set(ids) wkf_todo = [] to_unlink = [] - to_drop_table = [] ids.sort() ids.reverse() for data in self.browse(cr, uid, ids, context): @@ -856,21 +855,6 @@ class ir_model_data(osv.osv): model_obj = self.pool.get(model) name = tools.ustr(data.name) - 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()] - if (set(external_ids)-ids_set): - # as installed modules have defined this element we must not delete it! - 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,)) - if cr.fetchone() and not name in to_drop_table: - to_drop_table.append(name) - continue - pair_to_unlink = (model, res_id) if pair_to_unlink not in to_unlink: to_unlink.append(pair_to_unlink) @@ -890,11 +874,6 @@ class ir_model_data(osv.osv): except: _logger.info('Unable to force processing of workflow for item %s@%s in order to leave activity to be deleted', res_id, model) - # drop m2m relation tables - for table in to_drop_table: - cr.execute('DROP TABLE %s CASCADE'% (table),) - _logger.info('Dropped table %s', table) - def unlink_if_refcount(to_unlink): for model, res_id in to_unlink: external_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)]) diff --git a/openerp/addons/base/ir/ir_model_constraint.py b/openerp/addons/base/ir/ir_model_constraint.py index 620edb32363..912284a8216 100644 --- a/openerp/addons/base/ir/ir_model_constraint.py +++ b/openerp/addons/base/ir/ir_model_constraint.py @@ -64,7 +64,6 @@ class ir_model_constraint(Model): 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) @@ -73,7 +72,6 @@ class ir_model_constraint(Model): 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) diff --git a/openerp/addons/base/module/module.py b/openerp/addons/base/module/module.py index 371bfb1b93c..20151946ed0 100644 --- a/openerp/addons/base/module/module.py +++ b/openerp/addons/base/module/module.py @@ -365,9 +365,12 @@ class module(osv.osv): tables, columns, constraints, etc.""" ir_model_data = self.pool.get('ir.model.data') ir_model_constraint = self.pool.get('ir.model.constraint') + ir_model_relation = self.pool.get('ir.model.relation') 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) + relation_ids = ir_model_relation.search(cr, uid, [('module', 'in', modules_to_remove)]) + ir_model_relation._module_data_uninstall(cr, uid, relation_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 64c627849ac..51485b83266 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -70,9 +70,6 @@ _schema = logging.getLogger(__name__ + '.schema') # List of etree._Element subclasses that we choose to ignore when parsing XML. from openerp.tools import SKIPPED_ELEMENT_TYPES -# Prefixes for external IDs of schema elements -EXT_ID_PREFIX_M2M_TABLE = "_m2m_rel_table_" - regex_order = re.compile('^(([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I) regex_object_name = re.compile(r'^[a-z0-9_.]+$') @@ -2743,6 +2740,7 @@ class BaseModel(object): self._table, column['attname']) def _save_constraint(self, cr, constraint_name, type): + """ TODO """ assert type in ('f', 'u') cr.execute(""" SELECT 1 FROM ir_model_constraint, ir_module_module @@ -2759,13 +2757,20 @@ class BaseModel(object): (SELECT id FROM ir_model WHERE model=%s), %s)""", (constraint_name, self._module, self._name, type)) - # quick creation of ir.model.data entry to make uninstall of schema elements easier - def _make_ext_id(self, cr, ext_id): - cr.execute('SELECT 1 FROM ir_model_data WHERE name=%s AND module=%s', (ext_id, self._module)) + def _save_relation_table(self, cr, relation_table): + """ TODO """ + cr.execute(""" + SELECT 1 FROM ir_model_relation, ir_module_module + WHERE ir_model_relation.module=ir_module_module.id + AND ir_model_relation.name=%s + AND ir_module_module.name=%s + """, (relation_table, self._module)) if not cr.rowcount: - cr.execute("""INSERT INTO ir_model_data (name,date_init,date_update,module,model) - VALUES (%s, now() AT TIME ZONE 'UTC', now() AT TIME ZONE 'UTC', %s, %s)""", - (ext_id, self._module, self._name)) + cr.execute("""INSERT INTO ir_model_relation (name, date_init, date_update, module, model) + VALUES (%s, now() AT TIME ZONE 'UTC', now() AT TIME ZONE 'UTC', + (SELECT id FROM ir_module_module WHERE name=%s), + (SELECT id FROM ir_model WHERE model=%s))""", + (relation_table, self._module, self._name)) # checked version: for direct m2o starting from `self` def _m2o_add_foreign_key_checked(self, source_field, dest_model, ondelete): @@ -3195,7 +3200,7 @@ class BaseModel(object): def _m2m_raise_or_create_relation(self, cr, f): m2m_tbl, col1, col2 = f._sql_names(self) - self._make_ext_id(cr, EXT_ID_PREFIX_M2M_TABLE + m2m_tbl) + self._save_relation_table(cr, m2m_tbl) cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (m2m_tbl,)) if not cr.dictfetchall(): if not self.pool.get(f._obj): diff --git a/openerp/tests/addons/test_uninstall/models.py b/openerp/tests/addons/test_uninstall/models.py index 335aedc941a..d7211dc2130 100644 --- a/openerp/tests/addons/test_uninstall/models.py +++ b/openerp/tests/addons/test_uninstall/models.py @@ -9,6 +9,7 @@ class test_uninstall_model(Model): _columns = { 'name': fields.char('Name', size=64), 'ref': fields.many2one('res.users', string='User'), + 'rel': fields.many2many('res.users', string='Users'), } _sql_constraints = [ From 9f46071958135b43b752246219503e30337749d0 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Fri, 1 Jun 2012 17:45:44 +0200 Subject: [PATCH 04/13] [IMP] uninstall: added some docstrings. bzr revid: vmt@openerp.com-20120601154544-e9a9cox7fcyd411m --- openerp/osv/orm.py | 11 +++++++++-- openerp/tests/addons/test_uninstall/models.py | 4 ++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 51485b83266..5fe926604d3 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -2740,7 +2740,11 @@ class BaseModel(object): self._table, column['attname']) def _save_constraint(self, cr, constraint_name, type): - """ TODO """ + """ + Record the creation of a constraint for this model, to make it possible + to delete it later when the module is uninstalled. Type can be either + 'f' or 'u' depending on the constraing being a foreign key or not. + """ assert type in ('f', 'u') cr.execute(""" SELECT 1 FROM ir_model_constraint, ir_module_module @@ -2758,7 +2762,10 @@ class BaseModel(object): (constraint_name, self._module, self._name, type)) def _save_relation_table(self, cr, relation_table): - """ TODO """ + """ + Record the creation of a many2many for this model, to make it possible + to delete it later when the module is uninstalled. + """ cr.execute(""" SELECT 1 FROM ir_model_relation, ir_module_module WHERE ir_model_relation.module=ir_module_module.id diff --git a/openerp/tests/addons/test_uninstall/models.py b/openerp/tests/addons/test_uninstall/models.py index d7211dc2130..96a5800917d 100644 --- a/openerp/tests/addons/test_uninstall/models.py +++ b/openerp/tests/addons/test_uninstall/models.py @@ -4,6 +4,10 @@ from openerp.osv import fields from openerp.osv.orm import Model class test_uninstall_model(Model): + """ + This model uses different types of columns to make it possible to test + the uninstall feature of OpenERP. + """ _name = 'test_uninstall.model' _columns = { From 15a45794763e547b13c2eb97370557a8e4b36ff5 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Fri, 1 Jun 2012 17:46:47 +0200 Subject: [PATCH 05/13] [FIX] uninstall: forgot to add a file to bzr. bzr revid: vmt@openerp.com-20120601154647-qhidwpw2bb3mlezw --- openerp/addons/base/ir/ir_model_relation.py | 65 +++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 openerp/addons/base/ir/ir_model_relation.py diff --git a/openerp/addons/base/ir/ir_model_relation.py b/openerp/addons/base/ir/ir_model_relation.py new file mode 100644 index 00000000000..2d147f5ee6e --- /dev/null +++ b/openerp/addons/base/ir/ir_model_relation.py @@ -0,0 +1,65 @@ +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_relation(Model): + """ + This model tracks PostgreSQL tables used to implement OpenERP many2many + relations. + """ + _name = 'ir.model.relation' + _columns = { + 'name': fields.char('Relation Name', required=True, size=128, select=1, + help="PostgreSQL table name implementing a many2many relation."), + 'model': fields.many2one('ir.model', string='Model Name', + required=True, select=1), + 'module': fields.many2one('ir.module.module', string='Module Name', + required=True, select=1), + 'date_update': fields.datetime('Update Date'), + 'date_init': fields.datetime('Initialization Date') + } + + def _module_data_uninstall(self, cr, uid, ids, context=None): + """ + Delete PostgreSQL many2many relations tracked by this model. + """ + + 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'))) + + ids_set = set(ids) + to_drop_table = [] + ids.sort() + ids.reverse() + to_unlink = [] + for data in self.browse(cr, uid, ids, context): + model = data.model + model_obj = self.pool.get(model) + name = openerp.tools.ustr(data.name) + + # double-check we are really going to delete all the owners of this schema element + cr.execute("""SELECT id from ir_model_relation where name = %s""", (data.name,)) + 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! + continue + + cr.execute("SELECT 1 FROM information_schema.tables WHERE table_name=%s", (name,)) + if cr.fetchone() and not name in to_drop_table: + to_drop_table.append(name) + + to_unlink.append(data.id) + + self.unlink(cr, uid, to_unlink, context) + + # drop m2m relation tables + for table in to_drop_table: + cr.execute('DROP TABLE %s CASCADE'% (table),) + _logger.info('Dropped table %s', table) + + cr.commit() From 30dfa719977c832f0d39c67163a973eefd492f37 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Fri, 1 Jun 2012 18:03:01 +0200 Subject: [PATCH 06/13] [IMP] uninstall: added views for ir_model_constraint (breaking a test by the way). bzr revid: vmt@openerp.com-20120601160301-b4xygkob9wuxllk9 --- openerp/addons/base/ir/ir.xml | 39 +++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/openerp/addons/base/ir/ir.xml b/openerp/addons/base/ir/ir.xml index 837374d371b..a63b8477bbc 100644 --- a/openerp/addons/base/ir/ir.xml +++ b/openerp/addons/base/ir/ir.xml @@ -1128,6 +1128,36 @@ + + ir.model.constraint.form + ir.model.constraint + form + +
+ + + + + + + + + +
+ + + ir.model.constraint.list + ir.model.constraint + tree + + + + + + + + + Models @@ -1157,6 +1187,15 @@ + + Model Constraints + ir.model.constraint + form + + + + From 71211720a0825a8e13e4bbf14ee0a4c31899b3be Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Mon, 4 Jun 2012 10:22:06 +0200 Subject: [PATCH 07/13] [IMP] uninstall: added views for ir.model.relation. bzr revid: vmt@openerp.com-20120604082206-h6ua22byq3otx53p --- openerp/addons/base/ir/ir.xml | 40 ++++++++++++++++++- openerp/addons/base/ir/ir_model_constraint.py | 4 +- openerp/addons/base/ir/ir_model_relation.py | 4 +- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/openerp/addons/base/ir/ir.xml b/openerp/addons/base/ir/ir.xml index a63b8477bbc..b32ba9e8a5a 100644 --- a/openerp/addons/base/ir/ir.xml +++ b/openerp/addons/base/ir/ir.xml @@ -1159,6 +1159,35 @@ + + ir.model.relation.form + ir.model.relation + form + +
+ + + + + + + + +
+ + + ir.model.relation.list + ir.model.relation + tree + + + + + + + + + Models ir.model @@ -1193,7 +1222,16 @@ form - + + + ManyToMany Relations + ir.model.relation + form + + + diff --git a/openerp/addons/base/ir/ir_model_constraint.py b/openerp/addons/base/ir/ir_model_constraint.py index 912284a8216..05d47df462d 100644 --- a/openerp/addons/base/ir/ir_model_constraint.py +++ b/openerp/addons/base/ir/ir_model_constraint.py @@ -16,9 +16,9 @@ class ir_model_constraint(Model): _columns = { 'name': fields.char('Constraint', required=True, size=128, select=1, help="PostgreSQL constraint or foreign key name."), - 'model': fields.many2one('ir.model', string='Model Name', + 'model': fields.many2one('ir.model', string='Model', required=True, select=1), - 'module': fields.many2one('ir.module.module', string='Module Name', + 'module': fields.many2one('ir.module.module', string='Module', required=True, select=1), 'type': fields.char('Constraint Type', required=True, size=1, select=1, help="Type of the constraint: `f` for a foreign key, " diff --git a/openerp/addons/base/ir/ir_model_relation.py b/openerp/addons/base/ir/ir_model_relation.py index 2d147f5ee6e..eede6eae44f 100644 --- a/openerp/addons/base/ir/ir_model_relation.py +++ b/openerp/addons/base/ir/ir_model_relation.py @@ -16,9 +16,9 @@ class ir_model_relation(Model): _columns = { 'name': fields.char('Relation Name', required=True, size=128, select=1, help="PostgreSQL table name implementing a many2many relation."), - 'model': fields.many2one('ir.model', string='Model Name', + 'model': fields.many2one('ir.model', string='Model', required=True, select=1), - 'module': fields.many2one('ir.module.module', string='Module Name', + 'module': fields.many2one('ir.module.module', string='Module', required=True, select=1), 'date_update': fields.datetime('Update Date'), 'date_init': fields.datetime('Initialization Date') From fa4c2a40c0659b2aef895addbbc56ca900a09fa6 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Mon, 4 Jun 2012 12:44:04 +0200 Subject: [PATCH 08/13] [IMP] uninstall: moving tests to the openerp test framework (i.e. under openerp/tests/). bzr revid: vmt@openerp.com-20120604104404-zibru6794ma1996x --- openerp/tests/__init__.py | 1 + openerp/tests/test_uninstall.py | 81 +++++++++++++++++++++++++++++++++ t.py | 77 ++++++++++++++++--------------- 3 files changed, 123 insertions(+), 36 deletions(-) create mode 100644 openerp/tests/test_uninstall.py diff --git a/openerp/tests/__init__.py b/openerp/tests/__init__.py index 5fccb07a082..913c5c759c9 100644 --- a/openerp/tests/__init__.py +++ b/openerp/tests/__init__.py @@ -11,6 +11,7 @@ See the :ref:`test-framework` section in the :ref:`features` list. import test_expression import test_ir_sequence import test_orm +import test_uninstall fast_suite = [ test_ir_sequence, diff --git a/openerp/tests/test_uninstall.py b/openerp/tests/test_uninstall.py new file mode 100644 index 00000000000..4cc396e3d3c --- /dev/null +++ b/openerp/tests/test_uninstall.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +# Run with one of these commands: +# > OPENERP_ADDONS_PATH='../../addons/trunk' OPENERP_PORT=8069 \ +# OPENERP_DATABASE=yy PYTHONPATH=. python tests/test_ir_sequence.py +# > OPENERP_ADDONS_PATH='../../addons/trunk' OPENERP_PORT=8069 \ +# OPENERP_DATABASE=yy nosetests tests/test_ir_sequence.py +# > OPENERP_ADDONS_PATH='../../../addons/trunk' OPENERP_PORT=8069 \ +# OPENERP_DATABASE=yy PYTHONPATH=../:. unit2 test_ir_sequence +# This assume an existing database. +import psycopg2 +import unittest2 + +import openerp +from openerp import SUPERUSER_ID +import common + +DB = common.DB +ADMIN_USER_ID = common.ADMIN_USER_ID + +def registry(model): + return openerp.modules.registry.RegistryManager.get(DB)[model] + +def cursor(): + return openerp.modules.registry.RegistryManager.get(DB).db.cursor() + +def get_module(module_name): + registry = openerp.modules.registry.RegistryManager.get(DB) + return registry.get(module_name) + +def reload_registry(): + openerp.modules.registry.RegistryManager.new( + DB, update_module=True) + +def search_registry(model_name, domain): + cr = cursor() + model = registry(model_name) + record_ids = model.search(cr, SUPERUSER_ID, domain, {}) + cr.close() + return record_ids + +def install_module(module_name): + ir_module_module = registry('ir.module.module') + cr = 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() + +def uninstall_module(module_name): + ir_module_module = registry('ir.module.module') + cr = 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() + +class test_uninstall(unittest2.TestCase): + """ Test install/uninstall of a test module. """ + + def test_00_nothing(self): + """ Test the assumption the test module is not installed yet. """ + assert not get_module('test_uninstall.model') + + assert not search_registry('ir.model.data', + [('module', '=', 'test_uninstall')]) + + assert not search_registry('ir.model.fields', + [('model', '=', 'test_uninstall.model')]) + + +if __name__ == '__main__': + unittest2.main() + + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/t.py b/t.py index 7fc33f7d281..8529145d4fa 100644 --- a/t.py +++ b/t.py @@ -1,3 +1,5 @@ +import sys + import openerp from openerp import SUPERUSER_ID from openerp.osv import fields @@ -6,73 +8,77 @@ 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 +DB = None -def reload_registry(database_name): +def registry(model_name): + return openerp.modules.registry.RegistryManager.get(DB)[model_name] + +def cursor(): + return openerp.modules.registry.RegistryManager.get(DB).db.cursor() + +def get_module(module_name): + registry = openerp.modules.registry.RegistryManager.get(DB) + return registry.get(module_name) + +def reload_registry(): openerp.modules.registry.RegistryManager.new( - database_name, update_module=True) + DB, 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, {}) +def search_registry(model_name, domain): + cr = cursor() + model = registry(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() +def install_module(module_name): + ir_module_module = registry('ir.module.module') + cr = 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) + reload_registry() -def uninstall_module(database_name, module_name): - registry = get_registry(database_name) - ir_module_module = registry.get('ir.module.module') - cr = registry.db.cursor() +def uninstall_module(module_name): + ir_module_module = registry('ir.module.module') + cr = 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) + reload_registry() if __name__ == '__main__': + assert len(sys.argv) == 2 + DB = sys.argv[1] openerp.netsvc.init_logger() 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') + install_module('test_uninstall') + assert get_module('test_uninstall.model') - assert search_registry('xx', 'ir.model.data', + assert search_registry('ir.model.data', [('module', '=', 'test_uninstall')]) - assert search_registry('xx', 'ir.model.fields', + assert search_registry('ir.model.fields', [('model', '=', 'test_uninstall.model')]) - uninstall_module('xx', 'test_uninstall') - registry = get_registry('xx') - assert not registry.get('test_uninstall.model') + uninstall_module('test_uninstall') + assert not get_module('test_uninstall.model') - assert not search_registry('xx', 'ir.model.data', + assert not search_registry('ir.model.data', [('module', '=', 'test_uninstall')]) - assert not search_registry('xx', 'ir.model.fields', + assert not search_registry('ir.model.fields', [('model', '=', 'test_uninstall.model')]) - ir_model_constraint = registry.get('ir.model.constraint') - cr = registry.db.cursor() + ir_model_constraint = registry('ir.model.constraint') + cr = cursor() ids = ir_model_constraint.search(cr, SUPERUSER_ID, [], {}) #print ir_model_constraint.browse(cr, SUPERUSER_ID, ids, {}) cr.close() @@ -95,9 +101,8 @@ MY_MODULE = { 'depends': ['base'], } -def create_virtual_module(database_name, module_name, info): - registry = get_registry(database_name) - cr = registry.db.cursor() +def create_virtual_module(module_name, info): + cr = cursor() cr.execute("""SELECT 1 FROM ir_module_module WHERE name=%s""", (module_name,)) if cr.fetchone(): return From 768aaa8854e8d71c732f6cd0ecde158ea48c6af9 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Mon, 4 Jun 2012 14:31:12 +0200 Subject: [PATCH 09/13] [IMP] uninstall: moved tests from t.py to openerp.tests. bzr revid: vmt@openerp.com-20120604123112-stegmdko2lakdv6f --- openerp/tests/test_uninstall.py | 22 +++++- t.py | 133 -------------------------------- 2 files changed, 19 insertions(+), 136 deletions(-) delete mode 100644 t.py diff --git a/openerp/tests/test_uninstall.py b/openerp/tests/test_uninstall.py index 4cc396e3d3c..514042188a9 100644 --- a/openerp/tests/test_uninstall.py +++ b/openerp/tests/test_uninstall.py @@ -61,10 +61,25 @@ def uninstall_module(module_name): reload_registry() class test_uninstall(unittest2.TestCase): - """ Test install/uninstall of a test module. """ + """ + Test the install/uninstall of a test module. The module is available in + `openerp.tests` which should be present in the addons-path. + """ - def test_00_nothing(self): - """ Test the assumption the test module is not installed yet. """ + def test_01_install(self): + """ Check a few things showing the module is installed. """ + install_module('test_uninstall') + assert get_module('test_uninstall.model') + + assert search_registry('ir.model.data', + [('module', '=', 'test_uninstall')]) + + assert search_registry('ir.model.fields', + [('model', '=', 'test_uninstall.model')]) + + def test_02_install(self): + """ Check a few things showing the module is uninstalled. """ + uninstall_module('test_uninstall') assert not get_module('test_uninstall.model') assert not search_registry('ir.model.data', @@ -74,6 +89,7 @@ class test_uninstall(unittest2.TestCase): [('model', '=', 'test_uninstall.model')]) + if __name__ == '__main__': unittest2.main() diff --git a/t.py b/t.py deleted file mode 100644 index 8529145d4fa..00000000000 --- a/t.py +++ /dev/null @@ -1,133 +0,0 @@ -import sys - -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). - -DB = None - -def registry(model_name): - return openerp.modules.registry.RegistryManager.get(DB)[model_name] - -def cursor(): - return openerp.modules.registry.RegistryManager.get(DB).db.cursor() - -def get_module(module_name): - registry = openerp.modules.registry.RegistryManager.get(DB) - return registry.get(module_name) - -def reload_registry(): - openerp.modules.registry.RegistryManager.new( - DB, update_module=True) - -def search_registry(model_name, domain): - cr = cursor() - model = registry(model_name) - record_ids = model.search(cr, SUPERUSER_ID, domain, {}) - cr.close() - return record_ids - -def install_module(module_name): - ir_module_module = registry('ir.module.module') - cr = 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() - -def uninstall_module(module_name): - ir_module_module = registry('ir.module.module') - cr = 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() - -if __name__ == '__main__': - assert len(sys.argv) == 2 - DB = sys.argv[1] - openerp.netsvc.init_logger() - conf['addons_path'] = './openerp/tests/addons,../../addons/trunk,../../web/trunk/addons' - - install_module('test_uninstall') - assert get_module('test_uninstall.model') - - assert search_registry('ir.model.data', - [('module', '=', 'test_uninstall')]) - - assert search_registry('ir.model.fields', - [('model', '=', 'test_uninstall.model')]) - - uninstall_module('test_uninstall') - assert not get_module('test_uninstall.model') - - assert not search_registry('ir.model.data', - [('module', '=', 'test_uninstall')]) - - assert not search_registry('ir.model.fields', - [('model', '=', 'test_uninstall.model')]) - - ir_model_constraint = registry('ir.model.constraint') - cr = cursor() - ids = ir_model_constraint.search(cr, SUPERUSER_ID, [], {}) - #print ir_model_constraint.browse(cr, SUPERUSER_ID, ids, {}) - cr.close() - -##################################################################### - -# Nice idea, but won't work without some more change to the framework (which -# expects everything on disk, maybe we can craft a zip file...). - -MY_MODULE = { - 'author': 'Jean Beauvoir', - 'website': 'http://www.youtube.com/watch?v=FeO5DfdZi7Y', - 'name': 'FEEL THE HEAT', - 'description': "Cobra's theme", - 'web': False, - 'license': 'WTFPL', - 'application': False, - 'icon': False, - 'sequence': 100, - 'depends': ['base'], -} - -def create_virtual_module(module_name, info): - cr = cursor() - - cr.execute("""SELECT 1 FROM ir_module_module WHERE name=%s""", (module_name,)) - if cr.fetchone(): return - - category_id = openerp.modules.db.create_categories(cr, ['Tests']) - cr.execute('INSERT INTO ir_module_module \ - (author, website, name, shortdesc, description, \ - category_id, auto_install, state, certificate, web, license, application, icon, sequence) \ - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING id', ( - info['author'], - info['website'], module_name, info['name'], - info['description'], category_id, - True, 'to install', False, - info['web'], - info['license'], - info['application'], info['icon'], - info['sequence'])) - module_id = cr.fetchone()[0] - cr.execute('INSERT INTO ir_model_data \ - (name,model,module, res_id, noupdate) VALUES (%s,%s,%s,%s,%s)', ( - 'module_' + module_name, 'ir.module.module', 'base', module_id, True)) - dependencies = info['depends'] - for d in dependencies: - cr.execute('INSERT INTO ir_module_module_dependency \ - (module_id,name) VALUES (%s, %s)', (module_id, d)) - - cr.commit() - cr.close() From 77ec840cb00d9910cd7751fd17dda8f0806849b2 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Tue, 5 Jun 2012 11:52:10 +0200 Subject: [PATCH 10/13] [FIX] ir_values: fix a test where some value was hard-coded. bzr revid: vmt@openerp.com-20120605095210-0t5bmlyxrtnoqvr0 --- openerp/addons/base/tests/test_ir_values.py | 9 ++++++--- openerp/modules/module.py | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/openerp/addons/base/tests/test_ir_values.py b/openerp/addons/base/tests/test_ir_values.py index b7ea0107776..b306e1f0929 100644 --- a/openerp/addons/base/tests/test_ir_values.py +++ b/openerp/addons/base/tests/test_ir_values.py @@ -54,7 +54,10 @@ class test_ir_values(common.TransactionCase): ir_values.set(self.cr, self.uid, 'action', 'tree_but_open', 'OnDblClick Action', ['unexisting_model'], 'ir.actions.act_window,10', isobject=True) ir_values.set(self.cr, self.uid, 'action', 'tree_but_open', 'OnDblClick Action 2', ['unexisting_model'], 'ir.actions.act_window,11', isobject=True) ir_values.set(self.cr, self.uid, 'action', 'client_action_multi', 'Side Wizard', ['unexisting_model'], 'ir.actions.act_window,12', isobject=True) - ir_values.set(self.cr, self.uid, 'action', 'client_print_multi', 'Nice Report', ['unexisting_model'], 'ir.actions.report.xml,45', isobject=True) + report_ids = self.registry('ir.actions.report.xml').search(self.cr, self.uid, [], {}) + reports = self.registry('ir.actions.report.xml').browse(self.cr, self.uid, report_ids, {}) + report_id = [report.id for report in reports if not report.groups_id][0] # assume at least one + ir_values.set(self.cr, self.uid, 'action', 'client_print_multi', 'Nice Report', ['unexisting_model'], 'ir.actions.report.xml,%s'%report_id, isobject=True) ir_values.set(self.cr, self.uid, 'action', 'client_action_relate', 'Related Stuff', ['unexisting_model'], 'ir.actions.act_window,14', isobject=True) # Replace one action binding to set a new name. @@ -86,7 +89,7 @@ class test_ir_values(common.TransactionCase): assert len(actions) == 1, "Mismatching number of bound actions" assert len(actions[0]) == 3, "Malformed action definition" assert actions[0][1] == 'Nice Report', 'Bound action does not match definition' - assert isinstance(actions[0][2], dict) and actions[0][2]['id'] == 45, 'Bound action does not match definition' + assert isinstance(actions[0][2], dict) and actions[0][2]['id'] == report_id, 'Bound action does not match definition' actions = ir_values.get(self.cr, self.uid, 'action', 'client_action_relate', ['unexisting_model']) assert len(actions) == 1, "Mismatching number of bound actions" @@ -96,4 +99,4 @@ class test_ir_values(common.TransactionCase): if __name__ == '__main__': - unittest2.main() \ No newline at end of file + unittest2.main() diff --git a/openerp/modules/module.py b/openerp/modules/module.py index e226b69a6fc..265a23190e5 100644 --- a/openerp/modules/module.py +++ b/openerp/modules/module.py @@ -493,7 +493,7 @@ def get_test_modules(module, submodule, explode): # It seems the module has no `tests` sub-module, no problem. pass else: - print 'Can not `import %s`.' % module + _logger.exception('Can not `import %s`.', module) return [] # Discover available test sub-modules. @@ -554,6 +554,7 @@ def run_unit_tests(module_name): """ import unittest2 ms = get_test_modules(module_name, '__fast_suite__', explode=False) + # TODO: No need to try again if the above call failed because of e.g. a syntax error. ms.extend(get_test_modules(module_name, '__sanity_checks__', explode=False)) suite = unittest2.TestSuite() for m in ms: From 83d799559c49852019381d0549c930882c0b11e7 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Thu, 7 Jun 2012 14:41:56 +0200 Subject: [PATCH 11/13] [FIX] uninstall: use ir_model.model instead of ir_model.name (the former is the model name while the later is not...). bzr revid: vmt@openerp.com-20120607124156-p4xupmdjoi2vgunt --- openerp/addons/base/ir/ir_model_constraint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/addons/base/ir/ir_model_constraint.py b/openerp/addons/base/ir/ir_model_constraint.py index 05d47df462d..4a69bc2f0a7 100644 --- a/openerp/addons/base/ir/ir_model_constraint.py +++ b/openerp/addons/base/ir/ir_model_constraint.py @@ -47,7 +47,7 @@ class ir_model_constraint(Model): ids.reverse() to_unlink = [] for data in self.browse(cr, uid, ids, context): - model = data.model.name + model = data.model.model model_obj = self.pool.get(model) name = openerp.tools.ustr(data.name) typ = data.type From 575e234cd41a163b55c26e87c9326d4fd95567a6 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Tue, 19 Jun 2012 17:16:26 +0200 Subject: [PATCH 12/13] [FIX] res.partner.category: the relation table name was manually chosen, which breaks under _inherit, so we use the automatic one. bzr revid: vmt@openerp.com-20120619151626-2xho611icmq7e1ky --- openerp/addons/base/ir/ir_model.py | 13 +++++++++++-- openerp/addons/base/module/module.py | 7 +------ openerp/addons/base/res/res_partner.py | 4 ++-- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/openerp/addons/base/ir/ir_model.py b/openerp/addons/base/ir/ir_model.py index f7b4bd15936..a3d1c7f8d78 100644 --- a/openerp/addons/base/ir/ir_model.py +++ b/openerp/addons/base/ir/ir_model.py @@ -836,7 +836,7 @@ class ir_model_data(osv.osv): cr.execute('UPDATE ir_values set value=%s WHERE model=%s and key=%s and name=%s'+where,(value, model, key, name)) return True - def _module_data_uninstall(self, cr, uid, ids, context=None): + def _module_data_uninstall(self, cr, uid, modules_to_remove, context=None): """Deletes all the records referenced by the ir.model.data entries ``ids`` along with their corresponding database backed (including dropping tables, columns, FKs, etc, as long as there is no other @@ -847,6 +847,8 @@ class ir_model_data(osv.osv): This step is performed as part of the full uninstallation of a module. """ + ids = self.search(cr, uid, [('module', 'in', modules_to_remove)]) + if uid != 1 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'))) @@ -888,7 +890,7 @@ class ir_model_data(osv.osv): external_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)]) if (set(external_ids)-ids_set): # if other modules have defined this record, we must not delete it - return + continue _logger.info('Deleting %s@%s', res_id, model) try: self.pool.get(model).unlink(cr, uid, [res_id], context=context) @@ -900,11 +902,18 @@ class ir_model_data(osv.osv): if model not in ('ir.model','ir.model.fields')) unlink_if_refcount((model, res_id) for model, res_id in to_unlink if model == 'ir.model.fields') + + ir_model_relation = self.pool.get('ir.model.relation') + relation_ids = ir_model_relation.search(cr, uid, [('module', 'in', modules_to_remove)]) + ir_model_relation._module_data_uninstall(cr, uid, relation_ids, context) + unlink_if_refcount((model, res_id) for model, res_id in to_unlink if model == 'ir.model') cr.commit() + self.unlink(cr, uid, ids, context) + def _process_end(self, cr, uid, modules): """ Clear records removed from updated module data. This method is called at the end of the module loading process. diff --git a/openerp/addons/base/module/module.py b/openerp/addons/base/module/module.py index cff3c73d721..392850f7b77 100644 --- a/openerp/addons/base/module/module.py +++ b/openerp/addons/base/module/module.py @@ -379,15 +379,10 @@ class module(osv.osv): tables, columns, constraints, etc.""" ir_model_data = self.pool.get('ir.model.data') ir_model_constraint = self.pool.get('ir.model.constraint') - ir_model_relation = self.pool.get('ir.model.relation') 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) - relation_ids = ir_model_relation.search(cr, uid, [('module', 'in', modules_to_remove)]) - ir_model_relation._module_data_uninstall(cr, uid, relation_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) + ir_model_data._module_data_uninstall(cr, uid, modules_to_remove, context) self.write(cr, uid, ids, {'state': 'uninstalled'}) return True diff --git a/openerp/addons/base/res/res_partner.py b/openerp/addons/base/res/res_partner.py index 1dbad434ed6..745910b569b 100644 --- a/openerp/addons/base/res/res_partner.py +++ b/openerp/addons/base/res/res_partner.py @@ -87,7 +87,7 @@ class res_partner_category(osv.osv): 'active' : fields.boolean('Active', help="The active field allows you to hide the category without removing it."), 'parent_left' : fields.integer('Left parent', select=True), 'parent_right' : fields.integer('Right parent', select=True), - 'partner_ids': fields.many2many('res.partner', 'res_partner_category_rel', 'category_id', 'partner_id', 'Partners'), + 'partner_ids': fields.many2many('res.partner', id1='category_id', id2='partner_id', string='Partners'), } _constraints = [ (osv.osv._check_recursion, 'Error ! You can not create recursive categories.', ['parent_id']) @@ -142,7 +142,7 @@ class res_partner(osv.osv): 'website': fields.char('Website',size=64, help="Website of Partner or Company"), 'comment': fields.text('Notes'), 'address': fields.one2many('res.partner.address', 'partner_id', 'Contacts'), # should be removed in version 7, but kept until then for backward compatibility - 'category_id': fields.many2many('res.partner.category', 'res_partner_category_rel', 'partner_id', 'category_id', 'Tags'), + 'category_id': fields.many2many('res.partner.category', id1='partner_id', id2='category_id', string='Tags'), 'credit_limit': fields.float(string='Credit Limit'), 'ean13': fields.char('EAN13', size=13), 'active': fields.boolean('Active'), From 5edca8990309952f1a9dd0cbc98fa1c046e3e296 Mon Sep 17 00:00:00 2001 From: Vo Minh Thu Date: Wed, 20 Jun 2012 12:07:05 +0200 Subject: [PATCH 13/13] [IMP] uninstall: typos bzr revid: vmt@openerp.com-20120620100705-p9o5kktmj8zkbshm --- openerp/addons/base/base.sql | 8 ++++---- openerp/tests/test_uninstall.py | 11 ++--------- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/openerp/addons/base/base.sql b/openerp/addons/base/base.sql index 0a8f50c24bf..f2cb77709ed 100644 --- a/openerp/addons/base/base.sql +++ b/openerp/addons/base/base.sql @@ -348,9 +348,9 @@ CREATE TABLE ir_model_data ( ); -- Records foreign keys and constraints installed by a module (so they can be --- removed them when the module is uninstalled): --- - for a foreign key: type 'f', --- - for a constraint: type 'u'. +-- removed when the module is uninstalled): +-- - for a foreign key: type is 'f', +-- - for a constraint: type is 'u' (this is the convention PostgreSQL uses). CREATE TABLE ir_model_constraint ( id serial NOT NULL, create_uid integer, @@ -366,7 +366,7 @@ CREATE TABLE ir_model_constraint ( ); -- Records relation tables (i.e. implementing many2many) installed by a module --- (so they can be removed them when the module is uninstalled). +-- (so they can be removed when the module is uninstalled). CREATE TABLE ir_model_relation ( id serial NOT NULL, create_uid integer, diff --git a/openerp/tests/test_uninstall.py b/openerp/tests/test_uninstall.py index 514042188a9..1f74b660ce0 100644 --- a/openerp/tests/test_uninstall.py +++ b/openerp/tests/test_uninstall.py @@ -1,12 +1,5 @@ # -*- coding: utf-8 -*- -# Run with one of these commands: -# > OPENERP_ADDONS_PATH='../../addons/trunk' OPENERP_PORT=8069 \ -# OPENERP_DATABASE=yy PYTHONPATH=. python tests/test_ir_sequence.py -# > OPENERP_ADDONS_PATH='../../addons/trunk' OPENERP_PORT=8069 \ -# OPENERP_DATABASE=yy nosetests tests/test_ir_sequence.py -# > OPENERP_ADDONS_PATH='../../../addons/trunk' OPENERP_PORT=8069 \ -# OPENERP_DATABASE=yy PYTHONPATH=../:. unit2 test_ir_sequence -# This assume an existing database. +# This assumes an existing but uninitialized database. import psycopg2 import unittest2 @@ -77,7 +70,7 @@ class test_uninstall(unittest2.TestCase): assert search_registry('ir.model.fields', [('model', '=', 'test_uninstall.model')]) - def test_02_install(self): + def test_02_uninstall(self): """ Check a few things showing the module is uninstalled. """ uninstall_module('test_uninstall') assert not get_module('test_uninstall.model')