[FIX] web: view compilers: use compiler in cache key

Before this commit, the key for the cache of compiled templates
(in the useViewCompiler hook) only relied on the arch, i.e. we had
a cache hit if we had to compile an arch that had already been
compiled before.

However, the Compiler to use might change. When this happens, the
template must be re-compiled, obviously, as the ouput may differ.

For instance, have a kanban arch with a js_class (pointing to a
custom view with a custom compiler), and have that arch used in an
x2many (where js_class is ignored, i.e. where it is rendered with
the basic kanban renderer). This happens in project, with the task
kanban view, which is also used for the child_ids field, in the
task form view.

This could lead to traceback, as the compiled template might refer
to attributes of a the KanbanRecord that only exist on the custom
KanbanRecord, not on the basic one.

Part-of: odoo/odoo#148351
This commit is contained in:
Aaron Bohy
2024-01-09 06:27:58 +00:00
parent edec454f53
commit 2005c38679
2 changed files with 75 additions and 1 deletions
+1 -1
View File
@@ -453,7 +453,7 @@ export function useViewCompiler(ViewCompiler, templates, params) {
const compiledTemplates = {};
let compiler;
for (const tname in templates) {
const key = templates[tname].outerHTML;
const key = `${ViewCompiler.name}/${templates[tname].outerHTML}`;
if (!templateCache[key]) {
compiler = compiler || new ViewCompiler(templates);
templateCache[key] = xml`${compiler.compile(tname, params).outerHTML}`;
@@ -46,6 +46,8 @@ import { AnimatedNumber } from "@web/views/view_components/animated_number";
import { Component, onRendered, onWillRender, xml } from "@odoo/owl";
import { SampleServer } from "@web/model/sample_server";
import { KanbanRenderer } from "@web/views/kanban/kanban_renderer";
import { KanbanCompiler } from "@web/views/kanban/kanban_compiler";
import { KanbanRecord } from "@web/views/kanban/kanban_record";
import {
patchDialog,
@@ -14150,4 +14152,76 @@ QUnit.module("Views", (hooks) => {
assert.containsN(target, ".o_kanban_group:nth-child(2) .o_kanban_record", 0);
assert.containsN(target, ".o_kanban_group:nth-child(3) .o_kanban_record", 2);
});
QUnit.test("kanbans with basic and custom compiler, same arch", async (assert) => {
// In this test, the exact same arch will be rendered by 2 different kanban renderers:
// once with the basic one, and once with a custom renderer having a custom compiler. The
// purpose of the test is to ensure that the template is compiled twice, once by each
// compiler, even though the arch is the same.
class MyKanbanCompiler extends KanbanCompiler {
setup() {
super.setup();
this.compilers.push({ selector: "div", fn: this.compileDiv });
}
compileDiv(node, params) {
const compiledNode = this.compileGenericNode(node, params);
compiledNode.setAttribute("class", "my_kanban_compiler");
return compiledNode;
}
}
class MyKanbanRecord extends KanbanRecord {}
MyKanbanRecord.Compiler = MyKanbanCompiler;
class MyKanbanRenderer extends KanbanRenderer {}
MyKanbanRenderer.components = {
...KanbanRenderer.components,
KanbanRecord: MyKanbanRecord,
};
viewRegistry.add("my_kanban", {
...kanbanView,
Renderer: MyKanbanRenderer,
});
serverData.models.partner.fields.one2many = {
type: "one2many",
name: "o2m",
relation: "partner",
};
serverData.models.partner.records[0].one2many = [1];
serverData.views = {
"partner,false,form": `<form><field name="one2many" mode="kanban"/></form>`,
"partner,false,search": `<search/>`,
"partner,false,kanban": `<kanban js_class="my_kanban">
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click"><field name="foo"/></div>
</t>
</templates>
</kanban>`,
};
const webClient = await createWebClient({ serverData });
await doAction(webClient, {
res_model: "partner",
type: "ir.actions.act_window",
views: [
[false, "kanban"],
[false, "form"],
],
});
// main kanban, custom view
assert.containsOnce(target, ".o_kanban_view");
assert.containsOnce(target, ".o_my_kanban_view");
assert.containsN(target, ".my_kanban_compiler", 4);
// switch to form
await click(target.querySelector(".o_kanban_record"));
assert.containsOnce(target, ".o_form_view");
assert.containsOnce(target, ".o_form_view .o_field_widget[name=one2many]");
// x2many kanban, basic renderer
assert.containsOnce(target, ".o_kanban_record:not(.o_kanban_ghost)");
assert.containsNone(target, ".my_kanban_compiler");
});
});