[FW][FIX] web: update owl from 2.0.2 to 2.0.3
Release notes: https://github.com/odoo/owl/releases/tag/v2.0.3 - fix: compiler: correctly escape backslashes when emitting block string - fix: reactivity: don't subscribe to keys when making reactive - fix: t-call-context: fix capture making component available in ctx - fix: t-call-context: make `this` unavailable in rendering context Description of the issue/feature this PR addresses: Current behavior before PR: Desired behavior after PR is merged: closes odoo/odoo#110196 Forward-port-of: odoo/odoo#109976 Forward-port-of: odoo/odoo#109788 Related: odoo/enterprise#35939 Signed-off-by: Géry Debongnie <ged@odoo.com> Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
<t t-name="analytic.AnalyticDistribution" owl="1">
|
||||
<div class="o_field_tags d-inline-flex flex-wrap mw-100" t-att-class="{'o_tags_input o_input': !props.readonly}" t-ref="analyticDistribution" t-on-keydown="onWidgetKeydown">
|
||||
<TagsList tags="tags"/>
|
||||
<TagsList tags="this.tags"/>
|
||||
<div t-if="!props.readonly" class="o_input_dropdown d-inline-flex w-100" tabindex="0" t-ref="mainElement" t-on-focus="onMainElementFocus" t-on-click="onMainElementFocus">
|
||||
<span class="analytic_distribution_placeholder"/>
|
||||
<a role="button" class="o_dropdown_button" draggable="false"/>
|
||||
|
||||
@@ -45,6 +45,7 @@ export class ActivityRecord extends Component {
|
||||
user_context: this.user.context,
|
||||
widget: this.widget,
|
||||
luxon,
|
||||
__comp__: Object.assign(Object.create(this), { this: this }),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<t t-name="mail.ActivityRecord" owl="1">
|
||||
<td class="o_activity_record p-3 cursor-pointer" t-on-click="() => this.props.openRecord(this.props.record)">
|
||||
<t t-call="{{ recordTemplate }}" t-call-context="getRenderingContext()"/>
|
||||
<t t-call="{{ recordTemplate }}" t-call-context="this.getRenderingContext()"/>
|
||||
</td>
|
||||
</t>
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ export class MailFormCompiler extends ViewCompiler {
|
||||
const chatterContainerHookXml = res.querySelector(".o_FormRenderer_chatterContainer");
|
||||
if (chatterContainerHookXml) {
|
||||
setAttributes(chatterContainerHookXml, {
|
||||
"t-if": `!this.hasAttachmentViewer() and this.uiService.size >= ${SIZES.XXL}`,
|
||||
"t-if": `!__comp__.hasAttachmentViewer() and __comp__.uiService.size >= ${SIZES.XXL}`,
|
||||
"t-attf-class": "o-aside",
|
||||
});
|
||||
const chatterContainerXml = chatterContainerHookXml.querySelector("ChatterContainer");
|
||||
@@ -99,7 +99,7 @@ export class MailFormCompiler extends ViewCompiler {
|
||||
const attachmentViewHookXml = res.querySelector(".o_attachment_preview");
|
||||
if (attachmentViewHookXml) {
|
||||
setAttributes(attachmentViewHookXml, {
|
||||
"t-if": `this.hasAttachmentViewer()`,
|
||||
"t-if": `__comp__.hasAttachmentViewer()`,
|
||||
});
|
||||
}
|
||||
return res;
|
||||
@@ -112,7 +112,7 @@ export class MailFormCompiler extends ViewCompiler {
|
||||
let compiledChild = this.compileNode(child, params, false);
|
||||
compiledChild = this.applyInvisible(invisible, compiledChild, {
|
||||
...params,
|
||||
recordExpr: "this.model.root",
|
||||
recordExpr: "__comp__.model.root",
|
||||
});
|
||||
append(compiledRoot, compiledChild);
|
||||
}
|
||||
@@ -122,16 +122,16 @@ export class MailFormCompiler extends ViewCompiler {
|
||||
compileChatter(node) {
|
||||
return compileChatter(node, {
|
||||
chatter: "chatter",
|
||||
threadId: "this.model.root.resId or undefined",
|
||||
threadModel: "this.model.root.resModel",
|
||||
webRecord: "this.model.root",
|
||||
threadId: "__comp__.model.root.resId or undefined",
|
||||
threadModel: "__comp__.model.root.resModel",
|
||||
webRecord: "__comp__.model.root",
|
||||
});
|
||||
}
|
||||
|
||||
compileAttachmentPreview(node) {
|
||||
return compileAttachmentPreview(node, {
|
||||
threadId: "this.model.root.resId or undefined",
|
||||
threadModel: "this.model.root.resModel",
|
||||
threadId: "__comp__.model.root.resId or undefined",
|
||||
threadModel: "__comp__.model.root.resModel",
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -140,10 +140,10 @@ registry.category("form_compilers").add("chatter_compiler", {
|
||||
selector: "div.oe_chatter",
|
||||
fn: (node) =>
|
||||
compileChatter(node, {
|
||||
chatter: "this.props.chatter",
|
||||
threadId: "this.props.record.resId or undefined",
|
||||
threadModel: "this.props.record.resModel",
|
||||
webRecord: "this.props.record",
|
||||
chatter: "__comp__.props.chatter",
|
||||
threadId: "__comp__.props.record.resId or undefined",
|
||||
threadModel: "__comp__.props.record.resModel",
|
||||
webRecord: "__comp__.props.record",
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -151,8 +151,8 @@ registry.category("form_compilers").add("attachment_preview_compiler", {
|
||||
selector: "div.o_attachment_preview",
|
||||
fn: (node) =>
|
||||
compileAttachmentPreview(node, {
|
||||
threadId: "this.props.record.resId or undefined",
|
||||
threadModel: "this.props.record.resModel",
|
||||
threadId: "__comp__.props.record.resId or undefined",
|
||||
threadModel: "__comp__.props.record.resModel",
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -183,7 +183,7 @@ patch(FormCompiler.prototype, "mail", {
|
||||
const sheetBgChatterContainerHookXml = chatterContainerHookXml.cloneNode(true);
|
||||
sheetBgChatterContainerHookXml.classList.add("o-isInFormSheetBg");
|
||||
setAttributes(sheetBgChatterContainerHookXml, {
|
||||
"t-if": `this.props.hasAttachmentViewer`,
|
||||
"t-if": `__comp__.props.hasAttachmentViewer`,
|
||||
});
|
||||
append(formSheetBgXml, sheetBgChatterContainerHookXml);
|
||||
const sheetBgChatterContainerXml = sheetBgChatterContainerHookXml.querySelector(
|
||||
@@ -195,7 +195,7 @@ patch(FormCompiler.prototype, "mail", {
|
||||
}
|
||||
// after sheet bg (standard position, below form)
|
||||
setAttributes(chatterContainerHookXml, {
|
||||
"t-if": `!this.props.hasAttachmentViewer and this.uiService.size < ${SIZES.XXL}`,
|
||||
"t-if": `!__comp__.props.hasAttachmentViewer and __comp__.uiService.size < ${SIZES.XXL}`,
|
||||
});
|
||||
append(parentXml, chatterContainerHookXml);
|
||||
return res;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<t t-inherit="web.FormView" t-inherit-mode="extension">
|
||||
<xpath expr="//div[hasclass('o_form_view_container')]" position="after">
|
||||
<t t-if="mailTemplate">
|
||||
<t t-call="{{ mailTemplate }}" />
|
||||
<t t-call="{{ mailTemplate }}" t-call-context="{ __comp__: Object.assign(Object.create(this), { this: this }) }"/>
|
||||
</t>
|
||||
</xpath>
|
||||
<xpath expr="//Layout/t[@t-component='props.Renderer']" position="attributes">
|
||||
|
||||
+9
-9
@@ -40,7 +40,7 @@ export class ProjectSharingChatterCompiler extends ViewCompiler {
|
||||
const chatterContainerHookXml = res.querySelector(".o_FormRenderer_chatterContainer");
|
||||
if (chatterContainerHookXml) {
|
||||
setAttributes(chatterContainerHookXml, {
|
||||
"t-if": `this.uiService.size >= ${SIZES.XXL}`,
|
||||
"t-if": `__comp__.uiService.size >= ${SIZES.XXL}`,
|
||||
});
|
||||
chatterContainerHookXml.classList.add('overflow-x-hidden', 'overflow-y-auto', 'o-aside', 'h-100');
|
||||
}
|
||||
@@ -54,7 +54,7 @@ export class ProjectSharingChatterCompiler extends ViewCompiler {
|
||||
let compiledChild = this.compileNode(child, params, false);
|
||||
compiledChild = this.applyInvisible(invisible, compiledChild, {
|
||||
...params,
|
||||
recordExpr: "this.model.root",
|
||||
recordExpr: "__comp__.model.root",
|
||||
});
|
||||
append(compiledRoot, compiledChild);
|
||||
}
|
||||
@@ -63,9 +63,9 @@ export class ProjectSharingChatterCompiler extends ViewCompiler {
|
||||
|
||||
compileChatter(node) {
|
||||
return compileChatter(node, {
|
||||
resId: 'this.model.root.resId or undefined',
|
||||
resModel: 'this.model.root.resModel',
|
||||
projectSharingId: 'this.model.root.context.active_id',
|
||||
resId: '__comp__.model.root.resId or undefined',
|
||||
resModel: '__comp__.model.root.resModel',
|
||||
projectSharingId: '__comp__.model.root.context.active_id',
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -74,9 +74,9 @@ registry.category("form_compilers").add("portal_chatter_compiler", {
|
||||
selector: "div.oe_chatter",
|
||||
fn: (node) =>
|
||||
compileChatter(node, {
|
||||
resId: "this.props.record.resId or undefined",
|
||||
resModel: "this.props.record.resModel",
|
||||
projectSharingId: "this.props.record.context.active_id",
|
||||
resId: "__comp__.props.record.resId or undefined",
|
||||
resModel: "__comp__.props.record.resModel",
|
||||
projectSharingId: "__comp__.props.record.context.active_id",
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -97,7 +97,7 @@ patch(FormCompiler.prototype, 'project_sharing_chatter', {
|
||||
}
|
||||
// after sheet bg (standard position, below form)
|
||||
setAttributes(chatterContainerHookXml, {
|
||||
't-if': `this.uiService.size < ${SIZES.XXL}`,
|
||||
't-if': `__comp__.uiService.size < ${SIZES.XXL}`,
|
||||
});
|
||||
append(parentXml, chatterContainerHookXml);
|
||||
return res;
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<t t-inherit="web.FormView" t-inherit-mode="extension">
|
||||
<xpath expr="//div[hasclass('o_form_view_container')]" position="after">
|
||||
<t t-if="mailTemplate">
|
||||
<t t-call="{{ mailTemplate }}" />
|
||||
<t t-call="{{ mailTemplate }}" t-call-context="{ __comp__: Object.assign(Object.create(this), { this: this }) }"/>
|
||||
</t>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
@@ -1759,7 +1759,7 @@
|
||||
* @returns the raw type of the object
|
||||
*/
|
||||
function rawType(obj) {
|
||||
return objectToString.call(obj).slice(8, -1);
|
||||
return objectToString.call(toRaw(obj)).slice(8, -1);
|
||||
}
|
||||
/**
|
||||
* Checks whether a given value can be made into a reactive object.
|
||||
@@ -2309,7 +2309,8 @@
|
||||
}
|
||||
}
|
||||
this.component = new C(props, env, this);
|
||||
this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this);
|
||||
const ctx = Object.assign(Object.create(this.component), { this: this.component });
|
||||
this.renderFn = app.getTemplate(C.template).bind(this.component, ctx, this);
|
||||
this.component.setup();
|
||||
currentNode = null;
|
||||
}
|
||||
@@ -2500,6 +2501,7 @@
|
||||
}
|
||||
_patch() {
|
||||
let hasChildren = false;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
for (let _k in this.children) {
|
||||
hasChildren = true;
|
||||
break;
|
||||
@@ -2894,8 +2896,8 @@
|
||||
}
|
||||
return slotBDom || text("");
|
||||
}
|
||||
function capture(ctx, component) {
|
||||
const result = ObjectCreate(component);
|
||||
function capture(ctx) {
|
||||
const result = ObjectCreate(ctx);
|
||||
for (let k in ctx) {
|
||||
result[k] = ctx[k];
|
||||
}
|
||||
@@ -2950,7 +2952,7 @@
|
||||
class LazyValue {
|
||||
constructor(fn, ctx, component, node, key) {
|
||||
this.fn = fn;
|
||||
this.ctx = capture(ctx, component);
|
||||
this.ctx = capture(ctx);
|
||||
this.component = component;
|
||||
this.node = node;
|
||||
this.key = key;
|
||||
@@ -3110,7 +3112,7 @@
|
||||
if (line[columnIndex]) {
|
||||
msg +=
|
||||
`\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
|
||||
`${line}\n${"-".repeat(columnIndex - 1)}^`;
|
||||
`${line}\n${"-".repeat(columnIndex - 1)}^`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3232,7 +3234,7 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// Misc types, constants and helpers
|
||||
//------------------------------------------------------------------------------
|
||||
const RESERVED_WORDS = "true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,this,eval,void,Math,RegExp,Array,Object,Date".split(",");
|
||||
const RESERVED_WORDS = "true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,eval,void,Math,RegExp,Array,Object,Date".split(",");
|
||||
const WORD_REPLACEMENT = Object.assign(Object.create(null), {
|
||||
and: "&&",
|
||||
or: "||",
|
||||
@@ -3710,7 +3712,7 @@
|
||||
for (let block of this.blocks) {
|
||||
if (block.dom) {
|
||||
let xmlString = block.asXmlString();
|
||||
xmlString = xmlString.replace(/`/g, "\\`");
|
||||
xmlString = xmlString.replace(/\\/g, "\\\\").replace(/`/g, "\\`");
|
||||
if (block.dynamicTagName) {
|
||||
xmlString = xmlString.replace(/^<\w+/, `<\${tag || '${block.dom.nodeName}'}`);
|
||||
xmlString = xmlString.replace(/\w+>$/, `\${tag || '${block.dom.nodeName}'}>`);
|
||||
@@ -3817,16 +3819,16 @@
|
||||
const mapping = new Map();
|
||||
return tokens
|
||||
.map((tok) => {
|
||||
if (tok.varName && !tok.isLocal) {
|
||||
if (!mapping.has(tok.varName)) {
|
||||
const varId = generateId("v");
|
||||
mapping.set(tok.varName, varId);
|
||||
this.define(varId, tok.value);
|
||||
}
|
||||
tok.value = mapping.get(tok.varName);
|
||||
if (tok.varName && !tok.isLocal) {
|
||||
if (!mapping.has(tok.varName)) {
|
||||
const varId = generateId("v");
|
||||
mapping.set(tok.varName, varId);
|
||||
this.define(varId, tok.value);
|
||||
}
|
||||
return tok.value;
|
||||
})
|
||||
tok.value = mapping.get(tok.varName);
|
||||
}
|
||||
return tok.value;
|
||||
})
|
||||
.join("");
|
||||
}
|
||||
/**
|
||||
@@ -3927,11 +3929,11 @@
|
||||
.split(".")
|
||||
.slice(1)
|
||||
.map((m) => {
|
||||
if (!MODS.has(m)) {
|
||||
throw new OwlError(`Unknown event modifier: '${m}'`);
|
||||
}
|
||||
return `"${m}"`;
|
||||
});
|
||||
if (!MODS.has(m)) {
|
||||
throw new OwlError(`Unknown event modifier: '${m}'`);
|
||||
}
|
||||
return `"${m}"`;
|
||||
});
|
||||
let modifiersCode = "";
|
||||
if (modifiers.length) {
|
||||
modifiersCode = `${modifiers.join(",")}, `;
|
||||
@@ -4508,7 +4510,7 @@
|
||||
if (this.target.loopLevel || !this.hasSafeContext) {
|
||||
ctxStr = generateId("ctx");
|
||||
this.helpers.add("capture");
|
||||
this.define(ctxStr, `capture(ctx, this)`);
|
||||
this.define(ctxStr, `capture(ctx)`);
|
||||
}
|
||||
let slotStr = [];
|
||||
for (let slotName in ast.slots) {
|
||||
@@ -4667,7 +4669,7 @@
|
||||
if (this.target.loopLevel || !this.hasSafeContext) {
|
||||
ctxStr = generateId("ctx");
|
||||
this.helpers.add("capture");
|
||||
this.define(ctxStr, `capture(ctx, this)`);
|
||||
this.define(ctxStr, `capture(ctx)`);
|
||||
}
|
||||
let id = generateId("comp");
|
||||
this.staticDefs.push({
|
||||
@@ -5412,7 +5414,7 @@
|
||||
if (line[columnIndex]) {
|
||||
msg +=
|
||||
`\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` +
|
||||
`${line}\n${"-".repeat(columnIndex - 1)}^`;
|
||||
`${line}\n${"-".repeat(columnIndex - 1)}^`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5856,9 +5858,9 @@ See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
|
||||
__info__.version = '2.0.2';
|
||||
__info__.date = '2022-11-29T14:11:21.188Z';
|
||||
__info__.hash = 'ef8baa2';
|
||||
__info__.version = '2.0.3';
|
||||
__info__.date = '2023-01-12T15:32:52.837Z';
|
||||
__info__.hash = '316eb06';
|
||||
__info__.url = 'https://github.com/odoo/owl';
|
||||
|
||||
|
||||
|
||||
@@ -76,13 +76,13 @@ export class FormCompiler extends ViewCompiler {
|
||||
} else {
|
||||
labelText = labelText
|
||||
? toStringExpression(labelText)
|
||||
: `this.props.record.fields['${fieldName}'].string`;
|
||||
: `__comp__.props.record.fields['${fieldName}'].string`;
|
||||
}
|
||||
const formLabel = createElement("FormLabel", {
|
||||
id: `'${fieldId}'`,
|
||||
fieldName: `'${fieldName}'`,
|
||||
record: `this.props.record`,
|
||||
fieldInfo: `this.props.archInfo.fieldNodes['${fieldId}']`,
|
||||
record: `__comp__.props.record`,
|
||||
fieldInfo: `__comp__.props.archInfo.fieldNodes['${fieldId}']`,
|
||||
className: `"${label.className}"`,
|
||||
string: labelText,
|
||||
});
|
||||
@@ -140,7 +140,7 @@ export class FormCompiler extends ViewCompiler {
|
||||
if (typeof invisible === "boolean") {
|
||||
isVisibleExpr = `${invisible ? false : true}`;
|
||||
} else {
|
||||
isVisibleExpr = `!this.evalDomainFromRecord(this.props.record,${JSON.stringify(
|
||||
isVisibleExpr = `!__comp__.evalDomainFromRecord(__comp__.props.record,${JSON.stringify(
|
||||
invisible
|
||||
)})`;
|
||||
}
|
||||
@@ -176,8 +176,8 @@ export class FormCompiler extends ViewCompiler {
|
||||
|
||||
compileButton(el, params) {
|
||||
const compiled = super.compileButton(el, params);
|
||||
compiled.setAttribute("disable", "this.props.disableViewButtons");
|
||||
compiled.setAttribute("enable", "this.props.enableViewButtons");
|
||||
compiled.setAttribute("disable", "__comp__.props.disableViewButtons");
|
||||
compiled.setAttribute("enable", "__comp__.props.enableViewButtons");
|
||||
return compiled;
|
||||
}
|
||||
|
||||
@@ -219,13 +219,13 @@ export class FormCompiler extends ViewCompiler {
|
||||
compileForm(el, params) {
|
||||
const sheetNode = el.querySelector("sheet");
|
||||
const displayClasses = sheetNode
|
||||
? `d-flex {{ this.uiService.size < ${SIZES.XXL} ? "flex-column" : "flex-nowrap h-100" }}`
|
||||
? `d-flex {{ __comp__.uiService.size < ${SIZES.XXL} ? "flex-column" : "flex-nowrap h-100" }}`
|
||||
: "d-block";
|
||||
const stateClasses =
|
||||
"{{ this.props.record.isDirty ? 'o_form_dirty' : !this.props.record.isVirtual ? 'o_form_saved' : '' }}";
|
||||
"{{ __comp__.props.record.isDirty ? 'o_form_dirty' : !__comp__.props.record.isVirtual ? 'o_form_saved' : '' }}";
|
||||
const form = createElement("div", {
|
||||
"t-att-class": "this.props.class",
|
||||
"t-attf-class": `{{this.props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} ${displayClasses} ${stateClasses}`,
|
||||
"t-att-class": "__comp__.props.class",
|
||||
"t-attf-class": `{{__comp__.props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} ${displayClasses} ${stateClasses}`,
|
||||
});
|
||||
if (!sheetNode) {
|
||||
for (const child of el.childNodes) {
|
||||
@@ -249,8 +249,9 @@ export class FormCompiler extends ViewCompiler {
|
||||
if (localization.multiLang) {
|
||||
const statusBar = form.querySelector(".o_form_statusbar");
|
||||
const translateAlert = createElement("t", {
|
||||
"t-if": "this.props.translateAlert",
|
||||
"t-if": "__comp__.props.translateAlert",
|
||||
"t-call": "web.TranslateAlert",
|
||||
"t-call-context": "__comp__",
|
||||
});
|
||||
if (statusBar) {
|
||||
statusBar.parentElement.insertBefore(translateAlert, statusBar.nextSibling);
|
||||
@@ -332,14 +333,14 @@ export class FormCompiler extends ViewCompiler {
|
||||
const props = {
|
||||
id: `${fieldId}`,
|
||||
fieldName: `'${fieldName}'`,
|
||||
record: `this.props.record`,
|
||||
record: `__comp__.props.record`,
|
||||
string: child.hasAttribute("string")
|
||||
? toStringExpression(child.getAttribute("string"))
|
||||
: `this.props.record.fields.${fieldName}.string`,
|
||||
fieldInfo: `this.props.archInfo.fieldNodes[${fieldId}]`,
|
||||
: `__comp__.props.record.fields.${fieldName}.string`,
|
||||
fieldInfo: `__comp__.props.archInfo.fieldNodes[${fieldId}]`,
|
||||
};
|
||||
mainSlot.setAttribute("props", objectToString(props));
|
||||
mainSlot.setAttribute("Component", "this.constructor.components.FormLabel");
|
||||
mainSlot.setAttribute("Component", "__comp__.constructor.components.FormLabel");
|
||||
mainSlot.setAttribute("subType", "'item_component'");
|
||||
}
|
||||
} else {
|
||||
@@ -360,7 +361,7 @@ export class FormCompiler extends ViewCompiler {
|
||||
if (typeof invisible === "boolean") {
|
||||
isVisibleExpr = `${invisible ? false : true}`;
|
||||
} else {
|
||||
isVisibleExpr = `!this.evalDomainFromRecord(this.props.record,${JSON.stringify(
|
||||
isVisibleExpr = `!__comp__.evalDomainFromRecord(__comp__.props.record,${JSON.stringify(
|
||||
invisible
|
||||
)})`;
|
||||
}
|
||||
@@ -504,11 +505,11 @@ export class FormCompiler extends ViewCompiler {
|
||||
|
||||
noteBook.setAttribute(
|
||||
"defaultPage",
|
||||
`this.props.record.isNew ? undefined : this.props.activeNotebookPages[${noteBookId}]`
|
||||
`__comp__.props.record.isNew ? undefined : __comp__.props.activeNotebookPages[${noteBookId}]`
|
||||
);
|
||||
noteBook.setAttribute(
|
||||
"onPageUpdate",
|
||||
`(page) => this.props.onNotebookPageChange(${noteBookId}, page)`
|
||||
`(page) => __comp__.props.onNotebookPageChange(${noteBookId}, page)`
|
||||
);
|
||||
|
||||
for (const child of el.children) {
|
||||
@@ -539,7 +540,7 @@ export class FormCompiler extends ViewCompiler {
|
||||
if (child.getAttribute("autofocus") === "autofocus") {
|
||||
noteBook.setAttribute(
|
||||
"defaultPage",
|
||||
`this.props.record.isNew ? "${pageId}" : (this.props.activeNotebookPages[${noteBookId}] || "${pageId}")`
|
||||
`__comp__.props.record.isNew ? "${pageId}" : (__comp__.props.activeNotebookPages[${noteBookId}] || "${pageId}")`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -558,7 +559,7 @@ export class FormCompiler extends ViewCompiler {
|
||||
if (typeof invisible === "boolean") {
|
||||
isVisible = `${!invisible}`;
|
||||
} else {
|
||||
isVisible = `!this.evalDomainFromRecord(this.props.record,${JSON.stringify(
|
||||
isVisible = `!__comp__.evalDomainFromRecord(__comp__.props.record,${JSON.stringify(
|
||||
invisible
|
||||
)})`;
|
||||
}
|
||||
@@ -632,7 +633,7 @@ export class FormCompiler extends ViewCompiler {
|
||||
*/
|
||||
compileWidget(el) {
|
||||
const widget = super.compileWidget(el);
|
||||
widget.setAttribute("readonly", `!this.props.record.isInEdition`);
|
||||
widget.setAttribute("readonly", `!__comp__.props.record.isInEdition`);
|
||||
return widget;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,16 +66,15 @@
|
||||
|
||||
<!--
|
||||
Used in FormRenderer
|
||||
Note: `props` is only accessible through `this` as we call the compiled template with
|
||||
`t-call-context="{}"`
|
||||
Note: `props` is only accessible through `this` as we call the compiled template a restricted context (t-call-context)
|
||||
-->
|
||||
<t t-name="web.TranslateAlert" owl="1">
|
||||
<div class="alert alert-info alert-dismissible" role="alertdialog">
|
||||
Please update translations of :
|
||||
<t t-foreach="Array.from(this.props.translateAlert.fields)" t-as="field" t-key="field.name">
|
||||
<t t-foreach="Array.from(props.translateAlert.fields)" t-as="field" t-key="field.name">
|
||||
<strong><a class="o_field_translate" t-att-name="field.name" href="#"><t t-esc="field.string"/><t t-if="!field_last">, </t></a></strong>
|
||||
</t>
|
||||
<button t-on-click="this.props.translateAlert.close" type="button" class="btn-close" aria-label="Close"></button>
|
||||
<button t-on-click="props.translateAlert.close" type="button" class="btn-close" aria-label="Close"></button>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
@@ -93,7 +93,7 @@ export class FormRenderer extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
FormRenderer.template = xml`<t t-call="{{ templates.FormRenderer }}" t-call-context="{}" />`;
|
||||
FormRenderer.template = xml`<t t-call="{{ templates.FormRenderer }}" t-call-context="{ __comp__: Object.assign(Object.create(this), { this: this }) }" />`;
|
||||
FormRenderer.components = {
|
||||
Field,
|
||||
FormLabel,
|
||||
|
||||
@@ -80,7 +80,7 @@ export class KanbanCompiler extends ViewCompiler {
|
||||
const strParams = Object.entries(nodeParams)
|
||||
.map(([k, v]) => [k, toStringExpression(v)].join(":"))
|
||||
.join(",");
|
||||
el.setAttribute("t-on-click", `()=>this.triggerAction({${strParams}})`);
|
||||
el.setAttribute("t-on-click", `()=>__comp__.triggerAction({${strParams}})`);
|
||||
|
||||
const compiled = createElement(el.nodeName);
|
||||
for (const { name, value } of el.attributes) {
|
||||
@@ -100,7 +100,10 @@ export class KanbanCompiler extends ViewCompiler {
|
||||
* @returns {Element}
|
||||
*/
|
||||
compileColorPicker() {
|
||||
return createElement("t", { "t-call": "web.KanbanColorPicker" });
|
||||
return createElement("t", {
|
||||
"t-call": "web.KanbanColorPicker",
|
||||
"t-call-context": "__comp__",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,7 +120,7 @@ export class KanbanCompiler extends ViewCompiler {
|
||||
} else {
|
||||
compiled = super.compileField(el, params);
|
||||
const fieldId = el.getAttribute("field_id") || el.getAttribute("name");
|
||||
compiled.setAttribute("id", `'${fieldId}_' + this.props.record.id`);
|
||||
compiled.setAttribute("id", `'${fieldId}_' + __comp__.props.record.id`);
|
||||
}
|
||||
|
||||
const { bold, display } = extractAttributes(el, ["bold", "display"]);
|
||||
@@ -177,7 +180,7 @@ export class KanbanCompiler extends ViewCompiler {
|
||||
const compiled = this.compileGenericNode(el, params);
|
||||
const tname = el.getAttribute("t-call");
|
||||
if (tname in this.templates) {
|
||||
compiled.setAttribute("t-call", `{{this.templates[${toStringExpression(tname)}]}}`);
|
||||
compiled.setAttribute("t-call", `{{__comp__.templates[${toStringExpression(tname)}]}}`);
|
||||
}
|
||||
return compiled;
|
||||
}
|
||||
|
||||
@@ -304,6 +304,7 @@ export class KanbanRecord extends Component {
|
||||
selection_mode: this.props.forceGlobalClick,
|
||||
user_context: this.user.context,
|
||||
widget: this.widget,
|
||||
__comp__: Object.assign(Object.create(this), { this: this }),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
t-att-tabindex="props.record.model.useSampleModel ? -1 : 0"
|
||||
t-on-click="onGlobalClick"
|
||||
t-ref="root">
|
||||
<t t-call="{{ templates[this.constructor.KANBAN_BOX_ATTRIBUTE] }}" t-call-context="renderingContext"/>
|
||||
<t t-call="{{ templates[this.constructor.KANBAN_BOX_ATTRIBUTE] }}" t-call-context="this.renderingContext"/>
|
||||
<t t-call="{{ this.constructor.menuTemplate }}"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
@@ -223,7 +223,7 @@ export class ViewCompiler {
|
||||
{ selector: "widget", fn: this.compileWidget },
|
||||
];
|
||||
this.templates = templates;
|
||||
this.ctx = { readonly: "this.props.readonly" };
|
||||
this.ctx = { readonly: "__comp__.props.readonly" };
|
||||
|
||||
this.owlDirectiveRegexesWhitelist = this.constructor.OWL_DIRECTIVE_WHITELIST.map(
|
||||
(d) => new RegExp(d)
|
||||
@@ -246,8 +246,10 @@ export class ViewCompiler {
|
||||
if (typeof invisible === "boolean") {
|
||||
return;
|
||||
}
|
||||
const recordExpr = params.recordExpr || "this.props.record";
|
||||
let isVisileExpr = `!this.evalDomainFromRecord(${recordExpr},${JSON.stringify(invisible)})`;
|
||||
const recordExpr = params.recordExpr || "__comp__.props.record";
|
||||
let isVisileExpr = `!__comp__.evalDomainFromRecord(${recordExpr},${JSON.stringify(
|
||||
invisible
|
||||
)})`;
|
||||
if (compiled.hasAttribute("t-if")) {
|
||||
const formerTif = compiled.getAttribute("t-if");
|
||||
isVisileExpr = `( ${formerTif} ) and ${isVisileExpr}`;
|
||||
@@ -323,7 +325,7 @@ export class ViewCompiler {
|
||||
}
|
||||
const button = createElement("ViewButton", {
|
||||
tag: toStringExpression(tag),
|
||||
record: "this.props.record",
|
||||
record: "__comp__.props.record",
|
||||
});
|
||||
|
||||
assignOwlDirectives(button, el);
|
||||
@@ -382,8 +384,8 @@ export class ViewCompiler {
|
||||
const field = createElement("Field");
|
||||
field.setAttribute("id", `'${fieldId}'`);
|
||||
field.setAttribute("name", `'${fieldName}'`);
|
||||
field.setAttribute("record", params.recordExpr || "this.props.record");
|
||||
field.setAttribute("fieldInfo", `this.props.archInfo.fieldNodes['${fieldId}']`);
|
||||
field.setAttribute("record", params.recordExpr || "__comp__.props.record");
|
||||
field.setAttribute("fieldInfo", `__comp__.props.archInfo.fieldNodes['${fieldId}']`);
|
||||
|
||||
if (el.hasAttribute("widget")) {
|
||||
field.setAttribute("type", `'${el.getAttribute("widget")}'`);
|
||||
@@ -422,7 +424,7 @@ export class ViewCompiler {
|
||||
*/
|
||||
compileWidget(el) {
|
||||
const attrs = {};
|
||||
const props = { record: "this.props.record", readonly: this.ctx.readonly };
|
||||
const props = { record: "__comp__.props.record", readonly: this.ctx.readonly };
|
||||
for (const { name, value } of el.attributes) {
|
||||
switch (name) {
|
||||
case "class":
|
||||
|
||||
@@ -17,8 +17,11 @@ export class SettingsFormCompiler extends FormCompiler {
|
||||
|
||||
compileForm(el, params) {
|
||||
const settingsPage = createElement("SettingsPage");
|
||||
settingsPage.setAttribute("slots", "{NoContentHelper:this.props.slots.NoContentHelper}");
|
||||
settingsPage.setAttribute("initialTab", "this.props.initialApp");
|
||||
settingsPage.setAttribute(
|
||||
"slots",
|
||||
"{NoContentHelper:__comp__.props.slots.NoContentHelper}"
|
||||
);
|
||||
settingsPage.setAttribute("initialTab", "__comp__.props.initialApp");
|
||||
settingsPage.setAttribute("t-slot-scope", "settings");
|
||||
|
||||
//props
|
||||
@@ -91,7 +94,7 @@ export class SettingsFormCompiler extends FormCompiler {
|
||||
help: toStringExpression(el.getAttribute("help") || ""),
|
||||
companyDependent: el.getAttribute("company_dependent") === "1" || "false",
|
||||
documentation: toStringExpression(el.getAttribute("documentation") || ""),
|
||||
record: `this.props.record`,
|
||||
record: `__comp__.props.record`,
|
||||
});
|
||||
let string = toStringExpression(el.getAttribute("string") || "");
|
||||
let addLabel = true;
|
||||
|
||||
@@ -38,7 +38,7 @@ QUnit.module("Form Compiler", (hooks) => {
|
||||
const arch = /*xml*/ `<form><div>lol</div></form>`;
|
||||
const expected = /*xml*/ `
|
||||
<t>
|
||||
<div t-att-class="this.props.class" t-attf-class="{{this.props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-block {{ this.props.record.isDirty ? 'o_form_dirty' : !this.props.record.isVirtual ? 'o_form_saved' : '' }}" class="o_form_nosheet" t-ref="compiled_view_root">
|
||||
<div t-att-class="__comp__.props.class" t-attf-class="{{__comp__.props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-block {{ __comp__.props.record.isDirty ? 'o_form_dirty' : !__comp__.props.record.isVirtual ? 'o_form_saved' : '' }}" class="o_form_nosheet" t-ref="compiled_view_root">
|
||||
<div>lol</div>
|
||||
</div>
|
||||
</t>`;
|
||||
@@ -52,9 +52,9 @@ QUnit.module("Form Compiler", (hooks) => {
|
||||
const arch = /*xml*/ `<form><field name="test"/><label for="test" string=""/></form>`;
|
||||
const expected = /*xml*/ `
|
||||
<t>
|
||||
<div t-att-class="this.props.class" t-attf-class="{{this.props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-block {{ this.props.record.isDirty ? 'o_form_dirty' : !this.props.record.isVirtual ? 'o_form_saved' : '' }}" class="o_form_nosheet" t-ref="compiled_view_root">
|
||||
<Field id="'test'" name="'test'" record="this.props.record" fieldInfo="this.props.archInfo.fieldNodes['test']" />
|
||||
<FormLabel id="'test'" fieldName="'test'" record="this.props.record" fieldInfo="this.props.archInfo.fieldNodes['test']" className="""" string="\`\`" />
|
||||
<div t-att-class="__comp__.props.class" t-attf-class="{{__comp__.props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-block {{ __comp__.props.record.isDirty ? 'o_form_dirty' : !__comp__.props.record.isVirtual ? 'o_form_saved' : '' }}" class="o_form_nosheet" t-ref="compiled_view_root">
|
||||
<Field id="'test'" name="'test'" record="__comp__.props.record" fieldInfo="__comp__.props.archInfo.fieldNodes['test']" />
|
||||
<FormLabel id="'test'" fieldName="'test'" record="__comp__.props.record" fieldInfo="__comp__.props.archInfo.fieldNodes['test']" className="""" string="\`\`" />
|
||||
</div>
|
||||
</t>`;
|
||||
assert.areEquivalent(compileTemplate(arch), expected);
|
||||
@@ -65,10 +65,10 @@ QUnit.module("Form Compiler", (hooks) => {
|
||||
const arch = /*xml*/ `<form><div class="someClass">lol<field name="display_name"/></div></form>`;
|
||||
const expected = /*xml*/ `
|
||||
<t>
|
||||
<div t-att-class="this.props.class" t-attf-class="{{this.props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-block {{ this.props.record.isDirty ? 'o_form_dirty' : !this.props.record.isVirtual ? 'o_form_saved' : '' }}" class="o_form_nosheet" t-ref="compiled_view_root">
|
||||
<div t-att-class="__comp__.props.class" t-attf-class="{{__comp__.props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-block {{ __comp__.props.record.isDirty ? 'o_form_dirty' : !__comp__.props.record.isVirtual ? 'o_form_saved' : '' }}" class="o_form_nosheet" t-ref="compiled_view_root">
|
||||
<div class="someClass">
|
||||
lol
|
||||
<Field id="'display_name'" name="'display_name'" record="this.props.record" fieldInfo="this.props.archInfo.fieldNodes['display_name']"/>
|
||||
<Field id="'display_name'" name="'display_name'" record="__comp__.props.record" fieldInfo="__comp__.props.archInfo.fieldNodes['display_name']"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>`;
|
||||
@@ -88,15 +88,15 @@ QUnit.module("Form Compiler", (hooks) => {
|
||||
<OuterGroup>
|
||||
<t t-set-slot="item_0" type="'item'" sequence="0" t-slot-scope="scope" isVisible="true" itemSpan="1">
|
||||
<InnerGroup class="scope && scope.className">
|
||||
<t t-set-slot="item_0" type="'item'" sequence="0" t-slot-scope="scope" props="{id:'display_name',fieldName:'display_name',record:this.props.record,string:this.props.record.fields.display_name.string,fieldInfo:this.props.archInfo.fieldNodes['display_name']}" Component="this.constructor.components.FormLabel" subType="'item_component'" isVisible="true" itemSpan="2">
|
||||
<Field id="'display_name'" name="'display_name'" record="this.props.record" fieldInfo="this.props.archInfo.fieldNodes['display_name']" class="scope && scope.className" />
|
||||
<t t-set-slot="item_0" type="'item'" sequence="0" t-slot-scope="scope" props="{id:'display_name',fieldName:'display_name',record:__comp__.props.record,string:__comp__.props.record.fields.display_name.string,fieldInfo:__comp__.props.archInfo.fieldNodes['display_name']}" Component="__comp__.constructor.components.FormLabel" subType="'item_component'" isVisible="true" itemSpan="2">
|
||||
<Field id="'display_name'" name="'display_name'" record="__comp__.props.record" fieldInfo="__comp__.props.archInfo.fieldNodes['display_name']" class="scope && scope.className" />
|
||||
</t>
|
||||
</InnerGroup>
|
||||
</t>
|
||||
<t t-set-slot="item_1" type="'item'" sequence="1" t-slot-scope="scope" isVisible="true" itemSpan="1">
|
||||
<InnerGroup class="scope && scope.className">
|
||||
<t t-set-slot="item_0" type="'item'" sequence="0" t-slot-scope="scope" props="{id:'charfield',fieldName:'charfield',record:this.props.record,string:this.props.record.fields.charfield.string,fieldInfo:this.props.archInfo.fieldNodes['charfield']}" Component="this.constructor.components.FormLabel" subType="'item_component'" isVisible="true" itemSpan="2">
|
||||
<Field id="'charfield'" name="'charfield'" record="this.props.record" fieldInfo="this.props.archInfo.fieldNodes['charfield']" class="scope && scope.className" />
|
||||
<t t-set-slot="item_0" type="'item'" sequence="0" t-slot-scope="scope" props="{id:'charfield',fieldName:'charfield',record:__comp__.props.record,string:__comp__.props.record.fields.charfield.string,fieldInfo:__comp__.props.archInfo.fieldNodes['charfield']}" Component="__comp__.constructor.components.FormLabel" subType="'item_component'" isVisible="true" itemSpan="2">
|
||||
<Field id="'charfield'" name="'charfield'" record="__comp__.props.record" fieldInfo="__comp__.props.archInfo.fieldNodes['charfield']" class="scope && scope.className" />
|
||||
</t>
|
||||
</InnerGroup>
|
||||
</t>
|
||||
@@ -120,13 +120,13 @@ QUnit.module("Form Compiler", (hooks) => {
|
||||
</form>`;
|
||||
const expected = /*xml*/ `
|
||||
<t>
|
||||
<div t-att-class="this.props.class" t-attf-class="{{this.props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-block {{ this.props.record.isDirty ? 'o_form_dirty' : !this.props.record.isVirtual ? 'o_form_saved' : '' }}" class="o_form_nosheet" t-ref="compiled_view_root">
|
||||
<div t-att-class="__comp__.props.class" t-attf-class="{{__comp__.props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-block {{ __comp__.props.record.isDirty ? 'o_form_dirty' : !__comp__.props.record.isVirtual ? 'o_form_saved' : '' }}" class="o_form_nosheet" t-ref="compiled_view_root">
|
||||
<OuterGroup>
|
||||
<t t-set-slot="item_0" type="'item'" sequence="0" t-slot-scope="scope" isVisible="true" itemSpan="1">
|
||||
<InnerGroup class="scope && scope.className">
|
||||
<t t-set-slot="item_0" type="'item'" sequence="0" t-slot-scope="scope" isVisible="true" itemSpan="1">
|
||||
<div t-att-class="this.props.class" t-attf-class="{{this.props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-block {{ this.props.record.isDirty ? 'o_form_dirty' : !this.props.record.isVirtual ? 'o_form_saved' : '' }} {{scope && scope.className || "" }}" class="o_form_nosheet">
|
||||
<div><Field id="'test'" name="'test'" record="this.props.record" fieldInfo="this.props.archInfo.fieldNodes['test']"/></div>
|
||||
<div t-att-class="__comp__.props.class" t-attf-class="{{__comp__.props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-block {{ __comp__.props.record.isDirty ? 'o_form_dirty' : !__comp__.props.record.isVirtual ? 'o_form_saved' : '' }} {{scope && scope.className || "" }}" class="o_form_nosheet">
|
||||
<div><Field id="'test'" name="'test'" record="__comp__.props.record" fieldInfo="__comp__.props.archInfo.fieldNodes['test']"/></div>
|
||||
</div>
|
||||
</t>
|
||||
</InnerGroup>
|
||||
@@ -149,12 +149,12 @@ QUnit.module("Form Compiler", (hooks) => {
|
||||
</form>`;
|
||||
|
||||
const expected = /*xml*/ `
|
||||
<Notebook defaultPage="this.props.record.isNew ? undefined : this.props.activeNotebookPages[0]" onPageUpdate="(page) => this.props.onNotebookPageChange(0, page)">
|
||||
<Notebook defaultPage="__comp__.props.record.isNew ? undefined : __comp__.props.activeNotebookPages[0]" onPageUpdate="(page) => __comp__.props.onNotebookPageChange(0, page)">
|
||||
<t t-set-slot="page_1" title="\`Page1\`" name="\`p1\`" isVisible="true">
|
||||
<Field id="'charfield'" name="'charfield'" record="this.props.record" fieldInfo="this.props.archInfo.fieldNodes['charfield']"/>
|
||||
<Field id="'charfield'" name="'charfield'" record="__comp__.props.record" fieldInfo="__comp__.props.archInfo.fieldNodes['charfield']"/>
|
||||
</t>
|
||||
<t t-set-slot="page_2" title="\`Page2\`" name="\`p2\`" isVisible="true">
|
||||
<Field id="'display_name'" name="'display_name'" record="this.props.record" fieldInfo="this.props.archInfo.fieldNodes['display_name']"/>
|
||||
<Field id="'display_name'" name="'display_name'" record="__comp__.props.record" fieldInfo="__comp__.props.archInfo.fieldNodes['display_name']"/>
|
||||
</t>
|
||||
</Notebook>`;
|
||||
|
||||
@@ -168,7 +168,7 @@ QUnit.module("Form Compiler", (hooks) => {
|
||||
</form>`;
|
||||
|
||||
const expected = /*xml*/ `
|
||||
<Field id="'display_name'" name="'display_name'" record="this.props.record" fieldInfo="this.props.archInfo.fieldNodes['display_name']"/>
|
||||
<Field id="'display_name'" name="'display_name'" record="__comp__.props.record" fieldInfo="__comp__.props.archInfo.fieldNodes['display_name']"/>
|
||||
`;
|
||||
|
||||
assert.areContentEquivalent(compileTemplate(arch), expected);
|
||||
@@ -183,7 +183,7 @@ QUnit.module("Form Compiler", (hooks) => {
|
||||
|
||||
const expected = /*xml*/ `
|
||||
<t>
|
||||
<div t-att-class="this.props.class" t-attf-class="{{this.props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-block {{ this.props.record.isDirty ? 'o_form_dirty' : !this.props.record.isVirtual ? 'o_form_saved' : '' }}" class="o_form_nosheet" t-ref="compiled_view_root">
|
||||
<div t-att-class="__comp__.props.class" t-attf-class="{{__comp__.props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-block {{ __comp__.props.record.isDirty ? 'o_form_dirty' : !__comp__.props.record.isVirtual ? 'o_form_saved' : '' }}" class="o_form_nosheet" t-ref="compiled_view_root">
|
||||
<div class="o_form_statusbar position-relative d-flex justify-content-between border-bottom"><StatusBarButtons/></div>
|
||||
<div>someDiv</div>
|
||||
</div>
|
||||
@@ -205,7 +205,7 @@ QUnit.module("Form Compiler", (hooks) => {
|
||||
|
||||
const expected = /*xml*/ `
|
||||
<t>
|
||||
<div t-att-class="this.props.class" t-attf-class="{{this.props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-flex {{ this.uiService.size < 6 ? "flex-column" : "flex-nowrap h-100" }} {{ this.props.record.isDirty ? 'o_form_dirty' : !this.props.record.isVirtual ? 'o_form_saved' : '' }}" t-ref="compiled_view_root">
|
||||
<div t-att-class="__comp__.props.class" t-attf-class="{{__comp__.props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-flex {{ __comp__.uiService.size < 6 ? "flex-column" : "flex-nowrap h-100" }} {{ __comp__.props.record.isDirty ? 'o_form_dirty' : !__comp__.props.record.isVirtual ? 'o_form_saved' : '' }}" t-ref="compiled_view_root">
|
||||
<div class="o_form_sheet_bg">
|
||||
<div class="o_form_statusbar position-relative d-flex justify-content-between border-bottom"><StatusBarButtons/></div>
|
||||
<div>someDiv</div>
|
||||
@@ -239,7 +239,7 @@ QUnit.module("Form Compiler", (hooks) => {
|
||||
|
||||
const expected = /*xml*/ `
|
||||
<div class="visible3" />
|
||||
<div t-if="!this.evalDomainFromRecord(this.props.record,[["display_name","=","take"]])" />
|
||||
<div t-if="!__comp__.evalDomainFromRecord(__comp__.props.record,[["display_name","=","take"]])" />
|
||||
`;
|
||||
|
||||
assert.areContentEquivalent(compileTemplate(arch), expected);
|
||||
@@ -255,7 +255,7 @@ QUnit.module("Form Compiler", (hooks) => {
|
||||
|
||||
const expected = /*xml*/ `
|
||||
<div class="visible3" />
|
||||
<div t-if="!this.evalDomainFromRecord(this.props.record,"[['display_name','=','take']]")" />
|
||||
<div t-if="!__comp__.evalDomainFromRecord(__comp__.props.record,"[['display_name','=','take']]")" />
|
||||
`;
|
||||
assert.areContentEquivalent(compileTemplate(arch), expected);
|
||||
});
|
||||
@@ -444,7 +444,7 @@ QUnit.module("Form Renderer", (hooks) => {
|
||||
|
||||
const arch = `<myNode modifiers="{"invisible": [["field", "=", "value"]]}" />`;
|
||||
|
||||
const expected = `<t><div class="myNode" t-if="( myCondition or myOtherCondition ) and !this.evalDomainFromRecord(this.props.record,[["field","=","value"]])" t-ref="compiled_view_root"/></t>`;
|
||||
const expected = `<t><div class="myNode" t-if="( myCondition or myOtherCondition ) and !__comp__.evalDomainFromRecord(__comp__.props.record,[["field","=","value"]])" t-ref="compiled_view_root"/></t>`;
|
||||
assert.areEquivalent(compileTemplate(arch), expected);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1457,11 +1457,11 @@ QUnit.module("SettingsFormView", (hooks) => {
|
||||
);
|
||||
|
||||
const expectedCompiled = `
|
||||
<SettingsPage slots="{NoContentHelper:this.props.slots.NoContentHelper}" initialTab="this.props.initialApp" t-slot-scope="settings" modules="[{"key":"crm","string":"CRM","imgurl":"/crm/static/description/icon.png"}]">
|
||||
<SettingsPage slots="{NoContentHelper:__comp__.props.slots.NoContentHelper}" initialTab="__comp__.props.initialApp" t-slot-scope="settings" modules="[{"key":"crm","string":"CRM","imgurl":"/crm/static/description/icon.png"}]">
|
||||
<SettingsApp key="\`crm\`" string="\`CRM\`" imgurl="\`/crm/static/description/icon.png\`" selectedTab="settings.selectedTab">
|
||||
<Setting title="\`\`" help="\`\`" companyDependent="false" documentation="\`\`" record="this.props.record" string="\`\`" addLabel="true" labels="["\`My\\" little ' Label\`"]">
|
||||
<FormLabel id="'display_name'" fieldName="'display_name'" record="this.props.record" fieldInfo="this.props.archInfo.fieldNodes['display_name']" className=""highhopes"" string="\`My" little ' Label\`"/>
|
||||
<Field id="'display_name'" name="'display_name'" record="this.props.record" fieldInfo="this.props.archInfo.fieldNodes['display_name']"/>
|
||||
<Setting title="\`\`" help="\`\`" companyDependent="false" documentation="\`\`" record="__comp__.props.record" string="\`\`" addLabel="true" labels="["\`My\\" little ' Label\`"]">
|
||||
<FormLabel id="'display_name'" fieldName="'display_name'" record="__comp__.props.record" fieldInfo="__comp__.props.archInfo.fieldNodes['display_name']" className=""highhopes"" string="\`My" little ' Label\`"/>
|
||||
<Field id="'display_name'" name="'display_name'" record="__comp__.props.record" fieldInfo="__comp__.props.archInfo.fieldNodes['display_name']"/>
|
||||
</Setting>
|
||||
</SettingsApp>
|
||||
</SettingsPage>`;
|
||||
@@ -1502,7 +1502,7 @@ QUnit.module("SettingsFormView", (hooks) => {
|
||||
|
||||
const expectedCompiled = `
|
||||
<HighlightText originalText="\`this is Baz value: \`"/>
|
||||
<Field id="'baz'" name="'baz'" record="this.props.record" fieldInfo="this.props.archInfo.fieldNodes['baz']"/>
|
||||
<Field id="'baz'" name="'baz'" record="__comp__.props.record" fieldInfo="__comp__.props.archInfo.fieldNodes['baz']"/>
|
||||
<HighlightText originalText="\` and this is the after text\`"/>`;
|
||||
assert.areEquivalent(
|
||||
compiled.querySelector("Setting div.text-muted").innerHTML,
|
||||
|
||||
@@ -34,6 +34,12 @@ _logger = logging.getLogger(__name__)
|
||||
|
||||
MOVABLE_BRANDING = ['data-oe-model', 'data-oe-id', 'data-oe-field', 'data-oe-xpath', 'data-oe-source-id']
|
||||
|
||||
# Some views have a js compiler that generates an owl template from the arch. In that template,
|
||||
# `__comp__` is a reserved keyword giving access to the component instance (e.g. the form renderer
|
||||
# or the kanban record). However, we don't want to see implementation details leaking in archs, so
|
||||
# we use the following regex to detect the use of `__comp__` in dynamic attributes, to forbid it.
|
||||
COMP_REGEX = r'(^|[^\w])\s*__comp__\s*([^\w]|$)'
|
||||
|
||||
ref_re = re.compile(r"""
|
||||
# first match 'form_view_ref' key, backrefs are used to handle single or
|
||||
# double quoting of the value
|
||||
@@ -1826,6 +1832,8 @@ actual arch.
|
||||
|
||||
elif (attr.startswith("t-")):
|
||||
self._validate_qweb_directive(node, attr, node_info["view_type"])
|
||||
if (re.search(COMP_REGEX, expr)):
|
||||
self._raise_view_error(_("Forbidden use of `__comp__` in arch."), node)
|
||||
|
||||
def _validate_classes(self, node, expr):
|
||||
""" Validate the classes present on node. """
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import ast
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import time
|
||||
|
||||
from functools import partial
|
||||
@@ -16,7 +17,7 @@ from odoo.exceptions import AccessError, ValidationError
|
||||
from odoo.tests import common, tagged
|
||||
from odoo.tools import get_cache_key_counter, mute_logger, view_validation
|
||||
from odoo.addons.base.models.ir_ui_view import (
|
||||
transfer_field_to_modifiers, transfer_node_to_modifiers, simplify_modifiers,
|
||||
transfer_field_to_modifiers, transfer_node_to_modifiers, simplify_modifiers, COMP_REGEX
|
||||
)
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
@@ -3401,6 +3402,17 @@ Forbidden attribute used in arch (t-att-data-tooltip)."""
|
||||
Forbidden attribute used in arch (t-attf-data-tooltip-template)."""
|
||||
)
|
||||
|
||||
@mute_logger('odoo.addons.base.models.ir_ui_view')
|
||||
def test_forbidden_use_of___comp___in_kanban(self):
|
||||
arch = "<kanban><templates><t t-name='kanban-box'>%s</t></templates></kanban>"
|
||||
self.assertInvalid(
|
||||
arch % '<t t-esc="__comp__.props.resId"/>',
|
||||
"""Error while validating view near:
|
||||
|
||||
<kanban __validate__="1"><templates><t t-name="kanban-box"><t t-esc="__comp__.props.resId"/></t></templates></kanban>
|
||||
Forbidden use of `__comp__` in arch."""
|
||||
)
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestDebugger(common.TransactionCase):
|
||||
@@ -4147,3 +4159,25 @@ class TestRenderAllViews(common.TransactionCase):
|
||||
|
||||
_logger.info('Rendered %d views as %s using (best of 5) %ss',
|
||||
count, self.env.user.name, elapsed)
|
||||
|
||||
|
||||
class CompRegexTest(common.TransactionCase):
|
||||
def test_comp_regex(self):
|
||||
self.assertIsNone(re.search(COMP_REGEX, ""))
|
||||
self.assertIsNone(re.search(COMP_REGEX, "__comp__2"))
|
||||
self.assertIsNone(re.search(COMP_REGEX, "__comp___that"))
|
||||
self.assertIsNone(re.search(COMP_REGEX, "a__comp__"))
|
||||
|
||||
self.assertIsNotNone(re.search(COMP_REGEX, "__comp__"))
|
||||
self.assertIsNotNone(re.search(COMP_REGEX, "__comp__ "))
|
||||
self.assertIsNotNone(re.search(COMP_REGEX, " __comp__ "))
|
||||
self.assertIsNotNone(re.search(COMP_REGEX, "__comp__.props"))
|
||||
self.assertIsNotNone(re.search(COMP_REGEX, "__comp__ .props"))
|
||||
self.assertIsNotNone(re.search(COMP_REGEX, "__comp__['props']"))
|
||||
self.assertIsNotNone(re.search(COMP_REGEX, "__comp__ ['props']"))
|
||||
self.assertIsNotNone(re.search(COMP_REGEX, "__comp__[\"props\"]"))
|
||||
self.assertIsNotNone(re.search(COMP_REGEX, "__comp__ [\"props\"]"))
|
||||
self.assertIsNotNone(re.search(COMP_REGEX, " __comp__ [\"props\"] "))
|
||||
self.assertIsNotNone(re.search(COMP_REGEX, "record ? __comp__ : false"))
|
||||
self.assertIsNotNone(re.search(COMP_REGEX, "!__comp__.props.resId"))
|
||||
self.assertIsNotNone(re.search(COMP_REGEX, "{{ __comp__ }}"))
|
||||
|
||||
Reference in New Issue
Block a user