From f2be4715589fb068e043f217734925eb3d045a7a Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Wed, 24 Jun 2015 11:15:20 +0200 Subject: [PATCH] [IMP] ir_ui_view: adapt translation of views by using callable field.translate Also add constraint to prevent using translated attributes in view inheritance. You may no longer select elements based on attributes 'string', 'help', etc: ... ... --- addons/website/controllers/main.py | 12 +-- addons/website_version/models/ir_ui_view.py | 7 +- openerp/addons/base/ir/ir_translation.py | 16 +++- openerp/addons/base/ir/ir_ui_view.py | 86 ++++++++++----------- openerp/addons/base/ir/ir_ui_view_view.xml | 6 +- openerp/addons/base/tests/test_views.py | 11 ++- openerp/tools/translate.py | 58 +------------- 7 files changed, 75 insertions(+), 121 deletions(-) diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py index f300487e7b8..9f01f71b2a0 100644 --- a/addons/website/controllers/main.py +++ b/addons/website/controllers/main.py @@ -253,7 +253,8 @@ class Website(openerp.addons.web.controllers.main.Home): old_trans = irt.search_read( request.cr, request.uid, [ - ('type', '=', 'view'), + ('type', '=', 'model'), + ('name', '=', 'ir.ui.view,arch_db'), ('res_id', '=', view_id), ('lang', '=', lang), ('src', '=', initial_content), @@ -261,15 +262,16 @@ class Website(openerp.addons.web.controllers.main.Home): if old_trans: tid = old_trans[0]['id'] if tid: - vals = {'value': new_content} + vals = {'value': new_content, 'state': 'translated'} irt.write(request.cr, request.uid, [tid], vals) else: new_trans = { - 'name': 'website', + 'type': 'model', + 'name': 'ir.ui.view,arch_db', + 'state': 'translated', 'res_id': view_id, 'lang': lang, - 'type': 'view', - 'source': initial_content, + 'src': initial_content, 'value': new_content, } if t.get('gengo_translation'): diff --git a/addons/website_version/models/ir_ui_view.py b/addons/website_version/models/ir_ui_view.py index 94fa6cab45e..4a3ded70f43 100644 --- a/addons/website_version/models/ir_ui_view.py +++ b/addons/website_version/models/ir_ui_view.py @@ -170,7 +170,12 @@ class view(models.Model): for v in view_list: views = self.customize_template_get(v.id, full=True) views_ids += [v.get('id') for v in views if v.get('active')] - domain = [('type', '=', 'view'), ('res_id', 'in', views_ids), ('lang', '=', lang)] + domain = [ + ('type', '=', 'model'), + ('name', '=', 'ir.ui.view,arch_db'), + ('res_id', 'in', views_ids), + ('lang', '=', lang), + ] element_list = irt.search_read(self.env.cr, self.env.uid, domain, field, context=self.env.context) for element in element_list: diff --git a/openerp/addons/base/ir/ir_translation.py b/openerp/addons/base/ir/ir_translation.py index ea18178d57a..1b0c78c9991 100644 --- a/openerp/addons/base/ir/ir_translation.py +++ b/openerp/addons/base/ir/ir_translation.py @@ -17,7 +17,7 @@ TRANSLATION_TYPE = [ ('model', 'Object'), ('report', 'Report/Template'), ('selection', 'Selection'), - ('view', 'View'), + ('view', 'View'), # deprecated ('help', 'Help'), # deprecated ('code', 'Code'), ('constraint', 'Constraint'), @@ -62,11 +62,14 @@ class ir_translation_import_cursor(object): # ugly hack for QWeb views - pending refactoring of translations in master if params['imd_model'] == 'website': params['imd_model'] = "ir.ui.view" - # non-QWeb views do not need a matching res_id -> force to 0 to avoid dropping them - elif params['res_id'] is None: + # non-QWeb views do not need a matching res_id in case they do not + # have an xml id -> force to 0 to avoid dropping them + elif params['res_id'] is None and not params['imd_name']: + # maybe we should insert this translation for all views of the + # given model? params['res_id'] = 0 - # backward compatibility: convert 'field', 'help' into 'model' + # backward compatibility: convert 'field', 'help', 'view' into 'model' if params['type'] == 'field': model, field = params['name'].split(',') params['type'] = 'model' @@ -81,6 +84,11 @@ class ir_translation_import_cursor(object): params['imd_model'] = 'ir.model.fields' params['imd_name'] = 'field_%s_%s' % (model.replace('.', '_'), field) + elif params['type'] == 'view': + params['type'] = 'model' + params['name'] = 'ir.ui.view,arch_db' + params['imd_model'] = "ir.ui.view" + self._cr.execute("""INSERT INTO %s (name, lang, res_id, src, type, imd_model, module, imd_name, value, state, comments) VALUES (%%(name)s, %%(lang)s, %%(res_id)s, %%(src)s, %%(type)s, %%(imd_model)s, %%(module)s, %%(imd_name)s, %%(value)s, %%(state)s, %%(comments)s)""" % self._table_name, diff --git a/openerp/addons/base/ir/ir_ui_view.py b/openerp/addons/base/ir/ir_ui_view.py index 25101e8d6ff..58f3b12a412 100644 --- a/openerp/addons/base/ir/ir_ui_view.py +++ b/openerp/addons/base/ir/ir_ui_view.py @@ -3,11 +3,11 @@ import collections import copy import datetime -import dateutil from dateutil.relativedelta import relativedelta import fnmatch import logging import os +import re import time from operator import itemgetter @@ -18,6 +18,7 @@ from lxml import etree import openerp from openerp import tools, api +from openerp.exceptions import ValidationError from openerp.http import request from openerp.modules.module import get_resource_path, get_resource_from_path from openerp.osv import fields, osv, orm @@ -27,7 +28,7 @@ from openerp.tools.parse_version import parse_version from openerp.tools.safe_eval import safe_eval as eval from openerp.tools.view_validation import valid_view from openerp.tools import misc -from openerp.tools.translate import _ +from openerp.tools.translate import TRANSLATED_ATTRS, encode, xml_translate, _ _logger = logging.getLogger(__name__) @@ -118,6 +119,22 @@ def get_view_arch_from_file(filename, xmlid): xpath_utils = etree.FunctionNamespace(None) xpath_utils['hasclass'] = _hasclass +TRANSLATED_ATTRS_RE = re.compile(r"@(%s)\b" % "|".join(TRANSLATED_ATTRS)) + +def valid_inheritance(arch): + """ Check whether view inheritance is based on translated attribute. """ + for node in arch.xpath('//*[@position]'): + # inheritance may not use a translated attribute as selector + if node.tag == 'xpath': + match = TRANSLATED_ATTRS_RE.search(node.get('expr', '')) + if match: + raise ValidationError("View inheritance may not use attribute %r as a selector." % match.group(1)) + else: + for attr in TRANSLATED_ATTRS: + if node.get(attr): + raise ValidationError("View inheritance may not use attribute %r as a selector." % attr) + return True + class view(osv.osv): _name = 'ir.ui.view' @@ -135,7 +152,6 @@ class view(osv.osv): def _arch_get(self, cr, uid, ids, name, arg, context=None): result = {} - for view in self.browse(cr, uid, ids, context=context): arch_fs = None if config['dev_mode'] and view.arch_fs and view.xml_id: @@ -164,6 +180,16 @@ class view(osv.osv): return True + @api.multi + def _arch_base_get(self, name, arg): + """ Return the field 'arch' without translation. """ + return self.with_context(lang=None)._arch_get(name, arg) + + @api.multi + def _arch_base_set(self, name, value, arg): + """ Assign the field 'arch' without translation. """ + return self.with_context(lang=None)._arch_set(name, value, arg) + _columns = { 'name': fields.char('View Name', required=True), 'model': fields.char('Object', select=True), @@ -182,7 +208,8 @@ class view(osv.osv): ('search','Search'), ('qweb', 'QWeb')], string='View Type'), 'arch': fields.function(_arch_get, fnct_inv=_arch_set, string='View Architecture', type="text", nodrop=True), - 'arch_db': fields.text('Arch Blob', oldname='arch'), + 'arch_base': fields.function(_arch_base_get, fnct_inv=_arch_base_set, string='View Architecture', type="text"), + 'arch_db': fields.text('Arch Blob', translate=xml_translate, oldname='arch'), 'arch_fs': fields.char('Arch Filename'), 'inherit_id': fields.many2one('ir.ui.view', 'Inherited View', ondelete='restrict', select=True), 'inherit_children_ids': fields.one2many('ir.ui.view','inherit_id', 'Inherit Views'), @@ -249,6 +276,9 @@ class view(osv.osv): # Sanity checks: the view should not break anything upon rendering! # Any exception raised below will cause a transaction rollback. for view in self.browse(cr, uid, ids, context): + view_arch = etree.fromstring(encode(view.arch)) + if not valid_inheritance(view_arch): + return False view_def = self.read_combined(cr, uid, view.id, None, context=context) view_arch_utf8 = view_def['arch'] if view.type != 'qweb': @@ -278,7 +308,7 @@ class view(osv.osv): " extend an other view"), ] _constraints = [ - (_check_xml, 'Invalid view definition', ['arch']), + (_check_xml, 'Invalid view definition', ['arch', 'arch_base']), ] def _auto_init(self, cr, context=None): @@ -462,7 +492,6 @@ class view(osv.osv): self.inherit_branding(node, view_id, root_id) else: node.set('data-oe-id', str(view_id)) - node.set('data-oe-source-id', str(root_id)) node.set('data-oe-xpath', xpath) node.set('data-oe-model', 'ir.ui.view') node.set('data-oe-field', 'arch') @@ -735,46 +764,6 @@ class view(osv.osv): # Get the attrs before they are (possibly) deleted by check_group below orm.transfer_node_to_modifiers(node, modifiers, context, in_tree_view) - # TODO remove attrs counterpart in modifiers when invisible is true ? - - # translate view - if 'lang' in context: - Translations = self.pool['ir.translation'] - if node.text and node.text.strip(): - term = node.text.strip() - trans = Translations._get_source(cr, user, model, 'view', context['lang'], term) - if trans: - node.text = node.text.replace(term, trans) - if node.tail and node.tail.strip(): - term = node.tail.strip() - trans = Translations._get_source(cr, user, model, 'view', context['lang'], term) - if trans: - node.tail = node.tail.replace(term, trans) - - if node.get('string') and node.get('string').strip() and not result: - term = node.get('string').strip() - trans = Translations._get_source(cr, user, model, 'view', context['lang'], term) - if trans == term: - if 'base_model_name' in context: - # If translation is same as source, perhaps we'd have more luck with the alternative model name - # (in case we are in a mixed situation, such as an inherited view where parent_view.model != model - trans = Translations._get_source(cr, user, context['base_model_name'], 'view', context['lang'], term) - else: - inherit_model = self.browse(cr, user, view_id, context=context).inherit_id.model or model - if inherit_model != model: - # parent view has a different model, if the terms belongs to the parent view, the translation - # should be checked on the parent model as well - trans = Translations._get_source(cr, user, inherit_model, 'view', context['lang'], term) - if trans: - node.set('string', trans) - - for attr_name in ('confirm', 'sum', 'avg', 'help', 'placeholder'): - attr_value = node.get(attr_name) - if attr_value and attr_value.strip(): - trans = Translations._get_source(cr, user, model, 'view', context['lang'], attr_value.strip()) - if trans: - node.set(attr_name, trans) - for f in node: if children or (node.tag == 'field' and f.tag in ('filter','separator')): fields.update(self.postprocess(cr, user, model, f, view_id, in_tree_view, model_fields, context)) @@ -1104,6 +1093,11 @@ class view(osv.osv): # Misc #------------------------------------------------------ + @api.multi + def open_translations(self): + """ Open a view for editing the translations of field 'arch_db'. """ + return self.env['ir.translation'].translate_fields('ir.ui.view', self.id, 'arch_db') + def graph_get(self, cr, uid, id, model, node_obj, conn_obj, src_node, des_node, label, scale, context=None): nodes=[] nodes_name=[] diff --git a/openerp/addons/base/ir/ir_ui_view_view.xml b/openerp/addons/base/ir/ir_ui_view_view.xml index 171d04d5651..02c0f69e83e 100644 --- a/openerp/addons/base/ir/ir_ui_view_view.xml +++ b/openerp/addons/base/ir/ir_ui_view_view.xml @@ -6,7 +6,7 @@ ir.ui.view
- + @@ -25,7 +25,9 @@ - +