9.5 KiB
Filtering
Graphene integrates with django-filter to provide filtering of results. See the usage documentation 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:
# You'll need to install django-filter
pip install django-filter>=2
After installing django-filter
you'll need to add the application in the settings.py
file:
= [
INSTALLED_APPS # ...
"django_filters",
]
Note: The techniques below are demoed in the cookbook example app.
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 documentation for full details on the range of options available.
For example:
class AnimalNode(DjangoObjectType):
class Meta:
# Assume you have an Animal model defined with the following fields
= Animal
model = '__all__'
fields = ['name', 'genus', 'is_domesticated']
filter_fields = (relay.Node, )
interfaces
class Query(ObjectType):
= relay.Node.Field(AnimalNode)
animal = DjangoFilterConnectionField(AnimalNode) all_animals
You could then perform a query such as:
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:
class AnimalNode(DjangoObjectType):
class Meta:
= Animal
model = '__all__'
fields # Provide more complex lookup types
= {
filter_fields 'name': ['exact', 'icontains', 'istartswith'],
'genus': ['exact'],
'is_domesticated': ['exact'],
}= (relay.Node, ) interfaces
Which you could query as follows:
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 filter_fields
.
However, you may find this to be insufficient. In these cases you can create your own FilterSet
. You can pass it directly as follows:
class AnimalNode(DjangoObjectType):
class Meta:
# Assume you have an Animal model defined with the following fields
= Animal
model = '__all__'
fields = ['name', 'genus', 'is_domesticated']
filter_fields = (relay.Node, )
interfaces
class AnimalFilter(django_filters.FilterSet):
# Do case-insensitive lookups on 'name'
= django_filters.CharFilter(lookup_expr=['iexact'])
name # Allow multiple genera to be selected at once
= django_filters.MultipleChoiceFilter(
genera ='genus',
field_name=(
choices'Canis', 'Canis'),
('Panthera', 'Panthera'),
('Seahorse', 'Seahorse')
(
)
)
class Meta:
= Animal
model = ['name', 'genus', 'is_domesticated']
fields
class Query(ObjectType):
= relay.Node.Field(AnimalNode)
animal # We specify our custom AnimalFilter using the filterset_class param
= DjangoFilterConnectionField(AnimalNode,
all_animals =AnimalFilter) filterset_class
If you were interested in selecting all dogs and cats, you might query as follows:
query {
allAnimals(genera: ["Canis", "Panthera"]) {
edges {
node {
id,
name
}
}
}
}
You can also specify the FilterSet
class using the filterset_class
parameter when defining your DjangoObjectType
, however, this can't be used in unison with the filter_fields
parameter:
class AnimalFilter(django_filters.FilterSet):
# Do case-insensitive lookups on 'name'
= django_filters.CharFilter(lookup_expr=['iexact'])
name
class Meta:
# Assume you have an Animal model defined with the following fields
= Animal
model = ['name', 'genus', 'is_domesticated']
fields
class AnimalNode(DjangoObjectType):
class Meta:
= Animal
model = '__all__'
fields = AnimalFilter
filterset_class = (relay.Node, )
interfaces
class Query(ObjectType):
= relay.Node.Field(AnimalNode)
animal = DjangoFilterConnectionField(AnimalNode) all_animals
The context argument is passed on as the request argument 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
).
class AnimalFilter(django_filters.FilterSet):
# Do case-insensitive lookups on 'name'
= django_filters.CharFilter(lookup_type=['iexact'])
name
class Meta:
= Animal
model = ['name', 'genus', 'is_domesticated']
fields
@property
def qs(self):
# The query context can be found in self.request.
return super(AnimalFilter, self).qs.filter(owner=self.request.user)
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.
from django_filters import FilterSet, OrderingFilter
class UserFilter(FilterSet):
class Meta:
= UserModel
model
= OrderingFilter(
order_by =(
fields'name', 'created_at'),
(
)
)
class Group(DjangoObjectType):
= DjangoFilterConnectionField(Ticket, filterset_class=UserFilter)
users
class Meta:
= 'Group'
name = GroupModel
model = '__all__'
fields = (relay.Node,)
interfaces
def resolve_users(self, info, **kwargs):
return UserFilter(kwargs).qs
with this set up, you can now order the users under group:
query {
group(id: "xxx") {
users(orderBy: "-created_at") {
xxx
}
}
}
PostgreSQL ArrayField
Graphene provides an easy to implement filters on ArrayField as they are not natively supported by django_filters:
from django.db import models
from django_filters import FilterSet, OrderingFilter
from graphene_django.filter import ArrayFilter
class Event(models.Model):
= models.CharField(max_length=50)
name = ArrayField(models.CharField(max_length=50))
tags
class EventFilterSet(FilterSet):
class Meta:
= Event
model = {
fields "name": ["exact", "contains"],
}
= ArrayFilter(field_name="tags", lookup_expr="contains")
tags__contains = ArrayFilter(field_name="tags", lookup_expr="overlap")
tags__overlap = ArrayFilter(field_name="tags", lookup_expr="exact")
tags
class EventType(DjangoObjectType):
class Meta:
= Event
model = (Node,)
interfaces = "__all__"
fields = EventFilterSet filterset_class
with this set up, you can now filter events by tags:
query {
events(tags_Overlap: ["concert", "festival"]) {
name
}
}
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`:
from django.db import models
from django_filters import FilterSet, OrderingFilter
import graphene
from graphene_django.filter import TypedFilter
class Event(models.Model):
= models.CharField(max_length=50)
name
class EventFilterSet(FilterSet):
class Meta:
= Event
model = {
fields "name": ["exact", "contains"],
}
= TypedFilter(input_type=graphene.Boolean, method="only_first_filter")
only_first
def only_first_filter(self, queryset, _name, value):
if value:
return queryset[:1]
else:
return queryset
class EventType(DjangoObjectType):
class Meta:
= Event
model = (Node,)
interfaces = "__all__"
fields = EventFilterSet filterset_class