django-patterns

Modern Django 5.x patterns and best practices.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "django-patterns" with this command: npx skills add peopleforrester/claude-dotfiles/peopleforrester-claude-dotfiles-django-patterns

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

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

api-designer

No summary provided by upstream source.

Repository SourceNeeds Review
General

golang-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
General

fastapi-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
General

api-docs

No summary provided by upstream source.

Repository SourceNeeds Review