Using Django Check Constraints to Prevent the Storage of The Empty String2021-01-31
Here’s another use case for creating a database constraint with Django’s
By default, a model
CharField will store any string that the database supports.
This includes the empty string, which is often inappropriate.
For example, imagine we have a
Team model, representing a group that users can belong to.
We started by giving it a unique
Whilst seemingly reasonable, this model definition allows the creation of teams with an empty
Such a team object is likely to break other parts of our application.
For example, links might not display or unexpected errors may occur due to
name being falsey.
We could try to prevent the creation of empty-named teams with form validation, but that does not prevent creation of bad data through other processes, such as bulk imports. To ensure all the data in our system is valid, we need a database constraint.
Here we want to add a minimal database constraint that ensures the length of
name is greater than 0.
Let’s walk through that now.
First, we need to register the
Length function for use with
This makes it available as a transform or lookup, like the default lookups such as
<field>__gt for “field greater than”.
Registering the function requires only a single function call:
We can add this at the top of our models file.
Django has many such functions we can register as extra” lookups/transforms - check out other references to
register_lookup() in the database functions documentation.
Second, we add our new constraint to
We use a
Q() object to represent what data is valid - where the name length is greater than 0:
We’d then run
makemigrations to generate a new migration:
This migration has only the one step, adding the new constraint:
Third, we can write a test.
I like to do this for all check constraints to ensure they do what I meant.
Here our test tries to create a bad team, and asserts that the database raises an
IntegrityError mentioning the name of our constraint:
Fourth, before we can deploy our change, we need to check that there is no empty-named team in our production database. If such a team does exist, the database will fail to apply the migration, since constraints apply to all data.
We can do this with
./manage.py shell on production:
Great, zero results. If such a team existed, we’d want to fix it, perhaps by renaming or deleting it.
Fifth, we’d want to add some extra user-facing validation, such as in forms or API endpoints. This can present a nice error message to the user if they attempt to create such a team. Without such validation attempts to use the empty string will crash:
In the forms and DRF
CharFields we can do this by declaring
May you store only valid data,
Want better tests? Check out my book Speed Up Your Django Tests which teaches you to write faster, more accurate tests.
One summary email a week, no spam, I pinky promise.
- Django's Field Choices Don't Constrain Your Data
- Using Django Check Constraints to Ensure Only One Field Is Set
- Using Django Check Constraints for the Sum of Percentage Fields
© 2021 All rights reserved.