[FIX] web: many2x fail for a good reason

Before this commit, when the "Create" button of a m2x is clicked and
an error is returned by the server, a form view dialog is opened to
create the record. We want to know if the error is caused by a missing
required field but not by any other type of error.
So we open the form view dialog only if the error is of type
ValidationError otherwise we display an error dialog.

closes odoo/odoo#116954

X-original-commit: 49bae69c602d1587fe159719667ff8dd0fb23984
Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
FrancoisGe
2023-03-29 11:47:15 +02:00
parent 2ac72eae76
commit 6371565e69
3 changed files with 113 additions and 66 deletions
@@ -255,12 +255,21 @@ export class Many2XAutocomplete extends Component {
try {
await this.props.quickCreate(request, params);
} catch (e) {
if (e && e.name === "RPC_ERROR") {
if (
e &&
e.name === "RPC_ERROR" &&
e.exceptionName === "odoo.exceptions.ValidationError"
) {
const context = this.getCreationContext(request);
return this.openMany2X({ context });
}
// Compatibility with legacy code
if (e && e.message && e.message.name === "RPC_ERROR") {
if (
e &&
e.message &&
e.message.name === "RPC_ERROR" &&
e.message.exceptionName === "odoo.exceptions.ValidationError"
) {
// The event.preventDefault() is necessary because we still use the legacy
e.event.preventDefault();
const context = this.getCreationContext(request);
@@ -1480,7 +1480,9 @@ QUnit.module("Fields", (hooks) => {
arch: '<form><field name="timmy" widget="many2many_tags"/></form>',
mockRPC(route, args) {
if (args.method === "name_create") {
throw new RPCError("Something went wrong");
const error = new RPCError("Something went wrong");
error.exceptionName = "odoo.exceptions.ValidationError";
throw error;
}
if (args.method === "create") {
assert.deepEqual(args.args[0], {
@@ -2967,19 +2967,64 @@ QUnit.module("Fields", (hooks) => {
await click(target, ".modal .modal-footer .btn-primary");
});
QUnit.test(
"failing quick create on a many2one because ValidationError",
async function (assert) {
assert.expect(5);
registry.category("services").add("error", errorService);
// remove the override in qunit.js that swallows unhandledrejection errors
// s.t. we let the error service handle them
const originalOnUnhandledRejection = window.onunhandledrejection;
window.onunhandledrejection = () => {};
registerCleanup(() => {
window.onunhandledrejection = originalOnUnhandledRejection;
});
serverData.views = {
"product,false,form": '<form><field name="name" /></form>',
};
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: '<form><field name="product_id" /></form>',
mockRPC(route, { args, method }) {
if (method === "name_create") {
const error = new RPCError("Something went wrong");
error.exceptionName = "odoo.exceptions.ValidationError";
throw error;
}
if (method === "create") {
assert.deepEqual(args[0], { name: "xyz" });
}
},
});
await editInput(target, ".o_field_widget[name='product_id'] input", "abcd");
await click(target.querySelector(".o_field_widget[name='product_id'] .dropdown-item"));
await nextTick(); // wait for the error service to ensure that there's no error dialog
assert.containsNone(target, ".o_error_dialog");
assert.containsOnce(target, ".modal .o_form_view");
assert.strictEqual(
target.querySelector(".modal .o_field_widget[name='name'] input").value,
"abcd"
);
await editInput(target, ".modal .o_field_widget[name='name'] input", "xyz");
await click(target, ".modal .o_form_button_save");
assert.strictEqual(
target.querySelector(".o_field_widget[name='product_id'] input").value,
"xyz"
);
}
);
QUnit.test("failing quick create on a many2one", async function (assert) {
assert.expect(5);
registry.category("services").add("error", errorService);
// remove the override in qunit.js that swallows unhandledrejection errors
// s.t. we let the error service handle them
const originalOnUnhandledRejection = window.onunhandledrejection;
window.onunhandledrejection = () => {};
registerCleanup(() => {
window.onunhandledrejection = originalOnUnhandledRejection;
});
serverData.views = {
"product,false,form": '<form><field name="name" /></form>',
};
@@ -2991,75 +3036,66 @@ QUnit.module("Fields", (hooks) => {
arch: '<form><field name="product_id" /></form>',
mockRPC(route, { args, method }) {
if (method === "name_create") {
throw new RPCError("Something went wrong");
}
if (method === "create") {
assert.deepEqual(args[0], { name: "xyz" });
return new RPCError("Something went wrong");
}
},
});
await editInput(target, ".o_field_widget[name='product_id'] input", "abcd");
await click(target.querySelector(".o_field_widget[name='product_id'] .dropdown-item"));
await nextTick(); // wait for the error service to ensure that there's no error dialog
assert.containsNone(target, ".o_error_dialog");
assert.containsOnce(target, ".modal .o_form_view");
assert.strictEqual(
target.querySelector(".modal .o_field_widget[name='name'] input").value,
"abcd"
);
await editInput(target, ".modal .o_field_widget[name='name'] input", "xyz");
await click(target, ".modal .o_form_button_save");
assert.strictEqual(
target.querySelector(".o_field_widget[name='product_id'] input").value,
"xyz"
);
await nextTick(); // wait for the error service
assert.containsOnce(target, ".o_error_dialog");
assert.containsNone(target, ".modal .o_form_view");
});
QUnit.test("failing quick create on a many2one inside a one2many", async function (assert) {
assert.expect(4);
QUnit.test(
"failing quick create on a many2one inside a one2many because ValidationError",
async function (assert) {
assert.expect(4);
serverData.views = {
"partner,false,list": `
serverData.views = {
"partner,false,list": `
<tree editable="bottom">
<field name="product_id" />
</tree>`,
"product,false,form": '<form><field name="name" /></form>',
};
"product,false,form": '<form><field name="name" /></form>',
};
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: '<form><field name="p" /></form>',
mockRPC(route, { args, method }) {
if (method === "name_create") {
throw new RPCError("Something went wrong");
}
if (method === "create") {
assert.deepEqual(args[0], { name: "xyz" });
}
},
});
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: '<form><field name="p" /></form>',
mockRPC(route, { args, method }) {
if (method === "name_create") {
const error = new RPCError("Something went wrong");
error.exceptionName = "odoo.exceptions.ValidationError";
throw error;
}
if (method === "create") {
assert.deepEqual(args[0], { name: "xyz" });
}
},
});
await click(target, ".o_field_x2many_list_row_add a");
await editInput(target, ".o_field_widget[name='product_id'] input", "abcd");
await click(target.querySelector(".o_field_widget[name='product_id'] .dropdown-item"));
await click(target, ".o_field_x2many_list_row_add a");
await editInput(target, ".o_field_widget[name='product_id'] input", "abcd");
await click(target.querySelector(".o_field_widget[name='product_id'] .dropdown-item"));
assert.containsOnce(target, ".modal .o_form_view");
assert.strictEqual(
target.querySelector(".modal .o_field_widget[name='name'] input").value,
"abcd"
);
assert.containsOnce(target, ".modal .o_form_view");
assert.strictEqual(
target.querySelector(".modal .o_field_widget[name='name'] input").value,
"abcd"
);
await editInput(target, ".modal .o_field_widget[name='name'] input", "xyz");
await click(target, ".modal .o_form_button_save");
assert.strictEqual(
target.querySelector(".o_field_widget[name='product_id'] input").value,
"xyz"
);
});
await editInput(target, ".modal .o_field_widget[name='name'] input", "xyz");
await click(target, ".modal .o_form_button_save");
assert.strictEqual(
target.querySelector(".o_field_widget[name='product_id'] input").value,
"xyz"
);
}
);
QUnit.test("slow create on a many2one", async function (assert) {
serverData.views = {