A database uniqueness constraint rejects a conflicting stored identity even when application validation was bypassed.
Python Django database constraints: enforce receipt identity per owner
Operation contract
The owned model defines receipt identity as owner_id plus receipt_id and requires a nonnegative amount at the database level. Two different owners may store the same receipt ID; a second row for the same owner is rejected. A bulk_create with a negative amount is rejected too, despite skipping model-level cleaning. Each expected failure is inside its own atomic block and caught outside it, leaving earlier accepted rows readable.
Failure and ownership boundary
A declared owner field is not authentication. A request must obtain its owner from a trusted principal and filter reads as well as writes. Django REST Framework creation ownership: assign the owner from the principal, Django REST Framework list visibility: filter the principal’s rows before paging and Django atomic transactions: rollback the batch and defer callbacks are all required for a service policy. This fixture checks an owned in-memory SQLite schema, not another engine’s isolation or production migration history.
Tested environment
Dependency check: this program was executed on CPython 3.14.6 with Django==5.2.17. Install these versions in a separate virtual environment. The download includes the recorded environment snapshot; no third-party package is part of the website runtime.
Working program
from django.conf import settings
settings.configure(INSTALLED_APPS=[], DATABASES={"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}}, DEFAULT_AUTO_FIELD="django.db.models.AutoField")
import django
django.setup()
from django.db import connection, models, transaction, IntegrityError
class OwnedReceipt(models.Model):
owner_id = models.PositiveIntegerField()
receipt_id = models.CharField(max_length=6)
amount = models.IntegerField()
class Meta:
app_label = "owned_receipt_fixture"
constraints = [models.UniqueConstraint(fields=["owner_id", "receipt_id"], name="owner_receipt_identity"),
models.CheckConstraint(condition=models.Q(amount__gte=0), name="owned_amount_nonnegative")]
with connection.schema_editor() as schema:
schema.create_model(OwnedReceipt)
try:
OwnedReceipt.objects.create(owner_id=1, receipt_id="R-0041", amount=125)
OwnedReceipt.objects.create(owner_id=2, receipt_id="R-0041", amount=250)
try:
with transaction.atomic():
OwnedReceipt.objects.create(owner_id=1, receipt_id="R-0041", amount=500)
except IntegrityError:
print("same-owner duplicate rejected")
try:
with transaction.atomic():
OwnedReceipt.objects.bulk_create([OwnedReceipt(owner_id=1, receipt_id="R-0042", amount=-1)])
except IntegrityError:
print("bulk negative amount rejected")
print("stored:", list(OwnedReceipt.objects.order_by("owner_id").values_list("owner_id", "amount")))
finally:
connection.close()Output
same-owner duplicate rejected
bulk negative amount rejected
stored: [(1, 125), (2, 250)]Costs and limits
Constraint enforcement has engine/index-dependent costs; application preflight checks are not a replacement for it under concurrent writers. The fixture contains two rows and provides no measured production plan. CharField length and receipt spelling also need explicit schema validation; the uniqueness check alone does not enforce that grammar.
Common Mistakes
- Application full_clean is not automatically called by every write path.
- Uniqueness per owner does not authorize a received owner_id.
Connected lessons
Django REST Framework creation ownership: assign the owner from the principal, Django REST Framework list visibility: filter the principal’s rows before paging, Django atomic transactions: rollback the batch and defer callbacks, Django migrations: generate and apply an owned application schema.
