Model Constraints
Enforce data rules with UniqueConstraint, CheckConstraint, and conditional constraints. Replace unique_together with modern constraints.
1. Introduction
Model constraints enforce data integrity rules at the database level — not just in Python. Unlike field validators or clean() methods which only run in forms, database constraints apply to every insert and update regardless of how it happens.
Django provides two main constraint types: UniqueConstraint for uniqueness rules and CheckConstraint for value rules. Both are defined in the model's Meta class and generate real database constraints.
- You should already be comfortable with model
Metaoptions and field definitions. - Your
.venvmust be active and Django 5.2 installed.
2. Why use constraints
Field validators and clean() run in Python — they protect you when data comes through forms. But if data is inserted directly via the shell, a management command, a data migration, or a third party tool, validators are bypassed entirely.
Database constraints are the last line of defense. They live in the database itself and reject invalid data no matter where it comes from.
3. UniqueConstraint
UniqueConstraint is the modern replacement for unique_together. It enforces that a combination of field values is unique across all rows.
Basic usage
from django.db import models
class Review(models.Model):
user = models.ForeignKey('auth.User', on_delete=models.CASCADE)
article = models.ForeignKey('Article', on_delete=models.CASCADE)
rating = models.PositiveSmallIntegerField()
class Meta:
constraints = [
models.UniqueConstraint(
fields=['user', 'article'],
name='unique_user_article_review',
)
]
This ensures each user can only leave one review per article. The name parameter is required — it is the constraint name in the database and must be unique across your project.
UniqueConstraint with a condition
You can apply a constraint conditionally — for example, only enforce uniqueness for published articles:
from django.db.models import Q
class Article(models.Model):
slug = models.SlugField(max_length=220)
status = models.CharField(max_length=20)
class Meta:
constraints = [
models.UniqueConstraint(
fields=['slug'],
condition=Q(status='published'),
name='unique_slug_for_published_articles',
)
]
Draft articles can share a slug — only published ones must be unique. This is a real-world use case that unique_together cannot handle.
4. CheckConstraint
CheckConstraint enforces that a field value meets a condition. For example, a rating must be between 1 and 5, or a price must be greater than zero.
from django.db import models
from django.db.models import Q
class Review(models.Model):
rating = models.PositiveSmallIntegerField()
helpful_votes = models.IntegerField(default=0)
class Meta:
constraints = [
models.CheckConstraint(
condition=Q(rating__gte=1) & Q(rating__lte=5),
name='rating_between_1_and_5',
),
models.CheckConstraint(
condition=Q(helpful_votes__gte=0),
name='helpful_votes_not_negative',
),
]
CheckConstraint takes a condition argument — a Q object that must evaluate to True for the row to be valid. The database rejects any insert or update that fails this check.
5. Combining multiple constraints
You can define as many constraints as needed in the constraints list:
from django.db import models
from django.db.models import Q
class Product(models.Model):
name = models.CharField(max_length=200)
sku = models.CharField(max_length=50)
price = models.DecimalField(max_digits=10, decimal_places=2)
stock = models.IntegerField(default=0)
category = models.ForeignKey('Category', on_delete=models.CASCADE)
class Meta:
constraints = [
models.UniqueConstraint(
fields=['sku', 'category'],
name='unique_sku_per_category',
),
models.CheckConstraint(
condition=Q(price__gt=0),
name='price_must_be_positive',
),
models.CheckConstraint(
condition=Q(stock__gte=0),
name='stock_not_negative',
),
]
6. unique_together vs UniqueConstraint
Both enforce uniqueness across multiple fields, but UniqueConstraint is more powerful:
unique_together— simple, no conditions, being phased out in favor ofUniqueConstraint.UniqueConstraint— supports conditions, nulls handling, and named constraints. Use this for all new code.
unique_together with UniqueConstraint in an existing model, Django generates a migration that drops the old constraint and adds a new one. Review the migration carefully before applying it to production.
7. Handling constraint errors
When a constraint is violated, the database raises an error that Django wraps in an IntegrityError. Catch it when you need to handle it gracefully:
from django.db import IntegrityError
try:
Review.objects.create(user=user, article=article, rating=5)
except IntegrityError:
# Handle duplicate review gracefully
print('User has already reviewed this article.')
8. Next steps
Constraints protect your data at the database level. The next step covers database indexes — how to speed up queries by telling the database which columns to index and when partial indexes help.