[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:
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user