[FIX] web: a modified field must always be sent to the server

Before this commit, when a field of type one2many or many2many, was
added and afterward deleted, on the onchange or write call to the server
the field wasn't added as a change (because the add and delete command
cancel each other). This can cause issues because when adding the x2many
field, the onchange on the server can have side effects that we want to
remove when deleting the x2many.

Now, we follow the following rule : If a field has been modified, its
value must always be sent to the server for onchange and write.

This commit also avoid to call update on the parent record if the chil
record didn't have changes.

Task 3179751

Part-of: odoo/odoo#114024
This commit is contained in:
Jorge Pinna Puissant
2023-07-24 20:17:53 +02:00
committed by Aaron Bohy
parent 17de016dc6
commit 1318be22a4
6 changed files with 29 additions and 15 deletions
@@ -481,9 +481,7 @@ export class Record extends DataPoint {
}
if (field.type === "one2many" || field.type === "many2many") {
const commands = value._getCommands({ withReadonly });
if (commands.length) {
result[fieldName] = commands;
}
result[fieldName] = commands;
} else {
result[fieldName] = this._formatServerValue(field.type, value);
}
@@ -388,6 +388,8 @@ export class StaticList extends DataPoint {
if (!this._currentIds.includes(record.isNew ? record._virtualId : record.resId)) {
// new record created, not yet in the list
await this._addRecord(record);
} else if (!record.dirty) {
return;
}
await this._onUpdate();
if (this.orderBy.length) {
@@ -2167,7 +2167,11 @@ export class MockServer {
}
this.writeRecord(
comodelName,
{ [inverseFieldName]: inverseFieldNewValue },
{
[inverseFieldName]: inverseFieldNewValue.length
? inverseFieldNewValue
: false,
},
removedRecordId
);
}
@@ -2382,7 +2386,9 @@ export class MockServer {
}
if (["one2many", "many2many"].includes(field.type)) {
let ids = record[fieldName] ? record[fieldName].slice() : [];
if (Array.isArray(value)) {
// if a field has been modified, its value must always be sent to the server for onchange and write.
// take into account that the value can be a empty list of commands.
if (Array.isArray(value) && value.length) {
if (
value.reduce((hasOnlyInt, val) => hasOnlyInt && Number.isInteger(val), true)
) {
+3 -3
View File
@@ -69,7 +69,7 @@ QUnit.module("MockServer", (hooks) => {
partner_id: 2,
partner_ids: [1],
select: "new",
partner_ref: "partner,2"
partner_ref: "partner,2",
},
{
foo: 17,
@@ -1343,8 +1343,8 @@ QUnit.module("MockServer", (hooks) => {
},
];
const mockServer = new MockServer(data);
mockServer.mockWrite("foo", [[2], { many2many_field: [] }]);
assert.deepEqual(mockServer.models.bar.records[0].many2many_field, []);
mockServer.mockWrite("foo", [[2], { many2many_field: [] }]); // save nothing
assert.deepEqual(mockServer.models.bar.records[0].many2many_field, [2]);
});
QUnit.test("many2one update should update inverse field", async function (assert) {
@@ -1538,7 +1538,7 @@ QUnit.module("Fields", (hooks) => {
await click(target.querySelector(".modal-body input[type=checkbox]"));
await click(target.querySelector(".modal .modal-footer .btn-primary"));
assert.verifySteps(["write", "web_read", "onchange2"]);
assert.verifySteps(["write", "web_read"]);
// there is nothing left to save -> should not do a 'write' RPC
await clickSave(target);
@@ -563,7 +563,7 @@ QUnit.module("Fields", (hooks) => {
if (route === "/web/dataset/call_kw/partner/write") {
assert.deepEqual(
args.args[1].p[0][2],
{ foo: "ff", qux: 99 },
{ foo: "ff", qux: 99, turtles: [] },
"The right values should be written"
);
}
@@ -3809,7 +3809,14 @@ QUnit.module("Fields", (hooks) => {
await clickSave(target);
assert.containsNone(target, "tr.o_data_row");
assert.verifySteps(["get_views", "web_read", "onchange2", "onchange2"]);
assert.verifySteps([
"get_views",
"web_read",
"onchange2",
"onchange2",
"write",
"web_read",
]);
});
QUnit.test("editable one2many list, adding line when only one page", async function (assert) {
@@ -6046,7 +6053,7 @@ QUnit.module("Fields", (hooks) => {
rec.turtles[0][1],
{
product_id: [1, "xenomorphe"],
partner_ids: rec.turtles[0][2].partner_ids
partner_ids: rec.turtles[0][2].partner_ids.length
? [
[3, 1],
[4, 2],
@@ -9619,8 +9626,8 @@ QUnit.module("Fields", (hooks) => {
assert.strictEqual(
numUserOnchange,
2,
"there should 2 and only 2 onchange from closing the partner modal"
1,
"there should 1 and only 1 onchange from closing the partner modal"
);
await click(target.querySelector(".o_data_row .o_data_cell"));
@@ -10902,6 +10909,7 @@ QUnit.module("Fields", (hooks) => {
async mockRPC(route, args) {
if (step === 3 && args.method === "onchange2" && args.model === "partner") {
assert.deepEqual(args.args[1].turtles[0][2], {
o2m: [],
turtle_bar: false,
});
}
@@ -12470,7 +12478,7 @@ QUnit.module("Fields", (hooks) => {
assert.containsNone(target, ".o_kanban_record:not(.o_kanban_ghost)");
await clickSave(target);
assert.verifySteps([]);
assert.verifySteps(["write"]);
});
QUnit.test("toggle boolean in o2m with the formView in edition", async function (assert) {