[MERGE] Forward-port saas-3 up to 09ab2d4
[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-2014 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 itertools
28 import logging
29 import os
30 import sys
31 import threading
32
33 import openerp
34 import openerp.modules.db
35 import openerp.modules.graph
36 import openerp.modules.migration
37 import openerp.modules.registry
38 import openerp.osv as osv
39 import openerp.tools as tools
40 from openerp import SUPERUSER_ID
41
42 from openerp.tools.translate import _
43 from openerp.modules.module import initialize_sys_path, \
44     load_openerp_module, init_module_models, adapt_version
45 from module import runs_post_install
46
47 _logger = logging.getLogger(__name__)
48 _test_logger = logging.getLogger('openerp.tests')
49
50
51 def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=None, report=None):
52     """Migrates+Updates or Installs all module nodes from ``graph``
53        :param graph: graph of module nodes to load
54        :param status: status dictionary for keeping track of progress
55        :param perform_checks: whether module descriptors should be checked for validity (prints warnings
56                               for same cases)
57        :param skip_modules: optional list of module names (packages) which have previously been loaded and can be skipped
58        :return: list of modules that were installed or updated
59     """
60     def load_test(module_name, idref, mode):
61         cr.commit()
62         try:
63             _load_data(cr, module_name, idref, mode, 'test')
64             return True
65         except Exception:
66             _test_logger.exception(
67                 'module %s: an exception occurred in a test', module_name)
68             return False
69         finally:
70             if tools.config.options['test_commit']:
71                 cr.commit()
72             else:
73                 cr.rollback()
74                 # avoid keeping stale xml_id, etc. in cache 
75                 openerp.modules.registry.RegistryManager.clear_caches(cr.dbname)
76
77
78     def _get_files_of_kind(kind):
79         if kind == 'demo':
80             kind = ['demo_xml', 'demo']
81         elif kind == 'data':
82             kind = ['init_xml', 'update_xml', 'data']
83         if isinstance(kind, str):
84             kind = [kind]
85         files = []
86         for k in kind:
87             for f in package.data[k]:
88                 files.append(f)
89                 if k.endswith('_xml') and not (k == 'init_xml' and not f.endswith('.xml')):
90                     # init_xml, update_xml and demo_xml are deprecated except
91                     # for the case of init_xml with yaml, csv and sql files as
92                     # we can't specify noupdate for those file.
93                     correct_key = 'demo' if k.count('demo') else 'data'
94                     _logger.warning(
95                         "module %s: key '%s' is deprecated in favor of '%s' for file '%s'.",
96                         package.name, k, correct_key, f
97                     )
98         return files
99
100     def _load_data(cr, module_name, idref, mode, kind):
101         """
102
103         kind: data, demo, test, init_xml, update_xml, demo_xml.
104
105         noupdate is False, unless it is demo data or it is csv data in
106         init mode.
107
108         """
109         try:
110             if kind in ('demo', 'test'):
111                 threading.currentThread().testing = True
112             for filename in _get_files_of_kind(kind):
113                 _logger.info("module %s: loading %s", module_name, filename)
114                 noupdate = False
115                 if kind in ('demo', 'demo_xml') or (filename.endswith('.csv') and kind in ('init', 'init_xml')):
116                     noupdate = True
117                 tools.convert_file(cr, module_name, filename, idref, mode, noupdate, kind, report)
118         finally:
119             if kind in ('demo', 'test'):
120                 threading.currentThread().testing = False
121
122     if status is None:
123         status = {}
124
125     processed_modules = []
126     loaded_modules = []
127     registry = openerp.registry(cr.dbname)
128     migrations = openerp.modules.migration.MigrationManager(cr, graph)
129     _logger.info('loading %d modules...', len(graph))
130
131     # Query manual fields for all models at once and save them on the registry
132     # so the initialization code for each model does not have to do it
133     # one model at a time.
134     registry.fields_by_model = {}
135     cr.execute('SELECT * FROM ir_model_fields WHERE state=%s', ('manual',))
136     for field in cr.dictfetchall():
137         registry.fields_by_model.setdefault(field['model'], []).append(field)
138
139     # register, instantiate and initialize models for each modules
140     for index, package in enumerate(graph):
141         module_name = package.name
142         module_id = package.id
143
144         if skip_modules and module_name in skip_modules:
145             continue
146
147         _logger.debug('module %s: loading objects', package.name)
148         migrations.migrate_module(package, 'pre')
149         load_openerp_module(package.name)
150
151         models = registry.load(cr, package)
152
153         loaded_modules.append(package.name)
154         if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
155             init_module_models(cr, package.name, models)
156         status['progress'] = float(index) / len(graph)
157
158         # Can't put this line out of the loop: ir.module.module will be
159         # registered by init_module_models() above.
160         modobj = registry['ir.module.module']
161
162         if perform_checks:
163             modobj.check(cr, SUPERUSER_ID, [module_id])
164
165         idref = {}
166
167         mode = 'update'
168         if hasattr(package, 'init') or package.state == 'to install':
169             mode = 'init'
170
171         if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
172             if package.state=='to upgrade':
173                 # upgrading the module information
174                 modobj.write(cr, SUPERUSER_ID, [module_id], modobj.get_values_from_terp(package.data))
175             _load_data(cr, module_name, idref, mode, kind='data')
176             has_demo = hasattr(package, 'demo') or (package.dbdemo and package.state != 'installed')
177             if has_demo:
178                 status['progress'] = (index + 0.75) / len(graph)
179                 _load_data(cr, module_name, idref, mode, kind='demo')
180                 cr.execute('update ir_module_module set demo=%s where id=%s', (True, module_id))
181
182             migrations.migrate_module(package, 'post')
183
184             registry._init_modules.add(package.name)
185             # validate all the views at a whole
186             registry['ir.ui.view']._validate_module_views(cr, SUPERUSER_ID, module_name)
187
188             if has_demo:
189                 # launch tests only in demo mode, allowing tests to use demo data.
190                 if tools.config.options['test_enable']:
191                     # Yamel test
192                     report.record_result(load_test(module_name, idref, mode))
193                     # Python tests
194                     ir_http = registry['ir.http']
195                     if hasattr(ir_http, '_routing_map'):
196                         # Force routing map to be rebuilt between each module test suite
197                         del(ir_http._routing_map)
198                     report.record_result(openerp.modules.module.run_unit_tests(module_name, cr.dbname))
199
200             processed_modules.append(package.name)
201
202             ver = adapt_version(package.data['version'])
203             # Set new modules and dependencies
204             modobj.write(cr, SUPERUSER_ID, [module_id], {'state': 'installed', 'latest_version': ver})
205             # Update translations for all installed languages
206             modobj.update_translations(cr, SUPERUSER_ID, [module_id], None, {'overwrite': openerp.tools.config["overwrite_existing_translations"]})
207
208             package.state = 'installed'
209             for kind in ('init', 'demo', 'update'):
210                 if hasattr(package, kind):
211                     delattr(package, kind)
212
213         registry._init_modules.add(package.name)
214         cr.commit()
215
216     # The query won't be valid for models created later (i.e. custom model
217     # created after the registry has been loaded), so empty its result.
218     registry.fields_by_model = None
219     
220     cr.commit()
221
222     return loaded_modules, processed_modules
223
224 def _check_module_names(cr, module_names):
225     mod_names = set(module_names)
226     if 'base' in mod_names:
227         # ignore dummy 'all' module
228         if 'all' in mod_names:
229             mod_names.remove('all')
230     if mod_names:
231         cr.execute("SELECT count(id) AS count FROM ir_module_module WHERE name in %s", (tuple(mod_names),))
232         if cr.dictfetchone()['count'] != len(mod_names):
233             # find out what module name(s) are incorrect:
234             cr.execute("SELECT name FROM ir_module_module")
235             incorrect_names = mod_names.difference([x['name'] for x in cr.dictfetchall()])
236             _logger.warning('invalid module names, ignored: %s', ", ".join(incorrect_names))
237
238 def load_marked_modules(cr, graph, states, force, progressdict, report, loaded_modules, perform_checks):
239     """Loads modules marked with ``states``, adding them to ``graph`` and
240        ``loaded_modules`` and returns a list of installed/upgraded modules."""
241     processed_modules = []
242     while True:
243         cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(states),))
244         module_list = [name for (name,) in cr.fetchall() if name not in graph]
245         graph.add_modules(cr, module_list, force)
246         _logger.debug('Updating graph with %d more modules', len(module_list))
247         loaded, processed = load_module_graph(cr, graph, progressdict, report=report, skip_modules=loaded_modules, perform_checks=perform_checks)
248         processed_modules.extend(processed)
249         loaded_modules.extend(loaded)
250         if not processed: break
251     return processed_modules
252
253 def load_modules(db, force_demo=False, status=None, update_module=False):
254     # TODO status['progress'] reporting is broken: used twice (and reset each
255     # time to zero) in load_module_graph, not fine-grained enough.
256     # It should be a method exposed by the registry.
257     initialize_sys_path()
258
259     force = []
260     if force_demo:
261         force.append('demo')
262
263     cr = db.cursor()
264     try:
265         if not openerp.modules.db.is_initialized(cr):
266             _logger.info("init db")
267             openerp.modules.db.initialize(cr)
268             update_module = True # process auto-installed modules
269             tools.config["init"]["all"] = 1
270             tools.config['update']['all'] = 1
271             if not tools.config['without_demo']:
272                 tools.config["demo"]['all'] = 1
273
274         # This is a brand new registry, just created in
275         # openerp.modules.registry.RegistryManager.new().
276         registry = openerp.registry(cr.dbname)
277
278         if 'base' in tools.config['update'] or 'all' in tools.config['update']:
279             cr.execute("update ir_module_module set state=%s where name=%s and state=%s", ('to upgrade', 'base', 'installed'))
280
281         # STEP 1: LOAD BASE (must be done before module dependencies can be computed for later steps) 
282         graph = openerp.modules.graph.Graph()
283         graph.add_module(cr, 'base', force)
284         if not graph:
285             _logger.critical('module base cannot be loaded! (hint: verify addons-path)')
286             raise osv.osv.except_osv(_('Could not load base module'), _('module base cannot be loaded! (hint: verify addons-path)'))
287
288         # processed_modules: for cleanup step after install
289         # loaded_modules: to avoid double loading
290         report = registry._assertion_report
291         loaded_modules, processed_modules = load_module_graph(cr, graph, status, perform_checks=update_module, report=report)
292
293         if tools.config['load_language']:
294             for lang in tools.config['load_language'].split(','):
295                 tools.load_language(cr, lang)
296
297         # STEP 2: Mark other modules to be loaded/updated
298         if update_module:
299             modobj = registry['ir.module.module']
300             if ('base' in tools.config['init']) or ('base' in tools.config['update']):
301                 _logger.info('updating modules list')
302                 modobj.update_list(cr, SUPERUSER_ID)
303
304             _check_module_names(cr, itertools.chain(tools.config['init'].keys(), tools.config['update'].keys()))
305
306             mods = [k for k in tools.config['init'] if tools.config['init'][k]]
307             if mods:
308                 ids = modobj.search(cr, SUPERUSER_ID, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
309                 if ids:
310                     modobj.button_install(cr, SUPERUSER_ID, ids)
311
312             mods = [k for k in tools.config['update'] if tools.config['update'][k]]
313             if mods:
314                 ids = modobj.search(cr, SUPERUSER_ID, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
315                 if ids:
316                     modobj.button_upgrade(cr, SUPERUSER_ID, ids)
317
318             cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
319
320
321         # STEP 3: Load marked modules (skipping base which was done in STEP 1)
322         # IMPORTANT: this is done in two parts, first loading all installed or
323         #            partially installed modules (i.e. installed/to upgrade), to
324         #            offer a consistent system to the second part: installing
325         #            newly selected modules.
326         #            We include the modules 'to remove' in the first step, because
327         #            they are part of the "currently installed" modules. They will
328         #            be dropped in STEP 6 later, before restarting the loading
329         #            process.
330         # IMPORTANT 2: We have to loop here until all relevant modules have been
331         #              processed, because in some rare cases the dependencies have
332         #              changed, and modules that depend on an uninstalled module
333         #              will not be processed on the first pass.
334         #              It's especially useful for migrations.
335         previously_processed = -1
336         while previously_processed < len(processed_modules):
337             previously_processed = len(processed_modules)
338             processed_modules += load_marked_modules(cr, graph,
339                 ['installed', 'to upgrade', 'to remove'],
340                 force, status, report, loaded_modules, update_module)
341             if update_module:
342                 processed_modules += load_marked_modules(cr, graph,
343                     ['to install'], force, status, report,
344                     loaded_modules, update_module)
345
346         # load custom models
347         cr.execute('select model from ir_model where state=%s', ('manual',))
348         for model in cr.dictfetchall():
349             registry['ir.model'].instanciate(cr, SUPERUSER_ID, model['model'], {})
350
351         # STEP 4: Finish and cleanup installations
352         if processed_modules:
353             cr.execute("""select model,name from ir_model where id NOT IN (select distinct model_id from ir_model_access)""")
354             for (model, name) in cr.fetchall():
355                 if model in registry and not registry[model].is_transient() and not isinstance(registry[model], openerp.osv.orm.AbstractModel):
356                     _logger.warning('The model %s has no access rules, consider adding one. E.g. access_%s,access_%s,model_%s,,1,1,1,1',
357                         model, model.replace('.', '_'), model.replace('.', '_'), model.replace('.', '_'))
358
359             # Temporary warning while we remove access rights on osv_memory objects, as they have
360             # been replaced by owner-only access rights
361             cr.execute("""select distinct mod.model, mod.name from ir_model_access acc, ir_model mod where acc.model_id = mod.id""")
362             for (model, name) in cr.fetchall():
363                 if model in registry and registry[model].is_transient():
364                     _logger.warning('The transient model %s (%s) should not have explicit access rules!', model, name)
365
366             cr.execute("SELECT model from ir_model")
367             for (model,) in cr.fetchall():
368                 if model in registry:
369                     registry[model]._check_removed_columns(cr, log=True)
370                 else:
371                     _logger.warning("Model %s is declared but cannot be loaded! (Perhaps a module was partially removed or renamed)", model)
372
373             # Cleanup orphan records
374             registry['ir.model.data']._process_end(cr, SUPERUSER_ID, processed_modules)
375
376         for kind in ('init', 'demo', 'update'):
377             tools.config[kind] = {}
378
379         cr.commit()
380
381         # STEP 5: Cleanup menus 
382         # Remove menu items that are not referenced by any of other
383         # (child) menu item, ir_values, or ir_model_data.
384         # TODO: This code could be a method of ir_ui_menu. Remove menu without actions of children
385         if update_module:
386             while True:
387                 cr.execute('''delete from
388                         ir_ui_menu
389                     where
390                         (id not IN (select parent_id from ir_ui_menu where parent_id is not null))
391                     and
392                         (id not IN (select res_id from ir_values where model='ir.ui.menu'))
393                     and
394                         (id not IN (select res_id from ir_model_data where model='ir.ui.menu'))''')
395                 cr.commit()
396                 if not cr.rowcount:
397                     break
398                 else:
399                     _logger.info('removed %d unused menus', cr.rowcount)
400
401         # STEP 6: Uninstall modules to remove
402         if update_module:
403             # Remove records referenced from ir_model_data for modules to be
404             # removed (and removed the references from ir_model_data).
405             cr.execute("SELECT id FROM ir_module_module WHERE state=%s", ('to remove',))
406             mod_ids_to_remove = [x[0] for x in cr.fetchall()]
407             if mod_ids_to_remove:
408                 registry['ir.module.module'].module_uninstall(cr, SUPERUSER_ID, mod_ids_to_remove)
409                 # Recursive reload, should only happen once, because there should be no
410                 # modules to remove next time
411                 cr.commit()
412                 _logger.info('Reloading registry once more after uninstalling modules')
413                 return openerp.modules.registry.RegistryManager.new(cr.dbname, force_demo, status, update_module)
414
415         # STEP 7: verify custom views on every model
416         if update_module:
417             Views = registry['ir.ui.view']
418             custom_view_test = True
419             for model in registry.models.keys():
420                 if not Views._validate_custom_views(cr, SUPERUSER_ID, model):
421                     custom_view_test = False
422                     _logger.error('invalid custom view(s) for model %s', model)
423             report.record_result(custom_view_test)
424
425         if report.failures:
426             _logger.error('At least one test failed when loading the modules.')
427         else:
428             _logger.info('Modules loaded.')
429
430         # STEP 8: call _register_hook on every model
431         for model in registry.models.values():
432             model._register_hook(cr)
433
434         # STEP 9: Run the post-install tests
435         cr.commit()
436         if openerp.tools.config['test_enable']:
437             cr.execute("SELECT name FROM ir_module_module WHERE state='installed'")
438             for module_name in cr.fetchall():
439                 report.record_result(openerp.modules.module.run_unit_tests(module_name[0], cr.dbname, position=runs_post_install))
440     finally:
441         cr.close()
442
443 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: