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