Skip to content

Commit 2cdbf6f

Browse files
author
Ryan P Kilby
committed
Add empty/null value filtering docs
1 parent f9c2838 commit 2cdbf6f

File tree

2 files changed

+120
-0
lines changed

2 files changed

+120
-0
lines changed

docs/guide/tips.txt

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,121 @@ uncategorized products and products for a set of categories:
8888
uncategorized = django_filters.BooleanFilter(name='category', lookup_expr='isnull')
8989

9090
More info on constructing ``in`` and ``range`` csv :ref:`filters <base-in-filter>`.
91+
92+
93+
Filtering by empty values
94+
-------------------------
95+
96+
There are a number of cases where you may need to filter by empty or null
97+
values. The following are some common solutions to these problems:
98+
99+
100+
Filtering by null values
101+
~~~~~~~~~~~~~~~~~~~~~~~~
102+
103+
As explained in the above "Filter and lookup expression mismatch" section, a
104+
common problem is how to correctly filter by null values on a field.
105+
106+
Solution 1: Using a ``BooleanFilter`` with ``isnull``
107+
"""""""""""""""""""""""""""""""""""""""""""""""""""""
108+
109+
Using ``BooleanFilter`` with an ``isnull`` lookup is a builtin solution used by
110+
the FilterSet's automatic filter generation. To do this manually, simply add:
111+
112+
.. code-block:: python
113+
114+
class ProductFilter(django_filters.FilterSet):
115+
uncategorized = django_filters.BooleanFilter(name='category', lookup_expr='isnull')
116+
117+
.. note::
118+
119+
Remember that the filter class is validating the input value. The underlying
120+
type of the mode field is not relevant here.
121+
122+
You may also reverse the logic with the ``exclude`` parameter.
123+
124+
.. code-block:: python
125+
126+
class ProductFilter(django_filters.FilterSet):
127+
has_category = django_filters.BooleanFilter(name='category', lookup_expr='isnull', exclude=True)
128+
129+
Solution 2: Using ``ChoiceFilter``'s null choice
130+
""""""""""""""""""""""""""""""""""""""""""""""""
131+
132+
If you're using a ChoiceFilter, you may also filter by null values by enabling
133+
the ``null_label`` parameter. More details in the ``ChoiceFilter`` reference
134+
:ref:`docs <choice-filter>`.
135+
136+
.. code-block:: python
137+
138+
class ProductFilter(django_filters.FilterSet):
139+
category = django_filters.ModelChoiceFilter(
140+
name='category', lookup_expr='isnull',
141+
null_label='Uncategorized',
142+
queryset=Category.objects.all(),
143+
)
144+
145+
146+
Filtering by an empty string
147+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
148+
149+
It's not currently possible to filter by an empty string, since empty values are
150+
interpreted as a skipped filter.
151+
152+
.. code-block:: http
153+
154+
GET http://localhost/api/my-model?myfield=
155+
156+
Solution 1: magic values
157+
""""""""""""""""""""""""
158+
159+
You can override the ``filter()`` method of a filter class to specifically check
160+
for magic values. This is similar to the ``ChoiceFilter``'s null value handling.
161+
162+
.. code-block:: http
163+
164+
GET http://localhost/api/my-model?myfield=EMPTY
165+
166+
.. code-block:: python
167+
168+
class MyCharFilter(filters.CharFilter):
169+
empty_value = 'EMPTY'
170+
171+
def filter(self, qs, value):
172+
if value != self.empty_value:
173+
return super(MyCharFilter, self).filter(qs, value)
174+
175+
qs = self.get_method(qs)(**{'%s__%s' % (self.name, self.lookup_expr): ""})
176+
return qs.distinct() if self.distinct else qs
177+
178+
179+
Solution 2: empty string filter
180+
"""""""""""""""""""""""""""""""
181+
182+
It would also be possible to create an empty value filter that exhibits the same
183+
behavior as an ``isnull`` filter.
184+
185+
.. code-block:: http
186+
187+
GET http://localhost/api/my-model?myfield__isempty=false
188+
189+
.. code-block:: python
190+
191+
from django.core.validators import EMPTY_VALUES
192+
193+
class EmptyStringFilter(filters.BooleanFilter):
194+
def filter(self, qs, value):
195+
if value in EMPTY_VALUES:
196+
return qs
197+
198+
exclude = self.exclude ^ (value is False)
199+
method = qs.exclude if exclude else qs.filter
200+
201+
return method(**{self.name: ""})
202+
203+
204+
class MyFilterSet(filters.FilterSet):
205+
myfield__isempty = EmptyStringFilter(name='myfield')
206+
207+
class Meta:
208+
model = MyModel

docs/ref/filters.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,8 @@ This filter matches UUID values, used with ``models.UUIDField`` by default.
192192
This filter matches a boolean, either ``True`` or ``False``, used with
193193
``BooleanField`` and ``NullBooleanField`` by default.
194194

195+
.. _choice-filter:
196+
195197
``ChoiceFilter``
196198
~~~~~~~~~~~~~~~~
197199

0 commit comments

Comments
 (0)