Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for filterset_class meta parameter #600

Merged
merged 4 commits into from Jun 9, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
4 changes: 3 additions & 1 deletion graphene_django/filter/fields.py
Expand Up @@ -40,8 +40,10 @@ def filterset_class(self):
if self._extra_filter_meta:
meta.update(self._extra_filter_meta)

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

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
13 changes: 11 additions & 2 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 @@ -75,8 +77,14 @@ def __init_subclass_with_meta__(
'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