Merge pull request #600 from sierreis/filterset-class

Add support for filterset_class meta parameter
This commit is contained in:
Mel van Londen 2019-06-09 16:48:46 -07:00 committed by GitHub
commit a9a8d672e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 112 additions and 6 deletions

View File

@ -100,7 +100,7 @@ features of ``django-filter``. This is done by transparently creating a
``filter_fields``. ``filter_fields``.
However, you may find this to be insufficient. In these cases you can 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 .. code:: python
@ -127,6 +127,33 @@ create your own ``Filterset`` as follows:
all_animals = DjangoFilterConnectionField(AnimalNode, all_animals = DjangoFilterConnectionField(AnimalNode,
filterset_class=AnimalFilter) 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>`__ 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 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 filters to be context-dependent. We could modify the ``AnimalFilter`` above to

View File

@ -181,8 +181,9 @@ def convert_field_to_list_or_connection(field, registry=None):
# into a DjangoConnectionField # into a DjangoConnectionField
if _type._meta.connection: if _type._meta.connection:
# Use a DjangoFilterConnectionField if there are # Use a DjangoFilterConnectionField if there are
# defined filter_fields in the DjangoObjectType Meta # defined filter_fields or a filterset_class in the
if _type._meta.filter_fields: # DjangoObjectType Meta
if _type._meta.filter_fields or _type._meta.filterset_class:
from .filter.fields import DjangoFilterConnectionField from .filter.fields import DjangoFilterConnectionField
return DjangoFilterConnectionField(_type) return DjangoFilterConnectionField(_type)

View File

@ -40,8 +40,10 @@ class DjangoFilterConnectionField(DjangoConnectionField):
if self._extra_filter_meta: if self._extra_filter_meta:
meta.update(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._filterset_class = get_filterset_class(
self._provided_filterset_class, **meta filterset_class, **meta
) )
return self._filterset_class return self._filterset_class

View File

@ -227,6 +227,73 @@ def test_filter_filterset_information_on_meta_related():
assert_not_orderable(articles_field) assert_not_orderable(articles_field)
def test_filter_filterset_class_filter_fields_exception():
with pytest.raises(Exception):
class ReporterFilter(FilterSet):
class Meta:
model = Reporter
fields = ["first_name", "articles"]
class ReporterFilterNode(DjangoObjectType):
class Meta:
model = Reporter
interfaces = (Node,)
filterset_class = ReporterFilter
filter_fields = ["first_name", "articles"]
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(): def test_filter_filterset_related_results():
class ReporterFilterNode(DjangoObjectType): class ReporterFilterNode(DjangoObjectType):
class Meta: class Meta:

View File

@ -45,6 +45,7 @@ class DjangoObjectTypeOptions(ObjectTypeOptions):
connection = None # type: Type[Connection] connection = None # type: Type[Connection]
filter_fields = () filter_fields = ()
filterset_class = None
class DjangoObjectType(ObjectType): class DjangoObjectType(ObjectType):
@ -57,6 +58,7 @@ class DjangoObjectType(ObjectType):
only_fields=(), only_fields=(),
exclude_fields=(), exclude_fields=(),
filter_fields=None, filter_fields=None,
filterset_class=None,
connection=None, connection=None,
connection_class=None, connection_class=None,
use_connection=None, use_connection=None,
@ -76,8 +78,14 @@ class DjangoObjectType(ObjectType):
'Registry, received "{}".' 'Registry, received "{}".'
).format(cls.__name__, registry) ).format(cls.__name__, registry)
if not DJANGO_FILTER_INSTALLED and filter_fields: if filter_fields and filterset_class:
raise Exception("Can only set filter_fields if Django-Filter is installed") 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( django_fields = yank_fields_from_attrs(
construct_fields(model, registry, only_fields, exclude_fields), _as=Field construct_fields(model, registry, only_fields, exclude_fields), _as=Field
@ -108,6 +116,7 @@ class DjangoObjectType(ObjectType):
_meta.model = model _meta.model = model
_meta.registry = registry _meta.registry = registry
_meta.filter_fields = filter_fields _meta.filter_fields = filter_fields
_meta.filterset_class = filterset_class
_meta.fields = django_fields _meta.fields = django_fields
_meta.connection = connection _meta.connection = connection