# Django

> Django 5+ framework guardrails, patterns, and best practices for AI-assisted development. Use when working with Django projects, or when the user mentions Django. Provides ORM patterns, views, templates, DRF, admin, and security guidelines.

- Skill: `majiayu000/django-2` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds add majiayu000/django-2`
- Raw SKILL.md: https://api.skillmd.com/api/skills/majiayu000/django-2/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- License: MIT
- Author: majiayu000 (https://skillmd.com/u/majiayu000)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/majiayu000/django-2

---


# Django Framework Guide

> Applies to: Django 5+, Django REST Framework, Django Channels
> Language: Python 3.10+

## Core Principles

1. **Batteries Included**: Leverage Django's built-in features before adding third-party packages
2. **DRY**: Don't Repeat Yourself -- use abstract models, mixins, and shared utilities
3. **Fat Models, Thin Views**: Keep business logic in models and services, not views
4. **Explicit Over Implicit**: Use clear URL patterns, explicit imports, named relationships
5. **Security by Default**: CSRF, XSS protection, SQL injection prevention are built in

## When to Use Django

**Good fit:**
- Full-stack web applications with templates
- Admin interface needed out of the box
- Content management systems
- Session-based authentication
- ORM with built-in migrations

**Consider alternatives:**
- Pure APIs with async-first needs (FastAPI)
- Minimal framework overhead (Flask)
- Real-time heavy workloads without Channels

## Project Structure

```
myproject/
├── manage.py
├── pyproject.toml
├── requirements/
│   ├── base.txt
│   ├── dev.txt
│   └── prod.txt
├── config/                    # Project configuration
│   ├── __init__.py
│   ├── settings/
│   │   ├── __init__.py
│   │   ├── base.py
│   │   ├── dev.py
│   │   └── prod.py
│   ├── urls.py
│   ├── wsgi.py
│   └── asgi.py
├── apps/                      # Django applications
│   ├── users/
│   │   ├── __init__.py
│   │   ├── admin.py
│   │   ├── apps.py
│   │   ├── models.py
│   │   ├── views.py
│   │   ├── urls.py
│   │   ├── forms.py
│   │   ├── serializers.py     # If using DRF
│   │   ├── services.py        # Business logic
│   │   └── tests/
│   │       ├── __init__.py
│   │       ├── test_models.py
│   │       ├── test_views.py
│   │       └── test_services.py
│   └── core/                  # Shared utilities
│       ├── __init__.py
│       ├── models.py          # Abstract base models
│       └── mixins.py
├── templates/
│   ├── base.html
│   └── components/
├── static/
│   ├── css/
│   └── js/
└── tests/
    └── conftest.py
```

- Split settings into `base.py`, `dev.py`, `prod.py`
- Group apps under `apps/` directory
- Keep `core/` app for shared abstract models and utilities
- Place business logic in `services.py`, not in views
- Co-locate tests inside each app under `tests/` directory

## Guardrails

### Settings

- Never hardcode `SECRET_KEY` -- use `python-decouple` or environment variables
- Split settings: `base.py` (shared), `dev.py` (debug), `prod.py` (secure)
- Always define `AUTH_USER_MODEL` before first migration
- Set `DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"`
- Use `ALLOWED_HOSTS` in production (never `["*"]`)

### Models

- Always define `__str__` on every model
- Always set `class Meta` with `db_table`, `ordering`, and `indexes`
- Use abstract base models for shared fields (`TimeStampedModel`, `UUIDModel`)
- Use `TextChoices`/`IntegerChoices` for status fields (not raw strings)
- Add `related_name` to all ForeignKey and OneToOneField relationships
- Use `on_delete` explicitly: `CASCADE`, `PROTECT`, `SET_NULL`, `SET_DEFAULT`
- Add database indexes for frequently queried fields
- Use `validators` at model level for domain constraints

### Abstract Base Models

```python
# apps/core/models.py
from django.db import models
import uuid


class TimeStampedModel(models.Model):
    """Abstract base with created/updated timestamps."""
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True


class UUIDModel(models.Model):
    """Abstract base with UUID primary key."""
    id = models.UUIDField(
        primary_key=True, default=uuid.uuid4, editable=False
    )

    class Meta:
        abstract = True
```

### Custom User Model

- Always create a custom user model before the first migration
- Extend `AbstractUser` (not `AbstractBaseUser` unless you need full control)
- Set `AUTH_USER_MODEL = "users.User"` in settings

