Skip to content

Commit 5c191b9

Browse files
author
sierreis
committed
Add support for filterset_class meta parameter
* Allow for use of either filter_fields or filterset_class * Add tests to check that the behavior is similar to filter_fields * Add documentation to show how to make use of the parameter
1 parent ea2cd98 commit 5c191b9

File tree

5 files changed

+105
-13
lines changed

5 files changed

+105
-13
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: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,17 @@ def args(self, args):
3535
@property
3636
def filterset_class(self):
3737
if not self._filterset_class:
38-
fields = self._fields or self.node_type._meta.filter_fields
39-
meta = dict(model=self.model, fields=fields)
40-
if self._extra_filter_meta:
41-
meta.update(self._extra_filter_meta)
38+
if not self.node_type._meta.filterset_class:
39+
fields = self._fields or self.node_type._meta.filter_fields
40+
meta = dict(model=self.model, fields=fields)
41+
if self._extra_filter_meta:
42+
meta.update(self._extra_filter_meta)
4243

43-
self._filterset_class = get_filterset_class(
44-
self._provided_filterset_class, **meta
45-
)
44+
self._filterset_class = get_filterset_class(
45+
self._provided_filterset_class, **meta
46+
)
47+
else:
48+
self._filterset_class = self.node_type._meta.filterset_class
4649

4750
return self._filterset_class
4851

graphene_django/filter/tests/test_fields.py

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

229229

230+
def test_filter_filterset_class_information_on_meta():
231+
class ReporterFilter(FilterSet):
232+
class Meta:
233+
model = Reporter
234+
fields = ["first_name", "articles"]
235+
236+
class ReporterFilterNode(DjangoObjectType):
237+
class Meta:
238+
model = Reporter
239+
interfaces = (Node,)
240+
filterset_class = ReporterFilter
241+
242+
field = DjangoFilterConnectionField(ReporterFilterNode)
243+
assert_arguments(field, "first_name", "articles")
244+
assert_not_orderable(field)
245+
246+
247+
def test_filter_filterset_class_information_on_meta_related():
248+
class ReporterFilter(FilterSet):
249+
class Meta:
250+
model = Reporter
251+
fields = ["first_name", "articles"]
252+
253+
class ArticleFilter(FilterSet):
254+
class Meta:
255+
model = Article
256+
fields = ["headline", "reporter"]
257+
258+
class ReporterFilterNode(DjangoObjectType):
259+
class Meta:
260+
model = Reporter
261+
interfaces = (Node,)
262+
filterset_class = ReporterFilter
263+
264+
class ArticleFilterNode(DjangoObjectType):
265+
class Meta:
266+
model = Article
267+
interfaces = (Node,)
268+
filterset_class = ArticleFilter
269+
270+
class Query(ObjectType):
271+
all_reporters = DjangoFilterConnectionField(ReporterFilterNode)
272+
all_articles = DjangoFilterConnectionField(ArticleFilterNode)
273+
reporter = Field(ReporterFilterNode)
274+
article = Field(ArticleFilterNode)
275+
276+
schema = Schema(query=Query)
277+
articles_field = ReporterFilterNode._meta.fields["articles"].get_type()
278+
assert_arguments(articles_field, "headline", "reporter")
279+
assert_not_orderable(articles_field)
280+
281+
230282
def test_filter_filterset_related_results():
231283
class ReporterFilterNode(DjangoObjectType):
232284
class Meta:

graphene_django/types.py

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

4646
filter_fields = ()
47+
filterset_class = None
4748

4849

4950
class DjangoObjectType(ObjectType):
@@ -56,6 +57,7 @@ def __init_subclass_with_meta__(
5657
only_fields=(),
5758
exclude_fields=(),
5859
filter_fields=None,
60+
filterset_class=None,
5961
connection=None,
6062
connection_class=None,
6163
use_connection=None,
@@ -74,9 +76,15 @@ def __init_subclass_with_meta__(
7476
"The attribute registry in {} needs to be an instance of "
7577
'Registry, received "{}".'
7678
).format(cls.__name__, registry)
77-
78-
if not DJANGO_FILTER_INSTALLED and filter_fields:
79-
raise Exception("Can only set filter_fields if Django-Filter is installed")
79+
80+
if filter_fields and filterset_class:
81+
raise Exception("Can't set both filter_fields and filterset_class")
82+
83+
if not DJANGO_FILTER_INSTALLED and (filter_fields or filterset_class):
84+
raise Exception((
85+
"Can only set filter_fields or filterset_class if "
86+
"Django-Filter is installed"
87+
))
8088

8189
django_fields = yank_fields_from_attrs(
8290
construct_fields(model, registry, only_fields, exclude_fields), _as=Field
@@ -107,6 +115,7 @@ def __init_subclass_with_meta__(
107115
_meta.model = model
108116
_meta.registry = registry
109117
_meta.filter_fields = filter_fields
118+
_meta.filterset_class = filterset_class
110119
_meta.fields = django_fields
111120
_meta.connection = connection
112121

0 commit comments

Comments
 (0)