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; } },