passing in GPL-3
[odoo/odoo.git] / bin / addons / __init__.py
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution   
5 #    Copyright (C) 2004-2008 Tiny SPRL (<http://tiny.be>). All Rights Reserved
6 #    $Id$
7 #
8 #    This program is free software: you can redistribute it and/or modify
9 #    it under the terms of the GNU General Public License as published by
10 #    the Free Software Foundation, either version 3 of the License, or
11 #    (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 General Public License for more details.
17 #
18 #    You should have received a copy of the GNU General Public License
19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 #
21 ##############################################################################
22
23 import os, sys, imp
24 import itertools
25 from sets import Set
26
27 import osv
28 import tools
29 import pooler
30
31
32 import netsvc
33 from osv import fields
34
35 import zipfile
36
37 logger = netsvc.Logger()
38
39 opj = os.path.join
40
41 _ad = os.path.abspath(opj(tools.config['root_path'], 'addons'))     # default addons path (base)
42 ad = os.path.abspath(tools.config['addons_path'])           # alternate addons path
43
44 sys.path.insert(1, _ad)
45 if ad != _ad:
46     sys.path.insert(1, ad)
47
48 class Graph(dict):
49
50     def addNode(self, name, deps):
51         max_depth, father = 0, None
52         for n in [Node(x, self) for x in deps]:
53             if n.depth >= max_depth:
54                 father = n
55                 max_depth = n.depth
56         if father:
57             father.addChild(name)
58         else:
59             Node(name, self)
60
61     def __iter__(self):
62         level = 0
63         done = Set(self.keys())
64         while done:
65             level_modules = [(name, module) for name, module in self.items() if module.depth==level]
66             for name, module in level_modules:
67                 done.remove(name)
68                 yield module
69             level += 1
70
71 class Singleton(object):
72
73     def __new__(cls, name, graph):
74         if name in graph:
75             inst = graph[name]
76         else:
77             inst = object.__new__(cls)
78             inst.name = name
79             graph[name] = inst
80         return inst
81
82 class Node(Singleton):
83
84     def __init__(self, name, graph):
85         self.graph = graph
86         if not hasattr(self, 'childs'):
87             self.childs = []
88         if not hasattr(self, 'depth'):
89             self.depth = 0
90
91     def addChild(self, name):
92         node = Node(name, self.graph)
93         node.depth = self.depth + 1
94         if node not in self.childs:
95             self.childs.append(node)
96         for attr in ('init', 'update', 'demo'):
97             if hasattr(self, attr):
98                 setattr(node, attr, True)
99         self.childs.sort(lambda x,y: cmp(x.name, y.name))
100
101     def hasChild(self, name):
102         return Node(name, self.graph) in self.childs or \
103                 bool([c for c in self.childs if c.hasChild(name)])
104
105     def __setattr__(self, name, value):
106         super(Singleton, self).__setattr__(name, value)
107         if name in ('init', 'update', 'demo'):
108             tools.config[name][self.name] = 1
109             for child in self.childs:
110                 setattr(child, name, value)
111         if name == 'depth':
112             for child in self.childs:
113                 setattr(child, name, value + 1)
114
115     def __iter__(self):
116         return itertools.chain(iter(self.childs), *map(iter, self.childs))
117
118     def __str__(self):
119         return self._pprint()
120
121     def _pprint(self, depth=0):
122         s = '%s\n' % self.name
123         for c in self.childs:
124             s += '%s`-> %s' % ('   ' * depth, c._pprint(depth+1))
125         return s
126
127 def get_module_path(module):
128     """Return the path of the given module.
129     """
130
131     if os.path.exists(opj(ad, module)) or os.path.exists(opj(ad, '%s.zip' % module)):
132         return opj(ad, module)
133
134     if os.path.exists(opj(_ad, module)) or os.path.exists(opj(_ad, '%s.zip' % module)):
135         return opj(_ad, module)
136
137     logger.notifyChannel('init', netsvc.LOG_WARNING, 'addon:%s:module not found' % (module,))
138     return False
139     raise IOError, 'Module not found : %s' % module
140
141 def get_module_resource(module, *args):
142     """Return the full path of a resource of the given module.
143
144     @param module: the module
145     @param args: the resource path components
146
147     @return: absolute path to the resource
148     """
149     a = get_module_path(module)
150     return a and opj(a, *args) or False
151
152 def get_modules():
153     """Returns the list of module names
154     """
155
156     module_list = os.listdir(ad)
157     module_names = [os.path.basename(m) for m in module_list]
158     module_list += [m for m in os.listdir(_ad) if m not in module_names]
159
160     return module_list
161
162 def create_graph(module_list, force=None):
163     if not force:
164         force=[]
165     graph = Graph()
166     packages = []
167
168     for module in module_list:
169         if module[-4:]=='.zip':
170             module = module[:-4]
171         try:
172             mod_path = get_module_path(module)
173             if not mod_path:
174                 continue
175         except IOError:
176             continue
177         terp_file = get_module_resource(module, '__terp__.py')
178         if not terp_file: continue
179         if os.path.isfile(terp_file) or zipfile.is_zipfile(mod_path):
180             try:
181                 info = eval(tools.file_open(terp_file).read())
182             except:
183                 logger.notifyChannel('init', netsvc.LOG_ERROR, 'addon:%s:eval file %s' % (module, terp_file))
184                 raise
185             if info.get('installable', True):
186                 packages.append((module, info.get('depends', []), info))
187
188     current,later = Set([p for p, dep, data in packages]), Set()
189     while packages and current > later:
190         package, deps, datas = packages[0]
191
192         # if all dependencies of 'package' are already in the graph, add 'package' in the graph
193         if reduce(lambda x,y: x and y in graph, deps, True):
194             if not package in current:
195                 packages.pop(0)
196                 continue
197             later.clear()
198             current.remove(package)
199             graph.addNode(package, deps)
200             node = Node(package, graph)
201             node.datas = datas
202             for kind in ('init', 'demo', 'update'):
203                 if package in tools.config[kind] or 'all' in tools.config[kind] or kind in force:
204                     setattr(node, kind, True)
205         else:
206             later.add(package)
207             packages.append((package, deps, datas))
208         packages.pop(0)
209     
210     for package in later:
211         logger.notifyChannel('init', netsvc.LOG_ERROR, 'addon:%s:Unmet dependency' % package)
212
213     return graph
214
215 def init_module_objects(cr, module_name, obj_list):
216     pool = pooler.get_pool(cr.dbname)
217     logger.notifyChannel('init', netsvc.LOG_INFO, 'addon:%s:creating or updating database tables' % module_name)
218     for obj in obj_list:
219         if hasattr(obj, 'init'):
220             obj.init(cr)
221         obj._auto_init(cr, {'module': module_name})
222         cr.commit()
223
224 def load_module_graph(cr, graph, status=None, **kwargs):
225     # **kwargs is passed directly to convert_xml_import
226     if not status:
227         status={}
228
229     status = status.copy()
230     package_todo = []
231     statusi = 0
232     for package in graph:
233         status['progress'] = (float(statusi)+0.1)/len(graph)
234         m = package.name
235         logger.notifyChannel('init', netsvc.LOG_INFO, 'addon:%s' % m)
236         sys.stdout.flush()
237         pool = pooler.get_pool(cr.dbname)
238         modules = pool.instanciate(m, cr)
239         
240         cr.execute('select id from ir_module_module where name=%s', (m,))
241         mid = int(cr.rowcount and cr.fetchone()[0] or 0)
242
243         cr.execute('select state, demo from ir_module_module where id=%d', (mid,))
244         (package_state, package_demo) = (cr.rowcount and cr.fetchone()) or ('uninstalled', False)
245         idref = {}
246         status['progress'] = (float(statusi)+0.4)/len(graph)
247         if hasattr(package, 'init') or hasattr(package, 'update') or package_state in ('to install', 'to upgrade'):
248             init_module_objects(cr, m, modules)
249             for kind in ('init', 'update'):
250                 for filename in package.datas.get('%s_xml' % kind, []):
251                     mode = 'update'
252                     if hasattr(package, 'init') or package_state=='to install':
253                         mode = 'init'
254                     logger.notifyChannel('init', netsvc.LOG_INFO, 'addon:%s:loading %s' % (m, filename))
255                     name, ext = os.path.splitext(filename)
256                     if ext == '.csv':
257                         tools.convert_csv_import(cr, m, os.path.basename(filename), tools.file_open(opj(m, filename)).read(), idref, mode=mode)
258                     elif ext == '.sql':
259                         queries = tools.file_open(opj(m, filename)).read().split(';')
260                         for query in queries:
261                             new_query = ' '.join(query.split())
262                             if new_query:
263                                 cr.execute(new_query)
264                     else:
265                         tools.convert_xml_import(cr, m, tools.file_open(opj(m, filename)), idref, mode=mode, **kwargs)
266             if hasattr(package, 'demo') or (package_demo and package_state != 'installed'):
267                 status['progress'] = (float(statusi)+0.75)/len(graph)
268                 for xml in package.datas.get('demo_xml', []):
269                     name, ext = os.path.splitext(xml)
270                     logger.notifyChannel('init', netsvc.LOG_INFO, 'addon:%s:loading %s' % (m, xml))
271                     if ext == '.csv':
272                         tools.convert_csv_import(cr, m, os.path.basename(xml), tools.file_open(opj(m, xml)).read(), idref, noupdate=True)
273                     else:
274                         tools.convert_xml_import(cr, m, tools.file_open(opj(m, xml)), idref, noupdate=True, **kwargs)
275                 cr.execute('update ir_module_module set demo=%s where id=%d', (True, mid))
276             package_todo.append(package.name)
277             cr.execute("update ir_module_module set state='installed' where state in ('to upgrade', 'to install') and id=%d", (mid,))
278
279             cr.commit()
280             
281             # Update translations for all installed languages
282             modobj = pool.get('ir.module.module')
283             if modobj:
284                 modobj.update_translations(cr, 1, [mid], None)
285                 cr.commit()
286
287         statusi+=1
288
289     cr.execute("""select model,name from ir_model where id not in (select model_id from ir_model_access)""")
290     for (model,name) in cr.fetchall():
291         logger.notifyChannel('init', netsvc.LOG_WARNING, 'addon:object %s (%s) has no access rules!' % (model,name))
292  
293     pool = pooler.get_pool(cr.dbname)
294     cr.execute('select * from ir_model where state=%s', ('manual',))
295     for model in cr.dictfetchall():
296         pool.get('ir.model').instanciate(cr, 1, model['model'], {})
297
298     pool.get('ir.model.data')._process_end(cr, 1, package_todo)
299     cr.commit()
300
301 def register_classes():
302     module_list = get_modules()
303     for package in create_graph(module_list):
304         m = package.name
305         logger.notifyChannel('init', netsvc.LOG_INFO, 'addon:%s:registering classes' % m)
306         sys.stdout.flush()
307
308         mod_path = get_module_path(m)
309         if not os.path.isfile(mod_path+'.zip'):
310             # XXX must restrict to only addons paths
311             imp.load_module(m, *imp.find_module(m))
312         else:
313             import zipimport
314             try:
315                 zimp = zipimport.zipimporter(mod_path+'.zip')
316                 zimp.load_module(m)
317             except zipimport.ZipImportError:
318                 logger.notifyChannel('init', netsvc.LOG_ERROR, 'Couldn\'t find module %s' % m)
319
320 def load_modules(db, force_demo=False, status=None, update_module=False):
321     if not status:
322         status={}
323     cr = db.cursor()
324     force = []
325     if force_demo:
326         force.append('demo')
327     if update_module:
328         for module in tools.config['init']:
329             cr.execute('update ir_module_module set state=%s where state=%s and name=%s', ('to install', 'uninstalled', module))
330             cr.commit()
331         cr.execute("select name from ir_module_module where state in ('installed', 'to install', 'to upgrade','to remove')")
332     else:
333         cr.execute("select name from ir_module_module where state in ('installed', 'to upgrade', 'to remove')")
334     module_list = [name for (name,) in cr.fetchall()]
335     graph = create_graph(module_list, force)
336     report = tools.assertion_report()
337     load_module_graph(cr, graph, status, report=report)
338     if report.get_report():
339         logger.notifyChannel('init', netsvc.LOG_INFO, 'assert:%s' % report)
340
341     for kind in ('init', 'demo', 'update'):
342         tools.config[kind]={}
343
344     cr.commit()
345     if update_module:
346         cr.execute("select id,name from ir_module_module where state in ('to remove')")
347         for mod_id, mod_name in cr.fetchall():
348             pool = pooler.get_pool(cr.dbname)
349             cr.execute('select model,res_id from ir_model_data where not noupdate and module=%s order by id desc', (mod_name,))
350             for rmod,rid in cr.fetchall():
351                 #
352                 # TO BE Improved:
353                 #   I can not use the class_pool has _table could be defined in __init__
354                 #   and I can not use the pool has the module could not be loaded in the pool
355                 #
356                 uid = 1
357                 pool.get(rmod).unlink(cr, uid, [rid])
358             cr.commit()
359         #
360         # TODO: remove menu without actions of childs
361         #
362         cr.execute('''delete from
363                 ir_ui_menu
364             where
365                 (id not in (select parent_id from ir_ui_menu where parent_id is not null))
366             and
367                 (id not in (select res_id from ir_values where model='ir.ui.menu'))
368             and
369                 (id not in (select res_id from ir_model_data where model='ir.ui.menu'))''')
370
371         cr.execute("update ir_module_module set state=%s where state in ('to remove')", ('uninstalled', ))
372         cr.commit()
373         pooler.restart_pool(cr.dbname)
374     cr.close()
375
376
377 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
378