Using Django Check Constraints to Limit A Model to a Single Instance2021-02-04
Yet another use case for creating a database constraint with Django’s
Sometimes it’s useful to have a model with only have one instance in the database, sometimes known as a singleton. This is useful for storing a small amount of structured data that we want to share between all our project’s processes.
For example, imagine a remote API that we authenticate with using a temporary access token. We have the username and password for the API in our Django settings, and use those to get a temporary access token. We then need to store that temporary access token for all operations with that API, and refresh it when it nears its expiry time. And for security reasons, we are only allowed to store the current access token.
We can store the token and its expiry in a model like this:
This model has the right fields for holding the token, but it can also have many instances of it in our database.
We can write code that always uses a single instance by always the model through
update_or_create(), and passing all field values through
But if any process ever creates a second instance, such as an accidental creation on the admin, that code will raise a
How ever careful we are in our code, it’s better if we disallow this from ever happening. We can do that by adding a constraint that limits the model to exactly one instance.
At first it might sound like a
UniqueConstraint would work, as we want to have a unique instance.
Unfortunately this is not possible since unique constraints need at least one field to enforce uniqueness on, but we’d want to specify no fields.
Instead, we can use a
CheckConstraint that constrains the
id field that Django adds to only ever be
First, we define the constraint in
Second, we run
makemigrations to generate a new migration:
We check the migration and indeed it spells out adding the constraint:
Third, we write a test to ensure the constraint works:
The test tries to create an instance with
id=2 and ensures this query fails with a database error listing the name of our constraint.
Fourth, we change all code that handles the token to specify
For example, our token refreshing function might look like this:
Fifth, if our code has already been running without the constraint, we’d want to check our production data is valid.
Otherwise, when we try to migrate the constraint addition will fail.
We can do this by checking there is one instance with id
1, and there are no others:
If there were bad instances, we could add a migration step to update or delete them, or do that manually.
Six for any user-facing forms or API endpoints for our model will need updating to ensure they are compatible. For example on the admin we would want to disable the “add” button if the instance already exists, to prevent a crash when trying to add a second instance.
May your single instance models work well,
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.
- Using Django Check Constraints to Prevent the Storage of The Empty String
- Django's Field Choices Don't Constrain Your Data
- Using Django Check Constraints to Ensure Only One Field Is Set
© 2021 All rights reserved.