Vo Minh Thu (OpenERP) has proposed merging 
lp:~openerp-dev/openobject-server/trunk-uninstall-vmt into lp:openobject-server.

Requested reviews:
  OpenERP Core Team (openerp)

For more details, see:
https://code.launchpad.net/~openerp-dev/openobject-server/trunk-uninstall-vmt/+merge/108557
-- 
https://code.launchpad.net/~openerp-dev/openobject-server/trunk-uninstall-vmt/+merge/108557
Your team OpenERP R&D Team is subscribed to branch 
lp:~openerp-dev/openobject-server/trunk-uninstall-vmt.
=== modified file 'openerp/addons/base/base.sql'
--- openerp/addons/base/base.sql	2012-04-20 15:22:20 +0000
+++ openerp/addons/base/base.sql	2012-06-04 12:37:03 +0000
@@ -347,6 +347,39 @@
     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
+);
+
+-- 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
 ---------------------------------

=== modified file 'openerp/addons/base/ir/__init__.py'
--- openerp/addons/base/ir/__init__.py	2012-03-30 13:24:52 +0000
+++ openerp/addons/base/ir/__init__.py	2012-06-04 12:37:03 +0000
@@ -20,6 +20,8 @@
 ##############################################################################
 
 import ir_model
+import ir_model_constraint
+import ir_model_relation
 import ir_sequence
 import ir_needaction
 import ir_ui_menu

=== modified file 'openerp/addons/base/ir/ir.xml'
--- openerp/addons/base/ir/ir.xml	2012-05-18 08:49:26 +0000
+++ openerp/addons/base/ir/ir.xml	2012-06-04 12:37:03 +0000
@@ -1128,6 +1128,65 @@
             </field>
         </record>
 
+        <record model="ir.ui.view" id="view_model_constraint_form">
+            <field name="name">ir.model.constraint.form</field>
+            <field name="model">ir.model.constraint</field>
+            <field name="type">form</field>
+            <field name="arch" type="xml">
+                <form string="Model Constraints">
+                    <field name="type"/>
+                    <field name="name"/>
+                    <field name="module"/>
+                    <field name="model"/>
+                    <newline/>
+                    <field name="date_update" />
+                    <field name="date_init" />
+                </form>
+            </field>
+        </record>
+
+        <record id="view_model_constraint_list" model="ir.ui.view">
+            <field name="name">ir.model.constraint.list</field>
+            <field name="model">ir.model.constraint</field>
+            <field name="type">tree</field>
+            <field name="arch" type="xml">
+                <tree string="Model Constraints">
+                    <field name="type"/>
+                    <field name="name"/>
+                    <field name="module"/>
+                    <field name="model"/>
+                </tree>
+            </field>
+        </record>
+
+        <record model="ir.ui.view" id="view_model_relation_form">
+            <field name="name">ir.model.relation.form</field>
+            <field name="model">ir.model.relation</field>
+            <field name="type">form</field>
+            <field name="arch" type="xml">
+                <form string="ManyToMany Relations">
+                    <field name="name"/>
+                    <field name="module"/>
+                    <field name="model"/>
+                    <newline/>
+                    <field name="date_update" />
+                    <field name="date_init" />
+                </form>
+            </field>
+        </record>
+
+        <record id="view_model_relation_list" model="ir.ui.view">
+            <field name="name">ir.model.relation.list</field>
+            <field name="model">ir.model.relation</field>
+            <field name="type">tree</field>
+            <field name="arch" type="xml">
+                <tree string="ManyToMany Relations">
+                    <field name="name"/>
+                    <field name="module"/>
+                    <field name="model"/>
+                </tree>
+            </field>
+        </record>
 
         <record id="action_model_model" model="ir.actions.act_window">
             <field name="name">Models</field>
@@ -1157,6 +1216,24 @@
         <menuitem action="action_model_data" id="ir_model_data_menu" parent="base.next_id_5"
                   groups="base.group_no_one"/>
 
+        <record id="action_model_constraint" model="ir.actions.act_window">
+            <field name="name">Model Constraints</field>
+            <field name="res_model">ir.model.constraint</field>
+            <field name="view_type">form</field>
+            <field name="view_id" ref="view_model_constraint_list"/>
+        </record>
+        <menuitem action="action_model_constraint" id="ir_model_constraint_menu" parent="base.next_id_9"
+                  groups="base.group_no_one"/>
+
+        <record id="action_model_relation" model="ir.actions.act_window">
+            <field name="name">ManyToMany Relations</field>
+            <field name="res_model">ir.model.relation</field>
+            <field name="view_type">form</field>
+            <field name="view_id" ref="view_model_relation_list"/>
+        </record>
+        <menuitem action="action_model_relation" id="ir_model_relation_menu" parent="base.next_id_9"
+                  groups="base.group_no_one"/>
+
         <!-- Translations -->
 
         <record id="view_translation_search" model="ir.ui.view">

=== modified file 'openerp/addons/base/ir/ir_model.py'
--- openerp/addons/base/ir/ir_model.py	2012-04-24 06:29:06 +0000
+++ openerp/addons/base/ir/ir_model.py	2012-06-04 12:37:03 +0000
@@ -29,8 +29,7 @@
 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
 
 _logger = logging.getLogger(__name__)
 
@@ -848,7 +847,6 @@
         ids_set = set(ids)
         wkf_todo = []
         to_unlink = []
-        to_drop_table = []
         ids.sort()
         ids.reverse()
         for data in self.browse(cr, uid, ids, context):
@@ -857,42 +855,6 @@
             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):
-                # 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_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,))
-                if cr.fetchone() and not name in to_drop_table:
-                    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)
@@ -912,11 +874,6 @@
             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)])

=== added file 'openerp/addons/base/ir/ir_model_constraint.py'
--- openerp/addons/base/ir/ir_model_constraint.py	1970-01-01 00:00:00 +0000
+++ openerp/addons/base/ir/ir_model_constraint.py	2012-06-04 12:37:03 +0000
@@ -0,0 +1,79 @@
+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
+    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',
+            required=True, select=1),
+        '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, "
+                "`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 != 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 {})
+
+        ids_set = set(ids)
+        ids.sort()
+        ids.reverse()
+        to_unlink = []
+        for data in self.browse(cr, uid, ids, context):
+            model = data.model.name
+            model_obj = self.pool.get(model)
+            name = openerp.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!
+                continue
+
+            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():
+                    cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table, name),)
+                    _logger.info('Dropped FK CONSTRAINT %s@%s', name, model)
+
+            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():
+                    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)

=== added file 'openerp/addons/base/ir/ir_model_relation.py'
--- openerp/addons/base/ir/ir_model_relation.py	1970-01-01 00:00:00 +0000
+++ openerp/addons/base/ir/ir_model_relation.py	2012-06-04 12:37:03 +0000
@@ -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',
+            required=True, select=1),
+        'module': fields.many2one('ir.module.module', string='Module',
+            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()

=== modified file 'openerp/addons/base/module/module.py'
--- openerp/addons/base/module/module.py	2012-05-23 05:52:00 +0000
+++ openerp/addons/base/module/module.py	2012-06-04 12:37:03 +0000
@@ -364,7 +364,13 @@
         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')
+        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)

=== modified file 'openerp/osv/orm.py'
--- openerp/osv/orm.py	2012-05-18 14:36:25 +0000
+++ openerp/osv/orm.py	2012-06-04 12:37:03 +0000
@@ -70,11 +70,6 @@
 # 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_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_.]+$')
 
@@ -2744,13 +2739,45 @@
                 _schema.debug("Table '%s': column '%s': dropped NOT NULL constraint",
                               self._table, column['attname'])
 
-    # 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))
-        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))
+    def _save_constraint(self, cr, constraint_name, type):
+        """
+        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
+            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))
+
+    def _save_relation_table(self, cr, relation_table):
+        """
+        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
+                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_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):
@@ -3091,7 +3118,7 @@
         """ 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
 
@@ -3180,7 +3207,7 @@
 
     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):
@@ -3216,7 +3243,7 @@
         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 = {

=== modified file 'openerp/tests/__init__.py'
--- openerp/tests/__init__.py	2012-03-01 13:46:08 +0000
+++ openerp/tests/__init__.py	2012-06-04 12:37:03 +0000
@@ -11,6 +11,7 @@
 import test_expression
 import test_ir_sequence
 import test_orm
+import test_uninstall
 
 fast_suite = [
     test_ir_sequence,

=== added directory 'openerp/tests/addons/test_uninstall'
=== added file 'openerp/tests/addons/test_uninstall/__init__.py'
--- openerp/tests/addons/test_uninstall/__init__.py	1970-01-01 00:00:00 +0000
+++ openerp/tests/addons/test_uninstall/__init__.py	2012-06-04 12:37:03 +0000
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+import models
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

=== added file 'openerp/tests/addons/test_uninstall/__openerp__.py'
--- openerp/tests/addons/test_uninstall/__openerp__.py	1970-01-01 00:00:00 +0000
+++ openerp/tests/addons/test_uninstall/__openerp__.py	2012-06-04 12:37:03 +0000
@@ -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:

=== added file 'openerp/tests/addons/test_uninstall/models.py'
--- openerp/tests/addons/test_uninstall/models.py	1970-01-01 00:00:00 +0000
+++ openerp/tests/addons/test_uninstall/models.py	2012-06-04 12:37:03 +0000
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+import openerp
+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 = {
+        'name': fields.char('Name', size=64),
+        'ref': fields.many2one('res.users', string='User'),
+        'rel': fields.many2many('res.users', string='Users'),
+    }
+
+    _sql_constraints = [
+        ('name_uniq', 'unique (name)', 'Each name must be unique.')
+    ]
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

=== added file 'openerp/tests/test_uninstall.py'
--- openerp/tests/test_uninstall.py	1970-01-01 00:00:00 +0000
+++ openerp/tests/test_uninstall.py	2012-06-04 12:37:03 +0000
@@ -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:

=== added file 't.py'
--- t.py	1970-01-01 00:00:00 +0000
+++ t.py	2012-06-04 12:37:03 +0000
@@ -0,0 +1,133 @@
+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()

_______________________________________________
Mailing list: https://launchpad.net/~openerp-dev-gtk
Post to     : [email protected]
Unsubscribe : https://launchpad.net/~openerp-dev-gtk
More help   : https://help.launchpad.net/ListHelp

Reply via email to