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)) — something unique_together simply cannot express.
  • CheckConstraint — a boolean invariant the row must satisfy, e.g. discount <= price or end_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 cryptic IntegrityError into a self-documenting one.

Example

Example · python
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.

Example · python
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.

Example · python
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.

Example · python
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

  • Be the first to comment on this lesson.