Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for method in Autofilter #2

Merged
merged 1 commit into from
Mar 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions rest_framework_filters/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ class BlogFilter(filters.FilterSet):
"""
creation_counter = 0

def __init__(self, field_name=None, *, lookups=None):
def __init__(self, field_name=None, *, method=None, lookups=None):
self.field_name = field_name
self.lookups = lookups or []

self.method = method
self.creation_counter = AutoFilter.creation_counter
AutoFilter.creation_counter += 1

Expand Down
10 changes: 10 additions & 0 deletions rest_framework_filters/filterset.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,21 @@ def expand_auto_filter(cls, new_class, filter_name, f):

# Use meta.fields to generate auto filters
new_class._meta.fields = {f.field_name: f.lookups or []}

for gen_name, gen_f in new_class.get_filters().items():
# get_filters() generates param names from the model field name, so
# replace the field name with the param name from the filerset
gen_name = gen_name.replace(f.field_name, filter_name, 1)

if f.method:
# Override method for auto-generated filters.
gen_f.method = f.method

if gen_f.lookup_expr != "exact":
# Update field name to also include lookup expr.
gen_f.field_name = "{field_name}__{lookup_expr}".format(field_name=f.field_name,
lookup_expr=gen_f.lookup_expr)

# do not overwrite declared filters
if gen_name not in orig_declared:
expanded[gen_name] = gen_f
Expand Down
58 changes: 58 additions & 0 deletions tests/test_filtering.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,64 @@ class Meta:
f = Subclass(GET, queryset=Note.objects.all())
self.assertEqual(len(list(f.qs)), 2)

def test_autofilter_with_method(self):
# Test that method param applies to all auto-generated filters.
def filter_iexact(qs, field_name, value):
# Test that the field name contains the lookup expression.
self.assertEqual(field_name, 'content__icontains')

return qs.filter(**{field_name: value}, author__username="user1")

class Actual(FilterSet):
title = filters.AutoFilter(lookups='__all__', method='filter_title')
content = filters.AutoFilter(lookups=['icontains'], method=filter_iexact)
author = filters.AutoFilter(lookups='__all__', field_name='author__username', method='filter_author')

class Meta:
model = Note
fields = []

def filter_title(self, qs, field_name, value):
return qs.filter(**{field_name: value}, author__username="user1")

def filter_author(self, qs, field_name, value):
return qs.filter(**{field_name: value})

# Test method as a function
GET = {'content__icontains': 'test content'}
f = Actual(GET, queryset=Note.objects.all())
self.assertEqual(len(list(f.qs)), 3)

# Test method as a string reference to filterset method
GET = {'title__contains': 'Hello'}
f = Actual(GET, queryset=Note.objects.all())
self.assertEqual(len(list(f.qs)), 1)
self.assertEqual(f.qs.first().author.username, "user1")

GET = {'title__iendswith': '4'}
f = Actual(GET, queryset=Note.objects.all())
self.assertEqual(len(list(f.qs)), 0)

GET = {'title': 'Hello Test 4'}
f = Actual(GET, queryset=Note.objects.all())
self.assertEqual(len(list(f.qs)), 0)

GET = {'title': 'Hello Test 3'}
f = Actual(GET, queryset=Note.objects.all())
self.assertEqual(len(list(f.qs)), 1)
self.assertEqual(f.qs.first().author.username, "user1")

# Test method in Autofilter on related field
GET = {'author__contains': 'user2'}
f = Actual(GET, queryset=Note.objects.all())
self.assertEqual(len(list(f.qs)), 1)
self.assertEqual(f.qs.first().author.username, "user2")

GET = {'author': 'user2'}
f = Actual(GET, queryset=Note.objects.all())
self.assertEqual(len(list(f.qs)), 1)
self.assertEqual(f.qs.first().author.username, "user2")


class RelatedFilterTests(TestCase):

Expand Down
28 changes: 28 additions & 0 deletions tests/test_filterset.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,34 @@ class F(FilterSet):
self.assertEqual(str(w[0].message), message)
self.assertIs(w[0].category, DeprecationWarning)

def test_autofilter_can_be_generated_with_method(self):
# ensure AutoFilters are generated with the provided method.
def external_method(instance, qs, field, value):
pass

class F(FilterSet):
id = filters.AutoFilter(lookups='__all__', method='filterset_method')
title = filters.AutoFilter(lookups=['exact'], method=external_method)
author = filters.AutoFilter(field_name='author__last_name', lookups='__all__', method='related_method')

class Meta:
model = Note
fields = []

def filterset_method(self, qs, field, value):
pass

def related_method(self, qs, field, value):
pass

for field_name, lookup_filter in F.base_filters.items():
# Ensure field name on filter is overridden to include lookup expression.
if lookup_filter.lookup_expr != 'exact':
self.assertTrue(lookup_filter.field_name.endswith(lookup_filter.lookup_expr))

self.assertIsNotNone(lookup_filter.method)
self.assertIsNotNone(lookup_filter._method)


class GetRelatedFiltersetsTests(TestCase):

Expand Down