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 Meta options and field definitions.
  • Your .venv must 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 of UniqueConstraint.
  • UniqueConstraint — supports conditions, nulls handling, and named constraints. Use this for all new code.

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.


Never miss a story on Django.wiki

Subscribe for fresh tutorials, snippets, and updates.

By subscribing you agree to our Privacy Policy.