3140 lines
118 KiB
Python
3140 lines
118 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import collections
|
|
import json
|
|
import os.path
|
|
import re
|
|
import markupsafe
|
|
|
|
from lxml import etree, html
|
|
from lxml.builder import E
|
|
from copy import deepcopy
|
|
from textwrap import dedent
|
|
|
|
from odoo.tests.common import TransactionCase
|
|
from odoo.addons.base.models.ir_qweb import QWebException, render
|
|
from odoo.tools import misc, mute_logger
|
|
from odoo.tools.json import scriptsafe as json_scriptsafe
|
|
from odoo.exceptions import UserError, ValidationError, MissingError
|
|
|
|
unsafe_eval = eval
|
|
|
|
|
|
class TestQWebTField(TransactionCase):
|
|
def setUp(self):
|
|
super(TestQWebTField, self).setUp()
|
|
self.env_branding = self.env(context={'inherit_branding': True})
|
|
self.engine = self.env_branding['ir.qweb']
|
|
|
|
def test_trivial(self):
|
|
field = etree.Element('span', {'t-field': 'company.name'})
|
|
company = self.env['res.company'].create({'name': "My Test Company"})
|
|
|
|
result = self.engine._render(field, {'company': company})
|
|
self.assertEqual(
|
|
etree.fromstring(result),
|
|
etree.fromstring('<span data-oe-model="res.company" data-oe-id="%d" '
|
|
'data-oe-field="name" data-oe-type="char" '
|
|
'data-oe-expression="company.name">%s</span>' % (
|
|
company.id,
|
|
"My Test Company",
|
|
)),
|
|
)
|
|
|
|
def test_i18n(self):
|
|
field = etree.Element('span', {'t-field': 'company.name'})
|
|
s = "Testing «ταБЬℓσ»: 1<2 & 4+1>3, now 20% off!"
|
|
company = self.env['res.company'].create({'name': s})
|
|
|
|
result = self.engine._render(field, {'company': company})
|
|
self.assertEqual(
|
|
etree.fromstring(result),
|
|
etree.fromstring('<span data-oe-model="res.company" data-oe-id="%d" '
|
|
'data-oe-field="name" data-oe-type="char" '
|
|
'data-oe-expression="company.name">%s</span>' % (
|
|
company.id,
|
|
misc.html_escape(s),
|
|
)),
|
|
)
|
|
|
|
def test_reject_crummy_tags(self):
|
|
field = etree.Element('td', {'t-field': 'company.name'})
|
|
|
|
with self.assertRaisesRegex(QWebException, r'QWeb widgets do not work correctly'):
|
|
self.engine._render(field, {'company': None})
|
|
|
|
def test_reject_t_tag(self):
|
|
field = etree.Element('t', {'t-field': 'company.name'})
|
|
|
|
with self.assertRaisesRegex(QWebException, r't-field can not be used on a t element'):
|
|
self.engine._render(field, {'company': None})
|
|
|
|
def test_render_t_options(self):
|
|
view1 = self.env['ir.ui.view'].create({
|
|
'name': "dummy",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="base.dummy"><root><span t-esc="5" t-options="{'widget': 'char'}" t-options-widget="'float'" t-options-precision="4"/></root></t>
|
|
"""
|
|
})
|
|
text = etree.fromstring(self.env['ir.qweb']._render(view1.id)).find('span').text
|
|
self.assertEqual(text, '5.0000')
|
|
|
|
def test_xss_breakout(self):
|
|
view = self.env['ir.ui.view'].create({
|
|
'name': 'dummy', 'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="base.dummy">
|
|
<root>
|
|
<script type="application/javascript">
|
|
var s = <t t-esc="json.dumps({'key': malicious})"/>;
|
|
</script>
|
|
</root>
|
|
</t>
|
|
"""
|
|
})
|
|
rendered = self.env['ir.qweb']._render(view.id, {'malicious': '1</script><script>alert("pwned")</script><script>'})
|
|
self.assertIn('alert', rendered, "%r doesn't seem to be rendered" % rendered)
|
|
doc = etree.fromstring(rendered)
|
|
self.assertEqual(len(doc.xpath('//script')), 1)
|
|
|
|
def test_default_value(self):
|
|
Partner = self.env['res.partner']
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="out-field-default">
|
|
<div t-field="record.name">
|
|
DEFAULT
|
|
<t t-out="'Text'" />
|
|
</div>
|
|
</t>''',
|
|
})
|
|
|
|
# record.name is non-empty
|
|
result = """
|
|
<div>My Company</div>
|
|
"""
|
|
rendered = self.env['ir.qweb']._render(t.id, {
|
|
'record': Partner.new({'name': 'My Company'})
|
|
})
|
|
self.assertEqual(str(rendered.strip()), result.strip(), "")
|
|
|
|
# record.name is empty but not False or None, we should render depending on force_display
|
|
result = """
|
|
<div></div>
|
|
"""
|
|
rendered = self.env['ir.qweb']._render(t.id, {
|
|
'record': Partner.new({'name': ''})
|
|
})
|
|
self.assertEqual(str(rendered.strip()), result.strip())
|
|
|
|
# record.name is False or None, we should render field default value
|
|
result = """
|
|
<div>
|
|
DEFAULT
|
|
Text
|
|
</div>
|
|
"""
|
|
rendered = self.env['ir.qweb']._render(t.id, {
|
|
'record': Partner.new({})
|
|
})
|
|
self.assertEqual(str(rendered.strip()), result.strip())
|
|
|
|
def test_no_value_no_default_value(self):
|
|
# no value, no default value with attributes on t-field
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="out-field-default">
|
|
<div t-field="record.name"/>
|
|
</t>''',
|
|
})
|
|
result = """
|
|
<div data-oe-xpath="/t[1]/div[1]" data-oe-model="res.partner" data-oe-field="name" data-oe-type="char" data-oe-expression="record.name"></div>
|
|
"""
|
|
# inherit_branding puts attribute on the field tag as well as force the display in case the field is empty
|
|
rendered = self.env['ir.qweb'].with_context(inherit_branding=True)._render(t.id, {
|
|
'record': self.env['res.partner'].new({}),
|
|
})
|
|
self.assertEqual(str(rendered.strip()), result.strip())
|
|
|
|
|
|
class TestQWebNS(TransactionCase):
|
|
def test_render_static_xml_with_namespace(self):
|
|
""" Test the rendering on a namespaced view with no static content. The resulting string should be untouched.
|
|
"""
|
|
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>80</f:width>
|
|
</f:table>
|
|
</root>
|
|
"""
|
|
|
|
view1 = self.env['ir.ui.view'].create({
|
|
'name': "dummy",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="base.dummy">%s</t>
|
|
""" % expected_result
|
|
})
|
|
|
|
self.assertEqual(etree.fromstring(self.env['ir.qweb']._render(view1.id)), etree.fromstring(expected_result))
|
|
|
|
def test_render_static_xml_with_namespace_2(self):
|
|
""" Test the rendering on a namespaced view with no static content. The resulting string should be untouched.
|
|
"""
|
|
expected_result = """
|
|
<html xmlns="http://www.w3.org/HTML/1998/html4" xmlns:xdc="http://www.xml.com/books">
|
|
<head>
|
|
<title>Book Review</title>
|
|
</head>
|
|
<body>
|
|
<xdc:bookreview>
|
|
<xdc:title>XML: A Primer</xdc:title>
|
|
<table>
|
|
<tr align="center">
|
|
<td>Author</td><td>Price</td>
|
|
<td>Pages</td><td>Date</td>
|
|
</tr>
|
|
<tr align="left">
|
|
<td><xdc:author>Simon St. Laurent</xdc:author></td>
|
|
<td><xdc:price>31.98</xdc:price></td>
|
|
<td><xdc:pages>352</xdc:pages></td>
|
|
<td><xdc:date>1998/01</xdc:date></td>
|
|
</tr>
|
|
</table>
|
|
</xdc:bookreview>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
view1 = self.env['ir.ui.view'].create({
|
|
'name': "dummy",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="base.dummy">%s</t>
|
|
""" % expected_result
|
|
})
|
|
|
|
self.assertEqual(etree.fromstring(self.env['ir.qweb']._render(view1.id)), etree.fromstring(expected_result))
|
|
|
|
def test_render_static_xml_with_useless_distributed_namespace(self):
|
|
""" Test 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 xmlns:h="http://www.example.org/table">
|
|
<h:td xmlns:h="http://www.w3.org/TD/html4/">Apples</h:td>
|
|
<h:td xmlns:h="http://www.example.org/table">Bananas</h:td>
|
|
</h:tr>
|
|
</h:table>
|
|
</root>
|
|
</t>
|
|
"""
|
|
})
|
|
|
|
expected_result = etree.fromstring("""
|
|
<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>
|
|
</root>
|
|
""")
|
|
|
|
self.assertEqual(etree.fromstring(self.env['ir.qweb']._render(view1.id)), expected_result)
|
|
|
|
def test_render_static_xml_with_namespace_3(self):
|
|
expected_result = """
|
|
<cfdi:Comprobante xmlns:cfdi="http://www.sat.gob.mx/cfd/3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sat.gob.mx/cfd/3 http://www.sat.gob.mx/sitio_internet/cfd/3/cfdv32.xsd"></cfdi:Comprobante>
|
|
"""
|
|
|
|
view1 = self.env['ir.ui.view'].create({
|
|
'name': "dummy",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="base.dummy">%s</t>
|
|
""" % expected_result
|
|
})
|
|
|
|
self.assertEqual(etree.fromstring(self.env['ir.qweb']._render(view1.id)), etree.fromstring(expected_result))
|
|
|
|
def test_render_static_xml_with_namespace_dynamic(self):
|
|
""" Test the rendering on a namespaced view with dynamic URI (need default namespace uri).
|
|
"""
|
|
tempate = """
|
|
<root xmlns:h="https://default.namespace.url/h">
|
|
<h:table t-att="{'xmlns:h': h1}">
|
|
<h:tr>
|
|
<h:td t-att="{'xmlns:h': h2}">Apples</h:td>
|
|
<h:td>Bananas</h:td>
|
|
</h:tr>
|
|
</h:table>
|
|
</root>
|
|
"""
|
|
expected_result = """
|
|
<root xmlns:h="https://default.namespace.url/h">
|
|
<h:table xmlns:h="%(h1)s">
|
|
<h:tr>
|
|
<h:td xmlns:h="%(h2)s">Apples</h:td>
|
|
<h:td>Bananas</h:td>
|
|
</h:tr>
|
|
</h:table>
|
|
</root>
|
|
"""
|
|
|
|
values = dict(h1="http://www.example.org/table", h2="http://www.w3.org/TD/html4/")
|
|
|
|
view1 = self.env['ir.ui.view'].create({
|
|
'name': "dummy",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="base.dummy">%s</t>
|
|
""" % tempate
|
|
})
|
|
|
|
rendering = self.env['ir.qweb']._render(view1.id, values)
|
|
|
|
self.assertEqual(etree.fromstring(rendering), etree.fromstring(expected_result % values))
|
|
|
|
def test_render_static_xml_with_namespace_dynamic_2(self):
|
|
""" Test the rendering on a namespaced view with dynamic URI (need default namespace uri).
|
|
Default URIs must be differents.
|
|
"""
|
|
tempate = """
|
|
<root xmlns:f="https://default.namespace.url/f" xmlns:h="https://default.namespace.url/h" >
|
|
<h:table t-att="{'xmlns:h': h1}">
|
|
<h:tr>
|
|
<h:td t-att="{'xmlns:h': h2}">Apples</h:td>
|
|
<h:td>Bananas</h:td>
|
|
</h:tr>
|
|
</h:table>
|
|
<f:table t-att="{'xmlns:f': f}">
|
|
<f:width>80</f:width>
|
|
</f:table>
|
|
</root>
|
|
"""
|
|
expected_result = """
|
|
<root xmlns:f="https://default.namespace.url/f" xmlns:h="https://default.namespace.url/h">
|
|
<h:table xmlns:h="%(h1)s">
|
|
<h:tr>
|
|
<h:td xmlns:h="%(h2)s">Apples</h:td>
|
|
<h:td>Bananas</h:td>
|
|
</h:tr>
|
|
</h:table>
|
|
<f:table xmlns:f="%(f)s">
|
|
<f:width>80</f:width>
|
|
</f:table>
|
|
</root>
|
|
"""
|
|
|
|
values = dict(h1="http://www.example.org/table", h2="http://www.w3.org/TD/html4/", f="http://www.example.org/furniture")
|
|
|
|
view1 = self.env['ir.ui.view'].create({
|
|
'name': "dummy",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="base.dummy">%s</t>
|
|
""" % tempate
|
|
})
|
|
|
|
rendering = self.env['ir.qweb']._render(view1.id, values)
|
|
|
|
self.assertEqual(etree.fromstring(rendering), etree.fromstring(expected_result % values))
|
|
|
|
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 = etree.fromstring("""<Invoice xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2">test</Invoice>""")
|
|
|
|
self.assertEqual(etree.fromstring(self.env['ir.qweb']._render(view1.id)), 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 = etree.fromstring("""
|
|
<Invoice xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" test="test">
|
|
<cac:Test>blabla</cac:Test>
|
|
</Invoice>
|
|
""")
|
|
|
|
self.assertEqual(etree.fromstring(self.env['ir.qweb']._render(view1.id)), 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 = etree.fromstring("""
|
|
<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.assertEqual(etree.fromstring(self.env['ir.qweb']._render(view1.id)), 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 = etree.fromstring("""
|
|
<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.assertEqual(etree.fromstring(self.env['ir.qweb']._render(view1.id)), 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 = etree.fromstring("""
|
|
<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.assertEqual(etree.fromstring(self.env['ir.qweb']._render(view1.id, {'version_id': 1.0})), expected_result)
|
|
|
|
def test_render_static_xml_with_namespaced_attributes(self):
|
|
view1 = self.env['ir.ui.view'].create({
|
|
'name': "dummy",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="base.dummy">
|
|
<cfdi:Comprobante xmlns:cfdi="http://www.sat.gob.mx/cfd/3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sat.gob.mx/cfd/3 http://www.sat.gob.mx/sitio_internet/cfd/3/cfdv32.xsd">abc</cfdi:Comprobante>
|
|
</t>
|
|
"""
|
|
})
|
|
|
|
expected_result = etree.fromstring("""<cfdi:Comprobante xmlns:cfdi="http://www.sat.gob.mx/cfd/3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sat.gob.mx/cfd/3 http://www.sat.gob.mx/sitio_internet/cfd/3/cfdv32.xsd">abc</cfdi:Comprobante>""")
|
|
|
|
self.assertEqual(etree.fromstring(self.env['ir.qweb']._render(view1.id)), expected_result)
|
|
|
|
def test_render_dynamic_xml_with_namespaced_attributes(self):
|
|
view1 = self.env['ir.ui.view'].create({
|
|
'name': "dummy",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="base.dummy">
|
|
<cfdi:Comprobante xmlns:cfdi="http://www.sat.gob.mx/cfd/3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sat.gob.mx/cfd/3 http://www.sat.gob.mx/sitio_internet/cfd/3/cfdv32.xsd" t-esc="'abc'"/>
|
|
</t>
|
|
"""
|
|
})
|
|
|
|
expected_result = etree.fromstring("""<cfdi:Comprobante xmlns:cfdi="http://www.sat.gob.mx/cfd/3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sat.gob.mx/cfd/3 http://www.sat.gob.mx/sitio_internet/cfd/3/cfdv32.xsd">abc</cfdi:Comprobante>""")
|
|
|
|
self.assertEqual(etree.fromstring(self.env['ir.qweb']._render(view1.id)), 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 = self.env['ir.qweb']._render(view2.id)
|
|
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.assertEqual(set(result_etree.nsmap.items()), expected_ns)
|
|
|
|
# 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.assertEqual(len(cac_lines), 2)
|
|
self.assertEqual(result.count('Appel'), 2)
|
|
|
|
# check that the t-call dit not output again the xmlns declaration
|
|
self.assertEqual(result.count('xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"'), 1)
|
|
|
|
def test_render_static_xml_with_extension(self):
|
|
""" Test the extension of a view by an xpath expression on a ns prefixed element.
|
|
"""
|
|
# primary view
|
|
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>
|
|
</root>
|
|
</t>
|
|
"""
|
|
})
|
|
# extension patching the primary view
|
|
view2 = self.env['ir.ui.view'].create({
|
|
'name': "dummy_ext",
|
|
'type': 'qweb',
|
|
'inherit_id': view1.id,
|
|
'arch': """
|
|
<xpath expr="//{http://www.example.org/table}table/{http://www.example.org/table}tr">
|
|
<h:td xmlns:h="http://www.example.org/table">Oranges</h:td>
|
|
</xpath>
|
|
"""
|
|
})
|
|
|
|
expected_result = etree.fromstring("""
|
|
<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:td>Oranges</h:td>
|
|
</h:tr>
|
|
</h:table>
|
|
</root>
|
|
""")
|
|
|
|
self.assertEqual(
|
|
etree.fromstring(self.env['ir.qweb'].with_context(check_view_ids=[view1.id, view2.id])._render(view1.id)),
|
|
expected_result
|
|
)
|
|
|
|
def test_render_dynamic_xml_with_code_error(self):
|
|
""" Test that, when rendering a template containing a namespaced node
|
|
that evaluates code with errors, the proper exception is raised
|
|
"""
|
|
view1 = self.env['ir.ui.view'].create({
|
|
'name': "dummy",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="base.dummy">
|
|
<Invoice xmlns:od="http://odoo.com/od">
|
|
<od:name t-att-test="'a' + 1"/>
|
|
</Invoice>
|
|
</t>
|
|
"""
|
|
})
|
|
|
|
error_msg = None
|
|
try:
|
|
"" + 0
|
|
except TypeError as e:
|
|
error_msg = e.args[0]
|
|
|
|
with self.assertRaises(QWebException, msg=error_msg):
|
|
self.env['ir.qweb']._render(view1.id)
|
|
|
|
class TestQWebBasic(TransactionCase):
|
|
def test_compile_expr(self):
|
|
tests = [
|
|
#pylint: disable=C0326
|
|
# source, values, result
|
|
("1 +2+ 3", {}, 6),
|
|
("(((1 +2+ 3)))", {}, 6),
|
|
("(1) +(2+ (3))", {}, 6),
|
|
("a == 5", {'a': 5}, True),
|
|
("{'a': True}", {}, {'a': True}),
|
|
("object.count(1)", {'object': [1, 2, 1 ,1]}, 3),
|
|
("dict(a=True)", {}, {'a': True}),
|
|
("fn(a=11, b=22) or a", {'a': 1, 'fn': lambda a,b: 0}, 1),
|
|
("fn(a=11, b=22) or a", {'a': 1, 'fn': lambda a,b: b}, 22),
|
|
("(lambda a: a)(5)", {}, 5),
|
|
("(lambda a: a[0])([5])", {}, 5),
|
|
("(lambda test: len(test))('aaa')", {}, 3),
|
|
("{'a': lambda a: a[0], 'b': 3}['a']([5])", {}, 5),
|
|
("list(map(lambda a: a[0], r))", {'r': [(1,11), (2,22)]}, [1, 2]),
|
|
("z + (head or 'z')", {'z': 'a'}, "az"),
|
|
("z + (head or 'z')", {'z': 'a', 'head': 'b'}, "ab"),
|
|
("{a:b for a, b in [(1,11), (2, 22)]}", {}, {1: 11, 2: 22}),
|
|
("any({x == 2 for x in [1,2,3]})", {}, True),
|
|
("any({x == 5 for x in [1,2,3]})", {}, False),
|
|
("{x:y for x,y in [('a', 11),('b', 22)]}", {}, {'a': 11, 'b': 22}),
|
|
("[(y,x) for x,y in [(1, 11),(2, 22)]]", {}, [(11, 1), (22, 2)]),
|
|
("(lambda a: a + 5)(x)", {'x': 10}, 15),
|
|
("(lambda a: a + x)(5)", {'x': 10}, 15),
|
|
("sum(x for x in range(4)) + ((x))", {'x': 10}, 16),
|
|
("['test_' + x for x in ['a', 'b']]", {}, ['test_a', 'test_b']),
|
|
("""1 and 2 and 0
|
|
or 9""", {}, 9),
|
|
]
|
|
|
|
IrQweb = self.env['ir.qweb']
|
|
for expr, q_values, result in tests:
|
|
expr_namespace = IrQweb._compile_expr(expr)
|
|
|
|
compiled = compile("""def test(values):\n values['result'] = %s""" % expr_namespace, '<test>', 'exec')
|
|
globals_dict = IrQweb._IrQWeb__prepare_globals()
|
|
values = {}
|
|
unsafe_eval(compiled, globals_dict, values)
|
|
test = values['test']
|
|
|
|
test(q_values)
|
|
q_result = dict(q_values, result=result)
|
|
self.assertDictEqual(q_values, q_result, "Should compile: %s" % expr)
|
|
|
|
def test_foreach_as_error_1(self):
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="iter-list">
|
|
<t t-foreach="[3, 2, 1]">
|
|
[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/>]</t>
|
|
</t>'''
|
|
})
|
|
|
|
with self.assertRaises(QWebException):
|
|
self.env['ir.qweb']._render(t.id)
|
|
|
|
try:
|
|
self.env['ir.qweb']._render(t.id)
|
|
except QWebException as e:
|
|
error = str(e)
|
|
self.assertIn("KeyError: 't-as'", error)
|
|
self.assertIn('<t t-foreach="[3, 2, 1]"/>', error)
|
|
|
|
def test_foreach_as_error_2(self):
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="iter-list">
|
|
<t t-foreach="[3, 2, 1]" t-as="">
|
|
[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/>]</t>
|
|
</t>'''
|
|
})
|
|
|
|
with self.assertRaises(QWebException):
|
|
self.env['ir.qweb']._render(t.id)
|
|
|
|
try:
|
|
self.env['ir.qweb']._render(t.id)
|
|
except QWebException as e:
|
|
error = str(e)
|
|
self.assertIn("KeyError: 't-as'", error)
|
|
self.assertIn('<t t-foreach="[3, 2, 1]" t-as=""/>', error)
|
|
|
|
def test_foreach_as_error_3(self):
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="iter-list">
|
|
<t t-foreach="[3, 2, 1]" t-as="b-2">
|
|
[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/>]</t>
|
|
</t>'''
|
|
})
|
|
|
|
with self.assertRaises(QWebException):
|
|
self.env['ir.qweb']._render(t.id)
|
|
|
|
try:
|
|
self.env['ir.qweb']._render(t.id)
|
|
except QWebException as e:
|
|
error = str(e)
|
|
self.assertIn("The varname 'b-2' can only contain alphanumeric characters and underscores", error)
|
|
self.assertIn('<t t-foreach="[3, 2, 1]" t-as="b-2"/>', error)
|
|
|
|
def test_compile_expr_security(self):
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="attr-escaping">
|
|
<div>
|
|
<t t-set="o" t-value="(lambda a=open: a)()"/>
|
|
<t t-out="o('/etc/passwd').read()"/>
|
|
</div>
|
|
</t>'''
|
|
})
|
|
values = {'other': 'any value'}
|
|
with self.assertRaises(Exception): # NotImplementedError for 'lambda a=open' and Undefined value 'open'.
|
|
self.env['ir.qweb']._render(t.id, values)
|
|
|
|
def test_foreach_iter_list(self):
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="iter-list">
|
|
<t t-foreach="[3, 2, 1]" t-as="item">
|
|
[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/>]</t>
|
|
</t>'''
|
|
})
|
|
result = """
|
|
[0: 3 3]
|
|
[1: 2 2]
|
|
[2: 1 1]
|
|
"""
|
|
|
|
rendered = self.env['ir.qweb']._render(t.id)
|
|
self.assertEqual(rendered.strip(), result.strip())
|
|
|
|
def test_foreach_iter_dict(self):
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="iter-dict">
|
|
<t t-foreach="{'a': 3, 'b': 2, 'c': 1}" t-as="item">
|
|
[<t t-esc="item_index"/>: <t t-esc="item"/> <t t-esc="item_value"/>]</t>
|
|
</t>'''
|
|
})
|
|
result = """
|
|
[0: a 3]
|
|
[1: b 2]
|
|
[2: c 1]
|
|
"""
|
|
|
|
rendered = self.env['ir.qweb']._render(t.id)
|
|
self.assertEqual(rendered.strip(), result.strip())
|
|
|
|
def test_att_escaping_1(self):
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="attr-escaping">
|
|
<div t-att-bibi="json.dumps(bibi)">1</div>
|
|
<div t-att-toto="toto">2</div>
|
|
</t>'''
|
|
})
|
|
result = """
|
|
<div bibi="{"a": "string", "b": 1}">1</div>
|
|
<div toto="a'b"c">2</div>
|
|
"""
|
|
values = {'json': json_scriptsafe, 'bibi': dict(a='string', b=1), 'toto': "a'b\"c"}
|
|
rendered = self.env['ir.qweb']._render(t.id, values)
|
|
self.assertEqual(rendered.strip(), result.strip())
|
|
|
|
def test_att_escaping_2(self):
|
|
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="attr-escaping">
|
|
<t t-set="abc"> <t t-if="add_abc"><t t-out="add_abc"/> <span a="b"> | </span></t><t t-out="efg"/> </t>
|
|
<div t-att-abc="abc">123</div>
|
|
</t>'''
|
|
})
|
|
result = """
|
|
<div abc=" &#34;yes&#34; <span a="b"> | </span>-efg- ">123</div>
|
|
"""
|
|
values = {'add_abc': '"yes"', 'efg': '-efg-'}
|
|
rendered = self.env['ir.qweb']._render(t.id, values)
|
|
self.assertEqual(rendered.strip(), result.strip())
|
|
|
|
def test_attf_escaping_1(self):
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="attr-escaping">
|
|
<div t-attf-bibi="a, b > c > #{d}">1</div>
|
|
</t>'''
|
|
})
|
|
result = """
|
|
<div bibi="a, b > c > a' > b"c">1</div>
|
|
"""
|
|
values = {'d': "a' > b\"c"}
|
|
rendered = self.env['ir.qweb']._render(t.id, values)
|
|
self.assertEqual(rendered.strip(), result.strip())
|
|
|
|
def test_attf_escaping_2(self):
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="attr-escaping">
|
|
<a t-attf-href="/link/#{ url }/#{other and 'sub'}">link</a>
|
|
<a t-attf-href="/link/#{ url }/#{(not other) and 'sub'}">link2</a>
|
|
</t>'''
|
|
})
|
|
result = """
|
|
<a href="/link/odoo/sub">link</a>
|
|
<a href="/link/odoo/">link2</a>
|
|
"""
|
|
values = {'url': 'odoo', 'other': True}
|
|
rendered = self.env['ir.qweb']._render(t.id, values)
|
|
self.assertEqual(rendered.strip(), result.strip())
|
|
|
|
def test_attf_escaping_3(self):
|
|
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="attr-escaping">
|
|
<div t-attf-abc="abc #{val} { other }">123</div>
|
|
</t>'''
|
|
})
|
|
result = """
|
|
<div abc="abc "yes" { other }">123</div>
|
|
"""
|
|
values = {'val': '"yes"'}
|
|
rendered = self.env['ir.qweb']._render(t.id, values)
|
|
self.assertEqual(rendered.strip(), result.strip())
|
|
|
|
def test_att_no_propagation_1(self):
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="bibi">
|
|
<div t-foreach="[1, 2]" t-as="v" class="toto"/>
|
|
<t class="remove_me" t-set="data">a</t>
|
|
<div t-out="data"/>
|
|
</t>'''
|
|
})
|
|
result = """
|
|
<div class="toto"></div><div class="toto"></div>
|
|
<div>a</div>
|
|
"""
|
|
rendered = self.env['ir.qweb']._render(t.id)
|
|
self.assertEqual(rendered.strip(), result.strip())
|
|
|
|
def test_att_no_propagation_2(self):
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="bibi">
|
|
<section>
|
|
<div t-foreach="[1, 2]" t-as="v">
|
|
<span t-att-test="v" t-esc="v_index"/>
|
|
</div>
|
|
<div t-foreach="[1, 2]" t-as="v" class="o">
|
|
<span t-att-test="v" t-esc="v_index"/>
|
|
</div>
|
|
</section>
|
|
</t>'''
|
|
})
|
|
result = """
|
|
<section>
|
|
<div>
|
|
<span test="1">0</span>
|
|
</div>
|
|
<div>
|
|
<span test="2">1</span>
|
|
</div>
|
|
<div class="o">
|
|
<span test="1">0</span>
|
|
</div>
|
|
<div class="o">
|
|
<span test="2">1</span>
|
|
</div>
|
|
</section>
|
|
"""
|
|
rendered = self.env['ir.qweb']._render(t.id)
|
|
self.assertEqual(etree.fromstring(rendered), etree.fromstring(result))
|
|
|
|
def test_set_1(self):
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="set">
|
|
<t t-set="a" t-value="'abc %s' % 1"/>
|
|
<div t-out="a"/>
|
|
</t>'''
|
|
})
|
|
result = """
|
|
<div>abc 1</div>
|
|
"""
|
|
rendered = self.env['ir.qweb']._render(t.id)
|
|
self.assertEqual(rendered.strip(), result.strip())
|
|
|
|
def test_set_2(self):
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="set">
|
|
<t t-set="a" t-valuef="abc {{1}}"/>
|
|
<div t-out="a"/>
|
|
</t>'''
|
|
})
|
|
result = """
|
|
<div>abc 1</div>
|
|
"""
|
|
rendered = self.env['ir.qweb']._render(t.id)
|
|
self.assertEqual(rendered.strip(), result.strip())
|
|
|
|
def test_set_3(self):
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="set">
|
|
<t t-set='{"a": "abc %s" % 1,
|
|
"b": 2}'/>
|
|
<div t-out="a"/>
|
|
</t>'''
|
|
})
|
|
result = """
|
|
<div>abc 1</div>
|
|
"""
|
|
rendered = self.env['ir.qweb']._render(t.id)
|
|
self.assertEqual(rendered.strip(), result.strip())
|
|
|
|
def test_set_body_1(self):
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="set">
|
|
<t t-set="abc"> <span a="b"> [%s] </span> </t>
|
|
<div t-att-abc="abc % add_abc">123</div>
|
|
</t>'''
|
|
})
|
|
result = """
|
|
<div abc=" <span a="b"> [&#34;yes&#34;] </span> ">123</div>
|
|
"""
|
|
values = {'add_abc': '"yes"'}
|
|
rendered = self.env['ir.qweb']._render(t.id, values)
|
|
self.assertEqual(rendered.strip(), result.strip())
|
|
|
|
def test_set_body_2(self):
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="set">
|
|
<t t-set="abc"> <span a="b"> toto </span> </t>
|
|
<div t-att-abc="'[%s]' % abc">123</div>
|
|
<div class="a1" t-out="abc"/>
|
|
<div class="a2" t-out="'[%s]' % abc"/>
|
|
</t>'''
|
|
})
|
|
result = """
|
|
<div abc="[ <span a="b"> toto </span> ]">123</div>
|
|
<div class="a1"> <span a="b"> toto </span> </div>
|
|
<div class="a2">[ <span a="b"> toto </span> ]</div>
|
|
"""
|
|
rendered = self.env['ir.qweb']._render(t.id)
|
|
self.assertEqual(rendered.strip(), result.strip())
|
|
|
|
def test_set_error_1(self):
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="set">
|
|
<t t-set="" t-value="1"/>
|
|
</t>'''
|
|
})
|
|
|
|
with self.assertRaises(QWebException):
|
|
self.env['ir.qweb']._render(t.id)
|
|
|
|
try:
|
|
self.env['ir.qweb']._render(t.id)
|
|
except QWebException as e:
|
|
error = str(e)
|
|
self.assertIn("KeyError: 't-set'", error)
|
|
self.assertIn('<t t-set="" t-value="1"/>', error)
|
|
|
|
def test_set_error_2(self):
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="set">
|
|
<t t-set="b-2" t-value="1"/>
|
|
</t>'''
|
|
})
|
|
|
|
with self.assertRaises(QWebException):
|
|
self.env['ir.qweb']._render(t.id)
|
|
|
|
try:
|
|
self.env['ir.qweb']._render(t.id)
|
|
except QWebException as e:
|
|
error = str(e)
|
|
self.assertIn("The varname can only contain alphanumeric characters and underscores", error)
|
|
self.assertIn('<t t-set="b-2" t-value="1"/>', error)
|
|
|
|
def test_out(self):
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="out-format"><div t-out="a">Default</div></t>'''
|
|
})
|
|
result = """<div>1</div>"""
|
|
rendered = self.env['ir.qweb']._render(t.id, {'a': 1})
|
|
self.assertEqual(rendered.strip(), result.strip())
|
|
|
|
def test_out_format_1(self):
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="out-format">
|
|
<t t-set="final_message">Powered by %s%s</t>
|
|
<div t-out="final_message % (a, b and ('-%s' % b) or '')"/>
|
|
</t>'''
|
|
})
|
|
result = """
|
|
<div>Powered by 1-2</div>
|
|
"""
|
|
rendered = self.env['ir.qweb']._render(t.id, {'a': 1, 'b': 2})
|
|
self.assertEqual(rendered.strip(), result.strip())
|
|
|
|
def test_out_format_2(self):
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="set">
|
|
<t t-set="toto">Toto %s</t>
|
|
<t t-set="abc"> <span a="b"> [%s , %s] </span> </t>
|
|
<div t-out="(abc % (add_abc, toto)) % 5">123</div>
|
|
</t>'''
|
|
})
|
|
result = """
|
|
<div> <span a="b"> ["yes" , Toto 5] </span> </div>
|
|
"""
|
|
values = {'add_abc': '"yes"'}
|
|
rendered = self.env['ir.qweb']._render(t.id, values)
|
|
self.assertEqual(rendered.strip(), result.strip())
|
|
|
|
def test_out_format_3(self):
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="attr-set">
|
|
<t t-set="toto">Toto %s</t>
|
|
<t t-set="abc"> <span a="b"> a </span> </t>
|
|
<div t-out="(toto + abc) % v">123</div>
|
|
</t>'''
|
|
})
|
|
result = """
|
|
<div>Toto "yes" <span a="b"> a </span> </div>
|
|
"""
|
|
values = {'v': '"yes"'}
|
|
rendered = self.env['ir.qweb']._render(t.id, values)
|
|
self.assertEqual(rendered.strip(), result.strip())
|
|
|
|
def test_out_format_4(self):
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="attr-set">
|
|
<t t-set="abc"> <span a="b"> a </span> </t>
|
|
<div t-out="(v + abc)">123</div>
|
|
</t>'''
|
|
})
|
|
result = """
|
|
<div>"yes" <span a="b"> a </span> </div>
|
|
"""
|
|
values = {'v': '"yes"'}
|
|
rendered = self.env['ir.qweb']._render(t.id, values)
|
|
self.assertEqual(rendered.strip(), result.strip())
|
|
|
|
def test_out_format_5(self):
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="attr-set">
|
|
<t t-set="abc"> <span a="b"> a </span> </t>
|
|
<div t-out="(abc + v)">123</div>
|
|
</t>'''
|
|
})
|
|
result = """
|
|
<div> <span a="b"> a </span> "yes"</div>
|
|
"""
|
|
values = {'v': '"yes"'}
|
|
rendered = self.env['ir.qweb']._render(t.id, values)
|
|
self.assertEqual(rendered.strip(), result.strip())
|
|
|
|
def test_out_format_6(self):
|
|
# Use str method will use the string value. t-out will escape this str
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="attr-set">
|
|
<t t-set="abc"> <span a="b"> a </span> </t>
|
|
<div t-out="(abc.strip() + v)">123</div>
|
|
</t>'''
|
|
})
|
|
result = """
|
|
<div><span a="b"> a </span>"yes"</div>
|
|
"""
|
|
values = {'v': '"yes"'}
|
|
rendered = self.env['ir.qweb']._render(t.id, values)
|
|
self.assertEqual(rendered.strip(), result.strip())
|
|
|
|
def test_out_escape_text(self):
|
|
view1 = self.env['ir.ui.view'].create({
|
|
'name': "dummy",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="base.dummy"><root><span t-out="text" t-options-widget="'text'"/></root></t>
|
|
"""
|
|
})
|
|
html = self.env['ir.qweb']._render(view1.id, {'text': """a
|
|
b <b>c</b>"""})
|
|
self.assertEqual(html, """<root><span data-oe-type="text" data-oe-expression="text">a<br>
|
|
b <b>c</b></span></root>""")
|
|
|
|
def test_out_markup(self):
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="esc-markup">
|
|
<t t-set="content"><span>toto</span></t>
|
|
<div t-out="content"/>
|
|
</t>'''
|
|
})
|
|
result = """
|
|
<div><span>toto</span></div>
|
|
"""
|
|
rendered = self.env['ir.qweb']._render(t.id, {})
|
|
self.assertEqual(rendered.strip(), result.strip())
|
|
|
|
def test_out_default_value(self):
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="out-default">
|
|
<span rows="10" t-out="a">
|
|
DEFAULT
|
|
<t t-out="'Text'" />
|
|
</span>
|
|
</t>'''
|
|
})
|
|
result = """
|
|
<span rows="10">Hello</span>
|
|
"""
|
|
rendered = self.env['ir.qweb']._render(t.id, {'a': 'Hello'})
|
|
self.assertEqual(str(rendered.strip()), result.strip())
|
|
|
|
result = """
|
|
<span rows="10">
|
|
DEFAULT
|
|
Text
|
|
</span>
|
|
"""
|
|
rendered = self.env['ir.qweb']._render(t.id, {})
|
|
self.assertEqual(str(rendered.strip()), result.strip())
|
|
|
|
def test_esc_markup(self):
|
|
# t-esc is equal to t-out
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="esc-markup">
|
|
<t t-set="content"><span>toto</span></t>
|
|
<div t-esc="content"/>
|
|
</t>'''
|
|
})
|
|
ref = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="esc-markup">
|
|
<t t-set="content"><span>toto</span></t>
|
|
<div t-out="content"/>
|
|
</t>'''
|
|
})
|
|
rendered = self.env['ir.qweb']._render(t.id, {})
|
|
result = self.env['ir.qweb']._render(ref.id, {})
|
|
self.assertEqual(rendered.strip(), result.strip())
|
|
|
|
def test_if_from_body(self):
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="attr-set">
|
|
<t t-set="abc"> <span a="b"> a </span> </t>
|
|
<div t-if="abc">123</div>
|
|
<div t-if="not abc">456</div>
|
|
</t>'''
|
|
})
|
|
result = """
|
|
<div>123</div>
|
|
"""
|
|
rendered = self.env['ir.qweb']._render(t.id)
|
|
self.assertEqual(rendered.strip(), result.strip())
|
|
|
|
def test_if_spaces(self):
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="test">
|
|
<div>
|
|
0
|
|
<t>1</t>
|
|
<t t-if="True">2</t>
|
|
<t>3</t>
|
|
4
|
|
<t>5</t>
|
|
6
|
|
<t t-if="True">7</t>
|
|
8
|
|
<t t-if="False">9</t>
|
|
10
|
|
<t t-if="False">11</t>
|
|
<t t-else="">12</t>
|
|
13
|
|
</div>
|
|
</t>'''
|
|
})
|
|
result = """
|
|
<div>
|
|
0
|
|
1
|
|
2
|
|
3
|
|
4
|
|
5
|
|
6
|
|
7
|
|
8
|
|
10
|
|
12
|
|
13
|
|
</div>
|
|
"""
|
|
rendered = str(self.env['ir.qweb']._render(t.id))
|
|
self.assertEqual(rendered.strip(), result.strip())
|
|
|
|
def test_error_message_1(self):
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="test">
|
|
<section>
|
|
<div t-esc="abc + def">
|
|
<span>content</span>
|
|
</div>
|
|
</section>
|
|
</t>'''
|
|
})
|
|
with self.assertRaises(QWebException):
|
|
self.env['ir.qweb']._render(t.id)
|
|
|
|
try:
|
|
self.env['ir.qweb']._render(t.id)
|
|
except QWebException as e:
|
|
error = str(e)
|
|
self.assertIn('<div t-esc="abc + def"/>', error)
|
|
|
|
def test_error_message_2(self):
|
|
t = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name="test">
|
|
<section>
|
|
<div t-esc="abc + def + (">
|
|
<span>content</span>
|
|
</div>
|
|
</section>
|
|
</t>'''
|
|
})
|
|
with self.assertRaises(QWebException):
|
|
self.env['ir.qweb']._render(t.id)
|
|
|
|
try:
|
|
self.env['ir.qweb']._render(t.id)
|
|
except QWebException as e:
|
|
error = str(e)
|
|
self.assertIn('Can not compile expression', error)
|
|
self.assertIn('<div t-esc="abc + def + ("/>', error)
|
|
|
|
def test_error_message_3(self):
|
|
# The format error tells the developer what to do.
|
|
template = '''<section>
|
|
<div t-esc="1+2">
|
|
<span>content</span>
|
|
</div>
|
|
</section>'''
|
|
with self.assertRaises(ValueError):
|
|
self.env['ir.qweb']._render(template)
|
|
try:
|
|
self.env['ir.qweb']._render(template)
|
|
except ValueError as e:
|
|
self.assertIn('Inline templates must be passed as `etree` documents', str(e))
|
|
|
|
template = '''toto <t t-esc="content"/>'''
|
|
with self.assertRaises(ValueError):
|
|
self.env['ir.qweb']._render(template)
|
|
try:
|
|
self.env['ir.qweb']._render(template)
|
|
except ValueError as e:
|
|
self.assertIn('Inline templates must be passed as `etree` documents', str(e))
|
|
|
|
def test_error_message_4(self):
|
|
# Template record view not found.
|
|
with self.assertRaises(MissingError):
|
|
self.env['ir.qweb']._render(-999)
|
|
try:
|
|
self.env['ir.qweb']._render(-999)
|
|
except MissingError as e:
|
|
self.assertIn('Record does not exist or has been deleted.', str(e))
|
|
|
|
with self.assertRaises(ValueError):
|
|
self.env['ir.qweb']._render('not.wrong_template_xmlid')
|
|
try:
|
|
self.env['ir.qweb']._render('not.wrong_template_xmlid')
|
|
except ValueError as e:
|
|
self.assertIn('External ID not found in the system', str(e))
|
|
|
|
with self.assertRaises(AssertionError):
|
|
self.env['ir.qweb']._render(False)
|
|
try:
|
|
self.env['ir.qweb']._render(False)
|
|
except AssertionError as e:
|
|
self.assertIn('template is required', str(e))
|
|
|
|
def test_error_message_5(self):
|
|
# Error not found a first rendering.
|
|
with self.assertRaises(MissingError, msg="Not Found"):
|
|
self.env['ir.qweb']._render(-9999)
|
|
|
|
@mute_logger('odoo.addons.base.models.ir_qweb') # warning for template not found
|
|
def test_error_message_6(self):
|
|
# Error not found a second rendering (first rendering with option hide this error).
|
|
html = self.env['ir.qweb']._render(-9999, raise_if_not_found=False)
|
|
self.assertEqual('', html)
|
|
|
|
# re try this rendering without any error (use cached method)
|
|
html = self.env['ir.qweb']._render(-9999, raise_if_not_found=False)
|
|
self.assertEqual('', html)
|
|
|
|
# re try this rendering but raise (use cached method)
|
|
with self.assertRaises(MissingError, msg="Not Found"):
|
|
self.env['ir.qweb']._render(-9999)
|
|
|
|
def test_error_message_7(self):
|
|
# UserError not found a first rendering.
|
|
with self.assertRaises(UserError, msg="Not Found"):
|
|
self.env['ir.qweb']._render(-9999)
|
|
|
|
@mute_logger('odoo.addons.base.models.ir_qweb') # warning for template not found
|
|
def test_error_message_8(self):
|
|
# UserError not found a second rendering (first rendering with option hide this error).
|
|
html = self.env['ir.qweb']._render(-9999, raise_if_not_found=False)
|
|
self.assertEqual('', html)
|
|
|
|
# re try this rendering without any error (use cached method)
|
|
html = self.env['ir.qweb']._render(-9999, raise_if_not_found=False)
|
|
self.assertEqual('', html)
|
|
|
|
# re try this rendering but raise (use cached method)
|
|
with self.assertRaises(UserError, msg="Not Found"):
|
|
self.env['ir.qweb']._render(-9999)
|
|
|
|
def test_call_set(self):
|
|
view0 = self.env['ir.ui.view'].create({
|
|
'name': "dummy",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="base.dummy">
|
|
<table>
|
|
<tr><td t-out="a"/></tr>
|
|
<t t-set="a">3</t>
|
|
</table>
|
|
</t>
|
|
"""
|
|
})
|
|
self.env.cr.execute("INSERT INTO ir_model_data(name, model, res_id, module)"
|
|
"VALUES ('dummy', 'ir.ui.view', %s, 'base')", [view0.id])
|
|
|
|
view1 = self.env['ir.ui.view'].create({
|
|
'name': "other",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="base.other">
|
|
<div>
|
|
<t t-set="a">1</t>
|
|
<t t-set="b">1</t>
|
|
<t t-call="base.dummy">
|
|
<t t-set="b">2</t>
|
|
</t>
|
|
<span t-out="a"/>
|
|
<span t-out="b"/>
|
|
</div>
|
|
</t>
|
|
"""
|
|
})
|
|
|
|
result = self.env['ir.qweb']._render(view1.id, {})
|
|
self.assertEqual(etree.fromstring(result), etree.fromstring("""
|
|
<div>
|
|
<table>
|
|
<tr><td>1</td></tr>
|
|
</table>
|
|
<span>1</span>
|
|
<span>1</span>
|
|
</div>
|
|
"""), 'render t-call use lexical scoping, t-call content use independant scoping')
|
|
|
|
def test_call_error(self):
|
|
view1 = self.env['ir.ui.view'].create({
|
|
'name': "other",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="base.other">
|
|
<div>
|
|
<t t-call="base.dummy"/>
|
|
</div>
|
|
</t>
|
|
"""
|
|
})
|
|
|
|
with self.assertRaises(QWebException):
|
|
self.env['ir.qweb']._render(view1.id)
|
|
|
|
try:
|
|
self.env['ir.qweb']._render(view1.id)
|
|
except QWebException as e:
|
|
error = str(e)
|
|
self.assertIn('External ID not found in the system: base.dummy', error)
|
|
self.assertIn('<t t-call="base.dummy"/>', error)
|
|
|
|
def test_render_t_call_propagates_t_lang(self):
|
|
current_lang = 'en_US'
|
|
other_lang = 'fr_FR'
|
|
|
|
lang = self.env['res.lang']._activate_lang(other_lang)
|
|
lang.write({
|
|
'decimal_point': '*',
|
|
'thousands_sep': '/'
|
|
})
|
|
|
|
view1 = self.env['ir.ui.view'].create({
|
|
'name': "callee",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="base.callee">
|
|
<t t-esc="9000000.00" t-options="{'widget': 'float', 'precision': 2}" />
|
|
</t>
|
|
"""
|
|
})
|
|
self.env['ir.model.data'].create({
|
|
'name': 'callee',
|
|
'model': 'ir.ui.view',
|
|
'module': 'base',
|
|
'res_id': view1.id,
|
|
})
|
|
|
|
view2 = self.env['ir.ui.view'].create({
|
|
'name': "calling",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="base.calling">
|
|
<t t-call="base.callee" t-lang="'%s'" />
|
|
</t>
|
|
""" % other_lang
|
|
})
|
|
|
|
rendered = self.env['ir.qweb'].with_context(lang=current_lang)._render(view2.id).strip()
|
|
self.assertEqual(rendered, '9/000/000*00')
|
|
|
|
def test_render_barcode(self):
|
|
partner = self.env['res.partner'].create({
|
|
'name': 'bacode_test',
|
|
'barcode': 'test'
|
|
})
|
|
|
|
view = self.env['ir.ui.view'].create({
|
|
'name': "a_barcode_view",
|
|
'type': 'qweb',
|
|
})
|
|
|
|
view.arch = """<div t-field="partner.barcode" t-options="{'widget': 'barcode', 'width': 100, 'height': 30}"/>"""
|
|
rendered = self.env['ir.qweb']._render(view.id, values={'partner': partner}).strip()
|
|
self.assertRegex(rendered, r'<div><img alt="Barcode test" src="data:image/png;base64,\S+"></div>')
|
|
|
|
partner.barcode = '4012345678901'
|
|
view.arch = """<div t-field="partner.barcode" t-options="{'widget': 'barcode', 'symbology': 'EAN13', 'width': 100, 'height': 30, 'img_style': 'width:100%;', 'img_alt': 'Barcode'}"/>"""
|
|
ean_rendered = self.env['ir.qweb']._render(view.id, values={'partner': partner}).strip()
|
|
self.assertRegex(ean_rendered, r'<div><img style="width:100%;" alt="Barcode" src="data:image/png;base64,\S+"></div>')
|
|
|
|
view.arch = """<div t-field="partner.barcode" t-options="{'widget': 'barcode', 'symbology': 'auto', 'width': 100, 'height': 30, 'img_style': 'width:100%;', 'img_alt': 'Barcode'}"/>"""
|
|
auto_rendered = self.env['ir.qweb']._render(view.id, values={'partner': partner}).strip()
|
|
self.assertRegex(auto_rendered, r'<div><img style="width:100%;" alt="Barcode" src="data:image/png;base64,\S+"></div>')
|
|
|
|
def test_render_comment_tail(self):
|
|
""" Test the rendering of a tail text, near a comment.
|
|
"""
|
|
|
|
view1 = self.env['ir.ui.view'].create({
|
|
'name': "dummy",
|
|
'type': "qweb",
|
|
'arch': """
|
|
<t>
|
|
<!-- it is a comment -->
|
|
<!-- it is another comment -->
|
|
Text 1
|
|
<!-- it is still another comment -->
|
|
Text 2
|
|
<t>ok</t>
|
|
</t>
|
|
"""
|
|
})
|
|
emptyline = '\n '
|
|
expected = markupsafe.Markup('Text 1' + emptyline + emptyline + 'Text 2' + emptyline + 'ok')
|
|
self.assertEqual(self.env['ir.qweb']._render(view1.id).strip(), expected)
|
|
|
|
def test_render_comments(self):
|
|
""" Test the rendering of comments with and without the
|
|
preserve_comments option.
|
|
"""
|
|
comment = '<!-- Hello, world! -->'
|
|
view = self.env['ir.ui.view'].create({
|
|
'name': 'dummy',
|
|
'type': 'qweb',
|
|
'arch': f'<t><p>{comment}</p></t>'
|
|
})
|
|
QWeb = self.env['ir.qweb']
|
|
self.assertEqual(
|
|
QWeb.with_context(preserve_comments=False)._render(view.id),
|
|
markupsafe.Markup('<p></p>'),
|
|
"Should not have the comment")
|
|
self.env.registry.clear_cache('templates')
|
|
self.assertEqual(
|
|
QWeb.with_context(preserve_comments=True)._render(view.id),
|
|
markupsafe.Markup(f'<p>{comment}</p>'),
|
|
"Should have the comment")
|
|
|
|
def test_render_processing_instructions(self):
|
|
""" Test the rendering of processing instructions with and without the
|
|
preserve_comments option.
|
|
"""
|
|
p_instruction = '<?hello world?>'
|
|
view = self.env['ir.ui.view'].create({
|
|
'name': 'dummy',
|
|
'type': 'qweb',
|
|
'arch': f'<t><p>{p_instruction}</p></t>'
|
|
})
|
|
QWeb = self.env['ir.qweb']
|
|
self.assertEqual(
|
|
QWeb.with_context(preserve_comments=False)._render(view.id),
|
|
markupsafe.Markup('<p></p>'),
|
|
"Should not have the processing instruction")
|
|
self.env.registry.clear_cache('templates')
|
|
self.assertEqual(
|
|
QWeb.with_context(preserve_comments=True)._render(view.id),
|
|
markupsafe.Markup(f'<p>{p_instruction}</p>'),
|
|
"Should have the processing instruction")
|
|
|
|
def test_render_widget_contact(self):
|
|
u = self.env['res.users'].create({
|
|
'name': 'Test',
|
|
'login': 'test@example.com',
|
|
})
|
|
u.name = ""
|
|
view1 = self.env['ir.ui.view'].create({
|
|
'name': "dummy",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="base.dummy"><root><span t-esc="user" t-options='{"widget": "contact", "fields": ["name"]}' /></root></t>
|
|
"""
|
|
})
|
|
self.env['ir.qweb']._render(view1.id, {'user': u}) # should not crash
|
|
|
|
def test_render_widget_duration_fallback(self):
|
|
self.env['res.lang'].with_context(active_test=False).search([('code', '=', 'pt_BR')]).active = True
|
|
view1 = self.env['ir.ui.view'].create({
|
|
'name': "dummy",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="base.dummy"><root><span t-esc="3600" t-options='{"widget": "duration", "format": "short"}' /></root></t>
|
|
"""
|
|
})
|
|
self.env['ir.qweb'].with_context(lang='pt_BR')._render(view1.id, {}) # should not crash
|
|
|
|
def test_void_element(self):
|
|
view = self.env['ir.ui.view'].create({
|
|
'name': 'master',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name='master'>
|
|
<meta name="1"/>
|
|
<t t-set="data" t-value="1"/>
|
|
<meta groups="base.group_no_one" name="2"/>
|
|
<meta t-if="False" name="3"/>
|
|
<meta t-if="True" name="4"/>
|
|
<span t-out="1"/>
|
|
</t>'''
|
|
})
|
|
|
|
result = '''
|
|
<meta name="1"/>
|
|
<meta name="4"/>
|
|
<span>1</span>
|
|
'''
|
|
rendered = self.env['ir.qweb']._render(view.id)
|
|
|
|
self.assertEqual(str(rendered).strip(), result.strip())
|
|
|
|
def test_space_remove_technical_space_t_foreach(self):
|
|
view = self.env['ir.ui.view'].create({
|
|
'name': 'master',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name='master'>
|
|
<section>
|
|
<article t-foreach="[0, 1, 2]" t-as="value" t-esc="value"/>
|
|
<t t-foreach="[0, 1, 2]" t-as="value">
|
|
<article t-esc="value"/>
|
|
</t>
|
|
</section>
|
|
</t>'''})
|
|
|
|
result = '''
|
|
<section>
|
|
<article>0</article><article>1</article><article>2</article>
|
|
<article>0</article>
|
|
<article>1</article>
|
|
<article>2</article>
|
|
</section>'''
|
|
|
|
rendered = self.env['ir.qweb']._render(view.id)
|
|
|
|
self.assertEqual(str(rendered), result)
|
|
|
|
def test_space_remove_technical_all(self):
|
|
test = self.env['ir.ui.view'].create({
|
|
'name': 'test',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name='test'>
|
|
<span t-out="value"/>
|
|
</t>'''
|
|
})
|
|
self.env.cr.execute("INSERT INTO ir_model_data(name, model, res_id, module)"
|
|
"VALUES ('test', 'ir.ui.view', %s, 'base')", [test.id])
|
|
|
|
view = self.env['ir.ui.view'].create({
|
|
'name': 'master',
|
|
'type': 'qweb',
|
|
'arch_db': '''<t t-name='master'>
|
|
|
|
<section>
|
|
<meta name="1"/>
|
|
<t t-set="data" t-value="1"/>
|
|
<meta groups="base.group_no_one" name="2"/>
|
|
<meta t-if="False" name="3"/>
|
|
<meta t-if="True" name="4"/>
|
|
|
|
<article>
|
|
<t t-foreach="[0, 1]" t-as="value">
|
|
<t t-call="base.test"/>
|
|
</t>
|
|
|
|
<t t-if="False">
|
|
a
|
|
</t>
|
|
|
|
b
|
|
|
|
<t t-if="True">
|
|
c <t t-out="1"/>
|
|
d
|
|
</t>
|
|
</article>
|
|
|
|
|
|
<article>
|
|
<div t-foreach="[0, 1]" t-as="value">
|
|
<t t-call="base.test"/>
|
|
</div>
|
|
|
|
<i t-if="False">
|
|
a
|
|
</i>
|
|
<u t-if="False">
|
|
a
|
|
</u>
|
|
|
|
b
|
|
|
|
<i t-if="True">
|
|
c <t t-out="1"/>
|
|
d
|
|
</i>
|
|
</article>
|
|
</section>
|
|
</t>'''
|
|
})
|
|
|
|
result = '''
|
|
<section>
|
|
<meta name="1"/>
|
|
<meta name="4"/>
|
|
|
|
<article>
|
|
<span>0</span>
|
|
<span>1</span>
|
|
|
|
|
|
b
|
|
|
|
c 1
|
|
d
|
|
</article>
|
|
|
|
|
|
<article>
|
|
<div>
|
|
<span>0</span>
|
|
</div><div>
|
|
<span>1</span>
|
|
</div>
|
|
|
|
|
|
b
|
|
|
|
<i>
|
|
c 1
|
|
d
|
|
</i>
|
|
</article>
|
|
</section>'''
|
|
|
|
rendered = self.env['ir.qweb']._render(view.id)
|
|
self.assertEqual(str(rendered), result)
|
|
|
|
class TestQwebCache(TransactionCase):
|
|
def test_render_xml_cache_base(self):
|
|
view1 = self.env['ir.ui.view'].create({
|
|
'name': "dummy",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="base.dummy">
|
|
<div t-cache="cache_id" class="toto">
|
|
<table>
|
|
<tr><td><span t-esc="value[0]"/></td></tr>
|
|
<tr><td><span t-esc="value[1]"/></td></tr>
|
|
<tr><td><span t-esc="value[2]"/></td></tr>
|
|
</table>
|
|
</div>
|
|
</t>
|
|
"""
|
|
})
|
|
expected_result = etree.fromstring("""
|
|
<div class="toto">
|
|
<table>
|
|
<tr><td><span>1</span></td></tr>
|
|
<tr><td><span>2</span></td></tr>
|
|
<tr><td><span>3</span></td></tr>
|
|
</table>
|
|
</div>
|
|
""")
|
|
|
|
IrQweb = self.env['ir.qweb'].with_context(is_t_cache_disabled=False)
|
|
|
|
result = etree.fromstring(IrQweb._render(view1.id, {'cache_id': 1, 'value': [1, 2, 3]}))
|
|
self.assertEqual(result, expected_result, 'First rendering (add in cache)')
|
|
|
|
result = etree.fromstring(IrQweb._render(view1.id, {'cache_id': 1, 'value': [10, 20, 30]}))
|
|
self.assertEqual(result, expected_result, 'Next rendering use cache')
|
|
|
|
def test_render_xml_cache_different(self):
|
|
view1 = self.env['ir.ui.view'].create({
|
|
'name': "dummy",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="base.dummy">
|
|
<div class="toto">
|
|
<table t-cache="cache_id">
|
|
<tr><td><span t-esc="value[0]"/></td></tr>
|
|
<tr><td><span t-esc="value[1]"/></td></tr>
|
|
<tr><td><span t-esc="value[2]"/></td></tr>
|
|
</table>
|
|
<table t-cache="cache_id2">
|
|
<tr><td><span t-esc="value2[0]"/></td></tr>
|
|
<tr><td><span t-esc="value2[1]"/></td></tr>
|
|
<tr><td><span t-esc="value2[2]"/></td></tr>
|
|
</table>
|
|
</div>
|
|
</t>
|
|
"""
|
|
})
|
|
IrQweb = self.env['ir.qweb'].with_context(is_t_cache_disabled=False)
|
|
|
|
# use same cache id, display the same content
|
|
result = etree.fromstring(IrQweb._render(view1.id, {
|
|
'cache_id': (1,),
|
|
'cache_id2': (1,),
|
|
'value': [1, 2, 3],
|
|
'value2': [10, 20, 30]
|
|
}))
|
|
self.assertEqual(result, etree.fromstring("""
|
|
<div class="toto">
|
|
<table>
|
|
<tr><td><span>1</span></td></tr>
|
|
<tr><td><span>2</span></td></tr>
|
|
<tr><td><span>3</span></td></tr>
|
|
</table>
|
|
<table>
|
|
<tr><td><span>10</span></td></tr>
|
|
<tr><td><span>20</span></td></tr>
|
|
<tr><td><span>30</span></td></tr>
|
|
</table>
|
|
</div>
|
|
"""), 'First rendering (add in cache with different cache)')
|
|
|
|
result = etree.fromstring(IrQweb._render(view1.id, {
|
|
'cache_id': (2, 5, 6),
|
|
'cache_id2': (2, 5, 5),
|
|
'value': [41, 42, 43],
|
|
'value2': [51, 52, 53]
|
|
}))
|
|
self.assertEqual(result, etree.fromstring("""
|
|
<div class="toto">
|
|
<table>
|
|
<tr><td><span>41</span></td></tr>
|
|
<tr><td><span>42</span></td></tr>
|
|
<tr><td><span>43</span></td></tr>
|
|
</table>
|
|
<table>
|
|
<tr><td><span>51</span></td></tr>
|
|
<tr><td><span>52</span></td></tr>
|
|
<tr><td><span>53</span></td></tr>
|
|
</table>
|
|
</div>
|
|
"""), 'Use different cache id')
|
|
|
|
def test_render_xml_cache_contains_nocache(self):
|
|
view1 = self.env['ir.ui.view'].create({
|
|
'name': "dummy",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="base.dummy">
|
|
<div t-cache="cache_id" class="toto">
|
|
<table>
|
|
<tr><td><span t-esc="value[0]"/></td></tr>
|
|
<tr t-nocache=""><td><span t-esc="value[1]"/></td></tr>
|
|
<tr><td><span t-esc="value[2]"/></td></tr>
|
|
</table>
|
|
</div>
|
|
</t>
|
|
"""
|
|
})
|
|
IrQweb = self.env['ir.qweb'].with_context(is_t_cache_disabled=False)
|
|
|
|
result = etree.fromstring(IrQweb._render(view1.id, {'cache_id': 1, 'value': [1, 2, 3]}))
|
|
self.assertEqual(result, etree.fromstring("""
|
|
<div class="toto">
|
|
<table>
|
|
<tr><td><span>1</span></td></tr>
|
|
<tr><td><span>2</span></td></tr>
|
|
<tr><td><span>3</span></td></tr>
|
|
</table>
|
|
</div>
|
|
"""), 'First rendering add compiled values in cache')
|
|
|
|
result = etree.fromstring(IrQweb._render(view1.id, {'cache_id': 1, 'value': [10, 20, 30]}))
|
|
self.assertEqual(result, etree.fromstring("""
|
|
<div class="toto">
|
|
<table>
|
|
<tr><td><span>1</span></td></tr>
|
|
<tr><td><span>20</span></td></tr>
|
|
<tr><td><span>3</span></td></tr>
|
|
</table>
|
|
</div>
|
|
"""), 'Next rendering use cache exept for t-nocache=""')
|
|
|
|
def test_render_xml_cache_nocache_cache(self):
|
|
view1 = self.env['ir.ui.view'].create({
|
|
'name': "dummy",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="base.dummy">
|
|
<div class="toto">
|
|
<table t-cache="cache_id">
|
|
<tr><td><t t-esc="value[0]"/></td></tr>
|
|
<tr>
|
|
<td>
|
|
<table t-nocache="The content is not used, we can put documentation in it." t-cache="cache_id2">
|
|
<tr><td><t t-esc="value2[0]"/></td></tr>
|
|
<tr><td><t t-esc="value2[1]"/></td></tr>
|
|
<tr><td><t t-esc="value2[2]"/></td></tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
<tr><td><t t-esc="value[2]"/></td></tr>
|
|
</table>
|
|
</div>
|
|
</t>
|
|
"""
|
|
})
|
|
|
|
IrQweb = self.env['ir.qweb'].with_context(is_t_cache_disabled=False)
|
|
|
|
# use same cache id, display the same content
|
|
result = etree.fromstring(IrQweb._render(view1.id, {
|
|
'cache_id': (1, 0),
|
|
'cache_id2': (2, 0),
|
|
'value': [1, 2, 3],
|
|
'value2': [10, 20, 30]
|
|
}))
|
|
self.assertEqual(result, etree.fromstring("""
|
|
<div class="toto">
|
|
<table>
|
|
<tr><td>1</td></tr>
|
|
<tr>
|
|
<td>
|
|
<table>
|
|
<tr><td>10</td></tr>
|
|
<tr><td>20</td></tr>
|
|
<tr><td>30</td></tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
<tr><td>3</td></tr>
|
|
</table>
|
|
</div>
|
|
"""), 'First rendering (add in cache)')
|
|
|
|
result = etree.fromstring(IrQweb._render(view1.id, {
|
|
'cache_id': (1, 0),
|
|
'cache_id2': (2, 1),
|
|
'value': [41, 42, 43],
|
|
'value2': [51, 52, 53]
|
|
}))
|
|
self.assertEqual(result, etree.fromstring("""
|
|
<div class="toto">
|
|
<table>
|
|
<tr><td>1</td></tr>
|
|
<tr>
|
|
<td>
|
|
<table>
|
|
<tr><td>51</td></tr>
|
|
<tr><td>52</td></tr>
|
|
<tr><td>53</td></tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
<tr><td>3</td></tr>
|
|
</table>
|
|
</div>
|
|
"""), 'Second rendering (change inside cache id)')
|
|
|
|
result = etree.fromstring(IrQweb._render(view1.id, {
|
|
'cache_id': (1, 1),
|
|
'cache_id2': (2, 0),
|
|
'value': [31, 32, 33],
|
|
'value2': [51, 52, 53]
|
|
}))
|
|
self.assertEqual(result, etree.fromstring("""
|
|
<div class="toto">
|
|
<table>
|
|
<tr><td>31</td></tr>
|
|
<tr>
|
|
<td>
|
|
<table>
|
|
<tr><td>10</td></tr>
|
|
<tr><td>20</td></tr>
|
|
<tr><td>30</td></tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
<tr><td>33</td></tr>
|
|
</table>
|
|
</div>
|
|
"""), 'Third rendering (change main cache id, old cache inside)')
|
|
|
|
def test_render_xml_cache_nocache_cache_on_same_tag(self):
|
|
view1 = self.env['ir.ui.view'].create({
|
|
'name': "dummy",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="base.dummy">
|
|
<div class="toto">
|
|
<table t-cache="cache_id">
|
|
<tr><td><t t-esc="value[0]"/></td></tr>
|
|
<tr t-nocache="">
|
|
<td>
|
|
<table t-cache="cache_id2">
|
|
<tr><td><t t-esc="value2[0]"/></td></tr>
|
|
<tr><td><t t-esc="value2[1]"/></td></tr>
|
|
<tr><td><t t-esc="value2[2]"/></td></tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
<tr><td><t t-esc="value[2]"/></td></tr>
|
|
</table>
|
|
</div>
|
|
</t>
|
|
"""
|
|
})
|
|
|
|
IrQweb = self.env['ir.qweb'].with_context(is_t_cache_disabled=False)
|
|
|
|
# use same cache id, display the same content
|
|
result = etree.fromstring(IrQweb._render(view1.id, {
|
|
'cache_id': (1, 0),
|
|
'cache_id2': (2, 0),
|
|
'value': [1, 2, 3],
|
|
'value2': [10, 20, 30]
|
|
}))
|
|
self.assertEqual(result, etree.fromstring("""
|
|
<div class="toto">
|
|
<table>
|
|
<tr><td>1</td></tr>
|
|
<tr>
|
|
<td>
|
|
<table>
|
|
<tr><td>10</td></tr>
|
|
<tr><td>20</td></tr>
|
|
<tr><td>30</td></tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
<tr><td>3</td></tr>
|
|
</table>
|
|
</div>
|
|
"""), 'First rendering (add in cache)')
|
|
|
|
result = etree.fromstring(IrQweb._render(view1.id, {
|
|
'cache_id': (1, 0),
|
|
'cache_id2': (2, 1),
|
|
'value': [41, 42, 43],
|
|
'value2': [51, 52, 53]
|
|
}))
|
|
self.assertEqual(result, etree.fromstring("""
|
|
<div class="toto">
|
|
<table>
|
|
<tr><td>1</td></tr>
|
|
<tr>
|
|
<td>
|
|
<table>
|
|
<tr><td>51</td></tr>
|
|
<tr><td>52</td></tr>
|
|
<tr><td>53</td></tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
<tr><td>3</td></tr>
|
|
</table>
|
|
</div>
|
|
"""), 'Second rendering (change inside cache id)')
|
|
|
|
result = etree.fromstring(IrQweb._render(view1.id, {
|
|
'cache_id': (1, 1),
|
|
'cache_id2': (2, 0),
|
|
'value': [31, 32, 33],
|
|
'value2': [51, 52, 53]
|
|
}))
|
|
self.assertEqual(result, etree.fromstring("""
|
|
<div class="toto">
|
|
<table>
|
|
<tr><td>31</td></tr>
|
|
<tr>
|
|
<td>
|
|
<table>
|
|
<tr><td>10</td></tr>
|
|
<tr><td>20</td></tr>
|
|
<tr><td>30</td></tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
<tr><td>33</td></tr>
|
|
</table>
|
|
</div>
|
|
"""), 'Third rendering (change main cache id, old cache inside)')
|
|
|
|
def test_render_xml_dont_use_cache_base(self):
|
|
view1 = self.env['ir.ui.view'].create({
|
|
'name': "dummy",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="base.dummy">
|
|
<div t-cache="cache_id" class="toto">
|
|
<table>
|
|
<tr><td><span t-esc="value[0]"/></td></tr>
|
|
<tr><td><span t-esc="value[1]"/></td></tr>
|
|
<tr><td><span t-esc="value[2]"/></td></tr>
|
|
</table>
|
|
</div>
|
|
</t>
|
|
"""
|
|
})
|
|
IrQweb = self.env['ir.qweb'].with_context(is_t_cache_disabled=True)
|
|
|
|
result = etree.fromstring(IrQweb._render(view1.id, {'cache_id': 1, 'value': [1, 2, 3]}))
|
|
self.assertEqual(result, etree.fromstring("""
|
|
<div class="toto">
|
|
<table>
|
|
<tr><td><span>1</span></td></tr>
|
|
<tr><td><span>2</span></td></tr>
|
|
<tr><td><span>3</span></td></tr>
|
|
</table>
|
|
</div>
|
|
"""), 'First rendering')
|
|
|
|
result = etree.fromstring(IrQweb._render(view1.id, {'cache_id': 1, 'value': [10, 20, 30]}))
|
|
self.assertEqual(result, etree.fromstring("""
|
|
<div class="toto">
|
|
<table>
|
|
<tr><td><span>10</span></td></tr>
|
|
<tr><td><span>20</span></td></tr>
|
|
<tr><td><span>30</span></td></tr>
|
|
</table>
|
|
</div>
|
|
"""), 'Next rendering cannot cache (use_qweb_t_cache is False)')
|
|
|
|
def test_render_xml_dont_use_cache_different(self):
|
|
view1 = self.env['ir.ui.view'].create({
|
|
'name': "dummy",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="base.dummy">
|
|
<div class="toto">
|
|
<table t-cache="cache_id">
|
|
<tr><td><span t-esc="value[0]"/></td></tr>
|
|
<tr><td><span t-esc="value[1]"/></td></tr>
|
|
<tr><td><span t-esc="value[2]"/></td></tr>
|
|
</table>
|
|
<table t-cache="cache_id2">
|
|
<tr><td><span t-esc="value2[0]"/></td></tr>
|
|
<tr><td><span t-esc="value2[1]"/></td></tr>
|
|
<tr><td><span t-esc="value2[2]"/></td></tr>
|
|
</table>
|
|
</div>
|
|
</t>
|
|
"""
|
|
})
|
|
IrQweb = self.env['ir.qweb'].with_context(is_t_cache_disabled=True)
|
|
|
|
# use same cache id, display the same content
|
|
result = etree.fromstring(IrQweb._render(view1.id, {
|
|
'cache_id': 1,
|
|
'cache_id2': 1,
|
|
'value': [1, 2, 3],
|
|
'value2': [10, 20, 30]
|
|
}))
|
|
self.assertEqual(result, etree.fromstring("""
|
|
<div class="toto">
|
|
<table>
|
|
<tr><td><span>1</span></td></tr>
|
|
<tr><td><span>2</span></td></tr>
|
|
<tr><td><span>3</span></td></tr>
|
|
</table>
|
|
<table>
|
|
<tr><td><span>10</span></td></tr>
|
|
<tr><td><span>20</span></td></tr>
|
|
<tr><td><span>30</span></td></tr>
|
|
</table>
|
|
</div>
|
|
"""), 'First rendering')
|
|
|
|
result = etree.fromstring(IrQweb._render(view1.id, {
|
|
'cache_id': (2, 5, 6),
|
|
'cache_id2': (2, 5, 5),
|
|
'value': [41, 42, 43],
|
|
'value2': [51, 52, 53]
|
|
}))
|
|
self.assertEqual(result, etree.fromstring("""
|
|
<div class="toto">
|
|
<table>
|
|
<tr><td><span>41</span></td></tr>
|
|
<tr><td><span>42</span></td></tr>
|
|
<tr><td><span>43</span></td></tr>
|
|
</table>
|
|
<table>
|
|
<tr><td><span>51</span></td></tr>
|
|
<tr><td><span>52</span></td></tr>
|
|
<tr><td><span>53</span></td></tr>
|
|
</table>
|
|
</div>
|
|
"""), 'Use different cache id')
|
|
|
|
def test_render_xml_dont_use_cache_contains_nocache(self):
|
|
view1 = self.env['ir.ui.view'].create({
|
|
'name': "dummy",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="base.dummy">
|
|
<div t-cache="cache_id" class="toto">
|
|
<table>
|
|
<tr><td><span t-esc="value[0]"/></td></tr>
|
|
<tr t-nocache=""><td><span t-esc="value[1]"/></td></tr>
|
|
<tr><td><span t-esc="value[2]"/></td></tr>
|
|
</table>
|
|
</div>
|
|
</t>
|
|
"""
|
|
})
|
|
IrQweb = self.env['ir.qweb'].with_context(is_t_cache_disabled=True)
|
|
|
|
result = etree.fromstring(IrQweb._render(view1.id, {'cache_id': 1, 'value': [1, 2, 3]}))
|
|
self.assertEqual(result, etree.fromstring("""
|
|
<div class="toto">
|
|
<table>
|
|
<tr><td><span>1</span></td></tr>
|
|
<tr><td><span>2</span></td></tr>
|
|
<tr><td><span>3</span></td></tr>
|
|
</table>
|
|
</div>
|
|
"""), 'First rendering')
|
|
|
|
result = etree.fromstring(IrQweb._render(view1.id, {'cache_id': 1, 'value': [10, 20, 30]}))
|
|
self.assertEqual(result, etree.fromstring("""
|
|
<div class="toto">
|
|
<table>
|
|
<tr><td><span>10</span></td></tr>
|
|
<tr><td><span>20</span></td></tr>
|
|
<tr><td><span>30</span></td></tr>
|
|
</table>
|
|
</div>
|
|
"""), 'Next rendering cannot use cache (use_qweb_t_cache is False)')
|
|
|
|
def test_render_xml_dont_use_cache_recursive(self):
|
|
view1 = self.env['ir.ui.view'].create({
|
|
'name': "dummy",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="base.dummy">
|
|
<div class="toto">
|
|
<table t-cache="cache_id">
|
|
<tr><td><t t-esc="value[0]"/></td></tr>
|
|
<tr>
|
|
<td>
|
|
<table t-nocache="" t-cache="cache_id2">
|
|
<tr><td><t t-esc="value2[0]"/></td></tr>
|
|
<tr><td><t t-esc="value2[1]"/></td></tr>
|
|
<tr><td><t t-esc="value2[2]"/></td></tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
<tr><td><t t-esc="value[2]"/></td></tr>
|
|
</table>
|
|
</div>
|
|
</t>
|
|
"""
|
|
})
|
|
IrQweb = self.env['ir.qweb'].with_context(is_t_cache_disabled=True)
|
|
|
|
# use same cache id, display the same content
|
|
result = etree.fromstring(IrQweb._render(view1.id, {
|
|
'cache_id': (1, 0),
|
|
'cache_id2': (2, 0),
|
|
'value': [1, 2, 3],
|
|
'value2': [10, 20, 30]
|
|
}))
|
|
self.assertEqual(result, etree.fromstring("""
|
|
<div class="toto">
|
|
<table>
|
|
<tr><td>1</td></tr>
|
|
<tr>
|
|
<td>
|
|
<table>
|
|
<tr><td>10</td></tr>
|
|
<tr><td>20</td></tr>
|
|
<tr><td>30</td></tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
<tr><td>3</td></tr>
|
|
</table>
|
|
</div>
|
|
"""), 'First rendering')
|
|
|
|
result = etree.fromstring(IrQweb._render(view1.id, {
|
|
'cache_id': (1, 0),
|
|
'cache_id2': (2, 1),
|
|
'value': [41, 42, 43],
|
|
'value2': [51, 52, 53]
|
|
}))
|
|
self.assertEqual(result, etree.fromstring("""
|
|
<div class="toto">
|
|
<table>
|
|
<tr><td>41</td></tr>
|
|
<tr>
|
|
<td>
|
|
<table>
|
|
<tr><td>51</td></tr>
|
|
<tr><td>52</td></tr>
|
|
<tr><td>53</td></tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
<tr><td>43</td></tr>
|
|
</table>
|
|
</div>
|
|
"""), 'Next rendering cannot use cache (use_qweb_t_cache is False)')
|
|
|
|
result = etree.fromstring(IrQweb._render(view1.id, {
|
|
'cache_id': (1, 1),
|
|
'cache_id2': (2, 0),
|
|
'value': [31, 32, 33],
|
|
'value2': [51, 52, 53]
|
|
}))
|
|
self.assertEqual(result, etree.fromstring("""
|
|
<div class="toto">
|
|
<table>
|
|
<tr><td>31</td></tr>
|
|
<tr>
|
|
<td>
|
|
<table>
|
|
<tr><td>51</td></tr>
|
|
<tr><td>52</td></tr>
|
|
<tr><td>53</td></tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
<tr><td>33</td></tr>
|
|
</table>
|
|
</div>
|
|
"""), 'Third rendering cannot use cache (use_qweb_t_cache is False)')
|
|
|
|
def test_render_xml_dont_use_cache_false_recursive(self):
|
|
view1 = self.env['ir.ui.view'].create({
|
|
'name': "dummy",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="base.dummy">
|
|
<div class="toto">
|
|
<table t-cache="cache_id">
|
|
<tr><td><t t-esc="value[0]"/></td></tr>
|
|
<tr t-nocache="">
|
|
<td>
|
|
<table t-cache="cache_id2">
|
|
<tr><td><t t-esc="value2[0]"/></td></tr>
|
|
<tr><td><t t-esc="value2[1]"/></td></tr>
|
|
<tr><td><t t-esc="value2[2]"/></td></tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
<tr><td><t t-esc="value[2]"/></td></tr>
|
|
</table>
|
|
</div>
|
|
</t>
|
|
"""
|
|
})
|
|
IrQweb = self.env['ir.qweb'].with_context(is_t_cache_disabled=True)
|
|
|
|
# use same cache id, display the same content
|
|
result = etree.fromstring(IrQweb._render(view1.id, {
|
|
'cache_id': (1, 0),
|
|
'cache_id2': (2, 0),
|
|
'value': [1, 2, 3],
|
|
'value2': [10, 20, 30]
|
|
}))
|
|
self.assertEqual(result, etree.fromstring("""
|
|
<div class="toto">
|
|
<table>
|
|
<tr><td>1</td></tr>
|
|
<tr>
|
|
<td>
|
|
<table>
|
|
<tr><td>10</td></tr>
|
|
<tr><td>20</td></tr>
|
|
<tr><td>30</td></tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
<tr><td>3</td></tr>
|
|
</table>
|
|
</div>
|
|
"""), 'First rendering')
|
|
|
|
result = etree.fromstring(self.env['ir.qweb']._render(view1.id, {
|
|
'cache_id': (1, 0),
|
|
'cache_id2': (2, 1),
|
|
'value': [41, 42, 43],
|
|
'value2': [51, 52, 53]
|
|
}))
|
|
self.assertEqual(result, etree.fromstring("""
|
|
<div class="toto">
|
|
<table>
|
|
<tr><td>41</td></tr>
|
|
<tr>
|
|
<td>
|
|
<table>
|
|
<tr><td>51</td></tr>
|
|
<tr><td>52</td></tr>
|
|
<tr><td>53</td></tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
<tr><td>43</td></tr>
|
|
</table>
|
|
</div>
|
|
"""), 'Next rendering cannot use cache (use_qweb_t_cache is False)')
|
|
|
|
result = etree.fromstring(self.env['ir.qweb']._render(view1.id, {
|
|
'cache_id': (1, 1),
|
|
'cache_id2': (2, 0),
|
|
'value': [31, 32, 33],
|
|
'value2': [51, 52, 53]
|
|
}))
|
|
self.assertEqual(result, etree.fromstring("""
|
|
<div class="toto">
|
|
<table>
|
|
<tr><td>31</td></tr>
|
|
<tr>
|
|
<td>
|
|
<table>
|
|
<tr><td>51</td></tr>
|
|
<tr><td>52</td></tr>
|
|
<tr><td>53</td></tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
<tr><td>33</td></tr>
|
|
</table>
|
|
</div>
|
|
"""), 'Third rendering cannot use cache (use_qweb_t_cache is False)')
|
|
|
|
def test_render_xml_nocache_use_the_root_values(self):
|
|
template_page = self.env['ir.ui.view'].create({
|
|
'name': "template_page",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="template_page">
|
|
<section t-cache="cache_id">
|
|
<t t-set="counter" t-value="counter + 100"/>
|
|
<article t-nocache=""><t t-out="counter"/></article>
|
|
<div>cache: <t t-out="counter"/></div>
|
|
</section>
|
|
</t>
|
|
"""
|
|
})
|
|
|
|
IrQweb = self.env['ir.qweb'].with_context(is_t_cache_disabled=False)
|
|
|
|
render = IrQweb._render(template_page.id, {
|
|
'cache_id': 1,
|
|
'counter': 1,
|
|
})
|
|
result = """
|
|
<section>
|
|
<article>1</article>
|
|
<div>cache: 101</div>
|
|
</section>
|
|
"""
|
|
self.assertEqual(etree.fromstring(render), etree.fromstring(result), 'rendering 1 (101 != 1: cached t-set should never be applied on root rendering)')
|
|
|
|
render = IrQweb._render(template_page.id, {
|
|
'cache_id': 1,
|
|
'counter': 2,
|
|
})
|
|
result = """
|
|
<section>
|
|
<article>2</article>
|
|
<div>cache: 101</div>
|
|
</section>
|
|
"""
|
|
self.assertEqual(etree.fromstring(render), etree.fromstring(result), 'rendering 2 (102 != 2: cached t-set should never be applied on root rendering)')
|
|
|
|
render = IrQweb._render(template_page.id, {
|
|
'cache_id': 3,
|
|
'counter': 3,
|
|
})
|
|
result = """
|
|
<section>
|
|
<article>3</article>
|
|
<div>cache: 103</div>
|
|
</section>
|
|
"""
|
|
self.assertEqual(etree.fromstring(render), etree.fromstring(result), 'rendering 3 (103 != 3: cached t-set should never be applied on root rendering)')
|
|
|
|
def test_render_xml_nocache_use_the_root_values_and_cached_values(self):
|
|
template_page = self.env['ir.ui.view'].create({
|
|
'name': "template_page",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="template_page">
|
|
<section t-cache="cache_id">
|
|
<t t-set="counter" t-value="counter + 100"/>
|
|
<article t-nocache="" t-nocache-counter="counter"><t t-out="counter"/></article>
|
|
<div>cache: <t t-out="counter"/></div>
|
|
</section>
|
|
</t>
|
|
"""
|
|
})
|
|
|
|
IrQweb = self.env['ir.qweb'].with_context(is_t_cache_disabled=False)
|
|
|
|
render = IrQweb._render(template_page.id, {
|
|
'cache_id': 1,
|
|
'counter': 1,
|
|
})
|
|
result = """
|
|
<section>
|
|
<article>101</article>
|
|
<div>cache: 101</div>
|
|
</section>
|
|
"""
|
|
self.assertEqual(etree.fromstring(render), etree.fromstring(result), 'rendering 1 (1 != 101: new cached values should be add to the root rendering)')
|
|
|
|
render = IrQweb._render(template_page.id, {
|
|
'cache_id': 1,
|
|
'counter': 2,
|
|
})
|
|
result = """
|
|
<section>
|
|
<article>101</article>
|
|
<div>cache: 101</div>
|
|
</section>
|
|
"""
|
|
self.assertEqual(etree.fromstring(render), etree.fromstring(result), 'rendering 2 (102 != 2: cached values should be used)')
|
|
|
|
render = IrQweb._render(template_page.id, {
|
|
'cache_id': 3,
|
|
'counter': 3,
|
|
})
|
|
result = """
|
|
<section>
|
|
<article>103</article>
|
|
<div>cache: 103</div>
|
|
</section>
|
|
"""
|
|
self.assertEqual(etree.fromstring(render), etree.fromstring(result), 'rendering 3 (3 != 103: new cached values should be add to the root rendering)')
|
|
|
|
def test_render_xml_nocache_use_the_root_values_and_cached_values_error(self):
|
|
template_page = self.env['ir.ui.view'].create({
|
|
'name': "template_page",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="template_page">
|
|
<section t-cache="cache_id">
|
|
<article t-nocache="" t-nocache-record="view_record"><t t-out="view_record"/></article>
|
|
</section>
|
|
</t>
|
|
"""
|
|
})
|
|
|
|
with self.assertRaisesRegex(QWebException, "The value type of 't-nocache-record' cannot be cached"):
|
|
self.env['ir.qweb'].with_context(is_t_cache_disabled=False)._render(template_page.id, {
|
|
'cache_id': 1,
|
|
'view_record': self.env['ir.ui.view'].search([], limit=1),
|
|
})
|
|
|
|
def test_render_xml_cache_with_t_set_out_of_cache(self):
|
|
template_page = self.env['ir.ui.view'].create({
|
|
'name': "template_page",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="template_page">
|
|
<root>
|
|
<t t-set="counter" t-value="counter + 100"/>
|
|
<section t-cache="cache_id">
|
|
<article t-nocache=""><t t-out="counter"/></article>
|
|
<div>cache: <t t-out="counter"/></div>
|
|
</section>
|
|
</root>
|
|
</t>
|
|
"""
|
|
})
|
|
|
|
IrQweb = self.env['ir.qweb'].with_context(is_t_cache_disabled=False)
|
|
|
|
render = IrQweb._render(template_page.id, {
|
|
'cache_id': 1,
|
|
'counter': 1,
|
|
})
|
|
result = """
|
|
<root>
|
|
<section>
|
|
<article>1</article>
|
|
<div>cache: 101</div>
|
|
</section>
|
|
</root>
|
|
"""
|
|
self.assertEqual(etree.fromstring(render), etree.fromstring(result), 'rendering 1 (1 != 101: cached t-set should is applied on first rendering)')
|
|
|
|
render = IrQweb._render(template_page.id, {
|
|
'cache_id': 1,
|
|
'counter': 2,
|
|
})
|
|
result = """
|
|
<root>
|
|
<section>
|
|
<article>2</article>
|
|
<div>cache: 101</div>
|
|
</section>
|
|
</root>
|
|
"""
|
|
self.assertEqual(etree.fromstring(render), etree.fromstring(result), 'rendering 2 (2 != 102: cached t-set should be applied the template part are rendered every time)')
|
|
|
|
render = IrQweb._render(template_page.id, {
|
|
'cache_id': 3,
|
|
'counter': 3,
|
|
})
|
|
result = """
|
|
<root>
|
|
<section>
|
|
<article>3</article>
|
|
<div>cache: 103</div>
|
|
</section>
|
|
</root>
|
|
"""
|
|
self.assertEqual(etree.fromstring(render), etree.fromstring(result), 'rendering 3 (3 != 103: cached t-set should applied because the new cache key is created)')
|
|
|
|
def test_render_xml_cache_with_t_set_in_cache(self):
|
|
template_page = self.env['ir.ui.view'].create({
|
|
'name': "template_page",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="template_page">
|
|
<root>
|
|
<section t-cache="cache_id">
|
|
<t t-set="counter" t-value="counter + 100"/>
|
|
<article t-nocache=""><t t-out="counter"/></article>
|
|
<div>cache: <t t-out="counter"/></div>
|
|
</section>
|
|
<div>out of cache: <t t-out="counter"/></div>
|
|
</root>
|
|
</t>
|
|
"""
|
|
})
|
|
IrQweb = self.env['ir.qweb'].with_context(is_t_cache_disabled=False)
|
|
|
|
render = IrQweb._render(template_page.id, {
|
|
'cache_id': 1,
|
|
'counter': 1,
|
|
})
|
|
result = """
|
|
<root>
|
|
<section>
|
|
<article>1</article>
|
|
<div>cache: 101</div>
|
|
</section>
|
|
<div>out of cache: 1</div>
|
|
</root>
|
|
"""
|
|
self.assertEqual(etree.fromstring(render), etree.fromstring(result), 'rendering 1')
|
|
|
|
render = IrQweb._render(template_page.id, {
|
|
'cache_id': 1,
|
|
'counter': 2,
|
|
})
|
|
result = """
|
|
<root>
|
|
<section>
|
|
<article>2</article>
|
|
<div>cache: 101</div>
|
|
</section>
|
|
<div>out of cache: 2</div>
|
|
</root>
|
|
"""
|
|
self.assertEqual(etree.fromstring(render), etree.fromstring(result), 'rendering 2')
|
|
|
|
render = IrQweb._render(template_page.id, {
|
|
'cache_id': 3,
|
|
'counter': 3,
|
|
})
|
|
result = """
|
|
<root>
|
|
<section>
|
|
<article>3</article>
|
|
<div>cache: 103</div>
|
|
</section>
|
|
<div>out of cache: 3</div>
|
|
</root>
|
|
"""
|
|
self.assertEqual(etree.fromstring(render), etree.fromstring(result), 'rendering 3')
|
|
|
|
def test_render_xml_cache_with_t_set_wrap_t_cache(self):
|
|
template_page = self.env['ir.ui.view'].create({
|
|
'name': "template_page",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="template_page">
|
|
<root t-cache="cache_1">
|
|
<t t-set="a">
|
|
<section t-cache="cache_2">
|
|
<t t-set="counter" t-value="counter + 100"/>
|
|
<article t-nocache="" class="no_cache"><t t-out="counter"/></article>
|
|
<div>cache: <t t-out="counter"/></div>
|
|
</section>
|
|
<footer t-nocache="" class="no_cache"><t t-out="counter * 10"/></footer>
|
|
</t>
|
|
<div>
|
|
<t t-out="a"/>
|
|
</div>
|
|
</root>
|
|
</t>
|
|
"""
|
|
})
|
|
IrQweb = self.env['ir.qweb'].with_context(is_t_cache_disabled=False)
|
|
|
|
render = IrQweb._render(template_page.id, {
|
|
'cache_1': 1,
|
|
'cache_2': 1,
|
|
'counter': 1,
|
|
})
|
|
result = """
|
|
<root>
|
|
<div>
|
|
<section>
|
|
<article class="no_cache">1</article>
|
|
<div>cache: 101</div>
|
|
</section>
|
|
<footer class="no_cache">10</footer>
|
|
</div>
|
|
</root>
|
|
"""
|
|
self.assertEqual(etree.fromstring(render), etree.fromstring(result), 'rendering 1')
|
|
|
|
render = IrQweb._render(template_page.id, {
|
|
'cache_1': 2,
|
|
'cache_2': 1,
|
|
'counter': 2,
|
|
})
|
|
result = """
|
|
<root>
|
|
<div>
|
|
<section>
|
|
<article class="no_cache">2</article>
|
|
<div>cache: 101</div>
|
|
</section>
|
|
<footer class="no_cache">20</footer>
|
|
</div>
|
|
</root>
|
|
"""
|
|
self.assertEqual(etree.fromstring(render), etree.fromstring(result), 'rendering 2')
|
|
|
|
render = IrQweb._render(template_page.id, {
|
|
'cache_1': 2,
|
|
'cache_2': 3,
|
|
'counter': 3,
|
|
})
|
|
result = """
|
|
<root>
|
|
<div>
|
|
<section>
|
|
<article class="no_cache">2</article>
|
|
<div>cache: 101</div>
|
|
</section>
|
|
<footer class="no_cache">20</footer>
|
|
</div>
|
|
</root>
|
|
"""
|
|
self.assertEqual(etree.fromstring(render), etree.fromstring(result), 'rendering 3')
|
|
|
|
render = IrQweb._render(template_page.id, {
|
|
'cache_1': 3,
|
|
'cache_2': 3,
|
|
'counter': 3,
|
|
})
|
|
result = """
|
|
<root>
|
|
<div>
|
|
<section>
|
|
<article class="no_cache">3</article>
|
|
<div>cache: 103</div>
|
|
</section>
|
|
<footer class="no_cache">30</footer>
|
|
</div>
|
|
</root>
|
|
"""
|
|
self.assertEqual(etree.fromstring(render), etree.fromstring(result), 'rendering 4')
|
|
|
|
def test_render_xml_t_set_wrap_t_cache(self):
|
|
template_page = self.env['ir.ui.view'].create({
|
|
'name': "template_page",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="template_page">
|
|
<root>
|
|
<t t-set="a">
|
|
<section t-cache="cache_id">
|
|
<t t-set="counter" t-value="counter + 100"/>
|
|
<article t-nocache="" class="no_cache"><t t-out="counter"/></article>
|
|
<div>cache: <t t-out="counter"/></div>
|
|
</section>
|
|
<footer t-nocache="" class="no_cache"><t t-out="counter * 10"/></footer>
|
|
</t>
|
|
<div>
|
|
<t t-out="a"/>
|
|
</div>
|
|
</root>
|
|
</t>
|
|
"""
|
|
})
|
|
IrQweb = self.env['ir.qweb'].with_context(is_t_cache_disabled=False)
|
|
|
|
render = IrQweb._render(template_page.id, {
|
|
'cache_id': 1,
|
|
'counter': 1,
|
|
})
|
|
result = """
|
|
<root>
|
|
<div>
|
|
<section>
|
|
<article class="no_cache">1</article>
|
|
<div>cache: 101</div>
|
|
</section>
|
|
<footer class="no_cache">10</footer>
|
|
</div>
|
|
</root>
|
|
"""
|
|
self.assertEqual(etree.fromstring(render), etree.fromstring(result), 'rendering 1')
|
|
|
|
render = IrQweb._render(template_page.id, {
|
|
'cache_id': 1,
|
|
'counter': 2,
|
|
})
|
|
result = """
|
|
<root>
|
|
<div>
|
|
<section>
|
|
<article class="no_cache">2</article>
|
|
<div>cache: 101</div>
|
|
</section>
|
|
<footer class="no_cache">20</footer>
|
|
</div>
|
|
</root>
|
|
"""
|
|
self.assertEqual(etree.fromstring(render), etree.fromstring(result), 'rendering 2')
|
|
|
|
render = IrQweb._render(template_page.id, {
|
|
'cache_id': 3,
|
|
'counter': 3,
|
|
})
|
|
result = """
|
|
<root>
|
|
<div>
|
|
<section>
|
|
<article class="no_cache">3</article>
|
|
<div>cache: 103</div>
|
|
</section>
|
|
<footer class="no_cache">30</footer>
|
|
</div>
|
|
</root>
|
|
"""
|
|
self.assertEqual(etree.fromstring(render), etree.fromstring(result), 'rendering 3')
|
|
|
|
def test_render_xml_nocache_in_cache_in_cache(self):
|
|
template_page = self.env['ir.ui.view'].create({
|
|
'name': "template_page",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="template_page">
|
|
<root>
|
|
<section t-cache="key1">
|
|
<span t-out="val"/>
|
|
<article t-cache="key2">
|
|
<span t-nocache="" t-out="val"/>
|
|
</article>
|
|
</section>
|
|
</root>
|
|
</t>
|
|
"""
|
|
})
|
|
|
|
IrQweb = self.env['ir.qweb'].with_context(is_t_cache_disabled=False)
|
|
|
|
render = IrQweb._render(template_page.id, {
|
|
'key1': (1,),
|
|
'key2': (1,),
|
|
'val': 1,
|
|
})
|
|
result = """
|
|
<root>
|
|
<section>
|
|
<span>1</span>
|
|
<article>
|
|
<span>1</span>
|
|
</article>
|
|
</section>
|
|
</root>
|
|
"""
|
|
self.assertEqual(etree.fromstring(render), etree.fromstring(result), 'rendering 1')
|
|
|
|
render = IrQweb._render(template_page.id, {
|
|
'key1': (1,),
|
|
'key2': (1,),
|
|
'val': 2,
|
|
})
|
|
result = """
|
|
<root>
|
|
<section>
|
|
<span>1</span>
|
|
<article>
|
|
<span>2</span>
|
|
</article>
|
|
</section>
|
|
</root>
|
|
"""
|
|
self.assertEqual(etree.fromstring(render), etree.fromstring(result), 'rendering 2')
|
|
|
|
render = IrQweb._render(template_page.id, {
|
|
'key1': (1,),
|
|
'key2': (2,),
|
|
'val': 3,
|
|
})
|
|
result = """
|
|
<root>
|
|
<section>
|
|
<span>1</span>
|
|
<article>
|
|
<span>3</span>
|
|
</article>
|
|
</section>
|
|
</root>
|
|
"""
|
|
self.assertEqual(etree.fromstring(render), etree.fromstring(result), 'rendering 3')
|
|
|
|
def test_render_xml_conditional_cache(self):
|
|
view1 = self.env['ir.ui.view'].create({
|
|
'name': "dummy",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="base.dummy">
|
|
<div t-cache="cache_id if condition else None" class="toto">
|
|
<table>
|
|
<tr><td><span t-esc="value[0]"/></td></tr>
|
|
<tr><td><span t-esc="value[1]"/></td></tr>
|
|
<tr><td><span t-esc="value[2]"/></td></tr>
|
|
</table>
|
|
</div>
|
|
</t>
|
|
"""
|
|
})
|
|
expected_result = etree.fromstring("""
|
|
<div class="toto">
|
|
<table>
|
|
<tr><td><span>1</span></td></tr>
|
|
<tr><td><span>2</span></td></tr>
|
|
<tr><td><span>3</span></td></tr>
|
|
</table>
|
|
</div>
|
|
""")
|
|
|
|
IrQweb = self.env['ir.qweb'].with_context(is_t_cache_disabled=False)
|
|
|
|
result = etree.fromstring(IrQweb._render(view1.id, {'cache_id': 1, 'condition': True, 'value': [1, 2, 3]}))
|
|
self.assertEqual(result, expected_result, 'First rendering (add in cache)')
|
|
|
|
result = etree.fromstring(IrQweb._render(view1.id, {'cache_id': 1, 'condition': True, 'value': [10, 20, 30]}))
|
|
self.assertEqual(result, expected_result, 'Next rendering use cache')
|
|
|
|
|
|
expected_result = etree.fromstring("""
|
|
<div class="toto">
|
|
<table>
|
|
<tr><td><span>10</span></td></tr>
|
|
<tr><td><span>20</span></td></tr>
|
|
<tr><td><span>30</span></td></tr>
|
|
</table>
|
|
</div>
|
|
""")
|
|
result = etree.fromstring(IrQweb._render(view1.id, {'cache_id': 1, 'value': [10, 20, 30]}))
|
|
self.assertEqual(result, expected_result, 'Next rendering use cache')
|
|
|
|
def test_render_xml_cache_and_inherit_view(self):
|
|
view1 = self.env['ir.ui.view'].create({
|
|
'name': "dummy",
|
|
'type': 'qweb',
|
|
'arch': """
|
|
<t t-name="base.dummy">
|
|
<div t-cache="True" class="toto">
|
|
<table>
|
|
<tr><td><span t-esc="value[0]"/></td></tr>
|
|
<tr><td><span t-esc="value[1]"/></td></tr>
|
|
<tr><td><span t-esc="value[2]"/></td></tr>
|
|
</table>
|
|
</div>
|
|
</t>
|
|
"""
|
|
})
|
|
# t-cache value can be an interable then we can add value as a tuple (without parenthesis)
|
|
view2 = self.env['ir.ui.view'].create({
|
|
'name': 'Child View',
|
|
'mode': 'extension',
|
|
'inherit_id': view1.id,
|
|
'arch': '''
|
|
<xpath expr="//div[@t-cache]" position="attributes">
|
|
<attribute name="t-cache" add="company,value[0]" remove="True," separator=","/>
|
|
</xpath>
|
|
''',
|
|
})
|
|
|
|
IrQweb = self.env['ir.qweb'].with_context(use_qweb_t_cache=True)
|
|
|
|
expected_result = etree.fromstring("""
|
|
<div class="toto">
|
|
<table>
|
|
<tr><td><span>1</span></td></tr>
|
|
<tr><td><span>2</span></td></tr>
|
|
<tr><td><span>3</span></td></tr>
|
|
</table>
|
|
</div>
|
|
""")
|
|
result = etree.fromstring(IrQweb._render(view2.id, {'value': [1, 2, 3]}))
|
|
self.assertEqual(result, expected_result, 'First rendering create cache from company and the value 1')
|
|
|
|
expected_result = etree.fromstring("""
|
|
<div class="toto">
|
|
<table>
|
|
<tr><td><span>10</span></td></tr>
|
|
<tr><td><span>20</span></td></tr>
|
|
<tr><td><span>30</span></td></tr>
|
|
</table>
|
|
</div>
|
|
""")
|
|
result = etree.fromstring(IrQweb._render(view2.id, {'value': [10, 20, 30]}))
|
|
self.assertEqual(result, expected_result, 'Next rendering create cache from company and the value 10')
|
|
|
|
def test_render_nodb(self):
|
|
""" Render an html page without db ans wihtout registry
|
|
"""
|
|
expected = dedent("""
|
|
<html>
|
|
<head>
|
|
<title>Odoo</title>
|
|
</head>
|
|
<body>
|
|
<section class="toto">
|
|
<div>3</div>
|
|
</section>
|
|
</body>
|
|
</html>
|
|
""").strip()
|
|
|
|
templates = {
|
|
'html': html.document_fromstring("""
|
|
<html t-name="html">
|
|
<head>
|
|
<title>Odoo</title>
|
|
</head>
|
|
<body>
|
|
<section class="toto">
|
|
<t t-call="content"/>
|
|
</section>
|
|
</body>
|
|
</html>
|
|
"""),
|
|
'content': html.fragment_fromstring("""
|
|
<t t-name="content">
|
|
<div><t t-out="val"/></div>
|
|
</t>
|
|
""")
|
|
}
|
|
def load(template_name):
|
|
return (templates[template_name], template_name)
|
|
rendering = render('html', {'val': 3}, load).strip()
|
|
|
|
self.assertEqual(html.document_fromstring(rendering), html.document_fromstring(expected))
|