```python
# apps/users/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
from apps.core.models import TimeStampedModel, UUIDModel


class User(AbstractUser, UUIDModel, TimeStampedModel):
    email = models.EmailField(unique=True)
    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = ["username"]

    class Meta:
        db_table = "users"
        ordering = ["-created_at"]
        indexes = [models.Index(fields=["email"])]

    def __str__(self) -> str:
        return self.email
```

## Views and URLs

### Class-Based Views (CBVs)

- Use `ListView`, `DetailView`, `CreateView`, `UpdateView` for CRUD
- Use `LoginRequiredMixin` for authenticated views
- Override `get_queryset()` to add filtering and `select_related`/`prefetch_related`
- Override `form_valid()` to inject the current user

```python
from django.views.generic import ListView
from django.db.models import Q
from .models import Product


class ProductListView(ListView):
    model = Product
    template_name = "products/list.html"
    context_object_name = "products"
    paginate_by = 20

    def get_queryset(self):
        qs = Product.objects.filter(
            status=Product.Status.PUBLISHED
        )
        search = self.request.GET.get("q")
        if search:
            qs = qs.filter(
                Q(name__icontains=search)
                | Q(description__icontains=search)
            )
        return qs.select_related("category")
```

### URL Configuration

```python
# config/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("api/", include("apps.api.urls")),
    path("", include("apps.products.urls")),
]
```

- Use `include()` for app-level URL namespaces
- Use `app_name` in each app's `urls.py` for reverse resolution
- Serve media files in DEBUG mode only

## ORM Essentials

### Query Optimization

- Always use `select_related()` for ForeignKey/OneToOne (SQL JOIN)
- Always use `prefetch_related()` for ManyToMany/reverse FK (separate query)
- Use `only()` or `defer()` to limit fields when not all columns needed
- Never call `Model.objects.all()` without pagination or limits
- Use `F()` expressions for database-level operations
- Use `Q()` objects for complex lookups

### Avoiding N+1 Queries

```python
# BAD: N+1 queries
for product in Product.objects.all():
    print(product.category.name)  # Extra query per product

# GOOD: Single JOIN query
for product in Product.objects.select_related("category"):
    print(product.category.name)  # No extra queries
```

### Transactions

- Use `@transaction.atomic` for multi-step writes
- Use `select_for_update()` for optimistic locking

```python
from django.db import transaction

@transaction.atomic
def transfer_stock(source_id, dest_id, qty):
    source = Product.objects.select_for_update().get(id=source_id)
    dest = Product.objects.select_for_update().get(id=dest_id)
    source.stock -= qty
    dest.stock += qty
    source.save(update_fields=["stock"])
    dest.save(update_fields=["stock"])
```

## Admin Configuration

```python
from django.contrib import admin
from .models import Product

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    list_display = ["name", "category", "price", "status"]
    list_filter = ["status", "category", "created_at"]
    search_fields = ["name", "description"]
    prepopulated_fields = {"slug": ("name",)}
    readonly_fields = ["created_at", "updated_at"]
    ordering = ["-created_at"]

    fieldsets = (
        (None, {"fields": ("name", "slug", "description")}),
        ("Pricing", {"fields": ("price", "stock")}),
        ("Classification", {"fields": ("category", "status")}),
        ("Timestamps", {
            "fields": ("created_at", "updated_at"),
            "classes": ("collapse",),
        }),
    )
```

- Always register models with `@admin.register(Model)`
- Use `list_display` for useful columns, `list_filter` for filtering
- Use `prepopulated_fields` for slug generation
- Group fields with `fieldsets` for organized admin forms

## Django REST Framework (DRF)

### Settings

```python
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "rest_framework.authentication.SessionAuthentication",
        "rest_framework_simplejwt.authentication.JWTAuthentication",
    ],
    "DEFAULT_PERMISSION_CLASSES": [
        "rest_framework.permissions.IsAuthenticated",
    ],
    "DEFAULT_PAGINATION_CLASS": (
        "rest_framework.pagination.PageNumberPagination"
    ),
    "PAGE_SIZE": 20,
    "DEFAULT_THROTTLE_RATES": {
        "anon": "100/hour",
        "user": "1000/hour",
    },
}
```

### ViewSets and Routers

- Use `ModelViewSet` for full CRUD
- Use `@action(detail=True/False)` for custom endpoints
- Override `get_serializer_class()` for different read/write serializers
- Override `get_queryset()` to add `select_related`/`prefetch_related`

