Skip to content

Commit

Permalink
Add support for filterset_class meta parameter
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
sierreis committed Mar 25, 2019
1 parent ea2cd98 commit 5c191b9
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 13 deletions.
29 changes: 28 additions & 1 deletion docs/filtering.rst
Expand Up @@ -100,7 +100,7 @@ features of ``django-filter``. This is done by transparently creating a
``filter_fields``.

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

.. code:: python
Expand All @@ -127,6 +127,33 @@ create your own ``Filterset`` as follows:
all_animals = DjangoFilterConnectionField(AnimalNode,
filterset_class=AnimalFilter)
You can also specify the ``FilterSet`` class using the ``filerset_class``
parameter when defining your ``DjangoObjectType``, however, this can't be used
in unison with the ``filter_fields`` parameter:

.. code:: python
class AnimalFilter(django_filters.FilterSet):
# Do case-insensitive lookups on 'name'
name = django_filters.CharFilter(lookup_expr=['iexact'])
class Meta:
# Assume you have an Animal model defined with the following fields
model = Animal
fields = ['name', 'genus', 'is_domesticated']
class AnimalNode(DjangoObjectType):
class Meta:
model = Animal
filterset_class = AnimalFilter
interfaces = (relay.Node, )
class Query(ObjectType):
animal = relay.Node.Field(AnimalNode)
all_animals = DjangoFilterConnectionField(AnimalNode)
The context argument is passed on as the `request argument <http://django-filter.readthedocs.io/en/master/guide/usage.html#request-based-filtering>`__
in a ``django_filters.FilterSet`` instance. You can use this to customize your
filters to be context-dependent. We could modify the ``AnimalFilter`` above to
Expand Down
5 changes: 3 additions & 2 deletions graphene_django/converter.py
Expand Up @@ -181,8 +181,9 @@ def dynamic_type():
# into a DjangoConnectionField
if _type._meta.connection:
# Use a DjangoFilterConnectionField if there are
# defined filter_fields in the DjangoObjectType Meta
if _type._meta.filter_fields:
# defined filter_fields or a filterset_class in the
# DjangoObjectType Meta
if _type._meta.filter_fields or _type._meta.filterset_class:
from .filter.fields import DjangoFilterConnectionField

return DjangoFilterConnectionField(_type)
Expand Down
17 changes: 10 additions & 7 deletions graphene_django/filter/fields.py
Expand Up @@ -35,14 +35,17 @@ def args(self, args):
@property
def filterset_class(self):
if not self._filterset_class:
fields = self._fields or self.node_type._meta.filter_fields
meta = dict(model=self.model, fields=fields)
if self._extra_filter_meta:
meta.update(self._extra_filter_meta)
if not self.node_type._meta.filterset_class:
fields = self._fields or self.node_type._meta.filter_fields
meta = dict(model=self.model, fields=fields)
if self._extra_filter_meta:
meta.update(self._extra_filter_meta)

self._filterset_class = get_filterset_class(
self._provided_filterset_class, **meta
)
self._filterset_class = get_filterset_class(
self._provided_filterset_class, **meta
)
else:
self._filterset_class = self.node_type._meta.filterset_class

return self._filterset_class

Expand Down
52 changes: 52 additions & 0 deletions graphene_django/filter/tests/test_fields.py
Expand Up @@ -227,6 +227,58 @@ class Query(ObjectType):
assert_not_orderable(articles_field)


def test_filter_filterset_class_information_on_meta():
class ReporterFilter(FilterSet):
class Meta:
model = Reporter
fields = ["first_name", "articles"]

class ReporterFilterNode(DjangoObjectType):
class Meta:
model = Reporter
interfaces = (Node,)
filterset_class = ReporterFilter

field = DjangoFilterConnectionField(ReporterFilterNode)
assert_arguments(field, "first_name", "articles")
assert_not_orderable(field)


def test_filter_filterset_class_information_on_meta_related():
class ReporterFilter(FilterSet):
class Meta:
model = Reporter
fields = ["first_name", "articles"]

class ArticleFilter(FilterSet):
class Meta:
model = Article
fields = ["headline", "reporter"]

class ReporterFilterNode(DjangoObjectType):
class Meta:
model = Reporter
interfaces = (Node,)
filterset_class = ReporterFilter

class ArticleFilterNode(DjangoObjectType):
class Meta:
model = Article
interfaces = (Node,)
filterset_class = ArticleFilter

class Query(ObjectType):
all_reporters = DjangoFilterConnectionField(ReporterFilterNode)
all_articles = DjangoFilterConnectionField(ArticleFilterNode)
reporter = Field(ReporterFilterNode)
article = Field(ArticleFilterNode)

schema = Schema(query=Query)
articles_field = ReporterFilterNode._meta.fields["articles"].get_type()
assert_arguments(articles_field, "headline", "reporter")
assert_not_orderable(articles_field)


def test_filter_filterset_related_results():
class ReporterFilterNode(DjangoObjectType):
class Meta:
Expand Down
15 changes: 12 additions & 3 deletions graphene_django/types.py
Expand Up @@ -44,6 +44,7 @@ class DjangoObjectTypeOptions(ObjectTypeOptions):
connection = None # type: Type[Connection]

filter_fields = ()
filterset_class = None


class DjangoObjectType(ObjectType):
Expand All @@ -56,6 +57,7 @@ def __init_subclass_with_meta__(
only_fields=(),
exclude_fields=(),
filter_fields=None,
filterset_class=None,
connection=None,
connection_class=None,
use_connection=None,
Expand All @@ -74,9 +76,15 @@ def __init_subclass_with_meta__(
"The attribute registry in {} needs to be an instance of "
'Registry, received "{}".'
).format(cls.__name__, registry)

if not DJANGO_FILTER_INSTALLED and filter_fields:
raise Exception("Can only set filter_fields if Django-Filter is installed")

if filter_fields and filterset_class:
raise Exception("Can't set both filter_fields and filterset_class")

if not DJANGO_FILTER_INSTALLED and (filter_fields or filterset_class):
raise Exception((
"Can only set filter_fields or filterset_class if "
"Django-Filter is installed"
))

django_fields = yank_fields_from_attrs(
construct_fields(model, registry, only_fields, exclude_fields), _as=Field
Expand Down Expand Up @@ -107,6 +115,7 @@ def __init_subclass_with_meta__(
_meta.model = model
_meta.registry = registry
_meta.filter_fields = filter_fields
_meta.filterset_class = filterset_class
_meta.fields = django_fields
_meta.connection = connection

Expand Down

0 comments on commit 5c191b9

Please sign in to comment.