Database Constraints and Validation
To enforce data integrity at the database level, use the CheckConstraint and UniqueConstraint classes within your model's Meta.constraints list. These constraints ensure that invalid data is rejected by the database and provide Python-level validation during model cleaning.
from django.db import models
from django.db.models import F, Q
class Product(models.Model):
price = models.IntegerField(null=True)
discounted_price = models.IntegerField(null=True)
slug = models.SlugField(max_length=100)
category = models.CharField(max_length=50)
class Meta:
constraints = [
# Ensure price is always greater than discounted_price
models.CheckConstraint(
condition=Q(price__gt=F("discounted_price")),
name="price_gt_discounted_price",
),
# Ensure slug is unique within a category
models.UniqueConstraint(
fields=["slug", "category"],
name="unique_slug_per_category",
),
]
Enforcing Logic with CheckConstraint
CheckConstraint allows you to define boolean conditions that must be met for a row to be inserted or updated. The condition argument accepts a Q object or a boolean expression.
In tests/constraints/models.py, complex logic is enforced using nested Q objects:
models.CheckConstraint(
condition=models.Q(
models.Q(unit__isnull=True) | models.Q(unit__in=["μg/mL", "ng/mL"])
),
name="unicode_unit_list",
)
Enforcing Uniqueness with UniqueConstraint
UniqueConstraint provides more flexibility than the standard unique=True field attribute, supporting multi-field uniqueness, partial constraints, and functional indexes.
Partial Unique Constraints
Use the condition argument to enforce uniqueness only on a subset of rows. This is useful for "soft delete" patterns or conditional logic.
# From tests/constraints/models.py
class UniqueConstraintConditionProduct(models.Model):
name = models.CharField(max_length=255)
color = models.CharField(max_length=32, null=True)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["name"],
name="name_without_color_uniq",
condition=models.Q(color__isnull=True),
),
]
Functional Unique Constraints
You can enforce uniqueness based on expressions or functions (e.g., case-insensitive uniqueness) by passing positional arguments instead of the fields kwarg.
from django.db.models.functions import Lower
# Enforce case-insensitive uniqueness on a title field
models.UniqueConstraint(
Lower("title"),
"author",
name="book_func_uq",
)
Covering Indexes
The include argument allows you to create a covering index, which includes non-key columns to speed up queries that only select those columns.
# From tests/constraints/models.py
models.UniqueConstraint(
fields=["name"],
name="name_include_color_uniq",
include=["color"],
)
Naming Constraints in Abstract Models
When defining constraints on abstract models, use %(app_label)s and %(class)s placeholders in the name to avoid collision in child models.
# From tests/constraints/models.py
class AbstractModel(models.Model):
age = models.IntegerField()
class Meta:
abstract = True
constraints = [
models.CheckConstraint(
condition=models.Q(age__gte=18),
name="%(app_label)s_%(class)s_adult",
),
]
Validation and Error Handling
Constraints are validated during the database transaction (raising django.db.IntegrityError) and during model.full_clean().
You can customize the error message and code returned during validation:
models.CheckConstraint(
condition=Q(price__gt=0),
name="positive_price",
violation_error_code="invalid_price",
violation_error_message="Price must be a positive number.",
)
Troubleshooting
- RawSQL in CheckConstraint: If you use
RawSQLin aCheckConstraint.condition, it will be enforced by the database but will not be validated duringmodel.full_clean(). - NULL Values: By default,
UniqueConstrainttreatsNULLvalues as distinct (i.e., multiple rows can haveNULLin a unique field). On supported backends (like PostgreSQL 15+), you can setnulls_distinct=Falseto treatNULLvalues as equal for uniqueness. - Mutual Exclusivity: In
UniqueConstraint, thefieldsandexpressionsarguments are mutually exclusive. Use positional arguments for expressions. - Deferred Constraints:
UniqueConstraintsupportsdeferrable=models.Deferrable.DEFERREDto check uniqueness at the end of a transaction, but this cannot be used withcondition,include,opclasses, orexpressions.