@@ -88,3 +88,121 @@ uncategorized products and products for a set of categories:
88
88
uncategorized = django_filters.BooleanFilter(name='category', lookup_expr='isnull')
89
89
90
90
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
0 commit comments