1 # -*- coding: utf-8 -*-
2 ##############################################################################
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>).
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.
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.
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/>.
21 ##############################################################################
23 """ Modules (also called addons) management.
35 import openerp.modules.db
36 import openerp.modules.graph
37 import openerp.modules.migration
38 import openerp.modules.registry
39 import openerp.osv as osv
40 import openerp.tools as tools
41 from openerp import SUPERUSER_ID
43 from openerp.tools.translate import _
44 from openerp.modules.module import initialize_sys_path, \
45 load_openerp_module, init_module_models, adapt_version
46 from module import runs_post_install
48 _logger = logging.getLogger(__name__)
49 _test_logger = logging.getLogger('openerp.tests')
52 def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=None, report=None):
53 """Migrates+Updates or Installs all module nodes from ``graph``
54 :param graph: graph of module nodes to load
55 :param status: deprecated parameter, unused, left to avoid changing signature in 8.0
56 :param perform_checks: whether module descriptors should be checked for validity (prints warnings
58 :param skip_modules: optional list of module names (packages) which have previously been loaded and can be skipped
59 :return: list of modules that were installed or updated
61 def load_test(module_name, idref, mode):
64 _load_data(cr, module_name, idref, mode, 'test')
67 _test_logger.exception(
68 'module %s: an exception occurred in a test', module_name)
71 if tools.config.options['test_commit']:
75 # avoid keeping stale xml_id, etc. in cache
76 openerp.modules.registry.RegistryManager.clear_caches(cr.dbname)
79 def _get_files_of_kind(kind):
81 kind = ['demo_xml', 'demo']
83 kind = ['init_xml', 'update_xml', 'data']
84 if isinstance(kind, str):
88 for f in package.data[k]:
90 if k.endswith('_xml') and not (k == 'init_xml' and not f.endswith('.xml')):
91 # init_xml, update_xml and demo_xml are deprecated except
92 # for the case of init_xml with yaml, csv and sql files as
93 # we can't specify noupdate for those file.
94 correct_key = 'demo' if k.count('demo') else 'data'
96 "module %s: key '%s' is deprecated in favor of '%s' for file '%s'.",
97 package.name, k, correct_key, f
101 def _load_data(cr, module_name, idref, mode, kind):
104 kind: data, demo, test, init_xml, update_xml, demo_xml.
106 noupdate is False, unless it is demo data or it is csv data in
111 if kind in ('demo', 'test'):
112 threading.currentThread().testing = True
113 for filename in _get_files_of_kind(kind):
114 _logger.info("loading %s/%s", module_name, filename)
116 if kind in ('demo', 'demo_xml') or (filename.endswith('.csv') and kind in ('init', 'init_xml')):
118 tools.convert_file(cr, module_name, filename, idref, mode, noupdate, kind, report)
120 if kind in ('demo', 'test'):
121 threading.currentThread().testing = False
123 processed_modules = []
125 registry = openerp.registry(cr.dbname)
126 migrations = openerp.modules.migration.MigrationManager(cr, graph)
127 _logger.info('loading %d modules...', len(graph))
129 # Query manual fields for all models at once and save them on the registry
130 # so the initialization code for each model does not have to do it
131 # one model at a time.
132 registry.fields_by_model = {}
133 cr.execute('SELECT * FROM ir_model_fields WHERE state=%s', ('manual',))
134 for field in cr.dictfetchall():
135 registry.fields_by_model.setdefault(field['model'], []).append(field)
137 # register, instantiate and initialize models for each modules
139 t0_sql = openerp.sql_db.sql_counter
141 for index, package in enumerate(graph):
142 module_name = package.name
143 module_id = package.id
145 if skip_modules and module_name in skip_modules:
148 migrations.migrate_module(package, 'pre')
149 load_openerp_module(package.name)
151 new_install = package.installed_version is None
153 py_module = sys.modules['openerp.addons.%s' % (module_name,)]
154 pre_init = package.info.get('pre_init_hook')
156 getattr(py_module, pre_init)(cr)
158 models = registry.load(cr, package)
160 loaded_modules.append(package.name)
161 if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
162 registry.setup_models(cr, partial=True)
163 init_module_models(cr, package.name, models)
165 # Can't put this line out of the loop: ir.module.module will be
166 # registered by init_module_models() above.
167 modobj = registry['ir.module.module']
170 modobj.check(cr, SUPERUSER_ID, [module_id])
175 if hasattr(package, 'init') or package.state == 'to install':
178 if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
179 if package.state=='to upgrade':
180 # upgrading the module information
181 modobj.write(cr, SUPERUSER_ID, [module_id], modobj.get_values_from_terp(package.data))
182 _load_data(cr, module_name, idref, mode, kind='data')
183 has_demo = hasattr(package, 'demo') or (package.dbdemo and package.state != 'installed')
185 _load_data(cr, module_name, idref, mode, kind='demo')
186 cr.execute('update ir_module_module set demo=%s where id=%s', (True, module_id))
187 modobj.invalidate_cache(cr, SUPERUSER_ID, ['demo'], [module_id])
189 migrations.migrate_module(package, 'post')
192 post_init = package.info.get('post_init_hook')
194 getattr(py_module, post_init)(cr, registry)
196 registry._init_modules.add(package.name)
197 # validate all the views at a whole
198 registry['ir.ui.view']._validate_module_views(cr, SUPERUSER_ID, module_name)
201 # launch tests only in demo mode, allowing tests to use demo data.
202 if tools.config.options['test_enable']:
204 report.record_result(load_test(module_name, idref, mode))
206 ir_http = registry['ir.http']
207 if hasattr(ir_http, '_routing_map'):
208 # Force routing map to be rebuilt between each module test suite
209 del(ir_http._routing_map)
210 report.record_result(openerp.modules.module.run_unit_tests(module_name, cr.dbname))
212 processed_modules.append(package.name)
214 ver = adapt_version(package.data['version'])
215 # Set new modules and dependencies
216 modobj.write(cr, SUPERUSER_ID, [module_id], {'state': 'installed', 'latest_version': ver})
217 # Update translations for all installed languages
218 modobj.update_translations(cr, SUPERUSER_ID, [module_id], None, {'overwrite': openerp.tools.config["overwrite_existing_translations"]})
220 package.state = 'installed'
221 for kind in ('init', 'demo', 'update'):
222 if hasattr(package, kind):
223 delattr(package, kind)
225 registry._init_modules.add(package.name)
228 registry.setup_models(cr, partial=True)
230 _logger.log(25, "%s modules loaded in %.2fs, %s queries", len(graph), time.time() - t0, openerp.sql_db.sql_counter - t0_sql)
232 # The query won't be valid for models created later (i.e. custom model
233 # created after the registry has been loaded), so empty its result.
234 registry.fields_by_model = None
238 return loaded_modules, processed_modules
240 def _check_module_names(cr, module_names):
241 mod_names = set(module_names)
242 if 'base' in mod_names:
243 # ignore dummy 'all' module
244 if 'all' in mod_names:
245 mod_names.remove('all')
247 cr.execute("SELECT count(id) AS count FROM ir_module_module WHERE name in %s", (tuple(mod_names),))
248 if cr.dictfetchone()['count'] != len(mod_names):
249 # find out what module name(s) are incorrect:
250 cr.execute("SELECT name FROM ir_module_module")
251 incorrect_names = mod_names.difference([x['name'] for x in cr.dictfetchall()])
252 _logger.warning('invalid module names, ignored: %s', ", ".join(incorrect_names))
254 def load_marked_modules(cr, graph, states, force, progressdict, report, loaded_modules, perform_checks):
255 """Loads modules marked with ``states``, adding them to ``graph`` and
256 ``loaded_modules`` and returns a list of installed/upgraded modules."""
257 processed_modules = []
259 cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(states),))
260 module_list = [name for (name,) in cr.fetchall() if name not in graph]
261 graph.add_modules(cr, module_list, force)
262 _logger.debug('Updating graph with %d more modules', len(module_list))
263 loaded, processed = load_module_graph(cr, graph, progressdict, report=report, skip_modules=loaded_modules, perform_checks=perform_checks)
264 processed_modules.extend(processed)
265 loaded_modules.extend(loaded)
266 if not processed: break
267 return processed_modules
269 def load_modules(db, force_demo=False, status=None, update_module=False):
270 initialize_sys_path()
278 if not openerp.modules.db.is_initialized(cr):
279 _logger.info("init db")
280 openerp.modules.db.initialize(cr)
281 update_module = True # process auto-installed modules
282 tools.config["init"]["all"] = 1
283 tools.config['update']['all'] = 1
284 if not tools.config['without_demo']:
285 tools.config["demo"]['all'] = 1
287 # This is a brand new registry, just created in
288 # openerp.modules.registry.RegistryManager.new().
289 registry = openerp.registry(cr.dbname)
291 if 'base' in tools.config['update'] or 'all' in tools.config['update']:
292 cr.execute("update ir_module_module set state=%s where name=%s and state=%s", ('to upgrade', 'base', 'installed'))
294 # STEP 1: LOAD BASE (must be done before module dependencies can be computed for later steps)
295 graph = openerp.modules.graph.Graph()
296 graph.add_module(cr, 'base', force)
298 _logger.critical('module base cannot be loaded! (hint: verify addons-path)')
299 raise osv.osv.except_osv(_('Could not load base module'), _('module base cannot be loaded! (hint: verify addons-path)'))
301 # processed_modules: for cleanup step after install
302 # loaded_modules: to avoid double loading
303 report = registry._assertion_report
304 loaded_modules, processed_modules = load_module_graph(cr, graph, status, perform_checks=update_module, report=report)
306 if tools.config['load_language']:
307 for lang in tools.config['load_language'].split(','):
308 tools.load_language(cr, lang)
310 # STEP 2: Mark other modules to be loaded/updated
312 modobj = registry['ir.module.module']
313 if ('base' in tools.config['init']) or ('base' in tools.config['update']):
314 _logger.info('updating modules list')
315 modobj.update_list(cr, SUPERUSER_ID)
317 _check_module_names(cr, itertools.chain(tools.config['init'].keys(), tools.config['update'].keys()))
319 mods = [k for k in tools.config['init'] if tools.config['init'][k]]
321 ids = modobj.search(cr, SUPERUSER_ID, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
323 modobj.button_install(cr, SUPERUSER_ID, ids)
325 mods = [k for k in tools.config['update'] if tools.config['update'][k]]
327 ids = modobj.search(cr, SUPERUSER_ID, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
329 modobj.button_upgrade(cr, SUPERUSER_ID, ids)
331 cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
332 modobj.invalidate_cache(cr, SUPERUSER_ID, ['state'])
335 # STEP 3: Load marked modules (skipping base which was done in STEP 1)
336 # IMPORTANT: this is done in two parts, first loading all installed or
337 # partially installed modules (i.e. installed/to upgrade), to
338 # offer a consistent system to the second part: installing
339 # newly selected modules.
340 # We include the modules 'to remove' in the first step, because
341 # they are part of the "currently installed" modules. They will
342 # be dropped in STEP 6 later, before restarting the loading
344 # IMPORTANT 2: We have to loop here until all relevant modules have been
345 # processed, because in some rare cases the dependencies have
346 # changed, and modules that depend on an uninstalled module
347 # will not be processed on the first pass.
348 # It's especially useful for migrations.
349 previously_processed = -1
350 while previously_processed < len(processed_modules):
351 previously_processed = len(processed_modules)
352 processed_modules += load_marked_modules(cr, graph,
353 ['installed', 'to upgrade', 'to remove'],
354 force, status, report, loaded_modules, update_module)
356 processed_modules += load_marked_modules(cr, graph,
357 ['to install'], force, status, report,
358 loaded_modules, update_module)
361 cr.execute('select model from ir_model where state=%s', ('manual',))
362 for model in cr.dictfetchall():
363 registry['ir.model'].instanciate(cr, SUPERUSER_ID, model['model'], {})
364 registry.setup_models(cr)
366 # STEP 4: Finish and cleanup installations
367 if processed_modules:
368 cr.execute("""select model,name from ir_model where id NOT IN (select distinct model_id from ir_model_access)""")
369 for (model, name) in cr.fetchall():
370 if model in registry and not registry[model].is_transient() and not isinstance(registry[model], openerp.osv.orm.AbstractModel):
371 _logger.warning('The model %s has no access rules, consider adding one. E.g. access_%s,access_%s,model_%s,,1,1,1,1',
372 model, model.replace('.', '_'), model.replace('.', '_'), model.replace('.', '_'))
374 # Temporary warning while we remove access rights on osv_memory objects, as they have
375 # been replaced by owner-only access rights
376 cr.execute("""select distinct mod.model, mod.name from ir_model_access acc, ir_model mod where acc.model_id = mod.id""")
377 for (model, name) in cr.fetchall():
378 if model in registry and registry[model].is_transient():
379 _logger.warning('The transient model %s (%s) should not have explicit access rules!', model, name)
381 cr.execute("SELECT model from ir_model")
382 for (model,) in cr.fetchall():
383 if model in registry:
384 registry[model]._check_removed_columns(cr, log=True)
386 _logger.warning("Model %s is declared but cannot be loaded! (Perhaps a module was partially removed or renamed)", model)
388 # Cleanup orphan records
389 registry['ir.model.data']._process_end(cr, SUPERUSER_ID, processed_modules)
391 for kind in ('init', 'demo', 'update'):
392 tools.config[kind] = {}
396 # STEP 5: Cleanup menus
397 # Remove menu items that are not referenced by any of other
398 # (child) menu item, ir_values, or ir_model_data.
399 # TODO: This code could be a method of ir_ui_menu. Remove menu without actions of children
402 cr.execute('''delete from
405 (id not IN (select parent_id from ir_ui_menu where parent_id is not null))
407 (id not IN (select res_id from ir_values where model='ir.ui.menu'))
409 (id not IN (select res_id from ir_model_data where model='ir.ui.menu'))''')
414 _logger.info('removed %d unused menus', cr.rowcount)
416 # STEP 6: Uninstall modules to remove
418 # Remove records referenced from ir_model_data for modules to be
419 # removed (and removed the references from ir_model_data).
420 cr.execute("SELECT name, id FROM ir_module_module WHERE state=%s", ('to remove',))
421 modules_to_remove = dict(cr.fetchall())
422 if modules_to_remove:
423 pkgs = reversed([p for p in graph if p.name in modules_to_remove])
425 uninstall_hook = pkg.info.get('uninstall_hook')
427 py_module = sys.modules['openerp.addons.%s' % (pkg.name,)]
428 getattr(py_module, uninstall_hook)(cr, registry)
430 registry['ir.module.module'].module_uninstall(cr, SUPERUSER_ID, modules_to_remove.values())
431 # Recursive reload, should only happen once, because there should be no
432 # modules to remove next time
434 _logger.info('Reloading registry once more after uninstalling modules')
435 return openerp.modules.registry.RegistryManager.new(cr.dbname, force_demo, status, update_module)
437 # STEP 7: verify custom views on every model
439 Views = registry['ir.ui.view']
440 custom_view_test = True
441 for model in registry.models.keys():
442 if not Views._validate_custom_views(cr, SUPERUSER_ID, model):
443 custom_view_test = False
444 _logger.error('invalid custom view(s) for model %s', model)
445 report.record_result(custom_view_test)
448 _logger.error('At least one test failed when loading the modules.')
450 _logger.info('Modules loaded.')
452 # STEP 8: call _register_hook on every model
453 for model in registry.models.values():
454 model._register_hook(cr)
456 # STEP 9: Run the post-install tests
460 t0_sql = openerp.sql_db.sql_counter
461 if openerp.tools.config['test_enable']:
462 cr.execute("SELECT name FROM ir_module_module WHERE state='installed'")
463 for module_name in cr.fetchall():
464 report.record_result(openerp.modules.module.run_unit_tests(module_name[0], cr.dbname, position=runs_post_install))
465 _logger.log(25, "All post-tested in %.2fs, %s queries", time.time() - t0, openerp.sql_db.sql_counter - t0_sql)
469 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: