Skip to content

Commit 887ee5c

Browse files
author
Thomas Leonard
committed
fix: empty list is not an empty value for list filters even when a custom filtering method is provided
1 parent 4ac3f3f commit 887ee5c

File tree

2 files changed

+105
-3
lines changed

2 files changed

+105
-3
lines changed

graphene_django/filter/filters/list_filter.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,32 @@
1+
from django_filters.filters import FilterMethod
2+
13
from .typed_filter import TypedFilter
24

35

6+
class ListFilterMethod(FilterMethod):
7+
def __call__(self, qs, value):
8+
if value is None:
9+
return qs
10+
return self.method(qs, self.f.field_name, value)
11+
12+
413
class ListFilter(TypedFilter):
514
"""
615
Filter that takes a list of value as input.
716
It is for example used for `__in` filters.
817
"""
918

19+
@TypedFilter.method.setter
20+
def method(self, value):
21+
"""
22+
Override method setter so that in case a custom `method` is provided, it doesn't fall back to
23+
checking if the value is in `EMPTY_VALUES` (from the `__call__` method of the `FilterMethod` class)
24+
and instead use our CustomFilterMethod that consider empty lists as values
25+
"""
26+
TypedFilter.method.fset(self, value)
27+
if value is not None:
28+
self.filter = ListFilterMethod(self)
29+
1030
def filter(self, qs, value):
1131
"""
1232
Override the default filter class to check first whether the list is

graphene_django/filter/tests/test_typed_filter.py

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
import operator
2+
from functools import reduce
3+
14
import pytest
5+
from django.db.models import Q
26
from django_filters import FilterSet
37

48
import graphene
@@ -44,6 +48,10 @@ class Meta:
4448
only_first = TypedFilter(
4549
input_type=graphene.Boolean, method="only_first_filter"
4650
)
51+
headline_search = ListFilter(
52+
method="headline_search_filter",
53+
input_type=graphene.List(graphene.String),
54+
)
4755

4856
def first_n_filter(self, queryset, _name, value):
4957
return queryset[:value]
@@ -54,6 +62,13 @@ def only_first_filter(self, queryset, _name, value):
5462
else:
5563
return queryset
5664

65+
def headline_search_filter(self, queryset, _name, value):
66+
if not value:
67+
return queryset.none()
68+
return queryset.filter(
69+
reduce(operator.or_, [Q(headline__icontains=v) for v in value])
70+
)
71+
5772
class ArticleType(DjangoObjectType):
5873
class Meta:
5974
model = Article
@@ -87,6 +102,7 @@ def test_typed_filter_schema(schema):
87102
"lang_InStr": "[String]",
88103
"firstN": "Int",
89104
"onlyFirst": "Boolean",
105+
"headlineSearch": "[String]",
90106
}
91107

92108
all_articles_filters = (
@@ -104,13 +120,48 @@ def test_typed_filters_work(schema):
104120
Article.objects.create(headline="A", reporter=reporter, editor=reporter, lang="es")
105121
Article.objects.create(headline="B", reporter=reporter, editor=reporter, lang="es")
106122
Article.objects.create(headline="C", reporter=reporter, editor=reporter, lang="en")
123+
Article.objects.create(headline="AB", reporter=reporter, editor=reporter, lang="es")
124+
125+
query = 'query { articles (lang_Contains: "n") { edges { node { headline } } } }'
126+
127+
result = schema.execute(query)
128+
assert not result.errors
129+
assert result.data["articles"]["edges"] == [
130+
{"node": {"headline": "C"}},
131+
]
132+
133+
query = "query { articles (firstN: 2) { edges { node { headline } } } }"
134+
135+
result = schema.execute(query)
136+
assert not result.errors
137+
assert result.data["articles"]["edges"] == [
138+
{"node": {"headline": "A"}},
139+
{"node": {"headline": "AB"}},
140+
]
141+
142+
query = "query { articles (onlyFirst: true) { edges { node { headline } } } }"
143+
144+
result = schema.execute(query)
145+
assert not result.errors
146+
assert result.data["articles"]["edges"] == [
147+
{"node": {"headline": "A"}},
148+
]
149+
150+
151+
def test_list_filters_work(schema):
152+
reporter = Reporter.objects.create(first_name="John", last_name="Doe", email="")
153+
Article.objects.create(headline="A", reporter=reporter, editor=reporter, lang="es")
154+
Article.objects.create(headline="B", reporter=reporter, editor=reporter, lang="es")
155+
Article.objects.create(headline="C", reporter=reporter, editor=reporter, lang="en")
156+
Article.objects.create(headline="AB", reporter=reporter, editor=reporter, lang="es")
107157

108158
query = "query { articles (lang_In: [ES]) { edges { node { headline } } } }"
109159

110160
result = schema.execute(query)
111161
assert not result.errors
112162
assert result.data["articles"]["edges"] == [
113163
{"node": {"headline": "A"}},
164+
{"node": {"headline": "AB"}},
114165
{"node": {"headline": "B"}},
115166
]
116167

@@ -120,30 +171,61 @@ def test_typed_filters_work(schema):
120171
assert not result.errors
121172
assert result.data["articles"]["edges"] == [
122173
{"node": {"headline": "A"}},
174+
{"node": {"headline": "AB"}},
123175
{"node": {"headline": "B"}},
124176
]
125177

126-
query = 'query { articles (lang_Contains: "n") { edges { node { headline } } } }'
178+
query = "query { articles (lang_InStr: []) { edges { node { headline } } } }"
179+
180+
result = schema.execute(query)
181+
assert not result.errors
182+
assert result.data["articles"]["edges"] == []
183+
184+
query = "query { articles (lang_InStr: null) { edges { node { headline } } } }"
127185

128186
result = schema.execute(query)
129187
assert not result.errors
130188
assert result.data["articles"]["edges"] == [
189+
{"node": {"headline": "A"}},
190+
{"node": {"headline": "AB"}},
191+
{"node": {"headline": "B"}},
131192
{"node": {"headline": "C"}},
132193
]
133194

134-
query = "query { articles (firstN: 2) { edges { node { headline } } } }"
195+
query = 'query { articles (headlineSearch: ["a", "B"]) { edges { node { headline } } } }'
135196

136197
result = schema.execute(query)
137198
assert not result.errors
138199
assert result.data["articles"]["edges"] == [
139200
{"node": {"headline": "A"}},
201+
{"node": {"headline": "AB"}},
140202
{"node": {"headline": "B"}},
141203
]
142204

143-
query = "query { articles (onlyFirst: true) { edges { node { headline } } } }"
205+
query = "query { articles (headlineSearch: []) { edges { node { headline } } } }"
206+
207+
result = schema.execute(query)
208+
assert not result.errors
209+
assert result.data["articles"]["edges"] == []
210+
211+
query = "query { articles (headlineSearch: null) { edges { node { headline } } } }"
212+
213+
result = schema.execute(query)
214+
assert not result.errors
215+
assert result.data["articles"]["edges"] == [
216+
{"node": {"headline": "A"}},
217+
{"node": {"headline": "AB"}},
218+
{"node": {"headline": "B"}},
219+
{"node": {"headline": "C"}},
220+
]
221+
222+
query = 'query { articles (headlineSearch: [""]) { edges { node { headline } } } }'
144223

145224
result = schema.execute(query)
146225
assert not result.errors
147226
assert result.data["articles"]["edges"] == [
148227
{"node": {"headline": "A"}},
228+
{"node": {"headline": "AB"}},
229+
{"node": {"headline": "B"}},
230+
{"node": {"headline": "C"}},
149231
]

0 commit comments

Comments
 (0)