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-2013 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
46 _logger = logging.getLogger(__name__)
47 _test_logger = logging.getLogger('openerp.tests')
50 def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=None, report=None):
51 """Migrates+Updates or Installs all module nodes from ``graph``
52 :param graph: graph of module nodes to load
53 :param status: status dictionary for keeping track of progress
54 :param perform_checks: whether module descriptors should be checked for validity (prints warnings
56 :param skip_modules: optional list of module names (packages) which have previously been loaded and can be skipped
57 :return: list of modules that were installed or updated
59 def process_sql_file(cr, fp):
60 queries = fp.read().split(';')
62 new_query = ' '.join(query.split())
66 load_init_xml = lambda *args: _load_data(cr, *args, kind='init_xml')
67 load_update_xml = lambda *args: _load_data(cr, *args, kind='update_xml')
68 load_demo_xml = lambda *args: _load_data(cr, *args, kind='demo_xml')
69 load_data = lambda *args: _load_data(cr, *args, kind='data')
70 load_demo = lambda *args: _load_data(cr, *args, kind='demo')
72 def load_test(module_name, idref, mode):
75 threading.currentThread().testing = True
76 _load_data(cr, module_name, idref, mode, 'test')
79 _test_logger.exception(
80 'module %s: an exception occurred in a test', module_name)
83 threading.currentThread().testing = False
84 if tools.config.options['test_commit']:
89 def _load_data(cr, module_name, idref, mode, kind):
92 kind: data, demo, test, init_xml, update_xml, demo_xml.
94 noupdate is False, unless it is demo data or it is csv data in
98 for filename in package.data[kind]:
99 _logger.info("module %s: loading %s", module_name, filename)
100 _, ext = os.path.splitext(filename)
101 pathname = os.path.join(module_name, filename)
102 fp = tools.file_open(pathname)
104 if kind in ('demo', 'demo_xml'):
109 if kind in ('init', 'init_xml'):
111 tools.convert_csv_import(cr, module_name, pathname, fp.read(), idref, mode, noupdate)
113 process_sql_file(cr, fp)
115 tools.convert_yaml_import(cr, module_name, fp, kind, idref, mode, noupdate, report)
117 tools.convert_xml_import(cr, module_name, fp, idref, mode, noupdate, report)
119 pass # .js files are valid but ignored here.
121 _logger.warning("Can't load unknown file type %s.", filename)
128 processed_modules = []
130 registry = openerp.registry(cr.dbname)
131 migrations = openerp.modules.migration.MigrationManager(cr, graph)
132 _logger.info('loading %d modules...', len(graph))
134 # Query manual fields for all models at once and save them on the registry
135 # so the initialization code for each model does not have to do it
136 # one model at a time.
137 registry.fields_by_model = {}
138 cr.execute('SELECT * FROM ir_model_fields WHERE state=%s', ('manual',))
139 for field in cr.dictfetchall():
140 registry.fields_by_model.setdefault(field['model'], []).append(field)
142 # register, instantiate and initialize models for each modules
143 for index, package in enumerate(graph):
144 module_name = package.name
145 module_id = package.id
147 if skip_modules and module_name in skip_modules:
150 _logger.debug('module %s: loading objects', package.name)
151 migrations.migrate_module(package, 'pre')
152 load_openerp_module(package.name)
154 models = registry.load(cr, package)
156 loaded_modules.append(package.name)
157 if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
158 init_module_models(cr, package.name, models)
159 registry._init_modules.add(package.name)
160 status['progress'] = float(index) / len(graph)
162 # Can't put this line out of the loop: ir.module.module will be
163 # registered by init_module_models() above.
164 modobj = registry['ir.module.module']
167 modobj.check(cr, SUPERUSER_ID, [module_id])
172 if hasattr(package, 'init') or package.state == 'to install':
175 if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
176 if package.state=='to upgrade':
177 # upgrading the module information
178 modobj.write(cr, SUPERUSER_ID, [module_id], modobj.get_values_from_terp(package.data))
179 load_init_xml(module_name, idref, mode)
180 load_update_xml(module_name, idref, mode)
181 load_data(module_name, idref, mode)
182 if hasattr(package, 'demo') or (package.dbdemo and package.state != 'installed'):
183 status['progress'] = (index + 0.75) / len(graph)
184 load_demo_xml(module_name, idref, mode)
185 load_demo(module_name, idref, mode)
186 cr.execute('update ir_module_module set demo=%s where id=%s', (True, module_id))
188 # launch tests only in demo mode, as most tests will depend
189 # on demo data. Other tests can be added into the regular
190 # 'data' section, but should probably not alter the data,
191 # as there is no rollback.
192 if tools.config.options['test_enable']:
193 report.record_result(load_test(module_name, idref, mode))
195 # Run the `fast_suite` and `checks` tests given by the module.
196 if module_name == 'base':
197 # Also run the core tests after the database is created.
198 report.record_result(openerp.modules.module.run_unit_tests('openerp'))
199 report.record_result(openerp.modules.module.run_unit_tests(module_name))
201 processed_modules.append(package.name)
203 migrations.migrate_module(package, 'post')
205 ver = adapt_version(package.data['version'])
206 # Set new modules and dependencies
207 modobj.write(cr, SUPERUSER_ID, [module_id], {'state': 'installed', 'latest_version': ver})
208 # Update translations for all installed languages
209 modobj.update_translations(cr, SUPERUSER_ID, [module_id], None)
211 package.state = 'installed'
212 for kind in ('init', 'demo', 'update'):
213 if hasattr(package, kind):
214 delattr(package, kind)
218 # The query won't be valid for models created later (i.e. custom model
219 # created after the registry has been loaded), so empty its result.
220 registry.fields_by_model = None
224 return loaded_modules, processed_modules
226 def _check_module_names(cr, module_names):
227 mod_names = set(module_names)
228 if 'base' in mod_names:
229 # ignore dummy 'all' module
230 if 'all' in mod_names:
231 mod_names.remove('all')
233 cr.execute("SELECT count(id) AS count FROM ir_module_module WHERE name in %s", (tuple(mod_names),))
234 if cr.dictfetchone()['count'] != len(mod_names):
235 # find out what module name(s) are incorrect:
236 cr.execute("SELECT name FROM ir_module_module")
237 incorrect_names = mod_names.difference([x['name'] for x in cr.dictfetchall()])
238 _logger.warning('invalid module names, ignored: %s', ", ".join(incorrect_names))
240 def load_marked_modules(cr, graph, states, force, progressdict, report, loaded_modules, perform_checks):
241 """Loads modules marked with ``states``, adding them to ``graph`` and
242 ``loaded_modules`` and returns a list of installed/upgraded modules."""
243 processed_modules = []
245 cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(states),))
246 module_list = [name for (name,) in cr.fetchall() if name not in graph]
247 graph.add_modules(cr, module_list, force)
248 _logger.debug('Updating graph with %d more modules', len(module_list))
249 loaded, processed = load_module_graph(cr, graph, progressdict, report=report, skip_modules=loaded_modules, perform_checks=perform_checks)
250 processed_modules.extend(processed)
251 loaded_modules.extend(loaded)
252 if not processed: break
253 return processed_modules
256 def load_modules(db, force_demo=False, status=None, update_module=False):
257 # TODO status['progress'] reporting is broken: used twice (and reset each
258 # time to zero) in load_module_graph, not fine-grained enough.
259 # It should be a method exposed by the registry.
260 initialize_sys_path()
268 if not openerp.modules.db.is_initialized(cr):
269 _logger.info("init db")
270 openerp.modules.db.initialize(cr)
271 tools.config["init"]["all"] = 1
272 tools.config['update']['all'] = 1
273 if not tools.config['without_demo']:
274 tools.config["demo"]['all'] = 1
276 # This is a brand new registry, just created in
277 # openerp.modules.registry.RegistryManager.new().
278 registry = openerp.registry(cr.dbname)
280 if 'base' in tools.config['update'] or 'all' in tools.config['update']:
281 cr.execute("update ir_module_module set state=%s where name=%s and state=%s", ('to upgrade', 'base', 'installed'))
283 # STEP 1: LOAD BASE (must be done before module dependencies can be computed for later steps)
284 graph = openerp.modules.graph.Graph()
285 graph.add_module(cr, 'base', force)
287 _logger.critical('module base cannot be loaded! (hint: verify addons-path)')
288 raise osv.osv.except_osv(_('Could not load base module'), _('module base cannot be loaded! (hint: verify addons-path)'))
290 # processed_modules: for cleanup step after install
291 # loaded_modules: to avoid double loading
292 report = registry._assertion_report
293 loaded_modules, processed_modules = load_module_graph(cr, graph, status, perform_checks=update_module, report=report)
295 if tools.config['load_language']:
296 for lang in tools.config['load_language'].split(','):
297 tools.load_language(cr, lang)
299 # STEP 2: Mark other modules to be loaded/updated
301 modobj = registry['ir.module.module']
302 if ('base' in tools.config['init']) or ('base' in tools.config['update']):
303 _logger.info('updating modules list')
304 modobj.update_list(cr, SUPERUSER_ID)
306 _check_module_names(cr, itertools.chain(tools.config['init'].keys(), tools.config['update'].keys()))
308 mods = [k for k in tools.config['init'] if tools.config['init'][k]]
310 ids = modobj.search(cr, SUPERUSER_ID, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
312 modobj.button_install(cr, SUPERUSER_ID, ids)
314 mods = [k for k in tools.config['update'] if tools.config['update'][k]]
316 ids = modobj.search(cr, SUPERUSER_ID, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
318 modobj.button_upgrade(cr, SUPERUSER_ID, ids)
320 cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
323 # STEP 3: Load marked modules (skipping base which was done in STEP 1)
324 # IMPORTANT: this is done in two parts, first loading all installed or
325 # partially installed modules (i.e. installed/to upgrade), to
326 # offer a consistent system to the second part: installing
327 # newly selected modules.
328 # We include the modules 'to remove' in the first step, because
329 # they are part of the "currently installed" modules. They will
330 # be dropped in STEP 6 later, before restarting the loading
332 states_to_load = ['installed', 'to upgrade', 'to remove']
333 processed = load_marked_modules(cr, graph, states_to_load, force, status, report, loaded_modules, update_module)
334 processed_modules.extend(processed)
336 states_to_load = ['to install']
337 processed = load_marked_modules(cr, graph, states_to_load, force, status, report, loaded_modules, update_module)
338 processed_modules.extend(processed)
341 cr.execute('select model from ir_model where state=%s', ('manual',))
342 for model in cr.dictfetchall():
343 registry['ir.model'].instanciate(cr, SUPERUSER_ID, model['model'], {})
345 # STEP 4: Finish and cleanup installations
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 if model in registry and not registry[model].is_transient():
350 _logger.warning('The model %s has no access rules, consider adding one. E.g. access_%s,access_%s,model_%s,,1,1,1,1',
351 model, model.replace('.', '_'), model.replace('.', '_'), model.replace('.', '_'))
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 if model in registry and registry[model].is_transient():
358 _logger.warning('The transient model %s (%s) should not have explicit access rules!', model, name)
360 cr.execute("SELECT model from ir_model")
361 for (model,) in cr.fetchall():
362 if model in registry:
363 registry[model]._check_removed_columns(cr, log=True)
365 _logger.warning("Model %s is declared but cannot be loaded! (Perhaps a module was partially removed or renamed)", model)
367 # Cleanup orphan records
368 registry['ir.model.data']._process_end(cr, SUPERUSER_ID, processed_modules)
370 for kind in ('init', 'demo', 'update'):
371 tools.config[kind] = {}
375 # STEP 5: Cleanup menus
376 # Remove menu items that are not referenced by any of other
377 # (child) menu item, ir_values, or ir_model_data.
378 # TODO: This code could be a method of ir_ui_menu. Remove menu without actions of children
381 cr.execute('''delete from
384 (id not IN (select parent_id from ir_ui_menu where parent_id is not null))
386 (id not IN (select res_id from ir_values where model='ir.ui.menu'))
388 (id not IN (select res_id from ir_model_data where model='ir.ui.menu'))''')
393 _logger.info('removed %d unused menus', cr.rowcount)
395 # STEP 6: Uninstall modules to remove
397 # Remove records referenced from ir_model_data for modules to be
398 # removed (and removed the references from ir_model_data).
399 cr.execute("SELECT id FROM ir_module_module WHERE state=%s", ('to remove',))
400 mod_ids_to_remove = [x[0] for x in cr.fetchall()]
401 if mod_ids_to_remove:
402 registry['ir.module.module'].module_uninstall(cr, SUPERUSER_ID, mod_ids_to_remove)
403 # Recursive reload, should only happen once, because there should be no
404 # modules to remove next time
406 _logger.info('Reloading registry once more after uninstalling modules')
407 return openerp.modules.registry.RegistryManager.new(cr.dbname, force_demo, status, update_module)
410 _logger.error('At least one test failed when loading the modules.')
412 _logger.info('Modules loaded.')
414 # STEP 7: call _register_hook on every model
415 for model in registry.models.values():
416 model._register_hook(cr)
422 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: