[ADD] QWeb: xml namespaces support in dynamic template
This is very similar and almost copy pasted from the implementation in static template, as the main method `compile_node` will either call `_compile_static_node` (which we patched in the static implementation) or `_compile_directives` which will call `_compile_tag` in the cases where xmlns matter (at least that's my understanding for today). So we patch `_compile_tag` the same way than `_compile_static_node`. It would be nicer if the method wasn't completely different and in two parts, but hey. This method is not always called on leaves, so setting options['nsmap'] to a copy before processing the content of the node is necessary (a node with a nsmap and a t-attf- having a child with an nsmap: the child has to be aware of the nsmap of its parent). Note that we set the xmlns attribtue before calling `_compile_all_attribute`, which will probably break xmlns set by t-* tag. However, we don't plan to support that the moment.
This commit is contained in:
@@ -876,17 +876,37 @@ class QWeb(object):
|
||||
|
||||
def _compile_tag(self, el, content, options, attr_already_created=False):
|
||||
""" Compile the tag of the given element into a list of AST nodes. """
|
||||
if el.tag == 't':
|
||||
extra_attrib = {}
|
||||
if not el.nsmap:
|
||||
unqualified_el_tag = el_tag = el.tag
|
||||
else:
|
||||
# Etree will remove the ns prefixes indirection by inlining the corresponding
|
||||
# nsmap definition into the tag attribute. Restore the tag and prefix here.
|
||||
unqualified_el_tag = etree.QName(el.tag).localname
|
||||
el_tag = unqualified_el_tag
|
||||
if el.prefix:
|
||||
el_tag = '%s:%s' % (el.prefix, el_tag)
|
||||
|
||||
# If `el` introduced new namespaces, write them as attribute by using the
|
||||
# `extra_attrib` dict.
|
||||
for ns_prefix, ns_definition in set(el.nsmap.items()) - set(options['nsmap'].items()):
|
||||
if ns_prefix is None:
|
||||
extra_attrib['xmlns'] = ns_definition
|
||||
else:
|
||||
extra_attrib['xmlns:%s' % ns_prefix] = ns_definition
|
||||
|
||||
if unqualified_el_tag == 't':
|
||||
return content
|
||||
body = [self._append(ast.Str(u'<%s' % el.tag))]
|
||||
|
||||
body = [self._append(ast.Str(u'<%s%s' % (el_tag, u''.join([u' %s="%s"' % (name, escape(unicodifier(value))) for name, value in extra_attrib.iteritems()]))))]
|
||||
body.extend(self._compile_all_attributes(el, options, attr_already_created))
|
||||
if el.tag in self._void_elements:
|
||||
if unqualified_el_tag in self._void_elements:
|
||||
body.append(self._append(ast.Str(u'/>')))
|
||||
body.extend(content)
|
||||
else:
|
||||
body.append(self._append(ast.Str(u'>')))
|
||||
body.extend(content)
|
||||
body.append(self._append(ast.Str(u'</%s>' % el.tag)))
|
||||
body.append(self._append(ast.Str(u'</%s>' % el_tag)))
|
||||
return body
|
||||
|
||||
# compile directives
|
||||
@@ -902,7 +922,17 @@ class QWeb(object):
|
||||
|
||||
def _compile_directive_tag(self, el, options):
|
||||
el.attrib.pop('t-tag', None)
|
||||
|
||||
# Update the dict of inherited namespaces before continuing the recursion. Note:
|
||||
# since `options['nsmap']` is a dict (and therefore mutable) and we do **not**
|
||||
# want changes done in deeper recursion to bevisible in earlier ones, we'll pass
|
||||
# a copy before continuing the recursion and restore the original afterwards.
|
||||
if el.nsmap:
|
||||
original_nsmap = options['nsmap']
|
||||
options['nsmap'] = dict(options['nsmap'], **el.nsmap)
|
||||
content = self._compile_directives(el, options)
|
||||
if el.nsmap:
|
||||
options['nsmap'] = original_nsmap
|
||||
return self._compile_tag(el, content, options, False)
|
||||
|
||||
def _compile_directive_set(self, el, options):
|
||||
|
||||
@@ -168,6 +168,211 @@ class TestQWebNS(TransactionCase):
|
||||
|
||||
self.assertEquals(dedent_and_strip(view1.render()), dedent_and_strip(expected_result))
|
||||
|
||||
def test_render_dynamic_xml_with_namespace_t_esc(self):
|
||||
""" Test that rendering a template containing a node having both an ns declaration and a t-esc attribute correctly
|
||||
handles the t-esc attribute and keep the ns declaration.
|
||||
"""
|
||||
view1 = self.env['ir.ui.view'].create({
|
||||
'name': "dummy",
|
||||
'type': 'qweb',
|
||||
'arch': """
|
||||
<t t-name="base.dummy">
|
||||
<Invoice xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" t-esc="'test'"/>
|
||||
</t>
|
||||
"""
|
||||
})
|
||||
|
||||
expected_result = """<Invoice xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2">test</Invoice>"""
|
||||
|
||||
self.assertEquals(dedent_and_strip(view1.render()), dedent_and_strip(expected_result))
|
||||
|
||||
def test_render_dynamic_xml_with_namespace_t_esc_with_useless_distributed_namespace(self):
|
||||
""" Test that rendering a template containing a node having both an ns declaration and a t-esc attribute correctly
|
||||
handles the t-esc attribute and keep the ns declaration, and distribute correctly the ns declaration to its children.
|
||||
"""
|
||||
view1 = self.env['ir.ui.view'].create({
|
||||
'name': "dummy",
|
||||
'type': 'qweb',
|
||||
'arch': """
|
||||
<t t-name="base.dummy">
|
||||
<Invoice xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" t-attf-test="test">
|
||||
<cac:Test xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2">blabla</cac:Test>
|
||||
</Invoice>
|
||||
</t>
|
||||
"""
|
||||
})
|
||||
|
||||
expected_result = """
|
||||
<Invoice xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" test="test">
|
||||
<cac:Test>blabla</cac:Test>
|
||||
</Invoice>
|
||||
"""
|
||||
|
||||
self.assertEquals(dedent_and_strip(view1.render()), dedent_and_strip(expected_result))
|
||||
|
||||
def test_render_dynamic_xml_with_namespace_t_attf(self):
|
||||
""" Test that rendering a template containing a node having both an ns declaration and a t-attf attribute correctly
|
||||
handles the t-attf attribute and keep the ns declaration.
|
||||
"""
|
||||
view1 = self.env['ir.ui.view'].create({
|
||||
'name': "dummy",
|
||||
'type': 'qweb',
|
||||
'arch': """
|
||||
<t t-name="base.dummy">
|
||||
<root>
|
||||
<h:table xmlns:h="http://www.example.org/table">
|
||||
<h:tr>
|
||||
<h:td xmlns:h="http://www.w3.org/TD/html4/">Apples</h:td>
|
||||
<h:td>Bananas</h:td>
|
||||
</h:tr>
|
||||
</h:table>
|
||||
<f:table xmlns:f="http://www.example.org/furniture">
|
||||
<f:width t-attf-test="1">80</f:width>
|
||||
</f:table>
|
||||
</root>
|
||||
</t>
|
||||
"""
|
||||
})
|
||||
|
||||
expected_result = """
|
||||
<root>
|
||||
<h:table xmlns:h="http://www.example.org/table">
|
||||
<h:tr>
|
||||
<h:td xmlns:h="http://www.w3.org/TD/html4/">Apples</h:td>
|
||||
<h:td>Bananas</h:td>
|
||||
</h:tr>
|
||||
</h:table>
|
||||
<f:table xmlns:f="http://www.example.org/furniture">
|
||||
<f:width test="1">80</f:width>
|
||||
</f:table>
|
||||
</root>
|
||||
"""
|
||||
|
||||
self.assertEquals(dedent_and_strip(view1.render()), dedent_and_strip(expected_result))
|
||||
|
||||
def test_render_dynamic_xml_with_namespace_t_attf_with_useless_distributed_namespace(self):
|
||||
""" Test that rendering a template containing a node having both an ns declaration and a t-attf attribute correctly
|
||||
handles the t-attf attribute and that redundant namespaces are stripped upon rendering.
|
||||
"""
|
||||
view1 = self.env['ir.ui.view'].create({
|
||||
'name': "dummy",
|
||||
'type': 'qweb',
|
||||
'arch': """
|
||||
<t t-name="base.dummy">
|
||||
<root>
|
||||
<h:table xmlns:h="http://www.example.org/table">
|
||||
<h:tr>
|
||||
<h:td xmlns:h="http://www.w3.org/TD/html4/">Apples</h:td>
|
||||
<h:td>Bananas</h:td>
|
||||
</h:tr>
|
||||
</h:table>
|
||||
<f:table xmlns:f="http://www.example.org/furniture">
|
||||
<f:width xmlns:f="http://www.example.org/furniture" t-attf-test="1">80</f:width>
|
||||
</f:table>
|
||||
</root>
|
||||
|
||||
</t>
|
||||
"""
|
||||
})
|
||||
|
||||
expected_result = """
|
||||
<root>
|
||||
<h:table xmlns:h="http://www.example.org/table">
|
||||
<h:tr>
|
||||
<h:td xmlns:h="http://www.w3.org/TD/html4/">Apples</h:td>
|
||||
<h:td>Bananas</h:td>
|
||||
</h:tr>
|
||||
</h:table>
|
||||
<f:table xmlns:f="http://www.example.org/furniture">
|
||||
<f:width test="1">80</f:width>
|
||||
</f:table>
|
||||
</root>
|
||||
|
||||
"""
|
||||
|
||||
self.assertEquals(dedent_and_strip(view1.render()), dedent_and_strip(expected_result))
|
||||
|
||||
def test_render_dynamic_xml_with_namespace_2(self):
|
||||
view1 = self.env['ir.ui.view'].create({
|
||||
'name': "dummy",
|
||||
'type': 'qweb',
|
||||
'arch': """
|
||||
<t t-name="base.dummy">
|
||||
<Invoice xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
||||
<cbc:UBLVersionID t-esc="version_id"/>
|
||||
<t t-foreach="[1, 2, 3, 4]" t-as="value">
|
||||
Oasis <cac:Test t-esc="value"/>
|
||||
</t>
|
||||
</Invoice>
|
||||
</t>
|
||||
"""
|
||||
})
|
||||
|
||||
expected_result = """
|
||||
<Invoice xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
||||
<cbc:UBLVersionID>1.0</cbc:UBLVersionID>
|
||||
|
||||
Oasis <cac:Test>1</cac:Test>
|
||||
|
||||
Oasis <cac:Test>2</cac:Test>
|
||||
|
||||
Oasis <cac:Test>3</cac:Test>
|
||||
|
||||
Oasis <cac:Test>4</cac:Test>
|
||||
|
||||
</Invoice>
|
||||
"""
|
||||
|
||||
self.assertEquals(dedent_and_strip(view1.render(dict(version_id=1.0))), dedent_and_strip(expected_result))
|
||||
|
||||
def test_render_static_xml_with_t_call(self):
|
||||
view1 = self.env['ir.ui.view'].create({
|
||||
'name': "dummy",
|
||||
'type': 'qweb',
|
||||
'arch': """
|
||||
<t t-name="base.dummy">
|
||||
<cac:fruit xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
|
||||
xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
|
||||
<cac:table>
|
||||
<cbc:td>Appel</cbc:td>
|
||||
<cbc:td>Pineappel</cbc:td>
|
||||
</cac:table>
|
||||
</cac:fruit>
|
||||
</t>
|
||||
"""
|
||||
})
|
||||
self.env.cr.execute("INSERT INTO ir_model_data(name, model, res_id, module)"
|
||||
"VALUES ('dummy', 'ir.ui.view', %s, 'base')" % view1.id)
|
||||
|
||||
# view2 will t-call view1
|
||||
view2 = self.env['ir.ui.view'].create({
|
||||
'name': "dummy2",
|
||||
'type': 'qweb',
|
||||
'arch': """
|
||||
<t t-name="base.dummy2">
|
||||
<root xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2" xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
|
||||
<cac:line t-foreach="[1, 2]" t-as="i" t-call="base.dummy"/>
|
||||
</root>
|
||||
</t>
|
||||
"""
|
||||
})
|
||||
|
||||
result = view2.render()
|
||||
result_etree = etree.fromstring(result)
|
||||
|
||||
# check that the root tag has all its xmlns
|
||||
expected_ns = {
|
||||
None: 'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2',
|
||||
'cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2',
|
||||
'cbc': 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2'
|
||||
}
|
||||
self.assertSetEqual(set(expected_ns.items()) - set(result_etree.nsmap.items()), set())
|
||||
|
||||
# check that the t-call did its work
|
||||
cac_lines = result_etree.findall('.//cac:line', namespaces={'cac': 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2'})
|
||||
self.assertEquals(len(cac_lines), 2)
|
||||
self.assertEquals(result.count('Appel'), 2)
|
||||
|
||||
|
||||
from copy import deepcopy
|
||||
class FileSystemLoader(object):
|
||||
|
||||
Reference in New Issue
Block a user