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