bc9aefc0a32ceac4ee7c09bc2273f9550fbc26f7
[odoo/odoo.git] / bin / addons / base / module / module.py
1 ##############################################################################
2 #
3 # Copyright (c) 2005 TINY SPRL. (http://tiny.be) All Rights Reserved.
4 #
5 # WARNING: This program as such is intended to be used by professional
6 # programmers who take the whole responsability of assessing all potential
7 # consequences resulting from its eventual inadequacies and bugs
8 # End users who are looking for a ready-to-use solution with commercial
9 # garantees and support are strongly adviced to contract a Free Software
10 # Service Company
11 #
12 # This program is Free Software; you can redistribute it and/or
13 # modify it under the terms of the GNU General Public License
14 # as published by the Free Software Foundation; either version 2
15 # of the License, or (at your option) any later version.
16 #
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 # GNU General Public License for more details.
21 #
22 # You should have received a copy of the GNU General Public License
23 # along with this program; if not, write to the Free Software
24 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
25 #
26 ##############################################################################
27
28 import tarfile
29 import re
30 import urllib
31 import os
32 import tools
33 from osv import fields, osv 
34
35 class module_repository(osv.osv):
36         _name = "ir.module.repository"
37         _description = "Module Repository"
38         _columns = {
39                 'name': fields.char('Name', size=128),
40                 'url': fields.char('Url', size=256, required=True),
41         }
42 module_repository()
43
44 def get_module_info(name):
45         try:
46                 f = file(os.path.join(tools.config['addons_path'], name, '__terp__.py'), 'r')
47                 data = f.read()
48                 info = eval(data)
49                 f.close()
50         except:
51                 return {}
52         return info
53
54 class module_category(osv.osv):
55         _name = "ir.module.category"
56         _description = "Module Category"
57         
58         def _module_nbr(self,cr,uid, ids, prop, unknow_none,context):
59                 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')
60                 result = dict(cr.fetchall())
61                 for id in ids:
62                         cr.execute('select id from ir_module_category where parent_id=%d', (id,))
63                         childs = [c for c, in cr.fetchall()]
64                         result[id] = reduce(lambda x,y:x+y, [result.get(c, 0) for c in childs], result.get(id, 0))
65                 return result
66                 
67         _columns = {
68                 'name': fields.char("Name", size=128, required=True),
69                 'parent_id': fields.many2one('ir.module.category', 'Parent Category', select=True),
70                 'child_ids': fields.one2many('ir.module.category', 'parent_id', 'Parent Category'),
71                 'module_nr': fields.function(_module_nbr, method=True, string='# of Modules', type='integer')
72         }
73         _order = 'name'
74 module_category()
75
76 class module(osv.osv):
77         _name = "ir.module.module"
78         _description = "Module"
79         
80         def _get_installed_version(self, cr, uid, ids, field_name=None, arg=None, context={}):
81                 res = {}
82                 for m in self.browse(cr, uid, ids):
83                         res[m.id] = get_module_info(m.name).get('version', False)
84                 return res
85
86         _columns = {
87                 'name': fields.char("Name", size=128, readonly=True, required=True),
88                 'category_id': fields.many2one('ir.module.category', 'Category', readonly=True),
89                 'shortdesc': fields.char('Short description', size=256, readonly=True),
90                 'description': fields.text("Description", readonly=True),
91                 'author': fields.char("Author", size=128, readonly=True),
92                 'website': fields.char("Website", size=256, readonly=True),
93                 'installed_version': fields.function(_get_installed_version, method=True, string='Installed version', type='char'),
94                 'latest_version': fields.char('Latest version', size=64, readonly=True),
95                 'url': fields.char('URL', size=128, readonly=True),
96                 'dependencies_id': fields.one2many('ir.module.module.dependency', 'module_id', 'Dependencies'),
97                 'state': fields.selection([
98                         ('uninstallable','Uninstallable'),
99                         ('uninstalled','Not Installed'),
100                         ('installed','Installed'),
101                         ('to upgrade','To be upgraded'),
102                         ('to remove','To be removed'),
103                         ('to install','To be installed')
104                 ], string='State', readonly=True),
105                 'demo': fields.boolean('Demo data'),
106         }
107         
108         _defaults = {
109                 'state': lambda *a: 'uninstalled',
110                 'demo': lambda *a: False,
111         }
112         _order = 'name'
113
114         def state_change(self, cr, uid, ids, newstate, context={}, level=50):
115                 if level<1:
116                         raise 'Recursion error in modules dependencies !'
117                 ids_dest = []
118                 for module in self.browse(cr, uid, ids):
119                         for dep in module.dependencies_id:
120                                 ids2 = self.search(cr, uid, [('name','=',dep.name)])
121                                 for id in ids2:
122                                         if (id not in ids_dest):
123                                                 ids_dest.append(id)
124                         if module.state=='uninstalled':
125                                 self.write(cr, uid, [module.id], {'state': newstate})
126                 if ids_dest:
127                         self.state_change(cr, uid, ids_dest, newstate, context, level-1)
128
129                 for module in self.browse(cr, uid, ids):
130                         if module.state!='installed':
131                                 demo=True
132                                 for dep in module.dependencies_id:
133                                         ids2 = self.search(cr, uid, [('name', '=', dep.name)])
134                                         for module2 in self.browse(cr, uid, ids2):
135                                                 demo = demo and module2.demo
136                                 self.write(cr, uid, [module.id], {'demo':demo})
137                 return True
138
139         def button_install(self, cr, uid, ids, context={}):
140                 return self.state_change(cr, uid, ids, 'to install', context)
141
142         def button_install_cancel(self, cr, uid, ids, context={}):
143                 self.write(cr, uid, ids, {'state': 'uninstalled'})
144                 return True
145
146         def button_uninstall(self, cr, uid, ids, context={}):
147                 self.write(cr, uid, ids, {'state': 'to remove'})
148                 return True
149
150         def button_remove_cancel(self, cr, uid, ids, context={}):
151                 self.write(cr, uid, ids, {'state': 'installed'})
152                 return True
153         def button_upgrade(self, cr, uid, ids, context={}):
154                 self.write(cr, uid, ids, {'state': 'to upgrade'})
155                 return True
156         def button_upgrade_cancel(self, cr, uid, ids, context={}):
157                 self.write(cr, uid, ids, {'state': 'installed'})
158                 return True
159         def button_update_translations(self, cr, uid, ids, context={}):
160                 cr.execute('select code from res_lang where translatable=TRUE')
161                 langs = [l[0] for l in cr.fetchall()]
162                 modules = self.read(cr, uid, ids, ['name'])
163                 for module in modules: 
164                         files = get_module_info(module['name']).get('translations', {})
165                         for lang in langs:
166                                 if files.has_key(lang):
167                                         filepath = files[lang]
168                                         # if filepath does not contain :// we prepend the path of the module
169                                         if filepath.find('://') == -1:
170                                                 filepath = os.path.join(tools.config['addons_path'], module['name'], filepath)
171                                         tools.trans_load(filepath, lang)
172                 return True
173
174         # update the list of available packages
175         def update_list(self, cr, uid, context={}):
176                 robj = self.pool.get('ir.module.repository')
177                 adp = tools.config['addons_path']
178
179                 # iterate through installed modules and mark them as being so
180                 for name in os.listdir(adp):
181                         if os.path.isdir(os.path.join(adp, name)):
182                                 version = get_module_info(name).get('version', False)
183                                 if version:
184                                         ids = self.search(cr, uid, [('name','=',name)])
185                                         if not ids:
186                                                 id = self.create(cr, uid, {
187                                                         'name': name,
188                                                         'latest_version': version,
189                                                         'state': 'installed',
190                                                 })
191                                         else:
192                                                 self.write(cr, uid, ids, {'state': 'installed'})
193                 
194                 # make the list of all installable modules
195                 for repository in robj.browse(cr, uid, robj.search(cr, uid, [])):
196                         index_page = urllib.urlopen(repository.url).read()
197                         modules = re.findall('.*<a href="([a-zA-Z0-9.\-]+)_([a-zA-Z0-9.\-]+)\.tar\.gz">.*', index_page)
198                         for name, version in modules:
199                                 # TODO: change this using urllib
200                                 url = os.path.join(repository.url, name + '_' + version + ".tar.gz")
201                                 ids = self.search(cr, uid, [('name','=',name)])
202                                 if not ids:
203                                         self.create(cr, uid, {
204                                                 'name': name,
205                                                 'latest_version': version,
206                                                 'url': url,
207                                                 'state': 'uninstalled',
208                                         })
209                                 else:
210                                         for r in self.read(cr, uid, ids, ['latest_version']):
211                                                 if r['latest_version'] < version:
212                                                         self.write(cr, uid, [r['id']], {'latest_version': version, 'url':url})
213                 return True
214
215         #
216         # TODO: update dependencies
217         #
218         def info_get(self, cr, uid, ids, context={}):
219                 categ_obj = self.pool.get('ir.module.category')
220                 
221                 for module in self.browse(cr, uid, ids, context):
222                         url = module.url
223                         adp = tools.config['addons_path']
224                         info = False
225                         if url:
226                                 tar = tarfile.open(mode="r|gz", fileobj=urllib.urlopen(url))
227                                 for tarinfo in tar:
228                                         if tarinfo.name.endswith('__terp__.py'):
229                                                 info = eval(tar.extractfile(tarinfo).read())
230                         elif os.path.isdir(os.path.join(adp, module.name)):
231                                 info = get_module_info(module.name)
232                         if info:
233                                 categ = info.get('category', 'Unclassified')
234                                 parent = False
235                                 for c in categ.split('/'):
236                                         ids = categ_obj.search(cr, uid, [('name','=',c), ('parent_id','=',parent)])
237                                         if not ids:
238                                                 parent = categ_obj.create(cr, uid, {'name':c, 'parent_id':parent})
239                                         else:
240                                                 parent = ids[0]
241                                 self.write(cr, uid, [module.id], {
242                                         'author': info.get('author',False),
243                                         'website': info.get('website',False),
244                                         'shortdesc': info.get('name',False),
245                                         'description': info.get('description',False),
246                                         'category_id': parent
247                                 })
248                 return True
249 module()
250
251 class module_dependency(osv.osv):
252         _name = "ir.module.module.dependency"
253         _description = "Module dependency"
254         _columns = {
255                 'name': fields.char('Name',  size=128),
256                 'module_id': fields.many2one('ir.module.module', 'Module', select=True),
257                 #'module_dest_id': fields.many2one('ir.module.module', 'Module'),
258                 'version_pattern': fields.char('Required Version', size=128),
259         }
260         # returns the ids of module version records which match all dependencies
261         # [version_id, ...]
262         def resolve(self, cr, uid, ids):
263                 vobj = self.pool.get('ir.module.module.version')
264                 objs = self.browse(cr, uid, ids)
265                 res = {}
266                 for o in objs:
267                         pattern = o.version_pattern and eval(o.version_pattern) or []
268                         print "pattern", pattern
269                         res[o.id] = vobj.search(cr, uid, [('module','=',o.module.id)]+pattern)
270                 #TODO: add smart dependencies resolver here
271                 # it should compute the best version for each module
272                 return [r[0] for r in res.itervalues()]
273 module_dependency()
274