[FIX] web: create favorite filters in dialogs

This is a manual forwardport of rev. b466b3bc60.

Consider the following scenario: open in edit mode a form view
containing a many2many field displayed with widget many2many_tags,
on the many2many field dropdown, click on 'Search More' (it opens
a dialog), and in the dialog, try to save the current search in
the searchview's favorite menu.

Before this rev., it crashed because it tried to retrieve the
action's context (to add to the saved filter), whereas there is no
action in this case.

Actually, the action's context should never been used in this case.
What must be used is the controller's context (this mainly makes
sense for Pivot and Graph views).

Fixes #23293
This commit is contained in:
Aaron Bohy
2018-03-06 09:12:21 +01:00
parent bc66da2dfa
commit cdb01d52ea
6 changed files with 199 additions and 4 deletions
@@ -68,6 +68,7 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, {
.load_filters(event.data.dataset, event.data.action_id)
.then(event.data.on_success);
},
create_filter: '_onCreateFilter',
push_state: '_onPushState',
show_effect: '_onShowEffect',
// session
@@ -337,6 +338,18 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, {
// Handlers
//--------------------------------------------------------------------------
/**
* @private
* @param {OdooEvent} e
* @param {Object} e.data.filter the filter description
* @param {function} e.data.on_success called when the RPC succeeds with its
* returned value as argument
*/
_onCreateFilter: function (e) {
data_manager
.create_filter(e.data.filter)
.then(e.data.on_success);
},
/**
* Displays a warning in a dialog of with the NotificationManager
*
@@ -21,6 +21,7 @@ ActionManager.include({
custom_events: _.extend({}, ActionManager.prototype.custom_events, {
env_updated: '_onEnvUpdated',
execute_action: '_onExecuteAction',
get_controller_context: '_onGetControllerContext',
search: '_onSearch',
switch_view: '_onSwitchView',
}),
@@ -715,6 +716,20 @@ ActionManager.include({
return self.doAction(action, options).then(ev.data.on_success, ev.data.on_fail);
});
},
/**
* Handles a context request: provides to the caller the context of the
* current controller.
*
* @private
* @param {OdooEvent} ev
* @param {function} ev.data.callback used to send the requested context
*/
_onGetControllerContext: function (ev) {
ev.stopPropagation();
var currentController = this.getCurrentController();
var context = currentController && currentController.widget.getContext();
ev.data.callback(context || {});
},
/**
* Called when there is a change in the search view, so the current action's
* environment needs to be updated with the new domain, context and groupby.
@@ -97,10 +97,15 @@ return Widget.extend({
}
var user_context = this.getSession().user_context;
var search = this.searchview.build_search_data();
var view_context = this.action.env.context;
var controllerContext;
this.trigger_up('get_controller_context', {
callback: function (ctx) {
controllerContext = ctx;
},
});
var results = pyeval.eval_domains_and_contexts({
domains: search.domains,
contexts: [user_context].concat(search.contexts.concat(view_context || [])),
contexts: [user_context].concat(search.contexts.concat(controllerContext || [])),
group_by_seq: search.groupbys || [],
});
if (!_.isEmpty(results.group_by)) {
@@ -123,7 +128,7 @@ return Widget.extend({
is_default: default_filter,
action_id: this.action_id,
};
return data_manager.create_filter(filter).done(function (id) {
return this._createFilter(filter).then(function (id) {
filter.id = id;
self.toggle_save_menu(false);
self.$save_name.find('input').val('').prop('checked', false);
@@ -273,6 +278,26 @@ return Widget.extend({
}
});
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Creates a new search view filter.
*
* @private
* @param {Object} filter the filter description
* @returns {$.Deferred} resolved with the RPC's result when it succeeds
*/
_createFilter: function (filter) {
var def = $.Deferred();
this.trigger_up('create_filter', {
filter: filter,
on_success: def.resolve.bind(def),
});
return def;
},
});
});
@@ -260,6 +260,7 @@ var SelectCreateDialog = ViewDialog.extend({
var searchData = this._process_search_data(d.domains, d.contexts, d.groupbys);
this.list_controller.reload(searchData);
},
get_controller_context: '_onGetControllerContext',
}),
/**
@@ -416,6 +417,24 @@ var SelectCreateDialog = ViewDialog.extend({
dialog.on('closed', this, this.close);
return dialog;
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* Handles a context request: provides to the caller the context of the
* list controller.
*
* @private
* @param {OdooEvent} ev
* @param {function} ev.data.callback used to send the requested context
*/
_onGetControllerContext: function (ev) {
ev.stopPropagation();
var context = this.list_controller && this.list_controller.getContext();
ev.data.callback(context || {});
}
});
return {
@@ -5,6 +5,7 @@ var ReportClientAction = require('report.client_action');
var ControlPanelMixin = require('web.ControlPanelMixin');
var core = require('web.core');
var ListController = require('web.ListController');
var ReportService = require('web.ReportService');
var testUtils = require('web.test_utils');
var Widget = require('web.Widget');
@@ -2347,6 +2348,70 @@ QUnit.module('ActionManager', {
actionManager.destroy();
});
QUnit.test('save current search', function (assert) {
assert.expect(4);
testUtils.patch(ListController, {
getContext: function () {
return {
shouldBeInFilterContext: true,
};
},
});
_.extend(this.archs, {
'partner,7,search':
'<search>' +
'<filter name="bar" help="Bar" domain="[(\'bar\', \'=\', 1)]"/>' +
'</search>',
});
this.actions.push({
id: 33,
context: {
shouldNotBeInFilterContext: false,
},
name: 'Partners',
res_model: 'partner',
search_view_id: [7, 'a custom search view'],
type: 'ir.actions.act_window',
views: [[false, 'list']],
});
var actionManager = createActionManager({
actions: this.actions,
archs: this.archs,
data: this.data,
intercepts: {
create_filter: function (event) {
var filter = event.data.filter;
assert.deepEqual(filter.domain, [['bar', '=', 1]],
"should save the correct domain");
assert.deepEqual(filter.context, {shouldBeInFilterContext: true},
"should save the correct context");
},
},
});
actionManager.doAction(33);
assert.strictEqual(actionManager.$('.o_data_row').length, 5,
"should contain 5 records");
// filter on bar
$('.o_control_panel .o_filters_menu a:contains(Bar)').click();
assert.strictEqual(actionManager.$('.o_data_row').length, 2,
"should contain 2 records");
// save filter
$('.o_control_panel .o_save_search a').click(); // toggle 'Save current search'
$('.o_control_panel .o_save_name input[type=text]').val('some name'); // name the filter
$('.o_control_panel .o_save_name button').click(); // click on 'Save'
testUtils.unpatch(ListController);
actionManager.destroy();
});
QUnit.module('Actions in target="new"');
QUnit.test('can execute act_window actions in target="new"', function (assert) {
@@ -1,8 +1,9 @@
odoo.define('web.view_dialogs_tests', function (require) {
"use strict";
var testUtils = require('web.test_utils');
var dialogs = require('web.view_dialogs');
var ListController = require('web.ListController');
var testUtils = require('web.test_utils');
var Widget = require('web.Widget');
QUnit.module('Views', {
@@ -241,6 +242,63 @@ QUnit.module('Views', {
parent.destroy();
});
QUnit.test('SelectCreateDialog: save current search', function (assert) {
assert.expect(4);
testUtils.patch(ListController, {
getContext: function () {
return {
shouldBeInFilterContext: true,
};
},
});
var parent = createParent({
data: this.data,
archs: {
'partner,false,list':
'<tree>' +
'<field name="display_name"/>' +
'</tree>',
'partner,false,search':
'<search>' +
'<filter name="bar" help="Bar" domain="[(\'bar\', \'=\', True)]"/>' +
'</search>',
},
intercepts: {
create_filter: function (event) {
var filter = event.data.filter;
assert.deepEqual(filter.domain, [['bar', '=', true]],
"should save the correct domain");
assert.deepEqual(filter.context, {shouldBeInFilterContext: true},
"should save the correct context");
},
},
});
var dialog = new dialogs.SelectCreateDialog(parent, {
context: {shouldNotBeInFilterContext: false},
res_model: 'partner',
}).open();
assert.strictEqual(dialog.$('.o_data_row').length, 3,
"should contain 3 records");
// filter on bar
dialog.$('.o_filters_menu a:contains(Bar)').click();
assert.strictEqual(dialog.$('.o_data_row').length, 2,
"should contain 2 records");
// save filter
dialog.$('.o_save_search a').click(); // toggle 'Save current search'
dialog.$('.o_save_name input[type=text]').val('some name'); // name the filter
dialog.$('.o_save_name button').click(); // click on 'Save'
testUtils.unpatch(ListController);
parent.destroy();
});
});
});