diff options
| author | Vincent Schippefilt <vsc@odoo.com> | 2019-09-27 13:47:52 +0000 |
|---|---|---|
| committer | Vincent Schippefilt <vsc@odoo.com> | 2019-09-30 08:57:23 +0000 |
| commit | e06117bd85c6b39dd62d4979b0c1c437b92dbafa (patch) | |
| tree | 4ce4e07b0fd8e8aa51cf1c5e6cc483323d6b807f | |
| parent | e8301cacb74175f4cb897e3dd2bc29192ab1a686 (diff) | |
[FIX] web: multi-hell-ditable list
only activate multi-edit on list with specific attribute.
When the attribute multi_edit="1" is set on the tree view the user
can select a/some records and it will activate the multi-edit
with confirmation dialog (even for a single record).
Also, on-change are not applied when using multi-edit
closes odoo/odoo#37525
Signed-off-by: VincentSchippefilt <VincentSchippefilt@users.noreply.github.com>
| -rw-r--r-- | addons/web/static/src/js/views/list/list_editable_renderer.js | 8 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/list/list_view.js | 18 | ||||
| -rw-r--r-- | addons/web/static/tests/fields/basic_fields_tests.js | 2 | ||||
| -rw-r--r-- | addons/web/static/tests/fields/relational_fields/field_many2one_tests.js | 2 | ||||
| -rw-r--r-- | addons/web/static/tests/views/list_tests.js | 131 | ||||
| -rw-r--r-- | doc/reference/views.rst | 4 | ||||
| -rw-r--r-- | odoo/addons/base/rng/tree_view.rng | 1 |
7 files changed, 123 insertions, 43 deletions
diff --git a/addons/web/static/src/js/views/list/list_editable_renderer.js b/addons/web/static/src/js/views/list/list_editable_renderer.js index 1f7bf20b79c..60e27998f18 100644 --- a/addons/web/static/src/js/views/list/list_editable_renderer.js +++ b/addons/web/static/src/js/views/list/list_editable_renderer.js @@ -39,12 +39,14 @@ ListRenderer.include({ * @param {boolean} params.addCreateLineInGroups * @param {boolean} params.addTrashIcon * @param {boolean} params.isMany2Many + * @param {boolean} params.isMultiEditable */ init: function (parent, state, params) { var self = this; this._super.apply(this, arguments); this.editable = params.editable; + this.isMultiEditable = params.isMultiEditable; this.columnWidths = false; // if addCreateLine (resp. addCreateLineInGroups) is true, the renderer @@ -348,7 +350,7 @@ ListRenderer.include({ * @returns {boolean} */ isInMultipleRecordEdition: function (recordId) { - return this.isEditable() && this.selection.length > 1 && this.selection.includes(recordId); + return this.isEditable() && this.isMultiEditable && this.selection.includes(recordId); }, /** * Returns whether the list can be edited. @@ -359,7 +361,7 @@ ListRenderer.include({ * @returns {boolean} */ isEditable: function () { - return this.editable || this.selection.length; + return this.editable || (this.isMultiEditable && this.selection.length); }, /** * Removes the line associated to the given recordID (the index of the row @@ -817,7 +819,7 @@ ListRenderer.include({ * @returns {boolean} */ _isRecordEditable: function (recordID) { - return this.editable || this.selection.includes(recordID); + return this.editable || (this.isMultiEditable && this.selection.includes(recordID)); }, /** * Moves to the next row in the list diff --git a/addons/web/static/src/js/views/list/list_view.js b/addons/web/static/src/js/views/list/list_view.js index f3f125ba8e3..bb6dfed8116 100644 --- a/addons/web/static/src/js/views/list/list_view.js +++ b/addons/web/static/src/js/views/list/list_view.js @@ -41,7 +41,6 @@ var ListView = BasicView.extend({ this._super.apply(this, arguments); var selectedRecords = []; // there is no selected records by default - var mode = this.arch.attrs.editable && !params.readonly ? "edit" : "readonly"; var pyevalContext = py.dict.fromJSON(_.pick(params.context, function(value, key, object) {return !_.isUndefined(value)}) || {}); var expandGroups = !!JSON.parse(pyUtils.py_eval(this.arch.attrs.expand || "0", {'context': pyevalContext})); @@ -52,24 +51,27 @@ var ListView = BasicView.extend({ } }); + let editable = false; + if ((!this.arch.attrs.edit || !!JSON.parse(this.arch.attrs.edit)) && !params.readonly) { + editable = this.arch.attrs.editable; + } + this.controllerParams.activeActions.export_xlsx = this.arch.attrs.export_xlsx ? !!JSON.parse(this.arch.attrs.export_xlsx): true; - this.controllerParams.editable = (!this.arch.attrs.edit || !!JSON.parse(this.arch.attrs.edit)) ? - this.arch.attrs.editable : false; + this.controllerParams.editable = editable; this.controllerParams.hasSidebar = params.hasSidebar; this.controllerParams.toolbarActions = viewInfo.toolbar; - this.controllerParams.mode = mode; + this.controllerParams.mode = editable ? 'edit' : 'readonly'; this.controllerParams.selectedRecords = selectedRecords; this.rendererParams.arch = this.arch; this.rendererParams.groupbys = this.groupbys; this.rendererParams.hasSelectors = 'hasSelectors' in params ? params.hasSelectors : true; - this.rendererParams.editable = - (!this.arch.attrs.edit || !!JSON.parse(this.arch.attrs.edit)) && !params.readonly ? - this.arch.attrs.editable : false; + this.rendererParams.editable = editable; this.rendererParams.selectedRecords = selectedRecords; this.rendererParams.addCreateLine = false; - this.rendererParams.addCreateLineInGroups = this.rendererParams.editable && this.controllerParams.activeActions.create; + this.rendererParams.addCreateLineInGroups = editable && this.controllerParams.activeActions.create; + this.rendererParams.isMultiEditable = this.arch.attrs.multi_edit && !!JSON.parse(this.arch.attrs.multi_edit); this.modelParams.groupbys = this.groupbys; diff --git a/addons/web/static/tests/fields/basic_fields_tests.js b/addons/web/static/tests/fields/basic_fields_tests.js index 5b5b5196e90..c4bfc8a4341 100644 --- a/addons/web/static/tests/fields/basic_fields_tests.js +++ b/addons/web/static/tests/fields/basic_fields_tests.js @@ -3711,7 +3711,7 @@ QUnit.module('basic_fields', { View: ListView, model: 'partner', data: this.data, - arch: '<tree editable="bottom">' + + arch: '<tree multi_edit="1">' + '<field name="datetime"/>' + '</tree>', translateParameters: { // Avoid issues due to localization formats diff --git a/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js b/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js index 4d5b5d8aa89..a8743a4cddf 100644 --- a/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js +++ b/addons/web/static/tests/fields/relational_fields/field_many2one_tests.js @@ -571,7 +571,7 @@ QUnit.module('fields', {}, function () { const list = await createView({ arch: ` - <tree editable="top"> + <tree multi_edit="1"> <field name="trululu"/> </tree>`, data: this.data, diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js index a889d8be389..8a71bc38266 100644 --- a/addons/web/static/tests/views/list_tests.js +++ b/addons/web/static/tests/views/list_tests.js @@ -2196,7 +2196,7 @@ QUnit.module('Views', { View: ListView, model: 'foo', data: this.data, - arch: '<tree editable="bottom">' + + arch: '<tree multi_edit="1">' + '<field name="datetime"/>' + '<field name="text"/>' + '</tree>', @@ -5312,7 +5312,7 @@ QUnit.module('Views', { View: ListView, model: 'foo', data: this.data, - arch: '<tree editable="top">' + + arch: '<tree multi_edit="1">' + '<field name="foo"/>' + '</tree>', mockRPC: function (route, args) { @@ -5346,7 +5346,7 @@ QUnit.module('Views', { assert.expect(2); const list = await createView({ - arch: `<tree editable="top"><field name="foo"/></tree>`, + arch: `<tree editable="top" multi_edit="1"><field name="foo"/></tree>`, data: this.data, model: 'foo', View: ListView, @@ -5375,7 +5375,7 @@ QUnit.module('Views', { View: ListView, model: 'foo', data: this.data, - arch: '<tree editable="bottom">' + + arch: '<tree editable="bottom" multi_edit="1">' + '<field name="foo"/>' + '<field name="int_field"/>' + '</tree>', @@ -5445,8 +5445,32 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('editable list view: multi edition and onchanges', async function (assert) { - assert.expect(10); + QUnit.test('create in multi editable list', async function (assert) { + assert.expect(1); + + var list = await createView({ + View: ListView, + model: 'foo', + data: this.data, + arch: '<tree multi_edit="1">' + + '<field name="foo"/>' + + '<field name="int_field"/>' + + '</tree>', + intercepts: { + switch_view: function (ev) { + assert.strictEqual(ev.data.view_type, 'form'); + }, + }, + }); + + // click on CREATE (should trigger a switch_view) + await testUtils.dom.click($('.o_list_button_add')); + + list.destroy(); + }); + + QUnit.test('editable list view: multi edition cannot call onchanges', async function (assert) { + assert.expect(15); this.data.foo.onchanges = { foo: function (obj) { @@ -5457,7 +5481,7 @@ QUnit.module('Views', { View: ListView, model: 'foo', data: this.data, - arch: '<tree editable="bottom">' + + arch: '<tree multi_edit="1">' + '<field name="foo"/>' + '<field name="int_field"/>' + '</tree>', @@ -5470,19 +5494,24 @@ QUnit.module('Views', { }, }); - // edit a single record to validate that onchanges work + assert.verifySteps(['/web/dataset/search_read']); + + // select and edit a single record + await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_list_record_selector input')); await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_data_cell:eq(0)')); await testUtils.fields.editInput(list.$('.o_field_widget[name=foo]'), 'hi'); await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_data_cell:eq(1)')); - await testUtils.dom.click(list.$('.o_list_button_discard')); - assert.containsOnce(document.body, '.modal'); // discard dialog + assert.containsOnce(document.body, '.modal'); // save dialog await testUtils.dom.click($('.modal .btn-primary')); - assert.verifySteps(['/web/dataset/search_read', 'onchange']); + assert.strictEqual(list.$('.o_data_row:eq(0) .o_data_cell').text(), "hi2"); + assert.strictEqual(list.$('.o_data_row:eq(1) .o_data_cell').text(), "blip9"); - // select two records - await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_list_record_selector input')); + assert.verifySteps(['write', 'read']); + + // select the second record (the first one is still selected) + assert.ok(list.$('.o_data_row:eq(0) .o_list_record_selector input').is(':checked')); await testUtils.dom.click(list.$('.o_data_row:eq(1) .o_list_record_selector input')); // edit foo, first row @@ -5508,7 +5537,7 @@ QUnit.module('Views', { View: ListView, model: 'foo', data: this.data, - arch: '<tree editable="bottom">' + + arch: '<tree multi_edit="1">' + '<field name="foo" required="1"/>' + '<field name="int_field"/>' + '</tree>', @@ -5556,7 +5585,7 @@ QUnit.module('Views', { View: ListView, model: 'foo', data: this.data, - arch: '<tree editable="bottom">' + + arch: '<tree multi_edit="1">' + '<field name="foo"/>' + '<field name="m2o"/>' + '</tree>', @@ -5592,7 +5621,7 @@ QUnit.module('Views', { const list = await createView({ arch: ` - <tree editable="top"> + <tree editable="top" multi_edit="1"> <field name="foo"/> </tree>`, data: this.data, @@ -5630,7 +5659,7 @@ QUnit.module('Views', { const list = await createView({ arch: ` - <tree editable="top"> + <tree multi_edit="1"> <field name="foo"/> </tree>`, data: this.data, @@ -5666,7 +5695,7 @@ QUnit.module('Views', { View: ListView, model: 'foo', data: this.data, - arch: '<tree editable="top">' + + arch: '<tree multi_edit="1">' + '<field name="id"/>' + '<field name="foo"/>' + '<field name="int_field" attrs=\'{"readonly": [("id", ">" , 2)]}\'/>' + @@ -5733,7 +5762,7 @@ QUnit.module('Views', { View: ListView, model: 'foo', data: this.data, - arch: '<tree editable="bottom">' + + arch: '<tree multi_edit="1">' + '<field name="foo" required="1"/>' + '</tree>', mockRPC: function (route, args) { @@ -5769,7 +5798,7 @@ QUnit.module('Views', { const list = await createView({ arch: ` - <tree> + <tree multi_edit="1"> <field name="foo"/> <field name="int_field"/> </tree>`, @@ -5827,7 +5856,7 @@ QUnit.module('Views', { View: ListView, model: 'foo', data: this.data, - arch: '<tree editable="bottom">' + + arch: '<tree multi_edit="1">' + '<field name="foo"/>' + '<field name="int_field"/>' + '</tree>', @@ -5860,7 +5889,7 @@ QUnit.module('Views', { const list = await createView({ arch: ` - <tree> + <tree multi_edit="1"> <field name="foo"/> </tree>`, data: this.data, @@ -5908,12 +5937,12 @@ QUnit.module('Views', { list.destroy(); }); - QUnit.test('editable readonly list view: single edition', async function (assert) { - assert.expect(2); + QUnit.test('editable readonly list view: single edition behaves like a multi-edition', async function (assert) { + assert.expect(3); const list = await createView({ arch: ` - <tree> + <tree multi_edit="1"> <field name="foo" required="1"/> </tree>`, data: this.data, @@ -5927,14 +5956,18 @@ QUnit.module('Views', { // edit a field (invalid input) await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_data_cell:eq(0)')); await testUtils.fields.editInput(list.$('.o_field_widget[name=foo]'), ""); - await testUtils.dom.click('body'); - assert.hasClass(list.$('.o_data_row:eq(0) .o_data_cell:eq(0)'), 'o_invalid_cell', - "like in standard single edit, field is marked as invalid"); + assert.containsOnce($('body'),'.modal', "should have a modal"); + await testUtils.dom.click($('.modal button.btn')); // edit a field + await testUtils.dom.click(list.$('.o_data_row:eq(0) .o_data_cell:eq(0)')); await testUtils.fields.editInput(list.$('.o_field_widget[name=foo]'), "bar"); - await testUtils.fields.triggerKeydown(list.$('.o_field_widget[name=foo]'), 'enter'); + + assert.containsOnce($('body'),'.modal', "should have a modal"); + await testUtils.dom.click($('.modal button.btn.btn-primary')); + + assert.strictEqual(list.$('.o_data_row:eq(0) .o_data_cell').text(), "bar", "the first row should be updated"); @@ -5946,7 +5979,7 @@ QUnit.module('Views', { const list = await createView({ arch: - `<tree> + `<tree multi_edit="1"> <field name="foo"/> <field name="int_field"/> </tree>`, @@ -8230,6 +8263,44 @@ QUnit.module('Views', { list.destroy(); delete widgetRegistry.map.test; }); + + QUnit.test('enter edition in editable list with multi_edit = 0', async function (assert) { + assert.expect(1); + + var list = await createView({ + View: ListView, + model: 'foo', + data: this.data, + arch: '<tree editable="top" multi_edit="0">' + + '<field name="int_field"/>' + + '</tree>', + }); + + // click on int_field cell of first row + await testUtils.dom.click(list.$('.o_data_row:first .o_data_cell:nth(0)')); + assert.strictEqual(document.activeElement.name, "int_field"); + + list.destroy(); + }); + + QUnit.test('enter edition in editable list with multi_edit = 1', async function (assert) { + assert.expect(1); + + var list = await createView({ + View: ListView, + model: 'foo', + data: this.data, + arch: '<tree editable="top" multi_edit="1">' + + '<field name="int_field"/>' + + '</tree>', + }); + + // click on int_field cell of first row + await testUtils.dom.click(list.$('.o_data_row:first .o_data_cell:nth(0)')); + assert.strictEqual(document.activeElement.name, "int_field"); + + list.destroy(); + }); }); }); diff --git a/doc/reference/views.rst b/doc/reference/views.rst index 9ab78b45073..afdc03a8b43 100644 --- a/doc/reference/views.rst +++ b/doc/reference/views.rst @@ -197,6 +197,10 @@ root can have the following attributes: .. note:: if the ``edit`` attribute is set to ``false``, the ``editable`` option will be ignored. +``multi_edit`` + editable or not editable list can activate the multi-edition feature by defining + the multi_edit=1 + ``default_order`` overrides the ordering of the view, replacing the model's default order. The value is a comma-separated list of fields, postfixed by ``desc`` to diff --git a/odoo/addons/base/rng/tree_view.rng b/odoo/addons/base/rng/tree_view.rng index e7373dc532a..64032125869 100644 --- a/odoo/addons/base/rng/tree_view.rng +++ b/odoo/addons/base/rng/tree_view.rng @@ -27,6 +27,7 @@ <rng:optional><rng:attribute name="create"/></rng:optional> <rng:optional><rng:attribute name="delete"/></rng:optional> <rng:optional><rng:attribute name="edit"/></rng:optional> + <rng:optional><rng:attribute name="multi_edit"/></rng:optional> <rng:optional><rng:attribute name="export_xlsx"/></rng:optional> <rng:optional><rng:attribute name="duplicate"/></rng:optional> <rng:optional><rng:attribute name="import"/></rng:optional> |
