+# -*- encoding: utf-8 -*-
##############################################################################
#
-# Copyright (c) 2005 TINY SPRL. (http://tiny.be) All Rights Reserved.
+# Copyright (c) 2004-2008 TINY SPRL. (http://tiny.be) All Rights Reserved.
+#
+# $Id$
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
import tools
from osv import fields, osv, orm
import zipfile
+import release
+import zipimport
+
+import wizard
+import addons
+
+ver_regexp = re.compile("^(\\d+)((\\.\\d+)*)([a-z]?)((_(pre|p|beta|alpha|rc)\\d*)*)(-r(\\d+))?$")
+suffix_regexp = re.compile("^(alpha|beta|rc|pre|p)(\\d*)$")
+
+def vercmp(ver1, ver2):
+ """
+ Compare two versions
+ Take from portage_versions.py
+ @param ver1: version to compare with
+ @type ver1: string (example "1.2-r3")
+ @param ver2: version to compare again
+ @type ver2: string (example "2.1-r1")
+ @rtype: None or float
+ @return:
+ 1. position if ver1 is greater than ver2
+ 2. negative if ver1 is less than ver2
+ 3. 0 if ver1 equals ver2
+ 4. None if ver1 or ver2 are invalid
+ """
+
+ match1 = ver_regexp.match(ver1)
+ match2 = ver_regexp.match(ver2)
+
+ if not match1 or not match1.groups():
+ return None
+ if not match2 or not match2.groups():
+ return None
+
+ list1 = [int(match1.group(1))]
+ list2 = [int(match2.group(1))]
+
+ if len(match1.group(2)) or len(match2.group(2)):
+ vlist1 = match1.group(2)[1:].split(".")
+ vlist2 = match2.group(2)[1:].split(".")
+ for i in range(0, max(len(vlist1), len(vlist2))):
+ # Implicit .0 is given -1, so 1.0.0 > 1.0
+ # would be ambiguous if two versions that aren't literally equal
+ # are given the same value (in sorting, for example).
+ if len(vlist1) <= i or len(vlist1[i]) == 0:
+ list1.append(-1)
+ list2.append(int(vlist2[i]))
+ elif len(vlist2) <= i or len(vlist2[i]) == 0:
+ list1.append(int(vlist1[i]))
+ list2.append(-1)
+ # Let's make life easy and use integers unless we're forced to use floats
+ elif (vlist1[i][0] != "0" and vlist2[i][0] != "0"):
+ list1.append(int(vlist1[i]))
+ list2.append(int(vlist2[i]))
+ # now we have to use floats so 1.02 compares correctly against 1.1
+ else:
+ list1.append(float("0."+vlist1[i]))
+ list2.append(float("0."+vlist2[i]))
+ # and now the final letter
+ if len(match1.group(4)):
+ list1.append(ord(match1.group(4)))
+ if len(match2.group(4)):
+ list2.append(ord(match2.group(4)))
+
+ for i in range(0, max(len(list1), len(list2))):
+ if len(list1) <= i:
+ return -1
+ elif len(list2) <= i:
+ return 1
+ elif list1[i] != list2[i]:
+ return list1[i] - list2[i]
+
+ # main version is equal, so now compare the _suffix part
+ list1 = match1.group(5).split("_")[1:]
+ list2 = match2.group(5).split("_")[1:]
+
+ for i in range(0, max(len(list1), len(list2))):
+ # Implicit _p0 is given a value of -1, so that 1 < 1_p0
+ if len(list1) <= i:
+ s1 = ("p","-1")
+ else:
+ s1 = suffix_regexp.match(list1[i]).groups()
+ if len(list2) <= i:
+ s2 = ("p","-1")
+ else:
+ s2 = suffix_regexp.match(list2[i]).groups()
+ if s1[0] != s2[0]:
+ return suffix_value[s1[0]] - suffix_value[s2[0]]
+ if s1[1] != s2[1]:
+ # it's possible that the s(1|2)[1] == ''
+ # in such a case, fudge it.
+ try:
+ r1 = int(s1[1])
+ except ValueError:
+ r1 = 0
+ try:
+ r2 = int(s2[1])
+ except ValueError:
+ r2 = 0
+ if r1 - r2:
+ return r1 - r2
+
+ # the suffix part is equal to, so finally check the revision
+ if match1.group(9):
+ r1 = int(match1.group(9))
+ else:
+ r1 = 0
+ if match2.group(9):
+ r2 = int(match2.group(9))
+ else:
+ r2 = 0
+ return r1 - r2
+
class module_repository(osv.osv):
- _name = "ir.module.repository"
- _description = "Module Repository"
- _columns = {
- 'name': fields.char('Name', size=128),
- 'url': fields.char('Url', size=256, required=True),
- }
+ _name = "ir.module.repository"
+ _description = "Module Repository"
+ _columns = {
+ 'name': fields.char('Name', size=128),
+ 'url': fields.char('Url', size=256, required=True),
+ 'sequence': fields.integer('Sequence', required=True),
+ 'filter': fields.char('Filter', size=128, required=True,
+ help='Regexp to search module on the repository webpage:\n'
+ '- The first parenthesis must match the name of the module.\n'
+ '- The second parenthesis must match all the version number.\n'
+ '- The last parenthesis must match the extension of the module.'),
+ 'active': fields.boolean('Active'),
+ }
+ _defaults = {
+ 'sequence': lambda *a: 5,
+ 'filter': lambda *a: 'href="([a-zA-Z0-9_]+)-('+release.version.rsplit('.', 1)[0]+'.(\\d+)((\\.\\d+)*)([a-z]?)((_(pre|p|beta|alpha|rc)\\d*)*)(-r(\\d+))?)(\.zip)"',
+ 'active': lambda *a: 1,
+ }
+ _order = "sequence"
module_repository()
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')
- result = dict(cr.fetchall())
- for id in ids:
- cr.execute('select id from ir_module_category where parent_id=%d', (id,))
- childs = [c for c, in cr.fetchall()]
- result[id] = reduce(lambda x,y:x+y, [result.get(c, 0) for c in childs], result.get(id, 0))
- return result
-
- _columns = {
- 'name': fields.char("Name", size=128, required=True),
- 'parent_id': fields.many2one('ir.module.category', 'Parent Category', select=True),
- 'child_ids': fields.one2many('ir.module.category', 'parent_id', 'Parent Category'),
- 'module_nr': fields.function(_module_nbr, method=True, string='# of Modules', type='integer')
- }
- _order = 'name'
+ _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')
+ result = dict(cr.fetchall())
+ for id in ids:
+ cr.execute('select id from ir_module_category where parent_id=%d', (id,))
+ childs = [c for c, in cr.fetchall()]
+ result[id] = reduce(lambda x,y:x+y, [result.get(c, 0) for c in childs], result.get(id, 0))
+ return result
+
+ _columns = {
+ 'name': fields.char("Name", size=128, required=True),
+ 'parent_id': fields.many2one('ir.module.category', 'Parent Category', select=True),
+ 'child_ids': fields.one2many('ir.module.category', 'parent_id', 'Parent Category'),
+ 'module_nr': fields.function(_module_nbr, method=True, string='# of Modules', type='integer')
+ }
+ _order = 'name'
module_category()
class module(osv.osv):
- _name = "ir.module.module"
- _description = "Module"
-
- def get_module_info(self, name):
- try:
- f = tools.file_open(os.path.join(tools.config['addons_path'], name, '__terp__.py'))
- data = f.read()
- info = eval(data)
- f.close()
- except:
- return {}
- return info
-
- def _get_installed_version(self, cr, uid, ids, field_name=None, arg=None, context={}):
- res = {}
- for m in self.browse(cr, uid, ids):
- if m.state in ('installed', 'to upgrade', 'to remove'):
- res[m.id] = self.get_module_info(m.name).get('version', False)
- else:
- res[m.id] = ''
- return res
-
- _columns = {
- 'name': fields.char("Name", size=128, readonly=True, required=True),
- 'category_id': fields.many2one('ir.module.category', 'Category', readonly=True),
- 'shortdesc': fields.char('Short description', size=256, readonly=True),
- 'description': fields.text("Description", readonly=True),
- 'author': fields.char("Author", size=128, readonly=True),
- 'website': fields.char("Website", size=256, readonly=True),
- 'installed_version': fields.function(_get_installed_version, method=True, string='Installed version', type='char'),
- 'latest_version': fields.char('Latest version', size=64, readonly=True),
- 'url': fields.char('URL', size=128, readonly=True),
- 'dependencies_id': fields.one2many('ir.module.module.dependency', 'module_id', 'Dependencies'),
- 'state': fields.selection([
- ('uninstallable','Uninstallable'),
- ('uninstalled','Not Installed'),
- ('installed','Installed'),
- ('to upgrade','To be upgraded'),
- ('to remove','To be removed'),
- ('to install','To be installed')
- ], string='State', readonly=True),
- 'demo': fields.boolean('Demo data'),
- 'license': fields.selection([('GPL-2', 'GPL-2'),
- ('Other proprietary', 'Other proprietary')], string='License',
- readonly=True),
- }
-
- _defaults = {
- 'state': lambda *a: 'uninstalled',
- 'demo': lambda *a: False,
- 'license': lambda *a: 'GPL-2',
- }
- _order = 'name'
-
- _sql_constraints = [
- ('name_uniq', 'unique (name)', 'The name of the module must be unique !')
- ]
-
- def state_change(self, cr, uid, ids, newstate, context={}, level=50):
- if level<1:
- raise 'Recursion error in modules dependencies !'
- demo = True
- for module in self.browse(cr, uid, ids):
- mdemo = True
- for dep in module.dependencies_id:
- ids2 = self.search(cr, uid, [('name','=',dep.name)])
- mdemo = self.state_change(cr, uid, ids2, newstate, context, level-1) and mdemo
- if not module.dependencies_id:
- mdemo = module.demo
- if module.state=='uninstalled':
- self.write(cr, uid, [module.id], {'state': newstate, 'demo':mdemo})
- demo = demo and mdemo
- return demo
-
- def button_install(self, cr, uid, ids, context={}):
- return self.state_change(cr, uid, ids, 'to install', context)
-
- def button_install_cancel(self, cr, uid, ids, context={}):
- self.write(cr, uid, ids, {'state': 'uninstalled', 'demo':False})
- return True
-
- def button_uninstall(self, cr, uid, ids, context={}):
- for module in self.browse(cr, uid, ids):
- cr.execute('''select m.state,m.name
- from
- ir_module_module_dependency d
- join
- ir_module_module m on (d.module_id=m.id)
- where
- d.name=%s and
- m.state not in ('uninstalled','uninstallable','to remove')''', (module.name,))
- res = cr.fetchall()
- if res:
- raise orm.except_orm('Error', 'The module you are trying to remove depends on installed modules :\n' + '\n'.join(map(lambda x: '\t%s: %s' % (x[0], x[1]), res)))
- self.write(cr, uid, ids, {'state': 'to remove'})
- return True
-
- def button_uninstall_cancel(self, cr, uid, ids, context={}):
- self.write(cr, uid, ids, {'state': 'installed'})
- return True
- def button_upgrade(self, cr, uid, ids, context={}):
- self.write(cr, uid, ids, {'state': 'to upgrade'})
- return True
- def button_upgrade_cancel(self, cr, uid, ids, context={}):
- self.write(cr, uid, ids, {'state': 'installed'})
- return True
- def button_update_translations(self, cr, uid, ids, context={}):
- cr.execute('select code from res_lang where translatable=TRUE')
- langs = [l[0] for l in cr.fetchall()]
- modules = self.read(cr, uid, ids, ['name'])
- for module in modules:
- files = self.get_module_info(module['name']).get('translations', {})
- for lang in langs:
- if files.has_key(lang):
- filepath = files[lang]
- # if filepath does not contain :// we prepend the path of the module
- if filepath.find('://') == -1:
- filepath = os.path.join(tools.config['addons_path'], module['name'], filepath)
- tools.trans_load(filepath, lang)
- return True
-
- # update the list of available packages
- def update_list(self, cr, uid, context={}):
- robj = self.pool.get('ir.module.repository')
- adp = tools.config['addons_path']
-
- # iterate through installed modules and mark them as being so
- for name in os.listdir(adp):
- mod_name = name
- if name[-4:]=='.zip':
- mod_name=name[:-4]
- ids = self.search(cr, uid, [('name','=',mod_name)])
- if ids:
- terp = self.get_module_info(mod_name)
- if terp.get('installable', True) and self.read(cr, uid, ids, ['state'])[0]['state'] == 'uninstallable':
- self.write(cr, uid, ids, {'state': 'uninstalled'})
- self.write(cr, uid, ids, {
- 'description': terp.get('description', ''),
- 'shortdesc': terp.get('name', ''),
- 'author': terp.get('author', 'Unknown'),
- 'website': terp.get('website', ''),
- 'latest_version': terp.get('version', ''),
- 'license': terp.get('license', 'GPL-2'),
- })
- cr.execute('DELETE FROM ir_module_module_dependency where module_id = %d', (ids[0],))
- self._update_dependencies(cr, uid, ids[0], terp.get('depends', []))
- self._update_category(cr, uid, ids[0], terp.get('category', 'Uncategorized'))
- continue
- terp_file = os.path.join(adp, name, '__terp__.py')
- mod_path = os.path.join(adp, name)
- if os.path.isdir(mod_path) or os.path.islink(mod_path) or zipfile.is_zipfile(mod_path):
- terp = self.get_module_info(mod_name)
- if not terp or not terp.get('installable', True):
- continue
- try:
- import imp
- # XXX must restrict to only addons paths
- imp.load_module(name, *imp.find_module(name))
- except ImportError:
- import zipimport
- mod_path = os.path.join(adp, name)
- zimp = zipimport.zipimporter(mod_path)
- zimp.load_module(mod_name)
- version = terp.get('version', False)
- 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', ''),
- 'latest_version': terp.get('version', ''),
- 'license': terp.get('license', 'GPL-2'),
- })
- self._update_dependencies(cr, uid, id, terp.get('depends', []))
- self._update_category(cr, uid, id, terp.get('category', 'Uncategorized'))
-
- # make the list of all installable modules
- for repository in robj.browse(cr, uid, robj.search(cr, uid, [])):
- index_page = urllib.urlopen(repository.url).read()
- modules = re.findall('.*<a href="([a-zA-Z0-9.\-]+)_([a-zA-Z0-9.\-]+)\.tar\.gz">.*', index_page)
- for name, version in modules:
- # TODO: change this using urllib
- url = os.path.join(repository.url, name + '_' + version + ".tar.gz")
- ids = self.search(cr, uid, [('name','=',name)])
- if not ids:
- self.create(cr, uid, {
- 'name': name,
- 'latest_version': version,
- 'url': url,
- 'state': 'uninstalled',
- })
- else:
- for r in self.read(cr, uid, ids, ['latest_version']):
- if r['latest_version'] < version:
- self.write(cr, uid, [r['id']], {'latest_version': version, 'url':url})
- return True
-
- def _update_dependencies(self, cr, uid, id, depends=[]):
- for d in depends:
- cr.execute('INSERT INTO ir_module_module_dependency (module_id, name) values (%d, %s)', (id, d))
-
- def _update_category(self, cr, uid, id, category='Uncategorized'):
- categs = category.split('/')
- p_id = None
- while categs:
- if p_id is not None:
- cr.execute('select id from ir_module_category where name=%s and parent_id=%d', (categs[0], p_id))
- else:
- cr.execute('select id from ir_module_category where name=%s and parent_id is NULL', (categs[0],))
- c_id = cr.fetchone()
- if not c_id:
- cr.execute('select nextval(\'ir_module_category_id_seq\')')
- c_id = cr.fetchone()[0]
- cr.execute('insert into ir_module_category (id, name, parent_id) values (%d, %s, %d)', (c_id, categs[0], p_id))
- else:
- c_id = c_id[0]
- p_id = c_id
- categs = categs[1:]
- self.write(cr, uid, [id], {'category_id': p_id})
+ _name = "ir.module.module"
+ _description = "Module"
+
+ def get_module_info(self, name):
+ try:
+ f = tools.file_open(os.path.join(name, '__terp__.py'))
+ data = f.read()
+ info = eval(data)
+ if 'version' in info:
+ info['version'] = release.version.rsplit('.', 1)[0] + '.' + info['version']
+ f.close()
+ except:
+ return {}
+ return info
+
+ def _get_installed_version(self, cr, uid, ids, field_name=None, arg=None, context={}):
+ res = {}
+ for m in self.browse(cr, uid, ids):
+ if m.state in ('installed', 'to upgrade', 'to remove'):
+ res[m.id] = self.get_module_info(m.name).get('version', '')
+ else:
+ res[m.id] = ''
+ return res
+
+ _columns = {
+ 'name': fields.char("Name", size=128, readonly=True, required=True),
+ 'category_id': fields.many2one('ir.module.category', 'Category', readonly=True),
+ 'shortdesc': fields.char('Short description', size=256, readonly=True),
+ 'description': fields.text("Description", readonly=True),
+ 'author': fields.char("Author", size=128, readonly=True),
+ 'website': fields.char("Website", size=256, readonly=True),
+ 'installed_version': fields.function(_get_installed_version, method=True,
+ string='Installed version', type='char'),
+ 'latest_version': fields.char('Latest version', size=64, readonly=True),
+ 'published_version': fields.char('Published Version', size=64, readonly=True),
+ 'url': fields.char('URL', size=128),
+ 'dependencies_id': fields.one2many('ir.module.module.dependency',
+ 'module_id', 'Dependencies', readonly=True),
+ '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')
+ ], string='State', readonly=True),
+ 'demo': fields.boolean('Demo data'),
+ 'license': fields.selection([('GPL-2', 'GPL-2'),
+ ('Other proprietary', 'Other proprietary')], string='License',
+ readonly=True),
+ }
+
+ _defaults = {
+ 'state': lambda *a: 'uninstalled',
+ 'demo': lambda *a: False,
+ 'license': lambda *a: 'GPL-2',
+ }
+ _order = 'name'
+
+ _sql_constraints = [
+ ('name_uniq', 'unique (name)', 'The name of the module must be unique !')
+ ]
+
+ def unlink(self, cr, uid, ids, context=None):
+ if not ids:
+ return True
+ if isinstance(ids, (int, long)):
+ ids = [ids]
+ for mod in self.read(cr, uid, ids, ['state'], 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'))
+ return super(module, self).unlink(cr, uid, ids, context=context)
+
+ def state_update(self, cr, uid, ids, newstate, states_to_update, context={}, level=50):
+ if level<1:
+ raise orm.except_orm(_('Error'), _('Recursion error in modules dependencies !'))
+ demo = True
+ for module in self.browse(cr, uid, ids):
+ mdemo = True
+ for dep in module.dependencies_id:
+ if dep.state == 'unknown':
+ raise orm.except_orm(_('Error'), _('You try to install a module that depends on the module: %s.\nBut this module is not available in your system.') % (dep.name,))
+ if dep.state != newstate:
+ ids2 = self.search(cr, uid, [('name','=',dep.name)])
+ mdemo = self.state_update(cr, uid, ids2, newstate, states_to_update, context, level-1,) and mdemo
+ 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})
+ demo = demo and mdemo
+ return demo
+
+ def button_install(self, cr, uid, ids, context={}):
+ return self.state_update(cr, uid, ids, 'to install', ['uninstalled'], context)
+
+ def button_install_cancel(self, cr, uid, ids, context={}):
+ self.write(cr, uid, ids, {'state': 'uninstalled', 'demo':False})
+ return True
+
+ def button_uninstall(self, cr, uid, ids, context={}):
+ for module in self.browse(cr, uid, ids):
+ cr.execute('''select m.state,m.name
+ from
+ ir_module_module_dependency d
+ join
+ ir_module_module m on (d.module_id=m.id)
+ where
+ d.name=%s and
+ m.state not in ('uninstalled','uninstallable','to remove')''', (module.name,))
+ res = cr.fetchall()
+ if res:
+ raise orm.except_orm(_('Error'), _('The module you are trying to remove depends on installed modules :\n %s') % '\n'.join(map(lambda x: '\t%s: %s' % (x[0], x[1]), res)))
+ self.write(cr, uid, ids, {'state': 'to remove'})
+ return True
+
+ def button_uninstall_cancel(self, cr, uid, ids, context={}):
+ self.write(cr, uid, ids, {'state': 'installed'})
+ return True
+ def button_upgrade(self, cr, uid, ids, context=None):
+ return self.state_update(cr, uid, ids, 'to upgrade', ['installed'], context)
+ def button_upgrade_cancel(self, cr, uid, ids, context={}):
+ self.write(cr, uid, ids, {'state': 'installed'})
+ return True
+ def button_update_translations(self, cr, uid, ids, context={}):
+ cr.execute('select code from res_lang where translatable=TRUE')
+ langs = [l[0] for l in cr.fetchall()]
+ modules = self.read(cr, uid, ids, ['name'])
+ for module in modules:
+ files = self.get_module_info(module['name']).get('translations', {})
+ for lang in langs:
+ if files.has_key(lang):
+ filepath = files[lang]
+ # if filepath does not contain :// we prepend the path of the module
+ if filepath.find('://') == -1:
+ filepath = addons.get_module_resource(module['name'], filepath)
+ tools.trans_load(filepath, lang)
+ return True
+
+ # update the list of available packages
+ def update_list(self, cr, uid, context={}):
+ robj = self.pool.get('ir.module.repository')
+ res = [0, 0] # [update, add]
+
+ # iterate through installed modules and mark them as being so
+ for name in addons.get_modules():
+ mod_name = name
+ if name[-4:]=='.zip':
+ mod_name=name[:-4]
+ ids = self.search(cr, uid, [('name','=',mod_name)])
+ 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 vercmp(terp.get('version', ''), mod.latest_version or '0') > 0:
+ self.write(cr, uid, id, {
+ 'latest_version': terp.get('version'),
+ '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'),
+ })
+ cr.execute('DELETE FROM ir_module_module_dependency\
+ WHERE module_id = %d', (id,))
+ self._update_dependencies(cr, uid, ids[0], terp.get('depends',
+ []))
+ self._update_category(cr, uid, ids[0], terp.get('category',
+ 'Uncategorized'))
+ continue
+ terp_file = addons.get_module_resource(name, '__terp__.py')
+ mod_path = addons.get_module_path(name)
+ if os.path.isdir(mod_path) or os.path.islink(mod_path) or zipfile.is_zipfile(mod_path):
+ terp = self.get_module_info(mod_name)
+ if not terp or not terp.get('installable', True):
+ continue
+ if not os.path.isfile(mod_path+'.zip'):
+ import imp
+ # XXX must restrict to only addons paths
+ imp.load_module(name, *imp.find_module(mod_name))
+ else:
+ import zipimport
+ zimp = zipimport.zipimporter(mod_path+'.zip')
+ zimp.load_module(mod_name)
+ 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', ''),
+ 'latest_version': terp.get('version', ''),
+ 'license': terp.get('license', 'GPL-2'),
+ })
+ res[1] += 1
+ self._update_dependencies(cr, uid, id, terp.get('depends', []))
+ self._update_category(cr, uid, id, terp.get('category', 'Uncategorized'))
+
+ import socket
+ socket.setdefaulttimeout(10)
+ for repository in robj.browse(cr, uid, robj.search(cr, uid, [])):
+ try:
+ index_page = urllib.urlopen(repository.url).read()
+ except IOError, e:
+ if e.errno == 21:
+ raise orm.except_orm(_('Error'),
+ _("This url '%s' must provide an html file with links to zip modules") % (repository.url))
+ else:
+ raise
+ modules = re.findall(repository.filter, index_page, re.I+re.M)
+ mod_sort = {}
+ for m in modules:
+ name = m[0]
+ version = m[1]
+ extension = m[-1]
+ if version == 'x': # 'x' version was a mistake
+ version = '0'
+ if name in mod_sort:
+ if vercmp(version, mod_sort[name][0]) <= 0:
+ continue
+ mod_sort[name] = [version, extension]
+ for name in mod_sort.keys():
+ version, extension = mod_sort[name]
+ url = repository.url+'/'+name+'-'+version+extension
+ ids = self.search(cr, uid, [('name','=',name)])
+ if not ids:
+ self.create(cr, uid, {
+ 'name': name,
+ 'latest_version': version,
+ 'published_version': version,
+ 'url': url,
+ 'state': 'uninstalled',
+ })
+ res[1] += 1
+ else:
+ id = ids[0]
+ latest_version = self.read(cr, uid, id, ['latest_version'])\
+ ['latest_version']
+ if latest_version == 'x': # 'x' version was a mistake
+ latest_version = '0'
+ c = vercmp(version, latest_version)
+ if c > 0:
+ self.write(cr, uid, id,
+ {'latest_version': version, 'url': url})
+ res[0] += 1
+ published_version = self.read(cr, uid, id, ['published_version'])\
+ ['published_version']
+ if published_version == 'x' or not published_version:
+ published_version = '0'
+ c = vercmp(version, published_version)
+ if c > 0:
+ self.write(cr, uid, id,
+ {'published_version': version})
+ return res
+
+ def download(self, cr, uid, ids, download=True, context=None):
+ res = []
+ 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'
+ if match:
+ version = match.group(1)
+ if vercmp(mod.installed_version or '0', version) >= 0:
+ continue
+ res.append(mod.url)
+ if not download:
+ continue
+ zipfile = urllib.urlopen(mod.url).read()
+ fname = addons.get_module_path(mod.name+'.zip')
+ try:
+ fp = file(fname, 'wb')
+ fp.write(zipfile)
+ fp.close()
+ except IOError, e:
+ 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'),
+ })
+ cr.execute('DELETE FROM ir_module_module_dependency ' \
+ 'WHERE module_id = %d', (mod.id,))
+ self._update_dependencies(cr, uid, mod.id, terp.get('depends',
+ []))
+ self._update_category(cr, uid, mod.id, terp.get('category',
+ 'Uncategorized'))
+ # Import module
+ zimp = zipimport.zipimporter(fname)
+ zimp.load_module(mod.name)
+ return res
+
+ def _update_dependencies(self, cr, uid, id, depends=[]):
+ for d in depends:
+ cr.execute('INSERT INTO ir_module_module_dependency (module_id, name) values (%d, %s)', (id, d))
+
+ def _update_category(self, cr, uid, id, category='Uncategorized'):
+ categs = category.split('/')
+ p_id = None
+ while categs:
+ if p_id is not None:
+ cr.execute('select id from ir_module_category where name=%s and parent_id=%d', (categs[0], p_id))
+ else:
+ cr.execute('select id from ir_module_category where name=%s and parent_id is NULL', (categs[0],))
+ c_id = cr.fetchone()
+ if not c_id:
+ cr.execute('select nextval(\'ir_module_category_id_seq\')')
+ c_id = cr.fetchone()[0]
+ cr.execute('insert into ir_module_category (id, name, parent_id) values (%d, %s, %d)', (c_id, categs[0], p_id))
+ else:
+ c_id = c_id[0]
+ p_id = c_id
+ categs = categs[1:]
+ self.write(cr, uid, [id], {'category_id': p_id})
+
+ def action_install(self,cr,uid,ids,context=None):
+ self.write(cr , uid, ids ,{'state' : 'to install'})
+ self.download(cr, uid, ids, context=context)
+ for id in ids:
+ cr.execute("select m.id as id from ir_module_module_dependency d inner join ir_module_module m on (m.name=d.name) where d.module_id=%d and m.state='uninstalled'",(id,))
+ dep_ids = map(lambda x:x[0],cr.fetchall())
+ if len(dep_ids):
+ self.action_install(cr,uid,dep_ids,context=context)
module()
class module_dependency(osv.osv):
- _name = "ir.module.module.dependency"
- _description = "Module dependency"
-
- def _state(self, cr, uid, ids, name, args, context={}):
- result = {}
- mod_obj = self.pool.get('ir.module.module')
- for md in self.browse(cr, uid, ids):
- ids = mod_obj.search(cr, uid, [('name', '=', md.name)])
- if ids:
- result[md.id] = mod_obj.read(cr, uid, [ids[0]], ['state'])[0]['state']
- else:
- result[md.id] = 'unknown'
- return result
-
- _columns = {
- 'name': fields.char('Name', size=128),
- 'module_id': fields.many2one('ir.module.module', 'Module', select=True),
- #'module_dest_id': fields.many2one('ir.module.module', 'Module'),
- 'version_pattern': fields.char('Required Version', size=128),
- 'state': fields.function(_state, method=True, 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'),
- ('unknown', 'Unknown'),
- ], string='State', readonly=True),
- }
- # returns the ids of module version records which match all dependencies
- # [version_id, ...]
- def resolve(self, cr, uid, ids):
- vobj = self.pool.get('ir.module.module.version')
- objs = self.browse(cr, uid, ids)
- res = {}
- for o in objs:
- pattern = o.version_pattern and eval(o.version_pattern) or []
- res[o.id] = vobj.search(cr, uid, [('module','=',o.module.id)]+pattern)
- #TODO: add smart dependencies resolver here
- # it should compute the best version for each module
- return [r[0] for r in res.itervalues()]
+ _name = "ir.module.module.dependency"
+ _description = "Module dependency"
+
+ def _state(self, cr, uid, ids, name, args, context={}):
+ result = {}
+ mod_obj = self.pool.get('ir.module.module')
+ for md in self.browse(cr, uid, ids):
+ ids = mod_obj.search(cr, uid, [('name', '=', md.name)])
+ if ids:
+ result[md.id] = mod_obj.read(cr, uid, [ids[0]], ['state'])[0]['state']
+ else:
+ result[md.id] = 'unknown'
+ return result
+
+ _columns = {
+ 'name': fields.char('Name', size=128),
+ 'module_id': fields.many2one('ir.module.module', 'Module', select=True, ondelete='cascade'),
+ 'state': fields.function(_state, method=True, 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'),
+ ('unknown', 'Unknown'),
+ ], string='State', readonly=True),
+ }
module_dependency()
+
+
+class module_config_wizard_step(osv.osv):
+ _name = 'ir.module.module.configuration.step'
+ _columns={
+ 'name':fields.char('Name',size=64,required=True, select=True),
+ 'note':fields.text('Text'),
+ 'action_id':fields.many2one('ir.actions.act_window', 'Action', select=True,required=True, ondelete='cascade'),
+ 'sequence':fields.integer('Sequence'),
+ 'state':fields.selection([('open', 'Not Started'),('done', 'Done'),('skip','Skipped')], string='State', required=True)
+ }
+ _defaults={
+ 'state': lambda *a: 'open',
+ 'sequence': lambda *a: 10,
+ }
+ _order="sequence"
+module_config_wizard_step()
+
+
+class module_configuration(osv.osv_memory):
+ _name='ir.module.module.configuration.wizard'
+ def _get_action_name(self, cr, uid, context={}):
+ item_obj = self.pool.get('ir.module.module.configuration.step')
+ item_ids = item_obj.search(cr, uid, [
+ ('state', '=', 'open'),
+ ], limit=1, context=context)
+ if item_ids and len(item_ids):
+ item = item_obj.browse(cr, uid, item_ids[0], context=context)
+ return item.note
+ else:
+ return "Your database is now fully configured.\n\nClick 'Continue' and enjoy your OpenERP experience..."
+ return False
+
+ def _get_action(self, cr, uid, context={}):
+ item_obj = self.pool.get('ir.module.module.configuration.step')
+ item_ids = item_obj.search(cr, uid, [
+ ('state', '=', 'open'),
+ ], limit=1, context=context)
+ if item_ids:
+ item = item_obj.browse(cr, uid, item_ids[0], context=context)
+ return item.id
+ return False
+
+ def _progress_get(self,cr,uid, context={}):
+ total = self.pool.get('ir.module.module.configuration.step').search_count(cr, uid, [], context)
+ todo = self.pool.get('ir.module.module.configuration.step').search_count(cr, uid, [('state','<>','open')], context)
+ return max(5.0,round(todo*100/total))
+
+ _columns = {
+ 'name': fields.text('Next Wizard',readonly=True),
+ 'progress': fields.float('Configuration Progress', readonly=True),
+ 'item_id':fields.many2one('ir.module.module.configuration.step', 'Next Configuration Wizard',invisible=True, readonly=True),
+ }
+ _defaults={
+ 'progress': _progress_get,
+ 'item_id':_get_action,
+ 'name':_get_action_name,
+ }
+ def button_skip(self,cr,uid,ids,context=None):
+ item_obj = self.pool.get('ir.module.module.configuration.step')
+ item_id=self.read(cr,uid,ids)[0]['item_id']
+ if item_id:
+ item = item_obj.browse(cr, uid, item_id, context=context)
+ item_obj.write(cr, uid, item.id, {
+ 'state': 'skip',
+ }, context=context)
+ return{
+ 'view_type': 'form',
+ "view_mode": 'form',
+ 'res_model': 'ir.module.module.configuration.wizard',
+ 'type': 'ir.actions.act_window',
+ 'target':'new',
+ }
+ return {'type':'ir.actions.act_window_close'}
+
+ def button_continue(self, cr, uid, ids, context=None):
+ item_obj = self.pool.get('ir.module.module.configuration.step')
+ item_id=self.read(cr,uid,ids)[0]['item_id']
+ if item_id:
+ item = item_obj.browse(cr, uid, item_id, context=context)
+ item_obj.write(cr, uid, item.id, {
+ 'state': 'done',
+ }, context=context)
+ return{
+ 'view_type': item.action_id.view_type,
+ 'view_id':item.action_id.view_id and [item.action_id.view_id.id] or False,
+ 'res_model': item.action_id.res_model,
+ 'type': item.action_id.type,
+ 'target':item.action_id.target,
+ }
+ return {'type':'ir.actions.act_window_close' }
+module_configuration()
+
+
+
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+