Stop using NullBooleanField

Code Review Doctor
2 min readNov 4, 2020

--

For four years the documentation for NullBooleanField was two sentences and one of was “yeah…don’t use it”. As of 3.1 the axe has fallen and the hint of future deprecation has been replaced with an actual deprecation warning. Instead of using NullBooleanField()use BooleanField(null=True).

I’m a GitHub bot that improves your Django by suggesting improvements

On the face of it, the existence of NullBooleanField seems odd. Why have an entire class that can be achieved with a null keyword argument? We don’t see NullCharField or NullDateField. Indeed, for those Django expects us to doCharField(null=True) and DateField(null=True) . So what’s was so special about NullBooleanField and why is it now deprecated?

Enter NullBooleanField

NullBooleanField renders a NullBooleanSelect widget which is a <select> containing options “Unknown” (None), “Yes” (True) and “No” (False). The implication is NullBooleanField was intended for when explicitly stating no answer yet was needed. Indeed, in many contexts it would be useful to clarify “is it False because the user has set it False, or because the user has not yet answered?”. To facilitate that, the database column must allow NULL (aka None at Python level).

Unfortunately time has shown a great deal of room for confusion: StackOverflow has many questions that are answered with “use NullBooleanField instead of BooleanField” and vice versa. If one of the reasons for separating BooleanField and NullBooleanField was to give clarity then instead the opposite occurred for many.

Exit NullBooleanField

Until Django 2.1 in 2018, null was not permitted in BooleanField because quite obviously None is not in a bool value. Why would we expect None to be used in a field that says it’s for boolean values? Well, on the other hand None is not a str either but CharField(null=True) was supported and None is not an int, but IntegerField(null=True) was also acceptable.

So in the deprecation of NullBooleanField there was an argument for consistency with how the other fields handle null. For consistency the choice was to either add NullCharField, NullIntegerField, NullDateField and so on or to rename NullBooleanField to BooleanField. Even though NullBooleanField was more explicit and a more of an accurate name.

With this deprecation three classes are impacted:

  • django.models.fields.NullBooleanField
  • django.forms.fields.BooleanField
  • django.forms.widgets.NullBooleanSelect

These three have slightly different handling of “empty” values, so for some the swap from NullBooleanField to BooleanField will need some careful testing:

from django.forms.fields import NullBooleanFieldfield = NullBooleanField()assert field.clean("True") is True
assert field.clean("") is None
assert field.clean(False) is False
from django.forms.fields import BooleanFieldfield = BooleanField(required=False)assert field.clean(True) is True
assert field.clean("") is False
assert field.clean(False) is False
from django.db.models import fieldsfield = fields.BooleanField(null=True, blank=True)
assert field.clean(True, "test") is True
assert field.clean("", "test") is None
assert field.clean(False, "test") is False

I’m a GitHub bot that suggest Django improvements to your code. You can check your entire codebase at django.doctor or install the GitHub PR bot to reduce dev effort and improve your code.

I’m a GitHub bot that improves your Django by suggesting improvements

--

--