1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
|
.. _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 <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(),
}
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.
|