[FIX]: uninstall module.

Remove foreign key references.
Remove sql constraint .
Remove workflow activity and transition based on deleted cascade.
Drop ir model fields columns and drop table.

bzr revid: atp@tinyerp.com-20120309124753-c4yzeoij5p2fmhgg
This commit is contained in:
Atul Patel (OpenERP) 2012-03-09 18:17:53 +05:30
parent c86917ea38
commit 180a23c7cc
7 changed files with 132 additions and 82 deletions

View File

@ -138,15 +138,21 @@ class ir_model(osv.osv):
def _drop_table(self, cr, uid, ids, context=None):
for model in self.browse(cr, uid, ids, context):
model_pool = self.pool.get(model.model)
if getattr(model_pool, '_auto', True) and not model.osv_memory:
cr.execute("DROP table %s cascade" % model_pool._table)
# this test should be removed, but check if drop view instead of drop table
# just check if table or view exists
cr.execute("select relkind from pg_class where relname=%s", (model_pool._table,))
result = cr.fetchone()
if result and result[0] == 'v':
cr.execute("DROP view %s" % (model_pool._table,))
elif result and result[0] == 'r':
cr.execute("DROP TABLE %s" % (model_pool._table,))
return True
def unlink(self, cr, user, ids, context=None):
# for model in self.browse(cr, user, ids, context):
# if model.state != 'manual':
# raise except_orm(_('Error'), _("You can not remove the model '%s' !") %(model.name,))
# self._drop_table(cr, user, ids, context)
self._drop_table(cr, user, ids, context)
res = super(ir_model, self).unlink(cr, user, ids, context)
pooler.restart_pool(cr.dbname)
return res
@ -273,11 +279,15 @@ class ir_model_fields(osv.osv):
]
def _drop_column(self, cr, uid, ids, context=None):
for field in self.browse(cr, uid, ids, context):
model = self.pool.get(field.model)
if not field.model.osv_memory and getattr(model, '_auto', True):
cr.execute("ALTER table %s DROP column %s" % (model._table, field.name))
model._columns.pop(field.name, None)
field = self.browse(cr, uid, ids, context)
model = self.pool.get(field.model)
cr.execute("select relkind from pg_class where relname=%s", (model._table,))
result = cr.fetchone()[0]
cr.execute("SELECT column_name FROM information_schema.columns WHERE table_name ='%s'and column_name='%s'"%(model._table, field.name))
column_name = cr.fetchone()
if column_name and result == 'r':
cr.execute("ALTER table %s DROP column %s cascade" % (model._table, field.name))
model._columns.pop(field.name, None)
return True
def unlink(self, cr, user, ids, context=None):
@ -630,7 +640,6 @@ class ir_model_data(osv.osv):
def __init__(self, pool, cr):
osv.osv.__init__(self, pool, cr)
self.doinit = True
# also stored in pool to avoid being discarded along with this osv instance
if getattr(pool, 'model_data_reference_ids', None) is None:
self.pool.model_data_reference_ids = {}
@ -682,7 +691,6 @@ class ir_model_data(osv.osv):
def unlink(self, cr, uid, ids, context=None):
""" Regular unlink method, but make sure to clear the caches. """
self._pre_process_unlink(cr, uid, ids, context)
self._get_id.clear_cache(self)
self.get_object_reference.clear_cache(self)
return super(ir_model_data,self).unlink(cr, uid, ids, context=context)
@ -691,10 +699,8 @@ class ir_model_data(osv.osv):
model_obj = self.pool.get(model)
if not context:
context = {}
# records created during module install should result in res.log entries that are already read!
context = dict(context, res_log_read=True)
if xml_id and ('.' in xml_id):
assert len(xml_id.split('.'))==2, _("'%s' contains too many dots. XML ids should not contain dots ! These are used to refer to other modules data, as in module.reference_id") % (xml_id)
module, xml_id = xml_id.split('.')
@ -807,41 +813,90 @@ class ir_model_data(osv.osv):
def _pre_process_unlink(self, cr, uid, ids, context=None):
wkf_todo = []
to_unlink = []
to_drop_table = []
ids.sort()
ids.reverse()
for data in self.browse(cr, uid, ids, context):
model = data.model
res_id = data.res_id
model_obj = self.pool.get(model)
if str(data.name).startswith('constraint_'):
cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table,data.name[11:]),)
_logger.info('Drop CONSTRAINT %s@%s', data.name[11:], model)
name = data.name
if str(name).startswith('foreign_key_'):
name = name[12:]
# test if constraint exists
cr.execute('select conname from pg_constraint where contype=%s and conname=%s',('f', name),)
if cr.fetchall():
cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model,name),)
_logger.info('Drop CONSTRAINT %s@%s', name, model)
continue
to_unlink.append((model,res_id))
if str(name).startswith('table_'):
cr.execute("SELECT table_name FROM information_schema.tables WHERE table_name='%s'"%(name[6:]))
column_name = cr.fetchone()
if column_name:
to_drop_table.append(name[6:])
continue
if str(name).startswith('constraint_'):
# test if constraint exists
cr.execute('select conname from pg_constraint where contype=%s and conname=%s',('u', name),)
if cr.fetchall():
cr.execute('ALTER TABLE "%s" DROP CONSTRAINT "%s"' % (model_obj._table,name[11:]),)
_logger.info('Drop CONSTRAINT %s@%s', name[11:], model)
continue
to_unlink.append((model, res_id))
if model=='workflow.activity':
cr.execute('select res_type,res_id from wkf_instance where id IN (select inst_id from wkf_workitem where act_id=%s)', (res_id,))
wkf_todo.extend(cr.fetchall())
cr.execute("update wkf_transition set condition='True', group_id=NULL, signal=NULL,act_to=act_from,act_from=%s where act_to=%s", (res_id,res_id))
cr.execute("delete from wkf_transition where act_to=%s", (res_id,))
for model,res_id in wkf_todo:
wf_service = netsvc.LocalService("workflow")
wf_service.trg_write(uid, model, res_id, cr)
try:
wf_service.trg_write(uid, model, res_id, cr)
except:
_logger.info('Unable to process workflow %s@%s', res_id, model)
#cr.commit()
if not config.get('import_partial'):
for (model, res_id) in to_unlink:
if self.pool.get(model):
_logger.info('Deleting %s@%s', res_id, model)
res_ids = self.pool.get(model).search(cr, uid, [('id', '=', res_id)])
if res_ids:
self.pool.get(model).unlink(cr, uid, [res_id])
cr.commit()
# except Exception:
# cr.rollback()
# _logger.warning(
# 'Could not delete obsolete record with id: %d of model %s\n'
## 'There should be some relation that points to this resource\n'
# 'You should manually fix this and restart with --update=module',
# res_id, model)
# drop relation .table
for model in to_drop_table:
cr.execute('DROP TABLE %s cascade'% (model),)
_logger.info('Dropping table %s', model)
for (model, res_id) in to_unlink:
if model in ('ir.model','ir.model.fields', 'ir.model.data'):
continue
model_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)])
if len(model_ids) > 1:
# if others module have defined this record, we do not delete it
continue
_logger.info('Deleting %s@%s', res_id, model)
try:
self.pool.get(model).unlink(cr, uid, res_id)
except:
_logger.info('Unable to delete %s@%s', res_id, model)
cr.commit()
for (model, res_id) in to_unlink:
if model not in ('ir.model.fields',):
continue
model_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)])
if len(model_ids) > 1:
# if others module have defined this record, we do not delete it
continue
_logger.info('Deleting %s@%s', res_id, model)
self.pool.get(model).unlink(cr, uid, res_id)
for (model, res_id) in to_unlink:
if model not in ('ir.model',):
continue
model_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)])
if len(model_ids) > 1:
# if others module have defined this record, we do not delete it
continue
_logger.info('Deleting %s@%s', res_id, model)
self.pool.get(model).unlink(cr, uid, [res_id])
cr.commit()
def _process_end(self, cr, uid, modules):
""" Clear records removed from updated module data.
@ -851,8 +906,6 @@ class ir_model_data(osv.osv):
and a module in ir_model_data and noupdate set to false, but not
present in self.loads.
"""
if not modules:
return True
modules = list(modules)
@ -871,9 +924,6 @@ class ir_model_data(osv.osv):
_logger.info('Deleting %s@%s', res_id, model)
self.pool.get(model).unlink(cr, uid, [res_id])
# cr.commit()
ir_model_data()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -194,7 +194,7 @@ class wkf_workitem(osv.osv):
_log_access = False
_rec_name = 'state'
_columns = {
'act_id': fields.many2one('workflow.activity', 'Activity', required=True, ondelete="restrict", select=True),
'act_id': fields.many2one('workflow.activity', 'Activity', required=True, ondelete="cascade", select=True),
'wkf_id': fields.related('act_id','wkf_id', type='many2one', relation='workflow', string='Workflow'),
'subflow_id': fields.many2one('workflow.instance', 'Subflow', ondelete="cascade", select=True),
'inst_id': fields.many2one('workflow.instance', 'Instance', required=True, ondelete="cascade", select=True),

View File

@ -343,7 +343,6 @@ class module(osv.osv):
# Mark them to be installed.
if to_install_ids:
self.button_install(cr, uid, to_install_ids, context=context)
return dict(ACTION_DICT, name=_('Install'))
def button_immediate_install(self, cr, uid, ids, context=None):
@ -377,11 +376,20 @@ class module(osv.osv):
return True
def module_uninstall(self, cr, uid, ids, context=None):
# you have to uninstall in the right order, not all modules at the same time
model_data = self.pool.get('ir.model.data')
remove_modules = map(lambda x: x.name, self.browse(cr, uid, ids, context))
data_ids = model_data.search(cr, uid, [('module', 'in', remove_modules)])
model_data._pre_process_unlink(cr, uid, data_ids, context)
model_data.unlink(cr, uid, data_ids, context)
self.write(cr, uid, ids, {'state': 'uninstalled'})
# should we call process_end istead of loading, or both ?
return True
def button_uninstall(self, cr, uid, ids, context=None):

View File

@ -97,12 +97,10 @@ class base_module_upgrade(osv.osv_memory):
raise osv.except_osv(_('Unmet dependency !'), _('Following modules are not installed or unknown: %s') % ('\n\n' + '\n'.join(unmet_packages)))
mod_obj.download(cr, uid, ids, context=context)
cr.commit()
# process to remove modules
remove_module_ids = mod_obj.search(cr, uid, [('state', 'in', ['to remove'])])
mod_obj.module_uninstall(cr, uid, remove_module_ids, context)
_db, pool = pooler.restart_pool(cr.dbname, update_module=True)
id2 = data_obj._get_id(cr, uid, 'base', 'view_base_module_upgrade_install')

View File

@ -107,21 +107,6 @@ class groups(osv.osv):
aid.write({'groups_id': [(4, gid)]})
return gid
def unlink(self, cr, uid, ids, context=None):
group_users = []
for record in self.read(cr, uid, ids, ['users'], context=context):
if record['users']:
group_users.extend(record['users'])
if group_users:
user_names = [user.name for user in self.pool.get('res.users').browse(cr, uid, group_users, context=context)]
user_names = list(set(user_names))
if len(user_names) >= 5:
user_names = user_names[:5] + ['...']
raise osv.except_osv(_('Warning !'),
_('Group(s) cannot be deleted, because some user(s) still belong to them: %s !') % \
', '.join(user_names))
return super(groups, self).unlink(cr, uid, ids, context=context)
def get_extended_interface_group(self, cr, uid, context=None):
data_obj = self.pool.get('ir.model.data')
extended_group_data_id = data_obj._get_id(cr, uid, 'base', 'group_extended')

View File

@ -374,13 +374,12 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
tools.config[kind] = {}
cr.commit()
if update_module:
# if update_module:
# Remove records referenced from ir_model_data for modules to be
# removed (and removed the references from ir_model_data).
#cr.execute("select id,name from ir_module_module where state=%s", ('to remove',))
#remove_modules = map(lambda x: x['name'], cr.dictfetchall())
# Cleanup orphan records
#print "pooler", pool.get('mrp.bom')
#pool.get('ir.model.data')._process_end(cr, 1, remove_modules, noupdate=None)
# for mod_id, mod_name in cr.fetchall():
# cr.execute('select model,res_id from ir_model_data where noupdate=%s and module=%s order by id desc', (False, mod_name,))
@ -398,20 +397,20 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
# (child) menu item, ir_values, or ir_model_data.
# This code could be a method of ir_ui_menu.
# TODO: remove menu without actions of children
while True:
cr.execute('''delete from
ir_ui_menu
where
(id not IN (select parent_id from ir_ui_menu where parent_id is not null))
and
(id not IN (select res_id from ir_values where model='ir.ui.menu'))
and
(id not IN (select res_id from ir_model_data where model='ir.ui.menu'))''')
cr.commit()
if not cr.rowcount:
break
else:
_logger.info('removed %d unused menus', cr.rowcount)
# while True:
# cr.execute('''delete from
# ir_ui_menu
# where
# (id not IN (select parent_id from ir_ui_menu where parent_id is not null))
# and
# (id not IN (select res_id from ir_values where model='ir.ui.menu'))
# and
# (id not IN (select res_id from ir_model_data where model='ir.ui.menu'))''')
# cr.commit()
# if not cr.rowcount:
# break
# else:
# _logger.info('removed %d unused menus', cr.rowcount)
# Pretend that modules to be removed are actually uninstalled.
#cr.execute("update ir_module_module set state=%s where state=%s", ('uninstalled', 'to remove',))

View File

@ -904,6 +904,7 @@ class BaseModel(object):
# If new class defines a constraint with
# same function name, we let it override
# the old one.
new[c2] = c
exist = True
break
@ -2760,7 +2761,6 @@ class BaseModel(object):
update_custom_fields = context.get('update_custom_fields', False)
self._field_create(cr, context=context)
create = not self._table_exist(cr)
if getattr(self, '_auto', True):
if create:
@ -3029,11 +3029,16 @@ class BaseModel(object):
return todo_end
def _auto_end(self, cr, context=None):
""" 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))
name_id = "foreign_key_"+t+"_"+k+"_fkey"
cr.execute('select * from ir_model_data where name=%s and module=%s', (name_id, self._module))
if not cr.rowcount:
cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module, model) VALUES (%s, now(), now(), %s, %s)", \
(name_id, self._module, t)
)
cr.commit()
del self._foreign_keys
@ -3112,6 +3117,7 @@ class BaseModel(object):
def _o2m_raise_on_missing_reference(self, cr, f):
# TODO this check should be a method on fields.one2many.
other = self.pool.get(f._obj)
if other:
# TODO the condition could use fields_get_keys().
@ -3119,7 +3125,6 @@ class BaseModel(object):
if f._fields_id not in other._inherit_fields.keys():
raise except_orm('Programming Error', ("There is no reference field '%s' found for '%s'") % (f._fields_id, f._obj,))
def _m2m_raise_or_create_relation(self, cr, f):
m2m_tbl, col1, col2 = f._sql_names(self)
cr.execute("SELECT relname FROM pg_class WHERE relkind IN ('r','v') AND relname=%s", (m2m_tbl,))
@ -3129,7 +3134,14 @@ class BaseModel(object):
dest_model = self.pool.get(f._obj)
ref = dest_model._table
cr.execute('CREATE TABLE "%s" ("%s" INTEGER NOT NULL, "%s" INTEGER NOT NULL, UNIQUE("%s","%s")) WITH OIDS' % (m2m_tbl, col1, col2, col1, col2))
#create many2many references
name_id = 'table_'+m2m_tbl
cr.execute('select * from ir_model_data where name=%s and module=%s', (name_id, self._module))
if not cr.rowcount:
cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module, model) VALUES (%s, now(), now(), %s, %s)", \
(name_id, self._module, self._name)
)
# self.pool.get('ir.model.data')._update(cr, 1, self._name, self._module, {}, 'table_'+m2m_tbl, store=True, noupdate=False, mode='init', res_id=False, context=None)
# create foreign key references with ondelete=cascade, unless the targets are SQL views
cr.execute("SELECT relkind FROM pg_class WHERE relkind IN ('v') AND relname=%s", (ref,))
if not cr.fetchall():
@ -3157,7 +3169,6 @@ class BaseModel(object):
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 = {
'drop': {
'execute': False,
@ -3198,11 +3209,10 @@ class BaseModel(object):
_schema.debug(sql_action['msg_ok'])
name_id = 'constraint_'+ conname
cr.execute('select * from ir_model_data where name=%s and module=%s', (name_id, module))
if not cr.rowcount:
if not cr.rowcount:
cr.execute("INSERT INTO ir_model_data (name,date_init,date_update,module, model) VALUES (%s, now(), now(), %s, %s)", \
(name_id, module, self._name)
)
#
except:
_schema.warning(sql_action['msg_err'])
cr.rollback()