Skip to content

Commit 1167ba6

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 1167ba6

File tree

2 files changed

+105
-4
lines changed

2 files changed

+105
-4
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 CustomFilterMethod(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 = CustomFilterMethod(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 & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import pytest
2+
import operator
3+
from functools import reduce
24
from django_filters import FilterSet
5+
from django.db.models import Q
36

47
import graphene
58
from graphene.relay import Node
@@ -44,6 +47,10 @@ class Meta:
4447
only_first = TypedFilter(
4548
input_type=graphene.Boolean, method="only_first_filter"
4649
)
50+
headline_search = ListFilter(
51+
method="headline_search_filter",
52+
input_type=graphene.List(graphene.String),
53+
)
4754

4855
def first_n_filter(self, queryset, _name, value):
4956
return queryset[:value]
@@ -54,6 +61,13 @@ def only_first_filter(self, queryset, _name, value):
5461
else:
5562
return queryset
5663

64+
def headline_search_filter(self, queryset, _name, value):
65+
if not value:
66+
return queryset.none()
67+
return queryset.filter(
68+
reduce(operator.or_, [Q(headline__icontains=v) for v in value])
69+
)
70+
5771
class ArticleType(DjangoObjectType):
5872
class Meta:
5973
model = Article
@@ -87,14 +101,15 @@ def test_typed_filter_schema(schema):
87101
"lang_InStr": "[String]",
88102
"firstN": "Int",
89103
"onlyFirst": "Boolean",
104+
"headlineSearch": "[String]",
90105
}
91106

92107
all_articles_filters = (
93108
schema_str.split(" articles(")[1]
94109
.split("): ArticleTypeConnection\n")[0]
95110
.split(", ")
96111
)
97-
112+
print(all_articles_filters)
98113
for filter_field, gql_type in filters.items():
99114
assert f"{filter_field}: {gql_type}" in all_articles_filters
100115

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

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

110159
result = schema.execute(query)
111160
assert not result.errors
112161
assert result.data["articles"]["edges"] == [
113162
{"node": {"headline": "A"}},
163+
{"node": {"headline": "AB"}},
114164
{"node": {"headline": "B"}},
115165
]
116166

@@ -120,30 +170,61 @@ def test_typed_filters_work(schema):
120170
assert not result.errors
121171
assert result.data["articles"]["edges"] == [
122172
{"node": {"headline": "A"}},
173+
{"node": {"headline": "AB"}},
123174
{"node": {"headline": "B"}},
124175
]
125176

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

128185
result = schema.execute(query)
129186
assert not result.errors
130187
assert result.data["articles"]["edges"] == [
188+
{"node": {"headline": "A"}},
189+
{"node": {"headline": "AB"}},
190+
{"node": {"headline": "B"}},
131191
{"node": {"headline": "C"}},
132192
]
133193

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

136196
result = schema.execute(query)
137197
assert not result.errors
138198
assert result.data["articles"]["edges"] == [
139199
{"node": {"headline": "A"}},
200+
{"node": {"headline": "AB"}},
140201
{"node": {"headline": "B"}},
141202
]
142203

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

145223
result = schema.execute(query)
146224
assert not result.errors
147225
assert result.data["articles"]["edges"] == [
148226
{"node": {"headline": "A"}},
227+
{"node": {"headline": "AB"}},
228+
{"node": {"headline": "B"}},
229+
{"node": {"headline": "C"}},
149230
]

0 commit comments

Comments
 (0)