# Transactions and Referential Protection — Django / FastAPI

Source: https://www.skillbyai.com/en/django-fastapi/v-atomic

> All or nothing; no orphaned rows.

## transaction.atomic and on_delete=PROTECT

`transaction.atomic()` wraps several writes so they commit together or roll back together when an exception occurs. Combine it with database constraints (unique, foreign keys) to keep data consistent even when application code has bugs. `on_delete=PROTECT` raises ProtectedError instead of deleting a category that still has products. Consider `ATOMIC_REQUESTS` or explicit atomic blocks around multi-step operations such as creating an order with its items.

## A rolled-back transaction and a protected delete, run

I ran this script with python manage.py shell --no-imports in the demo project (Django 6.1.1, SQLite). Creating a category and product, then a duplicate category name, raises IntegrityError: the whole block rolls back, so the product count and the Toys category are unchanged. Deleting a category with products raises ProtectedError.

```python
from decimal import Decimal
from django.db import IntegrityError, transaction
from catalog.models import Category, Product

before = Product.objects.count()
try:
    with transaction.atomic():
        toys = Category.objects.create(name="Toys")
        Product.objects.create(category=toys, name="Yo-yo", price=Decimal("99"))
        Category.objects.create(name="Office")          # violates unique=True -> error
except IntegrityError as e:
    print("rolled back:", type(e).__name__)
print("products before/after:", before, Product.objects.count())
print("Toys exists:", Category.objects.filter(name="Toys").exists())
try:
    Category.objects.get(name="Office").delete()
except Exception as e:
    print(type(e).__name__, "- PROTECT keeps products from losing their category")
```

Output:

```
rolled back: IntegrityError
products before/after: 4 4
Toys exists: False
ProtectedError - PROTECT keeps products from losing their category
```

## Keep external calls outside transactions

Do not call payment or email APIs inside atomic blocks; use transaction.on_commit to trigger them after a successful commit.

**Quiz:** What happens to earlier writes in an atomic block when a later write fails?

- [ ] They stay committed
- [x] They are rolled back too
- [ ] They are duplicated
- [ ] They are written to a log only

*Answer:* They are rolled back too. All or nothing.