```python
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response


class ProductViewSet(viewsets.ModelViewSet):
    queryset = Product.objects.select_related("category")
    permission_classes = [IsAuthenticatedOrReadOnly]

    def get_serializer_class(self):
        if self.action in ["create", "update", "partial_update"]:
            return ProductCreateSerializer
        return ProductSerializer

    def perform_create(self, serializer):
        serializer.save(created_by=self.request.user)

    @action(detail=True, methods=["post"])
    def publish(self, request, pk=None):
        product = self.get_object()
        product.status = Product.Status.PUBLISHED
        product.save(update_fields=["status"])
        return Response({"status": "published"})
```

### Serializers

- Use `ModelSerializer` for standard CRUD
- Add `read_only_fields` for computed/auto fields
- Validate in `validate_<field>()` or `validate()` methods
- Use nested serializers for read, flat IDs for write

## Security Essentials

- CSRF: Enabled by default for forms; use `@csrf_exempt` sparingly
- XSS: Django templates auto-escape by default; never use `|safe` with user data
- SQL Injection: ORM uses parameterized queries; never use raw SQL with string formatting
- Clickjacking: `X-Frame-Options` middleware enabled by default
- HTTPS: Set `SECURE_SSL_REDIRECT = True` in production
- HSTS: Set `SECURE_HSTS_SECONDS` in production
- Cookies: Set `SESSION_COOKIE_SECURE = True` and `CSRF_COOKIE_SECURE = True`
- Passwords: Use `AUTH_PASSWORD_VALIDATORS` (enabled by default)
- CORS: Use `django-cors-headers` with explicit allowed origins (never `CORS_ALLOW_ALL_ORIGINS` in production)

## Testing

### Standards

- Use `pytest` with `pytest-django` (not Django's built-in test runner)
- Mark database tests with `@pytest.mark.django_db`
- Use `factory-boy` or fixtures for test data
- Use `APIClient` for DRF endpoint testing
- Coverage target: >80% for business logic
- Test file naming: `test_models.py`, `test_views.py`, `test_services.py`

### Fixtures

```python
# conftest.py
import pytest
from rest_framework.test import APIClient
from apps.users.models import User


@pytest.fixture
def api_client():
    return APIClient()


@pytest.fixture
def user(db):
    return User.objects.create_user(
        username="testuser",
        email="test@example.com",
        password="testpass123",
    )


@pytest.fixture
def authenticated_client(api_client, user):
    api_client.force_authenticate(user=user)
    return api_client
```

## Commands Reference

```bash
# Development
python manage.py runserver
python manage.py shell_plus          # django-extensions

# Migrations
python manage.py makemigrations
python manage.py migrate
python manage.py showmigrations

# Testing
pytest
pytest -v --cov=apps --cov-report=html
pytest apps/products/ -k "test_create"

# Database
python manage.py dbshell
python manage.py dumpdata products > fixtures/products.json
python manage.py loaddata fixtures/products.json

# Static files
python manage.py collectstatic

# Celery
celery -A config worker -l info
celery -A config beat -l info
```

## Dependencies

**Base**: `Django>=5.0`, `djangorestframework`, `django-cors-headers`, `django-filter`, `djangorestframework-simplejwt`, `python-decouple`, `psycopg2-binary`, `whitenoise`

**Dev**: `pytest`, `pytest-django`, `pytest-cov`, `factory-boy`, `django-debug-toolbar`, `django-extensions`, `black`, `ruff`, `mypy`, `django-stubs`

## Best Practices

### Do

- Use `select_related` and `prefetch_related` for every queryset
- Create indexes for frequently queried fields
- Use `@transaction.atomic` for multi-step operations
- Validate at both model level and serializer level
- Write services for business logic (not in views)
- Use signals sparingly (prefer explicit service calls)
- Cache expensive queries with Django cache framework
- Use environment variables for all configuration

### Don't

- Put business logic in views
- Use raw SQL without parameterization
- Ignore N+1 query problems
- Store sensitive data in settings files
- Use `Model.objects.all()` without limits
- Skip migrations in production
- Use `CORS_ALLOW_ALL_ORIGINS = True` in production
- Use `|safe` template filter with user-provided data

## Advanced Topics

For detailed code examples and advanced patterns, see:

- [references/patterns.md](references/patterns.md) -- DRF serializers, middleware, services, signals, Celery tasks, management commands, deployment, and testing patterns

## External References

- [Django Documentation](https://docs.djangoproject.com/)
- [Django REST Framework](https://www.django-rest-framework.org/)
- [Two Scoops of Django](https://www.feldroy.com/books/two-scoops-of-django-3-x)
- [Django Best Practices](https://django-best-practices.readthedocs.io/)

