[FIX] web: throw an error when saving a record in a list view

Before this commit, in a list view, it is possible to have two records
in edit mode at the same time if an error is thrown when saving
the editedRecord.
The editedRecord is the only list record that can be in edit mode.

Why:
If a server error is returned when saving the editedRecord, then it
stays in edit mode but is no longer considered the editedRecord of the
list. It is therefore possible to switch another record to edit mode.

Solution:
The record that fails its save due to the error should stay the
editedRecord of the list.

How to reproduce:
- Go to a list view
- Edit a record
- Click on another record (the "write" operation returns an error)
- Close the error dialog
- Click again on the other record

Before this commit:
Both records are in edit mode

After this commit:
Only the edited commit is in edit mode.

closes odoo/odoo#102560

X-original-commit: 77092167cf8f8fca11a39005d720fa82abeec0ae
Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
FrancoisGe
2022-10-07 13:37:09 +02:00
parent 751ed08c3b
commit 9c8bff35be
2 changed files with 55 additions and 8 deletions
@@ -1424,7 +1424,13 @@ class DynamicList extends DataPoint {
if (editedRecord !== record && editedRecord.canBeAbandoned) {
this.abandonRecord(editedRecord.id);
} else {
const isSaved = await editedRecord.save();
let isSaved;
try {
isSaved = await editedRecord.save();
} catch (e) {
this.editedRecord = editedRecord;
throw e;
}
if (!isSaved) {
this.editedRecord = editedRecord;
return false;
@@ -2,12 +2,17 @@
import { browser } from "@web/core/browser/browser";
import { Domain } from "@web/core/domain";
import { errorService } from "@web/core/errors/error_service";
import { localization } from "@web/core/l10n/localization";
import { registry } from "@web/core/registry";
import { uiService } from "@web/core/ui/ui_service";
import { session } from "@web/session";
import { ListController } from "@web/views/list/list_controller";
import { tooltipService } from "@web/core/tooltip/tooltip_service";
import { uiService } from "@web/core/ui/ui_service";
import { getNextTabableElement } from "@web/core/utils/ui";
import { session } from "@web/session";
import { FloatField } from "@web/views/fields/float/float_field";
import { TextField } from "@web/views/fields/text/text_field";
import { ListController } from "@web/views/list/list_controller";
import { DynamicRecordList } from "@web/views/relational_model";
import { actionService } from "@web/webclient/actions/action_service";
import { makeFakeLocalizationService, makeFakeUserService } from "../helpers/mock_services";
import {
@@ -55,10 +60,6 @@ import {
} from "../search/helpers";
import { createWebClient, doAction, loadState } from "../webclient/helpers";
import { makeView, setupViewRegistries } from "./helpers";
import { getNextTabableElement } from "@web/core/utils/ui";
import { FloatField } from "@web/views/fields/float/float_field";
import { TextField } from "@web/views/fields/text/text_field";
import { DynamicRecordList } from "@web/views/relational_model";
const { Component, onWillStart, xml } = owl;
@@ -15537,4 +15538,44 @@ QUnit.module("Views", (hooks) => {
"the widget has access to the record's data"
);
});
QUnit.test(
"edit a record then select another record with a throw error when saving",
async function (assert) {
serviceRegistry.add("error", errorService);
await makeView({
type: "list",
resModel: "foo",
serverData,
arch: `
<tree editable="bottom">
<field name="foo"/>
</tree>`,
mockRPC(route, args) {
if (args.method === "write") {
throw new Error("Can't write");
}
},
});
await click(target.querySelectorAll(".o_data_cell")[1]);
await editInput(target, "[name='foo'] input", "plop");
assert.containsOnce(target, "[name='foo'] input");
await click(target.querySelectorAll(".o_data_cell")[0]);
assert.containsOnce(target, ".o_dialog_error");
await click(target, ".o_dialog_error .btn-primary.o-default-button");
assert.containsOnce(target, ".o_selected_row");
assert.hasClass(target.querySelectorAll(".o_data_row")[1], "o_selected_row");
await click(target.querySelectorAll(".o_data_cell")[0]);
assert.containsOnce(target, ".o_dialog_error");
await click(target, ".o_dialog_error .btn-primary.o-default-button");
assert.containsOnce(target, ".o_selected_row");
assert.hasClass(target.querySelectorAll(".o_data_row")[1], "o_selected_row");
}
);
});