2016-09-21 08:30:36 +03:00
|
|
|
Filtering
|
|
|
|
=========
|
|
|
|
|
|
|
|
Graphene integrates with
|
2023-04-21 21:40:51 +03:00
|
|
|
`django-filter <https://django-filter.readthedocs.io/en/stable/>`__ to provide filtering of results.
|
2022-02-12 17:31:45 +03:00
|
|
|
See the `usage documentation <https://django-filter.readthedocs.io/en/main/guide/usage.html#the-filter>`__
|
2016-09-21 08:30:36 +03:00
|
|
|
for details on the format for ``filter_fields``.
|
|
|
|
|
|
|
|
This filtering is automatically available when implementing a ``relay.Node``.
|
|
|
|
Additionally ``django-filter`` is an optional dependency of Graphene.
|
|
|
|
|
|
|
|
You will need to install it manually, which can be done as follows:
|
|
|
|
|
|
|
|
.. code:: bash
|
|
|
|
|
2020-04-12 14:57:11 +03:00
|
|
|
# You'll need to install django-filter
|
2018-08-05 17:26:14 +03:00
|
|
|
pip install django-filter>=2
|
2021-03-31 20:31:45 +03:00
|
|
|
|
2020-04-12 14:57:11 +03:00
|
|
|
After installing ``django-filter`` you'll need to add the application in the ``settings.py`` file:
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
INSTALLED_APPS = [
|
|
|
|
# ...
|
|
|
|
"django_filters",
|
|
|
|
]
|
2016-09-21 08:30:36 +03:00
|
|
|
|
|
|
|
Note: The techniques below are demoed in the `cookbook example
|
2022-02-13 08:50:53 +03:00
|
|
|
app <https://github.com/graphql-python/graphene-django/tree/main/examples/cookbook>`__.
|
2016-09-21 08:30:36 +03:00
|
|
|
|
|
|
|
Filterable fields
|
|
|
|
-----------------
|
|
|
|
|
|
|
|
The ``filter_fields`` parameter is used to specify the fields which can
|
|
|
|
be filtered upon. The value specified here is passed directly to
|
|
|
|
``django-filter``, so see the `filtering
|
2022-02-12 17:31:45 +03:00
|
|
|
documentation <https://django-filter.readthedocs.io/en/main/guide/usage.html#the-filter>`__
|
2016-09-21 08:30:36 +03:00
|
|
|
for full details on the range of options available.
|
|
|
|
|
|
|
|
For example:
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
class AnimalNode(DjangoObjectType):
|
|
|
|
class Meta:
|
|
|
|
# Assume you have an Animal model defined with the following fields
|
|
|
|
model = Animal
|
2020-06-11 13:09:52 +03:00
|
|
|
fields = '__all__'
|
2016-09-21 08:30:36 +03:00
|
|
|
filter_fields = ['name', 'genus', 'is_domesticated']
|
|
|
|
interfaces = (relay.Node, )
|
|
|
|
|
|
|
|
class Query(ObjectType):
|
|
|
|
animal = relay.Node.Field(AnimalNode)
|
|
|
|
all_animals = DjangoFilterConnectionField(AnimalNode)
|
|
|
|
|
|
|
|
You could then perform a query such as:
|
|
|
|
|
2016-09-26 11:13:31 +03:00
|
|
|
.. code::
|
2016-09-21 08:30:36 +03:00
|
|
|
|
|
|
|
query {
|
|
|
|
# Note that fields names become camelcased
|
|
|
|
allAnimals(genus: "cat", isDomesticated: true) {
|
|
|
|
edges {
|
|
|
|
node {
|
|
|
|
id,
|
|
|
|
name
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
You can also make more complex lookup types available:
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
class AnimalNode(DjangoObjectType):
|
|
|
|
class Meta:
|
|
|
|
model = Animal
|
2020-06-11 13:09:52 +03:00
|
|
|
fields = '__all__'
|
2016-09-21 08:30:36 +03:00
|
|
|
# Provide more complex lookup types
|
|
|
|
filter_fields = {
|
|
|
|
'name': ['exact', 'icontains', 'istartswith'],
|
|
|
|
'genus': ['exact'],
|
|
|
|
'is_domesticated': ['exact'],
|
|
|
|
}
|
|
|
|
interfaces = (relay.Node, )
|
|
|
|
|
|
|
|
Which you could query as follows:
|
|
|
|
|
2016-09-26 11:13:31 +03:00
|
|
|
.. code::
|
2016-09-21 08:30:36 +03:00
|
|
|
|
|
|
|
query {
|
|
|
|
# Note that fields names become camelcased
|
|
|
|
allAnimals(name_Icontains: "lion") {
|
|
|
|
edges {
|
|
|
|
node {
|
|
|
|
id,
|
|
|
|
name
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Custom Filtersets
|
|
|
|
-----------------
|
|
|
|
|
|
|
|
By default Graphene provides easy access to the most commonly used
|
|
|
|
features of ``django-filter``. This is done by transparently creating a
|
|
|
|
``django_filters.FilterSet`` class for you and passing in the values for
|
2016-11-23 13:32:52 +03:00
|
|
|
``filter_fields``.
|
2016-09-21 08:30:36 +03:00
|
|
|
|
|
|
|
However, you may find this to be insufficient. In these cases you can
|
2019-03-25 06:42:06 +03:00
|
|
|
create your own ``FilterSet``. You can pass it directly as follows:
|
2016-09-21 08:30:36 +03:00
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
class AnimalNode(DjangoObjectType):
|
|
|
|
class Meta:
|
|
|
|
# Assume you have an Animal model defined with the following fields
|
|
|
|
model = Animal
|
2020-06-11 13:09:52 +03:00
|
|
|
fields = '__all__'
|
2016-09-21 08:30:36 +03:00
|
|
|
filter_fields = ['name', 'genus', 'is_domesticated']
|
|
|
|
interfaces = (relay.Node, )
|
|
|
|
|
|
|
|
|
|
|
|
class AnimalFilter(django_filters.FilterSet):
|
|
|
|
# Do case-insensitive lookups on 'name'
|
2017-08-13 05:24:50 +03:00
|
|
|
name = django_filters.CharFilter(lookup_expr=['iexact'])
|
2020-04-25 16:22:09 +03:00
|
|
|
# Allow multiple genera to be selected at once
|
|
|
|
genera = django_filters.MultipleChoiceFilter(
|
|
|
|
field_name='genus',
|
|
|
|
choices=(
|
|
|
|
('Canis', 'Canis'),
|
|
|
|
('Panthera', 'Panthera'),
|
|
|
|
('Seahorse', 'Seahorse')
|
|
|
|
)
|
|
|
|
)
|
2016-09-21 08:30:36 +03:00
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = Animal
|
|
|
|
fields = ['name', 'genus', 'is_domesticated']
|
|
|
|
|
|
|
|
|
|
|
|
class Query(ObjectType):
|
|
|
|
animal = relay.Node.Field(AnimalNode)
|
|
|
|
# We specify our custom AnimalFilter using the filterset_class param
|
|
|
|
all_animals = DjangoFilterConnectionField(AnimalNode,
|
|
|
|
filterset_class=AnimalFilter)
|
2017-07-28 17:46:39 +03:00
|
|
|
|
2020-04-25 16:22:09 +03:00
|
|
|
|
|
|
|
If you were interested in selecting all dogs and cats, you might query as follows:
|
|
|
|
|
|
|
|
.. code::
|
|
|
|
|
|
|
|
query {
|
|
|
|
allAnimals(genera: ["Canis", "Panthera"]) {
|
|
|
|
edges {
|
|
|
|
node {
|
|
|
|
id,
|
|
|
|
name
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-17 19:13:47 +03:00
|
|
|
You can also specify the ``FilterSet`` class using the ``filterset_class``
|
2019-03-25 06:42:06 +03:00
|
|
|
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
|
2020-06-11 13:09:52 +03:00
|
|
|
fields = '__all__'
|
2019-03-25 06:42:06 +03:00
|
|
|
filterset_class = AnimalFilter
|
|
|
|
interfaces = (relay.Node, )
|
|
|
|
|
|
|
|
|
|
|
|
class Query(ObjectType):
|
|
|
|
animal = relay.Node.Field(AnimalNode)
|
|
|
|
all_animals = DjangoFilterConnectionField(AnimalNode)
|
|
|
|
|
2020-04-25 16:22:09 +03:00
|
|
|
|
2022-02-12 17:31:45 +03:00
|
|
|
The context argument is passed on as the `request argument <http://django-filter.readthedocs.io/en/main/guide/usage.html#request-based-filtering>`__
|
2017-07-28 17:46:39 +03:00
|
|
|
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
|
|
|
|
pre-filter animals owned by the authenticated user (set in ``context.user``).
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
class AnimalFilter(django_filters.FilterSet):
|
|
|
|
# Do case-insensitive lookups on 'name'
|
2019-04-26 18:48:37 +03:00
|
|
|
name = django_filters.CharFilter(lookup_type=['iexact'])
|
2017-07-28 17:46:39 +03:00
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = Animal
|
|
|
|
fields = ['name', 'genus', 'is_domesticated']
|
|
|
|
|
|
|
|
@property
|
|
|
|
def qs(self):
|
|
|
|
# The query context can be found in self.request.
|
2018-02-27 13:29:34 +03:00
|
|
|
return super(AnimalFilter, self).qs.filter(owner=self.request.user)
|
2019-04-26 18:48:37 +03:00
|
|
|
|
|
|
|
|
|
|
|
Ordering
|
|
|
|
--------
|
|
|
|
|
|
|
|
You can use ``OrderFilter`` to define how you want your returned results to be ordered.
|
|
|
|
|
|
|
|
Extend the tuple of fields if you want to order by more than one field.
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
from django_filters import FilterSet, OrderingFilter
|
|
|
|
|
|
|
|
class UserFilter(FilterSet):
|
|
|
|
class Meta:
|
|
|
|
model = UserModel
|
|
|
|
|
|
|
|
order_by = OrderingFilter(
|
|
|
|
fields=(
|
2020-12-31 02:37:57 +03:00
|
|
|
('name', 'created_at'),
|
2019-04-26 18:48:37 +03:00
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
class Group(DjangoObjectType):
|
|
|
|
users = DjangoFilterConnectionField(Ticket, filterset_class=UserFilter)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
name = 'Group'
|
|
|
|
model = GroupModel
|
2020-06-11 13:09:52 +03:00
|
|
|
fields = '__all__'
|
2019-04-26 18:48:37 +03:00
|
|
|
interfaces = (relay.Node,)
|
|
|
|
|
|
|
|
def resolve_users(self, info, **kwargs):
|
|
|
|
return UserFilter(kwargs).qs
|
|
|
|
|
|
|
|
|
|
|
|
with this set up, you can now order the users under group:
|
|
|
|
|
|
|
|
.. code::
|
|
|
|
|
|
|
|
query {
|
|
|
|
group(id: "xxx") {
|
|
|
|
users(orderBy: "-created_at") {
|
|
|
|
xxx
|
|
|
|
}
|
|
|
|
}
|
2019-09-17 19:13:47 +03:00
|
|
|
}
|
2021-02-23 07:21:32 +03:00
|
|
|
|
|
|
|
|
|
|
|
PostgreSQL `ArrayField`
|
|
|
|
-----------------------
|
|
|
|
|
|
|
|
Graphene provides an easy to implement filters on `ArrayField` as they are not natively supported by django_filters:
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
from django.db import models
|
|
|
|
from django_filters import FilterSet, OrderingFilter
|
|
|
|
from graphene_django.filter import ArrayFilter
|
|
|
|
|
|
|
|
class Event(models.Model):
|
|
|
|
name = models.CharField(max_length=50)
|
|
|
|
tags = ArrayField(models.CharField(max_length=50))
|
|
|
|
|
|
|
|
class EventFilterSet(FilterSet):
|
|
|
|
class Meta:
|
|
|
|
model = Event
|
|
|
|
fields = {
|
|
|
|
"name": ["exact", "contains"],
|
|
|
|
}
|
|
|
|
|
|
|
|
tags__contains = ArrayFilter(field_name="tags", lookup_expr="contains")
|
|
|
|
tags__overlap = ArrayFilter(field_name="tags", lookup_expr="overlap")
|
|
|
|
tags = ArrayFilter(field_name="tags", lookup_expr="exact")
|
|
|
|
|
|
|
|
class EventType(DjangoObjectType):
|
|
|
|
class Meta:
|
|
|
|
model = Event
|
|
|
|
interfaces = (Node,)
|
2021-03-31 20:31:45 +03:00
|
|
|
fields = "__all__"
|
2021-02-23 07:21:32 +03:00
|
|
|
filterset_class = EventFilterSet
|
|
|
|
|
|
|
|
with this set up, you can now filter events by tags:
|
|
|
|
|
|
|
|
.. code::
|
|
|
|
|
|
|
|
query {
|
|
|
|
events(tags_Overlap: ["concert", "festival"]) {
|
|
|
|
name
|
|
|
|
}
|
|
|
|
}
|
2021-03-31 20:31:45 +03:00
|
|
|
|
|
|
|
|
|
|
|
`TypedFilter`
|
|
|
|
-------------
|
|
|
|
|
|
|
|
Sometimes the automatic detection of the filter input type is not satisfactory for what you are trying to achieve.
|
|
|
|
You can then explicitly specify the input type you want for your filter by using a `TypedFilter`:
|
|
|
|
|
|
|
|
.. code:: python
|
|
|
|
|
|
|
|
from django.db import models
|
|
|
|
from django_filters import FilterSet, OrderingFilter
|
|
|
|
import graphene
|
|
|
|
from graphene_django.filter import TypedFilter
|
|
|
|
|
|
|
|
class Event(models.Model):
|
|
|
|
name = models.CharField(max_length=50)
|
|
|
|
|
|
|
|
class EventFilterSet(FilterSet):
|
|
|
|
class Meta:
|
|
|
|
model = Event
|
|
|
|
fields = {
|
|
|
|
"name": ["exact", "contains"],
|
|
|
|
}
|
|
|
|
|
|
|
|
only_first = TypedFilter(input_type=graphene.Boolean, method="only_first_filter")
|
|
|
|
|
|
|
|
def only_first_filter(self, queryset, _name, value):
|
|
|
|
if value:
|
|
|
|
return queryset[:1]
|
|
|
|
else:
|
|
|
|
return queryset
|
|
|
|
|
|
|
|
class EventType(DjangoObjectType):
|
|
|
|
class Meta:
|
|
|
|
model = Event
|
|
|
|
interfaces = (Node,)
|
|
|
|
fields = "__all__"
|
|
|
|
filterset_class = EventFilterSet
|