django-patterns

Production-grade Django architecture patterns for scalable, maintainable applications. Covers project structure with split settings (base, development, production, test), custom managers and QuerySets, and service layer separation for business logic Includes Django REST Framework patterns: serializers with validation, ViewSets with custom actions, filtering, searching, and pagination Demonstrates ORM best practices: select_related and prefetch_related for N+1 prevention, bulk operations, database indexing, and constraint definitions Provides caching strategies at view, template fragment, and low-level cache layers, plus signal patterns for event-driven actions and custom middleware for request logging and user tracking

INSTALLATION
npx skills add https://github.com/affaan-m/everything-claude-code --skill django-patterns
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

Django Development Patterns

Production-grade Django architecture patterns for scalable, maintainable applications.

When to Activate

  • Building Django web applications
  • Designing Django REST Framework APIs
  • Working with Django ORM and models
  • Setting up Django project structure
  • Implementing caching, signals, middleware

Project Structure

Recommended Layout

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/

        └── ...

Split Settings Pattern

# 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,

        },

    },

}

Model Design Patterns

Model Best Practices

from 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)

QuerySet Best Practices

from 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()

Manager Methods

class 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()

Django REST Framework Patterns

Serializer Patterns

from 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

ViewSet Patterns

from 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)

Custom Actions

from 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)

Service Layer Pattern

# 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

Caching Strategies

View-Level Caching

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'

Template Fragment Caching

{% load cache %}

{% cache 500 sidebar %}

    ... expensive sidebar content ...

{% endcache %}

Low-Level Caching

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

QuerySet Caching

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

Signals

Signal Patterns

# 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

Custom Middleware

# 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

Performance Optimization

N+1 Query Prevention

# 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)

Database Indexing

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 Operations

# 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()

Quick Reference

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.

BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card