[FIX] web: uses the right activeFields based on the view of an x2m
This commit allows the correct activeFields to be used depending on the view used to open an x2many field. Before this commit, if we had an x2many field with a list view and a form inline view with the same field containing a different context depending on the view. The context of the form view will always be used. This is a problem: If we have an inline form view with fields common to the list view, the context of the activeFields in the form view will always be used and we will try to combine the different modifiers (required, readonly, etc.). We don't want to do this, we want to use the activeFields of the list view when we open the record in the list and the activeFields of the form view when we open the record in the form view. Solution: When we create the activeFields, we'll use the activeFields from the list view and complete them with those from the form view. We will therefore no longer replace or merge them. Then, when we open a record in form view, we'll use the activeFields from the form view and when the form view closes, we'll restore the activeFields from the list view. closes odoo/odoo#129989 Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
@@ -439,6 +439,7 @@ export class Record extends DataPoint {
|
||||
this._invalidFields.clear();
|
||||
this._closeInvalidFieldsNotification();
|
||||
this._closeInvalidFieldsNotification = () => {};
|
||||
this._restoreActiveFields();
|
||||
}
|
||||
|
||||
_formatServerValue(fieldType, value) {
|
||||
@@ -769,6 +770,20 @@ export class Record extends DataPoint {
|
||||
}
|
||||
}
|
||||
|
||||
_restoreActiveFields() {
|
||||
if (!this._activeFieldsToRestore) {
|
||||
return;
|
||||
}
|
||||
this.model._updateConfig(
|
||||
this.config,
|
||||
{
|
||||
activeFields: { ...this._activeFieldsToRestore },
|
||||
},
|
||||
{ noReload: true }
|
||||
);
|
||||
this._activeFieldsToRestore = undefined;
|
||||
}
|
||||
|
||||
async _save({ noReload, onError } = {}) {
|
||||
// before saving, abandon new invalid, untouched records in x2manys
|
||||
for (const fieldName in this.activeFields) {
|
||||
|
||||
@@ -213,10 +213,18 @@ export class StaticList extends DataPoint {
|
||||
}
|
||||
|
||||
if (record) {
|
||||
record._activeFieldsToRestore = { ...this.config.activeFields };
|
||||
const config = {
|
||||
...record.config,
|
||||
...params,
|
||||
activeFields,
|
||||
};
|
||||
|
||||
// case 1: the record already exists
|
||||
if (this._extendedRecords.has(record.id)) {
|
||||
// case 1.1: the record has already been extended
|
||||
// -> simply switch it to edit and store a savepoint
|
||||
this.model._updateConfig(record.config, config, { noReload: true });
|
||||
record._switchMode("edit");
|
||||
record._addSavePoint();
|
||||
return record;
|
||||
@@ -231,11 +239,6 @@ export class StaticList extends DataPoint {
|
||||
// These operations must be done in that specific order to ensure that the model is
|
||||
// mutated only once (in a tick), and that datapoints have the correct config to
|
||||
// handle field values they receive.
|
||||
const config = {
|
||||
...record.config,
|
||||
...params,
|
||||
activeFields,
|
||||
};
|
||||
let data = {};
|
||||
if (!record.isNew) {
|
||||
const evalContext = Object.assign({}, record.evalContext, config.context);
|
||||
@@ -275,8 +278,8 @@ export class StaticList extends DataPoint {
|
||||
withoutParent: params.withoutParent,
|
||||
manuallyAdded: true,
|
||||
});
|
||||
record._activeFieldsToRestore = { ...this.config.activeFields };
|
||||
}
|
||||
|
||||
// mark the record as being extended, to go through case 1.1 next time
|
||||
this._extendedRecords.add(record.id);
|
||||
|
||||
@@ -379,6 +382,7 @@ export class StaticList extends DataPoint {
|
||||
if (this.orderBy.length) {
|
||||
await this._sort();
|
||||
}
|
||||
record._restoreActiveFields();
|
||||
record._savePoint = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -52,6 +52,24 @@ export function addFieldDependencies(activeFields, fields, fieldDependencies = [
|
||||
}
|
||||
}
|
||||
|
||||
function completeActiveField(activeField, extra) {
|
||||
if (extra.related) {
|
||||
for (const fieldName in extra.related.activeFields) {
|
||||
if (fieldName in activeField.related.activeFields) {
|
||||
completeActiveField(
|
||||
activeField.related.activeFields[fieldName],
|
||||
extra.related.activeFields[fieldName]
|
||||
);
|
||||
} else {
|
||||
activeField.related.activeFields[fieldName] = {
|
||||
...extra.related.activeFields[fieldName],
|
||||
};
|
||||
}
|
||||
}
|
||||
Object.assign(activeField.related.fields, extra.related.fields);
|
||||
}
|
||||
}
|
||||
|
||||
export function createPropertyActiveField(property) {
|
||||
const { type } = property;
|
||||
|
||||
@@ -170,12 +188,13 @@ export function extractFieldsFromArchInfo({ fieldNodes, widgetNodes }, fields) {
|
||||
invisible: true,
|
||||
};
|
||||
if (fieldName in activeField.related.activeFields) {
|
||||
patchActiveFields(
|
||||
formActiveField,
|
||||
activeField.related.activeFields[fieldName]
|
||||
completeActiveField(
|
||||
activeField.related.activeFields[fieldName],
|
||||
formActiveField
|
||||
);
|
||||
} else {
|
||||
activeField.related.activeFields[fieldName] = formActiveField;
|
||||
}
|
||||
activeField.related.activeFields[fieldName] = formActiveField;
|
||||
}
|
||||
Object.assign(activeField.related.fields, formArchInfo.fields);
|
||||
}
|
||||
|
||||
@@ -287,6 +287,73 @@ QUnit.module("Fields", (hooks) => {
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("one2many in a list x2many editable use the right context", async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="p">
|
||||
<tree editable="bottom">
|
||||
<field name="int_field" widget="handle"/>
|
||||
<field name="trululu" context="{'my_context': 'list'}" />
|
||||
</tree>
|
||||
<form>
|
||||
<field name="trululu" context="{'my_context': 'form'}"/>
|
||||
</form>
|
||||
</field>
|
||||
</form>`,
|
||||
mockRPC(route, args) {
|
||||
if (args.method === "name_create") {
|
||||
assert.step(`name_create ${args.kwargs.context.my_context}`);
|
||||
}
|
||||
},
|
||||
resId: 1,
|
||||
});
|
||||
|
||||
await addRow(target, ".o_field_x2many_list");
|
||||
await editInput(target, "[name='trululu'] input", "new partner");
|
||||
await selectDropdownItem(target, "trululu", 'Create "new partner"');
|
||||
|
||||
assert.verifySteps(["name_create list"]);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"one2many in a list x2many non-editable use the right context",
|
||||
async function (assert) {
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="p">
|
||||
<tree>
|
||||
<field name="int_field" widget="handle"/>
|
||||
<field name="trululu" context="{'my_context': 'list'}" />
|
||||
</tree>
|
||||
<form>
|
||||
<field name="trululu" context="{'my_context': 'form'}"/>
|
||||
</form>
|
||||
</field>
|
||||
</form>`,
|
||||
mockRPC(route, args) {
|
||||
if (args.method === "name_create") {
|
||||
assert.step(`name_create ${args.kwargs.context.my_context}`);
|
||||
}
|
||||
},
|
||||
resId: 1,
|
||||
});
|
||||
|
||||
await addRow(target, ".o_field_x2many_list");
|
||||
await editInput(target, "[name='trululu'] input", "new partner");
|
||||
await selectDropdownItem(target, "trululu", 'Create "new partner"');
|
||||
|
||||
assert.verifySteps(["name_create form"]);
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("O2M field without relation_field", async function (assert) {
|
||||
delete serverData.models.partner.fields.p.relation_field;
|
||||
|
||||
@@ -2064,8 +2131,6 @@ QUnit.module("Fields", (hooks) => {
|
||||
fields: {
|
||||
turtle_foo: {},
|
||||
},
|
||||
limit: 40,
|
||||
order: "",
|
||||
},
|
||||
},
|
||||
limit: 40,
|
||||
@@ -6738,8 +6803,6 @@ QUnit.module("Fields", (hooks) => {
|
||||
fields: {
|
||||
turtle_foo: {},
|
||||
},
|
||||
limit: 40,
|
||||
order: "",
|
||||
},
|
||||
},
|
||||
limit: 40,
|
||||
|
||||
Reference in New Issue
Block a user