Skip to content

Commit a9a8d67

Browse files
authored
Merge pull request #600 from sierreis/filterset-class
Add support for filterset_class meta parameter
2 parents b271b25 + d2f8bf7 commit a9a8d67

File tree

5 files changed

+112
-6
lines changed

5 files changed

+112
-6
lines changed

docs/filtering.rst

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ features of ``django-filter``. This is done by transparently creating a
100100
``filter_fields``.
101101

102102
However, you may find this to be insufficient. In these cases you can
103-
create your own ``Filterset`` as follows:
103+
create your own ``FilterSet``. You can pass it directly as follows:
104104

105105
.. code:: python
106106
@@ -127,6 +127,33 @@ create your own ``Filterset`` as follows:
127127
all_animals = DjangoFilterConnectionField(AnimalNode,
128128
filterset_class=AnimalFilter)
129129
130+
You can also specify the ``FilterSet`` class using the ``filerset_class``
131+
parameter when defining your ``DjangoObjectType``, however, this can't be used
132+
in unison with the ``filter_fields`` parameter:
133+
134+
.. code:: python
135+
136+
class AnimalFilter(django_filters.FilterSet):
137+
# Do case-insensitive lookups on 'name'
138+
name = django_filters.CharFilter(lookup_expr=['iexact'])
139+
140+
class Meta:
141+
# Assume you have an Animal model defined with the following fields
142+
model = Animal
143+
fields = ['name', 'genus', 'is_domesticated']
144+
145+
146+
class AnimalNode(DjangoObjectType):
147+
class Meta:
148+
model = Animal
149+
filterset_class = AnimalFilter
150+
interfaces = (relay.Node, )
151+
152+
153+
class Query(ObjectType):
154+
animal = relay.Node.Field(AnimalNode)
155+
all_animals = DjangoFilterConnectionField(AnimalNode)
156+
130157
The context argument is passed on as the `request argument <http://django-filter.readthedocs.io/en/master/guide/usage.html#request-based-filtering>`__
131158
in a ``django_filters.FilterSet`` instance. You can use this to customize your
132159
filters to be context-dependent. We could modify the ``AnimalFilter`` above to

graphene_django/converter.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,9 @@ def dynamic_type():
181181
# into a DjangoConnectionField
182182
if _type._meta.connection:
183183
# Use a DjangoFilterConnectionField if there are
184-
# defined filter_fields in the DjangoObjectType Meta
185-
if _type._meta.filter_fields:
184+
# defined filter_fields or a filterset_class in the
185+
# DjangoObjectType Meta
186+
if _type._meta.filter_fields or _type._meta.filterset_class:
186187
from .filter.fields import DjangoFilterConnectionField
187188

188189
return DjangoFilterConnectionField(_type)

graphene_django/filter/fields.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ def filterset_class(self):
4040
if self._extra_filter_meta:
4141
meta.update(self._extra_filter_meta)
4242

43+
filterset_class = self._provided_filterset_class or (
44+
self.node_type._meta.filterset_class)
4345
self._filterset_class = get_filterset_class(
44-
self._provided_filterset_class, **meta
46+
filterset_class, **meta
4547
)
4648

4749
return self._filterset_class

graphene_django/filter/tests/test_fields.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,73 @@ class Query(ObjectType):
227227
assert_not_orderable(articles_field)
228228

229229

230+
def test_filter_filterset_class_filter_fields_exception():
231+
with pytest.raises(Exception):
232+
class ReporterFilter(FilterSet):
233+
class Meta:
234+
model = Reporter
235+
fields = ["first_name", "articles"]
236+
237+
class ReporterFilterNode(DjangoObjectType):
238+
class Meta:
239+
model = Reporter
240+
interfaces = (Node,)
241+
filterset_class = ReporterFilter
242+
filter_fields = ["first_name", "articles"]
243+
244+
245+
def test_filter_filterset_class_information_on_meta():
246+
class ReporterFilter(FilterSet):
247+
class Meta:
248+
model = Reporter
249+
fields = ["first_name", "articles"]
250+
251+
class ReporterFilterNode(DjangoObjectType):
252+
class Meta:
253+
model = Reporter
254+
interfaces = (Node,)
255+
filterset_class = ReporterFilter
256+
257+
field = DjangoFilterConnectionField(ReporterFilterNode)
258+
assert_arguments(field, "first_name", "articles")
259+
assert_not_orderable(field)
260+
261+
262+
def test_filter_filterset_class_information_on_meta_related():
263+
class ReporterFilter(FilterSet):
264+
class Meta:
265+
model = Reporter
266+
fields = ["first_name", "articles"]
267+
268+
class ArticleFilter(FilterSet):
269+
class Meta:
270+
model = Article
271+
fields = ["headline", "reporter"]
272+
273+
class ReporterFilterNode(DjangoObjectType):
274+
class Meta:
275+
model = Reporter
276+
interfaces = (Node,)
277+
filterset_class = ReporterFilter
278+
279+
class ArticleFilterNode(DjangoObjectType):
280+
class Meta:
281+
model = Article
282+
interfaces = (Node,)
283+
filterset_class = ArticleFilter
284+
285+
class Query(ObjectType):
286+
all_reporters = DjangoFilterConnectionField(ReporterFilterNode)
287+
all_articles = DjangoFilterConnectionField(ArticleFilterNode)
288+
reporter = Field(ReporterFilterNode)
289+
article = Field(ArticleFilterNode)
290+
291+
schema = Schema(query=Query)
292+
articles_field = ReporterFilterNode._meta.fields["articles"].get_type()
293+
assert_arguments(articles_field, "headline", "reporter")
294+
assert_not_orderable(articles_field)
295+
296+
230297
def test_filter_filterset_related_results():
231298
class ReporterFilterNode(DjangoObjectType):
232299
class Meta:

graphene_django/types.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class DjangoObjectTypeOptions(ObjectTypeOptions):
4545
connection = None # type: Type[Connection]
4646

4747
filter_fields = ()
48+
filterset_class = None
4849

4950

5051
class DjangoObjectType(ObjectType):
@@ -57,6 +58,7 @@ def __init_subclass_with_meta__(
5758
only_fields=(),
5859
exclude_fields=(),
5960
filter_fields=None,
61+
filterset_class=None,
6062
connection=None,
6163
connection_class=None,
6264
use_connection=None,
@@ -76,8 +78,14 @@ def __init_subclass_with_meta__(
7678
'Registry, received "{}".'
7779
).format(cls.__name__, registry)
7880

79-
if not DJANGO_FILTER_INSTALLED and filter_fields:
80-
raise Exception("Can only set filter_fields if Django-Filter is installed")
81+
if filter_fields and filterset_class:
82+
raise Exception("Can't set both filter_fields and filterset_class")
83+
84+
if not DJANGO_FILTER_INSTALLED and (filter_fields or filterset_class):
85+
raise Exception((
86+
"Can only set filter_fields or filterset_class if "
87+
"Django-Filter is installed"
88+
))
8189

8290
django_fields = yank_fields_from_attrs(
8391
construct_fields(model, registry, only_fields, exclude_fields), _as=Field
@@ -108,6 +116,7 @@ def __init_subclass_with_meta__(
108116
_meta.model = model
109117
_meta.registry = registry
110118
_meta.filter_fields = filter_fields
119+
_meta.filterset_class = filterset_class
111120
_meta.fields = django_fields
112121
_meta.connection = connection
113122

0 commit comments

Comments
 (0)