[IMP] Move uninstall behavior in modules.loading where it belongs

bzr revid: odo@openerp.com-20120330163422-jkatnkpw0cl8hd8t
This commit is contained in:
Olivier Dony 2012-03-30 18:34:22 +02:00
parent fd14556b8c
commit eb9b0021ff
4 changed files with 77 additions and 70 deletions

View File

@ -161,7 +161,11 @@ class ir_model(osv.osv):
self._drop_table(cr, user, ids, context)
res = super(ir_model, self).unlink(cr, user, ids, context)
pooler.restart_pool(cr.dbname)
if not context.get(MODULE_UNINSTALL_FLAG):
# only reload pool for normal unlink. For module uninstall the
# reload is done independently in openerp.modules.loading
pooler.restart_pool(cr.dbname)
return res
def write(self, cr, user, ids, vals, context=None):
@ -832,6 +836,7 @@ class ir_model_data(osv.osv):
context = dict(context or {})
context[MODULE_UNINSTALL_FLAG] = True # enable model/field deletion
ids_set = set(ids)
wkf_todo = []
to_unlink = []
to_drop_table = []
@ -867,8 +872,11 @@ class ir_model_data(osv.osv):
_logger.info('Drop CONSTRAINT %s@%s', name[11:], model)
continue
to_unlink.append((model, res_id))
if model=='workflow.activity':
pair_to_unlink = (model, res_id)
if pair_to_unlink not in to_unlink:
to_unlink.append(pair_to_unlink)
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))
@ -888,13 +896,13 @@ class ir_model_data(osv.osv):
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:
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 do not delete it
continue
_logger.info('Deleting %s@%s', res_id, model)
try:
self.pool.get(model).unlink(cr, uid, res_id, context=context)
self.pool.get(model).unlink(cr, uid, [res_id], context=context)
except:
_logger.info('Unable to delete %s@%s', res_id, model, exc_info=True)
cr.commit()
@ -902,18 +910,18 @@ class ir_model_data(osv.osv):
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:
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 do not delete it
continue
_logger.info('Deleting %s@%s', res_id, model)
self.pool.get(model).unlink(cr, uid, res_id, context=context)
self.pool.get(model).unlink(cr, uid, [res_id], context=context)
for (model, res_id) in to_unlink:
if model != 'ir.model':
continue
model_ids = self.search(cr, uid, [('model', '=', model),('res_id', '=', res_id)])
if len(model_ids) > 1:
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 do not delete it
continue
_logger.info('Deleting %s@%s', res_id, model)

View File

