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 load_openerp_module, init_module_models
63 _logger = logging.getLogger(__name__)
65 def open_openerp_namespace():
66 # See comment for open_openerp_namespace.
67 if openerp.conf.deprecation.open_openerp_namespace:
68 for k, v in list(sys.modules.items()):
69 if k.startswith('openerp.') and sys.modules.get(k[8:]) is None:
70 sys.modules[k[8:]] = v
73 def load_module_graph(cr, graph, status=None, perform_checks=True, skip_modules=None, report=None):
74 """Migrates+Updates or Installs all module nodes from ``graph``
75 :param graph: graph of module nodes to load
76 :param status: status dictionary for keeping track of progress
77 :param perform_checks: whether module descriptors should be checked for validity (prints warnings
78 for same cases, and even raise osv_except if certificate is invalid)
79 :param skip_modules: optional list of module names (packages) which have previously been loaded and can be skipped
80 :return: list of modules that were installed or updated
82 def process_sql_file(cr, fp):
83 queries = fp.read().split(';')
85 new_query = ' '.join(query.split())
89 load_init_xml = lambda *args: _load_data(cr, *args, kind='init_xml')
90 load_update_xml = lambda *args: _load_data(cr, *args, kind='update_xml')
91 load_demo_xml = lambda *args: _load_data(cr, *args, kind='demo_xml')
92 load_data = lambda *args: _load_data(cr, *args, kind='data')
93 load_demo = lambda *args: _load_data(cr, *args, kind='demo')
95 def load_test(module_name, idref, mode):
97 if not tools.config.options['test_disable']:
99 threading.currentThread().testing = True
100 _load_data(cr, module_name, idref, mode, 'test')
103 'Tests failed to execute in module %s', module_name)
105 threading.currentThread().testing = False
106 if tools.config.options['test_commit']:
111 def _load_data(cr, module_name, idref, mode, kind):
114 kind: data, demo, test, init_xml, update_xml, demo_xml.
116 noupdate is False, unless it is demo data or it is csv data in
120 for filename in package.data[kind]:
121 _logger.info("module %s: loading %s", module_name, filename)
122 _, ext = os.path.splitext(filename)
123 pathname = os.path.join(module_name, filename)
124 fp = tools.file_open(pathname)
126 if kind in ('demo', 'demo_xml'):
130 if kind in ('init', 'init_xml'):
132 tools.convert_csv_import(cr, module_name, pathname, fp.read(), idref, mode, noupdate)
134 process_sql_file(cr, fp)
136 tools.convert_yaml_import(cr, module_name, fp, idref, mode, noupdate)
138 tools.convert_xml_import(cr, module_name, fp, idref, mode, noupdate, report)
145 processed_modules = []
147 pool = pooler.get_pool(cr.dbname)
148 migrations = openerp.modules.migration.MigrationManager(cr, graph)
149 _logger.debug('loading %d packages...', len(graph))
152 cr.execute("select now()::timestamp")
153 dt_before_load = cr.fetchone()[0]
155 # register, instantiate and initialize models for each modules
156 for index, package in enumerate(graph):
157 module_name = package.name
158 module_id = package.id
160 if skip_modules and module_name in skip_modules:
163 _logger.info('module %s: loading objects', package.name)
164 migrations.migrate_module(package, 'pre')
165 load_openerp_module(package.name)
167 models = pool.load(cr, package)
168 loaded_modules.append(package.name)
169 if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
170 init_module_models(cr, package.name, models)
172 status['progress'] = float(index) / len(graph)
174 # Can't put this line out of the loop: ir.module.module will be
175 # registered by init_module_models() above.
176 modobj = pool.get('ir.module.module')
179 modobj.check(cr, 1, [module_id])
184 if hasattr(package, 'init') or package.state == 'to install':
187 if hasattr(package, 'init') or hasattr(package, 'update') or package.state in ('to install', 'to upgrade'):
188 if package.state=='to upgrade':
189 # upgrading the module information
190 modobj.write(cr, 1, [module_id], modobj.get_values_from_terp(package.data))
191 load_init_xml(module_name, idref, mode)
192 load_update_xml(module_name, idref, mode)
193 load_data(module_name, idref, mode)
194 if hasattr(package, 'demo') or (package.dbdemo and package.state != 'installed'):
195 status['progress'] = (index + 0.75) / len(graph)
196 load_demo_xml(module_name, idref, mode)
197 load_demo(module_name, idref, mode)
198 cr.execute('update ir_module_module set demo=%s where id=%s', (True, module_id))
200 # launch tests only in demo mode, as most tests will depend
201 # on demo data. Other tests can be added into the regular
202 # 'data' section, but should probably not alter the data,
203 # as there is no rollback.
204 load_test(module_name, idref, mode)
206 # Run the `fast_suite` and `checks` tests given by the module.
207 if module_name == 'base':
208 # Also run the core tests after the dabase is created.
209 openerp.modules.module.run_unit_tests('openerp')
210 openerp.modules.module.run_unit_tests(module_name)
212 processed_modules.append(package.name)
214 migrations.migrate_module(package, 'post')
216 ver = release.major_version + '.' + package.data['version']
217 # Set new modules and dependencies
218 modobj.write(cr, 1, [module_id], {'state': 'installed', 'latest_version': ver})
219 # Update translations for all installed languages
220 modobj.update_translations(cr, 1, [module_id], None)
222 package.state = 'installed'
223 for kind in ('init', 'demo', 'update'):
224 if hasattr(package, kind):
225 delattr(package, kind)
229 # mark new res_log records as read
230 cr.execute("update res_log set read=True where create_date >= %s", (dt_before_load,))
234 return loaded_modules, processed_modules
236 def _check_module_names(cr, module_names):
237 mod_names = set(module_names)
238 if 'base' in mod_names:
239 # ignore dummy 'all' module
240 if 'all' in mod_names:
241 mod_names.remove('all')
243 cr.execute("SELECT count(id) AS count FROM ir_module_module WHERE name in %s", (tuple(mod_names),))
244 if cr.dictfetchone()['count'] != len(mod_names):
245 # find out what module name(s) are incorrect:
246 cr.execute("SELECT name FROM ir_module_module")
247 incorrect_names = mod_names.difference([x['name'] for x in cr.dictfetchall()])
248 _logger.warning('invalid module names, ignored: %s', ", ".join(incorrect_names))
250 def load_marked_modules(cr, graph, states, force, progressdict, report, loaded_modules):
251 """Loads modules marked with ``states``, adding them to ``graph`` and
252 ``loaded_modules`` and returns a list of installed/upgraded modules."""
253 processed_modules = []
255 cr.execute("SELECT name from ir_module_module WHERE state IN %s" ,(tuple(states),))
256 module_list = [name for (name,) in cr.fetchall() if name not in graph]
257 new_modules_in_graph = graph.add_modules(cr, module_list, force)
258 _logger.debug('Updating graph with %d more modules', len(module_list))
259 loaded, processed = load_module_graph(cr, graph, progressdict, report=report, skip_modules=loaded_modules)
260 processed_modules.extend(processed)
261 loaded_modules.extend(loaded)
262 if not processed: break
263 return processed_modules
266 def load_modules(db, force_demo=False, status=None, update_module=False):
267 # TODO status['progress'] reporting is broken: used twice (and reset each
268 # time to zero) in load_module_graph, not fine-grained enough.
269 # It should be a method exposed by the pool.
270 initialize_sys_path()
272 open_openerp_namespace()
280 if not openerp.modules.db.is_initialized(cr):
281 _logger.info("init db")
282 openerp.modules.db.initialize(cr)
283 tools.config["init"]["all"] = 1
284 tools.config['update']['all'] = 1
285 if not tools.config['without_demo']:
286 tools.config["demo"]['all'] = 1
288 # This is a brand new pool, just created in pooler.get_db_and_pool()
289 pool = pooler.get_pool(cr.dbname)
291 report = tools.assertion_report()
292 if 'base' in tools.config['update'] or 'all' in tools.config['update']:
293 cr.execute("update ir_module_module set state=%s where name=%s and state=%s", ('to upgrade', 'base', 'installed'))
295 # STEP 1: LOAD BASE (must be done before module dependencies can be computed for later steps)
296 graph = openerp.modules.graph.Graph()
297 graph.add_module(cr, 'base', force)
299 _logger.critical('module base cannot be loaded! (hint: verify addons-path)')
300 raise osv.osv.except_osv(_('Could not load base module'), _('module base cannot be loaded! (hint: verify addons-path)'))
302 # processed_modules: for cleanup step after install
303 # loaded_modules: to avoid double loading
304 loaded_modules, processed_modules = load_module_graph(cr, graph, status, perform_checks=(not update_module), report=report)
306 if tools.config['load_language']:
307 for lang in tools.config['load_language'].split(','):
308 tools.load_language(cr, lang)
310 # STEP 2: Mark other modules to be loaded/updated
312 modobj = pool.get('ir.module.module')
313 if ('base' in tools.config['init']) or ('base' in tools.config['update']):
314 _logger.info('updating modules list')
315 modobj.update_list(cr, 1)
317 _check_module_names(cr, itertools.chain(tools.config['init'].keys(), tools.config['update'].keys()))
319 mods = [k for k in tools.config['init'] if tools.config['init'][k]]
321 ids = modobj.search(cr, 1, ['&', ('state', '=', 'uninstalled'), ('name', 'in', mods)])
323 modobj.button_install(cr, 1, ids)
325 mods = [k for k in tools.config['update'] if tools.config['update'][k]]
327 ids = modobj.search(cr, 1, ['&', ('state', '=', 'installed'), ('name', 'in', mods)])
329 modobj.button_upgrade(cr, 1, ids)
331 cr.execute("update ir_module_module set state=%s where name=%s", ('installed', 'base'))
334 # STEP 3: Load marked modules (skipping base which was done in STEP 1)
335 # IMPORTANT: this is done in two parts, first loading all installed or
336 # partially installed modules (i.e. installed/to upgrade), to
337 # offer a consistent system to the second part: installing
338 # newly selected modules.
339 states_to_load = ['installed', 'to upgrade']
340 processed = load_marked_modules(cr, graph, states_to_load, force, status, report, loaded_modules)
341 processed_modules.extend(processed)
343 states_to_load = ['to install']
344 processed = load_marked_modules(cr, graph, states_to_load, force, status, report, loaded_modules)
345 processed_modules.extend(processed)
348 cr.execute('select model from ir_model where state=%s', ('manual',))
349 for model in cr.dictfetchall():
350 pool.get('ir.model').instanciate(cr, 1, model['model'], {})
352 # STEP 4: Finish and cleanup
353 if processed_modules:
354 cr.execute("""select model,name from ir_model where id NOT IN (select distinct model_id from ir_model_access)""")
355 for (model, name) in cr.fetchall():
356 model_obj = pool.get(model)
357 if model_obj and not model_obj.is_transient():
358 _logger.warning('Model %s (%s) has no access rules!', model, name)
360 # Temporary warning while we remove access rights on osv_memory objects, as they have
361 # been replaced by owner-only access rights
362 cr.execute("""select distinct mod.model, mod.name from ir_model_access acc, ir_model mod where acc.model_id = mod.id""")
363 for (model, name) in cr.fetchall():
364 model_obj = pool.get(model)
365 if model_obj and model_obj.is_transient():
366 _logger.warning('The transient model %s (%s) should not have explicit access rules!', model, name)
368 cr.execute("SELECT model from ir_model")
369 for (model,) in cr.fetchall():
370 obj = pool.get(model)
372 obj._check_removed_columns(cr, log=True)
374 _logger.warning("Model %s is declared but cannot be loaded! (Perhaps a module was partially removed or renamed)", model)
376 # Cleanup orphan records
377 pool.get('ir.model.data')._process_end(cr, 1, processed_modules)
379 for kind in ('init', 'demo', 'update'):
380 tools.config[kind] = {}
384 # Remove records referenced from ir_model_data for modules to be
385 # removed (and removed the references from ir_model_data).
386 cr.execute("select id,name from ir_module_module where state=%s", ('to remove',))
387 for mod_id, mod_name in cr.fetchall():
388 cr.execute('select model,res_id from ir_model_data where noupdate=%s and module=%s order by id desc', (False, mod_name,))
389 for rmod, rid in cr.fetchall():
391 rmod_module= pool.get(rmod)
393 # TODO group by module so that we can delete multiple ids in a call
394 rmod_module.unlink(cr, uid, [rid])
396 _logger.error('Could not locate %s to remove res=%d' % (rmod,rid))
397 cr.execute('delete from ir_model_data where noupdate=%s and module=%s', (False, mod_name,))
400 # Remove menu items that are not referenced by any of other
401 # (child) menu item, ir_values, or ir_model_data.
402 # This code could be a method of ir_ui_menu.
403 # TODO: remove menu without actions of children
405 cr.execute('''delete from
408 (id not IN (select parent_id from ir_ui_menu where parent_id is not null))
410 (id not IN (select res_id from ir_values where model='ir.ui.menu'))
412 (id not IN (select res_id from ir_model_data where model='ir.ui.menu'))''')
417 _logger.info('removed %d unused menus', cr.rowcount)
419 # Pretend that modules to be removed are actually uninstalled.
420 cr.execute("update ir_module_module set state=%s where state=%s", ('uninstalled', 'to remove',))
423 _logger.info('Modules loaded.')
428 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: