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