Integration with DRF¶
Integration with Django Rest Framework is provided through a DRF-specific FilterSet
and a filter backend. These may be found in the rest_framework
sub-package.
Quickstart¶
Using the new FilterSet
simply requires changing the import path. Instead of importing from django_filters
, import from the rest_framework
sub-package.
from django_filters import rest_framework as filters
class ProductFilter(filters.FilterSet):
...
Your view class will also need to add DjangoFilterBackend
to the filter_backends
.
from django_filters import rest_framework as filters
class ProductList(generics.ListAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filter_backends = (filters.DjangoFilterBackend,)
filterset_fields = ('category', 'in_stock')
If you want to use the django-filter backend by default, add it to the DEFAULT_FILTER_BACKENDS
setting.
# settings.py
INSTALLED_APPS = [
...
'rest_framework',
'django_filters',
]
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': (
'django_filters.rest_framework.DjangoFilterBackend',
...
),
}
Adding a FilterSet with filterset_class
¶
To enable filtering with a FilterSet
, add it to the filterset_class
parameter on your view class.
from rest_framework import generics
from django_filters import rest_framework as filters
from myapp import Product
class ProductFilter(filters.FilterSet):
min_price = filters.NumberFilter(field_name="price", lookup_expr='gte')
max_price = filters.NumberFilter(field_name="price", lookup_expr='lte')
class Meta:
model = Product
fields = ['category', 'in_stock']
class ProductList(generics.ListAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filter_backends = (filters.DjangoFilterBackend,)
filterset_class = ProductFilter
Using the filterset_fields
shortcut¶
You may bypass creating a FilterSet
by instead adding filterset_fields
to your view class. This is equivalent to creating a FilterSet
with just Meta.fields.
from rest_framework import generics
from django_filters import rest_framework as filters
from myapp import Product
class ProductList(generics.ListAPIView):
queryset = Product.objects.all()
filter_backends = (filters.DjangoFilterBackend,)
filterset_fields = ('category', 'in_stock')
# Equivalent FilterSet:
class ProductFilter(filters.FilterSet):
class Meta:
model = Product
fields = ('category', 'in_stock')
Note that using filterset_fields
and filterset_class
together is not
supported.
Overriding FilterSet creation¶
FilterSet
creation can be customized by overriding the following methods on the backend class:
.get_filterset(self, request, queryset, view)
.get_filterset_class(self, view, queryset=None)
.get_filterset_kwargs(self, request, queryset, view)
You can override these methods on a case-by-case basis for each view, creating unique backends, or these methods can be used to write your own hooks to the view class.
class MyFilterBackend(filters.DjangoFilterBackend):
def get_filterset_kwargs(self, request, queryset, view):
kwargs = super().get_filterset_kwargs(request, queryset, view)
# merge filterset kwargs provided by view class
if hasattr(view, 'get_filterset_kwargs'):
kwargs.update(view.get_filterset_kwargs())
return kwargs
class BookFilter(filters.FilterSet):
def __init__(self, *args, author=None, **kwargs):
super().__init__(*args, **kwargs)
# do something w/ author
class BookViewSet(viewsets.ModelViewSet):
filter_backends = [MyFilterBackend]
filterset_class = BookFilter
def get_filterset_kwargs(self):
return {
'author': self.get_author(),
}
Schema Generation with Core API and Open API¶
The backend class integrates with DRF’s schema generation by implementing get_schema_fields()
and get_schema_operation_parameters()
. get_schema_fields()
is automatically enabled when Core API is installed. get_schema_operation_parameters()
is always enabled for Open API (new since DRF 3.9). Schema generation usually functions seamlessly, however the implementation does expect to invoke the view’s get_queryset()
method. There is a caveat in that views are artificially constructed during schema generation, so the args
and kwargs
attributes will be empty. If you depend on arguments parsed from the URL, you will need to handle their absence in get_queryset()
.
For example, your get queryset method may look like this:
class IssueViewSet(views.ModelViewSet):
queryset = models.Issue.objects.all()
def get_project(self):
return models.Project.objects.get(pk=self.kwargs['project_id'])
def get_queryset(self):
project = self.get_project()
return self.queryset \
.filter(project=project) \
.filter(author=self.request.user)
This could be rewritten like so:
class IssueViewSet(views.ModelViewSet):
queryset = models.Issue.objects.all()
def get_project(self):
try:
return models.Project.objects.get(pk=self.kwargs['project_id'])
except models.Project.DoesNotExist:
return None
def get_queryset(self):
project = self.get_project()
if project is None:
return self.queryset.none()
return self.queryset \
.filter(project=project) \
.filter(author=self.request.user)
Or more simply as:
class IssueViewSet(views.ModelViewSet):
queryset = models.Issue.objects.all()
def get_queryset(self):
# project_id may be None
return self.queryset \
.filter(project_id=self.kwargs.get('project_id')) \
.filter(author=self.request.user)
Crispy Forms¶
If you are using DRF’s browsable API or admin API you may also want to install django-crispy-forms
, which will enhance the presentation of the filter forms in HTML views, by allowing them to render Bootstrap 3 HTML. Note that this isn’t actively supported, although pull requests for bug fixes are welcome.
pip install django-crispy-forms
With crispy forms installed and added to Django’s INSTALLED_APPS
, the browsable API will present a filtering control for DjangoFilterBackend
, like so:
Additional FilterSet
Features¶
The following features are specific to the rest framework FilterSet:
BooleanFilter
’s use the API-friendlyBooleanWidget
, which accepts lowercasetrue
/false
.Filter generation uses
IsoDateTimeFilter
for datetime model fields.Raised
ValidationError
’s are reraised as their DRF equivalent.