diff options
Diffstat (limited to 'openerp/addons/base/module/module.py')
| -rw-r--r-- | openerp/addons/base/module/module.py | 261 |
1 files changed, 176 insertions, 85 deletions
diff --git a/openerp/addons/base/module/module.py b/openerp/addons/base/module/module.py index a10b38cf586..47a4cfc8174 100644 --- a/openerp/addons/base/module/module.py +++ b/openerp/addons/base/module/module.py @@ -24,12 +24,22 @@ from docutils.transforms import Transform, writer_aux from docutils.writers.html4css1 import Writer import imp import logging +import os import re +import shutil +import tempfile import urllib +import urllib2 +import zipfile import zipimport +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO # NOQA + import openerp -from openerp import modules, pooler, release, tools, addons +from openerp import modules, pooler, tools, addons from openerp.modules.db import create_categories from openerp.tools.parse_version import parse_version from openerp.tools.translate import _ @@ -43,14 +53,29 @@ ACTION_DICT = { 'res_model': 'base.module.upgrade', 'target': 'new', 'type': 'ir.actions.act_window', - 'nodestroy':True, + 'nodestroy': True, } +def backup(path, raise_exception=True): + path = os.path.normpath(path) + if not os.path.exists(path): + if not raise_exception: + return None + raise OSError('path does not exists') + cnt = 1 + while True: + bck = '%s~%d' % (path, cnt) + if not os.path.exists(bck): + shutil.move(path, bck) + return bck + cnt += 1 + + class module_category(osv.osv): _name = "ir.module.category" _description = "Application" - def _module_nbr(self,cr,uid, ids, prop, unknow_none, context): + def _module_nbr(self, cr, uid, ids, prop, unknow_none, context): cr.execute('SELECT category_id, COUNT(*) \ FROM ir_module_module \ WHERE category_id IN %(ids)s \ @@ -58,7 +83,7 @@ class module_category(osv.osv): FROM ir_module_category \ WHERE parent_id IN %(ids)s) \ GROUP BY category_id', {'ids': tuple(ids)} - ) + ) result = dict(cr.fetchall()) for id in ids: cr.execute('select id from ir_module_category where parent_id=%s', (id,)) @@ -71,16 +96,16 @@ class module_category(osv.osv): 'parent_id': fields.many2one('ir.module.category', 'Parent Application', select=True), 'child_ids': fields.one2many('ir.module.category', 'parent_id', 'Child Applications'), 'module_nr': fields.function(_module_nbr, string='Number of Modules', type='integer'), - 'module_ids' : fields.one2many('ir.module.module', 'category_id', 'Modules'), - 'description' : fields.text("Description", translate=True), - 'sequence' : fields.integer('Sequence'), - 'visible' : fields.boolean('Visible'), + 'module_ids': fields.one2many('ir.module.module', 'category_id', 'Modules'), + 'description': fields.text("Description", translate=True), + 'sequence': fields.integer('Sequence'), + 'visible': fields.boolean('Visible'), 'xml_id': fields.function(osv.osv.get_external_id, type='char', size=128, string="External ID"), } _order = 'name' _defaults = { - 'visible' : 1, + 'visible': 1, } class MyFilterMessages(Transform): @@ -119,7 +144,6 @@ class module(osv.osv): info = {} try: info = modules.load_information_from_description_file(name) - info['version'] = release.major_version + '.' + info['version'] except Exception: _logger.debug('Error when trying to fetch informations for ' 'module %s', name, exc_info=True) @@ -134,9 +158,10 @@ class module(osv.osv): return res def _get_latest_version(self, cr, uid, ids, field_name=None, arg=None, context=None): - res = dict.fromkeys(ids, '') + default_version = modules.adapt_version('1.0') + res = dict.fromkeys(ids, default_version) for m in self.browse(cr, uid, ids): - res[m.id] = self.get_module_info(m.name).get('version', '') + res[m.id] = self.get_module_info(m.name).get('version', default_version) return res def _get_views(self, cr, uid, ids, field_name=None, arg=None, context=None): @@ -167,9 +192,11 @@ class module(osv.osv): continue # then, search and group ir.model.data records - imd_models = dict( [(m,[]) for m in dmodels]) - imd_ids = model_data_obj.search(cr,uid,[('module','=', module_rec.name), - ('model','in',tuple(dmodels))]) + imd_models = dict([(m, []) for m in dmodels]) + imd_ids = model_data_obj.search(cr, uid, [ + ('module', '=', module_rec.name), + ('model', 'in', tuple(dmodels)) + ]) for imd_res in model_data_obj.read(cr, uid, imd_ids, ['model', 'res_id'], context=context): imd_models[imd_res['model']].append(imd_res['res_id']) @@ -181,7 +208,7 @@ class module(osv.osv): view_ids = imd_models.get('ir.ui.view', []) for v in view_obj.browse(cr, uid, view_ids, context=context): aa = v.inherit_id and '* INHERIT ' or '' - res_mod_dic['views_by_module'].append(aa + v.name + '('+v.type+')') + res_mod_dic['views_by_module'].append('%s%s (%s)' % (aa, v.name, v.type)) report_ids = imd_models.get('ir.actions.report.xml', []) for rx in report_obj.browse(cr, uid, report_ids, context=context): @@ -191,15 +218,13 @@ class module(osv.osv): for um in menu_obj.browse(cr, uid, menu_ids, context=context): res_mod_dic['menus_by_module'].append(um.complete_name) except KeyError, e: - _logger.warning( - 'Data not found for items of %s', module_rec.name) + _logger.warning('Data not found for items of %s', module_rec.name) except AttributeError, e: - _logger.warning( - 'Data not found for items of %s %s', module_rec.name, str(e)) + _logger.warning('Data not found for items of %s %s', module_rec.name, str(e)) except Exception, e: - _logger.warning('Unknown error while fetching data of %s', - module_rec.name, exc_info=True) - for key, _ in res.iteritems(): + _logger.warning('Unknown error while fetching data of %s', module_rec.name, exc_info=True) + + for key in res.iterkeys(): for k, v in res[key].iteritems(): res[key][k] = "\n".join(sorted(v)) return res @@ -232,36 +257,34 @@ class module(osv.osv): # installed_version refer the latest version (the one on disk) # latest_version refer the installed version (the one in database) # published_version refer the version available on the repository - 'installed_version': fields.function(_get_latest_version, - string='Latest Version', type='char'), + 'installed_version': fields.function(_get_latest_version, string='Latest Version', type='char'), 'latest_version': fields.char('Installed Version', size=64, readonly=True), 'published_version': fields.char('Published Version', size=64, readonly=True), 'url': fields.char('URL', size=128, readonly=True), 'sequence': fields.integer('Sequence'), - 'dependencies_id': fields.one2many('ir.module.module.dependency', - 'module_id', 'Dependencies', readonly=True), + 'dependencies_id': fields.one2many('ir.module.module.dependency', 'module_id', 'Dependencies', readonly=True), 'auto_install': fields.boolean('Automatic Installation', - help='An auto-installable module is automatically installed by the ' - 'system when all its dependencies are satisfied. ' - 'If the module has no dependency, it is always installed.'), + help='An auto-installable module is automatically installed by the ' + 'system when all its dependencies are satisfied. ' + 'If the module has no dependency, it is always installed.'), 'state': fields.selection([ - ('uninstallable','Not Installable'), - ('uninstalled','Not Installed'), - ('installed','Installed'), - ('to upgrade','To be upgraded'), - ('to remove','To be removed'), - ('to install','To be installed') + ('uninstallable', 'Not Installable'), + ('uninstalled', 'Not Installed'), + ('installed', 'Installed'), + ('to upgrade', 'To be upgraded'), + ('to remove', 'To be removed'), + ('to install', 'To be installed') ], string='Status', readonly=True, select=True), 'demo': fields.boolean('Demo Data', readonly=True), 'license': fields.selection([ - ('GPL-2', 'GPL Version 2'), - ('GPL-2 or any later version', 'GPL-2 or later version'), - ('GPL-3', 'GPL Version 3'), - ('GPL-3 or any later version', 'GPL-3 or later version'), - ('AGPL-3', 'Affero GPL-3'), - ('Other OSI approved licence', 'Other OSI Approved Licence'), - ('Other proprietary', 'Other Proprietary') + ('GPL-2', 'GPL Version 2'), + ('GPL-2 or any later version', 'GPL-2 or later version'), + ('GPL-3', 'GPL Version 3'), + ('GPL-3 or any later version', 'GPL-3 or later version'), + ('AGPL-3', 'Affero GPL-3'), + ('Other OSI approved licence', 'Other OSI Approved Licence'), + ('Other proprietary', 'Other Proprietary') ], string='License', readonly=True), 'menus_by_module': fields.function(_get_views, string='Menus', type='text', multi="meta", store=True), 'reports_by_module': fields.function(_get_views, string='Reports', type='text', multi="meta", store=True), @@ -283,7 +306,7 @@ class module(osv.osv): return _('The name of the module must be unique !') _sql_constraints = [ - ('name_uniq', 'UNIQUE (name)',_name_uniq_msg ), + ('name_uniq', 'UNIQUE (name)', _name_uniq_msg), ] def unlink(self, cr, uid, ids, context=None): @@ -292,10 +315,9 @@ class module(osv.osv): if isinstance(ids, (int, long)): ids = [ids] mod_names = [] - for mod in self.read(cr, uid, ids, ['state','name'], context): + for mod in self.read(cr, uid, ids, ['state', 'name'], context): if mod['state'] in ('installed', 'to upgrade', 'to remove', 'to install'): - raise orm.except_orm(_('Error'), - _('You try to remove a module that is installed or will be installed')) + raise orm.except_orm(_('Error'), _('You try to remove a module that is installed or will be installed')) mod_names.append(mod['name']) #Removing the entry from ir_model_data #ids_meta = self.pool.get('ir.model.data').search(cr, uid, [('name', '=', 'module_meta_information'), ('module', 'in', mod_names)]) @@ -340,7 +362,7 @@ class module(osv.osv): raise orm.except_orm(_('Error'), msg % (module_name, e.args[0])) def state_update(self, cr, uid, ids, newstate, states_to_update, context=None, level=100): - if level<1: + if level < 1: raise orm.except_orm(_('Error'), _('Recursion error in modules dependencies !')) demo = False for module in self.browse(cr, uid, ids, context=context): @@ -348,9 +370,9 @@ class module(osv.osv): for dep in module.dependencies_id: if dep.state == 'unknown': raise orm.except_orm(_('Error'), _("You try to install module '%s' that depends on module '%s'.\nBut the latter module is not available in your system.") % (module.name, dep.name,)) - ids2 = self.search(cr, uid, [('name','=',dep.name)]) + ids2 = self.search(cr, uid, [('name', '=', dep.name)]) if dep.state != newstate: - mdemo = self.state_update(cr, uid, ids2, newstate, states_to_update, context, level-1,) or mdemo + mdemo = self.state_update(cr, uid, ids2, newstate, states_to_update, context, level - 1) or mdemo else: od = self.browse(cr, uid, ids2)[0] mdemo = od.demo or mdemo @@ -359,7 +381,7 @@ class module(osv.osv): if not module.dependencies_id: mdemo = module.demo if module.state in states_to_update: - self.write(cr, uid, [module.id], {'state': newstate, 'demo':mdemo}) + self.write(cr, uid, [module.id], {'state': newstate, 'demo': mdemo}) demo = demo or mdemo return demo @@ -403,7 +425,7 @@ class module(osv.osv): return self._button_immediate_function(cr, uid, ids, self.button_install, context=context) def button_install_cancel(self, cr, uid, ids, context=None): - self.write(cr, uid, ids, {'state': 'uninstalled', 'demo':False}) + self.write(cr, uid, ids, {'state': 'uninstalled', 'demo': False}) return True def module_uninstall(self, cr, uid, ids, context=None): @@ -421,12 +443,13 @@ class module(osv.osv): return True def downstream_dependencies(self, cr, uid, ids, known_dep_ids=None, - exclude_states=['uninstalled','uninstallable','to remove'], + exclude_states=['uninstalled', 'uninstallable', 'to remove'], context=None): """Return the ids of all modules that directly or indirectly depend on the given module `ids`, and that satisfy the `exclude_states` filter""" - if not ids: return [] + if not ids: + return [] known_dep_ids = set(known_dep_ids or []) cr.execute('''SELECT DISTINCT m.id FROM @@ -437,13 +460,13 @@ class module(osv.osv): d.name IN (SELECT name from ir_module_module where id in %s) AND m.state NOT IN %s AND m.id NOT IN %s ''', - (tuple(ids),tuple(exclude_states), tuple(known_dep_ids or ids))) + (tuple(ids), tuple(exclude_states), tuple(known_dep_ids or ids))) new_dep_ids = set([m[0] for m in cr.fetchall()]) missing_mod_ids = new_dep_ids - known_dep_ids known_dep_ids |= new_dep_ids if missing_mod_ids: known_dep_ids |= set(self.downstream_dependencies(cr, uid, list(missing_mod_ids), - known_dep_ids, exclude_states,context)) + known_dep_ids, exclude_states, context)) return list(known_dep_ids) def _button_immediate_function(self, cr, uid, ids, function, context=None): @@ -460,9 +483,9 @@ class module(osv.osv): menu_obj = self.pool.get('ir.ui.menu') menu_ids = menu_obj.search(cr, uid, [('parent_id', '=', False)], context=context) return { - 'type' : 'ir.actions.client', - 'tag' : 'reload', - 'params' : {'menu_id' : menu_ids and menu_ids[0] or False} + 'type': 'ir.actions.client', + 'tag': 'reload', + 'params': {'menu_id': menu_ids and menu_ids[0] or False} } def button_immediate_uninstall(self, cr, uid, ids, context=None): @@ -496,20 +519,19 @@ class module(osv.osv): self.update_list(cr, uid) i = 0 - while i<len(todo): + while i < len(todo): mod = todo[i] i += 1 - if mod.state not in ('installed','to upgrade'): - raise orm.except_orm(_('Error'), - _("Can not upgrade module '%s'. It is not installed.") % (mod.name,)) + if mod.state not in ('installed', 'to upgrade'): + raise orm.except_orm(_('Error'), _("Can not upgrade module '%s'. It is not installed.") % (mod.name,)) self.check_external_dependencies(mod.name, 'to upgrade') iids = depobj.search(cr, uid, [('name', '=', mod.name)], context=context) for dep in depobj.browse(cr, uid, iids, context=context): - if dep.module_id.state=='installed' and dep.module_id not in todo: + if dep.module_id.state == 'installed' and dep.module_id not in todo: todo.append(dep.module_id) ids = map(lambda x: x.id, todo) - self.write(cr, uid, ids, {'state':'to upgrade'}, context=context) + self.write(cr, uid, ids, {'state': 'to upgrade'}, context=context) to_install = [] for mod in todo: @@ -517,7 +539,7 @@ class module(osv.osv): if dep.state == 'unknown': raise orm.except_orm(_('Error'), _('You try to upgrade a module that depends on the module: %s.\nBut this module is not available in your system.') % (dep.name,)) if dep.state == 'uninstalled': - ids2 = self.search(cr, uid, [('name','=',dep.name)]) + ids2 = self.search(cr, uid, [('name', '=', dep.name)]) to_install.extend(ids2) self.button_install(cr, uid, to_install, context=context) @@ -550,8 +572,9 @@ class module(osv.osv): # update the list of available packages def update_list(self, cr, uid, context=None): - res = [0, 0] # [update, add] + res = [0, 0] # [update, add] + default_version = modules.adapt_version('1.0') known_mods = self.browse(cr, uid, self.search(cr, uid, [])) known_mods_names = dict([(m.name, m) for m in known_mods]) @@ -570,7 +593,7 @@ class module(osv.osv): updated_values[key] = values[key] if terp.get('installable', True) and mod.state == 'uninstallable': updated_values['state'] = 'uninstalled' - if parse_version(terp.get('version', '')) > parse_version(mod.latest_version or ''): + if parse_version(terp.get('version', default_version)) > parse_version(mod.latest_version or default_version): res[0] += 1 if updated_values: self.write(cr, uid, mod.id, updated_values) @@ -587,24 +610,32 @@ class module(osv.osv): self._update_dependencies(cr, uid, mod, terp.get('depends', [])) self._update_category(cr, uid, mod, terp.get('category', 'Uncategorized')) + # Trigger load_addons if new module have been discovered it exists on + # wsgi handlers, so they can react accordingly + if tuple(res) != (0, 0): + for handler in openerp.service.wsgi_server.module_handlers: + if hasattr(handler,'load_addons'): + handler.load_addons() + return res def download(self, cr, uid, ids, download=True, context=None): res = [] + default_version = modules.adapt_version('1.0') for mod in self.browse(cr, uid, ids, context=context): if not mod.url: continue match = re.search('-([a-zA-Z0-9\._-]+)(\.zip)', mod.url, re.I) - version = '0' + version = default_version if match: version = match.group(1) - if parse_version(mod.installed_version or '0') >= parse_version(version): + if parse_version(mod.installed_version) >= parse_version(version): continue res.append(mod.url) if not download: continue zip_content = urllib.urlopen(mod.url).read() - fname = modules.get_module_path(str(mod.name)+'.zip', downloaded=True) + fname = modules.get_module_path(str(mod.name) + '.zip', downloaded=True) try: with open(fname, 'wb') as fp: fp.write(zip_content) @@ -614,17 +645,77 @@ class module(osv.osv): raise orm.except_orm(_('Error'), _('Can not create the module file:\n %s') % (fname,)) terp = self.get_module_info(mod.name) self.write(cr, uid, mod.id, self.get_values_from_terp(terp)) - cr.execute('DELETE FROM ir_module_module_dependency ' \ - 'WHERE module_id = %s', (mod.id,)) - self._update_dependencies(cr, uid, mod, terp.get('depends', - [])) - self._update_category(cr, uid, mod, terp.get('category', - 'Uncategorized')) + cr.execute('DELETE FROM ir_module_module_dependency WHERE module_id = %s', (mod.id,)) + self._update_dependencies(cr, uid, mod, terp.get('depends', [])) + self._update_category(cr, uid, mod, terp.get('category', 'Uncategorized')) # Import module zimp = zipimport.zipimporter(fname) zimp.load_module(mod.name) return res + def install_from_urls(self, cr, uid, urls, context=None): + OPENERP = 'openerp' + tmp = tempfile.mkdtemp() + try: + for module_name, url in urls.items(): + if not url: + continue # nothing to download, local version is already the last one + try: + content = urllib2.urlopen(url).read() + except Exception, e: + _logger.exception('ggr') + raise osv.except_osv('grrr', e) + else: + zipfile.ZipFile(StringIO(content)).extractall(tmp) + assert os.path.isdir(os.path.join(tmp, module_name)) + + for module_name in urls: + if module_name == OPENERP: + continue # special case. handled below + module_path = modules.get_module_path(module_name, downloaded=True, display_warning=False) + bck = backup(module_path, False) + shutil.move(os.path.join(tmp, module_name), module_path) + if bck: + shutil.rmtree(bck) + + if urls.get(OPENERP, None): + # special case. it containt the server and the base module. + # extract path is not the same + + base_path = os.path.dirname(modules.get_module_path('base')) + + # copy all modules in the SERVER/openerp/addons directory to the new "openerp" module (except base itself) + for d in os.listdir(base_path): + if d != 'base' and os.path.isdir(os.path.join(base_path, d)): + destdir = os.path.join(tmp, OPENERP, 'addons', d) # XXX 'openerp' subdirectory ? + shutil.copytree(os.path.join(base_path, d), destdir) + + # then replace the server by the new "base" module + server_dir = openerp.tools.config['root_path'] # XXX or dirname() + bck = backup(server_dir) + shutil.move(os.path.join(tmp, OPENERP), server_dir) + #if bck: + # shutil.rmtree(bck) + + self.update_list(cr, uid, context=context) + + ids = self.search(cr, uid, [('name', 'in', urls.keys())], context=context) + if self.search_count(cr, uid, [('id', 'in', ids), ('state', '=', 'installed')], context=context): + # if any to update + cr.commit() + openerp.service.restart_server() + return { + 'type': 'ir.actions.client', + 'tag': 'home', + 'params': {'wait': True}, + } + return self.button_immediate_install(cr, uid, ids, context=context) + finally: + shutil.rmtree(tmp) + + def install_by_names(self, cr, uid, names, context=None): + raise NotImplementedError('# TODO') + def _update_dependencies(self, cr, uid, mod_browse, depends=None): if depends is None: depends = [] @@ -679,20 +770,20 @@ class module_dependency(osv.osv): _columns = { # The dependency name - 'name': fields.char('Name', size=128, select=True), + 'name': fields.char('Name', size=128, select=True), # The module that depends on it 'module_id': fields.many2one('ir.module.module', 'Module', select=True, ondelete='cascade'), 'state': fields.function(_state, type='selection', selection=[ - ('uninstallable','Uninstallable'), - ('uninstalled','Not Installed'), - ('installed','Installed'), - ('to upgrade','To be upgraded'), - ('to remove','To be removed'), - ('to install','To be installed'), + ('uninstallable', 'Uninstallable'), + ('uninstalled', 'Not Installed'), + ('installed', 'Installed'), + ('to upgrade', 'To be upgraded'), + ('to remove', 'To be removed'), + ('to install', 'To be installed'), ('unknown', 'Unknown'), - ], string='Status', readonly=True, select=True), + ], string='Status', readonly=True, select=True), } # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
