Skip to content

Commit 44768d9

Browse files
author
Ryan P Kilby
authored
Merge pull request #97 from rpkilby/fix-related-alllookups
Temporarily fix some issues with all lookups
2 parents ee3cb8c + 292e42a commit 44768d9

File tree

5 files changed

+85
-16
lines changed

5 files changed

+85
-16
lines changed

CHANGELOG.rst

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1-
Unreleased
2-
----------
1+
Unreleased:
2+
-----------
33

4-
* Fixes #79, enabling compatibility with ``django.contrib.postgres``
5-
* Adds basic infinite recursion prevention for chainable transforms
4+
v0.8.1:
5+
-------
6+
7+
* Fix bug where AllLookupsFilter would override a declared filter of the same name
8+
* #84 Fix AllLookupsFilter compatibility with ``ForeignObject`` related fields
9+
* #82 Fix AllLookupsFilter compatibility with mixin FilterSets
10+
* #81 Fix bug where FilterSet modified ``ViewSet.filter_fields``
11+
* #79 Prevent infinite recursion for chainable transforms, fixing compatiblity
12+
w/ ``django.contrib.postgres``
613

7-
v0.8.0
8-
------
14+
v0.8.0:
15+
-------
916

1017
This release is tied to a major update of django-filter (more details in #66),
1118
which fixes how lookup expressions are resolved. 'in', 'range', and 'isnull'
@@ -15,8 +22,8 @@ This has the following effects:
1522
* Deprecates ArrayDecimalField/InSetNumberFilter
1623
* Deprecates ArrayCharField/InSetCharFilter
1724
* Deprecates FilterSet.fix_filter_field
18-
* Deprecates ALL_LOOKUPS in favor of '__all__' constant.
19-
* AllLookupsFilter now generates only valid lookup expressions.
25+
* Deprecates ALL_LOOKUPS in favor of '__all__' constant
26+
* AllLookupsFilter now generates only valid lookup expressions
2027

2128
* #2 'range' lookup types do not work
2229
* #15 Date lookup types do not work (year, day, ...)
@@ -52,7 +59,7 @@ v0.5.0:
5259
* #31 Fix timezone-aware datetime handling
5360
* #36 Fix '__in' filter to work with strings
5461
* #33 Fix RelatedFilter handling to not override existing isnull filters
55-
* #35 Fix python 3.5 compatibility issue.
62+
* #35 Fix python 3.5 compatibility issue
5663
* Drops support for Django 1.6 and below
5764

5865
v0.4.0:

rest_framework_filters/filterset.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,14 @@ def convert__all__(attrs):
9999
"""
100100
Extract Meta.fields and convert any fields w/ `__all__`
101101
to a declared AllLookupsFilter.
102+
103+
This is a temporary hack to fix #87.
102104
"""
103105
meta = attrs.get('Meta', None)
106+
model = getattr(meta, 'model', None)
104107
fields = getattr(meta, 'fields', None)
105108

106-
if isinstance(fields, dict):
109+
if model and isinstance(fields, dict):
107110
for name, lookups in six.iteritems(fields.copy()):
108111
if lookups == filters.ALL_LOOKUPS:
109112
warnings.warn(
@@ -114,8 +117,10 @@ def convert__all__(attrs):
114117
lookups = '__all__'
115118

116119
if lookups == '__all__':
117-
del fields[name]
118-
attrs[name] = filters.AllLookupsFilter()
120+
# Modifying fields is incorrect. The correct behavior will
121+
# require hooking into filters_for_model
122+
field = model._meta.get_field(name)
123+
fields[name] = utils.lookups_for_field(field)
119124

120125

121126
class FilterSet(six.with_metaclass(FilterSetMetaclass, filterset.FilterSet)):

rest_framework_filters/utils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import django
55
from django.db.models.constants import LOOKUP_SEP
66
from django.db.models.expressions import Expression
7+
from django.db.models.fields.related import ForeignObject
78
from django.db.models.lookups import Transform
89
from django.utils import six
910

@@ -12,6 +13,11 @@ def lookups_for_field(model_field):
1213
"""
1314
Generates a list of all possible lookup expressions for a model field.
1415
"""
16+
# This is a hack to work around:
17+
# https://github.com/django/django/pull/6906
18+
if isinstance(model_field, ForeignObject):
19+
return ['exact', 'gt', 'gte', 'lt', 'lte', 'in', 'isnull']
20+
1521
lookups = []
1622

1723
for expr, lookup in six.iteritems(class_lookups(model_field)):

tests/test_filterset.py

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from django.test import TestCase
88

99
from rest_framework_filters import FilterSet, filters
10+
from django_filters.filters import BaseInFilter
1011

1112
from .testapp.models import (
1213
User, Note, Post, Cover, Page, A, B, C, Person, Tag, BlogPost,
@@ -156,7 +157,48 @@ def test_relatedfilter(self):
156157
note = list(f)[0]
157158
self.assertEqual(note.title, "Hello Test 4")
158159

159-
def test_relatedfilter_combined_with_alllookups(self):
160+
def test_alllookupsfilter_for_relation(self):
161+
# See: https://github.com/philipn/django-rest-framework-filters/issues/84
162+
class F(FilterSet):
163+
class Meta:
164+
model = Note
165+
fields = {
166+
'author': '__all__',
167+
}
168+
169+
self.assertIsInstance(F.base_filters['author'], filters.ModelChoiceFilter)
170+
self.assertIsInstance(F.base_filters['author__in'], BaseInFilter)
171+
172+
def test_relatedfilter_combined_with__all__(self):
173+
# ensure that related filter is compatible with __all__ lookups.
174+
class F(FilterSet):
175+
author = filters.RelatedFilter(UserFilter)
176+
177+
class Meta:
178+
model = Note
179+
fields = {
180+
'author': '__all__',
181+
}
182+
183+
self.assertIsInstance(F.base_filters['author'], filters.RelatedFilter)
184+
self.assertIsInstance(F.base_filters['author__in'], BaseInFilter)
185+
186+
def test_filter_persistence_with__all__(self):
187+
# ensure that __all__ does not overwrite declared filters.
188+
class F(FilterSet):
189+
name = filters.ChoiceFilter(lookup_expr='iexact')
190+
191+
class Meta:
192+
model = Person
193+
fields = {
194+
'name': '__all__',
195+
}
196+
197+
self.assertIsInstance(F.base_filters['name'], filters.ChoiceFilter)
198+
199+
def test_relatedfilter_for_related_alllookups(self):
200+
# ensure that filters work for AllLookupsFilter across a RelatedFilter.
201+
160202
# Test that the default exact filter works
161203
GET = {
162204
'author': User.objects.get(username='user2').pk,
@@ -195,7 +237,7 @@ def test_relatedfilter_combined_with_alllookups(self):
195237
f = NoteFilterWithRelatedAll(GET, queryset=Note.objects.all())
196238
self.assertEqual(len(list(f)), 4)
197239

198-
def test_relatedfilter_combined_with_alllookups_and_different_filter_name(self):
240+
def test_relatedfilter_for_related_alllookups_and_different_filter_name(self):
199241
# Test that the default exact filter works
200242
GET = {
201243
'writer': User.objects.get(username='user2').pk,
@@ -294,7 +336,7 @@ def test_m2m_relation(self):
294336
}
295337
f = BlogPostFilter(GET, queryset=BlogPost.objects.all())
296338
self.assertEqual(len(list(f)), 2)
297-
titles = set([p.title for p in f])
339+
titles = set([person.title for person in f])
298340
self.assertEqual(titles, set(["First post", "Second post"]))
299341

300342
def test_nonexistent_related_field(self):

tests/test_utils.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from rest_framework_filters import utils
77

8-
from .testapp.models import Person
8+
from .testapp.models import Person, Note
99

1010

1111
class LookupsForFieldTests(TestCase):
@@ -29,6 +29,15 @@ def test_transformed_field(self):
2929
self.assertIn('year__exact', lookups)
3030
self.assertIn('date__year__exact', lookups)
3131

32+
def test_relation_field(self):
33+
# ForeignObject relations are special cased currently
34+
model_field = Note._meta.get_field('author')
35+
lookups = utils.lookups_for_field(model_field)
36+
37+
self.assertIn('exact', lookups)
38+
self.assertIn('in', lookups)
39+
self.assertNotIn('regex', lookups)
40+
3241

3342
@unittest.skipIf(django.VERSION < (1, 9), "version does not support transformed lookup expressions")
3443
class LookupsForTransformTests(TestCase):

0 commit comments

Comments
 (0)