From f10d5b665d9eabe7687694ca8ab55a060b1615d3 Mon Sep 17 00:00:00 2001 From: FrancoisGe Date: Tue, 2 Aug 2022 07:19:23 +0000 Subject: [PATCH] [FIX] web: leave a listView with an empty new record Before this commit, in a list view, if you create a new record without modifying it and then exit the action then your new empty record will be saved. This causes two problems: - you don't want to save a new empty record - you get a "View can't be saved" error if there is a require field The solution is to abandon new invalid records if they have not yet been modified. closes odoo/odoo#97169 Signed-off-by: Aaron Bohy (aab) --- .../src/views/basic_relational_model.js | 15 +++--- .../static/src/views/list/list_controller.js | 5 +- .../web/static/src/views/relational_model.js | 21 ++++---- .../web/static/tests/views/list_view_tests.js | 50 +++++++++++++++++++ 4 files changed, 72 insertions(+), 19 deletions(-) diff --git a/addons/web/static/src/views/basic_relational_model.js b/addons/web/static/src/views/basic_relational_model.js index 75f174b8ae6..bb855ab51eb 100644 --- a/addons/web/static/src/views/basic_relational_model.js +++ b/addons/web/static/src/views/basic_relational_model.js @@ -372,11 +372,11 @@ export class Record extends DataPoint { async switchMode(mode) { if (this.mode === mode) { - return; + return true; } - const preventSwitch = await this._onWillSwitchMode(this, mode); - if (preventSwitch === false) { - return; + const canSwitch = await this._onWillSwitchMode(this, mode); + if (canSwitch === false) { + return false; } if (mode === "edit") { @@ -397,6 +397,7 @@ export class Record extends DataPoint { } this.mode = mode; this.model.notify(); + return true; } /** @@ -1094,9 +1095,9 @@ export class StaticList extends DataPoint { } this.__syncData(); this.editedRecord = null; - await editedRecord.switchMode("readonly"); + return await editedRecord.switchMode("readonly"); } else if (await editedRecord.checkValidity()) { - await editedRecord.switchMode("readonly"); + return await editedRecord.switchMode("readonly"); } else { if (!editedRecord.isDirty) { this.model.__bm__.discardChanges(handle); @@ -1104,7 +1105,7 @@ export class StaticList extends DataPoint { this.model.__bm__.removeLine(handle); } this.editedRecord = null; - await editedRecord.switchMode("readonly"); + return await editedRecord.switchMode("readonly"); } return false; } diff --git a/addons/web/static/src/views/list/list_controller.js b/addons/web/static/src/views/list/list_controller.js index 61cbf7e56a1..cced5e79877 100644 --- a/addons/web/static/src/views/list/list_controller.js +++ b/addons/web/static/src/views/list/list_controller.js @@ -86,9 +86,10 @@ export class ListController extends Component { useSetupView({ rootRef: this.rootRef, beforeLeave: async () => { - const editedRecord = this.model.root.editedRecord; + const list = this.model.root; + const editedRecord = list.editedRecord; if (editedRecord) { - if (!(await editedRecord.save())) { + if (!(await list.unselectRecord(true))) { throw new Error("View can't be saved"); } } diff --git a/addons/web/static/src/views/relational_model.js b/addons/web/static/src/views/relational_model.js index ace65a32271..70a2cdef2a8 100644 --- a/addons/web/static/src/views/relational_model.js +++ b/addons/web/static/src/views/relational_model.js @@ -853,15 +853,15 @@ export class Record extends DataPoint { /** * @param {"edit" | "readonly"} mode - * @returns {Promise} + * @returns {Promise} */ async switchMode(mode) { if (this.mode === mode) { - return; + return true; } - const preventSwitch = await this._onWillSwitchMode(this, mode); - if (preventSwitch === false) { - return; + const canSwitch = await this._onWillSwitchMode(this, mode); + if (canSwitch === false) { + return false; } if (mode === "readonly") { for (const fieldName in this.activeFields) { @@ -876,6 +876,7 @@ export class Record extends DataPoint { } this.mode = mode; this.model.notify(); + return true; } toggleSelection(selected) { @@ -1412,7 +1413,7 @@ class DynamicList extends DataPoint { abandonRecord(recordId) { const record = this.records.find((r) => r.id === recordId); - this.removeRecord(record); + return this.removeRecord(record); } /** @@ -1697,8 +1698,8 @@ class DynamicList extends DataPoint { return records; } - async unselectRecord() { - await unselectRecord(this.editedRecord, this.abandonRecord.bind(this)); + unselectRecord() { + return unselectRecord(this.editedRecord, this.abandonRecord.bind(this)); } } @@ -3182,8 +3183,8 @@ export class StaticList extends DataPoint { await Promise.all(proms); } - async unselectRecord() { - await unselectRecord(this.editedRecord); + unselectRecord() { + return unselectRecord(this.editedRecord); } } diff --git a/addons/web/static/tests/views/list_view_tests.js b/addons/web/static/tests/views/list_view_tests.js index 9658494ad44..1d293533988 100644 --- a/addons/web/static/tests/views/list_view_tests.js +++ b/addons/web/static/tests/views/list_view_tests.js @@ -13615,6 +13615,56 @@ QUnit.module("Views", (hooks) => { assert.containsN(target, ".o_data_row", 5); }); + QUnit.test( + "Auto save: create a new record without modifying it and leave action", + async function (assert) { + serverData.models.foo.fields.foo.required = true; + serverData.actions = { + 1: { + id: 1, + name: "Action 1", + res_model: "foo", + type: "ir.actions.act_window", + views: [[2, "list"]], + search_view_id: [1, "search"], + }, + 2: { + id: 2, + name: "Action 2", + res_model: "foo", + type: "ir.actions.act_window", + views: [[3, "list"]], + search_view_id: [1, "search"], + }, + }; + serverData.views = { + "foo,1,search": "", + "foo,2,list": '', + "foo,3,list": '', + }; + const webClient = await createWebClient({ serverData }); + + await doAction(webClient, 1); + assert.deepEqual( + [...target.querySelectorAll(".o_data_cell")].map((el) => el.textContent), + ["yop", "blip", "gnap", "blip"] + ); + assert.containsN(target, ".o_data_row", 4); + + await click(target, ".o_list_button_add"); + assert.containsN(target, ".o_data_row", 5); + + // change action and come back + await doAction(webClient, 2); + await doAction(webClient, 1, { clearBreadcrumbs: true }); + assert.deepEqual( + [...target.querySelectorAll(".o_data_cell")].map((el) => el.textContent), + ["yop", "blip", "gnap", "blip"] + ); + assert.containsN(target, ".o_data_row", 4); + } + ); + QUnit.test("Auto save: modify a record and leave action", async function (assert) { serverData.actions = { 1: {