summaryrefslogtreecommitdiff
path: root/addons/web/static
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2019-02-28 13:47:09 +0100
committerChristophe Simonis <chs@odoo.com>2019-02-28 13:47:09 +0100
commit06898fac3c9b21d4fd5ef7efd35f829cdff15e46 (patch)
tree6b6d657677778365b06bbf4fb7babcab688edcc2 /addons/web/static
parent012816f85f8f20243bd3dfb55abdfaac59722284 (diff)
parentf9d3b5b7dfe15fb53f14fa0a0ae91bbeded9939b (diff)
[MERGE] forward port branch saas-12.1 up to f9d3b5b7dfe
Diffstat (limited to 'addons/web/static')
-rw-r--r--addons/web/static/src/js/fields/relational_fields.js34
-rw-r--r--addons/web/static/src/js/views/abstract_view.js3
-rw-r--r--addons/web/static/src/js/views/control_panel/control_panel_view.js17
-rw-r--r--addons/web/static/src/js/views/view_dialogs.js2
-rw-r--r--addons/web/static/tests/fields/relational_fields/field_many2one_tests.js103
-rw-r--r--addons/web/static/tests/helpers/mock_server.js4
-rw-r--r--addons/web/static/tests/helpers/test_utils_fields.js11
7 files changed, 162 insertions, 12 deletions
diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js
index d30d263fa12..bd46faf1e08 100644
--- a/addons/web/static/src/js/fields/relational_fields.js
+++ b/addons/web/static/src/js/fields/relational_fields.js
@@ -107,6 +107,7 @@ var FieldMany2One = AbstractField.extend({
'click': '_onClick',
}),
AUTOCOMPLETE_DELAY: 200,
+ SEARCH_MORE_LIMIT: 320,
/**
* @override
@@ -501,18 +502,33 @@ var FieldMany2One = AbstractField.extend({
values.push({
label: _t("Search More..."),
action: function () {
- self._rpc({
+ var prom;
+ if (search_val !== '') {
+ prom = self._rpc({
model: self.field.relation,
method: 'name_search',
kwargs: {
name: search_val,
args: domain,
operator: "ilike",
- limit: 160,
+ limit: self.SEARCH_MORE_LIMIT,
context: context,
},
- })
- .then(self._searchCreatePopup.bind(self, "search"));
+ });
+ }
+ $.when(prom).then(function (results) {
+ var dynamicFilters;
+ if (results) {
+ var ids = _.map(results, function (x) {
+ return x[0];
+ });
+ dynamicFilters = [{
+ description: _.str.sprintf(_t('Quick search: %s'), search_val),
+ domain: [['id', 'in', ids]],
+ }];
+ }
+ self._searchCreatePopup("search", false, {}, dynamicFilters);
+ });
},
classname: 'o_m2o_dropdown_option',
});
@@ -555,19 +571,25 @@ var FieldMany2One = AbstractField.extend({
/**
* all search/create popup handling
*
+ * TODO: ids argument is no longer used, remove it in master (as well as
+ * initial_ids param of the dialog)
+ *
* @private
* @param {any} view
* @param {any} ids
* @param {any} context
+ * @param {Object[]} [dynamicFilters=[]] filters to add to the search view
+ * in the dialog (each filter has keys 'description' and 'domain')
*/
- _searchCreatePopup: function (view, ids, context) {
+ _searchCreatePopup: function (view, ids, context, dynamicFilters) {
var self = this;
return new dialogs.SelectCreateDialog(this, _.extend({}, this.nodeOptions, {
res_model: this.field.relation,
domain: this.record.getDomain({fieldName: this.name}),
context: _.extend({}, this.record.getContext(this.recordParams), context || {}),
+ dynamicFilters: dynamicFilters || [],
title: (view === 'search' ? _t("Search: ") : _t("Create: ")) + this.string,
- initial_ids: ids ? _.map(ids, function (x) { return x[0]; }) : undefined,
+ initial_ids: ids,
initial_view: view,
disable_multiple_selection: true,
no_create: !self.can_create,
diff --git a/addons/web/static/src/js/views/abstract_view.js b/addons/web/static/src/js/views/abstract_view.js
index 72108e3c8ad..ce8951f6ee8 100644
--- a/addons/web/static/src/js/views/abstract_view.js
+++ b/addons/web/static/src/js/views/abstract_view.js
@@ -78,6 +78,8 @@ var AbstractView = Factory.extend({
* @param {string} [params.controllerState]
* @param {string} [params.displayName]
* @param {Array[]} [params.domain=[]]
+ * @param {Object[]} [params.dynamicFilters] transmitted to the
+ * ControlPanelView
* @param {number[]} [params.ids]
* @param {boolean} [params.isEmbedded=false]
* @param {Object} [params.searchQuery={}]
@@ -165,6 +167,7 @@ var AbstractView = Factory.extend({
this.controlPanelParams = {
action: action,
activateDefaultFavorite: params.activateDefaultFavorite,
+ dynamicFilters: params.dynamicFilters,
breadcrumbs: params.breadcrumbs,
context: this.loadParams.context,
domain: this.loadParams.domain,
diff --git a/addons/web/static/src/js/views/control_panel/control_panel_view.js b/addons/web/static/src/js/views/control_panel/control_panel_view.js
index 2bde26340e7..0a140e1f901 100644
--- a/addons/web/static/src/js/views/control_panel/control_panel_view.js
+++ b/addons/web/static/src/js/views/control_panel/control_panel_view.js
@@ -43,6 +43,10 @@ var ControlPanelView = Factory.extend({
* breadcrumbs won't be rendered
* @param {boolean} [params.withSearchBar=true] if set to false, no default
* search bar will be rendered
+ * @param {Object[]} [params.dynamicFilters=[]] filters to add to the
+ * search (in addition to those described in the arch), each filter being
+ * an object with keys 'description' (what is displayed in the searchbar)
+ * and 'domain'
*/
init: function (params) {
var self = this;
@@ -101,6 +105,19 @@ var ControlPanelView = Factory.extend({
INTERVAL_OPTIONS = INTERVAL_OPTIONS.map(function (option) {
return _.extend(option, {description: option.description.toString()});
});
+
+ // add a filter group with the dynamic filters, if any
+ if (params.dynamicFilters && params.dynamicFilters.length) {
+ var dynamicFiltersGroup = params.dynamicFilters.map(function (filter) {
+ return {
+ description: filter.description,
+ domain: JSON.stringify(filter.domain),
+ isDefault: true,
+ type: 'filter',
+ };
+ });
+ this.loadParams.groups.unshift(dynamicFiltersGroup);
+ }
},
//--------------------------------------------------------------------------
diff --git a/addons/web/static/src/js/views/view_dialogs.js b/addons/web/static/src/js/views/view_dialogs.js
index 65fee4a01b4..befe4247c00 100644
--- a/addons/web/static/src/js/views/view_dialogs.js
+++ b/addons/web/static/src/js/views/view_dialogs.js
@@ -317,6 +317,7 @@ var SelectCreateDialog = ViewDialog.extend({
* - list_view_options: dict of options to pass to the List View
* - on_selected: optional callback to execute when records are selected
* - disable_multiple_selection: true to allow create/select multiple records
+ * - dynamicFilters: filters to add to the searchview
*/
init: function () {
this._super.apply(this, arguments);
@@ -359,6 +360,7 @@ var SelectCreateDialog = ViewDialog.extend({
controlPanelFieldsView: fieldsViews.search,
},
action_buttons: false,
+ dynamicFilters: this.options.dynamicFilters,
context: this.context,
domain: domain,
hasSelectors: !this.options.disable_multiple_selection,
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 84fac135ad2..04b58b017ba 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
@@ -958,7 +958,7 @@ QUnit.module('fields', {}, function () {
return result;
},
});
- await testUtils.fields.many2one.searchAndClickItem('trululu', 'b');
+ await testUtils.fields.many2one.searchAndClickItem('trululu', {search: 'b'});
testUtils.form.clickSave(form);
assert.verifySteps(['name_create'],
@@ -2596,6 +2596,107 @@ QUnit.module('fields', {}, function () {
form.destroy();
});
+ QUnit.test('search more in many2one: no text in input', async function (assert) {
+ // when the user clicks on 'Search More...' in a many2one dropdown, and there is no text
+ // in the input (i.e. no value to search on), we bypass the name_search that is meant to
+ // return a list of preselected ids to filter on in the list view (opened in a dialog)
+ assert.expect(6);
+
+ for (var i = 0; i < 8; i++) {
+ this.data.partner.records.push({id: 100 + i, display_name: 'test_' + i});
+ }
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form><field name="trululu"/></form>',
+ archs: {
+ 'partner,false,list': '<list><field name="display_name"/></list>',
+ 'partner,false,search': '<search></search>',
+ },
+ mockRPC: function (route, args) {
+ assert.step(args.method || route);
+ if (route === '/web/dataset/search_read') {
+ assert.deepEqual(args.domain, [],
+ "should not preselect ids as there as nothing in the m2o input");
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ await testUtils.fields.many2one.searchAndClickItem('trululu', {
+ item: 'Search More',
+ search: '',
+ });
+
+ assert.verifySteps([
+ 'default_get',
+ 'name_search', // to display results in the dropdown
+ 'load_views', // list view in dialog
+ '/web/dataset/search_read', // to display results in the dialog
+ ]);
+
+ form.destroy();
+ });
+
+ QUnit.test('search more in many2one: text in input', async function (assert) {
+ // when the user clicks on 'Search More...' in a many2one dropdown, and there is some
+ // text in the input, we perform a name_search to get a (limited) list of preselected
+ // ids and we add a dynamic filter (with those ids) to the search view in the dialog, so
+ // that the user can remove this filter to bypass the limit
+ assert.expect(12);
+
+ for (var i = 0; i < 8; i++) {
+ this.data.partner.records.push({id: 100 + i, display_name: 'test_' + i});
+ }
+
+ var expectedDomain;
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form><field name="trululu"/></form>',
+ archs: {
+ 'partner,false,list': '<list><field name="display_name"/></list>',
+ 'partner,false,search': '<search></search>',
+ },
+ mockRPC: function (route, args) {
+ assert.step(args.method || route);
+ if (route === '/web/dataset/search_read') {
+ assert.deepEqual(args.domain, expectedDomain);
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ expectedDomain = [['id', 'in', [100, 101, 102, 103, 104, 105, 106, 107]]];
+ await testUtils.fields.many2one.searchAndClickItem('trululu', {
+ item: 'Search More',
+ search: 'test',
+ });
+
+ assert.containsOnce(document.body, '.modal .o_list_view');
+ assert.containsOnce(document.body, '.modal .o_cp_searchview .o_facet_values',
+ "should have a special facet for the pre-selected ids");
+
+ // remove the filter on ids
+ expectedDomain = [];
+ testUtils.dom.click($('.modal .o_cp_searchview .o_facet_remove'));
+
+ assert.verifySteps([
+ 'default_get',
+ 'name_search', // empty search, triggered when the user clicks in the input
+ 'name_search', // to display results in the dropdown
+ 'name_search', // to get preselected ids matching the search
+ 'load_views', // list view in dialog
+ '/web/dataset/search_read', // to display results in the dialog
+ '/web/dataset/search_read', // after removal of dynamic filter
+ ]);
+
+ form.destroy();
+ });
+
QUnit.test('updating a many2one from a many2many', function (assert) {
assert.expect(4);
diff --git a/addons/web/static/tests/helpers/mock_server.js b/addons/web/static/tests/helpers/mock_server.js
index 09f6852951b..ff75722b333 100644
--- a/addons/web/static/tests/helpers/mock_server.js
+++ b/addons/web/static/tests/helpers/mock_server.js
@@ -705,8 +705,8 @@ var MockServer = Class.extend({
var result = _.map(records, function (record) {
return [record.id, record.display_name];
});
- if (args.limit) {
- return result.slice(0, args.limit);
+ if (_kwargs.limit) {
+ return result.slice(0, _kwargs.limit);
}
return result;
},
diff --git a/addons/web/static/tests/helpers/test_utils_fields.js b/addons/web/static/tests/helpers/test_utils_fields.js
index f7d8829ce82..f0f29839a9d 100644
--- a/addons/web/static/tests/helpers/test_utils_fields.js
+++ b/addons/web/static/tests/helpers/test_utils_fields.js
@@ -125,15 +125,16 @@ function clickM2OItem(fieldName, searchText) {
* - click to open the dropdown
* - enter a search string in the input
* - wait for the selection
- * - click on the active menuitem
+ * - click on the requested menuitem, or the active one by default
*
* Example:
- * testUtils.fields.many2one.searchAndClickM2OItem('partner_id', 'George');
+ * testUtils.fields.many2one.searchAndClickM2OItem('partner_id', {search: 'George'});
*
* @param {string} fieldName
* @param {[Object]} options
* @param {[string]} options.selector
* @param {[string]} options.search
+ * @param {[string]} options.item
* @returns {Promise}
*/
function searchAndClickM2OItem(fieldName, options) {
@@ -156,7 +157,11 @@ function searchAndClickM2OItem(fieldName, options) {
}
return $.when(def).then(function () {
- clickM2OHighlightedItem(fieldName, options.selector);
+ if (options.item) {
+ clickM2OItem(fieldName, options.item);
+ } else {
+ clickM2OHighlightedItem(fieldName, options.selector);
+ }
});
}
return {