[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:
@@ -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'):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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=[]
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user