[FIX] web: search more in many2one: filter on ids

Let's assume a many2one field with a lot of possible values. The
user clicks on 'Search More...'. In the opened dialog, only 160
records are available (their ids have been obtained with a
name_search, with the optional text the user could have typed in
the many2one input).

Before 4cd379cf, that extra domain on ids was removed automatically
as soon as the user interacted with the search view in the dialog.
This was especially useful when there were more than 160 records.
However, this was rather an happy coincidence than a designed
feature.

From 4cd379cf, the ids selection was added to the initial domain
of the list, so they couldn't be removed from the domain
afterwards. The user was thus stucked with its preselected 160
records.

This rev. doesn't restore the former behavior, but rather improves
the current one, as follows:
 - when there is no text in the many2one input (i.e. no value to
   filter on), we bypass the name_search, s.t. all records are
   available in the dialog
 - when there is some text in the input, we perform a name_search (as
   before) to get a list of record ids, and we add a special filter
   to the search view in the dialog (the filter on those ids), s.t.
   the user can remove it if he wants to access the remaining records.
 - finally, the limit is now set to 320, to mitigate the problem

Issue reported on the saas-12.1 migration pad.

closes odoo/odoo#31232
This commit is contained in:
Aaron Bohy
2019-02-26 08:26:51 +00:00
parent a76a958ca8
commit 6a8e1b53e3
7 changed files with 164 additions and 14 deletions
@@ -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,
@@ -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,
@@ -43,8 +43,12 @@ 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 {boolean} [activateDefaultFavorite=false] determine if the default
* custom filters can be activated
* @param {boolean} [params.activateDefaultFavorite] determine if the
* default custom filters can be activated
* @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;
@@ -103,6 +107,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);
}
},
//--------------------------------------------------------------------------
@@ -304,6 +304,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);
@@ -346,6 +347,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,