diff --git a/addons/web/static/src/model/relational_model/utils.js b/addons/web/static/src/model/relational_model/utils.js index db14c579185..8b21df9a4fc 100644 --- a/addons/web/static/src/model/relational_model/utils.js +++ b/addons/web/static/src/model/relational_model/utils.js @@ -225,6 +225,22 @@ export function extractFieldsFromArchInfo({ fieldNodes, widgetNodes }, fields) { ); Object.assign(activeField.related.fields, formArchInfo.fields); } + + if (fieldNode.viewMode !== "default" && fieldNode.views.default) { + const defaultArchInfo = extractFieldsFromArchInfo( + fieldNode.views.default, + fieldNode.views.default.fields + ); + activeField.related.activeFields = { + ...defaultArchInfo.activeFields, + ...activeField.related.activeFields, + }; + activeField.related.fields = Object.assign( + {}, + defaultArchInfo.fields, + activeField.related.fields + ); + } } } if (fieldNode.field?.useSubView) { diff --git a/addons/web/static/src/views/fields/field.js b/addons/web/static/src/views/fields/field.js index dec588ccf9c..18b4ba4a471 100644 --- a/addons/web/static/src/views/fields/field.js +++ b/addons/web/static/src/views/fields/field.js @@ -312,39 +312,47 @@ Field.parseFieldNode = function (node, models, modelName, viewType, jsClass) { if (relatedFields instanceof Function) { relatedFields = relatedFields(fieldInfo); } + for (const relatedField of relatedFields) { + if (!("readonly" in relatedField)) { + relatedField.readonly = true; + } + } relatedFields = Object.fromEntries(relatedFields.map((f) => [f.name, f])); views.default = { fieldNodes: relatedFields, fields: relatedFields }; - fieldInfo.viewMode = "default"; - } else { - for (const child of node.children) { - const viewType = child.tagName === "tree" ? "list" : child.tagName; - const { ArchParser } = viewRegistry.get(viewType); - const xmlSerializer = new XMLSerializer(); - const subArch = xmlSerializer.serializeToString(child); - const archInfo = new ArchParser().parse(subArch, models, fields[name].relation); - views[viewType] = { - ...archInfo, - limit: archInfo.limit || 40, - fields: models[fields[name].relation], - }; + if (!fieldInfo.field.useSubView) { + fieldInfo.viewMode = "default"; } + } + for (const child of node.children) { + const viewType = child.tagName === "tree" ? "list" : child.tagName; + const { ArchParser } = viewRegistry.get(viewType); + const xmlSerializer = new XMLSerializer(); + const subArch = xmlSerializer.serializeToString(child); + const archInfo = new ArchParser().parse(subArch, models, fields[name].relation); + views[viewType] = { + ...archInfo, + limit: archInfo.limit || 40, + fields: models[fields[name].relation], + }; + } - let viewMode = node.getAttribute("mode"); - if (!viewMode) { - if (views.list && !views.kanban) { - viewMode = "list"; - } else if (!views.list && views.kanban) { - viewMode = "kanban"; - } else if (views.list && views.kanban) { - viewMode = isSmall() ? "kanban" : "list"; - } + let viewMode = node.getAttribute("mode"); + if (viewMode) { + if (viewMode.split(",").length !== 1) { + viewMode = isSmall() ? "kanban" : "list"; } else { - if (viewMode.split(",").length !== 1) { - viewMode = isSmall() ? "kanban" : "list"; - } else { - viewMode = viewMode === "tree" ? "list" : viewMode; - } + viewMode = viewMode === "tree" ? "list" : viewMode; } + } else { + if (views.list && !views.kanban) { + viewMode = "list"; + } else if (!views.list && views.kanban) { + viewMode = "kanban"; + } else if (views.list && views.kanban) { + viewMode = isSmall() ? "kanban" : "list"; + } + } + if (viewMode) { fieldInfo.viewMode = viewMode; } if (Object.keys(views).length) { diff --git a/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.js b/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.js index 84bc05ffb98..60317428e57 100644 --- a/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.js +++ b/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.js @@ -274,7 +274,7 @@ export const many2ManyTagsField = { relatedFields: ({ options }) => { const relatedFields = [{ name: "display_name", type: "char" }]; if (options.color_field) { - relatedFields.push({ name: options.color_field, type: "integer" }); + relatedFields.push({ name: options.color_field, type: "integer", readonly: false }); } return relatedFields; }, 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 4f5e09f28eb..4bdfd251c98 100644 --- a/addons/web/static/tests/views/form/form_view_tests.js +++ b/addons/web/static/tests/views/form/form_view_tests.js @@ -26,8 +26,8 @@ import { } from "@web/../tests/helpers/utils"; import { toggleActionMenu, - toggleSearchBarMenu, toggleMenuItem, + toggleSearchBarMenu, } from "@web/../tests/search/helpers"; import { makeView, makeViewInDialog, setupViewRegistries } from "@web/../tests/views/helpers"; import { createWebClient, doAction } from "@web/../tests/webclient/helpers"; @@ -41,10 +41,11 @@ import { tooltipService } from "@web/core/tooltip/tooltip_service"; import { SIZES } from "@web/core/ui/ui_service"; import { useService } from "@web/core/utils/hooks"; import { session } from "@web/session"; -import { Field } from "@web/views/fields/field"; import { CharField } from "@web/views/fields/char/char_field"; import { DateTimeField } from "@web/views/fields/datetime/datetime_field"; +import { Field } from "@web/views/fields/field"; import { IntegerField } from "@web/views/fields/integer/integer_field"; +import { X2ManyField, x2ManyField } from "@web/views/fields/x2many/x2many_field"; import { FormController } from "@web/views/form/form_controller"; import { companyService } from "@web/webclient/company_service"; @@ -13985,4 +13986,122 @@ QUnit.module("Views", (hooks) => { assert.equal(target.querySelectorAll(".o_data_cell")[0].innerText, "updated"); } ); + + QUnit.test("custom x2many with relatedFields and list view inline", async function (assert) { + class MyField extends X2ManyField {} + fieldRegistry.add("my_widget", { + ...x2ManyField, + component: MyField, + relatedFields: [ + { name: "trululu", type: "many2one", relation: "partner" }, + { name: "int_field", type: "integer" }, + ], + }); + + await makeView({ + type: "form", + resModel: "partner", + serverData, + arch: ` +
+ + + + + + +
`, + resId: 2, + mockRPC(route, args) { + if (args.method === "web_read") { + assert.step("web_read"); + assert.deepEqual(args.kwargs.specification.p.fields, { + trululu: { fields: { display_name: {} } }, + foo: {}, + int_field: {}, + }); + } else if (args.method === "write") { + assert.step("write"); + assert.deepEqual(args.args[1].p[0][2], { + foo: "new record", + int_field: 0, + }); + } else if (args.method === "web_save") { + assert.step("web_save"); + assert.deepEqual(args.kwargs.specification.p.fields, { + trululu: { fields: { display_name: {} } }, + foo: {}, + int_field: {}, + }); + } + }, + }); + + await addRow(target); + await editInput(target, ".o_data_row [name='foo'] input", "new record"); + await clickSave(target); + assert.verifySteps(["web_read", "web_save"]); + }); + + QUnit.test( + "custom x2many with relatedFields and list view not inline", + async function (assert) { + class MyField extends X2ManyField {} + fieldRegistry.add("my_widget", { + ...x2ManyField, + component: MyField, + relatedFields: [ + { name: "trululu", type: "many2one", relation: "partner" }, + { name: "int_field", type: "integer" }, + ], + }); + + serverData.views = { + "partner,false,list": ` + + + + `, + }; + + await makeView({ + type: "form", + resModel: "partner", + serverData, + arch: ` +
+ + `, + resId: 2, + mockRPC(route, args) { + if (args.method === "web_read") { + assert.step("web_read"); + assert.deepEqual(args.kwargs.specification.p.fields, { + trululu: { fields: { display_name: {} } }, + foo: {}, + int_field: {}, + }); + } else if (args.method === "write") { + assert.step("write"); + assert.deepEqual(args.args[1].p[0][2], { + foo: "new record", + int_field: 0, + }); + } else if (args.method === "web_save") { + assert.step("web_save"); + assert.deepEqual(args.kwargs.specification.p.fields, { + trululu: { fields: { display_name: {} } }, + foo: {}, + int_field: {}, + }); + } + }, + }); + + await addRow(target); + await editInput(target, ".o_data_row [name='foo'] input", "new record"); + await clickSave(target); + assert.verifySteps(["web_read", "web_save"]); + } + ); });