Advanced Django patterns — service layer, caching, signals, middleware, performance optimization (N+1, bulk ops), and DDD patterns (fat models, bounded contexts, domain events). Extends django-patterns.
From clarcnpx claudepluginhub marvinrichter/clarc --plugin clarcThis skill uses the workspace's default tool permissions.
Designs and optimizes AI agent action spaces, tool definitions, observation formats, error recovery, and context for higher task completion rates.
Enables AI agents to execute x402 payments with per-task budgets, spending controls, and non-custodial wallets via MCP tools. Use when agents pay for APIs, services, or other agents.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
This skill extends django-patterns with service layer, caching, signals, middleware, performance, and DDD.
# apps/orders/services.py
from typing import Optional
from django.db import transaction
from .models import Order, OrderItem
class OrderService:
"""Service layer for order-related business logic."""
@staticmethod
@transaction.atomic
def create_order(user, cart: Cart) -> Order:
"""Create order from cart."""
order = Order.objects.create(
user=user,
total_price=cart.total_price
)
for item in cart.items.all():
OrderItem.objects.create(
order=order,
product=item.product,
quantity=item.quantity,
price=item.product.price
)
# Clear cart
cart.items.all().delete()
return order
@staticmethod
def process_payment(order: Order, payment_data: dict) -> bool:
"""Process payment for order."""
payment = PaymentGateway.charge(
amount=order.total_price,
token=payment_data['token']
)
if payment.success:
order.status = Order.Status.PAID
order.save()
OrderService.send_confirmation_email(order)
return True
return False
@staticmethod
def send_confirmation_email(order: Order):
"""Send order confirmation email."""
pass
from django.views.decorators.cache import cache_page
from django.utils.decorators import method_decorator
@method_decorator(cache_page(60 * 15), name='dispatch') # 15 minutes
class ProductListView(generic.ListView):
model = Product
template_name = 'products/list.html'
context_object_name = 'products'
{% load cache %}
{% cache 500 sidebar %}
... expensive sidebar content ...
{% endcache %}
from django.core.cache import cache
def get_featured_products():
"""Get featured products with caching."""
cache_key = 'featured_products'
products = cache.get(cache_key)
if products is None:
products = list(Product.objects.filter(is_featured=True))
cache.set(cache_key, products, timeout=60 * 15) # 15 minutes
return products
from django.core.cache import cache
def get_popular_categories():
cache_key = 'popular_categories'
categories = cache.get(cache_key)
if categories is None:
categories = list(Category.objects.annotate(
product_count=Count('products')
).filter(product_count__gt=10).order_by('-product_count')[:20])
cache.set(cache_key, categories, timeout=60 * 60) # 1 hour
return categories
# apps/users/signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth import get_user_model
from .models import Profile
User = get_user_model()
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
"""Create profile when user is created."""
if created:
Profile.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
"""Save profile when user is saved."""
instance.profile.save()
# apps/users/apps.py
from django.apps import AppConfig
class UsersConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.users'
def ready(self):
"""Import signals when app is ready."""
import apps.users.signals
# middleware/active_user_middleware.py
import time
from django.utils.deprecation import MiddlewareMixin
class ActiveUserMiddleware(MiddlewareMixin):
"""Middleware to track active users."""
def process_request(self, request):
"""Process incoming request."""
if request.user.is_authenticated:
request.user.last_active = timezone.now()
request.user.save(update_fields=['last_active'])
class RequestLoggingMiddleware(MiddlewareMixin):
"""Middleware for logging requests."""
def process_request(self, request):
"""Log request start time."""
request.start_time = time.time()
def process_response(self, request, response):
"""Log request duration."""
if hasattr(request, 'start_time'):
duration = time.time() - request.start_time
logger.info(f'{request.method} {request.path} - {response.status_code} - {duration:.3f}s')
return response
# Bad - N+1 queries
products = Product.objects.all()
for product in products:
print(product.category.name) # Separate query for each product
# Good - Single query with select_related
products = Product.objects.select_related('category').all()
for product in products:
print(product.category.name)
# Good - Prefetch for many-to-many
products = Product.objects.prefetch_related('tags').all()
for product in products:
for tag in product.tags.all():
print(tag.name)
class Product(models.Model):
name = models.CharField(max_length=200, db_index=True)
slug = models.SlugField(unique=True)
category = models.ForeignKey('Category', on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
indexes = [
models.Index(fields=['name']),
models.Index(fields=['-created_at']),
models.Index(fields=['category', 'created_at']),
]
# Bulk create
Product.objects.bulk_create([
Product(name=f'Product {i}', price=10.00)
for i in range(1000)
])
# Bulk update
products = Product.objects.all()[:100]
for product in products:
product.is_active = True
Product.objects.bulk_update(products, ['is_active'])
# Bulk delete
Product.objects.filter(stock=0).delete()
| Pattern | Description |
|---|---|
| Split settings | Separate dev/prod/test settings |
| Custom QuerySet | Reusable query methods |
| Service Layer | Business logic separation |
| ViewSet | REST API endpoints |
| Serializer validation | Request/response transformation |
| select_related | Foreign key optimization |
| prefetch_related | Many-to-many optimization |
| Cache first | Cache expensive operations |
| Signals | Event-driven actions |
| Middleware | Request/response processing |
Why not hexagonal? Django's ORM models intentionally couple domain and persistence — they are both the data model and the persistence model. Separating them (hexagonal) means fighting the framework: no
save(), no querysets, manual mappers everywhere. Use DDD concepts within Django's conventions instead.
Each Django app = one Bounded Context. Apps should be self-contained with their own models, views, and services:
apps/
markets/ ← Market bounded context
models.py # Market, MarketStatus — the aggregate root
services.py # Use cases (business operations)
views.py # Inbound adapter (HTTP)
serializers.py # DTO mapping
orders/ ← Order bounded context
models.py # Order, OrderLine — separate aggregate root
services.py
Cross-app references: use IDs or signals, not direct model imports where possible.
Django models with business behavior are DDD-aligned. Logic belongs in the model, not in the view or serializer:
# apps/markets/models.py
from django.db import models
from django.core.exceptions import ValidationError
class Market(models.Model):
name = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
status = models.CharField(
max_length=20,
choices=[("DRAFT", "Draft"), ("ACTIVE", "Active"), ("SUSPENDED", "Suspended")],
default="DRAFT",
)
created_at = models.DateTimeField(auto_now_add=True)
# ✅ Domain behavior on the model — not in the view
def publish(self) -> None:
"""Publish the market. Raises ValidationError if not in DRAFT state."""
if self.status != "DRAFT":
raise ValidationError(f"Cannot publish market '{self.slug}': status is {self.status}")
self.status = "ACTIVE"
self.save(update_fields=["status"])
def suspend(self, reason: str) -> None:
if self.status != "ACTIVE":
raise ValidationError(f"Cannot suspend market '{self.slug}': not active")
self.status = "SUSPENDED"
self.save(update_fields=["status"])
MarketSuspendedEvent.objects.create(market=self, reason=reason)
class Meta:
db_table = "markets"
Services orchestrate multi-step operations, transactions, and cross-model coordination:
# apps/markets/services.py
from django.db import transaction
from .models import Market
def create_market(name: str, slug: str, created_by_id: int) -> Market:
"""Use case: create a new market and notify the creator."""
if not name.strip():
raise ValueError("Market name is required")
with transaction.atomic():
market = Market.objects.create(name=name, slug=slug)
_notify_market_created(market, created_by_id)
return market
def publish_market(slug: str) -> Market:
"""Use case: publish a draft market."""
market = Market.objects.select_for_update().get(slug=slug)
market.publish() # domain behavior on the model
return market
def _notify_market_created(market: Market, user_id: int) -> None:
pass
# apps/markets/models.py
class MarketQuerySet(models.QuerySet):
def active(self):
return self.filter(status="ACTIVE")
def draft(self):
return self.filter(status="DRAFT")
def by_slug(self, slug: str):
return self.filter(slug=slug)
class Market(models.Model):
objects = MarketQuerySet.as_manager()
# ...
# Usage — reads like domain language
active_markets = Market.objects.active().order_by("-created_at")[:20]
# apps/markets/signals.py
from django.dispatch import Signal, receiver
market_published = Signal() # custom domain event signal
@receiver(market_published)
def on_market_published(sender, market, **kwargs):
"""Listener — lives in adapter (e.g., send notification)."""
send_notification.delay(market.id)
# In Market.publish():
def publish(self) -> None:
if self.status != "DRAFT":
raise ValidationError(...)
self.status = "ACTIVE"
self.save(update_fields=["status"])
market_published.send(sender=self.__class__, market=self) # raise event
Market.objects queries in views → move to service or custom QuerySetmarket.status = "ACTIVE" in view without calling market.publish() → bypasses invariantstransaction.atomic() missing for multi-step operations → consistency riskRemember: Django provides many shortcuts, but for production applications, structure and organization matter more than concise code. Build for maintainability.