diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index 3fce58fe0bb..4b00f6f4e01 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -1710,11 +1710,15 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ self.clear_selection(); }) .on('reset', this.proxy('clear_selection')); - return this.model.call('get_filters', [this.view.model]) + return this.model.call('get_filters', [this.view.model, this.get_action_id()]) .then(this.proxy('set_filters')) .done(function () { self.is_ready.resolve(); }) .fail(function () { self.is_ready.reject.apply(self.is_ready, arguments); }); }, + get_action_id: function(){ + var action = instance.client.action_manager.inner_action; + if (action) return action.id; + }, /** * Special implementation delaying defaults until CustomFilters is loaded */ @@ -1734,9 +1738,11 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ * @return {String} mapping key corresponding to the filter */ key_for: function (filter) { - var user_id = filter.user_id; + var user_id = filter.user_id, + action_id = filter.action_id; var uid = (user_id instanceof Array) ? user_id[0] : user_id; - return _.str.sprintf('(%s)%s', uid, filter.name); + var act_id = (action_id instanceof Array) ? action_id[0] : action_id; + return _.str.sprintf('(%s)(%s)%s', uid, act_id, filter.name); }, /** * Generates a :js:class:`~instance.web.search.Facet` descriptor from a diff --git a/addons/web/static/test/search.js b/addons/web/static/test/search.js index 5faf13b2599..d93cb131e05 100644 --- a/addons/web/static/test/search.js +++ b/addons/web/static/test/search.js @@ -171,6 +171,7 @@ var makeSearchView = function (instance, dummy_widget_attributes, defaults) { dummy: {type: 'char', string: 'Dummy'} }; }; + instance.client = { action_manager: { inner_action: undefined } }; var dataset = new instance.web.DataSet(null, 'dummy.model'); var mock_parent = {getParent: function () {return null;}}; diff --git a/openerp/addons/base/ir/ir_filters.py b/openerp/addons/base/ir/ir_filters.py index 6364a2492be..f179a8169f0 100644 --- a/openerp/addons/base/ir/ir_filters.py +++ b/openerp/addons/base/ir/ir_filters.py @@ -36,15 +36,30 @@ class ir_filters(osv.osv): default.update({'name':_('%s (copy)') % name}) return super(ir_filters, self).copy(cr, uid, id, default, context) - def get_filters(self, cr, uid, model): + def _get_action_domain(self, cr, uid, action_id=None): + """Return a domain component for matching filters that are visible in the + same context (menu/view) as the given action.""" + if action_id: + # filters specific to this menu + global ones + return [('action_id', 'in' , [action_id, False])] + # only global ones + return [('action_id', '=', False)] + + def get_filters(self, cr, uid, model, action_id=None): """Obtain the list of filters available for the user on the given model. + :param action_id: optional ID of action to restrict filters to this action + plus global filters. If missing only global filters are returned. + The action does not have to correspond to the model, it may only be + a contextual action. :return: list of :meth:`~osv.read`-like dicts containing the - ``name``, ``is_default``, ``domain``, ``user_id`` (m2o tuple) and - ``context`` of the matching ``ir.filters``. + ``name``, ``is_default``, ``domain``, ``user_id`` (m2o tuple), + ``action_id`` (m2o tuple) and ``context`` of the matching ``ir.filters``. """ - # available filters: private filters (user_id=uid) and public filters (uid=NULL) - filter_ids = self.search(cr, uid, + # available filters: private filters (user_id=uid) and public filters (uid=NULL), + # and filters for the action (action_id=action_id) or global (action_id=NULL) + action_domain = self._get_action_domain(cr, uid, action_id) + filter_ids = self.search(cr, uid, action_domain + [('model_id','=',model),('user_id','in',[uid, False])]) my_filters = self.read(cr, uid, filter_ids, ['name', 'is_default', 'domain', 'context', 'user_id']) @@ -66,7 +81,8 @@ class ir_filters(osv.osv): :raises openerp.exceptions.Warning: if there is an existing default and we're not updating it """ - existing_default = self.search(cr, uid, [ + action_domain = self._get_action_domain(cr, uid, vals.get('action_id')) + existing_default = self.search(cr, uid, action_domain + [ ('model_id', '=', vals['model_id']), ('user_id', '=', False), ('is_default', '=', True)], context=context) @@ -83,7 +99,9 @@ class ir_filters(osv.osv): def create_or_replace(self, cr, uid, vals, context=None): lower_name = vals['name'].lower() - matching_filters = [f for f in self.get_filters(cr, uid, vals['model_id']) + action_id = vals.get('action_id') + current_filters = self.get_filters(cr, uid, vals['model_id'], action_id) + matching_filters = [f for f in current_filters if f['name'].lower() == lower_name # next line looks for matching user_ids (specific or global), i.e. # f.user_id is False and vals.user_id is False or missing, @@ -92,18 +110,22 @@ class ir_filters(osv.osv): if vals.get('is_default'): if vals.get('user_id'): - act_ids = self.search(cr, uid, [ + # Setting new default: any other default that belongs to the user + # should be turned off + action_domain = self._get_action_domain(cr, uid, action_id) + act_ids = self.search(cr, uid, action_domain + [ ('model_id', '=', vals['model_id']), ('user_id', '=', vals['user_id']), ('is_default', '=', True), ], context=context) - self.write(cr, uid, act_ids, {'is_default': False}, context=context) + if act_ids: + self.write(cr, uid, act_ids, {'is_default': False}, context=context) else: self._check_global_default( cr, uid, vals, matching_filters, context=None) # When a filter exists for the same (name, model, user) triple, we simply - # replace its definition. + # replace its definition (considering action_id irrelevant here) if matching_filters: self.write(cr, uid, matching_filters[0]['id'], vals, context) return matching_filters[0]['id'] @@ -114,16 +136,17 @@ class ir_filters(osv.osv): # Partial constraint, complemented by unique index (see below) # Still useful to keep because it provides a proper error message when a violation # occurs, as it shares the same prefix as the unique index. - ('name_model_uid_unique', 'unique (name, model_id, user_id)', 'Filter names must be unique'), + ('name_model_uid_unique', 'unique (name, model_id, user_id, action_id)', 'Filter names must be unique'), ] def _auto_init(self, cr, context=None): super(ir_filters, self)._auto_init(cr, context) # Use unique index to implement unique constraint on the lowercase name (not possible using a constraint) - cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = 'ir_filters_name_model_uid_unique_index'") + cr.execute("DROP INDEX IF EXISTS ir_filters_name_model_uid_unique_index") # drop old index w/o action + cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = 'ir_filters_name_model_uid_unique_action_index'") if not cr.fetchone(): - cr.execute("""CREATE UNIQUE INDEX "ir_filters_name_model_uid_unique_index" ON ir_filters - (lower(name), model_id, COALESCE(user_id,-1))""") + cr.execute("""CREATE UNIQUE INDEX "ir_filters_name_model_uid_unique_action_index" ON ir_filters + (lower(name), model_id, COALESCE(user_id,-1), COALESCE(action_id,-1))""") _columns = { 'name': fields.char('Filter Name', translate=True, required=True), @@ -133,7 +156,11 @@ class ir_filters(osv.osv): 'domain': fields.text('Domain', required=True), 'context': fields.text('Context', required=True), 'model_id': fields.selection(_list_all_models, 'Model', required=True), - 'is_default': fields.boolean('Default filter') + 'is_default': fields.boolean('Default filter'), + 'action_id': fields.many2one('ir.actions.actions', 'Action', ondelete='cascade', + help="The menu action this filter applies to. " + "When left empty the filter applies to all menus " + "for this model.") } _defaults = { 'domain': '[]', @@ -141,5 +168,6 @@ class ir_filters(osv.osv): 'user_id': lambda self,cr,uid,context=None: uid, 'is_default': False } + _order = 'model_id, name, id desc' # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/addons/base/ir/ir_filters.xml b/openerp/addons/base/ir/ir_filters.xml index 46242b67ab4..acc16e172b4 100644 --- a/openerp/addons/base/ir/ir_filters.xml +++ b/openerp/addons/base/ir/ir_filters.xml @@ -20,6 +20,7 @@ + @@ -37,6 +38,7 @@ + diff --git a/openerp/addons/base/tests/test_ir_filters.py b/openerp/addons/base/tests/test_ir_filters.py index 04ecca74525..9ca093d77af 100644 --- a/openerp/addons/base/tests/test_ir_filters.py +++ b/openerp/addons/base/tests/test_ir_filters.py @@ -5,10 +5,9 @@ from openerp import exceptions from openerp.tests import common def noid(d): - """ Removes `id` key from a dict so we don't have to keep these things - around when trying to match - """ - if 'id' in d: del d['id'] + """ Removes values that are not relevant for the test comparisons """ + d.pop('id', None) + d.pop('action_id', None) return d class FiltersCase(common.TransactionCase):