[IMP] tests: removed the severity concept from `assert` tags.
[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-2011 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 base64
28 import imp
29 import itertools
30 import logging
31 import os
32 import re
33 import sys
34 import threading
35 import zipfile
36 import zipimport
37
38 from cStringIO import StringIO
39 from os.path import join as opj
40 from zipfile import PyZipFile, ZIP_DEFLATED
41
42
43 import openerp
44 import openerp.modules.db
45 import openerp.modules.graph
46 import openerp.modules.migration
47 import openerp.netsvc as netsvc
48 import openerp.osv as osv
49 import openerp.pooler as pooler
50 import openerp.release as release
51 import openerp.tools as tools
52 import openerp.tools.osutil as osutil
53 import openerp.tools.assertion_report as assertion_report
54
55 from openerp.tools.safe_eval import safe_eval as eval
56 from openerp.tools.translate import _
57 from openerp.modules.module import \
58     get_modules, get_modules_with_version, \
59     load_information_from_description_file, \
60     get_module_resource, zip_directory, \
61     get_module_path, initialize_sys_path, \
62     load_openerp_module, init_module_models
63
64 _logger = logging.getLogger(__name__)
65
66 def open_openerp_namespace():
67     # See comment for open_openerp_namespace.
68     if openerp.conf.deprecation.open_openerp_namespace:
69         for k, v in list(sys.modules.items()):
70             if k.startswith('openerp.') and sys.modules.get(k[8:]) is None:
71                 sys.modules[k[8:]] = v
72
73
74 def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=None, report=None):
75     """Migrates+Updates or Installs all module nodes from ``graph``
76        :param graph: graph of module nodes to load
77        :param status: status dictionary for keeping track of progress
78        :param perform_checks: whether module descriptors should be checked for validity (prints warnings
79                               for same cases, and even raise osv_except if certificate is invalid)
80        :param skip_modules: optional list of module names (packages) which have previously been loaded and can be skipped
81        :return: list of modules that were installed or updated
82     """
83     def process_sql_file(cr, fp):
84         queries = fp.read().split(';')
85         for query in queries:
86             new_query = ' '.join(query.split())
87             if new_query:
88                 cr.execute(new_query)
89
90     load_init_xml = lambda *args: _load_data(cr, *args, kind='init_xml')
91     load_update_xml = lambda *args: _load_data(cr, *args, kind='update_xml')
92     load_demo_xml = lambda *args: _load_data(cr, *args, kind='demo_xml')
93     load_data = lambda *args: _load_data(cr, *args, kind='data')
94     load_demo = lambda *args: _load_data(cr, *args, kind='demo')
95
96     def load_test(module_name, idref, mode):
97         cr.commit()
98         try:
99             threading.currentThread().testing = True
100             _load_data(cr, module_name, idref, mode, 'test')
101             return True
102         except Exception, e:
103             _logger.error(
104                 'module %s: an exception occurred in a test', module_name)
105             return False
106         finally:
107             threading.currentThread().testing = False
108             if tools.config.options['test_commit']:
109                 cr.commit()
110             else:
111                 cr.rollback()
112
113     def _load_data(cr, module_name, idref, mode, kind):
114         """
115
116         kind: data, demo, test, init_xml, update_xml, demo_xml.
117
118         noupdate is False, unless it is demo data or it is csv data in
119         init mode.
120
121         """
122         for filename in package.data[kind]:
123             _logger.info("module %s: loading %s", module_name, filename)
124             _, ext = os.path.splitext(filename)
125             pathname = os.path.join(module_name, filename)
126             fp = tools.file_open(pathname)
127             noupdate = False
128             if kind in ('demo', 'demo_xml'):
129                 noupdate = True
130             try:
131                 if ext == '.csv':
132                     if kind in ('init', 'init_xml'):
133                         noupdate = True
134                     tools.convert_csv_import(cr, module_name, pathname, fp.read(), idref, mode, noupdate)
135                 elif ext == '.sql':
136                     process_sql_file(cr, fp)
137                 elif ext == '.yml':
138                     tools.convert_yaml_import(cr, module_name, fp, idref, mode, noupdate, report)
139                 else:
140                     tools.convert_xml_import(cr, module_name, fp, idref, mode, noupdate, report)
141             finally:
142                 fp.close()
143
144     if status is None:
145         status = {}
146
147     processed_modules = []
148     loaded_modules = []
149     pool = pooler.get_pool(cr.dbname)
150     migrations = openerp.modules.migration.MigrationManager(cr, graph)
151     _logger.debug('loading %d packages...', len(graph))
152
153     # get db timestamp
154     cr.execute("select now()::timestamp")
155     dt_before_load = cr.fetchone()[0]
156
157     # register, instantiate and initialize models for each modules
158     for index, package in enumerate(graph):
159         module_name = package.name
160         module_id = package.id
161
162         if skip_modules and module_name in skip_modules:
163             continue
164
165         _logger.info('module %s: loading objects', package.name)
166         migrations.migrate_module(package, 'pre')
167         load_openerp_module(package.name)
168
169         models = pool.load(cr, package)
170         loaded_modules.append(package.name)
171         if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
172             init_module_models(cr, package.name, models)
173
174         status['progress'] = float(index) / len(graph)
175
176         # Can't put this line out of the loop: ir.module.module will be
177         # registered by init_module_models() above.
178         modobj = pool.get('ir.module.module')
179
180         if perform_checks:
181             modobj.check(cr, 1, [module_id])
182
183         idref = {}
184
185         mode = 'update'
186         if hasattr(package, 'init') or package.state == 'to install':
187             mode = 'init'
188
189         if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
190             if package.state=='to upgrade':
191                 # upgrading the module information
192                 modobj.write(cr, 1, [module_id], modobj.get_values_from_terp(package.data))
193             load_init_xml(module_name, idref, mode)
194             load_update_xml(module_name, idref, mode)
195             load_data(module_name, idref, mode)
196             if hasattr(package, 'demo') or (package.dbdemo and package.state != 'installed'):
197                 status['progress'] = (index + 0.75) / len(graph)
198                 load_demo_xml(module_name, idref, mode)
199                 load_demo(module_name, idref, mode)
200                 cr.execute('update ir_module_module set demo=%s where id=%s', (True, module_id))
201
202                 # launch tests only in demo mode, as most tests will depend
203                 # on demo data. Other tests can be added into the regular
204                 # 'data' section, but should probably not alter the data,
205                 # as there is no rollback.
206                 if tools.config.options['test_enable']:
207                     report.record_result(load_test(module_name, idref, mode))
208
209                     # Run the `fast_suite` and `checks` tests given by the module.
210                     if module_name == 'base':
211                         # Also run the core tests after the database is created.
212                         report.record_result(openerp.modules.module.run_unit_tests('openerp'))
213                     report.record_result(openerp.modules.module.run_unit_tests(module_name))
214
215             processed_modules.append(package.name)
216
217             migrations.migrate_module(package, 'post')
218
219             ver = release.major_version + '.' + package.data['version']
220             # Set new modules and dependencies
221             modobj.write(cr, 1, [module_id], {'state': 'installed', 'latest_version': ver})
222             # Update translations for all installed languages
223             modobj.update_translations(cr, 1, [module_id], None)
224
225             package.state = 'installed'
226             for kind in ('init', 'demo', 'update'):
227                 if hasattr(package, kind):
228                     delattr(package, kind)
229
230         cr.commit()
231
232     # mark new res_log records as read
233     cr.execute("update res_log set read=True where create_date >= %s", (dt_before_load,))
234
235     cr.commit()
236
237     return loaded_modules, processed_modules
238
239 def _check_module_names(cr, module_names):
240     mod_names = set(module_names)
241     if 'base' in mod_names:
242         # ignore dummy 'all' module
243         if 'all' in mod_names:
244             mod_names.remove('all')
245     if mod_names:
246         cr.execute("SELECT count(id) AS count FROM ir_module_module WHERE name in %s", (tuple(mod_names),))
247         if cr.dictfetchone()['count'] != len(mod_names):
248             # find out what module name(s) are incorrect:
249             cr.execute("SELECT name FROM ir_module_module")
250             incorrect_names = mod_names.difference([x['name'] for x in cr.dictfetchall()])
251             _logger.warning('invalid module names, ignored: %s', ", ".join(incorrect_names))
252
253 def load_marked_modules(cr, graph, states, force, progressdict, report, loaded_modules):
254     """Loads modules marked with ``states``, adding them to ``graph`` and
255        ``loaded_modules`` and returns a list of installed/upgraded modules."""
256     processed_modules = []
257     while True:
258         cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(states),))
259         module_list = [name for (name,) in cr.fetchall() if name not in graph]
260         new_modules_in_graph = graph.add_modules(cr, module_list, force)
261         _logger.debug('Updating graph with %d more modules', len(module_list))
262         loaded, processed = load_module_graph(cr, graph, progressdict, report=report, skip_modules=loaded_modules)
263         processed_modules.extend(processed)
264         loaded_modules.extend(loaded)
265         if not processed: break
266     return processed_modules
267
268
269 def load_modules(db, force_demo=False, status=None, update_module=False):
270     # TODO status['progress'] reporting is broken: used twice (and reset each
271     # time to zero) in load_module_graph, not fine-grained enough.
272     # It should be a method exposed by the pool.
273     initialize_sys_path()
274
275     open_openerp_namespace()
276
277     force = []
278     if force_demo:
279         force.append('demo')
280
281     cr = db.cursor()
282     try:
283         if not openerp.modules.db.is_initialized(cr):
284             _logger.info("init db")
285             openerp.modules.db.initialize(cr)
286             tools.config["init"]["all"] = 1
287             tools.config['update']['all'] = 1
288             if not tools.config['without_demo']:
289                 tools.config["demo"]['all'] = 1
290
291         # This is a brand new pool, just created in pooler.get_db_and_pool()
292         pool = pooler.get_pool(cr.dbname)
293
294         if 'base' in tools.config['update'] or 'all' in tools.config['update']:
295             cr.execute("update ir_module_module set state=%s where name=%s and state=%s", ('to upgrade', 'base', 'installed'))
296
297         # STEP 1: LOAD BASE (must be done before module dependencies can be computed for later steps) 
298         graph = openerp.modules.graph.Graph()
299         graph.add_module(cr, 'base', force)
300         if not graph:
301             _logger.critical('module base cannot be loaded! (hint: verify addons-path)')
302             raise osv.osv.except_osv(_('Could not load base module'), _('module base cannot be loaded! (hint: verify addons-path)'))
303
304         # processed_modules: for cleanup step after install
305         # loaded_modules: to avoid double loading
306         report = assertion_report.assertion_report()
307         loaded_modules, processed_modules = load_module_graph(cr, graph, status, perform_checks=(not update_module), report=report)
308
309         if tools.config['load_language']:
310             for lang in tools.config['load_language'].split(','):
311                 tools.load_language(cr, lang)
312
313         # STEP 2: Mark other modules to be loaded/updated
314         if update_module:
315             modobj = pool.get('ir.module.module')
316             if ('base' in tools.config['init']) or ('base' in tools.config['update']):
317                 _logger.info('updating modules list')
318                 modobj.update_list(cr, 1)
319
320             _check_module_names(cr, itertools.chain(tools.config['init'].keys(), tools.config['update'].keys()))
321
322             mods = [k for k in tools.config['init'] if tools.config['init'][k]]
323             if mods:
324                 ids = modobj.search(cr, 1, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
325                 if ids:
326                     modobj.button_install(cr, 1, ids)
327
328             mods = [k for k in tools.config['update'] if tools.config['update'][k]]
329             if mods:
330                 ids = modobj.search(cr, 1, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
331                 if ids:
332                     modobj.button_upgrade(cr, 1, ids)
333
334             cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
335
336
337         # STEP 3: Load marked modules (skipping base which was done in STEP 1)
338         # IMPORTANT: this is done in two parts, first loading all installed or
339         #            partially installed modules (i.e. installed/to upgrade), to
340         #            offer a consistent system to the second part: installing
341         #            newly selected modules.
342         states_to_load = ['installed', 'to upgrade']
343         processed = load_marked_modules(cr, graph, states_to_load, force, status, report, loaded_modules)
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)
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, 1, model['model'], {})
354
355         # STEP 4: Finish and cleanup
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('Model %s (%s) has no access rules!', model, name)
362
363             # Temporary warning while we remove access rights on osv_memory objects, as they have
364             # been replaced by owner-only access rights
365             cr.execute("""select distinct mod.model, mod.name from ir_model_access acc, ir_model mod where acc.model_id = mod.id""")
366             for (model, name) in cr.fetchall():
367                 model_obj = pool.get(model)
368                 if model_obj and model_obj.is_transient():
369                     _logger.warning('The transient model %s (%s) should not have explicit access rules!', model, name)
370
371             cr.execute("SELECT model from ir_model")
372             for (model,) in cr.fetchall():
373                 obj = pool.get(model)
374                 if obj:
375                     obj._check_removed_columns(cr, log=True)
376                 else:
377                     _logger.warning("Model %s is declared but cannot be loaded! (Perhaps a module was partially removed or renamed)", model)
378
379             # Cleanup orphan records
380             pool.get('ir.model.data')._process_end(cr, 1, processed_modules)
381
382         for kind in ('init', 'demo', 'update'):
383             tools.config[kind] = {}
384
385         cr.commit()
386         if update_module:
387             # Remove records referenced from ir_model_data for modules to be
388             # removed (and removed the references from ir_model_data).
389             cr.execute("select id,name from ir_module_module where state=%s", ('to remove',))
390             for mod_id, mod_name in cr.fetchall():
391                 cr.execute('select model,res_id from ir_model_data where noupdate=%s and module=%s order by id desc', (False, mod_name,))
392                 for rmod, rid in cr.fetchall():
393                     uid = 1
394                     rmod_module= pool.get(rmod)
395                     if rmod_module:
396                         # TODO group by module so that we can delete multiple ids in a call
397                         rmod_module.unlink(cr, uid, [rid])
398                     else:
399                         _logger.error('Could not locate %s to remove res=%d' % (rmod,rid))
400                 cr.execute('delete from ir_model_data where noupdate=%s and module=%s', (False, mod_name,))
401                 cr.commit()
402
403             # Remove menu items that are not referenced by any of other
404             # (child) menu item, ir_values, or ir_model_data.
405             # This code could be a method of ir_ui_menu.
406             # TODO: remove menu without actions of children
407             while True:
408                 cr.execute('''delete from
409                         ir_ui_menu
410                     where
411                         (id not IN (select parent_id from ir_ui_menu where parent_id is not null))
412                     and
413                         (id not IN (select res_id from ir_values where model='ir.ui.menu'))
414                     and
415                         (id not IN (select res_id from ir_model_data where model='ir.ui.menu'))''')
416                 cr.commit()
417                 if not cr.rowcount:
418                     break
419                 else:
420                     _logger.info('removed %d unused menus', cr.rowcount)
421
422             # Pretend that modules to be removed are actually uninstalled.
423             cr.execute("update ir_module_module set state=%s where state=%s", ('uninstalled', 'to remove',))
424             cr.commit()
425
426         if report.failures:
427             _logger.error('At least one test failed when loading the modules.')
428         else:
429             _logger.info('Modules loaded.')
430     finally:
431         cr.close()
432
433
434 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: