.. _drf integration: ==================== 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. __ http://www.django-rest-framework.org/ __ http://www.django-rest-framework.org/api-guide/filtering/ Quickstart ---------- Using the new ``FilterSet`` simply requires changing the import path. Instead of importing from ``django_filters``, import from the ``rest_framework`` sub-package. .. code-block:: python 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``. .. code-block:: python 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. .. code-block:: python # 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. .. code-block:: python 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 :ref:`Meta.fields `. .. code-block:: python 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. .. code-block:: python 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: .. code-block:: python 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: .. code-block:: python 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: .. code-block:: python 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. .. code-block:: bash 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: .. image:: ../assets/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.