[FIX] module loading: call adapt_version() instead of computing it ourself
[odoo/odoo.git] / openerp / modules / loading.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
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>).
7 #
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.
12 #
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.
17 #
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/>.
20 #
21 ##############################################################################
22
23 """ Modules (also called addons) management.
24
25 """
26
27 import itertools
28 import logging
29 import os
30 import sys
31 import threading
32
33 import openerp
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
41
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
46 _logger = logging.getLogger(__name__)
47
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
54
55
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
61                               for same cases)
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
64     """
65     def process_sql_file(cr, fp):
66         queries = fp.read().split(';')
67         for query in queries:
68             new_query = ' '.join(query.split())
69             if new_query:
70                 cr.execute(new_query)
71
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')
77
78     def load_test(module_name, idref, mode):
79         cr.commit()
80         try:
81             threading.currentThread().testing = True
82             _load_data(cr, module_name, idref, mode, 'test')
83             return True
84         except Exception:
85             _logger.exception(
86                 'module %s: an exception occurred in a test', module_name)
87             return False
88         finally:
89             threading.currentThread().testing = False
90             if tools.config.options['test_commit']:
91                 cr.commit()
92             else:
93                 cr.rollback()
94
95     def _load_data(cr, module_name, idref, mode, kind):
96         """
97
98         kind: data, demo, test, init_xml, update_xml, demo_xml.
99
100         noupdate is False, unless it is demo data or it is csv data in
101         init mode.
102
103         """
104         for filename in package.data[kind]:
105             if kind == 'test':
106                 _logger.log(logging.TEST, "module %s: loading %s", module_name, filename)
107             else:
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)
112             noupdate = False
113             if kind in ('demo', 'demo_xml'):
114                 noupdate = True
115             try:
116                 ext = ext.lower()
117                 if ext == '.csv':
118                     if kind in ('init', 'init_xml'):
119                         noupdate = True
120                     tools.convert_csv_import(cr, module_name, pathname, fp.read(), idref, mode, noupdate)
121                 elif ext == '.sql':
122                     process_sql_file(cr, fp)
123                 elif ext == '.yml':
124                     tools.convert_yaml_import(cr, module_name, fp, kind, idref, mode, noupdate, report)
125                 elif ext == '.xml':
126                     tools.convert_xml_import(cr, module_name, fp, idref, mode, noupdate, report)
127                 elif ext == '.js':
128                     pass # .js files are valid but ignored here.
129                 else:
130                     _logger.warning("Can't load unknown file type %s.", filename)
131             finally:
132                 fp.close()
133
134     if status is None:
135         status = {}
136
137     processed_modules = []
138     loaded_modules = []
139     pool = pooler.get_pool(cr.dbname)
140     migrations = openerp.modules.migration.MigrationManager(cr, graph)
141     _logger.debug('loading %d packages...', len(graph))
142
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)
150
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
155
156         if skip_modules and module_name in skip_modules:
157             continue
158
159         _logger.info('module %s: loading objects', package.name)
160         migrations.migrate_module(package, 'pre')
161         load_openerp_module(package.name)
162
163         models = pool.load(cr, package)
164
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)
170
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')
174
175         if perform_checks:
176             modobj.check(cr, SUPERUSER_ID, [module_id])
177
178         idref = {}
179
180         mode = 'update'
181         if hasattr(package, 'init') or package.state == 'to install':
182             mode = 'init'
183
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))
196
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))
203
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))
209
210             processed_modules.append(package.name)
211
212             migrations.migrate_module(package, 'post')
213
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)
219
220             package.state = 'installed'
221             for kind in ('init', 'demo', 'update'):
222                 if hasattr(package, kind):
223                     delattr(package, kind)
224
225         cr.commit()
226
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
230     
231     cr.commit()
232
233     return loaded_modules, processed_modules
234
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')
241     if mod_names:
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))
248
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 = []
253     while True:
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
263
264
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()
270
271     open_openerp_namespace()
272
273     force = []
274     if force_demo:
275         force.append('demo')
276
277     cr = db.cursor()
278     try:
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
286
287         # This is a brand new pool, just created in pooler.get_db_and_pool()
288         pool = pooler.get_pool(cr.dbname)
289
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'))
292
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)
296         if not graph:
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)'))
299
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)
304
305         if tools.config['load_language']:
306             for lang in tools.config['load_language'].split(','):
307                 tools.load_language(cr, lang)
308
309         # STEP 2: Mark other modules to be loaded/updated
310         if update_module:
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)
315
316             _check_module_names(cr, itertools.chain(tools.config['init'].keys(), tools.config['update'].keys()))
317
318             mods = [k for k in tools.config['init'] if tools.config['init'][k]]
319             if mods:
320                 ids = modobj.search(cr, SUPERUSER_ID, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
321                 if ids:
322                     modobj.button_install(cr, SUPERUSER_ID, ids)
323
324             mods = [k for k in tools.config['update'] if tools.config['update'][k]]
325             if mods:
326                 ids = modobj.search(cr, SUPERUSER_ID, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
327                 if ids:
328                     modobj.button_upgrade(cr, SUPERUSER_ID, ids)
329
330             cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
331
332
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
341         #            process.
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)
345         if update_module:
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)
349
350         # load custom models
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'], {})
354
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('.', '_'))
363
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)
371
372             cr.execute("SELECT model from ir_model")
373             for (model,) in cr.fetchall():
374                 obj = pool.get(model)
375                 if obj:
376                     obj._check_removed_columns(cr, log=True)
377                 else:
378                     _logger.warning("Model %s is declared but cannot be loaded! (Perhaps a module was partially removed or renamed)", model)
379
380             # Cleanup orphan records
381             pool.get('ir.model.data')._process_end(cr, SUPERUSER_ID, processed_modules)
382
383         for kind in ('init', 'demo', 'update'):
384             tools.config[kind] = {}
385
386         cr.commit()
387
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
392         if update_module:
393             while True:
394                 cr.execute('''delete from
395                         ir_ui_menu
396                     where
397                         (id not IN (select parent_id from ir_ui_menu where parent_id is not null))
398                     and
399                         (id not IN (select res_id from ir_values where model='ir.ui.menu'))
400                     and
401                         (id not IN (select res_id from ir_model_data where model='ir.ui.menu'))''')
402                 cr.commit()
403                 if not cr.rowcount:
404                     break
405                 else:
406                     _logger.info('removed %d unused menus', cr.rowcount)
407
408         # STEP 6: Uninstall modules to remove
409         if update_module:
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
418                 cr.commit()
419                 _logger.info('Reloading registry once more after uninstalling modules')
420                 return pooler.restart_pool(cr.dbname, force_demo, status, update_module)
421
422         if report.failures:
423             _logger.error('At least one test failed when loading the modules.')
424         else:
425             _logger.info('Modules loaded.')
426
427         # STEP 7: call _register_hook on every model
428         for model in pool.models.values():
429             model._register_hook(cr)
430
431     finally:
432         cr.close()
433
434
435 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: