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