| name | django-development |
| description | Django framework workflow guidelines. Activate when working with Django projects, manage.py, django-admin, or Django-specific patterns. |
Django Workflow
Django 5.x Features
Composite Primary Keys (Django 5.2+)
Django 5.2 introduces native composite primary key support. You SHOULD use CompositePrimaryKey for junction tables and legacy database integration:
from django.db import models
class OrderItem(models.Model):
order = models.ForeignKey("Order", on_delete=models.CASCADE)
product = models.ForeignKey("Product", on_delete=models.CASCADE)
quantity = models.PositiveIntegerField()
class Meta:
constraints = [
models.CompositePrimaryKey("order", "product"),
]
Async Views
You SHOULD prefer async views for I/O-bound operations. Django 5.x has improved async ORM support:
from django.http import JsonResponse
async def fetch_items(request):
items = [item async for item in Item.objects.filter(active=True)]
return JsonResponse({"items": [i.name for i in items]})
Key async patterns:
- Use
async for with QuerySets
- Use
await with aget(), afirst(), acount(), aexists()
- Sync ORM calls in async views trigger
SynchronousOnlyOperation exceptions
Django Tasks (Background Tasks)
For simple background tasks, you MAY use Django's built-in task system (Django 5.1+):
from django.tasks import task
@task
def send_welcome_email(user_id: int) -> None:
user = User.objects.get(pk=user_id)
For complex workflows, Celery remains RECOMMENDED.
Architecture Patterns
Fat Models to Services Pattern
Business logic MUST NOT reside in views. You MUST use the services pattern:
from dataclasses import dataclass
from decimal import Decimal
from django.db import transaction
@dataclass
class OrderService:
"""Order-related business operations."""
@staticmethod
@transaction.atomic
def create_order(user, cart_items: list) -> "Order":
"""Create order from cart items with inventory validation."""
order = Order.objects.create(user=user, status=Order.Status.PENDING)
for item in cart_items:
if item.product.stock < item.quantity:
raise InsufficientStockError(item.product)
OrderItem.objects.create(
order=order,
product=item.product,
quantity=item.quantity,
price=item.product.price,
)
item.product.stock -= item.quantity
item.product.save(update_fields=["stock"])
return order
@staticmethod
def calculate_total(order: "Order") -> Decimal:
"""Calculate order total with discounts applied."""
return sum(item.subtotal for item in order.items.all())
Selectors Pattern
Query logic MUST be encapsulated in selectors. Views MUST NOT contain complex querysets:
from django.db.models import QuerySet, Q, Prefetch
class ProductSelectors:
"""Product query encapsulation."""
@staticmethod
def get_active_products() -> QuerySet:
return Product.objects.filter(
is_active=True,
stock__gt=0,
).select_related("category")
@staticmethod
def search_products(query: str) -> QuerySet:
return Product.objects.filter(
Q(name__icontains=query) | Q(description__icontains=query),
is_active=True,
)
@staticmethod
def get_product_with_reviews(product_id: int) -> Product:
return Product.objects.prefetch_related(
Prefetch(
"reviews",
queryset=Review.objects.filter(approved=True).order_by("-created_at"),
)
).get(pk=product_id)
Views
Class-Based vs Function-Based Views
You SHOULD use CBVs for standard CRUD operations and FBVs for simple, one-off logic:
Use CBVs when:
- Standard CRUD operations
- Pagination, filtering, or sorting needed
- Template rendering with context
- Code reuse via mixins
Use FBVs when:
- Simple API endpoints
- Webhooks or callbacks
- Complex conditional logic that doesn't fit CBV flow
CBV Example
from django.views.generic import ListView, DetailView
from django.contrib.auth.mixins import LoginRequiredMixin
class ProductListView(ListView):
model = Product
template_name = "products/list.html"
context_object_name = "products"
paginate_by = 20
def get_queryset(self):
return ProductSelectors.get_active_products()
class ProductDetailView(DetailView):
model = Product
template_name = "products/detail.html"
slug_url_kwarg = "slug"
def get_object(self):
return ProductSelectors.get_product_with_reviews(self.kwargs["slug"])
Testing
Test Organization
import pytest
from decimal import Decimal
from apps.orders.services import OrderService
@pytest.mark.django_db
class TestOrderService:
def test_create_order_success(self, user, cart_with_items):
order = OrderService.create_order(user, cart_with_items)
assert order.user == user
assert order.items.count() == len(cart_with_items)
def test_create_order_insufficient_stock(self, user, cart_with_unavailable_item):
with pytest.raises(InsufficientStockError):
OrderService.create_order(user, cart_with_unavailable_item)
Fixtures
import pytest
from django.contrib.auth import get_user_model
@pytest.fixture
def user(db):
User = get_user_model()
return User.objects.create_user(
email="test@example.com",
password="testpass123",
)
@pytest.fixture
def product(db, category):
return Product.objects.create(
name="Test Product",
slug="test-product",
category=category,
price=Decimal("29.99"),
stock=10,
)