From 4bf77b9ef18f49e409af904334f4dfab88db3cf0 Mon Sep 17 00:00:00 2001 From: Jorge Pinna Puissant Date: Fri, 28 Jul 2023 15:43:48 +0200 Subject: [PATCH] [REF] web: RelationalModel: add option to save after update This commit adds an option to save the record after an update. When the save option is set to true, the record will be saved and reload after the update. Note that, the onChange is not done in this case. Part-of task-id~3179751 closes odoo/odoo#130328 Related: odoo/enterprise#44905 Signed-off-by: Aaron Bohy (aab) --- .../project_task_kanban_model.js | 4 +- .../relational_model/dynamic_group_list.js | 6 +- .../src/model/relational_model/record.js | 13 +- .../boolean_favorite_field.js | 6 +- .../boolean_toggle/boolean_toggle_field.js | 6 +- .../list_boolean_toggle_field.js | 8 +- .../many2many_tags/many2many_tags_field.js | 9 +- .../views/fields/priority/priority_field.js | 5 +- .../fields/progress_bar/progress_bar_field.js | 5 +- .../state_selection/state_selection_field.js | 5 +- .../views/kanban/kanban_cover_image_dialog.js | 3 +- .../static/src/views/kanban/kanban_record.js | 3 +- .../views/fields/one2many_field_tests.js | 7 +- .../tests/views/kanban/kanban_view_tests.js | 161 ++---------------- 14 files changed, 41 insertions(+), 200 deletions(-) diff --git a/addons/project/static/src/views/project_task_kanban/project_task_kanban_model.js b/addons/project/static/src/views/project_task_kanban/project_task_kanban_model.js index 05d3e179f63..dd887ebac27 100644 --- a/addons/project/static/src/views/project_task_kanban/project_task_kanban_model.js +++ b/addons/project/static/src/views/project_task_kanban/project_task_kanban_model.js @@ -41,13 +41,13 @@ export class ProjectTaskKanbanDynamicGroupList extends RelationalModel.DynamicGr } export class ProjectTaskRecord extends RelationalModel.Record { - async _update(changes, options) { + _update(changes, options) { const value = changes.personal_stage_type_ids; if (Array.isArray(value)) { delete changes.personal_stage_type_ids; changes.personal_stage_type_id = value; } - await super._update(changes, options); + return super._update(changes, options); } get context() { diff --git a/addons/web/static/src/model/relational_model/dynamic_group_list.js b/addons/web/static/src/model/relational_model/dynamic_group_list.js index 6d4d21cb77c..6dc4fa19ae6 100644 --- a/addons/web/static/src/model/relational_model/dynamic_group_list.js +++ b/addons/web/static/src/model/relational_model/dynamic_group_list.js @@ -116,10 +116,10 @@ export class DynamicGroupList extends DynamicList { sourceGroup._addRecord(record, oldIndex); }; try { - await record.update({ [targetGroup.groupByField.name]: value }); - const res = await record.save({ noReload: true }); + const changes = { [targetGroup.groupByField.name]: value }; + const res = await record.update(changes, { save: true }); if (!res) { - revert(); + return revert(); } } catch (e) { // revert changes diff --git a/addons/web/static/src/model/relational_model/record.js b/addons/web/static/src/model/relational_model/record.js index f06ddc66898..450ac218a06 100644 --- a/addons/web/static/src/model/relational_model/record.js +++ b/addons/web/static/src/model/relational_model/record.js @@ -237,11 +237,11 @@ export class Record extends DataPoint { return this.model.mutex.exec(() => this._toggleArchive(false)); } - update(changes) { + update(changes, { save } = {}) { if (this.model._urgentSave) { - return this._update(changes); + return this._update(changes, { save: false }); // save is already scheduled } - return this.model.mutex.exec(() => this._update(changes)); + return this.model.mutex.exec(() => this._update(changes, { save })); } urgentSave() { @@ -913,7 +913,7 @@ export class Record extends DataPoint { } } - async _update(changes, { withoutOnchange, withoutParentUpdate } = {}) { + async _update(changes, { withoutOnchange, withoutParentUpdate, save } = {}) { this.dirty = true; const prom = this._preprocessChanges(changes); if (prom && !this.model._urgentSave) { @@ -935,7 +935,7 @@ export class Record extends DataPoint { const onChangeFields = Object.keys(changes).filter( (fieldName) => this.activeFields[fieldName] && this.activeFields[fieldName].onChange ); - if (onChangeFields.length && !this.model._urgentSave && !withoutOnchange) { + if (onChangeFields.length && !this.model._urgentSave && !withoutOnchange && !save) { const localChanges = this._getChanges( { ...this._changes, ...changes }, { withReadonly: true } @@ -976,5 +976,8 @@ export class Record extends DataPoint { } await this.model.hooks.onRecordChanged(this, this._getChanges()); } + if (save) { + return this._save(); + } } } diff --git a/addons/web/static/src/views/fields/boolean_favorite/boolean_favorite_field.js b/addons/web/static/src/views/fields/boolean_favorite/boolean_favorite_field.js index 44e2dbcef6e..20003f2d67b 100644 --- a/addons/web/static/src/views/fields/boolean_favorite/boolean_favorite_field.js +++ b/addons/web/static/src/views/fields/boolean_favorite/boolean_favorite_field.js @@ -19,10 +19,8 @@ export class BooleanFavoriteField extends Component { }; async update() { - await this.props.record.update({ [this.props.name]: !this.props.record.data[this.props.name] }); - if (this.props.autosave) { - await this.props.record.save(); - } + const changes = { [this.props.name]: !this.props.record.data[this.props.name] }; + await this.props.record.update(changes, { save: this.props.autosave }); } } diff --git a/addons/web/static/src/views/fields/boolean_toggle/boolean_toggle_field.js b/addons/web/static/src/views/fields/boolean_toggle/boolean_toggle_field.js index ab8878bc513..6b9382bdba7 100644 --- a/addons/web/static/src/views/fields/boolean_toggle/boolean_toggle_field.js +++ b/addons/web/static/src/views/fields/boolean_toggle/boolean_toggle_field.js @@ -12,10 +12,8 @@ export class BooleanToggleField extends BooleanField { }; async onChange(newValue) { - await this.props.record.update({ [this.props.name]: newValue }); - if (this.props.autosave) { - return this.props.record.save(); - } + const changes = { [this.props.name]: newValue }; + await this.props.record.update(changes, { save: this.props.autosave }); } } diff --git a/addons/web/static/src/views/fields/boolean_toggle/list_boolean_toggle_field.js b/addons/web/static/src/views/fields/boolean_toggle/list_boolean_toggle_field.js index cb62d2766bc..5dd7e8c5691 100644 --- a/addons/web/static/src/views/fields/boolean_toggle/list_boolean_toggle_field.js +++ b/addons/web/static/src/views/fields/boolean_toggle/list_boolean_toggle_field.js @@ -8,12 +8,8 @@ export class ListBooleanToggleField extends BooleanToggleField { async onClick() { if (!this.props.readonly && this.props.record.isInEdition) { - await this.props.record.update({ - [this.props.name]: !this.props.record.data[this.props.name], - }); - if (this.props.autosave) { - return this.props.record.save(); - } + const changes = { [this.props.name]: !this.props.record.data[this.props.name] }; + await this.props.record.update(changes, { save: this.props.autosave }); } } } 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 273fe45af75..81f6a78b4df 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 @@ -352,10 +352,10 @@ export class Many2ManyTagsFieldColorEditable extends Many2ManyTagsField { if (tagRecord.data[this.props.colorField] != 0) { this.previousColorsMap[tagRecord.resId] = tagRecord.data[this.props.colorField]; } - tagRecord.update({ + const changes = { [this.props.colorField]: isHidden ? 0 : this.previousColorsMap[tagRecord.resId] || 1, - }); - tagRecord.save(); + }; + tagRecord.update(changes, { save: true }); this.popover.close(); } @@ -363,8 +363,7 @@ export class Many2ManyTagsFieldColorEditable extends Many2ManyTagsField { const tagRecord = this.props.record.data[this.props.name].records.find( (record) => record.id === tag.id ); - tagRecord.update({ [this.props.colorField]: colorIndex }); - tagRecord.save(); + tagRecord.update({ [this.props.colorField]: colorIndex }, { save: true }); this.popover.close(); } } diff --git a/addons/web/static/src/views/fields/priority/priority_field.js b/addons/web/static/src/views/fields/priority/priority_field.js index a33ef944035..a7a0bfb6336 100644 --- a/addons/web/static/src/views/fields/priority/priority_field.js +++ b/addons/web/static/src/views/fields/priority/priority_field.js @@ -82,10 +82,7 @@ export class PriorityField extends Component { } async updateRecord(value) { - await this.props.record.update({ [this.props.name]: value }); - if (this.props.autosave) { - return this.props.record.save(); - } + await this.props.record.update({ [this.props.name]: value }, { save: this.props.autosave }); } } diff --git a/addons/web/static/src/views/fields/progress_bar/progress_bar_field.js b/addons/web/static/src/views/fields/progress_bar/progress_bar_field.js index 0833b61aa2b..068e261318b 100644 --- a/addons/web/static/src/views/fields/progress_bar/progress_bar_field.js +++ b/addons/web/static/src/views/fields/progress_bar/progress_bar_field.js @@ -75,10 +75,7 @@ export class ProgressBarField extends Component { if (this.props.record.fields[fieldName].type === "integer") { parsedValue = Math.floor(parsedValue); } - this.props.record.update({ [fieldName]: parsedValue }); - if (this.props.readonly) { - this.props.record.save(); - } + this.props.record.update({ [fieldName]: parsedValue }, { save: this.props.readonly }); } onCurrentValueChange(ev) { this.onValueChange(ev.target.value, this.currentValueField); diff --git a/addons/web/static/src/views/fields/state_selection/state_selection_field.js b/addons/web/static/src/views/fields/state_selection/state_selection_field.js index c26a546f621..9ca6d39e577 100644 --- a/addons/web/static/src/views/fields/state_selection/state_selection_field.js +++ b/addons/web/static/src/views/fields/state_selection/state_selection_field.js @@ -71,10 +71,7 @@ export class StateSelectionField extends Component { } async updateRecord(value) { - await this.props.record.update({ [this.props.name]: value }); - if (this.props.autosave) { - return this.props.record.save(); - } + await this.props.record.update({ [this.props.name]: value }, { save: this.props.autosave }); } } diff --git a/addons/web/static/src/views/kanban/kanban_cover_image_dialog.js b/addons/web/static/src/views/kanban/kanban_cover_image_dialog.js index 875e9873b77..8d7c85833ac 100644 --- a/addons/web/static/src/views/kanban/kanban_cover_image_dialog.js +++ b/addons/web/static/src/views/kanban/kanban_cover_image_dialog.js @@ -59,8 +59,7 @@ export class KanbanCoverImageDialog extends Component { async setCover() { const id = this.state.selectedAttachmentId ? [this.state.selectedAttachmentId] : false; - await this.props.record.update({ [this.props.fieldName]: id }); - await this.props.record.save(); + await this.props.record.update({ [this.props.fieldName]: id }, { save: true }); this.props.close(); } diff --git a/addons/web/static/src/views/kanban/kanban_record.js b/addons/web/static/src/views/kanban/kanban_record.js index ba0149dcfea..191845b201b 100644 --- a/addons/web/static/src/views/kanban/kanban_record.js +++ b/addons/web/static/src/views/kanban/kanban_record.js @@ -297,8 +297,7 @@ export class KanbanRecord extends Component { async selectColor(colorIndex) { const { archInfo, record } = this.props; - await record.update({ [archInfo.colorField]: colorIndex }); - await record.save(); + await record.update({ [archInfo.colorField]: colorIndex }, { save: true }); } /** 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 70fda3e135b..53486d9ae87 100644 --- a/addons/web/static/tests/views/fields/one2many_field_tests.js +++ b/addons/web/static/tests/views/fields/one2many_field_tests.js @@ -13097,12 +13097,7 @@ QUnit.module("Fields", (hooks) => { assert.verifySteps(["get_views partner", "web_read partner"]); await click(target, ".o_boolean_toggle"); - assert.verifySteps([ - "onchange2 turtle", - "onchange2 partner", - "write turtle", - "web_read turtle", - ]); + assert.verifySteps(["onchange2 partner", "write turtle", "web_read turtle"]); }); QUnit.test("create a new record with an x2m invisible", async function (assert) { diff --git a/addons/web/static/tests/views/kanban/kanban_view_tests.js b/addons/web/static/tests/views/kanban/kanban_view_tests.js index bb9b6eecea6..8eb61142545 100644 --- a/addons/web/static/tests/views/kanban/kanban_view_tests.js +++ b/addons/web/static/tests/views/kanban/kanban_view_tests.js @@ -15,7 +15,6 @@ import { mouseEnter, nextTick, patchWithCleanup, - selectDropdownItem, triggerEvent, triggerHotkey, } from "@web/../tests/helpers/utils"; @@ -5432,11 +5431,7 @@ QUnit.module("Views", (hooks) => { } ); - QUnit.test("prevent drag and drop of record if onchange fails", async (assert) => { - serverData.models.partner.onchanges = { - product_id() {}, - }; - + QUnit.test("prevent drag and drop of record if save fails", async (assert) => { await makeView({ type: "kanban", resModel: "partner", @@ -5453,7 +5448,7 @@ QUnit.module("Views", (hooks) => { "", groupBy: ["product_id"], async mockRPC(route, { model, method }) { - if (model === "partner" && method === "onchange2") { + if (model === "partner" && method === "write") { return Promise.reject({}); } }, @@ -9928,14 +9923,17 @@ QUnit.module("Views", (hooks) => { "unity_web_search_read", "unity_web_search_read", "write", + "web_read", "read_progress_bar", "/web/dataset/resequence", "read", "write", + "web_read", "read_progress_bar", "/web/dataset/resequence", "read", "write", + "web_read", "read_progress_bar", "/web/dataset/resequence", "read", @@ -9996,6 +9994,7 @@ QUnit.module("Views", (hooks) => { "unity_web_search_read", "unity_web_search_read", "write", + "web_read", "read_progress_bar", "web_read_group", "/web/dataset/resequence", @@ -10077,6 +10076,7 @@ QUnit.module("Views", (hooks) => { "unity_web_search_read", "unity_web_search_read", "write", + "web_read", "read_progress_bar", "/web/dataset/resequence", "read", @@ -11544,6 +11544,7 @@ QUnit.module("Views", (hooks) => { "unity_web_search_read", "unity_web_search_read", "write", + "web_read", "read_progress_bar", "/web/dataset/resequence", "read", @@ -11629,6 +11630,7 @@ QUnit.module("Views", (hooks) => { assert.deepEqual(getCardTexts(1), ["2blip", "3gnap"]); assert.verifySteps([ "write", + "web_read", "read_progress_bar", "unity_web_search_read", "/web/dataset/resequence", @@ -12841,97 +12843,6 @@ QUnit.module("Views", (hooks) => { assert.verifySteps(["resequence"]); }); - QUnit.test("dragged record cannot be saved", async (assert) => { - let def; - serverData.models.partner.records = [ - { id: 1, bar: false, state: "abc" }, - { id: 2, bar: true, state: "def", foo: "blip" }, - ]; - serverData.models.partner.onchanges = { - bar() {}, - }; - - const kanban = await makeView({ - type: "kanban", - resModel: "partner", - serverData, - arch: ` - - - - -
- - -
-
-
-
- `, - groupBy: ["bar"], - async mockRPC(route, args) { - if (args.method === "onchange2") { - assert.step("onchange2"); - await Promise.resolve(def); - } - }, - }); - - patchWithCleanup(kanban.env.services.notification, { - add(message, options) { - assert.step("notification"); - assert.strictEqual(options.title, "Invalid fields: "); - assert.strictEqual(`${message}`, ""); - }, - }); - - def = makeDeferred(); - - assert.containsOnce(target, ".o_kanban_group:nth-child(1) .o_kanban_record"); - assert.strictEqual( - target.querySelector(".o_kanban_group:nth-child(1) .o_kanban_record").innerText, - "ABC" - ); - assert.containsOnce(target, ".o_kanban_group:nth-child(2) .o_kanban_record"); - assert.strictEqual( - target.querySelector(".o_kanban_group:nth-child(2) .o_kanban_record").innerText, - "blipDEF" - ); - - assert.verifySteps([]); - - // move "yop" from first to second column - await dragAndDrop( - ".o_kanban_group:nth-child(1) .o_kanban_record", - ".o_kanban_group:nth-child(2)" - ); - - assert.containsNone(target, ".o_kanban_group:nth-child(1) .o_kanban_record"); - assert.containsN(target, ".o_kanban_group:nth-child(2) .o_kanban_record", 2); - assert.deepEqual( - [...target.querySelectorAll(".o_kanban_group:nth-child(2) .o_kanban_record")].map( - (el) => el.innerText - ), - ["blipDEF", "ABC"] - ); - assert.verifySteps(["onchange2"]); - - def.resolve(); - await nextTick(); - - assert.containsOnce(target, ".o_kanban_group:nth-child(1) .o_kanban_record"); - assert.strictEqual( - target.querySelector(".o_kanban_group:nth-child(1) .o_kanban_record").innerText, - "ABC" - ); - assert.containsOnce(target, ".o_kanban_group:nth-child(2) .o_kanban_record"); - assert.strictEqual( - target.querySelector(".o_kanban_group:nth-child(2) .o_kanban_record").innerText, - "blipDEF" - ); - assert.verifySteps(["notification"]); - }); - QUnit.test("renders banner_route", async (assert) => { await makeView({ type: "kanban", @@ -13190,6 +13101,7 @@ QUnit.module("Views", (hooks) => { "unity_web_search_read", "unity_web_search_read", "write", + "web_read", "read_progress_bar", "web_read_group", ]); @@ -13284,55 +13196,6 @@ QUnit.module("Views", (hooks) => { ]); }); - QUnit.test("Move new record with onchanges and different active fields", async (assert) => { - serverData.models.partner.fields.foo.default = "abc"; - serverData.models.partner.onchanges = { - bar(obj) { - obj.foo = [...obj.foo].reverse().join(""); - }, - }; - serverData.views["partner,some_view_ref,form"] = /* xml */ ` -
- - - `; - - await makeView({ - type: "kanban", - resModel: "partner", - serverData, - arch: /* xml */ ` - - - - -
- - -
-
-
- `, - groupBy: ["bar"], - }); - - assert.deepEqual(getCardTexts(0), ["blip-4"]); - assert.deepEqual(getCardTexts(1), ["yop10", "blip9", "gnap17"]); - - await quickCreateRecord(0); - await editQuickCreateInput("int_field", "13"); - await selectDropdownItem(target, "category_ids", "gold"); - await validateRecord(); - - assert.deepEqual(getCardTexts(0), ["abc13", "blip-4"]); - assert.deepEqual(getCardTexts(1), ["yop10", "blip9", "gnap17"]); - - await dragAndDrop(".o_kanban_record", ".o_kanban_group:nth-child(2)"); - - assert.deepEqual(getCardTexts(0), ["blip-4"]); - assert.deepEqual(getCardTexts(1), ["yop10", "blip9", "gnap17", "cba13"]); - }); - QUnit.test("no sample data when all groups are folded then one is unfolded", async (assert) => { await makeView({ type: "kanban", @@ -13606,7 +13469,7 @@ QUnit.module("Views", (hooks) => { prom.resolve(); await nextTick(); - assert.verifySteps(["write", "/web/dataset/resequence", "read"]); + assert.verifySteps(["write", "web_read", "/web/dataset/resequence", "read"]); assert.deepEqual( [...target.querySelectorAll(".o_kanban_record")].map((el) => el.innerText), ["hello", "hello", "hello", "xmo"] @@ -13672,7 +13535,7 @@ QUnit.module("Views", (hooks) => { `, groupBy: ["product_id"], mockRPC: async (route, { method }) => { - if (method === "onchange2") { + if (method === "write") { await def; } },