Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Django architecture patterns, REST API design with DRF, ORM best practices, caching, signals, middleware, and production-grade Django apps.
.claude/skills/django-patterns/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | — | — |
| case-02 | ✗→✗ | = Same ✗ | — | — |
| case-11 | ✗→✗ | = Same ✗ | — | — |
| case-03 | ✗→✗ | = Same ✗ | — | — |
| case-05 | ✗→✗ | = Same ✗ | — | — |
Production-grade Django architecture patterns for scalable, maintainable applications.
myproject/
├── config/
│ ├── __init__.py
│ ├── settings/
│ │ ├── __init__.py
│ │ ├── base.py # Base settings
│ │ ├── development.py # Dev settings
│ │ ├── production.py # Production settings
│ │ └── test.py # Test settings
│ ├── urls.py
│ ├── wsgi.py
│ └── asgi.py
├── manage.py
└── apps/
├── __init__.py
├── users/
│ ├── __init__.py
│ ├── models.py
│ ├── views.py
│ ├── serializers.py
│ ├── urls.py
│ ├── permissions.py
│ ├── filters.py
│ ├── services.py
│ └── tests/
└── products/
└── ...python# config/settings/base.py from pathlib import Path BASE_DIR = Path(__file__).resolve().parent.parent.parent SECRET_KEY = env('DJANGO_SECRET_KEY') DEBUG = False ALLOWED_HOSTS = [] INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', 'rest_framework.authtoken', 'corsheaders', # Local apps 'apps.users', 'apps.products', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'whitenoise.middleware.WhiteNoiseMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'corsheaders.middleware.CorsMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'config.urls' WSGI_APPLICATION = 'config.wsgi.application' DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': env('DB_NAME'), 'USER': env('DB_USER'), 'PASSWORD': env('DB_PASSWORD'), 'HOST': env('DB_HOST'), 'PORT': env('DB_PORT', default='5432'), } } # config/settings/development.py from .base import * DEBUG = True ALLOWED_HOSTS = ['localhost', '127.0.0.1'] DATABASES['default']['NAME'] = 'myproject_dev' INSTALLED_APPS += ['debug_toolbar'] MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware'] EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' # config/settings/production.py from .base import * DEBUG = False ALLOWED_HOSTS = env.list('ALLOWED_HOSTS') SECURE_SSL_REDIRECT = True SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True SECURE_HSTS_SECONDS = 31536000 SECURE_HSTS_INCLUDE_SUBDOMAINS = True SECURE_HSTS_PRELOAD = True # Logging LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'file': { 'level': 'WARNING', 'class': 'logging.FileHandler', 'filename': '/var/log/django/django.log', }, }, 'loggers': { 'django': { 'handlers': ['file'], 'level': 'WARNING', 'propagate': True, }, }, }
pythonfrom django.db import models from django.contrib.auth.models import AbstractUser from django.core.validators import MinValueValidator, MaxValueValidator class User(AbstractUser): """Custom user model extending AbstractUser.""" email = models.EmailField(unique=True) phone = models.CharField(max_length=20, blank=True) birth_date = models.DateField(null=True, blank=True) USERNAME_FIELD = 'email' REQUIRED_FIELDS = ['username'] class Meta: db_table = 'users' verbose_name = 'user' verbose_name_plural = 'users' ordering = ['-date_joined'] def __str__(self): return self.email def get_full_name(self): return f"{self.first_name} {self.last_name}".strip() class Product(models.Model): """Product model with proper field configuration.""" name = models.CharField(max_length=200) slug = models.SlugField(unique=True, max_length=250) description = models.TextField(blank=True) price = models.DecimalField( max_digits=10, decimal_places=2, validators=[MinValueValidator(0)] ) stock = models.PositiveIntegerField(default=0) is_active = models.BooleanField(default=True) category = models.ForeignKey( 'Category', on_delete=models.CASCADE, related_name='products' ) tags = models.ManyToManyField('Tag', blank=True, related_name='products') created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: db_table = 'products' ordering = ['-created_at'] indexes = [ models.Index(fields=['slug']), models.Index(fields=['-created_at']), models.Index(fields=['category', 'is_active']), ] constraints = [ models.CheckConstraint( check=models.Q(price__gte=0), name='price_non_negative' ) ] def __str__(self): return self.name def save(self, *args, **kwargs): if not self.slug: self.slug = slugify(self.name) super().save(*args, **kwargs)
pythonfrom django.db import models class ProductQuerySet(models.QuerySet): """Custom QuerySet for Product model.""" def active(self): """Return only active products.""" return self.filter(is_active=True) def with_category(self): """Select related category to avoid N+1 queries.""" return self.select_related('category') def with_tags(self): """Prefetch tags for many-to-many relationship.""" return self.prefetch_related('tags') def in_stock(self): """Return products with stock > 0.""" return self.filter(stock__gt=0) def search(self, query): """Search products by name or description.""" return self.filter( models.Q(name__icontains=query) | models.Q(description__icontains=query) ) class Product(models.Model): # ... fields ... objects = ProductQuerySet.as_manager() # Use custom QuerySet # Usage Product.objects.active().with_category().in_stock()
pythonclass ProductManager(models.Manager): """Custom manager for complex queries.""" def get_or_none(self, **kwargs): """Return object or None instead of DoesNotExist.""" try: return self.get(**kwargs) except self.model.DoesNotExist: return None def create_with_tags(self, name, price, tag_names): """Create product with associated tags.""" product = self.create(name=name, price=price) tags = [Tag.objects.get_or_create(name=name)[0] for name in tag_names] product.tags.set(tags) return product def bulk_update_stock(self, product_ids, quantity): """Bulk update stock for multiple products.""" return self.filter(id__in=product_ids).update(stock=quantity) # In model class Product(models.Model): # ... fields ... custom = ProductManager()
pythonfrom rest_framework import serializers from django.contrib.auth.password_validation import validate_password from .models import Product, User class ProductSerializer(serializers.ModelSerializer): """Serializer for Product model.""" category_name = serializers.CharField(source='category.name', read_only=True) average_rating = serializers.FloatField(read_only=True) discount_price = serializers.SerializerMethodField() class Meta: model = Product fields = [ 'id', 'name', 'slug', 'description', 'price', 'discount_price', 'stock', 'category_name', 'average_rating', 'created_at' ] read_only_fields = ['id', 'slug', 'created_at'] def get_discount_price(self, obj): """Calculate discount price if applicable.""" if hasattr(obj, 'discount') and obj.discount: return obj.price * (1 - obj.discount.percent / 100) return obj.price def validate_price(self, value): """Ensure price is non-negative.""" if value < 0: raise serializers.ValidationError("Price cannot be negative.") return value class ProductCreateSerializer(serializers.ModelSerializer): """Serializer for creating products.""" class Meta: model = Product fields = ['name', 'description', 'price', 'stock', 'category'] def validate(self, data): """Custom validation for multiple fields.""" if data['price'] > 10000 and data['stock'] > 100: raise serializers.ValidationError( "Cannot have high-value products with large stock." ) return data class UserRegistrationSerializer(serializers.ModelSerializer): """Serializer for user registration.""" password = serializers.CharField( write_only=True, required=True, validators=[validate_password], style={'input_type': 'password'} ) password_confirm = serializers.CharField(write_only=True, style={'input_type': 'password'}) class Meta: model = User fields = ['email', 'username', 'password', 'password_confirm'] def validate(self, data): """Validate passwords match.""" if data['password'] != data['password_confirm']: raise serializers.ValidationError({ "password_confirm": "Password fields didn't match." }) return data def create(self, validated_data): """Create user with hashed password.""" validated_data.pop('password_confirm') password = validated_data.pop('password') user = User.objects.create(**validated_data) user.set_password(password) user.save() return user
pythonfrom rest_framework import viewsets, status, filters from rest_framework.decorators import action from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated, IsAdminUser from django_filters.rest_framework import DjangoFilterBackend from .models import Product from .serializers import ProductSerializer, ProductCreateSerializer from .permissions import IsOwnerOrReadOnly from .filters import ProductFilter from .services import ProductService class ProductViewSet(viewsets.ModelViewSet): """ViewSet for Product model.""" queryset = Product.objects.select_related('category').prefetch_related('tags') permission_classes = [IsAuthenticated, IsOwnerOrReadOnly] filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] filterset_class = ProductFilter search_fields = ['name', 'description'] ordering_fields = ['price', 'created_at', 'name'] ordering = ['-created_at'] def get_serializer_class(self): """Return appropriate serializer based on action.""" if self.action == 'create': return ProductCreateSerializer return ProductSerializer def perform_create(self, serializer): """Save with user context.""" serializer.save(created_by=self.request.user) @action(detail=False, methods=['get']) def featured(self, request): """Return featured products.""" featured = self.queryset.filter(is_featured=True)[:10] serializer = self.get_serializer(featured, many=True) return Response(serializer.data) @action(detail=True, methods=['post']) def purchase(self, request, pk=None): """Purchase a product.""" product = self.get_object() service = ProductService() result = service.purchase(product, request.user) return Response(result, status=status.HTTP_201_CREATED) @action(detail=False, methods=['get'], permission_classes=[IsAuthenticated]) def my_products(self, request): """Return products created by current user.""" products = self.queryset.filter(created_by=request.user) page = self.paginate_queryset(products) serializer = self.get_serializer(page, many=True) return self.get_paginated_response(serializer.data)
pythonfrom rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response @api_view(['POST']) @permission_classes([IsAuthenticated]) def add_to_cart(request): """Add product to user cart.""" product_id = request.data.get('product_id') quantity = request.data.get('quantity', 1) try: product = Product.objects.get(id=product_id) except Product.DoesNotExist: return Response( {'error': 'Product not found'}, status=status.HTTP_404_NOT_FOUND ) cart, _ = Cart.objects.get_or_create(user=request.user) CartItem.objects.create( cart=cart, product=product, quantity=quantity ) return Response({'message': 'Added to cart'}, status=status.HTTP_201_CREATED)
python# 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.""" # Integration with payment gateway payment = PaymentGateway.charge( amount=order.total_price, token=payment_data['token'] ) if payment.success: order.status = Order.Status.PAID order.save() # Send confirmation email OrderService.send_confirmation_email(order) return True return False @staticmethod def send_confirmation_email(order: Order): """Send order confirmation email.""" # Email sending logic pass
pythonfrom 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'
django{% load cache %} {% cache 500 sidebar %} ... expensive sidebar content ... {% endcache %}
pythonfrom 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
pythonfrom 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
python# 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
python# 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: # Update last active time 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
python# 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)
pythonclass 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']), ]
python# 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 |
Remember: Django provides many shortcuts, but for production applications, structure and organization matter more than concise code. Build for maintainability.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-02 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of -67 percentage points is the difference between those two pass rates over the 22 comparable cases. 2 cases got worse with the skill loaded, and they are included in that figure.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.