[FIX] loading: pool.get("ir.module.module") was done before it was available.
[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, module_name, idref, mode):
92         _load_data(cr, module_name, idref, mode, 'init_xml')
93
94     def load_update_xml(cr, module_name, idref, mode):
95         _load_data(cr, module_name, idref, mode, 'update_xml')
96
97     def load_demo_xml(cr, module_name, idref, mode):
98         _load_data(cr, module_name, 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, instantiate and initialize models for each modules
161     for package in graph:
162         module_name = package.name
163         module_id = package.id
164
165         if skip_modules and module_name in skip_modules:
166             continue
167
168         logger.notifyChannel('init', netsvc.LOG_INFO, 'module %s: loading objects' % package.name)
169         migrations.migrate_module(package, 'pre')
170         register_module_classes(package.name)
171         models = pool.instanciate(package.name, cr)
172         if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
173             init_module_models(cr, package.name, models)
174
175         status['progress'] = float(statusi) / len(graph)
176
177         # Can't put this line out of the loop: ir.module.module will be
178         # registered by init_module_models() above.
179         modobj = pool.get('ir.module.module')
180
181         if perform_checks:
182             modobj.check(cr, 1, [module_id])
183
184         idref = {}
185
186         mode = 'update'
187         if hasattr(package, 'init') or package.state == 'to install':
188             mode = 'init'
189
190         if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
191             if package.state=='to upgrade':
192                 # upgrading the module information
193                 modobj.write(cr, 1, [module_id], modobj.get_values_from_terp(package.data))
194             load_init_xml(cr, module_name, idref, mode)
195             load_update_xml(cr, module_name, idref, mode)
196             load_data(cr, module_name, idref, mode)
197             if hasattr(package, 'demo') or (package.dbdemo and package.state != 'installed'):
198                 status['progress'] = (float(statusi)+0.75) / len(graph)
199                 load_demo_xml(cr, module_name, idref, mode)
200                 load_demo(cr, module_name, idref, mode)
201                 cr.execute('update ir_module_module set demo=%s where id=%s', (True, module_id))
202
203                 # launch tests only in demo mode, as most tests will depend
204                 # on demo data. Other tests can be added into the regular
205                 # 'data' section, but should probably not alter the data,
206                 # as there is no rollback.
207                 load_test(cr, module_name, idref, mode)
208
209             processed_modules.append(package.name)
210
211             migrations.migrate_module(package, 'post')
212
213             ver = release.major_version + '.' + package.data['version']
214             # Set new modules and dependencies
215             modobj.write(cr, 1, [module_id], {'state': 'installed', 'latest_version': ver})
216             # Update translations for all installed languages
217             modobj.update_translations(cr, 1, [module_id], None)
218
219             package.state = 'installed'
220             for kind in ('init', 'demo', 'update'):
221                 if hasattr(package, kind):
222                     delattr(package, kind)
223
224         cr.commit()
225         statusi += 1
226
227     cr.commit()
228
229     return processed_modules
230
231 def _check_module_names(cr, module_names):
232     mod_names = set(module_names)
233     if 'base' in mod_names:
234         # ignore dummy 'all' module
235         if 'all' in mod_names:
236             mod_names.remove('all')
237     if mod_names:
238         cr.execute("SELECT count(id) AS count FROM ir_module_module WHERE name in %s", (tuple(mod_names),))
239         if cr.dictfetchone()['count'] != len(mod_names):
240             # find out what module name(s) are incorrect:
241             cr.execute("SELECT name FROM ir_module_module")
242             incorrect_names = mod_names.difference([x['name'] for x in cr.dictfetchall()])
243             logging.getLogger('init').warning('invalid module names, ignored: %s', ", ".join(incorrect_names))
244
245 def load_modules(db, force_demo=False, status=None, update_module=False):
246     # TODO status['progress'] reporting is broken: used twice (and reset each
247     # time to zero) in load_module_graph, not fine-grained enough.
248     # It should be a method exposed by the pool.
249
250     initialize_sys_path()
251
252     open_openerp_namespace()
253
254     force = []
255     if force_demo:
256         force.append('demo')
257
258     cr = db.cursor()
259     try:
260         if not openerp.modules.db.is_initialized(cr):
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
268         # This is a brand new pool, just created in pooler.get_db_and_pool()
269         pool = pooler.get_pool(cr.dbname)
270
271         processed_modules = []
272         report = tools.assertion_report()
273         # NOTE: Try to also load the modules that have been marked as uninstallable previously...
274         STATES_TO_LOAD = ['installed', 'to upgrade', 'uninstallable']
275         if 'base' in tools.config['update'] or 'all' in tools.config['update']:
276             cr.execute("update ir_module_module set state=%s where name=%s and state=%s", ('to upgrade', 'base', 'installed'))
277
278         # STEP 1: LOAD BASE (must be done before module dependencies can be computed for later steps) 
279         graph = openerp.modules.graph.Graph()
280         graph.add_module(cr, 'base', force)
281         if not graph:
282             logger.notifyChannel('init', netsvc.LOG_CRITICAL, 'module base cannot be loaded! (hint: verify addons-path)')
283             raise osv.osv.except_osv(_('Could not load base module'), _('module base cannot be loaded! (hint: verify addons-path)'))
284         processed_modules.extend(load_module_graph(cr, graph, status, perform_checks=(not update_module), report=report))
285
286         if tools.config['load_language']:
287             for lang in tools.config['load_language'].split(','):
288                 tools.load_language(cr, lang)
289
290         # STEP 2: Mark other modules to be loaded/updated
291         if update_module:
292             modobj = pool.get('ir.module.module')
293             if ('base' in tools.config['init']) or ('base' in tools.config['update']):
294                 logger.notifyChannel('init', netsvc.LOG_INFO, 'updating modules list')
295                 modobj.update_list(cr, 1)
296
297             _check_module_names(cr, itertools.chain(tools.config['init'].keys(), tools.config['update'].keys()))
298
299             mods = [k for k in tools.config['init'] if tools.config['init'][k]]
300             if mods:
301                 ids = modobj.search(cr, 1, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
302                 if ids:
303                     modobj.button_install(cr, 1, ids)
304
305             mods = [k for k in tools.config['update'] if tools.config['update'][k]]
306             if mods:
307                 ids = modobj.search(cr, 1, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
308                 if ids:
309                     modobj.button_upgrade(cr, 1, ids)
310
311             cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
312
313             STATES_TO_LOAD += ['to install']
314
315
316         # STEP 3: Load marked modules (skipping base which was done in STEP 1)
317         loop_guardrail = 0
318         while True:
319             loop_guardrail += 1
320             if loop_guardrail > 100:
321                 raise ValueError('Possible recursive module tree detected, aborting.')
322             cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(STATES_TO_LOAD),))
323
324             module_list = [name for (name,) in cr.fetchall() if name not in graph]
325             if not module_list:
326                 break
327
328             new_modules_in_graph = graph.add_modules(cr, module_list, force)
329             if new_modules_in_graph == 0:
330                 # nothing to load
331                 break
332
333             logger.notifyChannel('init', netsvc.LOG_DEBUG, 'Updating graph with %d more modules' % (len(module_list)))
334             processed_modules.extend(load_module_graph(cr, graph, status, report=report, skip_modules=processed_modules))
335
336         # load custom models
337         cr.execute('select model from ir_model where state=%s', ('manual',))
338         for model in cr.dictfetchall():
339             pool.get('ir.model').instanciate(cr, 1, model['model'], {})
340
341         # STEP 4: Finish and cleanup
342         if processed_modules:
343             cr.execute("""select model,name from ir_model where id NOT IN (select distinct model_id from ir_model_access)""")
344             for (model, name) in cr.fetchall():
345                 model_obj = pool.get(model)
346                 if model_obj and not isinstance(model_obj, osv.osv.osv_memory):
347                     logger.notifyChannel('init', netsvc.LOG_WARNING, 'object %s (%s) has no access rules!' % (model, name))
348
349             # Temporary warning while we remove access rights on osv_memory objects, as they have
350             # been replaced by owner-only access rights
351             cr.execute("""select distinct mod.model, mod.name from ir_model_access acc, ir_model mod where acc.model_id = mod.id""")
352             for (model, name) in cr.fetchall():
353                 model_obj = pool.get(model)
354                 if isinstance(model_obj, osv.osv.osv_memory):
355                     logger.notifyChannel('init', netsvc.LOG_WARNING, 'In-memory object %s (%s) should not have explicit access rules!' % (model, name))
356
357             cr.execute("SELECT model from ir_model")
358             for (model,) in cr.fetchall():
359                 obj = pool.get(model)
360                 if obj:
361                     obj._check_removed_columns(cr, log=True)
362                 else:
363                     logger.notifyChannel('init', netsvc.LOG_WARNING, "Model %s is referenced but not present in the orm pool!" % model)
364
365             # Cleanup orphan records
366             pool.get('ir.model.data')._process_end(cr, 1, processed_modules)
367
368         if report.get_report():
369             logger.notifyChannel('init', netsvc.LOG_INFO, report)
370
371         for kind in ('init', 'demo', 'update'):
372             tools.config[kind] = {}
373
374         cr.commit()
375         if update_module:
376             # Remove records referenced from ir_model_data for modules to be
377             # removed (and removed the references from ir_model_data).
378             cr.execute("select id,name from ir_module_module where state=%s", ('to remove',))
379             for mod_id, mod_name in cr.fetchall():
380                 cr.execute('select model,res_id from ir_model_data where noupdate=%s and module=%s order by id desc', (False, mod_name,))
381                 for rmod, rid in cr.fetchall():
382                     uid = 1
383                     rmod_module= pool.get(rmod)
384                     if rmod_module:
385                         # TODO group by module so that we can delete multiple ids in a call
386                         rmod_module.unlink(cr, uid, [rid])
387                     else:
388                         logger.notifyChannel('init', netsvc.LOG_ERROR, 'Could not locate %s to remove res=%d' % (rmod,rid))
389                 cr.execute('delete from ir_model_data where noupdate=%s and module=%s', (False, mod_name,))
390                 cr.commit()
391
392             # Remove menu items that are not referenced by any of other
393             # (child) menu item, ir_values, or ir_model_data.
394             # This code could be a method of ir_ui_menu.
395             # TODO: remove menu without actions of children
396             while True:
397                 cr.execute('''delete from
398                         ir_ui_menu
399                     where
400                         (id not IN (select parent_id from ir_ui_menu where parent_id is not null))
401                     and
402                         (id not IN (select res_id from ir_values where model='ir.ui.menu'))
403                     and
404                         (id not IN (select res_id from ir_model_data where model='ir.ui.menu'))''')
405                 cr.commit()
406                 if not cr.rowcount:
407                     break
408                 else:
409                     logger.notifyChannel('init', netsvc.LOG_INFO, 'removed %d unused menus' % (cr.rowcount,))
410
411             # Pretend that modules to be removed are actually uninstalled.
412             cr.execute("update ir_module_module set state=%s where state=%s", ('uninstalled', 'to remove',))
413             cr.commit()
414     finally:
415         cr.close()
416
417
418 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: