from django.db import transaction
from django.shortcuts import get_object_or_404
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .models import Order, OrderItem, Product
@api_view(['POST'])
@transaction.atomic
def create_order(request):
"""Create an order with items atomically."""
order = Order.objects.create(
customer=request.user,
status='pending'
)
items_data = request.data.get('items', [])
for item_data in items_data:
product = get_object_or_404(Product, id=item_data['product_id'])
# Check inventory
if product.stock < item_data['quantity']:
# Transaction will rollback
return Response(
{'error': f'Insufficient stock for {product.name}'},
status=status.HTTP_400_BAD_REQUEST
)
# Reduce stock
product.stock -= item_data['quantity']
product.save()
# Create order item
OrderItem.objects.create(
order=order,
product=product,
quantity=item_data['quantity'],
price=product.price
)
# Send email only after successful commit
transaction.on_commit(
lambda: send_order_confirmation.delay(order.id)
)
return Response({'order_id': order.id}, status=status.HTTP_201_CREATED)
Database transactions ensure data integrity when multiple operations must succeed or fail together. I use @transaction.atomic on views or functions to wrap them in a transaction. For partial rollbacks, I use transaction.atomic() as a context manager and catch exceptions inside. The on_commit hook schedules actions (like sending emails) only after successful commit, preventing premature side effects if the transaction rolls back. I'm careful with transactions in loops to avoid locking issues. For complex workflows, I sometimes use savepoints for nested transactions.