[IMP] [MOV] Moved test_osv and test_translate from unchecked test directory to tests...
[odoo/odoo.git] / openerp / modules / graph.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
6 #    Copyright (C) 2010-2011 OpenERP s.a. (<http://openerp.com>).
7 #
8 #    This program is free software: you can redistribute it and/or modify
9 #    it under the terms of the GNU Affero General Public License as
10 #    published by the Free Software Foundation, either version 3 of the
11 #    License, or (at your option) any later version.
12 #
13 #    This program is distributed in the hope that it will be useful,
14 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 #    GNU Affero General Public License for more details.
17 #
18 #    You should have received a copy of the GNU Affero General Public License
19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21 ##############################################################################
22
23 """ Modules dependency graph. """
24
25 import os, sys, imp
26 from os.path import join as opj
27 import itertools
28 import zipimport
29
30 import openerp
31
32 import openerp.osv as osv
33 import openerp.tools as tools
34 import openerp.tools.osutil as osutil
35 from openerp.tools.safe_eval import safe_eval as eval
36 import openerp.pooler as pooler
37 from openerp.tools.translate import _
38
39 import openerp.netsvc as netsvc
40
41 import zipfile
42 import openerp.release as release
43
44 import re
45 import base64
46 from zipfile import PyZipFile, ZIP_DEFLATED
47 from cStringIO import StringIO
48
49 import logging
50
51 _logger = logging.getLogger(__name__)
52
53 class Graph(dict):
54     """ Modules dependency graph.
55
56     The graph is a mapping from module name to Nodes.
57
58     """
59
60     def add_node(self, name, info):
61         max_depth, father = 0, None
62         for n in [Node(x, self, None) for x in info['depends']]:
63             if n.depth >= max_depth:
64                 father = n
65                 max_depth = n.depth
66         if father:
67             return father.add_child(name, info)
68         else:
69             return Node(name, self, info)
70
71     def update_from_db(self, cr):
72         if not len(self):
73             return
74         # update the graph with values from the database (if exist)
75         ## First, we set the default values for each package in graph
76         additional_data = dict.fromkeys(self.keys(), {'id': 0, 'state': 'uninstalled', 'dbdemo': False, 'installed_version': None})
77         ## Then we get the values from the database
78         cr.execute('SELECT name, id, state, demo AS dbdemo, latest_version AS installed_version'
79                    '  FROM ir_module_module'
80                    ' WHERE name IN %s',(tuple(additional_data),)
81                    )
82
83         ## and we update the default values with values from the database
84         additional_data.update(dict([(x.pop('name'), x) for x in cr.dictfetchall()]))
85
86         for package in self.values():
87             for k, v in additional_data[package.name].items():
88                 setattr(package, k, v)
89
90     def add_module(self, cr, module, force=None):
91         self.add_modules(cr, [module], force)
92
93     def add_modules(self, cr, module_list, force=None):
94         if force is None:
95             force = []
96         packages = []
97         len_graph = len(self)
98         for module in module_list:
99             # This will raise an exception if no/unreadable descriptor file.
100             # NOTE The call to load_information_from_description_file is already
101             # done by db.initialize, so it is possible to not do it again here.
102             info = openerp.modules.module.load_information_from_description_file(module)
103             if info and info['installable']:
104                 packages.append((module, info)) # TODO directly a dict, like in get_modules_with_version
105             else:
106                 _logger.warning('module %s: not installable, skipped', module)
107
108         dependencies = dict([(p, info['depends']) for p, info in packages])
109         current, later = set([p for p, info in packages]), set()
110
111         while packages and current > later:
112             package, info = packages[0]
113             deps = info['depends']
114
115             # if all dependencies of 'package' are already in the graph, add 'package' in the graph
116             if reduce(lambda x, y: x and y in self, deps, True):
117                 if not package in current:
118                     packages.pop(0)
119                     continue
120                 later.clear()
121                 current.remove(package)
122                 node = self.add_node(package, info)
123                 node.data = info
124                 for kind in ('init', 'demo', 'update'):
125                     if package in tools.config[kind] or 'all' in tools.config[kind] or kind in force:
126                         setattr(node, kind, True)
127             else:
128                 later.add(package)
129                 packages.append((package, info))
130             packages.pop(0)
131
132         self.update_from_db(cr)
133
134         for package in later:
135             unmet_deps = filter(lambda p: p not in self, dependencies[package])
136             _logger.error('module %s: Unmet dependencies: %s', package, ', '.join(unmet_deps))
137
138         result = len(self) - len_graph
139         if result != len(module_list):
140             _logger.warning('Some modules were not loaded.')
141         return result
142
143
144     def __iter__(self):
145         level = 0
146         done = set(self.keys())
147         while done:
148             level_modules = sorted((name, module) for name, module in self.items() if module.depth==level)
149             for name, module in level_modules:
150                 done.remove(name)
151                 yield module
152             level += 1
153
154
155 class Singleton(object):
156     def __new__(cls, name, graph, info):
157         if name in graph:
158             inst = graph[name]
159         else:
160             inst = object.__new__(cls)
161             inst.name = name
162             inst.info = info
163             graph[name] = inst
164         return inst
165
166
167 class Node(Singleton):
168     """ One module in the modules dependency graph.
169
170     Node acts as a per-module singleton. A node is constructed via
171     Graph.add_module() or Graph.add_modules(). Some of its fields are from
172     ir_module_module (setted by Graph.update_from_db()).
173
174     """
175
176     def __init__(self, name, graph, info):
177         self.graph = graph
178         if not hasattr(self, 'children'):
179             self.children = []
180         if not hasattr(self, 'depth'):
181             self.depth = 0
182
183     def add_child(self, name, info):
184         node = Node(name, self.graph, info)
185         node.depth = self.depth + 1
186         if node not in self.children:
187             self.children.append(node)
188         for attr in ('init', 'update', 'demo'):
189             if hasattr(self, attr):
190                 setattr(node, attr, True)
191         self.children.sort(lambda x, y: cmp(x.name, y.name))
192         return node
193
194     def __setattr__(self, name, value):
195         super(Singleton, self).__setattr__(name, value)
196         if name in ('init', 'update', 'demo'):
197             tools.config[name][self.name] = 1
198             for child in self.children:
199                 setattr(child, name, value)
200         if name == 'depth':
201             for child in self.children:
202                 setattr(child, name, value + 1)
203
204     def __iter__(self):
205         return itertools.chain(iter(self.children), *map(iter, self.children))
206
207     def __str__(self):
208         return self._pprint()
209
210     def _pprint(self, depth=0):
211         s = '%s\n' % self.name
212         for c in self.children:
213             s += '%s`-> %s' % ('   ' * depth, c._pprint(depth+1))
214         return s
215
216
217 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: