[FIX] module: fix dependency computation to work recursively

bzr revid: odo@openerp.com-20120326231839-q7x3zr1bgenom3pc
This commit is contained in:
Olivier Dony 2012-03-27 01:18:39 +02:00
parent 8bd0cf42f3
commit c6f72008ed
1 changed files with 44 additions and 41 deletions

View File

@ -2,8 +2,7 @@
############################################################################## ##############################################################################
# #
# OpenERP, Open Source Management Solution # OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). # Copyright (C) 2004-2012 OpenERP S.A. (<http://openerp.com>).
# Copyright (C) 2010 OpenERP s.a. (<http://openerp.com>).
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as # it under the terms of the GNU Affero General Public License as
@ -113,7 +112,7 @@ class module(osv.osv):
if field_name is None or 'menus_by_module' in field_name: if field_name is None or 'menus_by_module' in field_name:
dmodels.append('ir.ui.menu') dmodels.append('ir.ui.menu')
assert dmodels, "no models for %s" % field_name assert dmodels, "no models for %s" % field_name
for module_rec in self.browse(cr, uid, ids, context=context): for module_rec in self.browse(cr, uid, ids, context=context):
res[module_rec.id] = { res[module_rec.id] = {
'menus_by_module': [], 'menus_by_module': [],
@ -174,7 +173,7 @@ class module(osv.osv):
# installed_version refer the latest version (the one on disk) # installed_version refer the latest version (the one on disk)
# latest_version refer the installed version (the one in database) # latest_version refer the installed version (the one in database)
# published_version refer the version available on the repository # published_version refer the version available on the repository
'installed_version': fields.function(_get_latest_version, 'installed_version': fields.function(_get_latest_version,
string='Latest version', type='char'), string='Latest version', type='char'),
'latest_version': fields.char('Installed version', size=64, readonly=True), 'latest_version': fields.char('Installed version', size=64, readonly=True),
'published_version': fields.char('Published Version', size=64, readonly=True), 'published_version': fields.char('Published Version', size=64, readonly=True),
@ -364,46 +363,50 @@ class module(osv.osv):
def button_install_cancel(self, cr, uid, ids, context=None): def button_install_cancel(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'state': 'uninstalled', 'demo':False}) self.write(cr, uid, ids, {'state': 'uninstalled', 'demo':False})
return True return True
def module_uninstall(self, cr, uid, ids, context=None): def module_uninstall(self, cr, uid, ids, context=None):
# uninstall must be done respecting the reverse-dependency order
# you have to uninstall in the right order, not all modules at the same time ir_model_data = self.pool.get('ir.model.data')
modules_to_remove = [m.name for m in self.browse(cr, uid, ids, context)]
model_data = self.pool.get('ir.model.data') data_ids = ir_model_data.search(cr, uid, [('module', 'in', modules_to_remove)])
remove_modules = map(lambda x: x.name, self.browse(cr, uid, ids, context)) ir_model_data._pre_process_unlink(cr, uid, data_ids, context)
ir_model_data.unlink(cr, uid, data_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'}) self.write(cr, uid, ids, {'state': 'uninstalled'})
# should we call process_end istead of loading, or both ? # should we call process_end instead of loading, or both ?
return True return True
def check_dependancy(self, cr, uid, ids, context): def downstream_dependencies(self, cr, uid, ids, known_dep_ids=None,
res = [] exclude_states=['uninstalled','uninstallable','to remove'],
for module in self.browse(cr, uid, ids): context=None):
cr.execute('''select m.id """Return the ids of all modules that directly or indirectly depend
from on the given module `ids`, and that satisfy the `exclude_states`
ir_module_module_dependency d filter"""
join if not ids: return []
ir_module_module m on (d.module_id=m.id) known_dep_ids = set(known_dep_ids or [])
where cr.execute('''SELECT DISTINCT m.id
d.name=%s and FROM
m.state not in ('uninstalled','uninstallable','to remove')''', (module.name,)) ir_module_module_dependency d
res = cr.fetchall() JOIN
return res ir_module_module m ON (d.module_id=m.id)
WHERE
d.name IN (SELECT name from ir_module_module where id in %s) AND
m.state NOT IN %s AND
m.id NOT IN %s ''',
(tuple(ids),tuple(exclude_states), tuple(known_dep_ids or ids)))
new_dep_ids = set([m[0] for m in cr.fetchall()])
missing_mod_ids = new_dep_ids - known_dep_ids
known_dep_ids |= new_dep_ids
if missing_mod_ids:
known_dep_ids |= set(self.downstream_dependencies(cr, uid, list(missing_mod_ids),
known_dep_ids, exclude_states,context))
return list(known_dep_ids)
def button_uninstall(self, cr, uid, ids, context=None): def button_uninstall(self, cr, uid, ids, context=None):
res = self.check_dependancy(cr, uid, ids, context) if any(m.name == 'base' for m in self.browse(cr, uid, ids)):
for i in range(0,len(res)): raise orm.except_orm(_('Error'), _("The `base` module cannot be uninstalled"))
res_depend = self.check_dependancy(cr, uid, [res[i][0]], context) dep_ids = self.downstream_dependencies(cr, uid, ids, context=context)
for j in range(0,len(res_depend)): self.write(cr, uid, ids + dep_ids, {'state': 'to remove'})
ids.append(res_depend[j][0])
ids.append(res[i][0])
self.write(cr, uid, ids, {'state': 'to remove'})
return dict(ACTION_DICT, name=_('Uninstall')) return dict(ACTION_DICT, name=_('Uninstall'))
def button_uninstall_cancel(self, cr, uid, ids, context=None): def button_uninstall_cancel(self, cr, uid, ids, context=None):
@ -486,7 +489,7 @@ class module(osv.osv):
updated_values = {} updated_values = {}
for key in values: for key in values:
old = getattr(mod, key) old = getattr(mod, key)
updated = isinstance(values[key], basestring) and tools.ustr(values[key]) or values[key] updated = isinstance(values[key], basestring) and tools.ustr(values[key]) or values[key]
if not old == updated: if not old == updated:
updated_values[key] = values[key] updated_values[key] = values[key]
if terp.get('installable', True) and mod.state == 'uninstallable': if terp.get('installable', True) and mod.state == 'uninstallable':