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.pooler as pooler
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
47 _logger = logging.getLogger(__name__)
49 def open_openerp_namespace():
50 # See comment for open_openerp_namespace.
51 if openerp.conf.deprecation.open_openerp_namespace:
52 for k, v in list(sys.modules.items()):
53 if k.startswith('openerp.') and sys.modules.get(k[8:]) is None:
54 sys.modules[k[8:]] = v
57 def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=None, report=None):
58 """Migrates+Updates or Installs all module nodes from ``graph``
59 :param graph: graph of module nodes to load
60 :param status: status dictionary for keeping track of progress
61 :param perform_checks: whether module descriptors should be checked for validity (prints warnings
63 :param skip_modules: optional list of module names (packages) which have previously been loaded and can be skipped
64 :return: list of modules that were installed or updated
66 def process_sql_file(cr, fp):
67 queries = fp.read().split(';')
69 new_query = ' '.join(query.split())
73 load_init_xml = lambda *args: _load_data(cr, *args, kind='init_xml')
74 load_update_xml = lambda *args: _load_data(cr, *args, kind='update_xml')
75 load_demo_xml = lambda *args: _load_data(cr, *args, kind='demo_xml')
76 load_data = lambda *args: _load_data(cr, *args, kind='data')
77 load_demo = lambda *args: _load_data(cr, *args, kind='demo')
79 def load_test(module_name, idref, mode):
82 threading.currentThread().testing = True
83 _load_data(cr, module_name, idref, mode, 'test')
87 'module %s: an exception occurred in a test', module_name)
90 threading.currentThread().testing = False
91 if tools.config.options['test_commit']:
96 def _load_data(cr, module_name, idref, mode, kind):
99 kind: data, demo, test, init_xml, update_xml, demo_xml.
101 noupdate is False, unless it is demo data or it is csv data in
105 for filename in package.data[kind]:
107 _logger.log(logging.TEST, "module %s: loading %s", module_name, filename)
109 _logger.info("module %s: loading %s", module_name, filename)
110 _, ext = os.path.splitext(filename)
111 pathname = os.path.join(module_name, filename)
112 fp = tools.file_open(pathname)
114 if kind in ('demo', 'demo_xml'):
119 if kind in ('init', 'init_xml'):
121 tools.convert_csv_import(cr, module_name, pathname, fp.read(), idref, mode, noupdate)
123 process_sql_file(cr, fp)
125 tools.convert_yaml_import(cr, module_name, fp, kind, idref, mode, noupdate, report)
127 tools.convert_xml_import(cr, module_name, fp, idref, mode, noupdate, report)
129 pass # .js files are valid but ignored here.
131 _logger.warning("Can't load unknown file type %s.", filename)
138 processed_modules = []
140 pool = pooler.get_pool(cr.dbname)
141 migrations = openerp.modules.migration.MigrationManager(cr, graph)
142 _logger.info('loading %d modules...', len(graph))
144 # Query manual fields for all models at once and save them on the registry
145 # so the initialization code for each model does not have to do it
146 # one model at a time.
147 pool.fields_by_model = {}
148 cr.execute('SELECT * FROM ir_model_fields WHERE state=%s', ('manual',))
149 for field in cr.dictfetchall():
150 pool.fields_by_model.setdefault(field['model'], []).append(field)
152 # register, instantiate and initialize models for each modules
153 for index, package in enumerate(graph):
154 module_name = package.name
155 module_id = package.id
157 if skip_modules and module_name in skip_modules:
160 _logger.debug('module %s: loading objects', package.name)
161 migrations.migrate_module(package, 'pre')
162 load_openerp_module(package.name)
164 models = pool.load(cr, package)
166 loaded_modules.append(package.name)
167 if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
168 init_module_models(cr, package.name, models)
169 pool._init_modules.add(package.name)
170 status['progress'] = float(index) / len(graph)
172 # Can't put this line out of the loop: ir.module.module will be
173 # registered by init_module_models() above.
174 modobj = pool.get('ir.module.module')
177 modobj.check(cr, SUPERUSER_ID, [module_id])
182 if hasattr(package, 'init') or package.state == 'to install':
185 if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
186 if package.state=='to upgrade':
187 # upgrading the module information
188 modobj.write(cr, SUPERUSER_ID, [module_id], modobj.get_values_from_terp(package.data))
189 load_init_xml(module_name, idref, mode)
190 load_update_xml(module_name, idref, mode)
191 load_data(module_name, idref, mode)
192 if hasattr(package, 'demo') or (package.dbdemo and package.state != 'installed'):
193 status['progress'] = (index + 0.75) / len(graph)
194 load_demo_xml(module_name, idref, mode)
195 load_demo(module_name, idref, mode)
196 cr.execute('update ir_module_module set demo=%s where id=%s', (True, module_id))
198 # launch tests only in demo mode, as most tests will depend
199 # on demo data. Other tests can be added into the regular
200 # 'data' section, but should probably not alter the data,
201 # as there is no rollback.
202 if tools.config.options['test_enable']:
203 report.record_result(load_test(module_name, idref, mode))
205 # Run the `fast_suite` and `checks` tests given by the module.
206 if module_name == 'base':
207 # Also run the core tests after the database is created.
208 report.record_result(openerp.modules.module.run_unit_tests('openerp'))
209 report.record_result(openerp.modules.module.run_unit_tests(module_name))
211 processed_modules.append(package.name)
213 migrations.migrate_module(package, 'post')
215 ver = adapt_version(package.data['version'])
216 # Set new modules and dependencies
217 modobj.write(cr, SUPERUSER_ID, [module_id], {'state': 'installed', 'latest_version': ver})
218 # Update translations for all installed languages
219 modobj.update_translations(cr, SUPERUSER_ID, [module_id], None)
221 package.state = 'installed'
222 for kind in ('init', 'demo', 'update'):
223 if hasattr(package, kind):
224 delattr(package, kind)
228 # The query won't be valid for models created later (i.e. custom model
229 # created after the registry has been loaded), so empty its result.
230 pool.fields_by_model = None
234 return loaded_modules, processed_modules
236 def _check_module_names(cr, module_names):
237 mod_names = set(module_names)
238 if 'base' in mod_names:
239 # ignore dummy 'all' module
240 if 'all' in mod_names:
241 mod_names.remove('all')
243 cr.execute("SELECT count(id) AS count FROM ir_module_module WHERE name in %s", (tuple(mod_names),))
244 if cr.dictfetchone()['count'] != len(mod_names):
245 # find out what module name(s) are incorrect:
246 cr.execute("SELECT name FROM ir_module_module")
247 incorrect_names = mod_names.difference([x['name'] for x in cr.dictfetchall()])
248 _logger.warning('invalid module names, ignored: %s', ", ".join(incorrect_names))
250 def load_marked_modules(cr, graph, states, force, progressdict, report, loaded_modules, perform_checks):
251 """Loads modules marked with ``states``, adding them to ``graph`` and
252 ``loaded_modules`` and returns a list of installed/upgraded modules."""
253 processed_modules = []
255 cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(states),))
256 module_list = [name for (name,) in cr.fetchall() if name not in graph]
257 graph.add_modules(cr, module_list, force)
258 _logger.debug('Updating graph with %d more modules', len(module_list))
259 loaded, processed = load_module_graph(cr, graph, progressdict, report=report, skip_modules=loaded_modules, perform_checks=perform_checks)
260 processed_modules.extend(processed)
261 loaded_modules.extend(loaded)
262 if not processed: break
263 return processed_modules
266 def load_modules(db, force_demo=False, status=None, update_module=False):
267 # TODO status['progress'] reporting is broken: used twice (and reset each
268 # time to zero) in load_module_graph, not fine-grained enough.
269 # It should be a method exposed by the pool.
270 initialize_sys_path()
272 open_openerp_namespace()
280 if not openerp.modules.db.is_initialized(cr):
281 _logger.info("init db")
282 openerp.modules.db.initialize(cr)
283 tools.config["init"]["all"] = 1
284 tools.config['update']['all'] = 1
285 if not tools.config['without_demo']:
286 tools.config["demo"]['all'] = 1
288 # This is a brand new pool, just created in pooler.get_db_and_pool()
289 pool = pooler.get_pool(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 = pool._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 = pool.get('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'))
334 # STEP 3: Load marked modules (skipping base which was done in STEP 1)
335 # IMPORTANT: this is done in two parts, first loading all installed or
336 # partially installed modules (i.e. installed/to upgrade), to
337 # offer a consistent system to the second part: installing
338 # newly selected modules.
339 # We include the modules 'to remove' in the first step, because
340 # they are part of the "currently installed" modules. They will
341 # be dropped in STEP 6 later, before restarting the loading
343 states_to_load = ['installed', 'to upgrade', 'to remove']
344 processed = load_marked_modules(cr, graph, states_to_load, force, status, report, loaded_modules, update_module)
345 processed_modules.extend(processed)
347 states_to_load = ['to install']
348 processed = load_marked_modules(cr, graph, states_to_load, force, status, report, loaded_modules, update_module)
349 processed_modules.extend(processed)
352 cr.execute('select model from ir_model where state=%s', ('manual',))
353 for model in cr.dictfetchall():
354 pool.get('ir.model').instanciate(cr, SUPERUSER_ID, model['model'], {})
356 # STEP 4: Finish and cleanup installations
357 if processed_modules:
358 cr.execute("""select model,name from ir_model where id NOT IN (select distinct model_id from ir_model_access)""")
359 for (model, name) in cr.fetchall():
360 model_obj = pool.get(model)
361 if model_obj and not model_obj.is_transient():
362 _logger.warning('The model %s has no access rules, consider adding one. E.g. access_%s,access_%s,model_%s,,1,1,1,1',
363 model, model.replace('.', '_'), model.replace('.', '_'), model.replace('.', '_'))
365 # Temporary warning while we remove access rights on osv_memory objects, as they have
366 # been replaced by owner-only access rights
367 cr.execute("""select distinct mod.model, mod.name from ir_model_access acc, ir_model mod where acc.model_id = mod.id""")
368 for (model, name) in cr.fetchall():
369 model_obj = pool.get(model)
370 if model_obj and model_obj.is_transient():
371 _logger.warning('The transient model %s (%s) should not have explicit access rules!', model, name)
373 cr.execute("SELECT model from ir_model")
374 for (model,) in cr.fetchall():
375 obj = pool.get(model)
377 obj._check_removed_columns(cr, log=True)
379 _logger.warning("Model %s is declared but cannot be loaded! (Perhaps a module was partially removed or renamed)", model)
381 # Cleanup orphan records
382 pool.get('ir.model.data')._process_end(cr, SUPERUSER_ID, processed_modules)
384 for kind in ('init', 'demo', 'update'):
385 tools.config[kind] = {}
389 # STEP 5: Cleanup menus
390 # Remove menu items that are not referenced by any of other
391 # (child) menu item, ir_values, or ir_model_data.
392 # TODO: This code could be a method of ir_ui_menu. Remove menu without actions of children
395 cr.execute('''delete from
398 (id not IN (select parent_id from ir_ui_menu where parent_id is not null))
400 (id not IN (select res_id from ir_values where model='ir.ui.menu'))
402 (id not IN (select res_id from ir_model_data where model='ir.ui.menu'))''')
407 _logger.info('removed %d unused menus', cr.rowcount)
409 # STEP 6: Uninstall modules to remove
411 # Remove records referenced from ir_model_data for modules to be
412 # removed (and removed the references from ir_model_data).
413 cr.execute("SELECT id FROM ir_module_module WHERE state=%s", ('to remove',))
414 mod_ids_to_remove = [x[0] for x in cr.fetchall()]
415 if mod_ids_to_remove:
416 pool.get('ir.module.module').module_uninstall(cr, SUPERUSER_ID, mod_ids_to_remove)
417 # Recursive reload, should only happen once, because there should be no
418 # modules to remove next time
420 _logger.info('Reloading registry once more after uninstalling modules')
421 return pooler.restart_pool(cr.dbname, force_demo, status, update_module)
424 _logger.error('At least one test failed when loading the modules.')
426 _logger.info('Modules loaded.')
428 # STEP 7: call _register_hook on every model
429 for model in pool.models.values():
430 model._register_hook(cr)
436 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: