1 # -*- encoding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2008 Tiny SPRL (<http://tiny.be>). All Rights Reserved
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.
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.
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/>.
21 ##############################################################################
33 from osv import fields
37 logger = netsvc.Logger()
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
44 sys.path.insert(1, _ad)
46 sys.path.insert(1, ad)
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:
63 done = Set(self.keys())
65 level_modules = [(name, module) for name, module in self.items() if module.depth==level]
66 for name, module in level_modules:
71 class Singleton(object):
73 def __new__(cls, name, graph):
77 inst = object.__new__(cls)
82 class Node(Singleton):
84 def __init__(self, name, graph):
86 if not hasattr(self, 'childs'):
88 if not hasattr(self, 'depth'):
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))
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)])
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)
112 for child in self.childs:
113 setattr(child, name, value + 1)
116 return itertools.chain(iter(self.childs), *map(iter, self.childs))
119 return self._pprint()
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))
127 def get_module_path(module):
128 """Return the path of the given module.
131 if os.path.exists(opj(ad, module)) or os.path.exists(opj(ad, '%s.zip' % module)):
132 return opj(ad, module)
134 if os.path.exists(opj(_ad, module)) or os.path.exists(opj(_ad, '%s.zip' % module)):
135 return opj(_ad, module)
137 logger.notifyChannel('init', netsvc.LOG_WARNING, 'addon:%s:module not found' % (module,))
139 raise IOError, 'Module not found : %s' % module
141 def get_module_resource(module, *args):
142 """Return the full path of a resource of the given module.
144 @param module: the module
145 @param args: the resource path components
147 @return: absolute path to the resource
149 a = get_module_path(module)
150 return a and opj(a, *args) or False
153 """Returns the list of module names
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]
162 def create_graph(module_list, force=None):
168 for module in module_list:
169 if module[-4:]=='.zip':
172 mod_path = get_module_path(module)
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):
181 info = eval(tools.file_open(terp_file).read())
183 logger.notifyChannel('init', netsvc.LOG_ERROR, 'addon:%s:eval file %s' % (module, terp_file))
185 if info.get('installable', True):
186 packages.append((module, info.get('depends', []), info))
188 current,later = Set([p for p, dep, data in packages]), Set()
189 while packages and current > later:
190 package, deps, datas = packages[0]
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:
198 current.remove(package)
199 graph.addNode(package, deps)
200 node = Node(package, graph)
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)
207 packages.append((package, deps, datas))
210 for package in later:
211 logger.notifyChannel('init', netsvc.LOG_ERROR, 'addon:%s:Unmet dependency' % package)
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)
219 if hasattr(obj, 'init'):
221 obj._auto_init(cr, {'module': module_name})
224 def load_module_graph(cr, graph, status=None, **kwargs):
225 # **kwargs is passed directly to convert_xml_import
229 status = status.copy()
232 for package in graph:
233 status['progress'] = (float(statusi)+0.1)/len(graph)
235 logger.notifyChannel('init', netsvc.LOG_INFO, 'addon:%s' % m)
237 pool = pooler.get_pool(cr.dbname)
238 modules = pool.instanciate(m, cr)
240 cr.execute('select id from ir_module_module where name=%s', (m,))
241 mid = int(cr.rowcount and cr.fetchone()[0] or 0)
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)
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, []):
252 if hasattr(package, 'init') or package_state=='to install':
254 logger.notifyChannel('init', netsvc.LOG_INFO, 'addon:%s:loading %s' % (m, filename))
255 name, ext = os.path.splitext(filename)
257 tools.convert_csv_import(cr, m, os.path.basename(filename), tools.file_open(opj(m, filename)).read(), idref, mode=mode)
259 queries = tools.file_open(opj(m, filename)).read().split(';')
260 for query in queries:
261 new_query = ' '.join(query.split())
263 cr.execute(new_query)
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))
272 tools.convert_csv_import(cr, m, os.path.basename(xml), tools.file_open(opj(m, xml)).read(), idref, noupdate=True)
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,))
281 # Update translations for all installed languages
282 modobj = pool.get('ir.module.module')
284 modobj.update_translations(cr, 1, [mid], None)
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))
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'], {})
298 pool.get('ir.model.data')._process_end(cr, 1, package_todo)
301 def register_classes():
302 module_list = get_modules()
303 for package in create_graph(module_list):
305 logger.notifyChannel('init', netsvc.LOG_INFO, 'addon:%s:registering classes' % m)
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))
315 zimp = zipimport.zipimporter(mod_path+'.zip')
317 except zipimport.ZipImportError:
318 logger.notifyChannel('init', netsvc.LOG_ERROR, 'Couldn\'t find module %s' % m)
320 def load_modules(db, force_demo=False, status=None, update_module=False):
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))
331 cr.execute("select name from ir_module_module where state in ('installed', 'to install', 'to upgrade','to remove')")
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)
341 for kind in ('init', 'demo', 'update'):
342 tools.config[kind]={}
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():
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
357 pool.get(rmod).unlink(cr, uid, [rid])
360 # TODO: remove menu without actions of childs
362 cr.execute('''delete from
365 (id not in (select parent_id from ir_ui_menu where parent_id is not null))
367 (id not in (select res_id from ir_values where model='ir.ui.menu'))
369 (id not in (select res_id from ir_model_data where model='ir.ui.menu'))''')
371 cr.execute("update ir_module_module set state=%s where state in ('to remove')", ('uninstalled', ))
373 pooler.restart_pool(cr.dbname)
377 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: