Constraints & Indexes
Enforce data integrity in the database itself with UniqueConstraint and CheckConstraint, and speed up queries with the right indexes.
Validation in a form or clean() method is a courtesy; a database constraint is a guarantee. Application checks can be bypassed by a shell script, a data migration, or two concurrent requests racing past the same check. For invariants that must always hold, push them into the schema.
Modern constraints live in Meta
Django 5.x wants you to use Meta.constraints rather than the older field-level unique_together:
UniqueConstraint— uniqueness, optionally conditional. "Only one default address per user" is a partial unique constraint (condition=Q(is_default=True)) — somethingunique_togethersimply cannot express.CheckConstraint— a boolean invariant the row must satisfy, e.g.discount <= priceorend_date >= start_date.
Indexes are about read speed
An index makes lookups on a column fast at the cost of slightly slower writes and some disk. Index the columns you actually filter and order by — foreign keys are indexed automatically, but that composite (status, published_at) your list view sorts on is not. Use Meta.indexes with models.Index, and consider a partial index (condition=...) for hot paths.
Give constraints and indexes explicit
name=s. Django needs stable names to generate clean migrations, and a good name (uniq_default_address_per_user) turns a crypticIntegrityErrorinto a self-documenting one.
Example
from django.db import models
from django.db.models import Q, F
class Address(models.Model):
user = models.ForeignKey("auth.User", on_delete=models.CASCADE, related_name="addresses")
line1 = models.CharField(max_length=200)
is_default = models.BooleanField(default=False)
class Meta:
constraints = [
# At most ONE default address per user — a partial unique constraint.
models.UniqueConstraint(
fields=["user"],
condition=Q(is_default=True),
name="uniq_default_address_per_user",
),
]
class Booking(models.Model):
room = models.ForeignKey("Room", on_delete=models.CASCADE)
start_date = models.DateField()
end_date = models.DateField()
price = models.DecimalField(max_digits=8, decimal_places=2)
discount = models.DecimalField(max_digits=8, decimal_places=2, default=0)
class Meta:
constraints = [
models.CheckConstraint(check=Q(end_date__gte=F("start_date")),
name="booking_end_after_start"),
models.CheckConstraint(check=Q(discount__lte=F("price")),
name="booking_discount_not_over_price"),
]
indexes = [
models.Index(fields=["room", "start_date"], name="idx_room_start"),
]When to use it
- A developer adds a UniqueConstraint on (user, post) in a Like model so a user can only like each post once.
- A team uses CheckConstraint to ensure that end_date is always greater than start_date at the database level.
- A developer uses ExclusionConstraint with PostgreSQL to prevent overlapping time slot bookings for a room.
More examples
UniqueConstraint for composite uniqueness
Enforces that each (user, post) pair can only appear once, preventing duplicate likes at the database level.
from django.db.models import UniqueConstraint
class Like(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
class Meta:
constraints = [
UniqueConstraint(fields=["user", "post"], name="unique_user_post_like")
]CheckConstraint for date ordering
Adds a database CHECK constraint ensuring the event end date is never before the start date.
from django.db.models import CheckConstraint, Q
class Event(models.Model):
start_date = models.DateField()
end_date = models.DateField()
class Meta:
constraints = [
CheckConstraint(
check=Q(end_date__gte=models.F("start_date")),
name="event_end_after_start",
)
]Conditional UniqueConstraint
Prevents duplicate active bookings while allowing a cancelled booking to be re-created.
from django.db.models import UniqueConstraint, Q
class Booking(models.Model):
room = models.ForeignKey("Room", on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
cancelled = models.BooleanField(default=False)
class Meta:
constraints = [
UniqueConstraint(
fields=["room", "user"],
condition=Q(cancelled=False),
name="unique_active_booking_per_room_user",
)
]
Discussion