Skip to content

Commit c344d61

Browse files
author
Ryan P Kilby
committed
Merge pull request #46 from rpkilby/fix-boolean-filter
Fix boolean filter
2 parents a4f8c02 + 2ab2550 commit c344d61

File tree

5 files changed

+120
-29
lines changed

5 files changed

+120
-29
lines changed

rest_framework_filters/fields.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
from django import forms
22

3+
# TODO: Remove when django-filter 0.12.0 is released
4+
try:
5+
from django_filters import BooleanWidget
6+
except ImportError:
7+
from .widgets import BooleanWidget
8+
9+
10+
class BooleanField(forms.BooleanField):
11+
widget = BooleanWidget
12+
313

414
class ArrayDecimalField(forms.DecimalField):
515
def clean(self, value):

rest_framework_filters/filters.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
def _import_class(path):
1616
module_path, class_name = path.rsplit('.', 1)
17-
class_name = str(class_name) # Ensure not unicode on py2.x
17+
class_name = str(class_name) # Ensure not unicode on py2.x
1818
module = __import__(module_path, fromlist=[class_name], level=0)
1919
return getattr(module, class_name)
2020

@@ -69,6 +69,9 @@ class AllLookupsFilter(Filter):
6969
###################################################
7070
# Fixed-up versions of some of the default filters
7171
###################################################
72+
class BooleanFilter(BooleanFilter):
73+
field_class = fields.BooleanField
74+
7275

7376
class InSetFilterBase(object):
7477
def filter(self, qs, value):

rest_framework_filters/widgets.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
2+
from __future__ import absolute_import
3+
from __future__ import unicode_literals
4+
5+
from django import forms
6+
7+
8+
# TODO: Remove when django-filter 0.12.0 is released
9+
class BooleanWidget(forms.Widget):
10+
"""Convert true/false values into the internal Python True/False.
11+
This can be used for AJAX queries that pass true/false from JavaScript's
12+
internal types through.
13+
"""
14+
def value_from_datadict(self, data, files, name):
15+
"""
16+
"""
17+
value = super(BooleanWidget, self).value_from_datadict(
18+
data, files, name)
19+
20+
if value is not None:
21+
if value.lower() == 'true':
22+
value = True
23+
elif value.lower() == 'false':
24+
value = False
25+
26+
return value

tests/filters.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ class Meta:
1919
class UserFilter(FilterSet):
2020
username = filters.CharFilter(name='username')
2121
email = filters.CharFilter(name='email')
22+
last_login = filters.AllLookupsFilter()
23+
is_active = filters.BooleanFilter(name='is_active')
2224

2325
class Meta:
2426
model = User
@@ -56,15 +58,10 @@ class Meta:
5658
model = Note
5759

5860

59-
class PostFilterWithRelated(FilterSet):
60-
note = RelatedFilter(NoteFilterWithRelatedAll, name='note')
61-
62-
class Meta:
63-
model = Post
64-
65-
66-
class PostFilterWithMethod(FilterSet):
61+
class PostFilter(FilterSet):
62+
# Used for Related filter and MethodFilter tests
6763
note = RelatedFilter(NoteFilterWithRelatedAll, name='note')
64+
date_published = filters.AllLookupsFilter()
6865
is_published = filters.MethodFilter()
6966

7067
class Meta:
@@ -91,23 +88,23 @@ def filter_is_published(self, name, qs, value):
9188

9289
class CoverFilterWithRelatedMethodFilter(FilterSet):
9390
comment = filters.CharFilter(name='comment')
94-
post = RelatedFilter(PostFilterWithMethod, name='post')
91+
post = RelatedFilter(PostFilter, name='post')
9592

9693
class Meta:
9794
model = Cover
9895

9996

10097
class CoverFilterWithRelated(FilterSet):
10198
comment = filters.CharFilter(name='comment')
102-
post = RelatedFilter(PostFilterWithRelated, name='post')
99+
post = RelatedFilter(PostFilter, name='post')
103100

104101
class Meta:
105102
model = Cover
106103

107104

108105
class PageFilterWithRelated(FilterSet):
109106
title = filters.CharFilter(name='title')
110-
previous_page = RelatedFilter(PostFilterWithRelated, name='previous_page')
107+
previous_page = RelatedFilter(PostFilter, name='previous_page')
111108

112109
class Meta:
113110
model = Page

tests/test_filterset.py

Lines changed: 72 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,22 @@
55
import datetime
66

77
from django.test import TestCase, override_settings
8-
from django.contrib.auth.models import User
98
from django.utils.dateparse import parse_time, parse_datetime
109

1110
from rest_framework_filters import filters
1211

1312
from .models import (
14-
Note, Post, Cover, Page, A, B, C, Person, Tag, BlogPost,
13+
User, Note, Post, Cover, Page, A, B, C, Person, Tag, BlogPost,
1514
)
1615

1716
from .filters import (
1817
NoteFilterWithAll,
19-
# UserFilter,
18+
UserFilter,
2019
# UserFilterWithAll,
2120
NoteFilterWithRelated,
2221
NoteFilterWithRelatedAll,
2322
NoteFilterWithRelatedAllDifferentFilterName,
24-
PostFilterWithRelated,
25-
PostFilterWithMethod,
23+
PostFilter,
2624
CoverFilterWithRelatedMethodFilter,
2725
CoverFilterWithRelated,
2826
# PageFilterWithRelated,
@@ -257,7 +255,7 @@ def test_double_relation_filter(self):
257255
GET = {
258256
'note__author__username__endswith': 'user2'
259257
}
260-
f = PostFilterWithRelated(GET, queryset=Post.objects.all())
258+
f = PostFilter(GET, queryset=Post.objects.all())
261259
self.assertEqual(len(list(f)), 1)
262260
post = list(f)[0]
263261
self.assertEqual(post.content, "Test content in post 3")
@@ -320,17 +318,8 @@ def test_get_filterset_subset(self):
320318

321319
class MethodFilterTests(TestCase):
322320

323-
if django.VERSION >= (1, 8):
324-
@classmethod
325-
def setUpTestData(cls):
326-
cls.generateTestData()
327-
328-
else:
329-
def setUp(self):
330-
self.generateTestData()
331-
332321
@classmethod
333-
def generateTestData(cls):
322+
def setUpTestData(cls):
334323
user = User.objects.create(username="user1", email="user1@example.org")
335324

336325
note1 = Note.objects.create(title="Test 1", content="Test content 1", author=user)
@@ -346,7 +335,7 @@ def test_method_filter(self):
346335
GET = {
347336
'is_published': 'true'
348337
}
349-
filterset = PostFilterWithMethod(GET, queryset=Post.objects.all())
338+
filterset = PostFilter(GET, queryset=Post.objects.all())
350339
results = list(filterset)
351340
self.assertEqual(len(results), 1)
352341
self.assertEqual(results[0].content, "Test content in post 2")
@@ -465,6 +454,9 @@ def setUpTestData(cls):
465454
john = Person.objects.create(name="John")
466455
Person.objects.create(name="Mark", best_friend=john)
467456

457+
User.objects.create(username="user1", email="user1@example.org", is_active=True, last_login=datetime.date.today())
458+
User.objects.create(username="user2", email="user2@example.org", is_active=False)
459+
468460
def test_inset_number_filter(self):
469461
p1 = Person.objects.get(name="John").pk
470462
p2 = Person.objects.get(name="Mark").pk
@@ -563,6 +555,69 @@ def test_dict_declaration(self):
563555
filters.BooleanFilter
564556
)
565557

558+
def test_boolean_filter(self):
559+
# Capitalized True
560+
GET = {'is_active': 'True'}
561+
filterset = UserFilter(GET, queryset=User.objects.all())
562+
results = list(filterset)
563+
self.assertEqual(len(results), 1)
564+
self.assertEqual(results[0].username, 'user1')
565+
566+
# Lowercase True
567+
GET = {'is_active': 'true'}
568+
filterset = UserFilter(GET, queryset=User.objects.all())
569+
results = list(filterset)
570+
self.assertEqual(len(results), 1)
571+
self.assertEqual(results[0].username, 'user1')
572+
573+
# Uppercase True
574+
GET = {'is_active': 'TRUE'}
575+
filterset = UserFilter(GET, queryset=User.objects.all())
576+
results = list(filterset)
577+
self.assertEqual(len(results), 1)
578+
self.assertEqual(results[0].username, 'user1')
579+
580+
# Capitalized False
581+
GET = {'is_active': 'False'}
582+
filterset = UserFilter(GET, queryset=User.objects.all())
583+
results = list(filterset)
584+
self.assertEqual(len(results), 1)
585+
self.assertEqual(results[0].username, 'user2')
586+
587+
# Lowercase False
588+
GET = {'is_active': 'false'}
589+
filterset = UserFilter(GET, queryset=User.objects.all())
590+
results = list(filterset)
591+
self.assertEqual(len(results), 1)
592+
self.assertEqual(results[0].username, 'user2')
593+
594+
# Uppercase False
595+
GET = {'is_active': 'FALSE'}
596+
filterset = UserFilter(GET, queryset=User.objects.all())
597+
results = list(filterset)
598+
self.assertEqual(len(results), 1)
599+
self.assertEqual(results[0].username, 'user2')
600+
601+
def test_isnull_override(self):
602+
import django_filters.filters
603+
604+
self.assertIsInstance(
605+
UserFilter().filters['last_login__isnull'],
606+
django_filters.filters.BooleanFilter
607+
)
608+
609+
GET = {'last_login__isnull': 'false'}
610+
filterset = UserFilter(GET, queryset=User.objects.all())
611+
results = list(filterset)
612+
self.assertEqual(len(results), 1)
613+
self.assertEqual(results[0].username, 'user1')
614+
615+
GET = {'last_login__isnull': 'true'}
616+
filterset = UserFilter(GET, queryset=User.objects.all())
617+
results = list(filterset)
618+
self.assertEqual(len(results), 1)
619+
self.assertEqual(results[0].username, 'user2')
620+
566621

567622
class FilterExclusionTests(TestCase):
568623

0 commit comments

Comments
 (0)