diff options
| author | Lucas Perais (lpe) <lpe@odoo.com> | 2019-06-04 13:29:58 +0000 |
|---|---|---|
| committer | Vincent Schippefilt <vsc@odoo.com> | 2019-07-24 12:21:59 +0000 |
| commit | fc5878ecc668ee83cd1a374474f0b21bf6a51940 (patch) | |
| tree | b08e064a72f84d4fb14a0b5560ae1fd142c05563 /addons/web/tests | |
| parent | 8be4567ae8f6b0388206c091590278ef11a2df21 (diff) | |
[IMP] base, web: static xml templates support serverside inheritance
QWeb templates that show up in the 'qweb' key of a module's manifest
now support server side inheritance and xpath evaluation
QWeb templates that show up in the xmlDependencies of a JS widget are not
impacted at all by theses changes, as they are served through the
Werkzeug sharedMiddleware
A similar syntax than ir.ui.view has been implemented in the QWeb templates
- each template must have a root node, whatever tag works
- the root node of a template must have a t-name containing the name of the template
The name -- without the module's name -- may contain dots pretty much anywhere
Though what is recommended is only underscores in template names
- if a template is to inherit from a parent, the root node has a t-inherit directive
containing either the full name of the template it inherits from which is module_name.template_name
or the name of the template, no module name necessary, if the parent template is in the same module
- there are 2 modes of inheriting
primary: copy the behavior of the parent into the template
extension: modifies the parent in place
Task: 1999528
closes odoo/odoo#33892
Signed-off-by: VincentSchippefilt <VincentSchippefilt@users.noreply.github.com>
Co-authored-by: Julien Mougenot <jum@odoo.com>
Co-authored-by: Lucas Perais <lpe@odoo.com>
Diffstat (limited to 'addons/web/tests')
| -rw-r--r-- | addons/web/tests/test_serving_base.py | 417 |
1 files changed, 416 insertions, 1 deletions
diff --git a/addons/web/tests/test_serving_base.py b/addons/web/tests/test_serving_base.py index e1c6c7d5ab0..bda394c5152 100644 --- a/addons/web/tests/test_serving_base.py +++ b/addons/web/tests/test_serving_base.py @@ -2,9 +2,16 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import random +import re +import unittest +from unittest.mock import patch +import textwrap +from datetime import datetime, timedelta +from lxml import etree -from odoo.tests.common import BaseCase +from odoo.tests.common import BaseCase, tagged from odoo.tools import topological_sort +from odoo.addons.web.controllers.main import HomeStaticTemplateHelpers def sample(population): @@ -36,3 +43,411 @@ class TestModulesLoading(BaseCase): module, sorted_modules.index(module), deps, seen )) seen.add(module) + + +@tagged('standard', 'at_install', 'static_templates') +class TestStaticInheritance(unittest.TestCase): + + def setUp(self): + super(TestStaticInheritance, self).setUp() + # output is "manifest_glob" return + self.modules = [ + ('module_1_file_1', None, 'module_1'), + ('module_2_file_1', None, 'module_2'), + ] + + self.template_files = { + 'module_1_file_1': b""" + <templates id="template" xml:space="preserve"> + <form t-name="template_1_1" random-attr="gloria"> + <div>At first I was afraid</div> + <div>Kept thinking I could never live without you by my side</div> + </form> + <t t-name="template_1_2"> + <div>And I grew strong</div> + </t> + </templates> + """, + + 'module_2_file_1': b""" + <templates id="template" xml:space="preserve"> + <form t-name="template_2_1" t-inherit="module_1.template_1_1" t-inherit-mode="primary"> + <xpath expr="//div[1]" position="after"> + <div>I was petrified</div> + </xpath> + <xpath expr="//div[2]" position="after"> + <div>But then I spent so many nights thinking how you did me wrong</div> + </xpath> + </form> + <div t-name="template_2_2"> + <div>And I learned how to get along</div> + </div> + <form t-inherit="module_1.template_1_2" t-inherit-mode="extension"> + <xpath expr="//div[1]" position="after"> + <div>And I learned how to get along</div> + </xpath> + </form> + </templates> + """, + } + self._set_patchers() + self._toggle_patchers('start') + self._reg_replace_ws = r"\s|\t" + + def tearDown(self): + super(TestStaticInheritance, self).tearDown() + self._toggle_patchers('stop') + + # Custom Assert + def assertXMLEqual(self, output, expected): + self.assertTrue(output) + self.assertTrue(expected) + output = textwrap.dedent(output.decode('UTF-8')).strip() + output = re.sub(self._reg_replace_ws, '', output) + + expected = textwrap.dedent(expected.decode('UTF-8')).strip() + expected = re.sub(self._reg_replace_ws, '', expected) + self.assertEqual(output, expected) + + # Private methods + def _get_module_names(self): + return ','.join([glob[2] for glob in self.modules]) + + def _set_patchers(self): + def _patched_for_manifest_glob(*args, **kwargs): + # Ordered by module + return self.modules + + def _patch_for_read_addon_file(*args, **kwargs): + return self.template_files[args[1]] + + self.patchers = [ + patch.object(HomeStaticTemplateHelpers, '_manifest_glob', _patched_for_manifest_glob), + patch.object(HomeStaticTemplateHelpers, '_read_addon_file', _patch_for_read_addon_file), + ] + + def _toggle_patchers(self, mode): + self.assertTrue(mode in ('start', 'stop')) + for p in self.patchers: + getattr(p, mode)() + + # Actual test cases + def test_static_inheritance_01(self): + contents = HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True) + expected = b""" + <templates> + <form t-name="template_1_1" random-attr="gloria"> + <div>At first I was afraid</div> + <div>Kept thinking I could never live without you by my side</div> + </form> + <t t-name="template_1_2"> + <div>And I grew strong</div> + <!-- Modified by anonymous_template_2 from module_2 --> + <div>And I learned how to get along</div> + </t> + <form t-name="template_2_1" random-attr="gloria" t-inherit="module_1.template_1_1"> + <div>At first I was afraid</div> + <div>I was petrified</div> + <div>But then I spent so many nights thinking how you did me wrong</div> + <div>Kept thinking I could never live without you by my side</div> + </form> + <div t-name="template_2_2"> + <div>And I learned how to get along</div> + </div> + </templates> + """ + + self.assertXMLEqual(contents, expected) + + def test_static_inheritance_in_same_module(self): + self.modules = [ + ('module_1_file_1', None, 'module_1'), + ('module_1_file_2', None, 'module_1'), + ] + + self.template_files = { + 'module_1_file_1': b''' + <templates id="template" xml:space="preserve"> + <form t-name="template_1_1"> + <div>At first I was afraid</div> + <div>Kept thinking I could never live without you by my side</div> + </form> + </templates> + ''', + + 'module_1_file_2': b''' + <templates id="template" xml:space="preserve"> + <form t-name="template_1_2" t-inherit="template_1_1" t-inherit-mode="primary"> + <xpath expr="//div[1]" position="after"> + <div>I was petrified</div> + </xpath> + </form> + </templates> + ''' + } + contents = HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True) + expected = b""" + <templates> + <form t-name="template_1_1"> + <div>At first I was afraid</div> + <div>Kept thinking I could never live without you by my side</div> + </form> + <form t-name="template_1_2" t-inherit="template_1_1"> + <div>At first I was afraid</div> + <div>I was petrified</div> + <div>Kept thinking I could never live without you by my side</div> + </form> + </templates> + """ + + self.assertXMLEqual(contents, expected) + + def test_static_inheritance_in_same_file(self): + self.modules = [ + ('module_1_file_1', None, 'module_1'), + ] + + self.template_files = { + 'module_1_file_1': b''' + <templates id="template" xml:space="preserve"> + <form t-name="template_1_1"> + <div>At first I was afraid</div> + <div>Kept thinking I could never live without you by my side</div> + </form> + <form t-name="template_1_2" t-inherit="template_1_1" t-inherit-mode="primary"> + <xpath expr="//div[1]" position="after"> + <div>I was petrified</div> + </xpath> + </form> + </templates> + ''', + } + contents = HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True) + expected = b""" + <templates> + <form t-name="template_1_1"> + <div>At first I was afraid</div> + <div>Kept thinking I could never live without you by my side</div> + </form> + <form t-name="template_1_2" t-inherit="template_1_1"> + <div>At first I was afraid</div> + <div>I was petrified</div> + <div>Kept thinking I could never live without you by my side</div> + </form> + </templates> + """ + + self.assertXMLEqual(contents, expected) + + def test_static_inherit_extended_template(self): + self.modules = [ + ('module_1_file_1', None, 'module_1'), + ] + self.template_files = { + 'module_1_file_1': b''' + <templates id="template" xml:space="preserve"> + <form t-name="template_1_1"> + <div>At first I was afraid</div> + <div>Kept thinking I could never live without you by my side</div> + </form> + <form t-name="template_1_2" t-inherit="template_1_1" t-inherit-mode="extension"> + <xpath expr="//div[1]" position="after"> + <div>I was petrified</div> + </xpath> + </form> + <form t-name="template_1_3" t-inherit="template_1_1" t-inherit-mode="primary"> + <xpath expr="//div[3]" position="after"> + <div>But then I spent so many nights thinking how you did me wrong</div> + </xpath> + </form> + </templates> + ''', + } + contents = HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True) + expected = b""" + <templates> + <form t-name="template_1_1"> + <div>At first I was afraid</div> + <!-- Modified by template_1_2 from module_1 --> + <div>I was petrified</div> + <div>Kept thinking I could never live without you by my side</div> + </form> + <form t-name="template_1_3" t-inherit="template_1_1"> + <div>At first I was afraid</div> + <div>I was petrified</div> + <div>Kept thinking I could never live without you by my side</div> + <div>But then I spent so many nights thinking how you did me wrong</div> + </form> + </templates> + """ + + self.assertXMLEqual(contents, expected) + + def test_sibling_extension(self): + self.modules = [ + ('module_1_file_1', None, 'module_1'), + ('module_2_file_1', None, 'module_2'), + ('module_3_file_1', None, 'module_3'), + ] + self.template_files = { + 'module_1_file_1': b''' + <templates id="template" xml:space="preserve"> + <form t-name="template_1_1"> + <div>I am a man of constant sorrow</div> + <div>I've seen trouble all my days</div> + </form> + </templates> + ''', + + 'module_2_file_1': b''' + <templates id="template" xml:space="preserve"> + <form t-name="template_2_1" t-inherit="module_1.template_1_1" t-inherit-mode="extension"> + <xpath expr="//div[1]" position="after"> + <div>In constant sorrow all through his days</div> + </xpath> + </form> + </templates> + ''', + + 'module_3_file_1': b''' + <templates id="template" xml:space="preserve"> + <form t-name="template_3_1" t-inherit="module_1.template_1_1" t-inherit-mode="extension"> + <xpath expr="//div[2]" position="after"> + <div>Oh Brother !</div> + </xpath> + </form> + </templates> + ''' + } + + contents = HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True) + expected = b""" + <templates> + <form t-name="template_1_1"> + <div>I am a man of constant sorrow</div> + <!-- Modified by template_2_1 from module_2 --> + <div>In constant sorrow all through his days</div> + <!-- Modified by template_3_1 from module_3 --> + <div>Oh Brother !</div> + <div>I've seen trouble all my days</div> + </form> + </templates> + """ + + self.assertXMLEqual(contents, expected) + + def test_static_misordered_modules(self): + self.modules.reverse() + with self.assertRaises(ValueError) as ve: + HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True) + + self.assertEqual( + str(ve.exception), + 'Module module_1 not loaded or inexistent, or templates of addon being loaded (module_2) are misordered' + ) + + def test_static_misordered_templates(self): + self.template_files['module_2_file_1'] = b""" + <templates id="template" xml:space="preserve"> + <form t-name="template_2_1" t-inherit="module_2.template_2_2" t-inherit-mode="primary"> + <xpath expr="//div[1]" position="after"> + <div>I was petrified</div> + </xpath> + </form> + <div t-name="template_2_2"> + <div>And I learned how to get along</div> + </div> + </templates> + """ + with self.assertRaises(ValueError) as ve: + HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True) + + self.assertEqual( + str(ve.exception), + 'No template found to inherit from. Module module_2 and template name template_2_2' + ) + + def _sick_script(self, nMod, nFilePerMod, nTemplatePerFile, stepInheritInModule=2, stepInheritPreviousModule=3): + """ + Make a sick amount of templates to test perf + nMod modules + each module: has nFilesPerModule files, each of which contains nTemplatePerFile templates + """ + self.modules = [] + self.template_files = {} + number_templates = 0 + for m in range(nMod): + for f in range(nFilePerMod): + mname = 'mod_%s' % m + fname = 'mod_%s_file_%s' % (m, f) + self.modules.append((fname, None, mname)) + + _file = '<templates id="template" xml:space="preserve">' + + for t in range(nTemplatePerFile): + _template = '' + if t % stepInheritInModule or t % stepInheritPreviousModule or t == 0: + _template += """ + <div t-name="template_%(t_number)s_mod_%(m_number)s"> + <div>Parent</div> + </div> + """ + + elif not t % stepInheritInModule and t >= 1: + _template += """ + <div t-name="template_%(t_number)s_mod_%(m_number)s" + t-inherit="template_%(t_inherit)s_mod_%(m_number)s" + t-inherit-mode="primary"> + <xpath expr="//div[1]" position="before"> + <div>Sick XPath</div> + </xpath> + </div> + """ + + elif not t % stepInheritPreviousModule and m >= 1: + _template += """ + <div t-name="template_%(t_number)s_mod_%(m_number)s" + t-inherit="mod_%(m_module_inherit)s.template_%(t_module_inherit)s_mod_%(m_module_inherit)s" + t-inherit-mode="primary"> + <xpath expr="//div[1]" position="inside"> + <div>Mental XPath</div> + </xpath> + </div> + """ + if _template: + number_templates += 1 + + _template_number = 1000 * f + t + _file += _template % { + 't_number': _template_number, + 'm_number': m, + 't_inherit': _template_number - 1, + 't_module_inherit': _template_number, + 'm_module_inherit': m - 1, + } + _file += '</templates>' + + self.template_files[fname] = _file.encode() + self.assertEqual(number_templates, nMod * nFilePerMod * nTemplatePerFile) + + def test_performance_2500(self): + nMod, nFilePerMod, nTemplatePerFile = 50, 5, 10 + self._sick_script(nMod, nFilePerMod, nTemplatePerFile) + + before = datetime.now() + contents = HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True) + after = datetime.now() + self.assertLessEqual(after - before, timedelta(milliseconds=130)) + + whole_tree = etree.fromstring(contents) + self.assertEqual(len(whole_tree), nMod * nFilePerMod * nTemplatePerFile) + + def test_performance_25000(self): + nMod, nFilePerMod, nTemplatePerFile = 50, 5, 100 + self._sick_script(nMod, nFilePerMod, nTemplatePerFile) + + before = datetime.now() + HomeStaticTemplateHelpers.get_qweb_templates(addons=self._get_module_names(), debug=True) + after = datetime.now() + + self.assertLessEqual(after - before, timedelta(milliseconds=1000)) |
