[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:

    <page string="Stuff" position="inside">...</page>

    <xpath expr="//page[@string='Stuff']" position="inside">...</xpath>
This commit is contained in:
Raphael Collet
2015-07-06 16:58:04 +02:00
parent 4e572e351e
commit f2be471558
7 changed files with 75 additions and 121 deletions
+7 -5
View File
@@ -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'):
+6 -1
View File
@@ -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:
+12 -4
View File
@@ -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,
+40 -46
View File
@@ -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=[]
+4 -2
View File
@@ -6,7 +6,7 @@
<field name="model">ir.ui.view</field>
<field name="arch" type="xml">
<form string="Views">
<sheet>
<sheet>
<group>
<group>
<field name="name"/>
@@ -25,7 +25,9 @@
</group>
<notebook>
<page string="Architecture">
<field name="arch"/>
<button type="object" name="open_translations"
string="Edit Translations" class="oe_link oe_right"/>
<field name="arch_base"/>
</page>
<page string="Groups">
<field name="groups_id"/>
+5 -6
View File
@@ -544,7 +544,6 @@ class TestTemplating(ViewCase):
'data-oe-id': str(id2),
'data-oe-field': 'arch',
'data-oe-xpath': '/xpath/item/content[1]',
'data-oe-source-id': str(id)
}), {
'order': '2',
}),
@@ -553,7 +552,7 @@ class TestTemplating(ViewCase):
'data-oe-model': 'ir.ui.view',
'data-oe-id': str(id),
'data-oe-field': 'arch',
'data-oe-xpath': '/root[1]/item[1]'
'data-oe-xpath': '/root[1]/item[1]',
})
)
)
@@ -707,7 +706,7 @@ class test_views(ViewCase):
'model': 'ir.ui.view',
'arch': """
<form string="Base title" version="7.0">
<separator string="separator" colspan="4"/>
<separator name="separator" string="Separator" colspan="4"/>
<footer>
<button name="action_next" type="object" string="Next button"/>
or
@@ -730,7 +729,7 @@ class test_views(ViewCase):
<button name="action_next" type="object" string="New button"/>
</footer>
</footer>
<separator string="separator" position="replace">
<separator name="separator" position="replace">
<p>Replacement data</p>
</separator>
</data>
@@ -775,7 +774,7 @@ class test_views(ViewCase):
'model': 'ir.ui.view.custom',
'arch': """
<form string="Base title" version="7.0">
<separator string="separator" colspan="4"/>
<separator name="separator" string="Separator" colspan="4"/>
<footer>
<button name="action_next" type="object" string="Next button"/>
or
@@ -798,7 +797,7 @@ class test_views(ViewCase):
<button name="action_next" type="object" string="New button"/>
</footer>
</footer>
<separator string="separator" position="replace">
<separator name="separator" position="replace">
<p>Replacement data</p>
</separator>
</data>
+1 -57
View File
@@ -647,28 +647,6 @@ def trans_export(lang, modules, buffer, format, cr):
_process(format, modules, translations, buffer, lang)
del translations
def trans_parse_xsl(de):
return list(set(trans_parse_xsl_aux(de, False)))
def trans_parse_xsl_aux(de, t):
res = []
for n in de:
t = t or n.get("t")
if t:
if isinstance(n, SKIPPED_ELEMENT_TYPES) or n.tag.startswith('{http://www.w3.org/1999/XSL/Transform}'):
continue
if n.text:
l = n.text.strip().replace('\n',' ')
if len(l):
res.append(l.encode("utf8"))
if n.tail:
l = n.tail.strip().replace('\n',' ')
if len(l):
res.append(l.encode("utf8"))
res.extend(trans_parse_xsl_aux(n, t))
return res
def trans_parse_rml(de):
res = []
for n in de:
@@ -689,28 +667,6 @@ def _push(callback, term, source_line):
if len(term) > 8 or any(x.isalpha() for x in term):
callback(term, source_line)
def trans_parse_view(element, callback):
""" Helper method to recursively walk an etree document representing a
regular view and call ``callback(term)`` for each translatable term
that is found in the document.
:param ElementTree element: root of etree document to extract terms from
:param callable callback: a callable in the form ``f(term, source_line)``,
that will be called for each extracted term.
"""
for el in element.iter():
if (not isinstance(el, SKIPPED_ELEMENT_TYPES)
and el.tag.lower() not in SKIPPED_ELEMENTS
and el.get("translation", '').strip() != "off"
and el.text):
_push(callback, el.text, el.sourceline)
if el.tail:
_push(callback, el.tail, el.sourceline)
for attr in ('string', 'help', 'sum', 'confirm', 'placeholder'):
value = el.get(attr)
if value:
_push(callback, value, el.sourceline)
# tests whether an object is in a list of modules
def in_modules(object_name, modules):
if 'all' in modules:
@@ -839,19 +795,7 @@ def trans_generate(lang, modules, cr):
_logger.warning("Unable to find object %r with id %d", model, res_id)
continue
if model=='ir.ui.view':
d = etree.XML(encode(obj.arch))
if obj.type == 'qweb':
view_id = get_root_view(xml_name)
push_qweb = lambda t,l: push(module, 'view', 'website', view_id, t)
_extract_translatable_qweb_terms(d, push_qweb)
else:
push_view = lambda t,l: push(module, 'view', obj.model, xml_name, t)
trans_parse_view(d, push_view)
elif model=='ir.actions.wizard':
pass # TODO Can model really be 'ir.actions.wizard' ?
elif model=='ir.model.fields':
if model=='ir.model.fields':
try:
field_name = encode(obj.name)
except AttributeError, exc: