[ADD][TEST] qweb: render a child view with its xmlid

Issue: rendering a child view with its xmlid return its `read_template`
wrapped in a "templates" tag. In the context of asset bundles, it leads
to the browser not being able to handle it correctly.

Explanation:

What happens when you render a view? First, QWeb will call is method
`get_template` with the argument `template`. This argument contains the
name of template you try to render. It can be an id or an xmlid (also an
etree but let's forget this one at the moment). Within the method
`get_template`, it'll call IrQweb `load` method, again with the argument
`template`.

This `load` method will call IrUiView's `read_template` wih `template`
as argument. This method will return the view in the database with all
inheritance applied, wrapped in a "templates" tag. When `read_template`
has returned, the `load` method will, according to `template` and in the
following order:

  * if `template` is an xmlid, return the whole `read_template`.
  * if `template` is an id and if one of the first child has "t-name"
    attribute (whatever its value): return the subtree of this first
    child.
  * if `template` is an id and one of the first child is a "t" tag: set
    a "t-name" attribute to this first child with the id as value and
    return the subtree of this child.

As you may note, inconsistencies already appear.

Now that QWeb's `get_template` has finished its call to IRQweb's `load`,
it'll do:

  * if one of the first child of what's returned as a "t-name" attribute
    matching the `template` argument, return this subtree
  * return the whole thing returned by IRQweb's `load`.

Let's say we have a view:

                <t t-name="base.dummy">
                    <div><span>something</span></div>
                </t>

What happens if we render this view with its xmlid? First Qweb's
`get_template` will load the template with the help of IRQweb's `load`
method. As it received an xmlid as argument, it'll return the whole
`read_template` output, wrapped in a "templates" tag. Qweb's
`get_template` will now check if one of the first child has a "t-name"
attribude matching the `template` argument (base.dummy in our case). It
has one, so it returns the subtree and the compilation continue.

What happens if we render this view with its id? IRQweb's receives an id
as argument, gets the `read_template` of it and check if one of the
first child has a "t-name" attribute. It does, so it returns this
subtree to `get_template`. The latest will check if one the first
children has a matching "t-name" attribute with the `template` argument.
It does not, so it returns the whole thing.

Weird things in the way we got here but the result is the same whatever
you tried to render the template with an id or an xmlid.

Now let's add an extension to this view and try to render it (the
extension):

                <xpath expr="//div" position="inside">
                    <span>another thing</span>
                </xpath>

What happens if we render this view with its xmlid? `load` will receive
an id, so it'll return the whole `read_template` stuff. Note here that
if you try to render a parent or child view, `read_template` will return
the same thing: the parent with all inheritance applied. Now `get_template`
looks through the first children of this to find a matching "t-name" and
it doesn't find one, as the "t-name" is the one of the parent view and
we tried to render with the child xmlid. It so returns the whole thing,
wrapped in a "templates" tag. There's our issue.

It's working if we tried to render the thing with its id because `load`
would have returned the right subtree.

This commit adds a test showing the failure.
This commit is contained in:
Simon Lejeune
2016-12-02 12:24:51 +01:00
committed by Olivier Dony
parent a894fb33c1
commit 9dd5bd9349
+41
View File
@@ -1183,3 +1183,44 @@ class TestXPathExtentions(common.BaseCase):
self.assertEqual(
len(tree.xpath('//node[hasclass("foo", "baz")]')),
1)
class TestQWebRender(ViewCase):
def test_render(self):
view1 = self.View.create({
'name': "dummy",
'type': 'qweb',
'arch': """
<t t-name="base.dummy">
<div><span>something</span></div>
</t>
"""
})
view2 = self.View.create({
'name': "dummy_ext",
'type': 'qweb',
'inherit_id': view1.id,
'arch': """
<xpath expr="//div" position="inside">
<span>another thing</span>
</xpath>
"""
})
# render with an id
content1 = self.env['ir.qweb'].with_context(check_view_ids=[view1.id, view2.id]).render(view1.id)
content2 = self.env['ir.qweb'].with_context(check_view_ids=[view1.id, view2.id]).render(view2.id)
self.assertEqual(content1, content2)
# render with an xmlid
self.env.cr.execute("INSERT INTO ir_model_data(name, model, res_id, module)"
"VALUES ('dummy', 'ir.ui.view', %s, 'base')" % view1.id)
self.env.cr.execute("INSERT INTO ir_model_data(name, model, res_id, module)"
"VALUES ('dummy_ext', 'ir.ui.view', %s, 'base')" % view2.id)
content1 = self.env['ir.qweb'].with_context(check_view_ids=[view1.id, view2.id]).render('base.dummy')
content2 = self.env['ir.qweb'].with_context(check_view_ids=[view1.id, view2.id]).render('base.dummy_ext')
self.assertEqual(content1, content2)