[FIX] web: prevent resequence of readonly list field

**Current behavior before PR:**

Even when the "drag-handle" is already hidden (since the field is already readonly),
the items of x2m field can still be resequenced by the user.

**Desired behavior after PR is merged:**

User should not be able to resequence the list items when the list field is already
readonly.

**Solution**

If a shown list is representing a x2many field, it makes sense to prevent
resequencing when the field is "readonly". However, the list is oblivious
of that property unless we force it, therefore we introduce the "readonly"
props to the list renderer.

Note that a heuristic was tried to compute "resequenceability" based on
the "editable" property, but that is rather difficult if not impossible.

Also note that "readonly" defaults to "false" to be compatible with the
current situation where the list is rendered -- list view, field, select/
create dialog -- which by default, the items can be resequenced.

closes odoo/odoo#111797

X-original-commit: 7682a5c87b8e2f59a4c277143d6054adb40edef9
Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
Joseph Caburnay
2023-02-03 08:24:39 +01:00
parent 126f37734d
commit 2b1df29095
3 changed files with 88 additions and 1 deletions
@@ -195,6 +195,7 @@ export class X2ManyField extends Component {
props.archInfo = { ...archInfo, columns };
props.cycleOnTab = false;
props.editable = !this.props.readonly && editable;
props.readonly = this.props.readonly;
props.nestedKeyOptionalFieldsData = this.nestedKeyOptionalFieldsData;
props.onAdd = (params) => {
params.editable =
@@ -347,7 +347,7 @@ export class ListRenderer extends Component {
}
get canResequenceRows() {
if (!this.props.list.canResequence()) {
if (!this.props.list.canResequence() || this.props.readonly) {
return false;
}
const orderBy = this.props.list.orderBy;
@@ -1917,6 +1917,7 @@ ListRenderer.props = [
"editable?",
"noContentHelp?",
"nestedKeyOptionalFieldsData?",
"readonly?",
];
ListRenderer.defaultProps = { hasSelectors: false, cycleOnTab: true };
@@ -16958,4 +16958,89 @@ QUnit.module("Views", (hooks) => {
assert.containsN(target, ".o_data_row", 2);
});
QUnit.test("resequenceable list items", async function (assert) {
serverData.models.bar = {
fields: {
titi: { string: "Char", type: "char" },
int_field: { string: "Integer", type: "integer" },
},
records: [
{ id: 1, titi: "one", int_field: 1 },
{ id: 2, titi: "two", int_field: 2 },
],
};
serverData.models.foo.records[0].o2m = [1, 2];
await makeView({
type: "form",
resModel: "foo",
serverData,
resId: 1,
arch: `
<form>
<sheet>
<field name="o2m">
<tree>
<field name="int_field" widget="handle"/>
<field name="titi" readonly="1"/>
</tree>
</field>
</sheet>
</form>`,
});
assert.hasClass(target.querySelector(".o_data_row"), "o_row_draggable");
await dragAndDrop(
".o_data_row:nth-child(1) .o_handle_cell",
".o_data_row:nth-child(2) .o_handle_cell"
);
// After drag, first and second rows should have been swapped.
const first = target.querySelector(".o_data_row:nth-child(1) td[name=titi]");
const second = target.querySelector(".o_data_row:nth-child(2) td[name=titi]");
assert.strictEqual(first.textContent, "two", "'two' should now be the first child");
assert.strictEqual(second.textContent, "one", "'one' should now be the second child");
});
QUnit.test("readonly field x2m, don't allow resequencing", async function (assert) {
serverData.models.bar = {
fields: {
titi: { string: "Char", type: "char" },
int_field: { string: "Integer", type: "integer" },
},
records: [
{ id: 1, titi: "one", int_field: 1 },
{ id: 2, titi: "two", int_field: 2 },
],
};
serverData.models.foo.records[0].o2m = [1, 2];
await makeView({
type: "form",
resModel: "foo",
serverData,
resId: 1,
arch: `
<form>
<sheet>
<field name="o2m" readonly="1">
<tree>
<field name="int_field" widget="handle"/>
<field name="titi" readonly="1"/>
</tree>
</field>
</sheet>
</form>`,
});
assert.doesNotHaveClass(target.querySelector(".o_data_row"), "o_row_draggable");
await dragAndDrop(
".o_data_row:nth-child(1) .o_handle_cell",
".o_data_row:nth-child(2) .o_handle_cell"
);
// After drag, nothing should have changed.
// First row should still be 'one' and second row should still be 'two'.
const first = target.querySelector(".o_data_row:nth-child(1) td[name=titi]");
const second = target.querySelector(".o_data_row:nth-child(2) td[name=titi]");
assert.strictEqual(first.textContent, "one", "'one' should be kept as the first child");
assert.strictEqual(second.textContent, "two", "'two' should be kept as the second child");
});
});