From 40c81b3d8fe9a3bb61af8731fa6e178daee213a8 Mon Sep 17 00:00:00 2001 From: Julien Mougenot Date: Wed, 14 Sep 2022 15:35:35 +0000 Subject: [PATCH] [FIX] web: Apply correct active fields on quick created records Before this commit, a record quick created in kanban would have values left from the quick create form view in its `data` object, that would then be used by the model of the parent view to perform onchange actions. This would cause a crash if said values were set on a field not included in the parent view's active fields. In this commit, records data are cleared at each load, ensuring that they only contain values for their current active fields. closes odoo/odoo#100182 Signed-off-by: Aaron Bohy (aab) --- .../static/src/views/kanban/kanban_model.js | 22 +++++--- .../web/static/src/views/relational_model.js | 44 +++++++++------ .../static/tests/views/kanban_view_tests.js | 56 ++++++++++++++++++- 3 files changed, 94 insertions(+), 28 deletions(-) diff --git a/addons/web/static/src/views/kanban/kanban_model.js b/addons/web/static/src/views/kanban/kanban_model.js index 3ce8822d669..3284181ae4f 100644 --- a/addons/web/static/src/views/kanban/kanban_model.js +++ b/addons/web/static/src/views/kanban/kanban_model.js @@ -277,18 +277,22 @@ class KanbanGroup extends Group { await this.checkActiveValue(); } + /** + * @param {number} index + * @returns {Promise} + */ async validateQuickCreate(index) { const record = this.list.quickCreateRecord; - if (!record) { - return false; - } - const saved = await record.save(); - if (saved) { - this.addRecord(this.removeRecord(record), index); - this.count++; - this.list.count++; - return record; + if (record) { + const saved = await record.save(); + if (saved) { + this.addRecord(this.removeRecord(record), index); + this.count++; + this.list.count++; + return record; + } } + return false; } // ------------------------------------------------------------------------ diff --git a/addons/web/static/src/views/relational_model.js b/addons/web/static/src/views/relational_model.js index cc3b98091ca..5373cef3d91 100644 --- a/addons/web/static/src/views/relational_model.js +++ b/addons/web/static/src/views/relational_model.js @@ -242,7 +242,7 @@ class DataPoint { this.model = model; this.resModel = params.resModel; this.fields = params.fields; - this.activeFields = params.activeFields || {}; + this.setActiveFields(params.activeFields); this.rawContext = params.rawContext; this.defaultContext = params.defaultContext; @@ -324,6 +324,13 @@ class DataPoint { } } + /** + * @param {Object} [activeFields={}] + */ + setActiveFields(activeFields) { + this.activeFields = activeFields || {}; + } + // ------------------------------------------------------------------------- // Protected // ------------------------------------------------------------------------- @@ -397,8 +404,6 @@ export class Record extends DataPoint { this.onChanges = params.onChanges || (() => {}); this._invalidFields = new Set(); - this._requiredFields = {}; - this._setRequiredFields(); this.preloadedData = {}; this.preloadedDataCaches = {}; this.isInQuickCreation = params.isInQuickCreation || false; @@ -783,6 +788,7 @@ export class Record extends DataPoint { * @param {Object} [params.changes] */ async load(params = {}) { + this.data = {}; this._cache = {}; for (const fieldName in this.activeFields) { const field = this.fields[fieldName]; @@ -868,6 +874,23 @@ export class Record extends DataPoint { return this.model.mutex.exec(() => this._save(options)); } + /** + * Overridden to set required fields based on the new active fields. + * + * @override + */ + setActiveFields(activeFields) { + super.setActiveFields(activeFields); + + this._requiredFields = {}; + for (const [fieldName, activeField] of Object.entries(this.activeFields)) { + const { modifiers } = activeField; + if (modifiers && modifiers.required) { + this._requiredFields[fieldName] = modifiers.required; + } + } + } + async setInvalidField(fieldName) { if (this.selected && this.model.multiEdit) { const dialogProps = { @@ -1258,7 +1281,7 @@ export class Record extends DataPoint { * @returns {Promise} */ async _save(options = { stayInEdition: false, noReload: false }) { - if (!(await this._checkValidity())) { + if (!this._checkValidity()) { const invalidFields = [...this._invalidFields].map((fieldName) => { return `
  • ${escape(this.fields[fieldName].string || fieldName)}
  • `; }, this); @@ -1304,7 +1327,7 @@ export class Record extends DataPoint { // Switch to the parent active fields if (this.parentActiveFields) { - this.activeFields = this.parentActiveFields; + this.setActiveFields(this.parentActiveFields); this.parentActiveFields = false; } this.isInQuickCreation = false; @@ -1317,15 +1340,6 @@ export class Record extends DataPoint { return true; } - _setRequiredFields() { - for (const [fieldName, activeField] of Object.entries(this.activeFields)) { - const { modifiers } = activeField; - if (modifiers && modifiers.required) { - this._requiredFields[fieldName] = modifiers.required; - } - } - } - async _update(changes) { await this._applyChanges(changes); if (this.selected && this.model.multiEdit) { @@ -1769,7 +1783,6 @@ export class DynamicRecordList extends DynamicList { resModel: this.resModel, fields: this.fields, activeFields: this.activeFields, - parentActiveFields: this.activeFields, onRecordWillSwitchMode: this.onRecordWillSwitchMode, defaultContext: this.defaultContext, rawContext: { @@ -2033,7 +2046,6 @@ export class DynamicGroupList extends DynamicList { this.openGroupsByDefault = params.openGroupsByDefault || false; /** @type {Group[]} */ this.groups = state.groups || []; - this.activeFields = params.activeFields; this.isGrouped = true; this.quickCreateInfo = null; // Lazy loaded; this.expand = params.expand; diff --git a/addons/web/static/tests/views/kanban_view_tests.js b/addons/web/static/tests/views/kanban_view_tests.js index 1ea59ca73e3..9c56be0b5cc 100644 --- a/addons/web/static/tests/views/kanban_view_tests.js +++ b/addons/web/static/tests/views/kanban_view_tests.js @@ -3,8 +3,8 @@ import { makeFakeDialogService } from "@web/../tests/helpers/mock_services"; import { click, - drag, clickSave, + drag, dragAndDrop, editInput, getFixture, @@ -12,6 +12,7 @@ import { makeDeferred, nextTick, patchWithCleanup, + selectDropdownItem, triggerEvent, triggerHotkey, } from "@web/../tests/helpers/utils"; @@ -27,16 +28,16 @@ import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; import { createWebClient, doAction } from "@web/../tests/webclient/helpers"; import { browser } from "@web/core/browser/browser"; import { dialogService } from "@web/core/dialog/dialog_service"; -import { tooltipService } from "@web/core/tooltip/tooltip_service"; import { makeErrorFromResponse, RPCError } from "@web/core/network/rpc_service"; import { registry } from "@web/core/registry"; +import { tooltipService } from "@web/core/tooltip/tooltip_service"; import { nbsp } from "@web/core/utils/strings"; import { getNextTabableElement } from "@web/core/utils/ui"; import { session } from "@web/session"; import { KanbanAnimatedNumber } from "@web/views/kanban/kanban_animated_number"; import { kanbanView } from "@web/views/kanban/kanban_view"; -import { ViewButton } from "@web/views/view_button/view_button"; import { DynamicRecordList } from "@web/views/relational_model"; +import { ViewButton } from "@web/views/view_button/view_button"; import AbstractField from "web.AbstractField"; import legacyFieldRegistry from "web.field_registry"; @@ -11759,4 +11760,53 @@ QUnit.module("Views", (hooks) => { "onchange", // reopen the quick create automatically ]); }); + + 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"]); + }); });