[FIX] dependencies handling in modules listing of web client
* Correctly fetch dependencies from server * Switch topological sort to a more formal algorithm (and test it) lp bug: https://launchpad.net/bugs/947161 fixed bzr revid: xmo@openerp.com-20120306142010-zflycbrj4aq41mv3
This commit is contained in:
parent
b4f08f9e7e
commit
532e60a007
|
@ -435,6 +435,50 @@ class Database(openerpweb.Controller):
|
||||||
return {'error': e.faultCode, 'title': 'Change Password'}
|
return {'error': e.faultCode, 'title': 'Change Password'}
|
||||||
return {'error': 'Error, password not changed !', 'title': 'Change Password'}
|
return {'error': 'Error, password not changed !', 'title': 'Change Password'}
|
||||||
|
|
||||||
|
def topological_sort(modules):
|
||||||
|
""" Return a list of module names sorted so that their dependencies of the
|
||||||
|
modules are listed before the module itself
|
||||||
|
|
||||||
|
modules is a dict of {module_name: dependencies}
|
||||||
|
|
||||||
|
:param modules: modules to sort
|
||||||
|
:type modules: dict
|
||||||
|
:returns: list(str)
|
||||||
|
"""
|
||||||
|
|
||||||
|
dependencies = set(itertools.chain.from_iterable(modules.itervalues()))
|
||||||
|
# incoming edge: dependency on other module (if a depends on b, a has an
|
||||||
|
# incoming edge from b, aka there's an edge from b to a)
|
||||||
|
# outgoing edge: other module depending on this one
|
||||||
|
|
||||||
|
# [Tarjan 1976], http://en.wikipedia.org/wiki/Topological_sorting#Algorithms
|
||||||
|
#L ← Empty list that will contain the sorted nodes
|
||||||
|
L = []
|
||||||
|
#S ← Set of all nodes with no outgoing edges (modules on which no other
|
||||||
|
# module depends)
|
||||||
|
S = set(module for module in modules if module not in dependencies)
|
||||||
|
|
||||||
|
visited = set()
|
||||||
|
#function visit(node n)
|
||||||
|
def visit(n):
|
||||||
|
#if n has not been visited yet then
|
||||||
|
if n not in visited:
|
||||||
|
#mark n as visited
|
||||||
|
visited.add(n)
|
||||||
|
#change: n not web module, can not be resolved, ignore
|
||||||
|
if n not in modules: return
|
||||||
|
#for each node m with an edge from m to n do (dependencies of n)
|
||||||
|
for m in modules[n]:
|
||||||
|
#visit(m)
|
||||||
|
visit(m)
|
||||||
|
#add n to L
|
||||||
|
L.append(n)
|
||||||
|
#for each node n in S do
|
||||||
|
for n in S:
|
||||||
|
#visit(n)
|
||||||
|
visit(n)
|
||||||
|
return L
|
||||||
|
|
||||||
class Session(openerpweb.Controller):
|
class Session(openerpweb.Controller):
|
||||||
_cp_path = "/web/session"
|
_cp_path = "/web/session"
|
||||||
|
|
||||||
|
@ -502,35 +546,32 @@ class Session(openerpweb.Controller):
|
||||||
def modules(self, req):
|
def modules(self, req):
|
||||||
# Compute available candidates module
|
# Compute available candidates module
|
||||||
loadable = openerpweb.addons_manifest
|
loadable = openerpweb.addons_manifest
|
||||||
loaded = req.config.server_wide_modules
|
loaded = set(req.config.server_wide_modules)
|
||||||
candidates = [mod for mod in loadable if mod not in loaded]
|
candidates = [mod for mod in loadable if mod not in loaded]
|
||||||
|
|
||||||
|
# already installed modules have no dependencies
|
||||||
|
modules = dict.fromkeys(loaded, [])
|
||||||
|
|
||||||
# Compute active true modules that might be on the web side only
|
# Compute active true modules that might be on the web side only
|
||||||
modules = dict((name, openerpweb.addons_manifest[name].get('depends', []))
|
modules.update((name, openerpweb.addons_manifest[name].get('depends', []))
|
||||||
for name in candidates
|
for name in candidates
|
||||||
if openerpweb.addons_manifest[name].get('active'))
|
if openerpweb.addons_manifest[name].get('active'))
|
||||||
|
|
||||||
# Retrieve database installed modules
|
# Retrieve database installed modules
|
||||||
Modules = req.session.model('ir.module.module')
|
Modules = req.session.model('ir.module.module')
|
||||||
modules.update((module['name'], module.get('depends', []))
|
for module in Modules.search_read(
|
||||||
for module in Modules.search_read(
|
[('state','=','installed'), ('name','in', candidates)],
|
||||||
[('state','=','installed'), ('name','in', candidates)],
|
['name', 'dependencies_id']):
|
||||||
['name', 'depends']))
|
deps = module.get('dependencies_id')
|
||||||
|
if deps:
|
||||||
|
dependencies = map(
|
||||||
|
operator.itemgetter('name'),
|
||||||
|
req.session.model('ir.module.module.dependency').read(deps, ['name']))
|
||||||
|
modules[module['name']] = list(
|
||||||
|
set(modules.get(module['name'], []) + dependencies))
|
||||||
|
|
||||||
ordered_mods = []
|
sorted_modules = topological_sort(modules)
|
||||||
def insert(module):
|
return [module for module in sorted_modules if module not in loaded]
|
||||||
# already inserted
|
|
||||||
if module in ordered_mods: return
|
|
||||||
# should be preloaded
|
|
||||||
if module not in modules: return
|
|
||||||
for name in modules[module]:
|
|
||||||
insert(name)
|
|
||||||
ordered_mods.append(module)
|
|
||||||
|
|
||||||
for name in modules:
|
|
||||||
insert(name)
|
|
||||||
|
|
||||||
return ordered_mods
|
|
||||||
|
|
||||||
@openerpweb.jsonrequest
|
@openerpweb.jsonrequest
|
||||||
def eval_domain_and_context(self, req, contexts, domains,
|
def eval_domain_and_context(self, req, contexts, domains,
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import random
|
||||||
|
import unittest2
|
||||||
|
|
||||||
|
from ..controllers.main import topological_sort
|
||||||
|
|
||||||
|
def sample(population):
|
||||||
|
return random.sample(
|
||||||
|
population,
|
||||||
|
random.randint(0, min(len(population), 5)))
|
||||||
|
|
||||||
|
class TestModulesLoading(unittest2.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.mods = map(str, range(1000))
|
||||||
|
def test_topological_sort(self):
|
||||||
|
random.shuffle(self.mods)
|
||||||
|
modules = [
|
||||||
|
(k, sample(self.mods[:i]))
|
||||||
|
for i, k in enumerate(self.mods)]
|
||||||
|
random.shuffle(modules)
|
||||||
|
ms = dict(modules)
|
||||||
|
|
||||||
|
seen = set()
|
||||||
|
sorted_modules = topological_sort(ms)
|
||||||
|
for module in sorted_modules:
|
||||||
|
deps = ms[module]
|
||||||
|
self.assertGreaterEqual(
|
||||||
|
seen, set(deps),
|
||||||
|
'Module %s (index %d), ' \
|
||||||
|
'missing dependencies %s from loaded modules %s' % (
|
||||||
|
module, sorted_modules.index(module), deps, seen
|
||||||
|
))
|
||||||
|
seen.add(module)
|
Loading…
Reference in New Issue