Django cookbook · Intermediate
Enforce rules with database constraints
Validation in forms and serializers isn't enough: scripts, imports and race conditions still create bad data.
Approach
Declare rules in Meta.constraints so the database enforces them for every write path. Conditional unique
constraints express rules like "one active subscription per user".
Solution
from django.conf import settings
from django.db import models
from django.db.models import Q
from django.db.models.functions import Lower
class Subscription(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
plan = models.CharField(max_length=20)
status = models.CharField(max_length=20, default="active")
seats = models.PositiveIntegerField(default=1)
starts_on = models.DateField()
ends_on = models.DateField(null=True, blank=True)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["user"], condition=Q(status="active"), name="one_active_subscription_per_user"
),
models.CheckConstraint(condition=Q(seats__gte=1, seats__lte=500), name="seats_in_range"),
models.CheckConstraint(
condition=Q(ends_on__isnull=True) | Q(ends_on__gt=models.F("starts_on")), name="ends_after_start"
),
]
class Team(models.Model):
name = models.CharField(max_length=100)
class Meta:
constraints = [models.UniqueConstraint(Lower("name"), name="unique_team_name_ci")]
Pitfalls
- Relying on
unique=Truealone when uniqueness depends on another field's value. - Not handling
IntegrityErrorin the view, turning a conflict into a 500 error. - Adding constraints to tables with existing bad data; clean the data in a migration first.
Variations
Model.full_clean()validates constraints in Python too, so forms show friendly errors.- On PostgreSQL,
ExclusionConstraintprevents overlapping date ranges (bookings).
Further reading: Model constraints
Reference: https://docs.djangoproject.com/en/6.0/ref/models/constraints/
More Django recipes
- Add a required column without downtime
- Add full-text search with PostgreSQL
- Add health checks and structured logging
- Avoid N+1 queries with select_related and prefetch_related
- Cache expensive queries and invalidate on change
- Call external APIs concurrently in async views
- Configure settings from environment variables
- Harden security settings, including a Content Security Policy