Skip to content

Commit efdf453

Browse files
author
Ryan P Kilby
committed
Make 'get_filter_name' more robust, add tests
1 parent e8efe11 commit efdf453

File tree

2 files changed

+105
-5
lines changed

2 files changed

+105
-5
lines changed

rest_framework_filters/filterset.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,21 @@ def get_filters(self):
134134
def get_filter_name(cls, param):
135135
"""
136136
Get the filter name for the request data parameter.
137+
138+
ex::
139+
140+
# regular attribute filters
141+
name = FilterSet.get_filter_name('email')
142+
assert name == 'email'
143+
144+
# exclusion filters
145+
name = FilterSet.get_filter_name('email!')
146+
assert name == 'email'
147+
148+
# related filters
149+
name = FilterSet.get_filter_name('author__email')
150+
assert name == 'author'
151+
137152
"""
138153
# Attempt to match against filters with lookups first. (username__endswith)
139154
if param in cls.base_filters:
@@ -143,11 +158,18 @@ def get_filter_name(cls, param):
143158
if param[-1] == '!' and param[:-1] in cls.base_filters:
144159
return param[:-1]
145160

146-
# Fallback to matching against relationships. (author__username__endswith)
147-
related_param = param.split(LOOKUP_SEP, 1)[0]
148-
f = cls.base_filters.get(related_param, None)
149-
if isinstance(f, filters.RelatedFilter):
150-
return related_param
161+
# Fallback to matching against relationships. (author__username__endswith).
162+
related_filters = [
163+
name for name, f in six.iteritems(cls.base_filters)
164+
if isinstance(f, filters.RelatedFilter)
165+
]
166+
167+
# preference more specific filters. eg, `note__author` over `note`.
168+
for name in sorted(related_filters)[::-1]:
169+
# we need to match against '__' to prevent eager matching against
170+
# like names. eg, note vs note2. Exact matches are handled above.
171+
if param.startswith("%s__" % name):
172+
return name
151173

152174
@classmethod
153175
def get_subset(cls, params):

tests/test_filterset.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,84 @@ def test_nonexistent_related_field(self):
324324
self.assertEqual(len(list(f)), 4)
325325

326326

327+
class GetFilterNameTests(TestCase):
328+
329+
def test_regular_filter(self):
330+
name = UserFilter.get_filter_name('email')
331+
self.assertEqual('email', name)
332+
333+
def test_exclusion_filter(self):
334+
name = UserFilter.get_filter_name('email!')
335+
self.assertEqual('email', name)
336+
337+
def test_non_filter(self):
338+
name = UserFilter.get_filter_name('foobar')
339+
self.assertEqual(None, name)
340+
341+
def test_related_filter(self):
342+
# 'exact' matches
343+
name = NoteFilterWithRelated.get_filter_name('author')
344+
self.assertEqual('author', name)
345+
346+
# related attribute filters
347+
name = NoteFilterWithRelated.get_filter_name('author__email')
348+
self.assertEqual('author', name)
349+
350+
# non-existent related filters should match, as it's the responsibility
351+
# of the related filterset to handle non-existent filters
352+
name = NoteFilterWithRelated.get_filter_name('author__foobar')
353+
self.assertEqual('author', name)
354+
355+
def test_twice_removed_related_filter(self):
356+
class PostFilterWithDirectAuthor(PostFilter):
357+
note__author = filters.RelatedFilter(UserFilter)
358+
note = filters.RelatedFilter(NoteFilterWithAll)
359+
360+
class Meta:
361+
model = Post
362+
363+
name = PostFilterWithDirectAuthor.get_filter_name('note__title')
364+
self.assertEqual('note', name)
365+
366+
# 'exact' matches, preference more specific filter name, as less specific
367+
# filter may not have related access.
368+
name = PostFilterWithDirectAuthor.get_filter_name('note__author')
369+
self.assertEqual('note__author', name)
370+
371+
# related attribute filters
372+
name = PostFilterWithDirectAuthor.get_filter_name('note__author__email')
373+
self.assertEqual('note__author', name)
374+
375+
# non-existent related filters should match, as it's the responsibility
376+
# of the related filterset to handle non-existent filters
377+
name = PostFilterWithDirectAuthor.get_filter_name('note__author__foobar')
378+
self.assertEqual('note__author', name)
379+
380+
def test_name_hiding(self):
381+
class PostFilterWithDirectAuthor(PostFilter):
382+
note__author = filters.RelatedFilter(UserFilter)
383+
note = filters.RelatedFilter(NoteFilterWithAll)
384+
note2 = filters.RelatedFilter(NoteFilterWithAll)
385+
386+
class Meta:
387+
model = Post
388+
389+
name = PostFilterWithDirectAuthor.get_filter_name('note__author')
390+
self.assertEqual('note__author', name)
391+
392+
name = PostFilterWithDirectAuthor.get_filter_name('note__title')
393+
self.assertEqual('note', name)
394+
395+
name = PostFilterWithDirectAuthor.get_filter_name('note')
396+
self.assertEqual('note', name)
397+
398+
name = PostFilterWithDirectAuthor.get_filter_name('note2')
399+
self.assertEqual('note2', name)
400+
401+
name = PostFilterWithDirectAuthor.get_filter_name('note2__author')
402+
self.assertEqual('note2', name)
403+
404+
327405
class FilterSubsetTests(TestCase):
328406

329407
def test_get_subset(self):

0 commit comments

Comments
 (0)