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-2011 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.
38 from cStringIO import StringIO
39 from os.path import join as opj
40 from zipfile import PyZipFile, ZIP_DEFLATED
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
54 from openerp.tools.safe_eval import safe_eval as eval
55 from openerp.tools.translate import _
56 from openerp.modules.module import \
57 get_modules, get_modules_with_version, \
58 load_information_from_description_file, \
59 get_module_resource, zip_directory, \
60 get_module_path, initialize_sys_path, \
61 register_module_classes, init_module_models
63 logger = netsvc.Logger()
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
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
83 logger = logging.getLogger('init.load')
84 def process_sql_file(cr, fp):
85 queries = fp.read().split(';')
87 new_query = ' '.join(query.split())
91 load_init_xml = lambda *args: _load_data(cr, *args, kind='init_xml')
92 load_update_xml = lambda *args: _load_data(cr, *args, kind='update_xml')
93 load_demo_xml = lambda *args: _load_data(cr, *args, kind='demo_xml')
94 load_data = lambda *args: _load_data(cr, *args, kind='data')
95 load_demo = lambda *args: _load_data(cr, *args, kind='demo')
97 def load_test(module_name, idref, mode):
99 if not tools.config.options['test_disable']:
101 threading.currentThread().testing = True
102 _load_data(cr, module_name, idref, mode, 'test')
104 logging.getLogger('init.test').exception(
105 'Tests failed to execute in module %s', module_name)
107 threading.currentThread().testing = False
108 if tools.config.options['test_commit']:
113 def _load_data(cr, module_name, idref, mode, kind):
116 kind: data, demo, test, init_xml, update_xml, demo_xml.
118 noupdate is False, unless it is demo data or it is csv data in
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)
128 if kind in ('demo', 'demo_xml'):
132 if kind in ('init', 'init_xml'):
134 tools.convert_csv_import(cr, module_name, pathname, fp.read(), idref, mode, noupdate)
136 process_sql_file(cr, fp)
138 tools.convert_yaml_import(cr, module_name, fp, idref, mode, noupdate)
140 tools.convert_xml_import(cr, module_name, fp, idref, mode, noupdate, report)
147 processed_modules = []
149 pool = pooler.get_pool(cr.dbname)
150 migrations = openerp.modules.migration.MigrationManager(cr, graph)
151 logger.debug('loading %d packages...', len(graph))
154 cr.execute("select now()::timestamp")
155 dt_before_load = cr.fetchone()[0]
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
162 if skip_modules and module_name in skip_modules:
165 logger.info('module %s: loading objects', package.name)
166 migrations.migrate_module(package, 'pre')
167 register_module_classes(package.name)
168 models = pool.load(cr, package)
169 loaded_modules.append(package.name)
170 if package.state in ('to install', 'to upgrade'):
171 init_module_models(cr, package.name, models)
173 status['progress'] = float(index) / len(graph)
175 # Can't put this line out of the loop: ir.module.module will be
176 # registered by init_module_models() above.
177 modobj = pool.get('ir.module.module')
180 modobj.check(cr, 1, [module_id])
184 if package.state == 'to install':
189 if 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 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))
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 load_test(module_name, idref, mode)
208 processed_modules.append(package.name)
210 migrations.migrate_module(package, 'post')
212 ver = release.major_version + '.' + package.data['version']
213 # Set new modules and dependencies
214 modobj.write(cr, 1, [module_id], {'state': 'installed', 'latest_version': ver})
215 # Update translations for all installed languages
216 modobj.update_translations(cr, 1, [module_id], None)
218 package.state = 'installed'
222 # mark new res_log records as read
223 cr.execute("update res_log set read=True where create_date >= %s", (dt_before_load,))
227 return loaded_modules, processed_modules
229 def _check_module_names(cr, module_names):
230 mod_names = set(module_names)
231 if 'base' in mod_names:
232 # ignore dummy 'all' module
233 if 'all' in mod_names:
234 mod_names.remove('all')
236 cr.execute("SELECT count(id) AS count FROM ir_module_module WHERE name in %s", (tuple(mod_names),))
237 if cr.dictfetchone()['count'] != len(mod_names):
238 # find out what module name(s) are incorrect:
239 cr.execute("SELECT name FROM ir_module_module")
240 incorrect_names = mod_names.difference([x['name'] for x in cr.dictfetchall()])
241 logging.getLogger('init').warning('invalid module names, ignored: %s', ", ".join(incorrect_names))
243 def load_marked_modules(cr, graph, states, force, progressdict, report, loaded_modules):
244 """Loads modules marked with ``states``, adding them to ``graph`` and
245 ``loaded_modules`` and returns a list of installed/upgraded modules."""
246 processed_modules = []
248 cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(states),))
249 module_list = [name for (name,) in cr.fetchall() if name not in graph]
250 new_modules_in_graph = graph.add_modules(cr, module_list, force)
251 logger.notifyChannel('init', netsvc.LOG_DEBUG, 'Updating graph with %d more modules' % (len(module_list)))
252 loaded, processed = load_module_graph(cr, graph, progressdict, report=report, skip_modules=loaded_modules)
253 processed_modules.extend(processed)
254 loaded_modules.extend(loaded)
255 if not processed: break
256 return processed_modules
259 def load_modules(db, force_demo=False, status=None, update_module=False):
260 # TODO status['progress'] reporting is broken: used twice (and reset each
261 # time to zero) in load_module_graph, not fine-grained enough.
262 # It should be a method exposed by the pool.
263 initialize_sys_path()
265 open_openerp_namespace()
273 if not openerp.modules.db.is_initialized(cr):
274 logger.notifyChannel("init", netsvc.LOG_INFO, "init db")
275 openerp.modules.db.initialize(cr)
278 # This is a brand new pool, just created in pooler.get_db_and_pool()
279 pool = pooler.get_pool(cr.dbname)
281 report = tools.assertion_report()
282 if 'base' in tools.config['update'] or 'all' in tools.config['update']:
283 cr.execute("update ir_module_module set state=%s where name=%s and state=%s", ('to upgrade', 'base', 'installed'))
285 # STEP 1: LOAD BASE (must be done before module dependencies can be computed for later steps)
286 graph = openerp.modules.graph.Graph()
287 graph.add_module(cr, 'base', force_demo)
289 logger.notifyChannel('init', netsvc.LOG_CRITICAL, 'module base cannot be loaded! (hint: verify addons-path)')
290 raise osv.osv.except_osv(_('Could not load base module'), _('module base cannot be loaded! (hint: verify addons-path)'))
292 # processed_modules: for cleanup step after install
293 # loaded_modules: to avoid double loading
294 # After load_module_graph(), 'base' has been installed or updated and its state is 'installed'.
295 loaded_modules, processed_modules = load_module_graph(cr, graph, status, report=report)
297 if tools.config['load_language']:
298 for lang in tools.config['load_language'].split(','):
299 tools.load_language(cr, lang)
301 # STEP 2: Mark other modules to be loaded/updated
302 # This is a one-shot use of tools.config[init|update] from the command line
303 # arguments. It is directly cleared to not interfer with later create/update
306 modobj = pool.get('ir.module.module')
307 if ('base' in tools.config['init']) or ('base' in tools.config['update']) \
308 or ('all' in tools.config['init']) or ('all' in tools.config['update']):
309 logger.notifyChannel('init', netsvc.LOG_INFO, 'updating modules list')
310 modobj.update_list(cr, 1)
312 if 'all' in tools.config['init']:
313 ids = modobj.search(cr, 1, [])
314 tools.config['init'] = dict.fromkeys([m['name'] for m in modobj.read(cr, 1, ids, ['name'])], 1)
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] and k not in ('base', 'all')]
319 ids = modobj.search(cr, 1, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
321 modobj.button_install(cr, 1, ids) # goes from 'uninstalled' to 'to install'
323 mods = [k for k in tools.config['update'] if tools.config['update'][k] and k not in ('base', 'all')]
324 ids = modobj.search(cr, 1, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
326 modobj.button_upgrade(cr, 1, ids) # goes from 'installed' to 'to upgrade'
328 for kind in ('init', 'demo', 'update'):
329 tools.config[kind] = {}
331 # STEP 3: Load marked modules (skipping base which was done in STEP 1)
332 # IMPORTANT: this is done in two parts, first loading all installed or
333 # partially installed modules (i.e. installed/to upgrade), to
334 # offer a consistent system to the second part: installing
335 # newly selected modules.
336 states_to_load = ['installed', 'to upgrade']
337 processed = load_marked_modules(cr, graph, states_to_load, force, status, report, loaded_modules)
338 processed_modules.extend(processed)
340 states_to_load = ['to install']
341 processed = load_marked_modules(cr, graph, states_to_load, force, status, report, loaded_modules)
342 processed_modules.extend(processed)
345 cr.execute('select model from ir_model where state=%s', ('manual',))
346 for model in cr.dictfetchall():
347 pool.get('ir.model').instanciate(cr, 1, model['model'], {})
349 # STEP 4: Finish and cleanup
350 if processed_modules:
351 cr.execute("""select model,name from ir_model where id NOT IN (select distinct model_id from ir_model_access)""")
352 for (model, name) in cr.fetchall():
353 model_obj = pool.get(model)
354 if model_obj and not model_obj.is_transient():
355 logger.notifyChannel('init', netsvc.LOG_WARNING, 'Model %s (%s) has no access rules!' % (model, name))
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.notifyChannel('init', netsvc.LOG_WARNING, 'The transient model %s (%s) should not have explicit access rules!' % (model, name))
365 cr.execute("SELECT model from ir_model")
366 for (model,) in cr.fetchall():
367 obj = pool.get(model)
369 obj._check_removed_columns(cr, log=True)
371 logger.notifyChannel('init', netsvc.LOG_WARNING, "Model %s is declared but cannot be loaded! (Perhaps a module was partially removed or renamed)" % model)
373 # Cleanup orphan records
374 pool.get('ir.model.data')._process_end(cr, 1, processed_modules)
376 if report.get_report():
377 logger.notifyChannel('init', netsvc.LOG_INFO, report)
381 # Remove records referenced from ir_model_data for modules to be
382 # removed (and removed the references from ir_model_data).
383 cr.execute("select id,name from ir_module_module where state=%s", ('to remove',))
384 for mod_id, mod_name in cr.fetchall():
385 cr.execute('select model,res_id from ir_model_data where noupdate=%s and module=%s order by id desc', (False, mod_name,))
386 for rmod, rid in cr.fetchall():
388 rmod_module= pool.get(rmod)
390 # TODO group by module so that we can delete multiple ids in a call
391 rmod_module.unlink(cr, uid, [rid])
393 logger.notifyChannel('init', netsvc.LOG_ERROR, 'Could not locate %s to remove res=%d' % (rmod,rid))
394 cr.execute('delete from ir_model_data where noupdate=%s and module=%s', (False, mod_name,))
397 # Remove menu items that are not referenced by any of other
398 # (child) menu item, ir_values, or ir_model_data.
399 # This code could be a method of ir_ui_menu.
400 # TODO: remove menu without actions of children
402 cr.execute('''delete from
405 (id not IN (select parent_id from ir_ui_menu where parent_id is not null))
407 (id not IN (select res_id from ir_values where model='ir.ui.menu'))
409 (id not IN (select res_id from ir_model_data where model='ir.ui.menu'))''')
414 logger.notifyChannel('init', netsvc.LOG_INFO, 'removed %d unused menus' % (cr.rowcount,))
416 # Pretend that modules to be removed are actually uninstalled.
417 cr.execute("update ir_module_module set state=%s where state=%s", ('uninstalled', 'to remove',))
423 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: