[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:
Julien Mougenot
2022-09-15 19:21:11 +02:00
parent f26f555f0c
commit 40c81b3d8f
3 changed files with 94 additions and 28 deletions
@@ -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;
}
// ------------------------------------------------------------------------
+28 -16
View File
@@ -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"]);
});
});