From 2b1df29095b3f1cb6b1593c0e1f5e224e06f005b Mon Sep 17 00:00:00 2001 From: Joseph Caburnay Date: Tue, 4 Oct 2022 13:17:08 +0000 Subject: [PATCH] [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) --- .../src/views/fields/x2many/x2many_field.js | 1 + .../static/src/views/list/list_renderer.js | 3 +- .../web/static/tests/views/list_view_tests.js | 85 +++++++++++++++++++ 3 files changed, 88 insertions(+), 1 deletion(-) diff --git a/addons/web/static/src/views/fields/x2many/x2many_field.js b/addons/web/static/src/views/fields/x2many/x2many_field.js index f30b45c1e3b..166070a30df 100644 --- a/addons/web/static/src/views/fields/x2many/x2many_field.js +++ b/addons/web/static/src/views/fields/x2many/x2many_field.js @@ -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 = diff --git a/addons/web/static/src/views/list/list_renderer.js b/addons/web/static/src/views/list/list_renderer.js index 3ec8c29b7ba..10f576b7c6f 100644 --- a/addons/web/static/src/views/list/list_renderer.js +++ b/addons/web/static/src/views/list/list_renderer.js @@ -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 }; diff --git a/addons/web/static/tests/views/list_view_tests.js b/addons/web/static/tests/views/list_view_tests.js index 261d0d2acfb..6b731550462 100644 --- a/addons/web/static/tests/views/list_view_tests.js +++ b/addons/web/static/tests/views/list_view_tests.js @@ -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: ` +
+ + + + + + + + +
`, + }); + 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: ` +
+ + + + + + + + +
`, + }); + 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"); + }); });