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.osv as osv
38 import openerp.pooler as pooler
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__)
48 def open_openerp_namespace():
49 # See comment for open_openerp_namespace.
50 if openerp.conf.deprecation.open_openerp_namespace:
51 for k, v in list(sys.modules.items()):
52 if k.startswith('openerp.') and sys.modules.get(k[8:]) is None:
53 sys.modules[k[8:]] = v
56 def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=None, report=None):
57 """Migrates+Updates or Installs all module nodes from ``graph``
58 :param graph: graph of module nodes to load
59 :param status: status dictionary for keeping track of progress
60 :param perform_checks: whether module descriptors should be checked for validity (prints warnings
62 :param skip_modules: optional list of module names (packages) which have previously been loaded and can be skipped
63 :return: list of modules that were installed or updated
65 def process_sql_file(cr, fp):
66 queries = fp.read().split(';')
68 new_query = ' '.join(query.split())
72 load_init_xml = lambda *args: _load_data(cr, *args, kind='init_xml')
73 load_update_xml = lambda *args: _load_data(cr, *args, kind='update_xml')
74 load_demo_xml = lambda *args: _load_data(cr, *args, kind='demo_xml')
75 load_data = lambda *args: _load_data(cr, *args, kind='data')
76 load_demo = lambda *args: _load_data(cr, *args, kind='demo')
78 def load_test(module_name, idref, mode):
81 threading.currentThread().testing = True
82 _load_data(cr, module_name, idref, mode, 'test')
86 'module %s: an exception occurred in a test', module_name)
89 threading.currentThread().testing = False
90 if tools.config.options['test_commit']:
95 def _load_data(cr, module_name, idref, mode, kind):
98 kind: data, demo, test, init_xml, update_xml, demo_xml.
100 noupdate is False, unless it is demo data or it is csv data in
104 for filename in package.data[kind]:
106 _logger.log(logging.TEST, "module %s: loading %s", module_name, filename)
108 _logger.info("module %s: loading %s", module_name, filename)
109 _, ext = os.path.splitext(filename)
110 pathname = os.path.join(module_name, filename)
111 fp = tools.file_open(pathname)
113 if kind in ('demo', 'demo_xml'):
118 if kind in ('init', 'init_xml'):
120 tools.convert_csv_import(cr, module_name, pathname, fp.read(), idref, mode, noupdate)
122 process_sql_file(cr, fp)
124 tools.convert_yaml_import(cr, module_name, fp, kind, idref, mode, noupdate, report)
126 tools.convert_xml_import(cr, module_name, fp, idref, mode, noupdate, report)
128 pass # .js files are valid but ignored here.
130 _logger.warning("Can't load unknown file type %s.", filename)
137 processed_modules = []
139 pool = pooler.get_pool(cr.dbname)
140 migrations = openerp.modules.migration.MigrationManager(cr, graph)
141 _logger.debug('loading %d packages...', len(graph))
143 # Query manual fields for all models at once and save them on the registry
144 # so the initialization code for each model does not have to do it
145 # one model at a time.
146 pool.fields_by_model = {}
147 cr.execute('SELECT * FROM ir_model_fields WHERE state=%s', ('manual',))
148 for field in cr.dictfetchall():
149 pool.fields_by_model.setdefault(field['model'], []).append(field)
151 # register, instantiate and initialize models for each modules
152 for index, package in enumerate(graph):
153 module_name = package.name
154 module_id = package.id
156 if skip_modules and module_name in skip_modules:
159 _logger.info('module %s: loading objects', package.name)
160 migrations.migrate_module(package, 'pre')
161 load_openerp_module(package.name)
163 models = pool.load(cr, package)
165 loaded_modules.append(package.name)
166 if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
167 init_module_models(cr, package.name, models)
168 pool._init_modules.add(package.name)
169 status['progress'] = float(index) / len(graph)
171 # Can't put this line out of the loop: ir.module.module will be
172 # registered by init_module_models() above.
173 modobj = pool.get('ir.module.module')
176 modobj.check(cr, SUPERUSER_ID, [module_id])
181 if hasattr(package, 'init') or package.state == 'to install':
184 if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
185 if package.state=='to upgrade':
186 # upgrading the module information
187 modobj.write(cr, SUPERUSER_ID, [module_id], modobj.get_values_from_terp(package.data))
188 load_init_xml(module_name, idref, mode)
189 load_update_xml(module_name, idref, mode)
190 load_data(module_name, idref, mode)
191 if hasattr(package, 'demo') or (package.dbdemo and package.state != 'installed'):
192 status['progress'] = (index + 0.75) / len(graph)
193 load_demo_xml(module_name, idref, mode)
194 load_demo(module_name, idref, mode)
195 cr.execute('update ir_module_module set demo=%s where id=%s', (True, module_id))
197 # launch tests only in demo mode, as most tests will depend
198 # on demo data. Other tests can be added into the regular
199 # 'data' section, but should probably not alter the data,
200 # as there is no rollback.
201 if tools.config.options['test_enable']:
202 report.record_result(load_test(module_name, idref, mode))
204 # Run the `fast_suite` and `checks` tests given by the module.
205 if module_name == 'base':
206 # Also run the core tests after the database is created.
207 report.record_result(openerp.modules.module.run_unit_tests('openerp'))
208 report.record_result(openerp.modules.module.run_unit_tests(module_name))
210 processed_modules.append(package.name)
212 migrations.migrate_module(package, 'post')
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)
220 package.state = 'installed'
221 for kind in ('init', 'demo', 'update'):
222 if hasattr(package, kind):
223 delattr(package, kind)
227 # The query won't be valid for models created later (i.e. custom model
228 # created after the registry has been loaded), so empty its result.
229 pool.fields_by_model = None
233 return loaded_modules, processed_modules
235 def _check_module_names(cr, module_names):
236 mod_names = set(module_names)
237 if 'base' in mod_names:
238 # ignore dummy 'all' module
239 if 'all' in mod_names:
240 mod_names.remove('all')
242 cr.execute("SELECT count(id) AS count FROM ir_module_module WHERE name in %s", (tuple(mod_names),))
243 if cr.dictfetchone()['count'] != len(mod_names):
244 # find out what module name(s) are incorrect:
245 cr.execute("SELECT name FROM ir_module_module")
246 incorrect_names = mod_names.difference([x['name'] for x in cr.dictfetchall()])
247 _logger.warning('invalid module names, ignored: %s', ", ".join(incorrect_names))
249 def load_marked_modules(cr, graph, states, force, progressdict, report, loaded_modules, perform_checks):
250 """Loads modules marked with ``states``, adding them to ``graph`` and
251 ``loaded_modules`` and returns a list of installed/upgraded modules."""
252 processed_modules = []
254 cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(states),))
255 module_list = [name for (name,) in cr.fetchall() if name not in graph]
256 graph.add_modules(cr, module_list, force)
257 _logger.debug('Updating graph with %d more modules', len(module_list))
258 loaded, processed = load_module_graph(cr, graph, progressdict, report=report, skip_modules=loaded_modules, perform_checks=perform_checks)
259 processed_modules.extend(processed)
260 loaded_modules.extend(loaded)
261 if not processed: break
262 return processed_modules
265 def load_modules(db, force_demo=False, status=None, update_module=False):
266 # TODO status['progress'] reporting is broken: used twice (and reset each
267 # time to zero) in load_module_graph, not fine-grained enough.
268 # It should be a method exposed by the pool.
269 initialize_sys_path()
271 open_openerp_namespace()
279 if not openerp.modules.db.is_initialized(cr):
280 _logger.info("init db")
281 openerp.modules.db.initialize(cr)
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 pool, just created in pooler.get_db_and_pool()
288 pool = pooler.get_pool(cr.dbname)
290 if 'base' in tools.config['update'] or 'all' in tools.config['update']:
291 cr.execute("update ir_module_module set state=%s where name=%s and state=%s", ('to upgrade', 'base', 'installed'))
293 # STEP 1: LOAD BASE (must be done before module dependencies can be computed for later steps)
294 graph = openerp.modules.graph.Graph()
295 graph.add_module(cr, 'base', force)
297 _logger.critical('module base cannot be loaded! (hint: verify addons-path)')
298 raise osv.osv.except_osv(_('Could not load base module'), _('module base cannot be loaded! (hint: verify addons-path)'))
300 # processed_modules: for cleanup step after install
301 # loaded_modules: to avoid double loading
302 report = pool._assertion_report
303 loaded_modules, processed_modules = load_module_graph(cr, graph, status, perform_checks=update_module, report=report)
305 if tools.config['load_language']:
306 for lang in tools.config['load_language'].split(','):
307 tools.load_language(cr, lang)
309 # STEP 2: Mark other modules to be loaded/updated
311 modobj = pool.get('ir.module.module')
312 if ('base' in tools.config['init']) or ('base' in tools.config['update']):
313 _logger.info('updating modules list')
314 modobj.update_list(cr, SUPERUSER_ID)
316 _check_module_names(cr, itertools.chain(tools.config['init'].keys(), tools.config['update'].keys()))
318 mods = [k for k in tools.config['init'] if tools.config['init'][k]]
320 ids = modobj.search(cr, SUPERUSER_ID, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
322 modobj.button_install(cr, SUPERUSER_ID, ids)
324 mods = [k for k in tools.config['update'] if tools.config['update'][k]]
326 ids = modobj.search(cr, SUPERUSER_ID, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
328 modobj.button_upgrade(cr, SUPERUSER_ID, ids)
330 cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
333 # STEP 3: Load marked modules (skipping base which was done in STEP 1)
334 # IMPORTANT: this is done in two parts, first loading all installed or
335 # partially installed modules (i.e. installed/to upgrade), to
336 # offer a consistent system to the second part: installing
337 # newly selected modules.
338 # We include the modules 'to remove' in the first step, because
339 # they are part of the "currently installed" modules. They will
340 # be dropped in STEP 6 later, before restarting the loading
342 states_to_load = ['installed', 'to upgrade', 'to remove']
343 processed = load_marked_modules(cr, graph, states_to_load, force, status, report, loaded_modules, update_module)
344 processed_modules.extend(processed)
346 states_to_load = ['to install']
347 processed = load_marked_modules(cr, graph, states_to_load, force, status, report, loaded_modules, update_module)
348 processed_modules.extend(processed)
351 cr.execute('select model from ir_model where state=%s', ('manual',))
352 for model in cr.dictfetchall():
353 pool.get('ir.model').instanciate(cr, SUPERUSER_ID, model['model'], {})
355 # STEP 4: Finish and cleanup installations
356 if processed_modules:
357 cr.execute("""select model,name from ir_model where id NOT IN (select distinct model_id from ir_model_access)""")
358 for (model, name) in cr.fetchall():
359 model_obj = pool.get(model)
360 if model_obj and not model_obj.is_transient():
361 _logger.warning('The model %s has no access rules, consider adding one. E.g. access_%s,access_%s,model_%s,,1,1,1,1',
362 model, model.replace('.', '_'), model.replace('.', '_'), model.replace('.', '_'))
364 # Temporary warning while we remove access rights on osv_memory objects, as they have
365 # been replaced by owner-only access rights
366 cr.execute("""select distinct mod.model, mod.name from ir_model_access acc, ir_model mod where acc.model_id = mod.id""")
367 for (model, name) in cr.fetchall():
368 model_obj = pool.get(model)
369 if model_obj and model_obj.is_transient():
370 _logger.warning('The transient model %s (%s) should not have explicit access rules!', model, name)
372 cr.execute("SELECT model from ir_model")
373 for (model,) in cr.fetchall():
374 obj = pool.get(model)
376 obj._check_removed_columns(cr, log=True)
378 _logger.warning("Model %s is declared but cannot be loaded! (Perhaps a module was partially removed or renamed)", model)
380 # Cleanup orphan records
381 pool.get('ir.model.data')._process_end(cr, SUPERUSER_ID, processed_modules)
383 for kind in ('init', 'demo', 'update'):
384 tools.config[kind] = {}
388 # STEP 5: Cleanup menus
389 # Remove menu items that are not referenced by any of other
390 # (child) menu item, ir_values, or ir_model_data.
391 # TODO: This code could be a method of ir_ui_menu. Remove menu without actions of children
394 cr.execute('''delete from
397 (id not IN (select parent_id from ir_ui_menu where parent_id is not null))
399 (id not IN (select res_id from ir_values where model='ir.ui.menu'))
401 (id not IN (select res_id from ir_model_data where model='ir.ui.menu'))''')
406 _logger.info('removed %d unused menus', cr.rowcount)
408 # STEP 6: Uninstall modules to remove
410 # Remove records referenced from ir_model_data for modules to be
411 # removed (and removed the references from ir_model_data).
412 cr.execute("SELECT id FROM ir_module_module WHERE state=%s", ('to remove',))
413 mod_ids_to_remove = [x[0] for x in cr.fetchall()]
414 if mod_ids_to_remove:
415 pool.get('ir.module.module').module_uninstall(cr, SUPERUSER_ID, mod_ids_to_remove)
416 # Recursive reload, should only happen once, because there should be no
417 # modules to remove next time
419 _logger.info('Reloading registry once more after uninstalling modules')
420 return pooler.restart_pool(cr.dbname, force_demo, status, update_module)
423 _logger.error('At least one test failed when loading the modules.')
425 _logger.info('Modules loaded.')
427 # STEP 7: call _register_hook on every model
428 for model in pool.models.values():
429 model._register_hook(cr)
435 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: