Skip to content

Commit 6a285bf

Browse files
Merge pull request #2 from sdelements/tech/pas/198_drf_filters
Add support for method in Autofilter
2 parents a6fc693 + 6cfd56b commit 6a285bf

File tree

4 files changed

+98
-2
lines changed

4 files changed

+98
-2
lines changed

rest_framework_filters/filters.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@ class BlogFilter(filters.FilterSet):
4444
"""
4545
creation_counter = 0
4646

47-
def __init__(self, field_name=None, *, lookups=None):
47+
def __init__(self, field_name=None, *, method=None, lookups=None):
4848
self.field_name = field_name
4949
self.lookups = lookups or []
50-
50+
self.method = method
5151
self.creation_counter = AutoFilter.creation_counter
5252
AutoFilter.creation_counter += 1
5353

rest_framework_filters/filterset.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,21 @@ def expand_auto_filter(cls, new_class, filter_name, f):
9898

9999
# Use meta.fields to generate auto filters
100100
new_class._meta.fields = {f.field_name: f.lookups or []}
101+
101102
for gen_name, gen_f in new_class.get_filters().items():
102103
# get_filters() generates param names from the model field name, so
103104
# replace the field name with the param name from the filerset
104105
gen_name = gen_name.replace(f.field_name, filter_name, 1)
105106

107+
if f.method:
108+
# Override method for auto-generated filters.
109+
gen_f.method = f.method
110+
111+
if gen_f.lookup_expr != "exact":
112+
# Update field name to also include lookup expr.
113+
gen_f.field_name = "{field_name}__{lookup_expr}".format(field_name=f.field_name,
114+
lookup_expr=gen_f.lookup_expr)
115+
106116
# do not overwrite declared filters
107117
if gen_name not in orig_declared:
108118
expanded[gen_name] = gen_f

tests/test_filtering.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,64 @@ class Meta:
7878
f = Subclass(GET, queryset=Note.objects.all())
7979
self.assertEqual(len(list(f.qs)), 2)
8080

81+
def test_autofilter_with_method(self):
82+
# Test that method param applies to all auto-generated filters.
83+
def filter_iexact(qs, field_name, value):
84+
# Test that the field name contains the lookup expression.
85+
self.assertEqual(field_name, 'content__icontains')
86+
87+
return qs.filter(**{field_name: value}, author__username="user1")
88+
89+
class Actual(FilterSet):
90+
title = filters.AutoFilter(lookups='__all__', method='filter_title')
91+
content = filters.AutoFilter(lookups=['icontains'], method=filter_iexact)
92+
author = filters.AutoFilter(lookups='__all__', field_name='author__username', method='filter_author')
93+
94+
class Meta:
95+
model = Note
96+
fields = []
97+
98+
def filter_title(self, qs, field_name, value):
99+
return qs.filter(**{field_name: value}, author__username="user1")
100+
101+
def filter_author(self, qs, field_name, value):
102+
return qs.filter(**{field_name: value})
103+
104+
# Test method as a function
105+
GET = {'content__icontains': 'test content'}
106+
f = Actual(GET, queryset=Note.objects.all())
107+
self.assertEqual(len(list(f.qs)), 3)
108+
109+
# Test method as a string reference to filterset method
110+
GET = {'title__contains': 'Hello'}
111+
f = Actual(GET, queryset=Note.objects.all())
112+
self.assertEqual(len(list(f.qs)), 1)
113+
self.assertEqual(f.qs.first().author.username, "user1")
114+
115+
GET = {'title__iendswith': '4'}
116+
f = Actual(GET, queryset=Note.objects.all())
117+
self.assertEqual(len(list(f.qs)), 0)
118+
119+
GET = {'title': 'Hello Test 4'}
120+
f = Actual(GET, queryset=Note.objects.all())
121+
self.assertEqual(len(list(f.qs)), 0)
122+
123+
GET = {'title': 'Hello Test 3'}
124+
f = Actual(GET, queryset=Note.objects.all())
125+
self.assertEqual(len(list(f.qs)), 1)
126+
self.assertEqual(f.qs.first().author.username, "user1")
127+
128+
# Test method in Autofilter on related field
129+
GET = {'author__contains': 'user2'}
130+
f = Actual(GET, queryset=Note.objects.all())
131+
self.assertEqual(len(list(f.qs)), 1)
132+
self.assertEqual(f.qs.first().author.username, "user2")
133+
134+
GET = {'author': 'user2'}
135+
f = Actual(GET, queryset=Note.objects.all())
136+
self.assertEqual(len(list(f.qs)), 1)
137+
self.assertEqual(f.qs.first().author.username, "user2")
138+
81139

82140
class RelatedFilterTests(TestCase):
83141

tests/test_filterset.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,34 @@ class F(FilterSet):
261261
self.assertEqual(str(w[0].message), message)
262262
self.assertIs(w[0].category, DeprecationWarning)
263263

264+
def test_autofilter_can_be_generated_with_method(self):
265+
# ensure AutoFilters are generated with the provided method.
266+
def external_method(instance, qs, field, value):
267+
pass
268+
269+
class F(FilterSet):
270+
id = filters.AutoFilter(lookups='__all__', method='filterset_method')
271+
title = filters.AutoFilter(lookups=['exact'], method=external_method)
272+
author = filters.AutoFilter(field_name='author__last_name', lookups='__all__', method='related_method')
273+
274+
class Meta:
275+
model = Note
276+
fields = []
277+
278+
def filterset_method(self, qs, field, value):
279+
pass
280+
281+
def related_method(self, qs, field, value):
282+
pass
283+
284+
for field_name, lookup_filter in F.base_filters.items():
285+
# Ensure field name on filter is overridden to include lookup expression.
286+
if lookup_filter.lookup_expr != 'exact':
287+
self.assertTrue(lookup_filter.field_name.endswith(lookup_filter.lookup_expr))
288+
289+
self.assertIsNotNone(lookup_filter.method)
290+
self.assertIsNotNone(lookup_filter._method)
291+
264292

265293
class GetRelatedFiltersetsTests(TestCase):
266294

0 commit comments

Comments
 (0)