Skip to content

Commit ca6362c

Browse files
author
Ryan P Kilby
authored
Merge pull request #145 from rpkilby/multiwidget-caveat
Document MultiWidget incompatibility
2 parents 062c092 + 6d473f3 commit ca6362c

File tree

3 files changed

+94
-3
lines changed

3 files changed

+94
-3
lines changed

README.rst

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ Using ``django-rest-framework-filters``, we can easily do stuff like::
2424
/api/article?author__first_name__icontains=john
2525
/api/article?is_published!=true
2626

27+
.. contents::
28+
**Table of Contents**
29+
:local:
30+
:depth: 2
2731

2832
Features
2933
--------
@@ -305,8 +309,8 @@ To work around this, you have the following options:
305309
model = Product
306310
307311
308-
Can I mix and match `django-filter` and `django-rest-framework-filters`?
309-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
312+
Can I mix and match ``django-filter`` and ``django-rest-framework-filters``?
313+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
310314
311315
Yes you can. ``django-rest-framework-filters`` is simply an extension of ``django-filter``. Note
312316
that ``RelatedFilter`` and other ``django-rest-framework-filters`` features are designed to work
@@ -332,6 +336,49 @@ and ``FilterSet``s from either package are compatible with the other's DRF backe
332336
drf = rest_framework_filters.RelatedFilter(filterset=DRFFilter)
333337
334338
339+
Caveats & Limitations
340+
~~~~~~~~~~~~~~~~~~~~~
341+
342+
``MultiWidget`` is incompatible
343+
"""""""""""""""""""""""""""""""
344+
345+
djangorestframework-filters is not compatible with form widgets that parse query names that differ from the filter's
346+
attribute name. Although this only practically applies to ``MultiWidget``, it is a general limitation that affects
347+
custom widgets that also have this behavior. Affected filters include ``RangeFilter``, ``DateTimeFromToRangeFilter``,
348+
``DateFromToRangeFilter``, ``TimeRangeFilter``, and ``NumericRangeFilter``.
349+
350+
To demonstrate the incompatiblity, take the following filterset:
351+
352+
.. code-block:: python
353+
354+
class PostFilter(FilterSet):
355+
publish_date = filters.DateFromToRangeFilter()
356+
357+
The above filter allows users to perform a ``range`` query on the publication date. The filter class internally uses
358+
``MultiWidget`` to separately parse the upper and lower bound values. The incompatibility lies in that ``MultiWidget``
359+
appends an index to its inner widget names. Instead of parsing ``publish_date``, it expects ``publish_date_0`` and
360+
``publish_date_1``. It is possible to fix this by including the attribute name in the querystring, although this is
361+
not recommended.
362+
363+
.. code-block::
364+
365+
?publish_date_0=2016-01-01&publish_date_1=2016-02-01&publish_date=
366+
367+
``MultiWidget`` is also discouraged since:
368+
369+
* ``core-api`` field introspection fails for similar reasons
370+
* ``_0`` and ``_1`` are less API-friendly than ``_min`` and ``_max``
371+
372+
The recommended solutions are to either:
373+
374+
* Create separate filters for each of the sub-widgets (such as ``publish_date_min`` and ``publish_date_max``).
375+
* Use a CSV-based filter such as those derived from ``BaseCSVFilter``/``BaseInFilter``/``BaseRangeFilter``. eg,
376+
377+
.. code-block::
378+
379+
?publish_date__range=2016-01-01,2016-02-01
380+
381+
335382
License
336383
-------
337384
Copyright (c) 2013-2015 Philip Neustrom <philipn@gmail.com>,

tests/test_filtering.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from rest_framework_filters.compat import set_many
88
from rest_framework_filters import FilterSet, filters
9+
from django_filters import FilterSet as DFFilterSet
910

1011
from .testapp.models import (
1112
User, Note, Post, Cover, A, B, C, Person, Tag, BlogPost,
@@ -324,3 +325,46 @@ def test_related_filters_caching(self):
324325
self.assertIn('_related_filters', PostFilter.__dict__)
325326

326327
self.assertEqual(len(filters), 1)
328+
329+
330+
class MiscTests(TestCase):
331+
def test_multiwidget_incompatibility(self):
332+
Person.objects.create(name='A')
333+
334+
# test django-filter functionality
335+
class PersonFilter(DFFilterSet):
336+
date_joined = filters.DateFromToRangeFilter(name='date_joined')
337+
338+
class Meta:
339+
model = Person
340+
fields = ['date_joined']
341+
342+
# Test from ... to 2016-01-01
343+
GET = {
344+
'date_joined_1': '2016-01-01',
345+
}
346+
f = PersonFilter(GET, queryset=Person.objects.all())
347+
self.assertEqual(f.qs.count(), 0)
348+
349+
# test drf-filters caveat
350+
class PersonFilter(FilterSet):
351+
date_joined = filters.DateFromToRangeFilter(name='date_joined')
352+
353+
class Meta:
354+
model = Person
355+
fields = ['date_joined']
356+
357+
# Test from ... to 2016-01-01, failure case
358+
GET = {
359+
'date_joined_1': '2016-01-01',
360+
}
361+
f = PersonFilter(GET, queryset=Person.objects.all())
362+
self.assertEqual(f.qs.count(), 1)
363+
364+
# Test from ... to 2016-01-01, "fix"
365+
GET = {
366+
'date_joined_1': '2016-01-01',
367+
'date_joined': '',
368+
}
369+
f = PersonFilter(GET, queryset=Person.objects.all())
370+
self.assertEqual(f.qs.count(), 0)

tests/test_performance.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class PerformanceTests(TestCase):
2121
def setUpTestData(cls):
2222
cls.client = Client()
2323
cls.iterations = range(1000)
24-
cls.threshold = 1.2
24+
cls.threshold = 1.3
2525

2626
def test_sanity(self):
2727
# sanity check to ensure our request are behaving as expected

0 commit comments

Comments
 (0)