[REF] renamed modules.__init__ to modules.loading.
[odoo/odoo.git] / openerp / modules / loading.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 (also called addons) management.
24
25 """
26
27 import os, sys, imp
28 from os.path import join as opj
29 import itertools
30 import zipimport
31
32 import openerp
33
34 import openerp.osv as osv
35 import openerp.tools as tools
36 import openerp.tools.osutil as osutil
37 from openerp.tools.safe_eval import safe_eval as eval
38 import openerp.pooler as pooler
39 from openerp.tools.translate import _
40
41 import openerp.netsvc as netsvc
42
43 import zipfile
44 import openerp.release as release
45
46 import re
47 import base64
48 from zipfile import PyZipFile, ZIP_DEFLATED
49 from cStringIO import StringIO
50
51 import logging
52
53 import openerp.modules.db
54 import openerp.modules.graph
55 import openerp.modules.migration
56
57 from openerp.modules.module import \
58     get_modules, get_modules_with_version, \
59     load_information_from_description_file, \
60     get_module_resource, zip_directory, \
61     get_module_path, initialize_sys_path, \
62     register_module_classes, init_module_models
63
64 logger = netsvc.Logger()
65
66
67 def open_openerp_namespace():
68     # See comment for open_openerp_namespace.
69     if openerp.conf.deprecation.open_openerp_namespace:
70         for k, v in list(sys.modules.items()):
71             if k.startswith('openerp.') and sys.modules.get(k[8:]) is None:
72                 sys.modules[k[8:]] = v
73
74
75 def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=None, report=None):
76     """Migrates+Updates or Installs all module nodes from ``graph``
77        :param graph: graph of module nodes to load
78        :param status: status dictionary for keeping track of progress
79        :param perform_checks: whether module descriptors should be checked for validity (prints warnings
80                               for same cases, and even raise osv_except if certificate is invalid)
81        :param skip_modules: optional list of module names (packages) which have previously been loaded and can be skipped
82        :return: list of modules that were installed or updated
83     """
84     def process_sql_file(cr, fp):
85         queries = fp.read().split(';')
86         for query in queries:
87             new_query = ' '.join(query.split())
88             if new_query:
89                 cr.execute(new_query)
90
91     def load_init_xml(cr, m, idref, mode):
92         _load_data(cr, m, idref, mode, 'init_xml')
93
94     def load_update_xml(cr, m, idref, mode):
95         _load_data(cr, m, idref, mode, 'update_xml')
96
97     def load_demo_xml(cr, m, idref, mode):
98         _load_data(cr, m, idref, mode, 'demo_xml')
99
100     def load_data(cr, module_name, idref, mode):
101         _load_data(cr, module_name, idref, mode, 'data')
102
103     def load_demo(cr, module_name, idref, mode):
104         _load_data(cr, module_name, idref, mode, 'demo')
105
106     def load_test(cr, module_name, idref, mode):
107         cr.commit()
108         if not tools.config.options['test_disable']:
109             try:
110                 _load_data(cr, module_name, idref, mode, 'test')
111             except Exception, e:
112                 logging.getLogger('test').exception('Tests failed to execute in module %s', module_name)
113             finally:
114                 if tools.config.options['test_commit']:
115                     cr.commit()
116                 else:
117                     cr.rollback()
118
119     def _load_data(cr, module_name, idref, mode, kind):
120         """
121
122         kind: data, demo, test, init_xml, update_xml, demo_xml.
123
124         noupdate is False, unless it is demo data or it is csv data in
125         init mode.
126
127         """
128         for filename in package.data[kind]:
129             log = logging.getLogger('init')
130             log.info("module %s: loading %s", module_name, filename)
131             _, ext = os.path.splitext(filename)
132             pathname = os.path.join(module_name, filename)
133             fp = tools.file_open(pathname)
134             noupdate = False
135             if kind in ('demo', 'demo_xml'):
136                 noupdate = True
137             try:
138                 if ext == '.csv':
139                     if kind in ('init', 'init_xml'):
140                         noupdate = True
141                     tools.convert_csv_import(cr, module_name, pathname, fp.read(), idref, mode, noupdate)
142                 elif ext == '.sql':
143                     process_sql_file(cr, fp)
144                 elif ext == '.yml':
145                     tools.convert_yaml_import(cr, module_name, fp, idref, mode, noupdate)
146                 else:
147                     tools.convert_xml_import(cr, module_name, fp, idref, mode, noupdate, report)
148             finally:
149                 fp.close()
150
151     if status is None:
152         status = {}
153
154     processed_modules = []
155     statusi = 0
156     pool = pooler.get_pool(cr.dbname)
157     migrations = openerp.modules.migration.MigrationManager(cr, graph)
158     logger.notifyChannel('init', netsvc.LOG_DEBUG, 'loading %d packages..' % len(graph))
159
160     # register, instanciate and initialize models for each modules
161     for package in graph:
162         if skip_modules and package.name in skip_modules:
163             continue
164         logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading objects' % package.name)
165         migrations.migrate_module(package, 'pre')
166         register_module_classes(package.name)
167         models = pool.instanciate(package.name, cr)
168         if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
169             init_module_models(cr, package.name, models)
170         cr.commit()
171
172     # load data for each modules
173     modobj = pool.get('ir.module.module')
174     for package in graph:
175         status['progress'] = (float(statusi)+0.1) / len(graph)
176         m = package.name
177         mid = package.id
178
179         if skip_modules and m in skip_modules:
180             continue
181
182         if perform_checks:
183             modobj.check(cr, 1, [mid])
184
185         idref = {}
186         status['progress'] = (float(statusi)+0.4) / len(graph)
187
188         mode = 'update'
189         if hasattr(package, 'init') or package.state == 'to install':
190             mode = 'init'
191
192         if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
193             if package.state=='to upgrade':
194                 # upgrading the module information
195                 modobj.write(cr, 1, [mid], modobj.get_values_from_terp(package.data))
196             load_init_xml(cr, m, idref, mode)
197             load_update_xml(cr, m, idref, mode)
198             load_data(cr, m, idref, mode)
199             if hasattr(package, 'demo') or (package.dbdemo and package.state != 'installed'):
200                 status['progress'] = (float(statusi)+0.75) / len(graph)
201                 load_demo_xml(cr, m, idref, mode)
202                 load_demo(cr, m, idref, mode)
203                 cr.execute('update ir_module_module set demo=%s where id=%s', (True, mid))
204
205                 # launch tests only in demo mode, as most tests will depend
206                 # on demo data. Other tests can be added into the regular
207                 # 'data' section, but should probably not alter the data,
208                 # as there is no rollback.
209                 load_test(cr, m, idref, mode)
210
211             processed_modules.append(package.name)
212
213             migrations.migrate_module(package, 'post')
214
215             ver = release.major_version + '.' + package.data['version']
216             # Set new modules and dependencies
217             modobj.write(cr, 1, [mid], {'state': 'installed', 'latest_version': ver})
218             cr.commit()
219             # Update translations for all installed languages
220             modobj.update_translations(cr, 1, [mid], None)
221             cr.commit()
222
223             package.state = 'installed'
224             for kind in ('init', 'demo', 'update'):
225                 if hasattr(package, kind):
226                     delattr(package, kind)
227
228         statusi += 1
229
230     cr.commit()
231
232     return processed_modules
233
234 def _check_module_names(cr, module_names):
235     mod_names = set(module_names)
236     if 'base' in mod_names:
237         # ignore dummy 'all' module
238         if 'all' in mod_names:
239             mod_names.remove('all')
240     if mod_names:
241         cr.execute("SELECT count(id) AS count FROM ir_module_module WHERE name in %s", (tuple(mod_names),))
242         if cr.dictfetchone()['count'] != len(mod_names):
243             # find out what module name(s) are incorrect:
244             cr.execute("SELECT name FROM ir_module_module")
245             incorrect_names = mod_names.difference([x['name'] for x in cr.dictfetchall()])
246             logging.getLogger('init').warning('invalid module names, ignored: %s', ", ".join(incorrect_names))
247
248 def load_modules(db, force_demo=False, status=None, update_module=False):
249     # TODO status['progress'] reporting is broken: used twice (and reset each
250     # time to zero) in load_module_graph, not fine-grained enough.
251     # It should be a method exposed by the pool.
252
253     initialize_sys_path()
254
255     open_openerp_namespace()
256
257     cr = db.cursor()
258     if cr:
259         cr.execute("SELECT relname FROM pg_class WHERE relkind='r' AND relname='ir_module_module'")
260         if len(cr.fetchall())==0:
261             logger.notifyChannel("init", netsvc.LOG_INFO, "init db")
262             openerp.modules.db.initialize(cr)
263             tools.config["init"]["all"] = 1
264             tools.config['update']['all'] = 1
265             if not tools.config['without_demo']:
266                 tools.config["demo"]['all'] = 1
267     force = []
268     if force_demo:
269         force.append('demo')
270
271     # This is a brand new pool, just created in pooler.get_db_and_pool()
272     pool = pooler.get_pool(cr.dbname)
273
274     try:
275         processed_modules = []
276         report = tools.assertion_report()
277         # NOTE: Try to also load the modules that have been marked as uninstallable previously...
278         STATES_TO_LOAD = ['installed', 'to upgrade', 'uninstallable']
279         if 'base' in tools.config['update'] or 'all' in tools.config['update']:
280             cr.execute("update ir_module_module set state=%s where name=%s and state=%s", ('to upgrade', 'base', 'installed'))
281
282         # STEP 1: LOAD BASE (must be done before module dependencies can be computed for later steps) 
283         graph = openerp.modules.graph.Graph()
284         graph.add_module(cr, 'base', force)
285         if not graph:
286             logger.notifyChannel('init', netsvc.LOG_CRITICAL, 'module base cannot be loaded! (hint: verify addons-path)')
287             raise osv.osv.except_osv(_('Could not load base module'), _('module base cannot be loaded! (hint: verify addons-path)'))
288         processed_modules.extend(load_module_graph(cr, graph, status, perform_checks=(not update_module), report=report))
289
290         if tools.config['load_language']:
291             for lang in tools.config['load_language'].split(','):
292                 tools.load_language(cr, lang)
293
294         # STEP 2: Mark other modules to be loaded/updated
295         if update_module:
296             modobj = pool.get('ir.module.module')
297             if ('base' in tools.config['init']) or ('base' in tools.config['update']):
298                 logger.notifyChannel('init', netsvc.LOG_INFO, 'updating modules list')
299                 modobj.update_list(cr, 1)
300
301             _check_module_names(cr, itertools.chain(tools.config['init'].keys(), tools.config['update'].keys()))
302
303             mods = [k for k in tools.config['init'] if tools.config['init'][k]]
304             if mods:
305                 ids = modobj.search(cr, 1, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
306                 if ids:
307                     modobj.button_install(cr, 1, ids)
308
309             mods = [k for k in tools.config['update'] if tools.config['update'][k]]
310             if mods:
311                 ids = modobj.search(cr, 1, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
312                 if ids:
313                     modobj.button_upgrade(cr, 1, ids)
314
315             cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
316
317             STATES_TO_LOAD += ['to install']
318
319
320         # STEP 3: Load marked modules (skipping base which was done in STEP 1)
321         loop_guardrail = 0
322         while True:
323             loop_guardrail += 1
324             if loop_guardrail > 100:
325                 raise ValueError('Possible recursive module tree detected, aborting.')
326             cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(STATES_TO_LOAD),))
327
328             module_list = [name for (name,) in cr.fetchall() if name not in graph]
329             if not module_list:
330                 break
331
332             new_modules_in_graph = graph.add_modules(cr, module_list, force)
333             if new_modules_in_graph == 0:
334                 # nothing to load
335                 break
336
337             logger.notifyChannel('init', netsvc.LOG_DEBUG, 'Updating graph with %d more modules' % (len(module_list)))
338             processed_modules.extend(load_module_graph(cr, graph, status, report=report, skip_modules=processed_modules))
339
340         # load custom models
341         cr.execute('select model from ir_model where state=%s', ('manual',))
342         for model in cr.dictfetchall():
343             pool.get('ir.model').instanciate(cr, 1, model['model'], {})
344
345         # STEP 4: Finish and cleanup
346         if processed_modules:
347             cr.execute("""select model,name from ir_model where id NOT IN (select distinct model_id from ir_model_access)""")
348             for (model, name) in cr.fetchall():
349                 model_obj = pool.get(model)
350                 if model_obj and not isinstance(model_obj, osv.osv.osv_memory):
351                     logger.notifyChannel('init', netsvc.LOG_WARNING, 'object %s (%s) has no access rules!' % (model, name))
352
353             # Temporary warning while we remove access rights on osv_memory objects, as they have
354             # been replaced by owner-only access rights
355             cr.execute("""select distinct mod.model, mod.name from ir_model_access acc, ir_model mod where acc.model_id = mod.id""")
356             for (model, name) in cr.fetchall():
357                 model_obj = pool.get(model)
358                 if isinstance(model_obj, osv.osv.osv_memory):
359                     logger.notifyChannel('init', netsvc.LOG_WARNING, 'In-memory object %s (%s) should not have explicit access rules!' % (model, name))
360
361             cr.execute("SELECT model from ir_model")
362             for (model,) in cr.fetchall():
363                 obj = pool.get(model)
364                 if obj:
365                     obj._check_removed_columns(cr, log=True)
366                 else:
367                     logger.notifyChannel('init', netsvc.LOG_WARNING, "Model %s is referenced but not present in the orm pool!" % model)
368
369             # Cleanup orphan records
370             pool.get('ir.model.data')._process_end(cr, 1, processed_modules)
371
372         if report.get_report():
373             logger.notifyChannel('init', netsvc.LOG_INFO, report)
374
375         for kind in ('init', 'demo', 'update'):
376             tools.config[kind] = {}
377
378         cr.commit()
379         if update_module:
380             # Remove records referenced from ir_model_data for modules to be
381             # removed (and removed the references from ir_model_data).
382             cr.execute("select id,name from ir_module_module where state=%s", ('to remove',))
383             for mod_id, mod_name in cr.fetchall():
384                 cr.execute('select model,res_id from ir_model_data where noupdate=%s and module=%s order by id desc', (False, mod_name,))
385                 for rmod, rid in cr.fetchall():
386                     uid = 1
387                     rmod_module= pool.get(rmod)
388                     if rmod_module:
389                         # TODO group by module so that we can delete multiple ids in a call
390                         rmod_module.unlink(cr, uid, [rid])
391                     else:
392                         logger.notifyChannel('init', netsvc.LOG_ERROR, 'Could not locate %s to remove res=%d' % (rmod,rid))
393                 cr.execute('delete from ir_model_data where noupdate=%s and module=%s', (False, mod_name,))
394                 cr.commit()
395
396             # Remove menu items that are not referenced by any of other
397             # (child) menu item, ir_values, or ir_model_data.
398             # This code could be a method of ir_ui_menu.
399             # TODO: remove menu without actions of children
400             while True:
401                 cr.execute('''delete from
402                         ir_ui_menu
403                     where
404                         (id not IN (select parent_id from ir_ui_menu where parent_id is not null))
405                     and
406                         (id not IN (select res_id from ir_values where model='ir.ui.menu'))
407                     and
408                         (id not IN (select res_id from ir_model_data where model='ir.ui.menu'))''')
409                 cr.commit()
410                 if not cr.rowcount:
411                     break
412                 else:
413                     logger.notifyChannel('init', netsvc.LOG_INFO, 'removed %d unused menus' % (cr.rowcount,))
414
415             # Pretend that modules to be removed are actually uninstalled.
416             cr.execute("update ir_module_module set state=%s where state=%s", ('uninstalled', 'to remove',))
417             cr.commit()
418     finally:
419         cr.close()
420
421
422 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: