Django has an interesting default behaviour for NullBooleanFields, which are used by django_filters BooleanFilter. While the String 'True' evaluates to Python Boolean True, and the String 'False' evaluates to Python Boolean False, this is not happening for the lowercase variants 'true' and 'false'. This is kind of annoying when you are using DJango Rest Filters, where you would have a REST API call like this (e.g., when calling from JavaScript):
GET /tasks/?show_only_my_tasks=true
This does not work as expected, as "show_only_my_tasks=true" evaluates to "None".
The correct usage according to Djangos NullBooleanField would have been this:
GET /tasks/?show_only_my_tasks=True
To overcome this issue, you can use the following code snippet:
class BetterBooleanSelect(NullBooleanSelect): """ Djangos NullBooleanSelect does not evaluate 'true' to True, and not 'false' to False This overwritten NullBooleanSelect allows that See https://code.djangoproject.com/ticket/22406#comment:3 """ def value_from_datadict(self, data, files, name): value = data.get(name) return { '2': True, True: True, 'true': True, # added, as NullBooleanSelect does not do that 'True': True, '3': False, 'false': False, # added, as NullBooleanSelect does not do that 'False': False, False: False, }.get(value) class BetterBooleanField(forms.NullBooleanField): """ Better Boolean Field that also evalutes 'false' to False and 'true' to True """ widget = BetterBooleanSelect def clean(self, value): return super(BetterBooleanField, self).clean(value) class BetterBooleanFilter(django_filters.BooleanFilter): """ This boolean filter allows evaluating 'true' and 'false' """ field_class = BetterBooleanField
In your REST Filter you then only need to write this:
class TaskFilter(BaseFilter): """ Filter for Tasks """ class Meta: model = Task show_only_my_tasks = BetterBooleanFilter()
Thomas
Hello, thanks for this code, it really helped us out.
If you want to avoid having to modify all of your filters (if you have many of them...), you can also create a custom FilterSet. In the code below I renamed the original REST framework filterset to keep the number of things that need to be changed to a minimum:
from django_filters.rest_framework import FilterSet as OriginalFilterSet
class FilterSet(OriginalFilterSet):
"""
This updates filterset with improved BooleanFilter.
"""
FILTER_DEFAULTS = deepcopy(django_filters.filterset.FILTER_FOR_DBFIELD_DEFAULTS)
FILTER_DEFAULTS.update(
{models.NullBooleanField: {"filter_class": BetterBooleanFilter,},}
)