[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) <aab@odoo.com>
This commit is contained in:
@@ -277,18 +277,22 @@ class KanbanGroup extends Group {
|
||||
await this.checkActiveValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} index
|
||||
* @returns {Promise<Record | false>}
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
@@ -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<boolean>}
|
||||
*/
|
||||
async _save(options = { stayInEdition: false, noReload: false }) {
|
||||
if (!(await this._checkValidity())) {
|
||||
if (!this._checkValidity()) {
|
||||
const invalidFields = [...this._invalidFields].map((fieldName) => {
|
||||
return `<li>${escape(this.fields[fieldName].string || fieldName)}</li>`;
|
||||
}, 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;
|
||||
|
||||
@@ -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 */ `
|
||||
<form>
|
||||
<field name="int_field" />
|
||||
<field name="category_ids" widget="many2many_tags" />
|
||||
</form>`;
|
||||
|
||||
await makeView({
|
||||
type: "kanban",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: /* xml */ `
|
||||
<kanban on_create="quick_create" quick_create_view="some_view_ref">
|
||||
<field name="foo" />
|
||||
<field name="bar" />
|
||||
<templates>
|
||||
<div t-name="kanban-box">
|
||||
<t t-esc="record.foo.raw_value" />
|
||||
<field name="int_field" />
|
||||
</div>
|
||||
</templates>
|
||||
</kanban>
|
||||
`,
|
||||
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"]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user