from django.db import transaction
from django.http import JsonResponse
@transaction.atomic
def transfer_funds(request):
"""Transfer money between accounts atomically."""
from_account_id = request.POST['from_account']
to_account_id = request.POST['to_account']
amount = Decimal(request.POST['amount'])
from_account = Account.objects.select_for_update().get(id=from_account_id)
to_account = Account.objects.select_for_update().get(id=to_account_id)
if from_account.balance < amount:
raise ValueError('Insufficient funds')
from_account.balance -= amount
to_account.balance += amount
from_account.save()
to_account.save()
# Create transaction record
Transaction.objects.create(
from_account=from_account,
to_account=to_account,
amount=amount
)
# If anything fails above, all changes roll back
return JsonResponse({'status': 'success'})
def create_order_with_items(order_data, items_data):
"""Create order and items in one transaction."""
with transaction.atomic():
order = Order.objects.create(**order_data)
for item_data in items_data:
OrderItem.objects.create(order=order, **item_data)
# Send email only if transaction succeeds
transaction.on_commit(lambda: send_order_email.delay(order.id))
return order
The atomic decorator/context manager ensures all-or-nothing database operations. I wrap related operations in @transaction.atomic or with transaction.atomic() blocks. If any operation fails, the entire transaction rolls back. This prevents partial data updates. I use savepoints for nested transactions. For long-running tasks, I keep transactions short to avoid lock contention. The on_commit hook runs code after successful commit. I'm careful with transactions in loops to prevent long-running locks. This is essential for maintaining data consistency.