[MERGE] demo data: remove salesman on customers
[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 = netsvc.Logger()
52
53
54 class Graph(dict):
55     """ Modules dependency graph.
56
57     The graph is a mapping from module name to Nodes.
58
59     """
60
61     def add_node(self, name, info):
62         max_depth, father = 0, None
63         for n in [Node(x, self, None) for x in info['depends']]:
64             if n.depth >= max_depth:
65                 father = n
66                 max_depth = n.depth
67         if father:
68             return father.add_child(name, info)
69         else:
70             return Node(name, self, info)
71
72     def update_from_db(self, cr):
73         if not len(self):
74             return
75         # update the graph with values from the database (if exist)
76         ## First, we set the default values for each package in graph
77         additional_data = dict.fromkeys(self.keys(), {'id': 0, 'state': 'uninstalled', 'dbdemo': False, 'installed_version': None})
78         ## Then we get the values from the database
79         cr.execute('SELECT name, id, state, demo AS dbdemo, latest_version AS installed_version'
80                    '  FROM ir_module_module'
81                    ' WHERE name IN %s',(tuple(additional_data),)
82                    )
83
84         ## and we update the default values with values from the database
85         additional_data.update(dict([(x.pop('name'), x) for x in cr.dictfetchall()]))
86
87         for package in self.values():
88             for k, v in additional_data[package.name].items():
89                 setattr(package, k, v)
90
91     def add_module(self, cr, module, force=None):
92         self.add_modules(cr, [module], force)
93
94     def add_modules(self, cr, module_list, force=None):
95         if force is None:
96             force = []
97         packages = []
98         len_graph = len(self)
99         for module in module_list:
100             # This will raise an exception if no/unreadable descriptor file.
101             # NOTE The call to load_information_from_description_file is already
102             # done by db.initialize, so it is possible to not do it again here.
103             info = openerp.modules.module.load_information_from_description_file(module)
104             if info and info['installable']:
105                 packages.append((module, info)) # TODO directly a dict, like in get_modules_with_version
106             else:
107                 logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: not installable, skipped' % (module))
108
109         dependencies = dict([(p, info['depends']) for p, info in packages])
110         current, later = set([p for p, info in packages]), set()
111
112         while packages and current > later:
113             package, info = packages[0]
114             deps = info['depends']
115
116             # if all dependencies of 'package' are already in the graph, add 'package' in the graph
117             if reduce(lambda x, y: x and y in self, deps, True):
118                 if not package in current:
119                     packages.pop(0)
120                     continue
121                 later.clear()
122                 current.remove(package)
123                 node = self.add_node(package, info)
124                 node.data = info
125                 for kind in ('init', 'demo', 'update'):
126                     if package in tools.config[kind] or 'all' in tools.config[kind] or kind in force:
127                         setattr(node, kind, True)
128             else:
129                 later.add(package)
130                 packages.append((package, info))
131             packages.pop(0)
132
133         self.update_from_db(cr)
134
135         for package in later:
136             unmet_deps = filter(lambda p: p not in self, dependencies[package])
137             logger.notifyChannel('init', netsvc.LOG_ERROR, 'module %s: Unmet dependencies: %s' % (package, ', '.join(unmet_deps)))
138
139         result = len(self) - len_graph
140         if result != len(module_list):
141             logger.notifyChannel('init', netsvc.LOG_WARNING, 'Not all modules have loaded.')
142         return result
143
144
145     def __iter__(self):
146         level = 0
147         done = set(self.keys())
148         while done:
149             level_modules = [(name, module) for name, module in self.items() if module.depth==level]
150             for name, module in level_modules:
151                 done.remove(name)
152                 yield module
153             level += 1
154
155
156 class Singleton(object):
157     def __new__(cls, name, graph, info):
158         if name in graph:
159             inst = graph[name]
160         else:
161             inst = object.__new__(cls)
162             inst.name = name
163             inst.info = info
164             graph[name] = inst
165         return inst
166
167
168 class Node(Singleton):
169     """ One module in the modules dependency graph.
170
171     Node acts as a per-module singleton. A node is constructed via
172     Graph.add_module() or Graph.add_modules(). Some of its fields are from
173     ir_module_module (setted by Graph.update_from_db()).
174
175     """
176
177     def __init__(self, name, graph, info):
178         self.graph = graph
179         if not hasattr(self, 'children'):
180             self.children = []
181         if not hasattr(self, 'depth'):
182             self.depth = 0
183
184     def add_child(self, name, info):
185         node = Node(name, self.graph, info)
186         node.depth = self.depth + 1
187         if node not in self.children:
188             self.children.append(node)
189         for attr in ('init', 'update', 'demo'):
190             if hasattr(self, attr):
191                 setattr(node, attr, True)
192         self.children.sort(lambda x, y: cmp(x.name, y.name))
193         return node
194
195     def __setattr__(self, name, value):
196         super(Singleton, self).__setattr__(name, value)
197         if name in ('init', 'update', 'demo'):
198             tools.config[name][self.name] = 1
199             for child in self.children:
200                 setattr(child, name, value)
201         if name == 'depth':
202             for child in self.children:
203                 setattr(child, name, value + 1)
204
205     def __iter__(self):
206         return itertools.chain(iter(self.children), *map(iter, self.children))
207
208     def __str__(self):
209         return self._pprint()
210
211     def _pprint(self, depth=0):
212         s = '%s\n' % self.name
213         for c in self.children:
214             s += '%s`-> %s' % ('   ' * depth, c._pprint(depth+1))
215         return s
216
217
218 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: