• Home
  • History
  • Annotate
Name Date Size #Lines LOC

..03-May-2022-

djangorestframework_filters.egg-info/H03-May-2022-472317

rest_framework_filters/H20-Oct-2017-469305

PKG-INFOH A D20-Oct-201719.4 KiB472317

README.rstH A D20-Oct-201715.1 KiB452298

setup.cfgH A D20-Oct-201767 85

setup.pyH A D20-Oct-20171.8 KiB6552

README.rst

1Django Rest Framework Filters
2=============================
3
4.. image:: https://travis-ci.org/philipn/django-rest-framework-filters.png?branch=master
5  :target: https://travis-ci.org/philipn/django-rest-framework-filters
6
7.. image:: https://codecov.io/gh/philipn/django-rest-framework-filters/branch/master/graph/badge.svg
8  :target: https://codecov.io/gh/philipn/django-rest-framework-filters
9
10.. image:: https://img.shields.io/pypi/v/djangorestframework-filters.svg
11  :target: https://pypi.python.org/pypi/djangorestframework-filters
12
13
14``django-rest-framework-filters`` is an extension to `Django REST framework`_ and `Django filter`_
15that makes it easy to filter across relationships. Historically, this extension also provided a
16number of additional features and fixes, however the number of features has shrunk as they are
17merged back into ``django-filter``.
18
19.. _`Django REST framework`: https://github.com/tomchristie/django-rest-framework
20.. _`Django filter`: https://github.com/carltongibson/django-filter
21
22Using ``django-rest-framework-filters``, we can easily do stuff like::
23
24    /api/article?author__first_name__icontains=john
25    /api/article?is_published!=true
26
27.. contents::
28    **Table of Contents**
29    :local:
30    :depth: 2
31    :backlinks: none
32
33Features
34--------
35
36* Easy filtering across relationships
37* Support for method filtering across relationships
38* Automatic filter negation with a simple ``param!=value`` syntax
39* Backend caching to increase performance
40
41
42Requirements
43------------
44
45* **Python**: 2.7 or 3.3+
46* **Django**: 1.8, 1.9, 1.10, 1.11
47* **DRF**: 3.5, 3.6
48
49
50Installation
51------------
52
53.. code-block:: bash
54
55    $ pip install djangorestframework-filters
56
57
58Usage
59-----
60
61Upgrading from ``django-filter`` to ``django-rest-framework-filters`` is straightforward:
62
63* Import from ``rest_framework_filters`` instead of from ``django_filters``
64* Use the ``rest_framework_filters`` backend instead of the one provided by ``django_filter``.
65
66.. code-block:: python
67
68    # django-filter
69    from django_filters.rest_framework import FilterSet, filters
70
71    class ProductFilter(FilterSet):
72        manufacturer = filters.ModelChoiceFilter(queryset=Manufacturer.objects.all())
73        ...
74
75
76    # django-rest-framework-filters
77    import rest_framework_filters as filters
78
79    class ProductFilter(filters.FilterSet):
80        manufacturer = filters.ModelChoiceFilter(queryset=Manufacturer.objects.all())
81        ...
82
83
84To use the django-rest-framework-filters backend, add the following to your settings:
85
86.. code-block:: python
87
88    REST_FRAMEWORK = {
89        'DEFAULT_FILTER_BACKENDS': (
90            'rest_framework_filters.backends.DjangoFilterBackend', ...
91        ),
92        ...
93
94
95Once configured, you can continue to use all of the filters found in ``django-filter``.
96
97
98Filtering across relationships
99~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
100
101You can easily traverse multiple relationships when filtering by using ``RelatedFilter``:
102
103.. code-block:: python
104
105    from rest_framework import viewsets
106    import rest_framework_filters as filters
107
108
109    class ManagerFilter(filters.FilterSet):
110        class Meta:
111            model = Manager
112            fields = {'name': ['exact', 'in', 'startswith']}
113
114
115    class DepartmentFilter(filters.FilterSet):
116        manager = filters.RelatedFilter(ManagerFilter, name='manager', queryset=Manager.objects.all())
117
118        class Meta:
119            model = Department
120            fields = {'name': ['exact', 'in', 'startswith']}
121
122
123    class CompanyFilter(filters.FilterSet):
124        department = filters.RelatedFilter(DepartmentFilter, name='department', queryset=Department.objects.all())
125
126        class Meta:
127            model = Company
128            fields = {'name': ['exact', 'in', 'startswith']}
129
130
131    # company viewset
132    class CompanyView(viewsets.ModelViewSet):
133        filter_class = CompanyFilter
134        ...
135
136Example filter calls:
137
138.. code-block::
139
140    /api/companies?department__name=Accounting
141    /api/companies?department__manager__name__startswith=Bob
142
143``queryset`` callables
144""""""""""""""""""""""
145
146Since ``RelatedFilter`` is a subclass of ``ModelChoiceFilter``, the ``queryset`` argument supports callable behavior.
147In the following example, the set of departments is restricted to those in the user's company.
148
149.. code-block:: python
150
151    def departments(request):
152        company = request.user.company
153        return company.department_set.all()
154
155    class EmployeeFilter(filters.FilterSet):
156        department = filters.RelatedFilter(filterset=DepartmentFilter, queryset=departments)
157        ...
158
159Recursive relationships
160"""""""""""""""""""""""
161
162Recursive relations are also supported. It may be necessary to specify the full module path.
163
164.. code-block:: python
165
166    class PersonFilter(filters.FilterSet):
167        name = filters.AllLookupsFilter(name='name')
168        best_friend = filters.RelatedFilter('people.views.PersonFilter', name='best_friend', queryset=Person.objects.all())
169
170        class Meta:
171            model = Person
172
173Supporting ``Filter.method``
174~~~~~~~~~~~~~~~~~~~~~~~~~~~~
175
176``django_filters.MethodFilter`` has been deprecated and reimplemented as the ``method`` argument
177to all filter classes. It incorporates some of the implementation details of the old
178``rest_framework_filters.MethodFilter``, but requires less boilerplate and is simpler to write.
179
180* It is no longer necessary to perform empty/null value checking.
181* You may use any filter class (``CharFilter``, ``BooleanFilter``, etc...) which will
182  validate input values for you.
183* The argument signature has changed from ``(name, qs, value)`` to ``(qs, name, value)``.
184
185.. code-block:: python
186
187    class PostFilter(filters.FilterSet):
188        # Note the use of BooleanFilter, the original model field's name, and the method argument.
189        is_published = filters.BooleanFilter(name='date_published', method='filter_is_published')
190
191        class Meta:
192            model = Post
193            fields = ['title', 'content']
194
195        def filter_is_published(self, qs, name, value):
196            """
197            `is_published` is based on the `date_published` model field.
198            If the publishing date is null, then the post is not published.
199            """
200            # incoming value is normalized as a boolean by BooleanFilter
201            isnull = not value
202            lookup_expr = LOOKUP_SEP.join([name, 'isnull'])
203
204            return qs.filter(**{lookup_expr: isnull})
205
206    class AuthorFilter(filters.FilterSet):
207        posts = filters.RelatedFilter('PostFilter', queryset=Post.objects.all())
208
209        class Meta:
210            model = Author
211            fields = ['name']
212
213The above would enable the following filter calls:
214
215.. code-block::
216
217    /api/posts?is_published=true
218    /api/authors?posts__is_published=true
219
220
221In the first API call, the filter method receives a queryset of posts. In the second,
222it receives a queryset of users. The filter method in the example modifies the lookup
223name to work across the relationship, allowing you to find published posts, or authors
224who have published posts.
225
226Automatic Filter Negation/Exclusion
227~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
228
229FilterSets support automatic exclusion using a simple ``param!=value`` syntax. This syntax
230internally sets the ``exclude`` property on the filter.
231
232.. code-block::
233
234    /api/page?title!=The%20Park
235
236This syntax supports regular filtering combined with exclusion filtering. For example, the
237following would search for all articles containing "Hello" in the title, while excluding
238those containing "World".
239
240.. code-block::
241
242    /api/articles?title__contains=Hello&title__contains!=World
243
244Note that most filters only accept a single query parameter. In the above, ``title__contains``
245and ``title__contains!`` are interpreted as two separate query parameters. The following would
246probably be invalid, although it depends on the specifics of the individual filter class:
247
248.. code-block::
249
250    /api/articles?title__contains=Hello&title__contains!=World&title_contains!=Friend
251
252
253Allowing any lookup type on a field
254~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
255
256If you need to enable several lookups for a field, django-filter provides the dict-syntax for
257``Meta.fields``.
258
259.. code-block:: python
260
261    class ProductFilter(filters.FilterSet):
262        class Meta:
263            model = Product
264            fields = {
265                'price': ['exact', 'lt', 'gt', ...],
266            }
267
268``django-rest-framework-filters`` also allows you to enable all possible lookups for any field.
269This can be achieved through the use of ``AllLookupsFilter`` or using the ``'__all__'`` value in
270the ``Meta.fields`` dict-style syntax. Generated filters (``Meta.fields``, ``AllLookupsFilter``)
271will never override your declared filters.
272
273Note that using all lookups comes with the same admonitions as enabling ``'__all__'`` fields in
274django forms (`docs`_). Exposing all lookups may allow users to construct queries that
275inadvertently leak data. Use this feature responsibly.
276
277.. _`docs`: https://docs.djangoproject.com/en/1.10/topics/forms/modelforms/#selecting-the-fields-to-use
278
279.. code-block:: python
280
281    class ProductFilter(filters.FilterSet):
282        # Not overridden by `__all__`
283        price__gt = filters.NumberFilter(name='price', lookup_expr='gt', label='Minimum price')
284
285        class Meta:
286            model = Product
287            fields = {
288                'price': '__all__',
289            }
290
291    # or
292
293    class ProductFilter(filters.FilterSet):
294        price = filters.AllLookupsFilter()
295
296        # Not overridden by `AllLookupsFilter`
297        price__gt = filters.NumberFilter(name='price', lookup_expr='gt', label='Minimum price')
298
299        class Meta:
300            model = Product
301
302You cannot combine ``AllLookupsFilter`` with ``RelatedFilter`` as the filter names would clash.
303
304.. code-block:: python
305
306    class ProductFilter(filters.FilterSet):
307        manufacturer = filters.RelatedFilter('ManufacturerFilter', queryset=Manufacturer.objects.all())
308        manufacturer = filters.AllLookupsFilter()
309
310To work around this, you have the following options:
311
312.. code-block:: python
313
314    class ProductFilter(filters.FilterSet):
315        manufacturer = filters.RelatedFilter('ManufacturerFilter', queryset=Manufacturer.objects.all())
316
317        class Meta:
318            model = Product
319            fields = {
320                'manufacturer': '__all__',
321            }
322
323    # or
324
325    class ProductFilter(filters.FilterSet):
326        manufacturer = filters.RelatedFilter('ManufacturerFilter', queryset=Manufacturer.objects.all(), lookups='__all__')  # `lookups` also accepts a list
327
328        class Meta:
329            model = Product
330
331
332Can I mix and match ``django-filter`` and ``django-rest-framework-filters``?
333~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
334
335Yes you can. ``django-rest-framework-filters`` is simply an extension of ``django-filter``. Note
336that ``RelatedFilter`` and other ``django-rest-framework-filters`` features are designed to work
337with ``rest_framework_filters.FilterSet`` and will not function on a ``django_filters.FilterSet``.
338However, the target ``RelatedFilter.filterset`` may point to a ``FilterSet`` from either package,
339and both ``FilterSet`` implementations are compatible with the other's DRF backend.
340
341.. code-block:: python
342
343    # valid
344    class VanillaFilter(django_filters.FilterSet):
345        ...
346
347    class DRFFilter(rest_framework_filters.FilterSet):
348        vanilla = rest_framework_filters.RelatedFilter(filterset=VanillaFilter, queryset=...)
349
350
351    # invalid
352    class DRFFilter(rest_framework_filters.FilterSet):
353        ...
354
355    class VanillaFilter(django_filters.FilterSet):
356        drf = rest_framework_filters.RelatedFilter(filterset=DRFFilter, queryset=...)
357
358
359Caveats & Limitations
360~~~~~~~~~~~~~~~~~~~~~
361
362``MultiWidget`` is incompatible
363"""""""""""""""""""""""""""""""
364
365djangorestframework-filters is not compatible with form widgets that parse query names that differ from the filter's
366attribute name. Although this only practically applies to ``MultiWidget``, it is a general limitation that affects
367custom widgets that also have this behavior. Affected filters include ``RangeFilter``, ``DateTimeFromToRangeFilter``,
368``DateFromToRangeFilter``, ``TimeRangeFilter``, and ``NumericRangeFilter``.
369
370To demonstrate the incompatiblity, take the following filterset:
371
372.. code-block:: python
373
374    class PostFilter(FilterSet):
375        publish_date = filters.DateFromToRangeFilter()
376
377The above filter allows users to perform a ``range`` query on the publication date. The filter class internally uses
378``MultiWidget`` to separately parse the upper and lower bound values. The incompatibility lies in that ``MultiWidget``
379appends an index to its inner widget names. Instead of parsing ``publish_date``, it expects ``publish_date_0`` and
380``publish_date_1``. It is possible to fix this by including the attribute name in the querystring, although this is
381not recommended.
382
383.. code-block::
384
385    ?publish_date_0=2016-01-01&publish_date_1=2016-02-01&publish_date=
386
387``MultiWidget`` is also discouraged since:
388
389* ``core-api`` field introspection fails for similar reasons
390* ``_0`` and ``_1`` are less API-friendly than ``_min`` and ``_max``
391
392The recommended solutions are to either:
393
394* Create separate filters for each of the sub-widgets (such as ``publish_date_min`` and ``publish_date_max``).
395* Use a CSV-based filter such as those derived from ``BaseCSVFilter``/``BaseInFilter``/``BaseRangeFilter``. eg,
396
397.. code-block::
398
399    ?publish_date__range=2016-01-01,2016-02-01
400
401
402Migrating to 1.0
403----------------
404
405``RelatedFilter.queryset`` now required
406~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
407
408The related filterset's model is no longer used to provide the default value for ``RelatedFilter.queryset``. This
409change reduces the chance of unintentionally exposing data in the rendered filter forms. You must now explicitly
410provide the ``queryset`` argument, or override the ``get_queryset()`` method (see `queryset callables`_).
411
412
413``get_filters()`` renamed to ``expand_filters()``
414~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
415
416django-filter has add a ``get_filters()`` classmethod to it's API, so this method has been renamed.
417
418
419Publishing
420----------
421
422.. code-block:: bash
423
424    $ pip install -U twine setuptools wheel
425    $ rm -rf dist/ build/
426    $ python setup.py sdist bdist_wheel
427    $ twine upload dist/*
428
429
430License
431-------
432Copyright (c) 2013-2015 Philip Neustrom <philipn@gmail.com>,
4332016-2017 Ryan P Kilby <rpkilby@ncsu.edu>
434
435Permission is hereby granted, free of charge, to any person obtaining a copy
436of this software and associated documentation files (the "Software"), to deal
437in the Software without restriction, including without limitation the rights
438to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
439copies of the Software, and to permit persons to whom the Software is
440furnished to do so, subject to the following conditions:
441
442The above copyright notice and this permission notice shall be included in
443all copies or substantial portions of the Software.
444
445THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
446IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
447FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
448AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
449LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
450OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
451THE SOFTWARE.
452