From e713ed2a69af9fe97fbe2d54843adc8f1ce689aa Mon Sep 17 00:00:00 2001 From: "Lucas Perais (lpe)" Date: Tue, 20 Nov 2018 09:00:43 +0000 Subject: [PATCH] [FIX] web: implement filter orderedBy related to: #28446 On a pure list view, click on a column to sort the records by that field. Save the filter as favorite Reload, now apply the filter Before this commit, there were two issues: 1. The order of the list was never propagated to search view, which couldn't save the filter with its attribute "sort" 2. There was no mechanism to load the attribute sort from a loading Filter After this commit, those two issues are corrected Unfortunately, complex search view flows are not testable in v11.0 OPW 1906968 closes odoo/odoo#28766 --- .../web/static/src/js/chrome/search_menus.js | 23 ++++- .../static/src/js/views/basic/basic_model.js | 3 +- .../src/js/views/list/list_controller.js | 9 ++ addons/web/static/tests/views/list_tests.js | 93 +++++++++++++++++++ 4 files changed, 125 insertions(+), 3 deletions(-) diff --git a/addons/web/static/src/js/chrome/search_menus.js b/addons/web/static/src/js/chrome/search_menus.js index d41052c882e..99e8bd6e799 100644 --- a/addons/web/static/src/js/chrome/search_menus.js +++ b/addons/web/static/src/js/chrome/search_menus.js @@ -100,9 +100,17 @@ return Widget.extend({ var controllerContext; this.trigger_up('get_controller_context', { callback: function (ctx) { - controllerContext = ctx; + if (ctx && ctx.orderedBy) { + var sort = _.map(ctx.orderedBy, function (obj) { + var order = obj.asc ? 'ASC' : 'DESC'; + return obj.name + ' ' + order; + }); + self.searchview.dataset.set_sort(sort); + } + controllerContext = _.omit(ctx, ['orderedBy']); }, }); + var results = pyeval.eval_domains_and_contexts({ domains: search.domains, contexts: [user_context].concat(search.contexts.concat(controllerContext || [])), @@ -182,7 +190,18 @@ return Widget.extend({ category: _t("Custom Filter"), icon: 'fa-star', field: { - get_context: function () { return filter.context; }, + get_context: function () { + var sortParsed = JSON.parse(filter.sort || "[]"); + var orderedBy = []; + _.each(sortParsed, function (sort) { + orderedBy.push({ + name: sort[0] === '-' ? sort.slice(1) : sort, + asc: sort[0] === '-' ? false : true, + }); + }); + + return _.defaults({}, filter.context, {orderedBy : orderedBy}); + }, get_groupby: function () { return [filter.context]; }, get_domain: function () { return filter.domain; } }, diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js index b9832996de5..0372f7cd5e7 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -3332,7 +3332,7 @@ var BasicModel = AbstractModel.extend({ model: params.modelName, offset: params.offset || (type === 'record' ? _.indexOf(res_ids, res_id) : 0), openGroupByDefault: params.openGroupByDefault, - orderedBy: params.orderedBy || [], + orderedBy: params.orderedBy || (params.context && params.context.orderedBy) || [], orderedResIDs: params.orderedResIDs, parentID: params.parentID, rawContext: params.rawContext, @@ -4016,6 +4016,7 @@ var BasicModel = AbstractModel.extend({ if (options.context !== undefined) { element.context = options.context; + element.orderedBy = options.context.orderedBy || element.orderedBy; } if (options.domain !== undefined) { element.domain = options.domain; diff --git a/addons/web/static/src/js/views/list/list_controller.js b/addons/web/static/src/js/views/list/list_controller.js index 02ee76bf919..df23fffa54c 100644 --- a/addons/web/static/src/js/views/list/list_controller.js +++ b/addons/web/static/src/js/views/list/list_controller.js @@ -78,6 +78,14 @@ var ListController = BasicController.extend({ return $.Deferred().resolve(); } }, + /* + * @override + */ + getContext: function () { + var context = this._super.apply(this, arguments); + context.orderedBy = this.model.get(this.handle, {raw: true}).orderedBy || []; + return context; + }, /** * Returns the list of currently selected res_ids (with the check boxes on * the left) @@ -179,6 +187,7 @@ var ListController = BasicController.extend({ } else { this.selectedRecords = []; } + params.selectedRecords = this.selectedRecords; return this._super.apply(this, arguments); }, diff --git a/addons/web/static/tests/views/list_tests.js b/addons/web/static/tests/views/list_tests.js index bd4c8f8e339..fb99828d2fe 100644 --- a/addons/web/static/tests/views/list_tests.js +++ b/addons/web/static/tests/views/list_tests.js @@ -307,6 +307,99 @@ QUnit.module('Views', { list.destroy(); }); + QUnit.test('ordered list, sort attribute in context', function (assert) { + assert.expect(1); + // Equivalent to saving a custom filter + + this.data.foo.fields.foo.sortable = true; + this.data.foo.fields.date.sortable = true; + + var list = createView({ + View: ListView, + model: 'foo', + data: this.data, + arch: '' + + '' + + '' + + '', + }); + + // Descending order on Foo + list.$('th.o_column_sortable:contains("Foo")').click(); + list.$('th.o_column_sortable:contains("Foo")').click(); + + // Ascending order on Date + list.$('th.o_column_sortable:contains("Date")').click(); + + var listContext = list.getContext(); + assert.deepEqual(listContext, + { + orderedBy: [{ + name: 'date', + asc: true, + }, { + name: 'foo', + asc: false, + }] + }, 'the list should have the right orderedBy in context'); + list.destroy(); + }); + + QUnit.test('Loading a filter with a sort attribute', function (assert) { + assert.expect(2); + + this.data.foo.fields.foo.sortable = true; + this.data.foo.fields.date.sortable = true; + + var searchReads = 0; + var list = createView({ + View: ListView, + model: 'foo', + data: this.data, + arch: '' + + '' + + '' + + '', + context: { + orderedBy: [{ + name: 'date', + asc: true, + }, { + name: 'foo', + asc: false, + }] + }, + mockRPC: function (route, args) { + if (route === '/web/dataset/search_read') { + if (searchReads === 0) { + assert.strictEqual(args.sort, 'date ASC, foo DESC', + 'The sort attribute of the filter should be used by the initial search_read'); + } else if (searchReads === 1) { + assert.strictEqual(args.sort, 'date DESC, foo ASC', + 'The sort attribute of the filter should be used by the next search_read'); + } + searchReads += 1; + } + return this._super.apply(this,arguments); + }, + }); + + // Simulate loading a filter + list.update({ + context: { + orderedBy: [{ + name: 'date', + asc: false, + }, { + name: 'foo', + asc: true, + }] + } + }); + + list.destroy() + }); + QUnit.test('many2one field rendering', function (assert) { assert.expect(1);