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.
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
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
47 _logger = logging.getLogger(__name__)
48 _test_logger = logging.getLogger('openerp.tests')
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
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
60 def load_test(module_name, idref, mode):
63 _load_data(cr, module_name, idref, mode, 'test')
66 _test_logger.exception(
67 'module %s: an exception occurred in a test', module_name)
70 if tools.config.options['test_commit']:
74 # avoid keeping stale xml_id, etc. in cache
75 openerp.modules.registry.RegistryManager.clear_caches(cr.dbname)
78 def _get_files_of_kind(kind):
80 kind = ['demo_xml', 'demo']
82 kind = ['init_xml', 'update_xml', 'data']
83 if isinstance(kind, str):
87 for f in package.data[k]:
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'
95 "module %s: key '%s' is deprecated in favor of '%s' for file '%s'.",
96 package.name, k, correct_key, f
100 def _load_data(cr, module_name, idref, mode, kind):
103 kind: data, demo, test, init_xml, update_xml, demo_xml.
105 noupdate is False, unless it is demo data or it is csv data in
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)
115 if kind in ('demo', 'demo_xml') or (filename.endswith('.csv') and kind in ('init', 'init_xml')):
117 tools.convert_file(cr, module_name, filename, idref, mode, noupdate, kind, report)
119 if kind in ('demo', 'test'):
120 threading.currentThread().testing = False
125 processed_modules = []
127 registry = openerp.registry(cr.dbname)
128 migrations = openerp.modules.migration.MigrationManager(cr, graph)
129 _logger.info('loading %d modules...', len(graph))
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)
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
144 if skip_modules and module_name in skip_modules:
147 _logger.debug('module %s: loading objects', package.name)
148 migrations.migrate_module(package, 'pre')
149 load_openerp_module(package.name)
151 models = registry.load(cr, package)
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)
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']
163 modobj.check(cr, SUPERUSER_ID, [module_id])
168 if hasattr(package, 'init') or package.state == 'to install':
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')
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))
182 migrations.migrate_module(package, 'post')
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)
189 # launch tests only in demo mode, allowing tests to use demo data.
190 if tools.config.options['test_enable']:
192 report.record_result(load_test(module_name, idref, mode))
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))
200 processed_modules.append(package.name)
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"]})
208 package.state = 'installed'
209 for kind in ('init', 'demo', 'update'):
210 if hasattr(package, kind):
211 delattr(package, kind)
213 registry._init_modules.add(package.name)
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
222 return loaded_modules, processed_modules
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')
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))
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 = []
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
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()
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
274 # This is a brand new registry, just created in
275 # openerp.modules.registry.RegistryManager.new().
276 registry = openerp.registry(cr.dbname)
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'))
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)
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)'))
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)
293 if tools.config['load_language']:
294 for lang in tools.config['load_language'].split(','):
295 tools.load_language(cr, lang)
297 # STEP 2: Mark other modules to be loaded/updated
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)
304 _check_module_names(cr, itertools.chain(tools.config['init'].keys(), tools.config['update'].keys()))
306 mods = [k for k in tools.config['init'] if tools.config['init'][k]]
308 ids = modobj.search(cr, SUPERUSER_ID, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
310 modobj.button_install(cr, SUPERUSER_ID, ids)
312 mods = [k for k in tools.config['update'] if tools.config['update'][k]]
314 ids = modobj.search(cr, SUPERUSER_ID, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
316 modobj.button_upgrade(cr, SUPERUSER_ID, ids)
318 cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
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
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)
342 processed_modules += load_marked_modules(cr, graph,
343 ['to install'], force, status, report,
344 loaded_modules, update_module)
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'], {})
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('.', '_'))
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)
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)
371 _logger.warning("Model %s is declared but cannot be loaded! (Perhaps a module was partially removed or renamed)", model)
373 # Cleanup orphan records
374 registry['ir.model.data']._process_end(cr, SUPERUSER_ID, processed_modules)
376 for kind in ('init', 'demo', 'update'):
377 tools.config[kind] = {}
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
387 cr.execute('''delete from
390 (id not IN (select parent_id from ir_ui_menu where parent_id is not null))
392 (id not IN (select res_id from ir_values where model='ir.ui.menu'))
394 (id not IN (select res_id from ir_model_data where model='ir.ui.menu'))''')
399 _logger.info('removed %d unused menus', cr.rowcount)
401 # STEP 6: Uninstall modules to remove
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
412 _logger.info('Reloading registry once more after uninstalling modules')
413 return openerp.modules.registry.RegistryManager.new(cr.dbname, force_demo, status, update_module)
415 # STEP 7: verify custom views on every model
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)
426 _logger.error('At least one test failed when loading the modules.')
428 _logger.info('Modules loaded.')
430 # STEP 8: call _register_hook on every model
431 for model in registry.models.values():
432 model._register_hook(cr)
434 # STEP 9: Run the post-install tests
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))
443 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: