Files
odoo_source/addons/web/tests/test_assets_xml.py
T
Xavier-Do 6d5d234f15 [IMP] base: better ormcache management
1. move cache to _get_asset_paths

The `_get_asset_content` cache has many cache key that are related to a
posprocessing of the `_get_asset_paths` result, the heavy part of this
method. Moving the cache to _get_asset_content will have the benefit
to create less duplicates entries in the ormcache as well as less cache
miss.

To simplify even further, the css and js parameters are removed since
they only filter the output of get_paths, the heavy part of globing the
file will be done before that. Anyway, they are both true when called
from _get_asset_content, and the only other call, in
`_get_related_bundle` don't really need to filter them since it is not
a critical part regarding performance, and the funtional result will
stay the same.

The initial orm cache key was using `_get_template_cache_keys`, a little
overkill and possibly creating duplicates entries again. The only
context key needed is website_id for `_get_related_assets`.

Note that it is not really enough, the orm cache key should actually
contain `request.session.get('force_website_id')` as well has
`request.httprequest.host`. This will be addressed latter since a nicer
solution would be to have website_id as a unique parameter computed
earlier.

2. better _get_asset_paths cache key

The orm cache key was simplified in previous point but there is still
one concern, the website_id depends on more parameters than that:
- request.session.get('force_website_id')
- request.httprequest.host
- existing websites

The idea here is to call `get_current_website` instead of using all
parameters that could define the webiste.

In the same spirit of `_get_template_cache_keys` `_assets_path_params`
can be overriden to give extra params that are usefull to list assets
path. Those params are computed before entering the method
`_get_asset_paths`. This may latter put at a higher level latter, in
get_asset_node, to simplify the _generate_asset_nodes_cache key.

3. better assets_node caches key

The main purpose of this part is to improve ormcache containing assets
nodes. The ormcache key contains
- to much context key
- missing session/host/env info
- unwanted boolean options.
- keys leading to the same cache value

The main goal being to reduce the size of the cache keys, decrease the
number of cache entries and improve the cache hit.
This will also make the behaviour more coherent and hopefully less bug
prone because of mismatch in parameters.

The main reason of the orm cache is the slowness of the validation of
the assets. This includes:
- listing files (dedicated orm cache)
- computing version

The cache key was depending on
- `debug`
The only relevant value for debug is "contains assets"
We dont need to differ between debug='', debug='1', debug='test',
and 'debug=assets', 'debug=tests,assets', ...
- `defer_load`, `lazy_load`, `media`
Those values are only useful to generate html node, a leightweight
operations that does not really needs to be in cache. `media` was also
used in the generation but it looks useless if we have the media on the
node. THIS NEEDS TO BE VALIDATED but in any case, since media is not
used to generate the url, it doesn't make sence to use it in the
generation.
The main idea to remove them from the ormcache key is simply to generate
the nodes outide the ormcached values.
-`async_load`
This one is similar to `defer_load` and `lazy_load` but it looks like
it wasn't used anymore. This was simply removed
- context.get('lang')
The only information needed is the direction, rtl or ltr. This means
en and fr languages, despite sharing the same css assets, will duplicate
the ormcache entries.
-`_get_template_cache_keys`
Only the lang and webiste where really relevant in this flow. Other
keys are actually useless in this flow.

Some information used in the generation where not in the orm cache key
- `self.env.user.lang` if there is no lang in the context
- `request.session.get('force_website_id')`
- `request.httprequest.host`
- ...

The proposed solution is to:
- extract any informùation needed from thecontext, request, environment
before entering the ormcache, reduce it to the minimal possible set of
values needed
```
    rtl = self.env['res.lang']._lang_get_direction(self.env.context.get('lang') or self.env.user.lang) == 'rtl'
    assets_params = self.env['ir.asset']._get_assets_params()  # website_id
    debug_assets = debug and 'assets' in debug
```

and remove a leightweight part of the logic

