Skip to content

Commit

Permalink
[FIX] web: auto save record when changing value of 'toggle' fields
Browse files Browse the repository at this point in the history
Before 16.0 and the "always edit" form views, some fields allowed to
be edited in readonly (e.g. the statusbar in crm lead or project
task). Users could thus change the stage of a record in readonly and
the change was saved directly. If the field was tracked, the change
was even logged in the chatter directly. Since the form view is always
in edition now, we loose that behavior and the user must click on the
save cloud icon to manually save and see the tracking messages. To
mitigate this, those fields that were editable in readonly could save
the record directly when edited, like buttons do.

Changing the values of the following fields should automatically save
the record.

- BooleanToggleField
- PriorityField
- StateSelectionField
- StatusBarField

To accomplish this, we introduce a second param to the update method
which can signal auto-save when calling update.

closes odoo#113848

Task-id: 3175672
Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
  • Loading branch information
caburj authored and aab-odoo committed Mar 9, 2023
1 parent fcafd60 commit 28e6b7e
Show file tree
Hide file tree
Showing 15 changed files with 123 additions and 32 deletions.
8 changes: 2 additions & 6 deletions addons/crm/static/tests/crm_rainbowman_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import "@crm/../tests/mock_server";
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
import {
click,
clickSave,
dragAndDrop,
getFixture,
} from '@web/../tests/helpers/utils';
Expand Down Expand Up @@ -131,8 +130,8 @@ QUnit.module('Crm Rainbowman Triggers', {
assert.verifySteps(['Go, go, go! Congrats for your first deal.']);
});

