django-patterns

SKILL.md

Django Patterns

Modern Django 5.x patterns and best practices.

Project Structure

project/
├── config/                 # Project settings (renamed from project/)
│   ├── settings/
│   │   ├── base.py        # Shared settings
│   │   ├── development.py # Dev overrides
│   │   └── production.py  # Prod overrides
│   ├── urls.py
│   └── wsgi.py
├── apps/
│   └── users/
│       ├── models.py
│       ├── views.py
│       ├── serializers.py  # DRF serializers
│       ├── urls.py
│       ├── services.py     # Business logic
│       ├── selectors.py    # Complex queries
│       └── tests/
│           ├── test_models.py
│           ├── test_views.py
│           └── factories.py
└── manage.py

ORM Optimization

Select Related (FK joins)

# Bad: N+1 queries
users = User.objects.all()
for user in users:
    print(user.profile.bio)  # Extra query per user

# Good: Single JOIN query
users = User.objects.select_related("profile").all()

Prefetch Related (M2M and reverse FK)

# Bad: N+1 queries for many-to-many
articles = Article.objects.all()
for article in articles:
    print(article.tags.all())  # Extra query per article

# Good: Two queries total (articles + tags)
articles = Article.objects.prefetch_related("tags").all()

Only/Defer for Partial Loading

# Load only needed fields
users = User.objects.only("id", "name", "email")

# Exclude heavy fields
articles = Article.objects.defer("body", "raw_html")

Efficient Bulk Operations

# Bulk create (single INSERT)
User.objects.bulk_create([
    User(name="Alice", email="alice@example.com"),
    User(name="Bob", email="bob@example.com"),
], batch_size=1000)

# Bulk update (single UPDATE)
User.objects.filter(is_active=False).update(is_active=True)

# F() expressions for atomic updates
from django.db.models import F
Product.objects.filter(id=product_id).update(stock=F("stock") - 1)

Custom QuerySet Managers

class ArticleQuerySet(models.QuerySet):
    def published(self):
        return self.filter(status="published", published_at__lte=timezone.now())

    def by_author(self, user):
        return self.filter(author=user)

class Article(models.Model):
    objects = ArticleQuerySet.as_manager()

# Chain: Article.objects.published().by_author(user)

Views

Class-Based Views (Generic)

from django.views.generic import ListView, DetailView, CreateView

class ArticleListView(ListView):
    model = Article
    queryset = Article.objects.published().select_related("author")
    paginate_by = 20
    template_name = "articles/list.html"

Django REST Framework ViewSets

from rest_framework import viewsets, permissions, status
from rest_framework.decorators import action
from rest_framework.response import Response

class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes = [permissions.IsAuthenticated]

    def get_queryset(self):
        return super().get_queryset().filter(is_active=True)

    @action(detail=True, methods=["post"])
    def deactivate(self, request, pk=None):
        user = self.get_object()
        user.is_active = False
        user.save(update_fields=["is_active"])
        return Response(status=status.HTTP_204_NO_CONTENT)

DRF Serializers

from rest_framework import serializers

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ["id", "name", "email", "created_at"]
        read_only_fields = ["id", "created_at"]

    def validate_email(self, value):
        if User.objects.filter(email=value).exclude(pk=self.instance and self.instance.pk).exists():
            raise serializers.ValidationError("Email already in use.")
        return value

Service Layer

# apps/users/services.py
from django.db import transaction

class UserService:
    @staticmethod
    @transaction.atomic
    def create_user(*, name: str, email: str, role: str = "viewer") -> User:
        user = User.objects.create(name=name, email=email, role=role)
        Profile.objects.create(user=user)
        AuditLog.objects.create(action="user_created", target_id=user.id)
        return user

Middleware

import time
import logging

logger = logging.getLogger(__name__)

class RequestTimingMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        start = time.perf_counter()
        response = self.get_response(request)
        elapsed = time.perf_counter() - start
        logger.info("%s %s %.4fs", request.method, request.path, elapsed)
        response["X-Process-Time"] = f"{elapsed:.4f}"
        return response

Signals (Use Sparingly)

from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance)

Prefer service layer over signals — signals create hidden coupling and are difficult to debug. Use signals only for cross-app decoupling where direct imports would create circular dependencies.

Model Best Practices

import uuid
from django.db import models

class TimeStampedModel(models.Model):
    """Abstract base with UUID primary key and timestamps."""
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True

class User(TimeStampedModel):
    name = models.CharField(max_length=100)
    email = models.EmailField(unique=True)
    is_active = models.BooleanField(default=True)

    class Meta:
        ordering = ["-created_at"]
        indexes = [
            models.Index(fields=["email"]),
            models.Index(fields=["is_active", "-created_at"]),
        ]

    def __str__(self):
        return self.name

Testing

import pytest
from django.test import TestCase
from rest_framework.test import APIClient

class TestUserAPI(TestCase):
    def setUp(self):
        self.client = APIClient()
        self.user = User.objects.create(name="Test", email="test@example.com")
        self.client.force_authenticate(user=self.user)

    def test_list_users(self):
        response = self.client.get("/api/users/")
        self.assertEqual(response.status_code, 200)
        self.assertEqual(len(response.data["results"]), 1)

    def test_create_user(self):
        response = self.client.post("/api/users/", {"name": "New", "email": "new@example.com"})
        self.assertEqual(response.status_code, 201)

Factory Pattern (factory_boy)

import factory

class UserFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = User

    name = factory.Faker("name")
    email = factory.LazyAttribute(lambda o: f"{o.name.lower().replace(' ', '.')}@example.com")
    is_active = True

Checklist

  • select_related for ForeignKey access in loops
  • prefetch_related for ManyToMany access in loops
  • Business logic in service layer, not views
  • F() expressions for atomic field updates
  • transaction.atomic for multi-model writes
  • Custom QuerySet managers for reusable filters
  • UUID primary keys for public-facing IDs
  • Abstract TimeStampedModel base class
  • Database indexes on filtered/sorted fields
  • Tests use force_authenticate or factory patterns
Weekly Installs
3
GitHub Stars
1
First Seen
Feb 28, 2026
Installed on
opencode3
gemini-cli3
github-copilot3
codex3
kimi-cli3
amp3