1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2012 OpenERP S.A. (<http://openerp.com>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
28 from openerp import modules, pooler, release, tools, addons
29 from openerp.tools.parse_version import parse_version
30 from openerp.tools.translate import _
31 from openerp.osv import fields, osv, orm
33 _logger = logging.getLogger(__name__)
38 'res_model': 'base.module.upgrade',
40 'type': 'ir.actions.act_window',
44 class module_category(osv.osv):
45 _name = "ir.module.category"
46 _description = "Application"
48 def _module_nbr(self,cr,uid, ids, prop, unknow_none, context):
49 cr.execute('SELECT category_id, COUNT(*) \
50 FROM ir_module_module \
51 WHERE category_id IN %(ids)s \
52 OR category_id IN (SELECT id \
53 FROM ir_module_category \
54 WHERE parent_id IN %(ids)s) \
55 GROUP BY category_id', {'ids': tuple(ids)}
57 result = dict(cr.fetchall())
59 cr.execute('select id from ir_module_category where parent_id=%s', (id,))
60 result[id] = sum([result.get(c, 0) for (c,) in cr.fetchall()],
65 'name': fields.char("Name", size=128, required=True, translate=True, select=True),
66 'parent_id': fields.many2one('ir.module.category', 'Parent Application', select=True),
67 'child_ids': fields.one2many('ir.module.category', 'parent_id', 'Child Applications'),
68 'module_nr': fields.function(_module_nbr, string='Number of Modules', type='integer'),
69 'module_ids' : fields.one2many('ir.module.module', 'category_id', 'Modules'),
70 'description' : fields.text("Description", translate=True),
71 'sequence' : fields.integer('Sequence'),
72 'visible' : fields.boolean('Visible'),
73 'xml_id': fields.function(osv.osv.get_external_id, type='char', size=128, string="External ID"),
81 class module(osv.osv):
82 _name = "ir.module.module"
83 _description = "Module"
86 def get_module_info(cls, name):
89 info = modules.load_information_from_description_file(name)
90 info['version'] = release.major_version + '.' + info['version']
92 _logger.debug('Error when trying to fetch informations for '
93 'module %s', name, exc_info=True)
96 def _get_latest_version(self, cr, uid, ids, field_name=None, arg=None, context=None):
97 res = dict.fromkeys(ids, '')
98 for m in self.browse(cr, uid, ids):
99 res[m.id] = self.get_module_info(m.name).get('version', '')
102 def _get_views(self, cr, uid, ids, field_name=None, arg=None, context=None):
104 model_data_obj = self.pool.get('ir.model.data')
105 view_obj = self.pool.get('ir.ui.view')
106 report_obj = self.pool.get('ir.actions.report.xml')
107 menu_obj = self.pool.get('ir.ui.menu')
110 if field_name is None or 'views_by_module' in field_name:
111 dmodels.append('ir.ui.view')
112 if field_name is None or 'reports_by_module' in field_name:
113 dmodels.append('ir.actions.report.xml')
114 if field_name is None or 'menus_by_module' in field_name:
115 dmodels.append('ir.ui.menu')
116 assert dmodels, "no models for %s" % field_name
118 for module_rec in self.browse(cr, uid, ids, context=context):
119 res[module_rec.id] = {
120 'menus_by_module': [],
121 'reports_by_module': [],
122 'views_by_module': []
125 # Skip uninstalled modules below, no data to find anyway.
126 if module_rec.state not in ('installed', 'to upgrade', 'to remove'):
129 # then, search and group ir.model.data records
130 imd_models = dict( [(m,[]) for m in dmodels])
131 imd_ids = model_data_obj.search(cr,uid,[('module','=', module_rec.name),
132 ('model','in',tuple(dmodels))])
134 for imd_res in model_data_obj.read(cr, uid, imd_ids, ['model', 'res_id'], context=context):
135 imd_models[imd_res['model']].append(imd_res['res_id'])
137 # For each one of the models, get the names of these ids.
138 # We use try except, because views or menus may not exist.
140 res_mod_dic = res[module_rec.id]
141 for v in view_obj.browse(cr, uid, imd_models.get('ir.ui.view', []), context=context):
142 aa = v.inherit_id and '* INHERIT ' or ''
143 res_mod_dic['views_by_module'].append(aa + v.name + '('+v.type+')')
145 for rx in report_obj.browse(cr, uid, imd_models.get('ir.actions.report.xml', []), context=context):
146 res_mod_dic['reports_by_module'].append(rx.name)
148 for um in menu_obj.browse(cr, uid, imd_models.get('ir.ui.menu', []), context=context):
149 res_mod_dic['menus_by_module'].append(um.complete_name)
152 'Data not found for items of %s', module_rec.name)
153 except AttributeError, e:
155 'Data not found for items of %s %s', module_rec.name, str(e))
157 _logger.warning('Unknown error while fetching data of %s',
158 module_rec.name, exc_info=True)
159 for key, _ in res.iteritems():
160 for k, v in res[key].iteritems():
161 res[key][k] = "\n".join(sorted(v))
164 def _get_icon_image(self, cr, uid, ids, field_name=None, arg=None, context=None):
165 res = dict.fromkeys(ids, '')
166 for module in self.browse(cr, uid, ids, context=context):
167 path = addons.get_module_resource(module.name, 'static', 'src', 'img', 'icon.png')
169 image_file = tools.file_open(path, 'rb')
171 res[module.id] = image_file.read().encode('base64')
177 'name': fields.char("Technical Name", size=128, readonly=True, required=True, select=True),
178 'category_id': fields.many2one('ir.module.category', 'Category', readonly=True, select=True),
179 'shortdesc': fields.char('Module Name', size=256, readonly=True, translate=True),
180 'description': fields.text("Description", readonly=True, translate=True),
181 'author': fields.char("Author", size=128, readonly=True),
182 'maintainer': fields.char('Maintainer', size=128, readonly=True),
183 'contributors': fields.text('Contributors', readonly=True),
184 'website': fields.char("Website", size=256, readonly=True),
186 # attention: Incorrect field names !!
187 # installed_version refer the latest version (the one on disk)
188 # latest_version refer the installed version (the one in database)
189 # published_version refer the version available on the repository
190 'installed_version': fields.function(_get_latest_version,
191 string='Latest Version', type='char'),
192 'latest_version': fields.char('Installed Version', size=64, readonly=True),
193 'published_version': fields.char('Published Version', size=64, readonly=True),
195 'url': fields.char('URL', size=128, readonly=True),
196 'sequence': fields.integer('Sequence'),
197 'dependencies_id': fields.one2many('ir.module.module.dependency',
198 'module_id', 'Dependencies', readonly=True),
199 'auto_install': fields.boolean('Automatic Installation',
200 help='An auto-installable module is automatically installed by the '
201 'system when all its dependencies are satisfied. '
202 'If the module has no dependency, it is always installed.'),
203 'state': fields.selection([
204 ('uninstallable','Not Installable'),
205 ('uninstalled','Not Installed'),
206 ('installed','Installed'),
207 ('to upgrade','To be upgraded'),
208 ('to remove','To be removed'),
209 ('to install','To be installed')
210 ], string='State', readonly=True, select=True),
211 'demo': fields.boolean('Demo Data', readonly=True),
212 'license': fields.selection([
213 ('GPL-2', 'GPL Version 2'),
214 ('GPL-2 or any later version', 'GPL-2 or later version'),
215 ('GPL-3', 'GPL Version 3'),
216 ('GPL-3 or any later version', 'GPL-3 or later version'),
217 ('AGPL-3', 'Affero GPL-3'),
218 ('Other OSI approved licence', 'Other OSI Approved Licence'),
219 ('Other proprietary', 'Other Proprietary')
220 ], string='License', readonly=True),
221 'menus_by_module': fields.function(_get_views, string='Menus', type='text', multi="meta", store=True),
222 'reports_by_module': fields.function(_get_views, string='Reports', type='text', multi="meta", store=True),
223 'views_by_module': fields.function(_get_views, string='Views', type='text', multi="meta", store=True),
224 'certificate' : fields.char('Quality Certificate', size=64, readonly=True),
225 'application': fields.boolean('Application', readonly=True),
226 'icon': fields.char('Icon URL', size=128),
227 'icon_image': fields.function(_get_icon_image, string='Icon', type="binary"),
231 'state': 'uninstalled',
236 _order = 'sequence,name'
238 def _name_uniq_msg(self, cr, uid, ids, context=None):
239 return _('The name of the module must be unique !')
240 def _certificate_uniq_msg(self, cr, uid, ids, context=None):
241 return _('The certificate ID of the module must be unique !')
244 ('name_uniq', 'UNIQUE (name)',_name_uniq_msg ),
245 ('certificate_uniq', 'UNIQUE (certificate)',_certificate_uniq_msg )
248 def unlink(self, cr, uid, ids, context=None):
251 if isinstance(ids, (int, long)):
254 for mod in self.read(cr, uid, ids, ['state','name'], context):
255 if mod['state'] in ('installed', 'to upgrade', 'to remove', 'to install'):
256 raise orm.except_orm(_('Error'),
257 _('You try to remove a module that is installed or will be installed'))
258 mod_names.append(mod['name'])
259 #Removing the entry from ir_model_data
260 #ids_meta = self.pool.get('ir.model.data').search(cr, uid, [('name', '=', 'module_meta_information'), ('module', 'in', mod_names)])
263 # self.pool.get('ir.model.data').unlink(cr, uid, ids_meta, context)
265 return super(module, self).unlink(cr, uid, ids, context=context)
268 def _check_external_dependencies(terp):
269 depends = terp.get('external_dependencies')
272 for pydep in depends.get('python', []):
273 parts = pydep.split('.')
279 _, path, _ = imp.find_module(part, path and [path] or None)
281 raise ImportError('No module named %s' % (pydep,))
283 for binary in depends.get('bin', []):
284 if tools.find_in_path(binary) is None:
285 raise Exception('Unable to find %r in path' % (binary,))
288 def check_external_dependencies(cls, module_name, newstate='to install'):
289 terp = cls.get_module_info(module_name)
291 cls._check_external_dependencies(terp)
293 if newstate == 'to install':
294 msg = _('Unable to install module "%s" because an external dependency is not met: %s')
295 elif newstate == 'to upgrade':
296 msg = _('Unable to upgrade module "%s" because an external dependency is not met: %s')
298 msg = _('Unable to process module "%s" because an external dependency is not met: %s')
299 raise orm.except_orm(_('Error'), msg % (module_name, e.args[0]))
301 def state_update(self, cr, uid, ids, newstate, states_to_update, context=None, level=100):
303 raise orm.except_orm(_('Error'), _('Recursion error in modules dependencies !'))
305 for module in self.browse(cr, uid, ids, context=context):
307 for dep in module.dependencies_id:
308 if dep.state == 'unknown':
309 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,))
310 ids2 = self.search(cr, uid, [('name','=',dep.name)])
311 if dep.state != newstate:
312 mdemo = self.state_update(cr, uid, ids2, newstate, states_to_update, context, level-1,) or mdemo
314 od = self.browse(cr, uid, ids2)[0]
315 mdemo = od.demo or mdemo
317 self.check_external_dependencies(module.name, newstate)
318 if not module.dependencies_id:
320 if module.state in states_to_update:
321 self.write(cr, uid, [module.id], {'state': newstate, 'demo':mdemo})
325 def button_install(self, cr, uid, ids, context=None):
327 # Mark the given modules to be installed.
328 self.state_update(cr, uid, ids, 'to install', ['uninstalled'], context)
330 # Mark (recursively) the newly satisfied modules to also be installed:
332 # Select all auto-installable (but not yet installed) modules.
333 domain = [('state', '=', 'uninstalled'), ('auto_install', '=', True),]
334 uninstalled_ids = self.search(cr, uid, domain, context=context)
335 uninstalled_modules = self.browse(cr, uid, uninstalled_ids, context=context)
337 # Keep those with all their dependencies satisfied.
338 def all_depencies_satisfied(m):
339 return all(x.state in ('to install', 'installed', 'to upgrade') for x in m.dependencies_id)
340 to_install_modules = filter(all_depencies_satisfied, uninstalled_modules)
341 to_install_ids = map(lambda m: m.id, to_install_modules)
343 # Mark them to be installed.
345 self.button_install(cr, uid, to_install_ids, context=context)
346 return dict(ACTION_DICT, name=_('Install'))
348 def button_immediate_install(self, cr, uid, ids, context=None):
349 """ Installs the selected module(s) immediately and fully,
350 returns the next res.config action to execute
352 :param ids: identifiers of the modules to install
353 :returns: next res.config item to execute
354 :rtype: dict[str, object]
356 self.button_install(cr, uid, ids, context=context)
358 _, pool = pooler.restart_pool(cr.dbname, update_module=True)
360 config = pool.get('res.config').next(cr, uid, [], context=context) or {}
361 if config.get('type') not in ('ir.actions.reload', 'ir.actions.act_window_close'):
365 menu_ids = self.root_menus(cr,uid,ids,context)
367 'type': 'ir.actions.client',
369 'params': {'menu_id': menu_ids and menu_ids[0] or False},
372 def button_install_cancel(self, cr, uid, ids, context=None):
373 self.write(cr, uid, ids, {'state': 'uninstalled', 'demo':False})
376 def module_uninstall(self, cr, uid, ids, context=None):
377 """Perform the various steps required to uninstall a module completely
378 including the deletion of all database structures created by the module:
379 tables, columns, constraints, etc."""
380 ir_model_data = self.pool.get('ir.model.data')
381 modules_to_remove = [m.name for m in self.browse(cr, uid, ids, context)]
382 data_ids = ir_model_data.search(cr, uid, [('module', 'in', modules_to_remove)])
383 ir_model_data._module_data_uninstall(cr, uid, data_ids, context)
384 ir_model_data.unlink(cr, uid, data_ids, context)
385 self.write(cr, uid, ids, {'state': 'uninstalled'})
388 def downstream_dependencies(self, cr, uid, ids, known_dep_ids=None,
389 exclude_states=['uninstalled','uninstallable','to remove'],
391 """Return the ids of all modules that directly or indirectly depend
392 on the given module `ids`, and that satisfy the `exclude_states`
394 if not ids: return []
395 known_dep_ids = set(known_dep_ids or [])
396 cr.execute('''SELECT DISTINCT m.id
398 ir_module_module_dependency d
400 ir_module_module m ON (d.module_id=m.id)
402 d.name IN (SELECT name from ir_module_module where id in %s) AND
403 m.state NOT IN %s AND
405 (tuple(ids),tuple(exclude_states), tuple(known_dep_ids or ids)))
406 new_dep_ids = set([m[0] for m in cr.fetchall()])
407 missing_mod_ids = new_dep_ids - known_dep_ids
408 known_dep_ids |= new_dep_ids
410 known_dep_ids |= set(self.downstream_dependencies(cr, uid, list(missing_mod_ids),
411 known_dep_ids, exclude_states,context))
412 return list(known_dep_ids)
414 def button_uninstall(self, cr, uid, ids, context=None):
415 if any(m.name == 'base' for m in self.browse(cr, uid, ids)):
416 raise orm.except_orm(_('Error'), _("The `base` module cannot be uninstalled"))
417 dep_ids = self.downstream_dependencies(cr, uid, ids, context=context)
418 self.write(cr, uid, ids + dep_ids, {'state': 'to remove'})
419 return dict(ACTION_DICT, name=_('Uninstall'))
421 def button_uninstall_cancel(self, cr, uid, ids, context=None):
422 self.write(cr, uid, ids, {'state': 'installed'})
425 def button_upgrade(self, cr, uid, ids, context=None):
426 depobj = self.pool.get('ir.module.module.dependency')
427 todo = self.browse(cr, uid, ids, context=context)
428 self.update_list(cr, uid)
434 if mod.state not in ('installed','to upgrade'):
435 raise orm.except_orm(_('Error'),
436 _("Can not upgrade module '%s'. It is not installed.") % (mod.name,))
437 self.check_external_dependencies(mod.name, 'to upgrade')
438 iids = depobj.search(cr, uid, [('name', '=', mod.name)], context=context)
439 for dep in depobj.browse(cr, uid, iids, context=context):
440 if dep.module_id.state=='installed' and dep.module_id not in todo:
441 todo.append(dep.module_id)
443 ids = map(lambda x: x.id, todo)
444 self.write(cr, uid, ids, {'state':'to upgrade'}, context=context)
448 for dep in mod.dependencies_id:
449 if dep.state == 'unknown':
450 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,))
451 if dep.state == 'uninstalled':
452 ids2 = self.search(cr, uid, [('name','=',dep.name)])
453 to_install.extend(ids2)
455 self.button_install(cr, uid, to_install, context=context)
456 return dict(ACTION_DICT, name=_('Upgrade'))
458 def button_upgrade_cancel(self, cr, uid, ids, context=None):
459 self.write(cr, uid, ids, {'state': 'installed'})
462 def button_update_translations(self, cr, uid, ids, context=None):
463 self.update_translations(cr, uid, ids)
467 def get_values_from_terp(terp):
469 'description': terp.get('description', ''),
470 'shortdesc': terp.get('name', ''),
471 'author': terp.get('author', 'Unknown'),
472 'maintainer': terp.get('maintainer', False),
473 'contributors': ', '.join(terp.get('contributors', [])) or False,
474 'website': terp.get('website', ''),
475 'license': terp.get('license', 'AGPL-3'),
476 'certificate': terp.get('certificate') or False,
477 'sequence': terp.get('sequence', 100),
478 'application': terp.get('application', False),
479 'auto_install': terp.get('auto_install', False),
480 'icon': terp.get('icon', False),
483 # update the list of available packages
484 def update_list(self, cr, uid, context=None):
485 res = [0, 0] # [update, add]
487 known_mods = self.browse(cr, uid, self.search(cr, uid, []))
488 known_mods_names = dict([(m.name, m) for m in known_mods])
490 # iterate through detected modules and update/create them in db
491 for mod_name in modules.get_modules():
492 mod = known_mods_names.get(mod_name)
493 terp = self.get_module_info(mod_name)
494 values = self.get_values_from_terp(terp)
499 old = getattr(mod, key)
500 updated = isinstance(values[key], basestring) and tools.ustr(values[key]) or values[key]
501 if not old == updated:
502 updated_values[key] = values[key]
503 if terp.get('installable', True) and mod.state == 'uninstallable':
504 updated_values['state'] = 'uninstalled'
505 if parse_version(terp.get('version', '')) > parse_version(mod.latest_version or ''):
508 self.write(cr, uid, mod.id, updated_values)
510 mod_path = modules.get_module_path(mod_name)
513 if not terp or not terp.get('installable', True):
515 id = self.create(cr, uid, dict(name=mod_name, state='uninstalled', **values))
516 mod = self.browse(cr, uid, id)
519 self._update_dependencies(cr, uid, mod, terp.get('depends', []))
520 self._update_category(cr, uid, mod, terp.get('category', 'Uncategorized'))
524 def download(self, cr, uid, ids, download=True, context=None):
526 for mod in self.browse(cr, uid, ids, context=context):
529 match = re.search('-([a-zA-Z0-9\._-]+)(\.zip)', mod.url, re.I)
532 version = match.group(1)
533 if parse_version(mod.installed_version or '0') >= parse_version(version):
538 zip_content = urllib.urlopen(mod.url).read()
539 fname = modules.get_module_path(str(mod.name)+'.zip', downloaded=True)
541 with open(fname, 'wb') as fp:
542 fp.write(zip_content)
544 _logger.exception('Error when trying to create module '
546 raise orm.except_orm(_('Error'), _('Can not create the module file:\n %s') % (fname,))
547 terp = self.get_module_info(mod.name)
548 self.write(cr, uid, mod.id, self.get_values_from_terp(terp))
549 cr.execute('DELETE FROM ir_module_module_dependency ' \
550 'WHERE module_id = %s', (mod.id,))
551 self._update_dependencies(cr, uid, mod, terp.get('depends',
553 self._update_category(cr, uid, mod, terp.get('category',
556 zimp = zipimport.zipimporter(fname)
557 zimp.load_module(mod.name)
560 def _update_dependencies(self, cr, uid, mod_browse, depends=None):
563 existing = set(x.name for x in mod_browse.dependencies_id)
564 needed = set(depends)
565 for dep in (needed - existing):
566 cr.execute('INSERT INTO ir_module_module_dependency (module_id, name) values (%s, %s)', (mod_browse.id, dep))
567 for dep in (existing - needed):
568 cr.execute('DELETE FROM ir_module_module_dependency WHERE module_id = %s and name = %s', (mod_browse.id, dep))
570 def _update_category(self, cr, uid, mod_browse, category='Uncategorized'):
571 current_category = mod_browse.category_id
572 current_category_path = []
573 while current_category:
574 current_category_path.insert(0, current_category.name)
575 current_category = current_category.parent_id
577 categs = category.split('/')
578 if categs != current_category_path:
582 cr.execute('SELECT id FROM ir_module_category WHERE name=%s AND parent_id=%s', (categs[0], p_id))
584 cr.execute('SELECT id FROM ir_module_category WHERE name=%s AND parent_id is NULL', (categs[0],))
587 cr.execute('INSERT INTO ir_module_category (name, parent_id) VALUES (%s, %s) RETURNING id', (categs[0], p_id))
588 c_id = cr.fetchone()[0]
593 self.write(cr, uid, [mod_browse.id], {'category_id': p_id})
595 def update_translations(self, cr, uid, ids, filter_lang=None, context=None):
599 pool = pooler.get_pool(cr.dbname)
600 lang_obj = pool.get('res.lang')
601 lang_ids = lang_obj.search(cr, uid, [('translatable', '=', True)])
602 filter_lang = [lang.code for lang in lang_obj.browse(cr, uid, lang_ids)]
603 elif not isinstance(filter_lang, (list, tuple)):
604 filter_lang = [filter_lang]
606 for mod in self.browse(cr, uid, ids):
607 if mod.state != 'installed':
609 modpath = modules.get_module_path(mod.name)
611 # unable to find the module. we skip
613 for lang in filter_lang:
614 iso_lang = tools.get_iso_codes(lang)
615 f = modules.get_module_resource(mod.name, 'i18n', iso_lang + '.po')
616 context2 = context and context.copy() or {}
617 if f and '_' in iso_lang:
618 iso_lang2 = iso_lang.split('_')[0]
619 f2 = modules.get_module_resource(mod.name, 'i18n', iso_lang2 + '.po')
621 _logger.info('module %s: loading base translation file %s for language %s', mod.name, iso_lang2, lang)
622 tools.trans_load(cr, f2, lang, verbose=False, context=context)
623 context2['overwrite'] = True
624 # Implementation notice: we must first search for the full name of
625 # the language derivative, like "en_UK", and then the generic,
627 if (not f) and '_' in iso_lang:
628 iso_lang = iso_lang.split('_')[0]
629 f = modules.get_module_resource(mod.name, 'i18n', iso_lang + '.po')
631 _logger.info('module %s: loading translation file (%s) for language %s', mod.name, iso_lang, lang)
632 tools.trans_load(cr, f, lang, verbose=False, context=context2)
633 elif iso_lang != 'en':
634 _logger.warning('module %s: no translation for language %s', mod.name, iso_lang)
636 def check(self, cr, uid, ids, context=None):
637 for mod in self.browse(cr, uid, ids, context=context):
638 if not mod.description:
639 _logger.warning('module %s: description is empty !', mod.name)
641 if not mod.certificate or not mod.certificate.isdigit():
642 _logger.info('module %s: no quality certificate', mod.name)
644 val = long(mod.certificate[2:]) % 97 == 29
646 _logger.critical('module %s: invalid quality certificate: %s', mod.name, mod.certificate)
647 raise osv.except_osv(_('Error'), _('Module %s: Invalid Quality Certificate') % (mod.name,))
649 def root_menus(self, cr, uid, ids, context=None):
650 """ Return root menu ids the menus created by the modules whose ids are
653 :param list[int] ids: modules to get menus from
655 values = self.read(cr, uid, ids, ['name'], context=context)
656 module_names = [i['name'] for i in values]
658 ids = self.pool.get('ir.model.data').search(cr, uid, [ ('model', '=', 'ir.ui.menu'), ('module', 'in', module_names) ], context=context)
659 values = self.pool.get('ir.model.data').read(cr, uid, ids, ['res_id'], context=context)
660 all_menu_ids = [i['res_id'] for i in values]
663 for menu in self.pool.get('ir.ui.menu').browse(cr, uid, all_menu_ids, context=context):
664 while menu.parent_id:
665 menu = menu.parent_id
666 if not menu.id in root_menu_ids:
667 root_menu_ids.append((menu.sequence,menu.id))
669 root_menu_ids = [i[1] for i in root_menu_ids]
672 class module_dependency(osv.osv):
673 _name = "ir.module.module.dependency"
674 _description = "Module dependency"
676 def _state(self, cr, uid, ids, name, args, context=None):
678 mod_obj = self.pool.get('ir.module.module')
679 for md in self.browse(cr, uid, ids):
680 ids = mod_obj.search(cr, uid, [('name', '=', md.name)])
682 result[md.id] = mod_obj.read(cr, uid, [ids[0]], ['state'])[0]['state']
684 result[md.id] = 'unknown'
688 # The dependency name
689 'name': fields.char('Name', size=128, select=True),
691 # The module that depends on it
692 'module_id': fields.many2one('ir.module.module', 'Module', select=True, ondelete='cascade'),
694 'state': fields.function(_state, type='selection', selection=[
695 ('uninstallable','Uninstallable'),
696 ('uninstalled','Not Installed'),
697 ('installed','Installed'),
698 ('to upgrade','To be upgraded'),
699 ('to remove','To be removed'),
700 ('to install','To be installed'),
701 ('unknown', 'Unknown'),
702 ], string='State', readonly=True, select=True),
705 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: