diff --git a/addons/web/static/src/legacy/legacy_fields.js b/addons/web/static/src/legacy/legacy_fields.js index c0be503acef..aca270272b3 100644 --- a/addons/web/static/src/legacy/legacy_fields.js +++ b/addons/web/static/src/legacy/legacy_fields.js @@ -340,6 +340,7 @@ function registerField(name, LegacyFieldWidget) { LegacyField.template = legacyFieldTemplate; LegacyField.components = { FieldAdapter }; LegacyField.fieldsToFetch = LegacyFieldWidget.prototype.fieldsToFetch || {}; + LegacyField.fieldDependencies = LegacyFieldWidget.prototype.fieldDependencies || {}; if (!fieldRegistry.contains(name)) { fieldRegistry.add(name, LegacyField); } diff --git a/addons/web/static/src/views/form/form_arch_parser.js b/addons/web/static/src/views/form/form_arch_parser.js index 790995ef9e1..0ac621be33a 100644 --- a/addons/web/static/src/views/form/form_arch_parser.js +++ b/addons/web/static/src/views/form/form_arch_parser.js @@ -1,8 +1,8 @@ /** @odoo-module **/ -import { XMLParser } from "@web/core/utils/xml"; +import { addFieldDependencies, archParseBoolean, getActiveActions } from "@web/views/utils"; import { Field } from "@web/views/fields/field"; -import { archParseBoolean, getActiveActions } from "@web/views/utils"; +import { XMLParser } from "@web/core/utils/xml"; import { Widget } from "@web/views/widgets/widget"; export class FormArchParser extends XMLParser { @@ -29,18 +29,14 @@ export class FormArchParser extends XMLParser { if (archParseBoolean(node.getAttribute("default_focus") || "")) { autofocusFieldId = fieldId; } + addFieldDependencies(activeFields, fieldInfo.FieldComponent.fieldDependencies); return false; } else if (node.tagName === "div" && node.classList.contains("oe_chatter")) { // remove this when chatter fields are declared as attributes on the root node return false; } else if (node.tagName === "widget") { - const widgetInfo = Widget.parseWidgetNode(node); - for (const [name, field] of Object.entries(widgetInfo.fieldDependencies)) { - activeFields[name] = { - name, - type: field.type, - }; - } + const { WidgetComponent } = Widget.parseWidgetNode(node); + addFieldDependencies(activeFields, WidgetComponent.fieldDependencies); } }); // TODO: generate activeFields for the model based on fieldNodes (merge duplicated fields) diff --git a/addons/web/static/src/views/kanban/kanban_arch_parser.js b/addons/web/static/src/views/kanban/kanban_arch_parser.js index 4d8fe0ec24d..f2b03778afb 100644 --- a/addons/web/static/src/views/kanban/kanban_arch_parser.js +++ b/addons/web/static/src/views/kanban/kanban_arch_parser.js @@ -1,8 +1,13 @@ /** @odoo-module **/ +import { + addFieldDependencies, + archParseBoolean, + getActiveActions, + stringToOrderBy, +} from "@web/views/utils"; import { extractAttributes, XMLParser } from "@web/core/utils/xml"; import { Field } from "@web/views/fields/field"; -import { archParseBoolean, getActiveActions, stringToOrderBy } from "@web/views/utils"; import { Widget } from "@web/views/widgets/widget"; /** @@ -52,7 +57,6 @@ export class KanbanArchParser extends XMLParser { const openAction = action && type ? { action, type } : null; const templateDocs = {}; const activeFields = {}; - // Root level of the template this.visitXML(xmlDoc, (node) => { if (node.hasAttribute("t-name")) { @@ -76,15 +80,11 @@ export class KanbanArchParser extends XMLParser { if (fieldInfo.widget === "handle") { handleField = name; } + addFieldDependencies(activeFields, fieldInfo.FieldComponent.fieldDependencies); } if (node.tagName === "widget") { - const widgetInfo = Widget.parseWidgetNode(node); - for (const [name, field] of Object.entries(widgetInfo.fieldDependencies)) { - activeFields[name] = { - name, - type: field.type, - }; - } + const { WidgetComponent } = Widget.parseWidgetNode(node); + addFieldDependencies(activeFields, WidgetComponent.fieldDependencies); } // Keep track of last update so images can be reloaded when they may have changed. diff --git a/addons/web/static/src/views/list/list_arch_parser.js b/addons/web/static/src/views/list/list_arch_parser.js index c1b4e22b978..96ea4d47783 100644 --- a/addons/web/static/src/views/list/list_arch_parser.js +++ b/addons/web/static/src/views/list/list_arch_parser.js @@ -1,15 +1,15 @@ /** @odoo-module */ -import { XMLParser } from "@web/core/utils/xml"; -import { Field } from "@web/views/fields/field"; import { + addFieldDependencies, archParseBoolean, getActiveActions, getDecoration, processButton, stringToOrderBy, } from "@web/views/utils"; -import { Widget } from "@web/views/widgets/widget"; +import { Field } from "@web/views/fields/field"; +import { XMLParser } from "@web/core/utils/xml"; export class GroupListArchParser extends XMLParser { parse(arch, models, modelName, jsClass) { @@ -84,6 +84,7 @@ export class ListArchParser extends XMLParser { if (fieldInfo.widget === "handle") { handleField = fieldInfo.name; } + addFieldDependencies(activeFields, fieldInfo.FieldComponent.fieldDependencies); if (!invisible || !archParseBoolean(invisible)) { const displayName = fieldInfo.FieldComponent.displayName; columns.push({ @@ -99,15 +100,6 @@ export class ListArchParser extends XMLParser { }); } return false; - } else if (node.tagName === "widget") { - const widgetInfo = Widget.parseWidgetNode(node); - for (const [name, field] of Object.entries(widgetInfo.fieldDependencies)) { - activeFields[name] = { - name, - type: field.type, - }; - } - return false; } else if (node.tagName === "groupby" && node.getAttribute("name")) { const fieldName = node.getAttribute("name"); const xmlSerializer = new XMLSerializer(); diff --git a/addons/web/static/src/views/utils.js b/addons/web/static/src/views/utils.js index 88b5bff104c..a1295c68205 100644 --- a/addons/web/static/src/views/utils.js +++ b/addons/web/static/src/views/utils.js @@ -7,6 +7,22 @@ export const X2M_TYPES = ["one2many", "many2many"]; const RELATIONAL_TYPES = [...X2M_TYPES, "many2one"]; const NUMERIC_TYPES = ["integer", "float", "monetary"]; +/** + * Add dependencies to activeFields + * + * @param {Object} activeFields + * @param {Object} [dependencies={}] + */ +export function addFieldDependencies(activeFields, dependencies = {}) { + for (const [name, dependency] of Object.entries(dependencies)) { + if (!(name in activeFields)) { + activeFields[name] = Object.assign({ name, rawAttrs: {} }, dependency, { + modifiers: { invisible: true }, + }); + } + } +} + /** * Parse the arch to check if is true or false * If the string is empty, 0, False or false it's considered as false diff --git a/addons/web/static/src/views/widgets/widget.js b/addons/web/static/src/views/widgets/widget.js index 361383d311c..58c1b3a0792 100644 --- a/addons/web/static/src/views/widgets/widget.js +++ b/addons/web/static/src/views/widgets/widget.js @@ -43,10 +43,6 @@ Widget.template = xml/*xml*/ ` Widget.parseWidgetNode = function (node) { const name = node.getAttribute("name"); - const component = findWidgetComponent(name); - return { - name, - component, - fieldDependencies: { ...component.fieldDependencies }, - }; + const WidgetComponent = findWidgetComponent(name); + return { WidgetComponent }; }; diff --git a/addons/web/static/tests/views/form/form_view_tests.js b/addons/web/static/tests/views/form/form_view_tests.js index 219f1694b4f..ca1e0500220 100644 --- a/addons/web/static/tests/views/form/form_view_tests.js +++ b/addons/web/static/tests/views/form/form_view_tests.js @@ -35,6 +35,8 @@ const fieldRegistry = registry.category("fields"); const serviceRegistry = registry.category("services"); const widgetRegistry = registry.category("view_widgets"); +const { Component, xml } = owl; + let target; let serverData; @@ -10962,4 +10964,61 @@ QUnit.module("Views", (hooks) => { await click(target.querySelector(".o_form_button_save")); // save the record } ); + + QUnit.test("fieldDependencies support for fields", async (assert) => { + serverData.models.partner.records = [{ id: 1, int_field: 2 }]; + + class CustomField extends Component {} + CustomField.fieldDependencies = { + int_field: { type: "integer" }, + }; + CustomField.template = xml``; + registry.category("fields").add("custom_field", CustomField); + + await makeView({ + type: "form", + resModel: "partner", + resId: 1, + arch: ` +
+ `, + serverData, + }); + + assert.strictEqual(target.querySelector("[name=foo] span").innerText, "2"); + }); + + QUnit.test( + "fieldDependencies support for fields: dependence on a relational field", + async (assert) => { + serverData.models.partner.records[0].product_id = 37; + + class CustomField extends Component {} + CustomField.fieldDependencies = { + product_id: { type: "many2one", relation: "product" }, + }; + CustomField.template = xml``; + registry.category("fields").add("custom_field", CustomField); + + await makeView({ + type: "form", + resModel: "partner", + resId: 1, + arch: ` + + `, + serverData, + mockRPC: (route, args) => { + assert.step(args.method); + }, + }); + + assert.strictEqual(target.querySelector("[name=foo] span").innerText, "xphone"); + assert.verifySteps(["get_views", "read"]); + } + ); }); diff --git a/addons/web/static/tests/views/kanban_view_tests.js b/addons/web/static/tests/views/kanban_view_tests.js index 69336ad13bf..67ced7bbc1c 100644 --- a/addons/web/static/tests/views/kanban_view_tests.js +++ b/addons/web/static/tests/views/kanban_view_tests.js @@ -10660,4 +10660,67 @@ QUnit.module("Views", (hooks) => { assert.verifySteps(["/mybody/isacage"]); assert.containsOnce(target, ".setmybodyfree"); }); + + QUnit.test("fieldDependencies support for fields", async (assert) => { + class CustomField extends Component {} + CustomField.fieldDependencies = { + int_field: { type: "integer" }, + }; + CustomField.template = xml``; + registry.category("fields").add("custom_field", CustomField); + + await makeView({ + resModel: "partner", + type: "kanban", + arch: ` +