File: rest_framework.txt

package info (click to toggle)
django-filter 2.4.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 1,396 kB
  • sloc: python: 7,483; javascript: 7,213; makefile: 144
file content (231 lines) | stat: -rw-r--r-- 7,865 bytes parent folder | download
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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
====================
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', 'min_price', 'max_price']


    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(),
            }

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.