summaryrefslogtreecommitdiff
path: root/bin/addons/base/module/module.py
diff options
context:
space:
mode:
authorNumerigraphe - Lionel Sausin <ls@numerigraphe.fr>2010-10-25 09:43:13 +0200
committerNumerigraphe - Lionel Sausin <ls@numerigraphe.fr>2010-10-25 09:43:13 +0200
commite3e8be4b0181018db564587d49f29ac907445ac2 (patch)
treec516f6257fb9a432534eef61da14b916c91df38a /bin/addons/base/module/module.py
parent25a424a89d32e4485ae1e120c1d8b2ada216800e (diff)
parent3af170274bc03f844210d2eb1db16158e9056280 (diff)
[MERGE] merge from 6.0.0-RC1
bzr revid: ls@numerigraphe.fr-20101025074313-uaspq2f2y523my99
Diffstat (limited to 'bin/addons/base/module/module.py')
-rw-r--r--bin/addons/base/module/module.py244
1 files changed, 162 insertions, 82 deletions
diff --git a/bin/addons/base/module/module.py b/bin/addons/base/module/module.py
index 223a9ac1e42..3f4b59dd0e6 100644
--- a/bin/addons/base/module/module.py
+++ b/bin/addons/base/module/module.py
@@ -3,6 +3,7 @@
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
+# Copyright (C) 2010 OpenERP s.a. (<http://openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@@ -18,31 +19,37 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
-
-import tarfile
+import imp
+import logging
+import os
import re
import urllib
-import os
-import tools
-from osv import fields, osv, orm
-import zipfile
-import release
import zipimport
-import wizard
import addons
-import pooler
import netsvc
+import pooler
+import release
+import tools
from tools.parse_version import parse_version
from tools.translate import _
+from osv import fields, osv, orm
+
class module_category(osv.osv):
_name = "ir.module.category"
_description = "Module Category"
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 ('+','.join(map(str, ids))+') or category_id in (select id from ir_module_category where parent_id in ('+','.join(map(str, ids))+')) group by category_id')
+ cr.execute('SELECT category_id, COUNT(*) \
+ FROM ir_module_module \
+ WHERE category_id IN %(ids)s \
+ OR category_id IN (SELECT id \
+ 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,))
@@ -62,6 +69,7 @@ module_category()
class module(osv.osv):
_name = "ir.module.module"
_description = "Module"
+ __logger = logging.getLogger('base.' + _name)
def get_module_info(self, name):
info = {}
@@ -69,8 +77,9 @@ class module(osv.osv):
info = addons.load_information_from_description_file(name)
if 'version' in info:
info['version'] = release.major_version + '.' + info['version']
- except:
- pass
+ except Exception:
+ self.__logger.debug('Error when trying to fetch informations for '
+ 'module %s', name, exc_info=True)
return info
def _get_latest_version(self, cr, uid, ids, field_name=None, arg=None, context={}):
@@ -101,23 +110,36 @@ class module(osv.osv):
try:
key = data_id['model']
if key=='ir.ui.view':
- v = view_obj.browse(cr,uid,data_id.res_id)
- aa = v.inherit_id and '* INHERIT ' or ''
- res[mnames[data_id.module]]['views_by_module'] += aa + v.name + ' ('+v.type+')\n'
+ try:
+ v = view_obj.browse(cr,uid,data_id.res_id)
+ aa = v.inherit_id and '* INHERIT ' or ''
+ res[mnames[data_id.module]]['views_by_module'] += aa + v.name + ' ('+v.type+')\n'
+ except Exception:
+ self.__logger.debug(
+ 'Unknown error while browsing ir.ui.view[%s]',
+ data_id.res_id, exc_info=True)
elif key=='ir.actions.report.xml':
res[mnames[data_id.module]]['reports_by_module'] += report_obj.browse(cr,uid,data_id.res_id).name + '\n'
elif key=='ir.ui.menu':
- res[mnames[data_id.module]]['menus_by_module'] += menu_obj.browse(cr,uid,data_id.res_id).complete_name + '\n'
- except KeyError, e:
+ try:
+ m = menu_obj.browse(cr,uid,data_id.res_id)
+ res[mnames[data_id.module]]['menus_by_module'] += m.complete_name + '\n'
+ except Exception:
+ self.__logger.debug(
+ 'Unknown error while browsing ir.ui.menu[%s]',
+ data_id.res_id, exc_info=True)
+ except KeyError:
pass
return res
_columns = {
- 'name': fields.char("Name", size=128, readonly=True, required=True),
- 'category_id': fields.many2one('ir.module.category', 'Category', readonly=True),
+ 'name': fields.char("Name", size=128, readonly=True, required=True, select=True),
+ 'category_id': fields.many2one('ir.module.category', 'Category', readonly=True, select=True),
'shortdesc': fields.char('Short Description', size=256, readonly=True, translate=True),
'description': fields.text("Description", readonly=True, translate=True),
'author': fields.char("Author", size=128, readonly=True),
+ 'maintainer': fields.char('Maintainer', size=128, readonly=True),
+ 'contributors': fields.text('Contributors', readonly=True),
'website': fields.char("Website", size=256, readonly=True),
# attention: Incorrect field names !!
@@ -139,25 +161,29 @@ class module(osv.osv):
('to upgrade','To be upgraded'),
('to remove','To be removed'),
('to install','To be installed')
- ], string='State', readonly=True),
+ ], string='State', readonly=True, select=True),
'demo': fields.boolean('Demo data'),
'license': fields.selection([
- ('GPL-2', 'GPL-2'),
+ ('GPL-2', 'GPL Version 2'),
('GPL-2 or any later version', 'GPL-2 or later version'),
- ('GPL-3', 'GPL-3'),
+ ('GPL-3', 'GPL Version 3'),
('GPL-3 or any later version', 'GPL-3 or later version'),
- ('Other proprietary', 'Other proprietary')
+ ('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, method=True, string='Menus', type='text', multi="meta", store=True),
'reports_by_module': fields.function(_get_views, method=True, string='Reports', type='text', multi="meta", store=True),
'views_by_module': fields.function(_get_views, method=True, string='Views', type='text', multi="meta", store=True),
'certificate' : fields.char('Quality Certificate', size=64, readonly=True),
+ 'web': fields.boolean('Has a web component', readonly=True),
}
_defaults = {
'state': lambda *a: 'uninstalled',
'demo': lambda *a: False,
- 'license': lambda *a: 'GPL-2',
+ 'license': lambda *a: 'AGPL-3',
+ 'web': False,
}
_order = 'name'
@@ -185,6 +211,27 @@ class module(osv.osv):
return super(module, self).unlink(cr, uid, ids, context=context)
+ @staticmethod
+ def _check_external_dependencies(terp):
+ depends = terp.get('external_dependencies')
+ if not depends:
+ return
+ for pydep in depends.get('python', []):
+ parts = pydep.split('.')
+ parts.reverse()
+ path = None
+ while parts:
+ part = parts.pop()
+ try:
+ f, path, descr = imp.find_module(part, path and [path] or None)
+ except ImportError:
+ raise ImportError('No module named %s' % (pydep,))
+
+ for binary in depends.get('bin', []):
+ if tools.find_in_path(binary) is None:
+ raise Exception('Unable to find %r in path' % (binary,))
+
+
def state_update(self, cr, uid, ids, newstate, states_to_update, context={}, level=100):
if level<1:
raise orm.except_orm(_('Error'), _('Recursion error in modules dependencies !'))
@@ -200,6 +247,12 @@ class module(osv.osv):
else:
od = self.browse(cr, uid, ids2)[0]
mdemo = od.demo or mdemo
+
+ terp = self.get_module_info(module.name)
+ try:
+ self._check_external_dependencies(terp)
+ except Exception, e:
+ raise orm.except_orm(_('Error'), _('Unable %s the module "%s" because an external dependencie is not met: %s' % (newstate, module.name, e.args[0])))
if not module.dependencies_id:
mdemo = module.demo
if module.state in states_to_update:
@@ -273,6 +326,20 @@ class module(osv.osv):
self.update_translations(cr, uid, ids)
return True
+ @staticmethod
+ def get_values_from_terp(terp):
+ return {
+ 'description': terp.get('description', ''),
+ 'shortdesc': terp.get('name', ''),
+ 'author': terp.get('author', 'Unknown'),
+ 'maintainer': terp.get('maintainer', False),
+ 'contributors': ', '.join(terp.get('contributors', [])) or False,
+ 'website': terp.get('website', ''),
+ 'license': terp.get('license', 'GPL-2'),
+ 'certificate': terp.get('certificate') or None,
+ 'web': terp.get('web') or False,
+ }
+
# update the list of available packages
def update_list(self, cr, uid, context={}):
res = [0, 0] # [update, add]
@@ -280,46 +347,31 @@ class module(osv.osv):
# iterate through installed modules and mark them as being so
for mod_name in addons.get_modules():
ids = self.search(cr, uid, [('name','=',mod_name)])
+ terp = self.get_module_info(mod_name)
+ values = self.get_values_from_terp(terp)
+
if ids:
id = ids[0]
mod = self.browse(cr, uid, id)
- terp = self.get_module_info(mod_name)
if terp.get('installable', True) and mod.state == 'uninstallable':
self.write(cr, uid, id, {'state': 'uninstalled'})
if parse_version(terp.get('version', '')) > parse_version(mod.latest_version or ''):
- self.write(cr, uid, id, { 'url': ''})
+ self.write(cr, uid, id, {'url': ''})
res[0] += 1
- self.write(cr, uid, id, {
- 'description': terp.get('description', ''),
- 'shortdesc': terp.get('name', ''),
- 'author': terp.get('author', 'Unknown'),
- 'website': terp.get('website', ''),
- 'license': terp.get('license', 'GPL-2'),
- 'certificate': terp.get('certificate') or None,
- })
+ self.write(cr, uid, id, values)
cr.execute('DELETE FROM ir_module_module_dependency WHERE module_id = %s', (id,))
- self._update_dependencies(cr, uid, ids[0], terp.get('depends', []))
- self._update_category(cr, uid, ids[0], terp.get('category', 'Uncategorized'))
- continue
- mod_path = addons.get_module_path(mod_name)
- if mod_path:
- terp = self.get_module_info(mod_name)
+ else:
+ mod_path = addons.get_module_path(mod_name)
+ if not mod_path:
+ continue
if not terp or not terp.get('installable', True):
continue
- id = self.create(cr, uid, {
- 'name': mod_name,
- 'state': 'uninstalled',
- 'description': terp.get('description', ''),
- 'shortdesc': terp.get('name', ''),
- 'author': terp.get('author', 'Unknown'),
- 'website': terp.get('website', ''),
- 'license': terp.get('license', 'GPL-2'),
- 'certificate': terp.get('certificate') or None,
- })
+ ids = self.search(cr, uid, [('name','=',mod_name)])
+ id = self.create(cr, uid, dict(name=mod_name, state='uninstalled', **values))
res[1] += 1
- self._update_dependencies(cr, uid, id, terp.get('depends', []))
- self._update_category(cr, uid, id, terp.get('category', 'Uncategorized'))
+ self._update_dependencies(cr, uid, id, terp.get('depends', []))
+ self._update_category(cr, uid, id, terp.get('category', 'Uncategorized'))
return res
@@ -343,17 +395,12 @@ class module(osv.osv):
fp = file(fname, 'wb')
fp.write(zipfile)
fp.close()
- except Exception, e:
+ except Exception:
+ self.__logger.exception('Error when trying to create module '
+ 'file %s', fname)
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, {
- 'description': terp.get('description', ''),
- 'shortdesc': terp.get('name', ''),
- 'author': terp.get('author', 'Unknown'),
- 'website': terp.get('website', ''),
- 'license': terp.get('license', 'GPL-2'),
- 'certificate': terp.get('certificate') or None,
- })
+ 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.id, terp.get('depends',
@@ -388,7 +435,7 @@ class module(osv.osv):
categs = categs[1:]
self.write(cr, uid, [id], {'category_id': p_id})
- def update_translations(self, cr, uid, ids, filter_lang=None):
+ def update_translations(self, cr, uid, ids, filter_lang=None, context={}):
logger = netsvc.Logger()
if not filter_lang:
pool = pooler.get_pool(cr.dbname)
@@ -415,34 +462,69 @@ class module(osv.osv):
iso_lang = iso_lang.split('_')[0]
if os.path.exists(f):
logger.notifyChannel("i18n", netsvc.LOG_INFO, 'module %s: loading translation file for language %s' % (mod.name, iso_lang))
- tools.trans_load(cr.dbname, f, lang, verbose=False)
+ tools.trans_load(cr.dbname, f, lang, verbose=False, context=context)
def check(self, cr, uid, ids, context=None):
- logger = netsvc.Logger()
+ logger = logging.getLogger('init')
for mod in self.browse(cr, uid, ids, context=context):
if not mod.description:
- logger.notifyChannel("init", netsvc.LOG_WARNING, 'module %s: description is empty !' % (mod.name,))
+ logger.warn('module %s: description is empty !', mod.name)
if not mod.certificate or not mod.certificate.isdigit():
- logger.notifyChannel('init', netsvc.LOG_WARNING, 'module %s: no quality certificate' % (mod.name,))
+ logger.info('module %s: no quality certificate', mod.name)
else:
val = long(mod.certificate[2:]) % 97 == 29
if not val:
- logger.notifyChannel('init', netsvc.LOG_CRITICAL, 'module %s: invalid quality certificate: %s' % (mod.name, mod.certificate))
+ logger.critical('module %s: invalid quality certificate: %s', mod.name, mod.certificate)
raise osv.except_osv(_('Error'), _('Module %s: Invalid Quality Certificate') % (mod.name,))
+ def list_web(self, cr, uid, context=None):
+ """ list_web(cr, uid, context) -> [module_name]
+ Lists all the currently installed modules with a web component.
+
+ Returns a list of addon names.
+ """
+ return [
+ module['name']
+ for module in self.browse(cr, uid,
+ self.search(cr, uid,
+ [('web', '=', True),
+ ('state', 'in', ['installed','to upgrade','to remove'])],
+ context=context),
+ context=context)]
+ def _web_dependencies(self, cr, uid, module, context=None):
+ for dependency in module.dependencies_id:
+ (parent,) = self.browse(cr, uid, self.search(cr, uid,
+ [('name', '=', dependency.name)], context=context),
+ context=context)
+ if parent.web:
+ yield parent.name
+ else:
+ self._web_dependencies(
+ cr, uid, parent, context=context)
+ def get_web(self, cr, uid, names, context=None):
+ """ get_web(cr, uid, [module_name], context) -> [{name, depends, content}]
+
+ Returns the web content of all the named addons.
+
+ The toplevel directory of the zipped content is called 'web',
+ its final naming has to be managed by the client
+ """
+ modules = self.browse(cr, uid,
+ self.search(cr, uid, [('name', 'in', names)], context=context),
+ context=context)
+ if not modules: return []
+ self.__logger.info('Sending web content of modules %s '
+ 'to web client', names)
+ return [
+ {'name': module.name,
+ 'depends': list(self._web_dependencies(
+ cr, uid, module, context=context)),
+ 'content': addons.zip_directory(
+ addons.get_module_resource(module.name, 'web'))}
+ for module in modules
+ ]
- def create(self, cr, uid, data, context={}):
- id = super(module, self).create(cr, uid, data, context)
- if data.get('name'):
- self.pool.get('ir.model.data').create(cr, uid, {
- 'name': 'module_meta_information',
- 'model': 'ir.module.module',
- 'res_id': id,
- 'module': data['name'],
- 'noupdate': True,
- })
- return id
module()
class module_dependency(osv.osv):
@@ -461,7 +543,7 @@ class module_dependency(osv.osv):
return result
_columns = {
- 'name': fields.char('Name', size=128),
+ 'name': fields.char('Name', size=128, select=True),
'module_id': fields.many2one('ir.module.module', 'Module', select=True, ondelete='cascade'),
'state': fields.function(_state, method=True, type='selection', selection=[
('uninstallable','Uninstallable'),
@@ -471,8 +553,6 @@ class module_dependency(osv.osv):
('to remove','To be removed'),
('to install','To be installed'),
('unknown', 'Unknown'),
- ], string='State', readonly=True),
+ ], string='State', readonly=True, select=True),
}
module_dependency()
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
-