From ce774f1322f849e2f7b079329e95c3ef632a0955 Mon Sep 17 00:00:00 2001 From: FrancoisGe Date: Tue, 1 Aug 2023 14:38:53 +0000 Subject: [PATCH] [FIX] web: complete x2m activeField The goal of this commit is to complete the list of activeFields for an x2many in list mode when a record is opened with the fields present in its form view. This ensures that there is always an activeField for all the data in a record. Before this commit, when the x2many's form view was not inline, when the form view was closed, the fields not present in the list view no longer had an activeField, which could cause crashes. closes odoo/odoo#130453 Signed-off-by: Aaron Bohy (aab) --- .../src/model/relational_model/static_list.js | 2 + .../src/model/relational_model/utils.js | 32 ++--- .../views/fields/one2many_field_tests.js | 113 +++++++++++++++++- 3 files changed, 131 insertions(+), 16 deletions(-) diff --git a/addons/web/static/src/model/relational_model/static_list.js b/addons/web/static/src/model/relational_model/static_list.js index 30788de8ae5..8935dcae513 100644 --- a/addons/web/static/src/model/relational_model/static_list.js +++ b/addons/web/static/src/model/relational_model/static_list.js @@ -3,6 +3,7 @@ import { x2ManyCommands } from "@web/core/orm_service"; import { intersection } from "@web/core/utils/arrays"; import { pick } from "@web/core/utils/objects"; +import { completeActiveFields } from "@web/model/relational_model/utils"; import { DataPoint } from "./datapoint"; import { fromUnityToServerValues, getId, patchActiveFields } from "./utils"; @@ -202,6 +203,7 @@ export class StaticList extends DataPoint { extendRecord(params, record) { return this.model.mutex.exec(async () => { // extend fields and activeFields of the list with those given in params + completeActiveFields(this.config.activeFields, params.activeFields); Object.assign(this.fields, params.fields); const activeFields = { ...params.activeFields }; for (const fieldName in this.activeFields) { diff --git a/addons/web/static/src/model/relational_model/utils.js b/addons/web/static/src/model/relational_model/utils.js index c64ce138c35..d4e9c5f522e 100644 --- a/addons/web/static/src/model/relational_model/utils.js +++ b/addons/web/static/src/model/relational_model/utils.js @@ -70,6 +70,20 @@ function completeActiveField(activeField, extra) { } } +export function completeActiveFields(activeFields, extraActiveFields) { + for (const fieldName in extraActiveFields) { + const extraActiveField = { + ...extraActiveFields[fieldName], + invisible: true, + }; + if (fieldName in activeFields) { + completeActiveField(activeFields[fieldName], extraActiveField); + } else { + activeFields[fieldName] = extraActiveField; + } + } +} + export function createPropertyActiveField(property) { const { type } = property; @@ -182,20 +196,10 @@ export function extractFieldsFromArchInfo({ fieldNodes, widgetNodes }, fields) { fieldNode.views.form, fieldNode.views.form.fields ); - for (const fieldName in formArchInfo.activeFields) { - const formActiveField = { - ...formArchInfo.activeFields[fieldName], - invisible: true, - }; - if (fieldName in activeField.related.activeFields) { - completeActiveField( - activeField.related.activeFields[fieldName], - formActiveField - ); - } else { - activeField.related.activeFields[fieldName] = formActiveField; - } - } + completeActiveFields( + activeField.related.activeFields, + formArchInfo.activeFields + ); Object.assign(activeField.related.fields, formArchInfo.fields); } } diff --git a/addons/web/static/tests/views/fields/one2many_field_tests.js b/addons/web/static/tests/views/fields/one2many_field_tests.js index cb6887a8923..363b5255fea 100644 --- a/addons/web/static/tests/views/fields/one2many_field_tests.js +++ b/addons/web/static/tests/views/fields/one2many_field_tests.js @@ -546,7 +546,9 @@ QUnit.module("Fields", (hooks) => { await editInput(target, ".o_field_widget[name=parent_id] input", "ABC"); await clickOpenedDropdownItem(target, "parent_id", "Create and edit..."); await click( - target.querySelector(".o_dialog:not(.o_inactive_modal) .modal-footer .o_form_button_save") + target.querySelector( + ".o_dialog:not(.o_inactive_modal) .modal-footer .o_form_button_save" + ) ); assert.strictEqual( target @@ -558,7 +560,9 @@ QUnit.module("Fields", (hooks) => { await nextTick(); // close all dialogs await click( - target.querySelector(".o_dialog:not(.o_inactive_modal) .modal-footer .o_form_button_save") + target.querySelector( + ".o_dialog:not(.o_inactive_modal) .modal-footer .o_form_button_save" + ) ); await nextTick(); assert.containsNone(target, ".o_dialog .o_form_view"); @@ -3216,6 +3220,111 @@ QUnit.module("Fields", (hooks) => { } ); + QUnit.test( + "onchange specification complete after open sub form view not inline", + async function (assert) { + serverData.models.partner.onchanges.display_name = () => {}; + serverData.views = { + "turtle,false,form": ` +
+ + + + + + + `, + }; + + await makeView({ + type: "form", + resModel: "partner", + serverData, + arch: ` +
+ + + + + + + `, + resId: 1, + async mockRPC(route, args) { + if (args.method === "onchange2" && args.model === "partner") { + if (args.args[1].display_name === "test") { + assert.deepEqual(args.args[3], { + display_name: {}, + turtles: { + fields: { + turtle_foo: {}, + }, + limit: 40, + order: "", + }, + }); + } else if (args.args[1].display_name === "test2") { + assert.deepEqual(args.args[3], { + display_name: {}, + turtles: { + fields: { + display_name: {}, + partner_ids: { + fields: { + display_name: {}, + }, + limit: 40, + order: "", + }, + turtle_foo: {}, + }, + limit: 40, + order: "", + }, + }); + return { + value: { + turtles: [ + [ + 1, + 2, + { + display_name: "yop", + partner_ids: [[1, 2, { display_name: "plop" }]], + }, + ], + ], + }, + }; + } + } + }, + }); + await editInput(target, "div[name='display_name'] input", "test"); + await click(target.querySelector(".o_data_row .o_data_cell")); + assert.strictEqual( + target.querySelector(".modal [name='display_name'] input").value, + "donatello" + ); + assert.deepEqual( + [...target.querySelectorAll(".modal .o_data_row")].map((el) => el.textContent), + ["second record", "aaa"] + ); + + await click(target.querySelector(".modal .o_form_button_save")); + await editInput(target, "div[name='display_name'] input", "test2"); + await click(target.querySelector(".o_data_row .o_data_cell")); + assert.strictEqual( + target.querySelector(".modal [name='display_name'] input").value, + "yop" + ); + assert.deepEqual( + [...target.querySelectorAll(".modal .o_data_row")].map((el) => el.textContent), + ["plop", "aaa"] + ); + } + ); + QUnit.test("sorting one2many fields", async function (assert) { serverData.models.partner.fields.foo.sortable = true; serverData.models.partner.records.push({ id: 23, foo: "abc", int_field: 1 });