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:

../_images/form.png

Additional FilterSet Features

The following features are specific to the rest framework FilterSet:

  • BooleanFilter’s use the API-friendly BooleanWidget, which accepts lowercase true/false.

  • Filter generation uses IsoDateTimeFilter for datetime model fields.

  • Raised ValidationError’s are reraised as their DRF equivalent.