QUnit.test("first lead won, click on statusbar in edit mode then save", async function (assert) {
assert.expect(3);
QUnit.test("first lead won, click on statusbar in edit mode", async function (assert) {
assert.expect(2);

await makeView({
...this.testFormView,
Expand All @@ -141,9 +140,6 @@ QUnit.module('Crm Rainbowman Triggers', {
});

await click(target.querySelector(".o_statusbar_status button[data-value='3']"));
assert.verifySteps([]); // no message displayed yet

await clickSave(target);
assert.verifySteps(['Go, go, go! Congrats for your first deal.']);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ export class BooleanToggleField extends BooleanField {
get isReadonly() {
return this.props.record.isReadonly(this.props.name);
}
onChange(newValue) {
this.props.update(newValue, { save: true });
}
}

BooleanToggleField.template = "web.BooleanToggleField";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { BooleanToggleField } from "./boolean_toggle_field";
export class ListBooleanToggleField extends BooleanToggleField {
onClick() {
if (!this.props.readonly) {
this.props.update(!this.props.value);
this.props.update(!this.props.value, { save: true });
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions addons/web/static/src/views/fields/field.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,16 +160,16 @@ export class Field extends Component {

return {
...fieldInfo.props,
update: async (value) => {
update: async (value, options = {}) => {
const { save } = Object.assign({ save: false }, options);
await record.update({ [this.props.name]: value });
if (record.selected && record.model.multiEdit) {
return;
}
const rootRecord =
record.model.root instanceof record.constructor && record.model.root;
const isInEdition = rootRecord ? rootRecord.isInEdition : record.isInEdition;
// We save only if we're on view mode readonly and no readonly field modifier
if (!isInEdition && !readonlyFromModifiers) {
if ((!isInEdition && !readonlyFromModifiers) || save) {
// TODO: maybe move this in the model
return record.save();
}
Expand Down
4 changes: 2 additions & 2 deletions addons/web/static/src/views/fields/priority/priority_field.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ export class PriorityField extends Component {
onStarClicked(value) {
if (this.props.value === value) {
this.state.index = -1;
this.props.update(this.options[0][0]);
this.props.update(this.options[0][0], { save: true });
} else {
this.props.update(value);
this.props.update(value, { save: true });
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class StateSelectionField extends Component {
this.options.map((value) => ({
name: value[1],
action: () => {
this.props.update(value[0]);
this.props.update(value[0], { save: true });
},
})),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
</div>
</t>
<t t-foreach="availableOptions" t-as="option" t-key="option[0]">
<DropdownItem onSelected="() => props.update(option[0])">
<DropdownItem onSelected="() => props.update(option[0], { save: true })">
<div class="d-flex align-items-center">
<span t-attf-class="o_status {{ statusColor(option[0]) }} "/>
<span class="ms-2" t-esc="option[1]"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,10 @@ export class StatusBarField extends Component {
selectItem(item) {
switch (this.props.type) {
case "many2one":
this.props.update([item.id, item.name]);
this.props.update([item.id, item.name], { save: true });
break;
case "selection":
this.props.update(item.id);
this.props.update(item.id, { save: true });
break;
}
}
Expand Down
20 changes: 20 additions & 0 deletions addons/web/static/tests/views/fields/boolean_toggle_field_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,4 +237,24 @@ QUnit.module("Fields", (hooks) => {
);
}
);

QUnit.test("BooleanToggleField - auto save record when field toggled", async function (assert) {
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: `
<form>
<field name="bar" widget="boolean_toggle" />
</form>`,
resId: 1,
mockRPC(_route, { method }) {
if (method === "write") {
assert.step("write");
}
},
});
await click(target, ".o_field_widget[name='bar'] input");
assert.verifySteps(["write"]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -12241,7 +12241,7 @@ QUnit.module("Fields", (hooks) => {
assert.verifySteps(["get_views partner", "read partner", "read turtle"]);

await click(target, ".o_boolean_toggle");
assert.verifySteps(["onchange turtle", "onchange partner"]);
assert.verifySteps(["onchange turtle", "onchange partner", "write turtle", "read turtle"]);
});

QUnit.test("create a new record with an x2m invisible", async function (assert) {
Expand Down
28 changes: 28 additions & 0 deletions addons/web/static/tests/views/fields/priority_field_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -611,4 +611,32 @@ QUnit.module("Fields", (hooks) => {
assert.containsN(target, "a.fa-star", 2);
}
);

QUnit.test("PriorityField - auto save record when field toggled", async function (assert) {
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `
<form>
<sheet>
<group>
<field name="selection" widget="priority" />
</group>
</sheet>
</form>`,
mockRPC(_route, { method }) {
if (method === "write") {
assert.step("write");
}
},
});

const stars = target.querySelectorAll(
".o_field_widget .o_priority a.o_priority_star.fa-star-o"
);
await click(stars[stars.length - 1]);
assert.verifySteps(["write"]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -537,4 +537,33 @@ QUnit.module("Fields", (hooks) => {
assert.verifySteps(["write"]);
assert.hasClass(target.querySelector(".o_field_state_selection span"), "o_status_green");
});

QUnit.test(
"StateSelectionField - auto save record when field toggled",
async function (assert) {
await makeView({
type: "form",
resModel: "partner",
serverData,
arch: `
<form>
<sheet>
<group>
<field name="selection" widget="state_selection"/>
</group>
</sheet>
</form>`,
resId: 1,
mockRPC(_route, { method }) {
if (method === "write") {
assert.step("write");
}
},
});

await click(target, ".o_field_widget.o_field_state_selection .o_status");
await click(target, ".dropdown-menu .dropdown-item:last-child");
assert.verifySteps(["write"]);
}
);
});
25 changes: 25 additions & 0 deletions addons/web/static/tests/views/fields/statusbar_field_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -598,4 +598,29 @@ QUnit.module("Fields", (hooks) => {
await nextTick();
assert.containsNone(target, ".modal", "command palette should not open");
});

QUnit.test("auto save record when field toggled", async function (assert) {
await makeView({
type: "form",
resModel: "partner",
resId: 1,
serverData,
arch: `
<form>
<header>
<field name="trululu" widget="statusbar" options="{'clickable': 1}" />
</header>
</form>`,
mockRPC(_route, { method }) {
if (method === "write") {
assert.step("write");
}
},
});
const clickableButtons = target.querySelectorAll(
".o_statusbar_status button.btn:not(.dropdown-toggle):not(:disabled):not(.o_arrow_button_current)"
);
await click(clickableButtons[clickableButtons.length - 1]);
assert.verifySteps(["write"]);
});
});
6 changes: 4 additions & 2 deletions addons/web/static/tests/views/kanban_view_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -1724,7 +1724,7 @@ QUnit.module("Views", (hooks) => {
});

QUnit.test("quick create record with quick_create_view", async (assert) => {
assert.expect(19);
assert.expect(20);

serverData.views["partner,some_view_ref,form"] =
"<form>" +
Expand Down Expand Up @@ -1795,6 +1795,7 @@ QUnit.module("Views", (hooks) => {
"get_views", // form view in quick create
"onchange", // quick create
"create", // should perform a create to create the record
"read",
"onchange", // new quick create
"read", // read the created record
]);
Expand Down Expand Up @@ -2024,7 +2025,7 @@ QUnit.module("Views", (hooks) => {
});

QUnit.test("quick create record in grouped on m2o (with quick_create_view)", async (assert) => {
assert.expect(15);
assert.expect(16);

serverData.views["partner,some_view_ref,form"] =
"<form>" +
Expand Down Expand Up @@ -2086,6 +2087,7 @@ QUnit.module("Views", (hooks) => {
"get_views", // form view in quick create
"onchange", // quick create
"create", // should perform a create to create the record
"read",
"onchange", // reopen the quick create automatically
"read", // read the created record
]);
Expand Down
14 changes: 1 addition & 13 deletions addons/web/static/tests/views/list_view_tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -2175,12 +2175,7 @@ QUnit.module("Views", (hooks) => {
</form>`,
mockRPC(route, args) {
if (args.method === "write") {
assert.deepEqual(args.args[1], {
o2m: [
[1, 1, { grosminet: false }],
[4, 2, false],
],
});
assert.deepEqual(args.args[1], { grosminet: false });
}
},
});
Expand All @@ -2191,7 +2186,6 @@ QUnit.module("Views", (hooks) => {
assert.hasClass(target.querySelector(".o_data_row"), "o_selected_row");
assert.containsOnce(target.querySelectorAll(".o_data_cell")[0], ".o_readonly_modifier");
await click(target.querySelectorAll(".o_data_cell")[1], ".o_boolean_toggle input");
await clickSave(target);
});

QUnit.test(
Expand Down Expand Up @@ -2270,12 +2264,6 @@ QUnit.module("Views", (hooks) => {
await click(target.querySelector(".o_data_row .o_data_cell .o_field_boolean_toggle div"));
assert.containsOnce(target, ".o_selected_row");
await click(target.querySelector(".o_selected_row .o_field_boolean_toggle div"));
assert.containsOnce(target, ".o_selected_row");
assert.verifySteps([]);

// save
await clickSave(target);
assert.containsNone(target, ".o_selected_row");
assert.verifySteps(["write: true"]);
});

Expand Down

0 comments on commit 28e6b7e

Please sign in to comment.