```
    def _get_asset_nodes(self, bundle, css=True, js=True, debug=False, defer_load=False, lazy_load=False, media=None):
        links = self._get_asset_links(bundle, css=css, js=js, debug=debug)
        return self._links_to_nodes(links, defer_load=defer_load, lazy_load=lazy_load, media=media)
```

Where _get_asset_links is the cached part, and _links_to_nodes is the
lightweight part generating the nodes based on the `defer_load`, ....

Additionnal notes:
- data-asset-version and data-asset-bundle are removed from the node
since they don't seem to be used anymore since 65d70acdbf
- async_load is removed since there is no occurence of this in the code.
- a small hack is still needed to pass javascript content instead of
links, this is only to manage css compile error and will hopefully be
removed in the future.
- a context key is still in use to generate the bundle, the
`commit_assetsbundle` but it has no impact on content and will hopefully
be removed in the future.

4. Add test for ormcache hit/miss

In this context, hit/miss is about having the same cache key for the
same result. This test demonstrates the current state, were entries are
create in the ormcache only if the key is really different and will lead
to a different result.

5. remove cache invalidation

This cache invalidation is quite agressive since everytime an
assetbundle is updated, all workers will clear their cache.

The concerned cache by this clear_cache is `_generate_asset_nodes_cache`
throug `_get_asset_nodes`.

The cache is ignored, both in dev=xml and debug=assets.

This clear cache was made conditionnal in 553ea82f81 but this does
not solve an issue we can have in production.

Lets imagine a clean solution
- all sources are updated
- all workers are restarted.

The orm caches are all empty, but since the sources
changed, all bundles will be recomputed. This means that every bundle
updated in database with save_attachement will invalidate the cache of
all workers. Rendering a pdf report of any kind using a specific bundle
will invalidate all cache. Starting a debug=assets for the first time
will invalidate all cache, even if the cache is not used in this case.

But for a regenerated bundle we would expect the ormcache to be:
- empty (did not generate the same bundle yet)
- have the same value (concurrent generation of the same bundle)

Having a different value would mean that the bundle was generated with
another version of the sources. In this case it is maybe even better not
to invalidate the cache since it could lead to an invalidation war
between two workers.

The only case where invalidating this cache is useful is when a bundle
changes, Usually if an ir_asset is created, modified, ...

There is still another rare but possible possibility to have a 404 if
the transaction is rollbacked after populating the assets node cache.
In this case, we only need to clear the cache locally in case of
rollback.

Part-of: odoo/odoo#121376
2023-06-10 11:14:11 +02:00