@ -365,6 +365,10 @@ class module(osv.osv):
return True
def module_uninstall(self, cr, uid, ids, context=None):
"""Perform the various steps required to uninstall a module completely
including the deletion of all database structures created by the module:
tables, columns, constraints, etc."""
# uninstall must be done respecting the reverse-dependency order
ir_model_data = self.pool.get('ir.model.data')
modules_to_remove = [m.name for m in self.browse(cr, uid, ids, context)]
@ -372,8 +376,6 @@ class module(osv.osv):
ir_model_data._pre_process_unlink(cr, uid, data_ids, context)
ir_model_data.unlink(cr, uid, data_ids, context)
self.write(cr, uid, ids, {'state': 'uninstalled'})
# should we call process_end instead of loading, or both ?
return True
def downstream_dependencies(self, cr, uid, ids, known_dep_ids=None,

View File

@ -72,7 +72,7 @@ class base_module_upgrade(osv.osv_memory):
def upgrade_module(self, cr, uid, ids, context=None):
ir_module = self.pool.get('ir.module.module')
# install and upgrade modules
# install/upgrade: double-check preconditions
ids = ir_module.search(cr, uid, [('state', 'in', ['to upgrade', 'to install'])])
unmet_packages = []
mod_dep_obj = self.pool.get('ir.module.module.dependency')
@ -86,16 +86,15 @@ 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)))
ir_module.download(cr, uid, ids, context=context)
# uninstall modules
to_remove_ids = ir_module.search(cr, uid, [('state', 'in', ['to remove'])])
ir_module.module_uninstall(cr, uid, to_remove_ids, context)
# uninstall: double-check preconditions
# TODO: check all dependent modules are uninstalled
# XXX mod_ids_to_uninstall = ir_module.search(cr, uid, [('state', '=', 'to remove')])
cr.commit()
cr.commit() # persist changes before reopening a cursor
pooler.restart_pool(cr.dbname, update_module=True)
ir_model_data = self.pool.get('ir.model.data')
_, res_id = ir_model_data.get_object_reference(cr, uid, 'base', 'view_base_module_upgrade_install')
return {
'view_type': 'form',
'view_mode': 'form',

View File

@ -40,6 +40,7 @@ import openerp.release as release
import openerp.tools as tools
import openerp.tools.assertion_report as assertion_report
from openerp import SUPERUSER_ID
from openerp.tools.translate import _
from openerp.modules.module import initialize_sys_path, \
load_openerp_module, init_module_models
@ -161,7 +162,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
modobj = pool.get('ir.module.module')
if perform_checks:
modobj.check(cr, 1, [module_id])
modobj.check(cr, SUPERUSER_ID, [module_id])
idref = {}
@ -172,7 +173,7 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
if package.state=='to upgrade':
# upgrading the module information
modobj.write(cr, 1, [module_id], modobj.get_values_from_terp(package.data))
modobj.write(cr, SUPERUSER_ID, [module_id], modobj.get_values_from_terp(package.data))
load_init_xml(module_name, idref, mode)
load_update_xml(module_name, idref, mode)
load_data(module_name, idref, mode)
@ -201,9 +202,9 @@ def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=
ver = release.major_version + '.' + package.data['version']
# Set new modules and dependencies
modobj.write(cr, 1, [module_id], {'state': 'installed', 'latest_version': ver})
modobj.write(cr, SUPERUSER_ID, [module_id], {'state': 'installed', 'latest_version': ver})
# Update translations for all installed languages
modobj.update_translations(cr, 1, [module_id], None)
modobj.update_translations(cr, SUPERUSER_ID, [module_id], None)
package.state = 'installed'
for kind in ('init', 'demo', 'update'):
@ -304,15 +305,15 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
mods = [k for k in tools.config['init'] if tools.config['init'][k]]
if mods:
ids = modobj.search(cr, 1, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
ids = modobj.search(cr, SUPERUSER_ID, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
if ids:
modobj.button_install(cr, 1, ids)
modobj.button_install(cr, SUPERUSER_ID, ids)
mods = [k for k in tools.config['update'] if tools.config['update'][k]]
if mods:
ids = modobj.search(cr, 1, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
ids = modobj.search(cr, SUPERUSER_ID, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
if ids:
modobj.button_upgrade(cr, 1, ids)
modobj.button_upgrade(cr, SUPERUSER_ID, ids)
cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
@ -322,7 +323,11 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
# partially installed modules (i.e. installed/to upgrade), to
# offer a consistent system to the second part: installing
# newly selected modules.
states_to_load = ['installed', 'to upgrade']
# We include the modules 'to remove' in the first step, because
# they are part of the "currently installed" modules. They will
# be dropped in STEP 6 later, before restarting the loading
# process.
states_to_load = ['installed', 'to upgrade', 'to remove']
processed = load_marked_modules(cr, graph, states_to_load, force, status, report, loaded_modules)
processed_modules.extend(processed)
if update_module:
@ -333,9 +338,9 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
# load custom models
cr.execute('select model from ir_model where state=%s', ('manual',))
for model in cr.dictfetchall():
pool.get('ir.model').instanciate(cr, 1, model['model'], {})
pool.get('ir.model').instanciate(cr, SUPERUSER_ID, model['model'], {})
# STEP 4: Finish and cleanup
# STEP 4: Finish and cleanup installations
if processed_modules:
cr.execute("""select model,name from ir_model where id NOT IN (select distinct model_id from ir_model_access)""")
for (model, name) in cr.fetchall():
@ -360,53 +365,46 @@ def load_modules(db, force_demo=False, status=None, update_module=False):
_logger.warning("Model %s is declared but cannot be loaded! (Perhaps a module was partially removed or renamed)", model)
# Cleanup orphan records
pool.get('ir.model.data')._process_end(cr, 1, processed_modules)
pool.get('ir.model.data')._process_end(cr, SUPERUSER_ID, processed_modules)
for kind in ('init', 'demo', 'update'):
tools.config[kind] = {}
cr.commit()
# if update_module:
# STEP 5: Cleanup menus
# Remove menu items that are not referenced by any of other
# (child) menu item, ir_values, or ir_model_data.
# TODO: This code could be a method of ir_ui_menu. Remove menu without actions of children
if update_module:
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)
# STEP 6: Uninstall modules to remove
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
#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,))
# for rmod, rid in cr.fetchall():
# uid = 1
# rmod_module= pool.get(rmod)
# if rmod_module:
# rmod_module.unlink(cr, uid, [rid])
# else:
# _logger.error('Could not locate %s to remove res=%d' % (rmod,rid))
# cr.execute('delete from ir_model_data where module=%s', (mod_name,))
# cr.commit()
# Remove menu items that are not referenced by any of other
# (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)
# Pretend that modules to be removed are actually uninstalled.
#cr.execute("update ir_module_module set state=%s where state=%s", ('uninstalled', 'to remove',))
#cr.commit()
cr.execute("SELECT id FROM ir_module_module WHERE state=%s", ('to remove',))
mod_ids_to_remove = [x[0] for x in cr.fetchall()]
if mod_ids_to_remove:
pool.get('ir.module.module').module_uninstall(cr, SUPERUSER_ID, mod_ids_to_remove)
# Recursive reload, should only happen once, because there should be no
# modules to remove next time
cr.commit()
_logger.info('Reloading registry once more after uninstalling modules')
return pooler.restart_pool(cr.dbname, force_demo, status, update_module)
if report.failures:
_logger.error('At least one test failed when loading the modules.')