Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9132b1d

Browse files
committed
[IMP] ir.filters: new filters are local to the menu/action by default
Allow binding an optional `action_id` to filters. The web client will try to identify the specific action ID when saving new filters. If no contextual action exists, the filter is saved globally for the model. This will automatically keep filters within their original menu when there are several menus/actions leading to a given list of documents. In some cases the action_id will not match the filter model, which should be fine (e.g. when opening a many2one completion popup for model `foo` within a menu of model `bar`). It is also still be possible to have a filter apply to all actions/menus for a given model by manually deleting the action_id value in the filter (e.g. via the Manage Filters debug menu). When updating a filter the action_id value is ignored so that old global filters will be gradually replaced by new "local" filters. Also added an _order to ensure stable ordering of the filters.
1 parent 686fea4 commit 9132b1d

File tree

5 files changed

+58
-22
lines changed

5 files changed

+58
-22
lines changed

addons/web/static/src/js/search.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1710,11 +1710,15 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({
17101710
self.clear_selection();
17111711
})
17121712
.on('reset', this.proxy('clear_selection'));
1713-
return this.model.call('get_filters', [this.view.model])
1713+
return this.model.call('get_filters', [this.view.model, this.get_action_id()])
17141714
.then(this.proxy('set_filters'))
17151715
.done(function () { self.is_ready.resolve(); })
17161716
.fail(function () { self.is_ready.reject.apply(self.is_ready, arguments); });
17171717
},
1718+
get_action_id: function(){
1719+
var action = instance.client.action_manager.inner_action;
1720+
if (action) return action.id;
1721+
},
17181722
/**
17191723
* Special implementation delaying defaults until CustomFilters is loaded
17201724
*/
@@ -1734,9 +1738,11 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({
17341738
* @return {String} mapping key corresponding to the filter
17351739
*/
17361740
key_for: function (filter) {
1737-
var user_id = filter.user_id;
1741+
var user_id = filter.user_id,
1742+
action_id = filter.action_id;
17381743
var uid = (user_id instanceof Array) ? user_id[0] : user_id;
1739-
return _.str.sprintf('(%s)%s', uid, filter.name);
1744+
var act_id = (action_id instanceof Array) ? action_id[0] : action_id;
1745+
return _.str.sprintf('(%s)(%s)%s', uid, act_id, filter.name);
17401746
},
17411747
/**
17421748
* Generates a :js:class:`~instance.web.search.Facet` descriptor from a

addons/web/static/test/search.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ var makeSearchView = function (instance, dummy_widget_attributes, defaults) {
171171
dummy: {type: 'char', string: 'Dummy'}
172172
};
173173
};
174+
instance.client = { action_manager: { inner_action: undefined } };
174175

175176
var dataset = new instance.web.DataSet(null, 'dummy.model');
176177
var mock_parent = {getParent: function () {return null;}};

openerp/addons/base/ir/ir_filters.py

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,30 @@ def copy(self, cr, uid, id, default=None, context=None):
3636
default.update({'name':_('%s (copy)') % name})
3737
return super(ir_filters, self).copy(cr, uid, id, default, context)
3838

39-
def get_filters(self, cr, uid, model):
39+
def _get_action_domain(self, cr, uid, action_id=None):
40+
"""Return a domain component for matching filters that are visible in the
41+
same context (menu/view) as the given action."""
42+
if action_id:
43+
# filters specific to this menu + global ones
44+
return [('action_id', 'in' , [action_id, False])]
45+
# only global ones
46+
return [('action_id', '=', False)]
47+
48+
def get_filters(self, cr, uid, model, action_id=None):
4049
"""Obtain the list of filters available for the user on the given model.
4150
51+
:param action_id: optional ID of action to restrict filters to this action
52+
plus global filters. If missing only global filters are returned.
53+
The action does not have to correspond to the model, it may only be
54+
a contextual action.
4255
:return: list of :meth:`~osv.read`-like dicts containing the
43-
``name``, ``is_default``, ``domain``, ``user_id`` (m2o tuple) and
44-
``context`` of the matching ``ir.filters``.
56+
``name``, ``is_default``, ``domain``, ``user_id`` (m2o tuple),
57+
``action_id`` (m2o tuple) and ``context`` of the matching ``ir.filters``.
4558
"""
46-
# available filters: private filters (user_id=uid) and public filters (uid=NULL)
47-
filter_ids = self.search(cr, uid,
59+
# available filters: private filters (user_id=uid) and public filters (uid=NULL),
60+
# and filters for the action (action_id=action_id) or global (action_id=NULL)
61+
action_domain = self._get_action_domain(cr, uid, action_id)
62+
filter_ids = self.search(cr, uid, action_domain +
4863
[('model_id','=',model),('user_id','in',[uid, False])])
4964
my_filters = self.read(cr, uid, filter_ids,
5065
['name', 'is_default', 'domain', 'context', 'user_id'])
@@ -66,7 +81,8 @@ def _check_global_default(self, cr, uid, vals, matching_filters, context=None):
6681
:raises openerp.exceptions.Warning: if there is an existing default and
6782
we're not updating it
6883
"""
69-
existing_default = self.search(cr, uid, [
84+
action_domain = self._get_action_domain(cr, uid, vals.get('action_id'))
85+
existing_default = self.search(cr, uid, action_domain + [
7086
('model_id', '=', vals['model_id']),
7187
('user_id', '=', False),
7288
('is_default', '=', True)], context=context)
@@ -83,7 +99,9 @@ def _check_global_default(self, cr, uid, vals, matching_filters, context=None):
8399

84100
def create_or_replace(self, cr, uid, vals, context=None):
85101
lower_name = vals['name'].lower()
86-
matching_filters = [f for f in self.get_filters(cr, uid, vals['model_id'])
102+
action_id = vals.get('action_id')
103+
current_filters = self.get_filters(cr, uid, vals['model_id'], action_id)
104+
matching_filters = [f for f in current_filters
87105
if f['name'].lower() == lower_name
88106
# next line looks for matching user_ids (specific or global), i.e.
89107
# f.user_id is False and vals.user_id is False or missing,
@@ -92,18 +110,22 @@ def create_or_replace(self, cr, uid, vals, context=None):
92110

93111
if vals.get('is_default'):
94112
if vals.get('user_id'):
95-
act_ids = self.search(cr, uid, [
113+
# Setting new default: any other default that belongs to the user
114+
# should be turned off
115+
action_domain = self._get_action_domain(cr, uid, action_id)
116+
act_ids = self.search(cr, uid, action_domain + [
96117
('model_id', '=', vals['model_id']),
97118
('user_id', '=', vals['user_id']),
98119
('is_default', '=', True),
99120
], context=context)
100-
self.write(cr, uid, act_ids, {'is_default': False}, context=context)
121+
if act_ids:
122+
self.write(cr, uid, act_ids, {'is_default': False}, context=context)
101123
else:
102124
self._check_global_default(
103125
cr, uid, vals, matching_filters, context=None)
104126

105127
# When a filter exists for the same (name, model, user) triple, we simply
106-
# replace its definition.
128+
# replace its definition (considering action_id irrelevant here)
107129
if matching_filters:
108130
self.write(cr, uid, matching_filters[0]['id'], vals, context)
109131
return matching_filters[0]['id']
@@ -114,16 +136,17 @@ def create_or_replace(self, cr, uid, vals, context=None):
114136
# Partial constraint, complemented by unique index (see below)
115137
# Still useful to keep because it provides a proper error message when a violation
116138
# occurs, as it shares the same prefix as the unique index.
117-
('name_model_uid_unique', 'unique (name, model_id, user_id)', 'Filter names must be unique'),
139+
('name_model_uid_unique', 'unique (name, model_id, user_id, action_id)', 'Filter names must be unique'),
118140
]
119141

120142
def _auto_init(self, cr, context=None):
121143
super(ir_filters, self)._auto_init(cr, context)
122144
# Use unique index to implement unique constraint on the lowercase name (not possible using a constraint)
123-
cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = 'ir_filters_name_model_uid_unique_index'")
145+
cr.execute("DROP INDEX IF EXISTS ir_filters_name_model_uid_unique_index") # drop old index w/o action
146+
cr.execute("SELECT indexname FROM pg_indexes WHERE indexname = 'ir_filters_name_model_uid_unique_action_index'")
124147
if not cr.fetchone():
125-
cr.execute("""CREATE UNIQUE INDEX "ir_filters_name_model_uid_unique_index" ON ir_filters
126-
(lower(name), model_id, COALESCE(user_id,-1))""")
148+
cr.execute("""CREATE UNIQUE INDEX "ir_filters_name_model_uid_unique_action_index" ON ir_filters
149+
(lower(name), model_id, COALESCE(user_id,-1), COALESCE(action_id,-1))""")
127150

128151
_columns = {
129152
'name': fields.char('Filter Name', translate=True, required=True),
@@ -133,13 +156,18 @@ def _auto_init(self, cr, context=None):
133156
'domain': fields.text('Domain', required=True),
134157
'context': fields.text('Context', required=True),
135158
'model_id': fields.selection(_list_all_models, 'Model', required=True),
136-
'is_default': fields.boolean('Default filter')
159+
'is_default': fields.boolean('Default filter'),
160+
'action_id': fields.many2one('ir.actions.actions', 'Action', ondelete='cascade',
161+
help="The menu action this filter applies to. "
162+
"When left empty the filter applies to all menus "
163+
"for this model.")
137164
}
138165
_defaults = {
139166
'domain': '[]',
140167
'context':'{}',
141168
'user_id': lambda self,cr,uid,context=None: uid,
142169
'is_default': False
143170
}
171+
_order = 'model_id, name, id desc'
144172

145173
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

openerp/addons/base/ir/ir_filters.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
<field name="user_id"/>
2121
<field name="model_id"/>
2222
<field name="is_default"/>
23+
<field name="action_id"/>
2324
</group>
2425
<group>
2526
<field name="domain"/>
@@ -37,6 +38,7 @@
3738
<field name="model_id"/>
3839
<field name="user_id"/>
3940
<field name="is_default"/>
41+
<field name="action_id"/>
4042
<field name="domain" groups="base.group_no_one"/>
4143
<field name="context" groups="base.group_no_one"/>
4244
</tree>

openerp/addons/base/tests/test_ir_filters.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55
from openerp.tests import common
66

77
def noid(d):
8-
""" Removes `id` key from a dict so we don't have to keep these things
9-
around when trying to match
10-
"""
11-
if 'id' in d: del d['id']
8+
""" Removes values that are not relevant for the test comparisons """
9+
d.pop('id', None)
10+
d.pop('action_id', None)
1211
return d
1312

1413
class FiltersCase(common.TransactionCase):

0 commit comments

Comments
 (0)