928 lines
40 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
import random
import re
from unittest.mock import patch
import textwrap
from datetime import datetime
from lxml import etree
import logging
import odoo
from odoo.tests.common import BaseCase, HttpCase, tagged
from odoo.tools import topological_sort
from odoo.addons.base.models.assetsbundle import AssetsBundle, WebAsset
_logger = logging.getLogger(__name__)
class TestStaticInheritanceCommon(odoo.tests.TransactionCase):
def setUp(self):
super().setUp()
self.template_files = {
'/module_1/static/xml/file_1.xml': """
<templates id="template" xml:space="preserve">
<form t-name="template_1_1" random-attr="gloria">
<span>Ho !</span>
<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/static/xml/file_1.xml': """
<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="//span" position="attributes">
<attribute name="type">Scary screams</attribute>
</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._patch = patch.object(WebAsset, '_fetch_content', lambda asset: self.template_files[asset.url])
self.startPatcher(self._patch)
def renderBundle(self, debug=False):
files = []
for url in self.template_files:
files.append({
'url': url,
'filename': url,
'content': None,
'media': None,
})
asset = AssetsBundle('web.test_bundle', files, env=self.env, css=False, js=True, debug_assets=debug)
content = asset.xml(show_inherit_info=debug)
return f'<templates xml:space="preserve">\n{content}\n</templates>'
# Custom Assert
def assertXMLEqual(self, output, expected):
self.assertTrue(output)
self.assertTrue(expected)
self.assertEqual(etree.fromstring(output), etree.fromstring(expected))
@tagged('assets_bundle', 'static_templates')
class TestStaticInheritance(TestStaticInheritanceCommon):
# Actual test cases
def test_static_with_debug_mode(self):
expected = """
<templates xml:space="preserve">
<!-- Filepath: /module_1/static/xml/file_1.xml -->
<form t-name="template_1_1" random-attr="gloria">
<span>Ho !</span>
<div>At first I was afraid</div>
<div>Kept thinking I could never live without you by my side</div>
</form>
<!-- Filepath: /module_1/static/xml/file_1.xml => /module_2/static/xml/file_1.xml -->
<t t-name="template_1_2">
<div>And I grew strong</div>
<!-- Filepath: /module_2/static/xml/file_1.xml ; position="after" ; {'expr': '//div[1]'} --><div>And I learned how to get along</div>
</t>
<!-- Filepath: /module_1/static/xml/file_1.xml => /module_2/static/xml/file_1.xml -->
<form t-name="template_2_1" random-attr="gloria"><!-- Filepath: /module_2/static/xml/file_1.xml ; position="attributes" ; {'expr': '//span'} -->
<span type="Scary screams">Ho !</span>
<div>At first I was afraid</div>
<!-- Filepath: /module_2/static/xml/file_1.xml ; position="after" ; {'expr': '//div[1]'} --><div>I was petrified</div>
<!-- Filepath: /module_2/static/xml/file_1.xml ; position="after" ; {'expr': '//div[2]'} --><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>
<!-- Filepath: /module_2/static/xml/file_1.xml -->
<div t-name="template_2_2">
<div>And I learned how to get along</div>
</div>
</templates>
"""
self.assertXMLEqual(self.renderBundle(debug=True), expected)
def test_static_inheritance_01(self):
expected = """
<templates xml:space="preserve">
<form t-name="template_1_1" random-attr="gloria">
<span>Ho !</span>
<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>
<div>And I learned how to get along</div>
</t>
<form t-name="template_2_1" random-attr="gloria">
<span type="Scary screams">Ho !</span>
<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(self.renderBundle(debug=False), expected)
def test_static_inheritance_02(self):
self.template_files = {
'/module_1/static/xml/file_1.xml': """
<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>
<form t-name="template_1_2" t-inherit="template_1_1" added="true">
<xpath expr="//div[1]" position="after">
<div>I was petrified</div>
</xpath>
</form>
</templates>
""",
}
expected = """
<templates 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>
<form t-name="template_1_2" random-attr="gloria" added="true">
<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(self.renderBundle(debug=False), expected)
def test_static_inheritance_03(self):
self.template_files = {
'/module_1/static/xml/file_1.xml': '''
<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" added="true">
<xpath expr="//div[1]" position="after">
<div>I was petrified</div>
</xpath>
</form>
<form t-name="template_1_3" t-inherit="template_1_2" added="false" other="here">
<xpath expr="//div[2]" position="replace"/>
</form>
</templates>
'''
}
expected = """
<templates 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" added="true">
<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>
<form t-name="template_1_3" added="false" other="here">
<div>At first I was afraid</div>
<div>Kept thinking I could never live without you by my side</div>
</form>
</templates>
"""
self.assertXMLEqual(self.renderBundle(debug=False), expected)
def test_static_inheritance_in_same_module(self):
self.template_files = {
'/module_1/static/xml/file_1.xml': '''
<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/static/xml/file_2.xml': '''
<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>
'''
}
expected = """
<templates 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">
<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(self.renderBundle(debug=False), expected)
def test_static_inheritance_in_same_file(self):
self.template_files = {
'/module_1/static/xml/file_1.xml': '''
<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>
''',
}
expected = """
<templates 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">
<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(self.renderBundle(debug=False), expected)
def test_static_inherit_extended_template(self):
self.template_files = {
'/module_1/static/xml/file_1.xml': '''
<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>
''',
}
expected = """
<templates xml:space="preserve">
<form t-name="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>
<form t-name="template_1_3">
<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(self.renderBundle(debug=False), expected)
def test_sibling_extension(self):
self.template_files = {
'/module_1/static/xml/file_1.xml': '''
<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/static/xml/file_1.xml': '''
<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/static/xml/file_1.xml': '''
<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>
'''
}
expected = """
<templates xml:space="preserve">
<form t-name="template_1_1">
<div>I am a man of constant sorrow</div>
<div>In constant sorrow all through his days</div>
<div>Oh Brother !</div>
<div>I've seen trouble all my days</div>
</form>
</templates>
"""
self.assertXMLEqual(self.renderBundle(debug=False), expected)
def test_static_misordered_modules(self):
files = self.template_files
self.template_files = {
'/module_2/static/xml/file_1.xml': files['/module_2/static/xml/file_1.xml'],
'/module_1/static/xml/file_1.xml': files['/module_1/static/xml/file_1.xml'],
}
with self.assertRaises(ValueError) as ve:
self.renderBundle(debug=False)
self.assertEqual(
str(ve.exception),
"Module 'module_1' not loaded or inexistent (try to inherit 'template_1_1'), or templates of addon being loaded 'module_2' are misordered (template 'template_2_1')"
)
def test_static_misordered_templates(self):
self.template_files['/module_2/static/xml/file_1.xml'] = """
<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:
self.renderBundle(debug=False)
self.assertEqual(
str(ve.exception),
"Cannot create 'module_2.template_2_1' because the template to inherit 'module_2.template_2_2' is not found.",
)
def test_replace_in_debug_mode(self):
"""
Replacing a template's meta definition in place doesn't keep the original attrs of the template
"""
self.template_files = {
'/module_1/static/xml/file_1.xml': """
<templates id="template" xml:space="preserve">
<form t-name="template_1_1" random-attr="gloria">
<div>At first I was afraid</div>
</form>
<t t-name="template_1_2" t-inherit="template_1_1" t-inherit-mode="extension">
<xpath expr="." position="replace">
<div overriden-attr="overriden">And I grew strong</div>
</xpath>
</t>
</templates>
""",
}
expected = """
<templates xml:space="preserve">
<div overriden-attr="overriden" t-name="template_1_1">
And I grew strong
</div>
</templates>
"""
self.assertXMLEqual(self.renderBundle(debug=False), expected)
def test_replace_in_debug_mode2(self):
self.template_files = {
'/module_1/static/xml/file_1.xml': """
<templates id="template" xml:space="preserve">
<form t-name="template_1_1" random-attr="gloria">
<div>At first I was afraid</div>
</form>
<t t-name="template_1_2" t-inherit="template_1_1" t-inherit-mode="extension">
<xpath expr="." position="replace">
<div>
And I grew strong
<p>And I learned how to get along</p>
And so you're back
</div>
</xpath>
</t>
</templates>
""",
}
expected = """
<templates xml:space="preserve">
<div t-name="template_1_1">
And I grew strong
<p>And I learned how to get along</p>
And so you're back
</div>
</templates>
"""
self.assertXMLEqual(self.renderBundle(debug=False), expected)
def test_replace_in_debug_mode3(self):
"""Text outside of a div which will replace a whole template
becomes outside of the template
This doesn't mean anything in terms of the business of template inheritance
But it is in the XPATH specs"""
self.template_files = {
'/module_1/static/xml/file_1.xml': """
<templates id="template" xml:space="preserve">
<form t-name="template_1_1" random-attr="gloria">
<div>At first I was afraid</div>
</form>
<t t-name="template_1_2" t-inherit="template_1_1" t-inherit-mode="extension">
<xpath expr="." position="replace">
<div>
And I grew strong
<p>And I learned how to get along</p>
</div>
And so you're back
</xpath>
</t>
</templates>
""",
}
expected = """
<templates xml:space="preserve">
<div t-name="template_1_1">
And I grew strong
<p>And I learned how to get along</p>
</div>
And so you're back
</templates>
"""
self.assertXMLEqual(self.renderBundle(debug=False), expected)
def test_replace_root_node_tag(self):
"""
Root node IS targeted by //NODE_TAG in xpath
"""
self.template_files = {
'/module_1/static/xml/file_1.xml': """
<templates id="template" xml:space="preserve">
<form t-name="template_1_1" random-attr="gloria">
<div>At first I was afraid</div>
<form>Inner Form</form>
</form>
<t t-name="template_1_2" t-inherit="template_1_1" t-inherit-mode="extension">
<xpath expr="//form" position="replace">
<div>
Form replacer
</div>
</xpath>
</t>
</templates>
""",
}
expected = """
<templates xml:space="preserve">
<div t-name="template_1_1">
Form replacer
</div>
</templates>
"""
self.assertXMLEqual(self.renderBundle(debug=False), expected)
def test_replace_root_node_tag_in_primary(self):
"""
Root node IS targeted by //NODE_TAG in xpath
"""
self.template_files = {
'/module_1/static/xml/file_1.xml': """
<templates id="template" xml:space="preserve">
<form t-name="template_1_1" random-attr="gloria">
<div>At first I was afraid</div>
<form>Inner Form</form>
</form>
<form t-name="template_1_2" t-inherit="template_1_1" t-inherit-mode="primary">
<xpath expr="//form" position="replace">
<div>Form replacer</div>
</xpath>
</form>
</templates>
""",
}
expected = """
<templates xml:space="preserve">
<form t-name="template_1_1" random-attr="gloria">
<div>At first I was afraid</div>
<form>Inner Form</form>
</form>
<div t-name="template_1_2">
Form replacer
</div>
</templates>
"""
self.assertXMLEqual(self.renderBundle(debug=False), expected)
def test_inherit_primary_replace_debug(self):
"""
The inheriting template has got both its own defining attrs
and new ones if one is to replace its defining root node
"""
self.template_files = {
'/module_1/static/xml/file_1.xml': """
<templates id="template" xml:space="preserve">
<form t-name="template_1_1" random-attr="gloria">
<div>At first I was afraid</div>
</form>
<t t-name="template_1_2" t-inherit="template_1_1" t-inherit-mode="primary">
<xpath expr="." position="replace">
<div overriden-attr="overriden">
And I grew strong
<p>And I learned how to get along</p>
</div>
</xpath>
</t>
</templates>
""",
}
expected = """
<templates xml:space="preserve">
<form t-name="template_1_1" random-attr="gloria">
<div>At first I was afraid</div>
</form>
<div overriden-attr="overriden" t-name="template_1_2">
And I grew strong
<p>And I learned how to get along</p>
</div>
</templates>
"""
self.assertXMLEqual(self.renderBundle(debug=False), expected)
def test_replace_in_nodebug_mode1(self):
"""Comments already in the arch are ignored"""
self.template_files = {
'/module_1/static/xml/file_1.xml': """
<templates id="template" xml:space="preserve">
<form t-name="template_1_1" random-attr="gloria">
<div>At first I was afraid</div>
</form>
<t t-name="template_1_2" t-inherit="template_1_1" t-inherit-mode="extension">
<xpath expr="." position="replace">
<div>
<!-- Random Comment -->
And I grew strong
<p>And I learned how to get along</p>
And so you're back
</div>
</xpath>
</t>
</templates>
""",
}
expected = """
<templates xml:space="preserve">
<div t-name="template_1_1">
And I grew strong
<p>And I learned how to get along</p>
And so you're back
</div>
</templates>
"""
self.assertXMLEqual(self.renderBundle(debug=False), expected)
def test_inherit_from_dotted_tname_1(self):
self.template_files = {
'/module_1/static/xml/file_1.xml': """
<templates id="template" xml:space="preserve">
<form t-name="module_1.template_1_1.dot" random-attr="gloria">
<div>At first I was afraid</div>
</form>
<t t-name="template_1_2" t-inherit="template_1_1.dot" t-inherit-mode="primary">
<xpath expr="." position="replace">
<div overriden-attr="overriden">
And I grew strong
<p>And I learned how to get along</p>
</div>
</xpath>
</t>
</templates>
""",
}
expected = """
<templates xml:space="preserve">
<form t-name="module_1.template_1_1.dot" random-attr="gloria">
<div>At first I was afraid</div>
</form>
<div overriden-attr="overriden" t-name="template_1_2">
And I grew strong
<p>And I learned how to get along</p>
</div>
</templates>
"""
self.assertXMLEqual(self.renderBundle(debug=False), expected)
def test_inherit_from_dotted_tname_2(self):
self.template_files = {
'/module_1/static/xml/file_1.xml': """
<templates id="template" xml:space="preserve">
<form t-name="template_1_1.dot" random-attr="gloria">
<div>At first I was afraid</div>
</form>
<t t-name="template_1_2" t-inherit="template_1_1.dot" t-inherit-mode="primary">
<xpath expr="." position="replace">
<div overriden-attr="overriden">
And I grew strong
<p>And I learned how to get along</p>
</div>
</xpath>
</t>
</templates>
""",
}
expected = """
<templates xml:space="preserve">
<form t-name="template_1_1.dot" random-attr="gloria">
<div>At first I was afraid</div>
</form>
<div overriden-attr="overriden" t-name="template_1_2">
And I grew strong
<p>And I learned how to get along</p>
</div>
</templates>
"""
self.assertXMLEqual(self.renderBundle(debug=False), expected)
def test_inherit_from_dotted_tname_2bis(self):
self.template_files = {
'/module_1/static/xml/file_1.xml': """
<templates id="template" xml:space="preserve">
<form t-name="template_1_1.dot" random-attr="gloria">
<div>At first I was afraid</div>
</form>
<t t-name="template_1_2" t-inherit="module_1.template_1_1.dot" t-inherit-mode="primary">
<xpath expr="." position="replace">
<div overriden-attr="overriden">
And I grew strong
<p>And I learned how to get along</p>
</div>
</xpath>
</t>
</templates>
""",
}
expected = """
<templates xml:space="preserve">
<form t-name="template_1_1.dot" random-attr="gloria">
<div>At first I was afraid</div>
</form>
<div overriden-attr="overriden" t-name="template_1_2">
And I grew strong
<p>And I learned how to get along</p>
</div>
</templates>
"""
self.assertXMLEqual(self.renderBundle(debug=False), expected)
def test_inherit_from_dotted_tname_2ter(self):
self.template_files = {
'/module_1/static/xml/file_1.xml': """
<templates id="template" xml:space="preserve">
<form t-name="module_1" random-attr="gloria">
<div>At first I was afraid</div>
</form>
<t t-name="template_1_2" t-inherit="module_1" t-inherit-mode="primary">
<xpath expr="." position="replace">
<div overriden-attr="overriden">
And I grew strong
<p>And I learned how to get along</p>
</div>
</xpath>
</t>
</templates>
""",
}
expected = """
<templates xml:space="preserve">
<form t-name="module_1" random-attr="gloria">
<div>At first I was afraid</div>
</form>
<div overriden-attr="overriden" t-name="template_1_2">
And I grew strong
<p>And I learned how to get along</p>
</div>
</templates>
"""
self.assertXMLEqual(self.renderBundle(debug=False), expected)
def test_inherit_from_dotted_tname_3(self):
self.template_files = {
'/module_1/static/xml/file_1.xml': """
<templates id="template" xml:space="preserve">
<form t-name="module_1.template_1_1.dot" random-attr="gloria">
<div>At first I was afraid</div>
</form>
</templates>
""",
'/module_2/static/xml/file_1.xml': """
<templates id="template" xml:space="preserve">
<t t-name="template_2_1" t-inherit="module_1.template_1_1.dot" t-inherit-mode="primary">
<xpath expr="." position="replace">
<div overriden-attr="overriden">
And I grew strong
<p>And I learned how to get along</p>
</div>
</xpath>
</t>
</templates>
"""
}
expected = """
<templates xml:space="preserve">
<form t-name="module_1.template_1_1.dot" random-attr="gloria">
<div>At first I was afraid</div>
</form>
<div overriden-attr="overriden" t-name="template_2_1">
And I grew strong
<p>And I learned how to get along</p>
</div>
</templates>
"""
self.assertXMLEqual(self.renderBundle(debug=False), expected)
def test_inherit_and_qweb_extend(self):
self.template_files['/module_1/static/xml/file_2.xml'] = """
<templates id="template" xml:space="preserve">
<t t-name="template_qw_1">
<div>111</div>
</t>
<form t-inherit="template_1_1" t-inherit-mode="extension">
<xpath expr="//span[1]" position="replace">
<article>!!!</article>
</xpath>
</form>
<t t-name="template_qw_2">
<div>222</div>
</t>
<t t-extend="template_qw_1">
<t t-jquery="div" t-operation="after">
<div>333</div>
</t>
</t>
</templates>
"""
expected = """
<templates xml:space="preserve">
<form t-name="template_1_1" random-attr="gloria">
<article>!!!</article>
<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>
<div>And I learned how to get along</div>
</t>
<t t-name="template_qw_1">
<div>111</div>
</t>
<t t-name="template_qw_2">
<div>222</div>
</t>
<t t-extend="template_qw_1">
<t t-jquery="div" t-operation="after">
<div>333</div>
</t>
</t>
<form t-name="template_2_1" random-attr="gloria">
<span type="Scary screams">Ho !</span>
<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(self.renderBundle(debug=False), expected)
@tagged('-standard', 'assets_bundle', 'static_templates_performance')
class TestStaticInheritancePerformance(TestStaticInheritanceCommon):
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.asset_paths = []
self.template_files = {}
number_templates = 0
for m in range(nMod):
for f in range(nFilePerMod):
mname = 'mod_%s' % m
fname = 'mod_%s/folder/file_%s.xml' % (m, f)
self.asset_paths.append((fname, mname, 'bundle_1'))
_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/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/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
self.assertEqual(number_templates, nMod * nFilePerMod * nTemplatePerFile)
def test_static_templates_treatment_linearity(self):
# With 2500 templates for starters
nMod, nFilePerMod, nTemplatePerFile = 50, 5, 10
self._sick_script(nMod, nFilePerMod, nTemplatePerFile)
before = datetime.now()
contents = self.renderBundle(debug=False)
after = datetime.now()
delta2500 = after - before
_logger.runbot('Static Templates Inheritance: 2500 templates treated in %s seconds' % delta2500.total_seconds())
whole_tree = etree.fromstring(contents)
self.assertEqual(len(whole_tree), nMod * nFilePerMod * nTemplatePerFile)
# With 25000 templates next
nMod, nFilePerMod, nTemplatePerFile = 50, 5, 100
self._sick_script(nMod, nFilePerMod, nTemplatePerFile)
before = datetime.now()
self.renderBundle(debug=False)
after = datetime.now()
delta25000 = after - before
time_ratio = delta25000.total_seconds() / delta2500.total_seconds()
_logger.runbot('Static Templates Inheritance: 25000 templates treated in %s seconds' % delta25000.total_seconds())
_logger.runbot('Static Templates Inheritance: Computed linearity ratio: %s' % time_ratio)
self.assertLessEqual(time_ratio, 14)