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"); + }); });