[MIG] base/module

This commit is contained in:
Raphael Collet
2016-05-11 11:40:25 +02:00
parent de54107fec
commit d465346f1e
9 changed files with 518 additions and 628 deletions
+326 -380
View File
@@ -1,40 +1,31 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details. # Part of Odoo. See LICENSE file for full copyright and licensing details.
from collections import defaultdict
from operator import attrgetter
import importlib
import logging
import os
import shutil
import tempfile
import urllib2
import urlparse
import zipfile
from docutils import nodes from docutils import nodes
from docutils.core import publish_string from docutils.core import publish_string
from docutils.transforms import Transform, writer_aux from docutils.transforms import Transform, writer_aux
from docutils.writers.html4css1 import Writer from docutils.writers.html4css1 import Writer
import importlib
import logging
from operator import attrgetter
import os
import re
import shutil
import tempfile
import urllib
import urllib2
import urlparse
import zipfile
import zipimport
import lxml.html import lxml.html
from openerp.exceptions import UserError
try: try:
from cStringIO import StringIO from cStringIO import StringIO
except ImportError: except ImportError:
from StringIO import StringIO # NOQA from StringIO import StringIO # NOQA
import openerp import odoo
import openerp.exceptions from odoo import api, fields, models, modules, tools, _
from openerp import modules, tools from odoo.exceptions import AccessDenied, UserError
from openerp.modules.db import create_categories from odoo.tools.parse_version import parse_version
from openerp.modules import get_module_resource
from openerp.tools import ormcache
from openerp.tools.parse_version import parse_version
from openerp.tools.translate import _
from openerp.tools import html_sanitize
from openerp.osv import osv, orm, fields
from openerp import api, fields as fields2
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@@ -61,42 +52,45 @@ def backup(path, raise_exception=True):
cnt += 1 cnt += 1
class module_category(osv.osv): class ModuleCategory(models.Model):
_name = "ir.module.category" _name = "ir.module.category"
_description = "Application" _description = "Application"
_order = 'name'
def _module_nbr(self, cr, uid, ids, prop, unknow_none, context): @api.depends('module_ids')
def _compute_module_nr(self):
cr = self._cr
cr.execute('SELECT category_id, COUNT(*) \ cr.execute('SELECT category_id, COUNT(*) \
FROM ir_module_module \ FROM ir_module_module \
WHERE category_id IN %(ids)s \ WHERE category_id IN %(ids)s \
OR category_id IN (SELECT id \ OR category_id IN (SELECT id \
FROM ir_module_category \ FROM ir_module_category \
WHERE parent_id IN %(ids)s) \ WHERE parent_id IN %(ids)s) \
GROUP BY category_id', {'ids': tuple(ids)} GROUP BY category_id', {'ids': tuple(self.ids)}
) )
result = dict(cr.fetchall()) result = dict(cr.fetchall())
for id in ids: for cat in self.filtered('id'):
cr.execute('select id from ir_module_category where parent_id=%s', (id,)) cr.execute('SELECT id FROM ir_module_category WHERE parent_id=%s', (cat.id,))
result[id] = sum([result.get(c, 0) for (c,) in cr.fetchall()], cat.module_nr = sum([result.get(c, 0) for (c,) in cr.fetchall()], result.get(cat.id, 0))
result.get(id, 0))
return result
_columns = { name = fields.Char(string='Name', required=True, translate=True, index=True)
'name': fields.char("Name", required=True, translate=True, select=True), parent_id = fields.Many2one('ir.module.category', string='Parent Application', index=True)
'parent_id': fields.many2one('ir.module.category', 'Parent Application', select=True), child_ids = fields.One2many('ir.module.category', 'parent_id', string='Child Applications')
'child_ids': fields.one2many('ir.module.category', 'parent_id', 'Child Applications'), module_nr = fields.Integer(string='Number of Apps', compute='_compute_module_nr')
'module_nr': fields.function(_module_nbr, string='Number of Apps', type='integer'), module_ids = fields.One2many('ir.module.module', 'category_id', string='Modules')
'module_ids': fields.one2many('ir.module.module', 'category_id', 'Modules'), description = fields.Text(string='Description', translate=True)
'description': fields.text("Description", translate=True), sequence = fields.Integer(string='Sequence')
'sequence': fields.integer('Sequence'), visible = fields.Boolean(string='Visible', default=True)
'visible': fields.boolean('Visible'), xml_id = fields.Char(string='External ID', compute='_compute_xml_id')
'xml_id': fields.function(osv.osv.get_external_id, type='char', string="External ID"),
} def _compute_xml_id(self):
_order = 'name' xml_ids = defaultdict(list)
domain = [('model', '=', self._name), ('res_id', 'in', self.ids)]
for data in self.env['ir.model.data'].search_read(domain, ['module', 'name', 'res_id']):
xml_ids[data['res_id']].append("%s.%s" % (data['module'], data['name']))
for cat in self:
cat.xml_id = xml_ids.get(cat.id, [''])[0]
_defaults = {
'visible': 1,
}
class MyFilterMessages(Transform): class MyFilterMessages(Transform):
""" """
@@ -107,7 +101,6 @@ class MyFilterMessages(Transform):
the `settings_override` dictionary, but if we use it, we can't see them the `settings_override` dictionary, but if we use it, we can't see them
and generate warnings.) and generate warnings.)
""" """
default_priority = 870 default_priority = 870
def apply(self): def apply(self):
@@ -115,43 +108,52 @@ class MyFilterMessages(Transform):
_logger.warning("docutils' system message present: %s", str(node)) _logger.warning("docutils' system message present: %s", str(node))
node.parent.remove(node) node.parent.remove(node)
class MyWriter(Writer): class MyWriter(Writer):
""" """
Custom docutils html4ccs1 writer that doesn't add the warnings to the Custom docutils html4ccs1 writer that doesn't add the warnings to the
output document. output document.
""" """
def get_transforms(self): def get_transforms(self):
return [MyFilterMessages, writer_aux.Admonitions] return [MyFilterMessages, writer_aux.Admonitions]
class module(osv.osv):
STATES = [
('uninstallable', 'Uninstallable'),
('uninstalled', 'Not Installed'),
('installed', 'Installed'),
('to upgrade', 'To be upgraded'),
('to remove', 'To be removed'),
('to install', 'To be installed'),
]
class Module(models.Model):
_name = "ir.module.module" _name = "ir.module.module"
_rec_name = "shortdesc" _rec_name = "shortdesc"
_description = "Module" _description = "Module"
_order = 'sequence,name'
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False): @api.model
res = super(module, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=False) def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
result = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'action_server_module_immediate_install')[1] res = super(Module, self).fields_view_get(view_id, view_type, toolbar=toolbar, submenu=False)
if view_type == 'form': if view_type == 'form' and res.get('toolbar',False):
if res.get('toolbar',False): install_id = self.env.ref('base.action_server_module_immediate_install').id
list = [rec for rec in res['toolbar']['action'] if rec.get('id', False) != result] action = [rec for rec in res['toolbar']['action'] if rec.get('id', False) != install_id]
res['toolbar'] = {'action': list} res['toolbar'] = {'action': action}
return res return res
@classmethod @classmethod
def get_module_info(cls, name): def get_module_info(cls, name):
info = {}
try: try:
info = modules.load_information_from_description_file(name) return modules.load_information_from_description_file(name)
except Exception: except Exception:
_logger.debug('Error when trying to fetch information for ' _logger.debug('Error when trying to fetch information for module %s', name, exc_info=True)
'module %s', name, exc_info=True) return {}
return info
def _get_desc(self, cr, uid, ids, field_name=None, arg=None, context=None): @api.depends('name', 'description')
res = dict.fromkeys(ids, '') def _get_desc(self):
for module in self.browse(cr, uid, ids, context=context): for module in self:
path = get_module_resource(module.name, 'static/description/index.html') path = modules.get_module_resource(module.name, 'static/description/index.html')
if path: if path:
with tools.file_open(path, 'rb') as desc_file: with tools.file_open(path, 'rb') as desc_file:
doc = desc_file.read() doc = desc_file.read()
@@ -159,7 +161,7 @@ class module(osv.osv):
for element, attribute, link, pos in html.iterlinks(): for element, attribute, link, pos in html.iterlinks():
if element.get('src') and not '//' in element.get('src') and not 'static/' in element.get('src'): if element.get('src') and not '//' in element.get('src') and not 'static/' in element.get('src'):
element.set('src', "/%s/static/description/%s" % (module.name, element.get('src'))) element.set('src', "/%s/static/description/%s" % (module.name, element.get('src')))
res[module.id] = html_sanitize(lxml.html.tostring(html)) module.description_html = tools.html_sanitize(lxml.html.tostring(html))
else: else:
overrides = { overrides = {
'embed_stylesheet': False, 'embed_stylesheet': False,
@@ -168,170 +170,115 @@ class module(osv.osv):
'xml_declaration': False, 'xml_declaration': False,
} }
output = publish_string(source=module.description or '', settings_overrides=overrides, writer=MyWriter()) output = publish_string(source=module.description or '', settings_overrides=overrides, writer=MyWriter())
res[module.id] = html_sanitize(output) module.description_html = tools.html_sanitize(output)
return res
def _get_latest_version(self, cr, uid, ids, field_name=None, arg=None, context=None): @api.depends('name')
def _get_latest_version(self):
default_version = modules.adapt_version('1.0') default_version = modules.adapt_version('1.0')
res = dict.fromkeys(ids, default_version) for module in self:
for m in self.browse(cr, uid, ids): module.installed_version = self.get_module_info(module.name).get('version', default_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): @api.depends('name', 'state')
res = {} def _get_views(self):
model_data_obj = self.pool.get('ir.model.data') IrModelData = self.env['ir.model.data'].with_context(active_test=True)
dmodels = ['ir.ui.view', 'ir.actions.report.xml', 'ir.ui.menu']
dmodels = []
if field_name is None or 'views_by_module' in field_name:
dmodels.append('ir.ui.view')
if field_name is None or 'reports_by_module' in field_name:
dmodels.append('ir.actions.report.xml')
if field_name is None or 'menus_by_module' in field_name:
dmodels.append('ir.ui.menu')
assert dmodels, "no models for %s" % field_name
for module_rec in self.browse(cr, uid, ids, context=context):
res_mod_dic = res[module_rec.id] = {
'menus_by_module': [],
'reports_by_module': [],
'views_by_module': []
}
for module in self:
# Skip uninstalled modules below, no data to find anyway. # Skip uninstalled modules below, no data to find anyway.
if module_rec.state not in ('installed', 'to upgrade', 'to remove'): if module.state not in ('installed', 'to upgrade', 'to remove'):
module.views_by_module = ""
module.reports_by_module = ""
module.menus_by_module = ""
continue continue
# then, search and group ir.model.data records # then, search and group ir.model.data records
imd_models = dict([(m, []) for m in dmodels]) imd_models = defaultdict(list)
imd_ids = model_data_obj.search(cr, uid, [ imd_domain = [('module', '=', module.name), ('model', 'in', tuple(dmodels))]
('module', '=', module_rec.name), for data in IrModelData.search(imd_domain):
('model', 'in', tuple(dmodels)) imd_models[data.model].append(data.res_id)
])
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'])
def browse(model): def browse(model):
M = self.pool[model] # as this method is called before the module update, some xmlid
# as this method is called before the module update, some xmlid may be invalid at this stage # may be invalid at this stage; explictly filter records before
# explictly filter records before reading them # reading them
ids = M.exists(cr, uid, imd_models.get(model, []), context) return self.env[model].browse(imd_models[model]).exists()
return M.browse(cr, uid, ids, context)
def format_view(v): def format_view(v):
aa = v.inherit_id and '* INHERIT ' or '' return '%s%s (%s)' % (v.inherit_id and '* INHERIT ' or '', v.name, v.type)
return '%s%s (%s)' % (aa, v.name, v.type)
res_mod_dic['views_by_module'] = map(format_view, browse('ir.ui.view')) module.views_by_module = "\n".join(sorted(map(format_view, browse('ir.ui.view'))))
res_mod_dic['reports_by_module'] = map(attrgetter('name'), browse('ir.actions.report.xml')) module.reports_by_module = "\n".join(sorted(map(attrgetter('name'), browse('ir.actions.report.xml'))))
res_mod_dic['menus_by_module'] = map(attrgetter('complete_name'), browse('ir.ui.menu')) module.menus_by_module = "\n".join(sorted(map(attrgetter('complete_name'), browse('ir.ui.menu'))))
for key in res.iterkeys(): @api.depends('name')
for k, v in res[key].iteritems(): def _get_icon_image(self):
res[key][k] = "\n".join(sorted(v)) for module in self:
return res module.icon_image = ''
path = modules.get_module_resource(module.name, 'static', 'description', 'icon.png')
def _get_icon_image(self, cr, uid, ids, field_name=None, arg=None, context=None):
res = dict.fromkeys(ids, '')
for module in self.browse(cr, uid, ids, context=context):
path = get_module_resource(module.name, 'static', 'description', 'icon.png')
if path: if path:
image_file = tools.file_open(path, 'rb') with tools.file_open(path, 'rb') as image_file:
try: module.icon_image = image_file.read().encode('base64')
res[module.id] = image_file.read().encode('base64')
finally:
image_file.close()
return res
_columns = { name = fields.Char('Technical Name', readonly=True, required=True, index=True)
'name': fields.char("Technical Name", readonly=True, required=True, select=True), category_id = fields.Many2one('ir.module.category', string='Category', readonly=True, index=True)
'category_id': fields.many2one('ir.module.category', 'Category', readonly=True, select=True), shortdesc = fields.Char('Module Name', readonly=True, translate=True)
'shortdesc': fields.char('Module Name', readonly=True, translate=True), summary = fields.Char('Summary', readonly=True, translate=True)
'summary': fields.char('Summary', readonly=True, translate=True), description = fields.Text('Description', readonly=True, translate=True)
'description': fields.text("Description", readonly=True, translate=True), description_html = fields.Html('Description HTML', compute='_get_desc')
'description_html': fields.function(_get_desc, string='Description HTML', type='html', method=True, readonly=True), author = fields.Char("Author", readonly=True)
'author': fields.char("Author", readonly=True), maintainer = fields.Char('Maintainer', readonly=True)
'maintainer': fields.char('Maintainer', readonly=True), contributors = fields.Text('Contributors', readonly=True)
'contributors': fields.text('Contributors', readonly=True), website = fields.Char("Website", readonly=True)
'website': fields.char("Website", readonly=True),
# attention: Incorrect field names !! # attention: Incorrect field names !!
# installed_version refers the latest version (the one on disk) # installed_version refers the latest version (the one on disk)
# latest_version refers the installed version (the one in database) # latest_version refers the installed version (the one in database)
# published_version refers the version available on the repository # published_version refers the version available on the repository
'installed_version': fields.function(_get_latest_version, string='Latest Version', type='char'), installed_version = fields.Char('Latest Version', compute='_get_latest_version')
'latest_version': fields.char('Installed Version', readonly=True), latest_version = fields.Char('Installed Version', readonly=True)
'published_version': fields.char('Published Version', readonly=True), published_version = fields.Char('Published Version', readonly=True)
'url': fields.char('URL', readonly=True), url = fields.Char('URL', readonly=True)
'sequence': fields.integer('Sequence'), sequence = fields.Integer('Sequence', default=100)
'dependencies_id': fields.one2many('ir.module.module.dependency', 'module_id', 'Dependencies', readonly=True), dependencies_id = fields.One2many('ir.module.module.dependency', 'module_id',
'auto_install': fields.boolean('Automatic Installation', string='Dependencies', readonly=True)
help='An auto-installable module is automatically installed by the ' auto_install = fields.Boolean('Automatic Installation',
'system when all its dependencies are satisfied. ' help='An auto-installable module is automatically installed by the '
'If the module has no dependency, it is always installed.'), 'system when all its dependencies are satisfied. '
'state': fields.selection([ 'If the module has no dependency, it is always installed.')
('uninstallable', 'Not Installable'), state = fields.Selection(STATES, string='Status', default='uninstalled', readonly=True, index=True)
('uninstalled', 'Not Installed'), demo = fields.Boolean('Demo Data', default=False, readonly=True)
('installed', 'Installed'), license = fields.Selection([
('to upgrade', 'To be upgraded'), ('GPL-2', 'GPL Version 2'),
('to remove', 'To be removed'), ('GPL-2 or any later version', 'GPL-2 or later version'),
('to install', 'To be installed') ('GPL-3', 'GPL Version 3'),
], string='Status', readonly=True, select=True), ('GPL-3 or any later version', 'GPL-3 or later version'),
'demo': fields.boolean('Demo Data', readonly=True), ('AGPL-3', 'Affero GPL-3'),
'license': fields.selection([ ('LGPL-3', 'LGPL Version 3'),
('GPL-2', 'GPL Version 2'), ('Other OSI approved licence', 'Other OSI Approved Licence'),
('GPL-2 or any later version', 'GPL-2 or later version'), ('OEEL-1', 'Odoo Enterprise Edition License v1.0'),
('GPL-3', 'GPL Version 3'), ('Other proprietary', 'Other Proprietary')
('GPL-3 or any later version', 'GPL-3 or later version'), ], string='License', default='LGPL-3', readonly=True)
('AGPL-3', 'Affero GPL-3'), menus_by_module = fields.Text(string='Menus', compute='_get_views', store=True)
('LGPL-3', 'LGPL Version 3'), reports_by_module = fields.Text(string='Reports', compute='_get_views', store=True)
('Other OSI approved licence', 'Other OSI Approved Licence'), views_by_module = fields.Text(string='Views', compute='_get_views', store=True)
('OEEL-1', 'Odoo Enterprise Edition License v1.0'), application = fields.Boolean('Application', readonly=True)
('Other proprietary', 'Other Proprietary') icon = fields.Char('Icon URL')
], string='License', readonly=True), icon_image = fields.Binary(string='Icon', compute='_get_icon_image')
'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),
'views_by_module': fields.function(_get_views, string='Views', type='text', multi="meta", store=True),
'application': fields.boolean('Application', readonly=True),
'icon': fields.char('Icon URL'),
'icon_image': fields.function(_get_icon_image, string='Icon', type="binary"),
}
_defaults = {
'state': 'uninstalled',
'sequence': 100,
'demo': False,
'license': 'LGPL-3',
}
_order = 'sequence,name'
def _name_uniq_msg(self, cr, uid, ids, context=None):
return _('The name of the module must be unique !')
_sql_constraints = [ _sql_constraints = [
('name_uniq', 'UNIQUE (name)', _name_uniq_msg), ('name_uniq', 'UNIQUE (name)', 'The name of the module must be unique!'),
] ]
def unlink(self, cr, uid, ids, context=None): @api.multi
if not ids: def unlink(self):
if not self:
return True return True
if isinstance(ids, (int, long)): for module in self:
ids = [ids] if module.state in ('installed', 'to upgrade', 'to remove', 'to install'):
mod_names = []
for mod in self.read(cr, uid, ids, ['state', 'name'], context):
if mod['state'] in ('installed', 'to upgrade', 'to remove', 'to install'):
raise UserError(_('You try to remove a module that is installed or will be installed')) raise UserError(_('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)])
#if ids_meta:
# self.pool.get('ir.model.data').unlink(cr, uid, ids_meta, context)
self.clear_caches() self.clear_caches()
return super(module, self).unlink(cr, uid, ids, context=context) return super(Module, self).unlink()
@staticmethod @staticmethod
def _check_external_dependencies(terp): def _check_external_dependencies(terp):
@@ -447,163 +394,166 @@ class module(osv.osv):
) % ("\n".join(themes_list))) ) % ("\n".join(themes_list)))
return dict(ACTION_DICT, name=_('Install')) return dict(ACTION_DICT, name=_('Install'))
def button_immediate_install(self, cr, uid, ids, context=None): @api.multi
def button_immediate_install(self):
""" Installs the selected module(s) immediately and fully, """ Installs the selected module(s) immediately and fully,
returns the next res.config action to execute returns the next res.config action to execute
:param ids: identifiers of the modules to install
:returns: next res.config item to execute :returns: next res.config item to execute
:rtype: dict[str, object] :rtype: dict[str, object]
""" """
return self._button_immediate_function(cr, uid, ids, self.button_install, context=context) return self._button_immediate_function(type(self).button_install)
def button_install_cancel(self, cr, uid, ids, context=None): @api.multi
self.write(cr, uid, ids, {'state': 'uninstalled', 'demo': False}) def button_install_cancel(self):
self.write({'state': 'uninstalled', 'demo': False})
return True return True
def module_uninstall(self, cr, uid, ids, context=None): @api.multi
"""Perform the various steps required to uninstall a module completely def module_uninstall(self):
""" Perform the various steps required to uninstall a module completely
including the deletion of all database structures created by the module: including the deletion of all database structures created by the module:
tables, columns, constraints, etc.""" tables, columns, constraints, etc.
ir_model_data = self.pool.get('ir.model.data') """
modules_to_remove = [m.name for m in self.browse(cr, uid, ids, context)] modules_to_remove = self.mapped('name')
ir_model_data._module_data_uninstall(cr, uid, modules_to_remove, context) self.env['ir.model.data']._module_data_uninstall(modules_to_remove)
self.write(cr, uid, ids, {'state': 'uninstalled', 'latest_version': False}) self.write({'state': 'uninstalled', 'latest_version': False})
return True return True
def downstream_dependencies(self, cr, uid, ids, known_dep_ids=None, @api.multi
exclude_states=['uninstalled', 'uninstallable', 'to remove'], @api.returns('self')
context=None): def downstream_dependencies(self, known_deps=None,
"""Return the ids of all modules that directly or indirectly depend exclude_states=('uninstalled', 'uninstallable', 'to remove')):
on the given module `ids`, and that satisfy the `exclude_states` """ Return the modules that directly or indirectly depend on the modules
filter""" in `self`, and that satisfy the `exclude_states` filter.
if not ids: """
return [] if not self:
known_dep_ids = set(known_dep_ids or []) return self
cr.execute('''SELECT DISTINCT m.id known_deps = known_deps or self.browse()
FROM query = """ SELECT DISTINCT m.id
ir_module_module_dependency d FROM ir_module_module_dependency d
JOIN JOIN ir_module_module m ON (d.module_id=m.id)
ir_module_module m ON (d.module_id=m.id) WHERE
WHERE d.name IN (SELECT name from ir_module_module where id in %s) AND
d.name IN (SELECT name from ir_module_module where id in %s) AND m.state NOT IN %s AND
m.state NOT IN %s AND m.id NOT IN %s """
m.id NOT IN %s ''', self._cr.execute(query, (tuple(self.ids), tuple(exclude_states), tuple(known_deps.ids or self.ids)))
(tuple(ids), tuple(exclude_states), tuple(known_dep_ids or ids))) new_deps = self.browse([row[0] for row in self._cr.fetchall()])
new_dep_ids = set([m[0] for m in cr.fetchall()]) missing_mods = new_deps - known_deps
missing_mod_ids = new_dep_ids - known_dep_ids known_deps |= new_deps
known_dep_ids |= new_dep_ids if missing_mods:
if missing_mod_ids: known_deps |= missing_mods.downstream_dependencies(known_deps, exclude_states)
known_dep_ids |= set(self.downstream_dependencies(cr, uid, list(missing_mod_ids), return known_deps
known_dep_ids, exclude_states, context))
return list(known_dep_ids)
def upstream_dependencies(self, cr, uid, ids, known_dep_ids=None, @api.multi
exclude_states=['installed', 'uninstallable', 'to remove'], @api.returns('self')
context=None): def upstream_dependencies(self, known_deps=None,
""" Return the dependency tree of modules of the given `ids`, and that exclude_states=('installed', 'uninstallable', 'to remove')):
satisfy the `exclude_states` filter """ """ Return the dependency tree of modules of the modules in `self`, and
if not ids: that satisfy the `exclude_states` filter.
return [] """
known_dep_ids = set(known_dep_ids or []) if not self:
cr.execute('''SELECT DISTINCT m.id return self
FROM known_deps = known_deps or self.browse()
ir_module_module_dependency d query = """ SELECT DISTINCT m.id
JOIN FROM ir_module_module_dependency d
ir_module_module m ON (d.module_id=m.id) JOIN ir_module_module m ON (d.module_id=m.id)
WHERE WHERE
m.name IN (SELECT name from ir_module_module_dependency where module_id in %s) AND m.name IN (SELECT name from ir_module_module_dependency where module_id in %s) AND
m.state NOT IN %s AND m.state NOT IN %s AND
m.id NOT IN %s ''', m.id NOT IN %s """
(tuple(ids), tuple(exclude_states), tuple(known_dep_ids or ids))) self._cr.execute(query, (tuple(self.ids), tuple(exclude_states), tuple(known_deps.ids or self.ids)))
new_dep_ids = set([m[0] for m in cr.fetchall()]) new_deps = self.browse([row[0] for row in self._cr.fetchall()])
missing_mod_ids = new_dep_ids - known_dep_ids missing_mods = new_deps - known_deps
known_dep_ids |= new_dep_ids known_deps |= new_deps
if missing_mod_ids: if missing_mods:
known_dep_ids |= set(self.upstream_dependencies(cr, uid, list(missing_mod_ids), known_deps |= missing_mods.upstream_dependencies(known_deps, exclude_states)
known_dep_ids, exclude_states, context)) return known_deps
return list(known_dep_ids)
def _button_immediate_function(self, cr, uid, ids, function, context=None): @api.multi
function(cr, uid, ids, context=context) def _button_immediate_function(self, function):
function(self)
cr.commit() self._cr.commit()
api.Environment.reset() api.Environment.reset()
registry = openerp.modules.registry.RegistryManager.new(cr.dbname, update_module=True) modules.registry.RegistryManager.new(self._cr.dbname, update_module=True)
cr.commit() self._cr.commit()
config = registry['res.config'].next(cr, uid, [], context=context) or {} env = api.Environment(self._cr, self._uid, self._context)
config = env['res.config'].next() or {}
if config.get('type') not in ('ir.actions.act_window_close',): if config.get('type') not in ('ir.actions.act_window_close',):
return config return config
# reload the client; open the first available root menu # reload the client; open the first available root menu
menu_obj = registry['ir.ui.menu'] menu = env['ir.ui.menu'].search([('parent_id', '=', False)])[:1]
menu_ids = menu_obj.search(cr, uid, [('parent_id', '=', False)], context=context)
return { return {
'type': 'ir.actions.client', 'type': 'ir.actions.client',
'tag': 'reload', 'tag': 'reload',
'params': {'menu_id': menu_ids and menu_ids[0] or False} 'params': {'menu_id': menu.id},
} }
def button_immediate_uninstall(self, cr, uid, ids, context=None): @api.multi
def button_immediate_uninstall(self):
""" """
Uninstall the selected module(s) immediately and fully, Uninstall the selected module(s) immediately and fully,
returns the next res.config action to execute returns the next res.config action to execute
""" """
return self._button_immediate_function(cr, uid, ids, self.button_uninstall, context=context) return self._button_immediate_function(type(self).button_uninstall)
def button_uninstall(self, cr, uid, ids, context=None): @api.multi
if any(m.name == 'base' for m in self.browse(cr, uid, ids, context=context)): def button_uninstall(self):
if 'base' in self.mapped('name'):
raise UserError(_("The `base` module cannot be uninstalled")) raise UserError(_("The `base` module cannot be uninstalled"))
dep_ids = self.downstream_dependencies(cr, uid, ids, context=context) deps = self.downstream_dependencies()
self.write(cr, uid, ids + dep_ids, {'state': 'to remove'}) (self + deps).write({'state': 'to remove'})
return dict(ACTION_DICT, name=_('Uninstall')) return dict(ACTION_DICT, name=_('Uninstall'))
def button_uninstall_cancel(self, cr, uid, ids, context=None): @api.multi
self.write(cr, uid, ids, {'state': 'installed'}) def button_uninstall_cancel(self):
self.write({'state': 'installed'})
return True return True
def button_immediate_upgrade(self, cr, uid, ids, context=None): @api.multi
def button_immediate_upgrade(self):
""" """
Upgrade the selected module(s) immediately and fully, Upgrade the selected module(s) immediately and fully,
return the next res.config action to execute return the next res.config action to execute
""" """
return self._button_immediate_function(cr, uid, ids, self.button_upgrade, context=context) return self._button_immediate_function(type(self).button_upgrade)
def button_upgrade(self, cr, uid, ids, context=None): @api.multi
depobj = self.pool.get('ir.module.module.dependency') def button_upgrade(self):
todo = list(self.browse(cr, uid, ids, context=context)) Dependency = self.env['ir.module.module.dependency']
self.update_list(cr, uid) self.update_list()
todo = list(self)
i = 0 i = 0
while i < len(todo): while i < len(todo):
mod = todo[i] module = todo[i]
i += 1 i += 1
if mod.state not in ('installed', 'to upgrade'): if module.state not in ('installed', 'to upgrade'):
raise UserError(_("Can not upgrade module '%s'. It is not installed.") % (mod.name,)) raise UserError(_("Can not upgrade module '%s'. It is not installed.") % (module.name,))
self.check_external_dependencies(mod.name, 'to upgrade') self.check_external_dependencies(module.name, 'to upgrade')
iids = depobj.search(cr, uid, [('name', '=', mod.name)], context=context) for dep in Dependency.search([('name', '=', module.name)]):
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) todo.append(dep.module_id)
ids = map(lambda x: x.id, todo) self.browse(module.id for module in todo).write({'state': 'to upgrade'})
self.write(cr, uid, ids, {'state': 'to upgrade'}, context=context)
to_install = [] to_install = []
for mod in todo: for module in todo:
for dep in mod.dependencies_id: for dep in module.dependencies_id:
if dep.state == 'unknown': if dep.state == 'unknown':
raise UserError(_('You try to upgrade a module that depends on the module: %s.\nBut this module is not available in your system.') % (dep.name,)) raise UserError(_('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': if dep.state == 'uninstalled':
ids2 = self.search(cr, uid, [('name', '=', dep.name)]) to_install += self.search([('name', '=', dep.name)]).ids
to_install.extend(ids2)
self.button_install(cr, uid, to_install, context=context) self.browse(to_install).button_install()
return dict(ACTION_DICT, name=_('Apply Schedule Upgrade')) return dict(ACTION_DICT, name=_('Apply Schedule Upgrade'))
def button_upgrade_cancel(self, cr, uid, ids, context=None): @api.multi
self.write(cr, uid, ids, {'state': 'installed'}) def button_upgrade_cancel(self):
self.write({'state': 'installed'})
return True return True
@staticmethod @staticmethod
@@ -624,25 +574,27 @@ class module(osv.osv):
'url': terp.get('url') or terp.get('live_test_url', ''), 'url': terp.get('url') or terp.get('live_test_url', ''),
} }
def create(self, cr, uid, vals, context=None): @api.model
new_id = super(module, self).create(cr, uid, vals, context=context) def create(self, vals):
new = super(Module, self).create(vals)
module_metadata = { module_metadata = {
'name': 'module_%s' % vals['name'], 'name': 'module_%s' % vals['name'],
'model': 'ir.module.module', 'model': 'ir.module.module',
'module': 'base', 'module': 'base',
'res_id': new_id, 'res_id': new.id,
'noupdate': True, 'noupdate': True,
} }
self.pool['ir.model.data'].create(cr, uid, module_metadata) self.env['ir.model.data'].create(module_metadata)
return new_id return new
# update the list of available packages # update the list of available packages
def update_list(self, cr, uid, context=None): @api.model
def update_list(self):
res = [0, 0] # [update, add] res = [0, 0] # [update, add]
default_version = modules.adapt_version('1.0') default_version = modules.adapt_version('1.0')
known_mods = self.browse(cr, uid, self.search(cr, uid, [])) known_mods = self.search([])
known_mods_names = dict([(m.name, m) for m in known_mods]) known_mods_names = {mod.name: mod for mod in known_mods}
# iterate through detected modules and update/create them in db # iterate through detected modules and update/create them in db
for mod_name in modules.get_modules(): for mod_name in modules.get_modules():
@@ -654,7 +606,7 @@ class module(osv.osv):
updated_values = {} updated_values = {}
for key in values: for key in values:
old = getattr(mod, key) old = getattr(mod, key)
updated = isinstance(values[key], basestring) and tools.ustr(values[key]) or values[key] updated = tools.ustr(values[key]) if isinstance(values[key], basestring) else values[key]
if (old or updated) and updated != old: if (old or updated) and updated != old:
updated_values[key] = values[key] updated_values[key] = values[key]
if terp.get('installable', True) and mod.state == 'uninstallable': if terp.get('installable', True) and mod.state == 'uninstallable':
@@ -662,43 +614,44 @@ class module(osv.osv):
if parse_version(terp.get('version', default_version)) > parse_version(mod.latest_version or default_version): if parse_version(terp.get('version', default_version)) > parse_version(mod.latest_version or default_version):
res[0] += 1 res[0] += 1
if updated_values: if updated_values:
self.write(cr, uid, mod.id, updated_values) mod.write(updated_values)
else: else:
mod_path = modules.get_module_path(mod_name) mod_path = modules.get_module_path(mod_name)
if not mod_path: if not mod_path:
continue continue
if not terp or not terp.get('installable', True): if not terp or not terp.get('installable', True):
continue continue
id = self.create(cr, uid, dict(name=mod_name, state='uninstalled', **values)) mod = self.create(dict(name=mod_name, state='uninstalled', **values))
mod = self.browse(cr, uid, id)
res[1] += 1 res[1] += 1
self._update_dependencies(cr, uid, mod, terp.get('depends', [])) mod._update_dependencies(terp.get('depends', []))
self._update_category(cr, uid, mod, terp.get('category', 'Uncategorized')) mod._update_category(terp.get('category', 'Uncategorized'))
return res return res
def download(self, cr, uid, ids, download=True, context=None): @api.multi
def download(self, download=True):
return [] return []
def install_from_urls(self, cr, uid, urls, context=None): @api.model
if not self.pool['res.users'].has_group(cr, uid, 'base.group_system'): def install_from_urls(self, urls):
raise openerp.exceptions.AccessDenied() if not self.env.user.has_group('base.group_system'):
raise AccessDenied()
apps_server = urlparse.urlparse(self.get_apps_server(cr, uid, context=context)) apps_server = urlparse.urlparse(self.get_apps_server())
OPENERP = openerp.release.product_name.lower() OPENERP = odoo.release.product_name.lower()
tmp = tempfile.mkdtemp() tmp = tempfile.mkdtemp()
_logger.debug('Install from url: %r', urls) _logger.debug('Install from url: %r', urls)
try: try:
# 1. Download & unzip missing modules # 1. Download & unzip missing modules
for module_name, url in urls.items(): for module_name, url in urls.iteritems():
if not url: if not url:
continue # nothing to download, local version is already the last one continue # nothing to download, local version is already the last one
up = urlparse.urlparse(url) up = urlparse.urlparse(url)
if up.scheme != apps_server.scheme or up.netloc != apps_server.netloc: if up.scheme != apps_server.scheme or up.netloc != apps_server.netloc:
raise openerp.exceptions.AccessDenied() raise AccessDenied()
try: try:
_logger.info('Downloading module `%s` from OpenERP Apps', module_name) _logger.info('Downloading module `%s` from OpenERP Apps', module_name)
@@ -711,7 +664,7 @@ class module(osv.osv):
assert os.path.isdir(os.path.join(tmp, module_name)) assert os.path.isdir(os.path.join(tmp, module_name))
# 2a. Copy/Replace module source in addons path # 2a. Copy/Replace module source in addons path
for module_name, url in urls.items(): for module_name, url in urls.iteritems():
if module_name == OPENERP or not url: if module_name == OPENERP or not url:
continue # OPENERP is special case, handled below, and no URL means local module continue # OPENERP is special case, handled below, and no URL means local module
module_path = modules.get_module_path(module_name, downloaded=True, display_warning=False) module_path = modules.get_module_path(module_name, downloaded=True, display_warning=False)
@@ -722,7 +675,7 @@ class module(osv.osv):
shutil.rmtree(bck) shutil.rmtree(bck)
# 2b. Copy/Replace server+base module source if downloaded # 2b. Copy/Replace server+base module source if downloaded
if urls.get(OPENERP, None): if urls.get(OPENERP):
# special case. it contains the server and the base module. # special case. it contains the server and the base module.
# extract path is not the same # extract path is not the same
base_path = os.path.dirname(modules.get_module_path('base')) base_path = os.path.dirname(modules.get_module_path('base'))
@@ -734,51 +687,51 @@ class module(osv.osv):
shutil.copytree(os.path.join(base_path, d), destdir) shutil.copytree(os.path.join(base_path, d), destdir)
# then replace the server by the new "base" module # then replace the server by the new "base" module
server_dir = openerp.tools.config['root_path'] # XXX or dirname() server_dir = tools.config['root_path'] # XXX or dirname()
bck = backup(server_dir) bck = backup(server_dir)
_logger.info('Copy downloaded module `openerp` to `%s`', server_dir) _logger.info('Copy downloaded module `openerp` to `%s`', server_dir)
shutil.move(os.path.join(tmp, OPENERP), server_dir) shutil.move(os.path.join(tmp, OPENERP), server_dir)
#if bck: #if bck:
# shutil.rmtree(bck) # shutil.rmtree(bck)
self.update_list(cr, uid, context=context) self.update_list()
with_urls = [m for m, u in urls.items() if u] with_urls = [module_name for module_name, url in urls.iteritems() if url]
downloaded_ids = self.search(cr, uid, [('name', 'in', with_urls)], context=context) downloaded = self.search([('name', 'in', with_urls)])
already_installed = self.search(cr, uid, [('id', 'in', downloaded_ids), ('state', '=', 'installed')], context=context) installed = self.search([('id', 'in', downloaded.ids), ('state', '=', 'installed')])
to_install_ids = self.search(cr, uid, [('name', 'in', urls.keys()), ('state', '=', 'uninstalled')], context=context) to_install = self.search([('name', 'in', urls.keys()), ('state', '=', 'uninstalled')])
post_install_action = self.button_immediate_install(cr, uid, to_install_ids, context=context) post_install_action = to_install.button_immediate_install()
if already_installed: if installed:
# in this case, force server restart to reload python code... # in this case, force server restart to reload python code...
cr.commit() self._cr.commit()
openerp.service.server.restart() odoo.service.server.restart()
return { return {
'type': 'ir.actions.client', 'type': 'ir.actions.client',
'tag': 'home', 'tag': 'home',
'params': {'wait': True}, 'params': {'wait': True},
} }
return post_install_action return post_install_action
finally: finally:
shutil.rmtree(tmp) shutil.rmtree(tmp)
def get_apps_server(self, cr, uid, context=None): @api.model
def get_apps_server(self):
return tools.config.get('apps_server', 'https://apps.openerp.com/apps') return tools.config.get('apps_server', 'https://apps.openerp.com/apps')
def _update_dependencies(self, cr, uid, mod_browse, depends=None): def _update_dependencies(self, depends=None):
if depends is None: existing = set(dep.name for dep in self.dependencies_id)
depends = [] needed = set(depends or [])
existing = set(x.name for x in mod_browse.dependencies_id)
needed = set(depends)
for dep in (needed - existing): for dep in (needed - existing):
cr.execute('INSERT INTO ir_module_module_dependency (module_id, name) values (%s, %s)', (mod_browse.id, dep)) self._cr.execute('INSERT INTO ir_module_module_dependency (module_id, name) values (%s, %s)', (self.id, dep))
for dep in (existing - needed): for dep in (existing - needed):
cr.execute('DELETE FROM ir_module_module_dependency WHERE module_id = %s and name = %s', (mod_browse.id, dep)) self._cr.execute('DELETE FROM ir_module_module_dependency WHERE module_id = %s and name = %s', (self.id, dep))
self.invalidate_cache(cr, uid, ['dependencies_id'], [mod_browse.id]) self.invalidate_cache(['dependencies_id'], self.ids)
def _update_category(self, cr, uid, mod_browse, category='Uncategorized'): def _update_category(self, category='Uncategorized'):
current_category = mod_browse.category_id current_category = self.category_id
current_category_path = [] current_category_path = []
while current_category: while current_category:
current_category_path.insert(0, current_category.name) current_category_path.insert(0, current_category.name)
@@ -786,26 +739,27 @@ class module(osv.osv):
categs = category.split('/') categs = category.split('/')
if categs != current_category_path: if categs != current_category_path:
cat_id = create_categories(cr, categs) cat_id = modules.db.create_categories(self._cr, categs)
mod_browse.write({'category_id': cat_id}) self.write({'category_id': cat_id})
def update_translations(self, cr, uid, ids, filter_lang=None, context=None): @api.multi
def update_translations(self, filter_lang=None):
if not filter_lang: if not filter_lang:
res_lang = self.pool.get('res.lang') langs = self.env['res.lang'].search([('translatable', '=', True)])
lang_ids = res_lang.search(cr, uid, [('translatable', '=', True)]) filter_lang = [lang.code for lang in langs]
filter_lang = [lang.code for lang in res_lang.browse(cr, uid, lang_ids)]
elif not isinstance(filter_lang, (list, tuple)): elif not isinstance(filter_lang, (list, tuple)):
filter_lang = [filter_lang] filter_lang = [filter_lang]
modules = [m.name for m in self.browse(cr, uid, ids) if m.state in ('installed', 'to install', 'to upgrade')] mod_names = [mod.name for mod in self if mod.state in ('installed', 'to install', 'to upgrade')]
self.pool.get('ir.translation').load_module_terms(cr, modules, filter_lang, context=context) self.env['ir.translation'].load_module_terms(mod_names, filter_lang)
def check(self, cr, uid, ids, context=None): @api.multi
for mod in self.browse(cr, uid, ids, context=context): def check(self):
if not mod.description: for module in self:
_logger.warning('module %s: description is empty !', mod.name) if not module.description:
_logger.warning('module %s: description is empty !', module.name)
@api.model @api.model
@ormcache() @tools.ormcache()
def _installed(self): def _installed(self):
""" Return the set of installed modules as a dictionary {name: id} """ """ Return the set of installed modules as a dictionary {name: id} """
return { return {
@@ -814,29 +768,21 @@ class module(osv.osv):
} }
DEP_STATES = [ DEP_STATES = STATES + [('unknown', 'Unknown')]
('uninstallable', 'Uninstallable'),
('uninstalled', 'Not Installed'),
('installed', 'Installed'),
('to upgrade', 'To be upgraded'),
('to remove', 'To be removed'),
('to install', 'To be installed'),
('unknown', 'Unknown'),
]
class module_dependency(osv.Model): class ModuleDependency(models.Model):
_name = "ir.module.module.dependency" _name = "ir.module.module.dependency"
_description = "Module dependency" _description = "Module dependency"
# the dependency name # the dependency name
name = fields2.Char(index=True) name = fields.Char(index=True)
# the module that depends on it # the module that depends on it
module_id = fields2.Many2one('ir.module.module', 'Module', ondelete='cascade') module_id = fields.Many2one('ir.module.module', 'Module', ondelete='cascade')
# the module corresponding to the dependency, and its status # the module corresponding to the dependency, and its status
depend_id = fields2.Many2one('ir.module.module', 'Dependency', compute='_compute_depend') depend_id = fields.Many2one('ir.module.module', 'Dependency', compute='_compute_depend')
state = fields2.Selection(DEP_STATES, string='Status', compute='_compute_state') state = fields.Selection(DEP_STATES, string='Status', compute='_compute_state')
@api.multi @api.multi
@api.depends('name') @api.depends('name')
@@ -1,42 +1,39 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details. # Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp import models, api from odoo import api, models
class IrModelReferenceReport(models.AbstractModel): class IrModelReferenceReport(models.AbstractModel):
_name = 'report.base.report_irmodulereference' _name = 'report.base.report_irmodulereference'
@api.model @api.model
def _object_find(self, module): def _object_find(self, module):
model_data_obj = self.env['ir.model.data'] Data = self.env['ir.model.data']
models_data = model_data_obj.search([('model','=','ir.model'), ('module','=',module.name)]) data = Data.search([('model','=','ir.model'), ('module','=',module.name)])
res_ids = [model_data.res_id for model_data in models_data] res_ids = data.mapped('res_id')
return self.env['ir.model'].browse(res_ids) return self.env['ir.model'].browse(res_ids)
@api.multi @api.multi
def _fields_find(self, model, module): def _fields_find(self, model, module):
res = [] Data = self.env['ir.model.data']
model_obj = self.env[model] fname_wildcard = 'field_' + model.replace('.', '_') + '_%'
model_fields = self.env['ir.model.fields'] data = Data.search([('model', '=', 'ir.model.fields'), ('module', '=', module.name), ('name', 'like', fname_wildcard)])
fname_wildcard = 'field_' + model_obj._name.replace('.', '_') + '_%' if data:
module_fields_ids = self.env['ir.model.data'].search([('model', '=', 'ir.model.fields'), ('module', '=', module.name), ('name', 'like', fname_wildcard)]) res_ids = data.mapped('res_ids')
module_fields_res_ids = [x.res_id for x in module_fields_ids] fnames = self.env['ir.model.fields'].browse(res_ids).mapped('name')
if module_fields_res_ids: return sorted(self.env[model].fields_get(fnames).iteritems())
module_fields_names = [x.name for x in model_fields.browse(module_fields_res_ids)] return []
res = model_obj.fields_get(allfields=module_fields_names).items()
res.sort()
return res
@api.multi @api.multi
def render_html(self, data=None): def render_html(self, data=None):
report_obj = self.env['report'] Report = self.env['report']
module_report = report_obj._get_report_from_name('base.report_irmodulereference') report = Report._get_report_from_name('base.report_irmodulereference')
selected_modules = self.env['ir.module.module'].browse(self.ids) selected_modules = self.env['ir.module.module'].browse(self.ids)
docargs = { docargs = {
'doc_ids': self.ids, 'doc_ids': self.ids,
'doc_model': module_report.model, 'doc_model': report.model,
'docs': selected_modules, 'docs': selected_modules,
'findobj': self._object_find, 'findobj': self._object_find,
'findfields': self._fields_find, 'findfields': self._fields_find,
} }
return report_obj.render('base.report_irmodulereference', docargs) return Report.render('base.report_irmodulereference', docargs)
@@ -5,58 +5,49 @@ import base64
import contextlib import contextlib
import cStringIO import cStringIO
from openerp import tools from odoo import api, fields, models, tools, _
from openerp.osv import fields,osv
from openerp.tools.translate import _
from openerp.tools.misc import get_iso_codes
NEW_LANG_KEY = '__new__' NEW_LANG_KEY = '__new__'
class base_language_export(osv.osv_memory): class BaseLanguageExport(models.TransientModel):
_name = "base.language.export" _name = "base.language.export"
def _get_languages(self, cr, uid, context): @api.model
lang_obj = self.pool.get('res.lang') def _get_languages(self):
ids = lang_obj.search(cr, uid, [('translatable', '=', True)]) langs = self.env['res.lang'].search([('translatable', '=', True)])
langs = lang_obj.browse(cr, uid, ids) return [(NEW_LANG_KEY, _('New Language (Empty translation template)'))] + \
return [(NEW_LANG_KEY, _('New Language (Empty translation template)'))] + [(lang.code, lang.name) for lang in langs] [(lang.code, lang.name) for lang in langs]
_columns = { name = fields.Char('File Name', readonly=True)
'name': fields.char('File Name', readonly=True), lang = fields.Selection(_get_languages, string='Language', required=True, default=NEW_LANG_KEY)
'lang': fields.selection(_get_languages, 'Language', required=True), format = fields.Selection([('csv','CSV File'), ('po','PO File'), ('tgz', 'TGZ Archive')],
'format': fields.selection([('csv','CSV File'), string='File Format', required=True, default='csv')
('po','PO File'), modules = fields.Many2many('ir.module.module', 'rel_modules_langexport', 'wiz_id', 'module_id',
('tgz', 'TGZ Archive')], 'File Format', required=True), string='Apps To Export', domain=[('state','=','installed')])
'modules': fields.many2many('ir.module.module', 'rel_modules_langexport', 'wiz_id', 'module_id', 'Apps To Export', domain=[('state','=','installed')]), data = fields.Binary('File', readonly=True)
'data': fields.binary('File', readonly=True), state = fields.Selection([('choose', 'choose'), ('get', 'get')], # choose language or get the file
'state': fields.selection([('choose', 'choose'), # choose language default='choose')
('get', 'get')]) # get the file
}
_defaults = {
'state': 'choose',
'lang': NEW_LANG_KEY,
'format': 'csv',
}
def act_getfile(self, cr, uid, ids, context=None): @api.multi
this = self.browse(cr, uid, ids, context=context)[0] def act_getfile(self):
this = self[0]
lang = this.lang if this.lang != NEW_LANG_KEY else False lang = this.lang if this.lang != NEW_LANG_KEY else False
mods = sorted(map(lambda m: m.name, this.modules)) or ['all'] mods = sorted(this.mapped('modules.name')) or ['all']
with contextlib.closing(cStringIO.StringIO()) as buf: with contextlib.closing(cStringIO.StringIO()) as buf:
tools.trans_export(lang, mods, buf, this.format, cr) tools.trans_export(lang, mods, buf, this.format, self._cr)
out = base64.encodestring(buf.getvalue()) out = base64.encodestring(buf.getvalue())
filename = 'new' filename = 'new'
if lang: if lang:
filename = get_iso_codes(lang) filename = tools.get_iso_codes(lang)
elif len(mods) == 1: elif len(mods) == 1:
filename = mods[0] filename = mods[0]
extension = this.format extension = this.format
if not lang and extension == 'po': if not lang and extension == 'po':
extension = 'pot' extension = 'pot'
name = "%s.%s" % (filename, extension) name = "%s.%s" % (filename, extension)
this.write({ 'state': 'get', 'data': out, 'name': name }) this.write({'state': 'get', 'data': out, 'name': name})
return { return {
'type': 'ir.actions.act_window', 'type': 'ir.actions.act_window',
'res_model': 'base.language.export', 'res_model': 'base.language.export',
@@ -1,52 +1,45 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details. # Part of Odoo. See LICENSE file for full copyright and licensing details.
import os
import base64 import base64
import logging import logging
import os
from tempfile import TemporaryFile from tempfile import TemporaryFile
from openerp import tools from odoo import api, fields, models, tools, _
from openerp.osv import osv, fields from odoo.exceptions import UserError
from openerp.exceptions import UserError
from openerp.tools.translate import _
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
class base_language_import(osv.osv_memory): class BaseLanguageImport(models.TransientModel):
""" Language Import """
_name = "base.language.import" _name = "base.language.import"
_description = "Language Import" _description = "Language Import"
_columns = {
'name': fields.char('Language Name', required=True),
'code': fields.char('ISO Code', size=5, help="ISO Language and Country code, e.g. en_US", required=True),
'data': fields.binary('File', required=True),
'filename': fields.char('File Name', required=True),
'overwrite': fields.boolean('Overwrite Existing Terms',
help="If you enable this option, existing translations (including custom ones) "
"will be overwritten and replaced by those in this file"),
}
def import_lang(self, cr, uid, ids, context=None): name = fields.Char('Language Name', required=True)
if context is None: code = fields.Char('ISO Code', size=5, required=True,
context = {} help="ISO Language and Country code, e.g. en_US")
this = self.browse(cr, uid, ids[0]) data = fields.Binary('File', required=True)
if this.overwrite: filename = fields.Char('File Name', required=True)
context = dict(context, overwrite=True) overwrite = fields.Boolean('Overwrite Existing Terms',
fileobj = TemporaryFile('w+') help="If you enable this option, existing translations (including custom ones) "
try: "will be overwritten and replaced by those in this file")
fileobj.write(base64.decodestring(this.data))
@api.multi
# now we determine the file format def import_lang(self):
fileobj.seek(0) this = self[0]
fileformat = os.path.splitext(this.filename)[-1][1:].lower() this = this.with_context(overwrite=this.overwrite)
with TemporaryFile('w+') as buf:
tools.trans_load_data(cr, fileobj, fileformat, this.code, lang_name=this.name, context=context) try:
except Exception, e: buf.write(base64.decodestring(this.data))
_logger.exception('File unsuccessfully imported, due to format mismatch.')
raise UserError(_('File not imported due to format mismatch or a malformed file. (Valid formats are .csv, .po, .pot)\n\nTechnical Details:\n%s') % tools.ustr(e)) # now we determine the file format
finally: buf.seek(0)
fileobj.close() fileformat = os.path.splitext(this.filename)[-1][1:].lower()
tools.trans_load_data(this._cr, buf, fileformat, this.code,
lang_name=this.name, context=this._context)
except Exception as e:
_logger.exception('File unsuccessfully imported, due to format mismatch.')
raise UserError(_('File not imported due to format mismatch or a malformed file. (Valid formats are .csv, .po, .pot)\n\nTechnical Details:\n%s') % tools.ustr(e))
return True return True
@@ -1,43 +1,30 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details. # Part of Odoo. See LICENSE file for full copyright and licensing details.
from operator import itemgetter from odoo import api, fields, models, _
from openerp import tools
from openerp.osv import osv, fields
from openerp.tools.translate import _
class base_language_install(osv.osv_memory): class BaseLanguageInstall(models.TransientModel):
""" Install Language"""
_name = "base.language.install" _name = "base.language.install"
_description = "Install Language" _description = "Install Language"
def _languages(self, cr, uid, context=None): @api.model
return self.pool['res.lang'].get_available(cr, uid, context=context) def _get_languages(self):
return self.env['res.lang'].get_available()
_columns = { lang = fields.Selection(_get_languages, string='Language', required=True)
'lang': fields.selection(_languages, 'Language', required=True), overwrite = fields.Boolean('Overwrite Existing Terms',
'overwrite': fields.boolean('Overwrite Existing Terms', help="If you check this box, your customized translations will be overwritten and replaced by the official ones."), help="If you check this box, your customized translations will be overwritten and replaced by the official ones.")
'state':fields.selection([('init','init'),('done','done')], 'Status', readonly=True), state = fields.Selection([('init', 'init'), ('done', 'done')],
} string='Status', readonly=True, default='init')
_defaults = { @api.multi
'state': 'init', def lang_install(self):
'overwrite': False this = self[0]
} this = this.with_context(overwrite=this.overwrite)
mods = self.env['ir.module.module'].search([('state', '=', 'installed')])
def lang_install(self, cr, uid, ids, context=None): mods.update_translations(this.lang)
if context is None: this.state = 'done'
context = {}
language_obj = self.browse(cr, uid, ids)[0]
lang = language_obj.lang
if lang:
modobj = self.pool.get('ir.module.module')
mids = modobj.search(cr, uid, [('state', '=', 'installed')])
if language_obj.overwrite:
context = {'overwrite': True}
modobj.update_translations(cr, uid, mids, lang, context or {})
self.write(cr, uid, ids, {'state': 'done'}, context=context)
return { return {
'name': _('Language Pack'), 'name': _('Language Pack'),
'view_type': 'form', 'view_type': 'form',
@@ -45,8 +32,8 @@ class base_language_install(osv.osv_memory):
'view_id': False, 'view_id': False,
'res_model': 'base.language.install', 'res_model': 'base.language.install',
'domain': [], 'domain': [],
'context': dict(context, active_ids=ids), 'context': dict(self._context, active_ids=self.ids),
'type': 'ir.actions.act_window', 'type': 'ir.actions.act_window',
'target': 'new', 'target': 'new',
'res_id': ids and ids[0] or False, 'res_id': this.id,
} }
@@ -1,33 +1,27 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details. # Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp.osv import osv from odoo import api, models, _
from openerp.tools.translate import _
class base_module_configuration(osv.osv_memory):
class BaseModuleConfiguration(models.TransientModel):
_name = "base.module.configuration" _name = "base.module.configuration"
def start(self, cr, uid, ids, context=None): @api.multi
todo_ids = self.pool.get('ir.actions.todo').search(cr, uid, def start(self):
['|', ('type','=','recurring'), ('state', '=', 'open')]) todos_domain = ['|', ('type','=','recurring'), ('state', '=', 'open')]
if not todo_ids: if self.env['ir.actions.todo'].search_count(todos_domain):
# Run the config wizards
return self.env['res.config'].start()
else:
# When there is no wizard todo it will display message # When there is no wizard todo it will display message
data_obj = self.pool.get('ir.model.data') view = self.env.ref('base.view_base_module_configuration_form')
result = data_obj._get_id(cr, uid, 'base', 'view_base_module_configuration_form') return {
view_id = data_obj.browse(cr, uid, result).res_id 'name': _('System Configuration done'),
value = { 'view_type': 'form',
'name': _('System Configuration done'), 'view_mode': 'form',
'view_type': 'form', 'res_model': 'base.mdule.configuration',
'view_mode': 'form', 'view_id': [view.id],
'res_model': 'base.module.configuration', 'type': 'ir.actions.act_window',
'view_id': [view_id], 'target': 'new',
'type': 'ir.actions.act_window', }
'target': 'new'
}
return value
# Run the config wizards
config_pool = self.pool.get('res.config')
return config_pool.start(cr, uid, ids, context=context)
base_module_configuration()
@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from openerp import models, fields, api from odoo import api, fields, models
class base_module_update(models.TransientModel):
class BaseModuleUpdate(models.TransientModel):
_name = "base.module.update" _name = "base.module.update"
_description = "Update Module" _description = "Update Module"
@@ -10,10 +11,11 @@ class base_module_update(models.TransientModel):
added = fields.Integer('Number of modules added', readonly=True) added = fields.Integer('Number of modules added', readonly=True)
state = fields.Selection([('init', 'init'), ('done', 'done')], 'Status', readonly=True, default='init') state = fields.Selection([('init', 'init'), ('done', 'done')], 'Status', readonly=True, default='init')
@api.one @api.multi
def update_module(self): def update_module(self):
self.updated, self.added = self.env['ir.module.module'].update_list() for this in self:
self.state = 'done' updated, added = self.env['ir.module.module'].update_list()
this.write({'updated': updated, 'added': added, 'state': 'done'})
return False return False
@api.multi @api.multi
@@ -1,34 +1,37 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details. # Part of Odoo. See LICENSE file for full copyright and licensing details.
import openerp import odoo
from openerp.osv import osv, fields from odoo import api, fields, models, _
from openerp.tools.translate import _ from odoo.exceptions import UserError
from openerp.exceptions import UserError
class base_module_upgrade(osv.osv_memory):
""" Module Upgrade """
class BaseModuleUpgrade(models.TransientModel):
_name = "base.module.upgrade" _name = "base.module.upgrade"
_description = "Module Upgrade" _description = "Module Upgrade"
_columns = { @api.model
'module_info': fields.text('Apps to Update',readonly=True), @api.returns('ir.module.module')
} def get_module_list(self):
states = ['to upgrade', 'to remove', 'to install']
return self.env['ir.module.module'].search([('state', 'in', states)])
def fields_view_get(self, cr, uid, view_id=None, view_type='form', context=None, toolbar=False, submenu=False): @api.model
res = super(base_module_upgrade, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar,submenu=False) def _default_module_info(self):
return "\n".join("%s: %s" % (mod.name, mod.state) for mod in self.get_module_list())
module_info = fields.Text('Apps to Update', readonly=True, default=_default_module_info)
@api.model
def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
res = super(BaseModuleUpgrade, self).fields_view_get(view_id, view_type, toolbar=toolbar,submenu=False)
if view_type != 'form': if view_type != 'form':
return res return res
context = {} if context is None else context if not(self._context.get('active_model') and self._context.get('active_id')):
record_id = context and context.get('active_id', False) or False
active_model = context.get('active_model')
if (not record_id) or (not active_model):
return res return res
ids = self.get_module_list(cr, uid, context=context) if not self.get_module_list():
if not ids:
res['arch'] = '''<form string="Upgrade Completed" version="7.0"> res['arch'] = '''<form string="Upgrade Completed" version="7.0">
<separator string="Upgrade Completed" colspan="4"/> <separator string="Upgrade Completed" colspan="4"/>
<footer> <footer>
@@ -39,55 +42,41 @@ class base_module_upgrade(osv.osv_memory):
return res return res
def get_module_list(self, cr, uid, context=None): @api.multi
mod_obj = self.pool.get('ir.module.module') def upgrade_module_cancel(self):
ids = mod_obj.search(cr, uid, [ Module = self.env['ir.module.module']
('state', 'in', ['to upgrade', 'to remove', 'to install'])]) to_install = Module.search([('state', 'in', ['to upgrade', 'to remove'])])
return ids to_install.write({'state': 'installed'})
to_uninstall = Module.search([('state', '=', 'to install')])
def default_get(self, cr, uid, fields, context=None): to_uninstall.write({'state': 'uninstalled'})
mod_obj = self.pool.get('ir.module.module')
ids = self.get_module_list(cr, uid, context=context)
res = mod_obj.read(cr, uid, ids, ['name','state'], context)
return {'module_info': '\n'.join(map(lambda x: x['name']+' : '+x['state'], res))}
def upgrade_module_cancel(self, cr, uid, ids, context=None):
mod_obj = self.pool.get('ir.module.module')
to_installed_ids = mod_obj.search(cr, uid, [
('state', 'in', ['to upgrade', 'to remove'])])
if to_installed_ids:
mod_obj.write(cr, uid, to_installed_ids, {'state': 'installed'}, context=context)
to_uninstalled_ids = mod_obj.search(cr, uid, [
('state', '=', 'to install')])
if to_uninstalled_ids:
mod_obj.write(cr, uid, to_uninstalled_ids, {'state': 'uninstalled'}, context=context)
return {'type': 'ir.actions.act_window_close'} return {'type': 'ir.actions.act_window_close'}
def upgrade_module(self, cr, uid, ids, context=None): @api.multi
ir_module = self.pool.get('ir.module.module') def upgrade_module(self):
Module = self.env['ir.module.module']
# install/upgrade: double-check preconditions # install/upgrade: double-check preconditions
ids = ir_module.search(cr, uid, [('state', 'in', ['to upgrade', 'to install'])]) mods = Module.search([('state', 'in', ['to upgrade', 'to install'])])
if ids: if mods:
cr.execute("""SELECT d.name FROM ir_module_module m query = """ SELECT d.name
JOIN ir_module_module_dependency d ON (m.id = d.module_id) FROM ir_module_module m
LEFT JOIN ir_module_module m2 ON (d.name = m2.name) JOIN ir_module_module_dependency d ON (m.id = d.module_id)
WHERE m.id in %s and (m2.state IS NULL or m2.state IN %s)""", LEFT JOIN ir_module_module m2 ON (d.name = m2.name)
(tuple(ids), ('uninstalled',))) WHERE m.id in %s and (m2.state IS NULL or m2.state IN %s) """
unmet_packages = [x[0] for x in cr.fetchall()] self._cr.execute(query, (tuple(mods.ids), ('uninstalled',)))
unmet_packages = [row[0] for row in self._cr.fetchall()]
if unmet_packages: if unmet_packages:
raise UserError(_('Following modules are not installed or unknown: %s') % ('\n\n' + '\n'.join(unmet_packages))) raise UserError(_('The following modules are not installed or unknown: %s') % ('\n\n' + '\n'.join(unmet_packages)))
ir_module.download(cr, uid, ids, context=context) mods.download()
# terminate transaction before re-creating cursor below # terminate transaction before re-creating cursor below
cr.commit() self._cr.commit()
openerp.api.Environment.reset() api.Environment.reset()
openerp.modules.registry.RegistryManager.new(cr.dbname, update_module=True) odoo.modules.registry.RegistryManager.new(self._cr.dbname, update_module=True)
return {'type': 'ir.actions.act_window_close'} return {'type': 'ir.actions.act_window_close'}
def config(self, cr, uid, ids, context=None): @api.multi
return self.pool.get('res.config').next(cr, uid, [], context=context) def config(self):
return self.env['res.config'].next()
@@ -1,52 +1,43 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details. # Part of Odoo. See LICENSE file for full copyright and licensing details.
import contextlib
import cStringIO import cStringIO
from openerp import tools from odoo import api, fields, models, tools, _
from openerp.osv import osv, fields from odoo.exceptions import UserError
from openerp.tools.translate import _
from openerp.exceptions import UserError
class base_update_translations(osv.osv_memory):
def _get_languages(self, cr, uid, context): class BaseUpdateTranslations(models.TransientModel):
lang_obj = self.pool.get('res.lang') _name = 'base.update.translations'
ids = lang_obj.search(cr, uid, ['&', ('active', '=', True), ('translatable', '=', True),])
langs = lang_obj.browse(cr, uid, ids) @api.model
def _get_languages(self):
langs = self.env['res.lang'].search([('active', '=', True), ('translatable', '=', True)])
return [(lang.code, lang.name) for lang in langs] return [(lang.code, lang.name) for lang in langs]
def _get_lang_name(self, cr, uid, lang_code): @api.model
lang_obj = self.pool.get('res.lang') def _default_language(self):
ids = lang_obj.search(cr, uid, [('code', '=', lang_code)]) if self._context.get('active_model') == 'res.lang':
if not ids: lang = self.env['res.lang'].browse(self._context.get('active_id'))
return lang.code
return False
lang = fields.Selection(_get_languages, 'Language', required=True,
default=_default_language)
@api.model
def _get_lang_name(self, lang_code):
lang = self.env['res.lang'].search([('code', '=', lang_code)], limit=1)
if not lang:
raise UserError(_('No language with code "%s" exists') % lang_code) raise UserError(_('No language with code "%s" exists') % lang_code)
lang = lang_obj.browse(cr, uid, ids[0])
return lang.name return lang.name
def act_update(self, cr, uid, ids, context=None): @api.multi
this = self.browse(cr, uid, ids)[0] def act_update(self):
lang_name = self._get_lang_name(cr, uid, this.lang) this = self[0]
buf = cStringIO.StringIO() lang_name = self._get_lang_name(this.lang)
tools.trans_export(this.lang, ['all'], buf, 'csv', cr) with contextlib.closing(cStringIO.StringIO()) as buf:
tools.trans_load_data(cr, buf, 'csv', this.lang, lang_name=lang_name) tools.trans_export(this.lang, ['all'], buf, 'csv', self._cr)
buf.close() tools.trans_load_data(self._cr, buf, 'csv', this.lang, lang_name=lang_name)
return {'type': 'ir.actions.act_window_close'} return {'type': 'ir.actions.act_window_close'}
def default_get(self, cr, uid, fields, context=None):
if context is None:
context = {}
res = super(base_update_translations, self).default_get(cr, uid, fields, context=context)
if context.get('active_model') != "res.lang":
return res
record_id = context.get('active_id', False) or False
if record_id:
lang = self.pool.get('res.lang').browse(cr, uid, record_id).code
res.update(lang=lang)
return res
_name = 'base.update.translations'
_columns = {
'lang': fields.selection(_get_languages, 'Language', required=True),
}