django
SKILL.md
Django Framework Guide
Applies to: Django 5+, Django REST Framework, Django Channels Language: Python 3.10+
Core Principles
- Batteries Included: Leverage Django's built-in features before adding third-party packages
- DRY: Don't Repeat Yourself -- use abstract models, mixins, and shared utilities
- Fat Models, Thin Views: Keep business logic in models and services, not views
- Explicit Over Implicit: Use clear URL patterns, explicit imports, named relationships
- 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-- usepython-decoupleor environment variables - Split settings:
base.py(shared),dev.py(debug),prod.py(secure) - Always define
AUTH_USER_MODELbefore first migration - Set
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" - Use
ALLOWED_HOSTSin production (never["*"])
Models
- Always define
__str__on every model - Always set
class Metawithdb_table,ordering, andindexes - Use abstract base models for shared fields (
TimeStampedModel,UUIDModel) - Use
TextChoices/IntegerChoicesfor status fields (not raw strings) - Add
related_nameto all ForeignKey and OneToOneField relationships - Use
on_deleteexplicitly:CASCADE,PROTECT,SET_NULL,SET_DEFAULT - Add database indexes for frequently queried fields
- Use
validatorsat model level for domain constraints
Abstract Base Models
# 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(notAbstractBaseUserunless you need full control) - Set
AUTH_USER_MODEL = "users.User"in settings
# 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,UpdateViewfor CRUD - Use
LoginRequiredMixinfor authenticated views - Override
get_queryset()to add filtering andselect_related/prefetch_related - Override
form_valid()to inject the current user
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
# 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_namein each app'surls.pyfor 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()ordefer()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
# 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.atomicfor multi-step writes - Use
select_for_update()for optimistic locking
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
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_displayfor useful columns,list_filterfor filtering - Use
prepopulated_fieldsfor slug generation - Group fields with
fieldsetsfor organized admin forms
Django REST Framework (DRF)
Settings
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
ModelViewSetfor full CRUD - Use
@action(detail=True/False)for custom endpoints - Override
get_serializer_class()for different read/write serializers - Override
get_queryset()to addselect_related/prefetch_related
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
ModelSerializerfor standard CRUD - Add
read_only_fieldsfor computed/auto fields - Validate in
validate_<field>()orvalidate()methods - Use nested serializers for read, flat IDs for write
Security Essentials
- CSRF: Enabled by default for forms; use
@csrf_exemptsparingly - XSS: Django templates auto-escape by default; never use
|safewith user data - SQL Injection: ORM uses parameterized queries; never use raw SQL with string formatting
- Clickjacking:
X-Frame-Optionsmiddleware enabled by default - HTTPS: Set
SECURE_SSL_REDIRECT = Truein production - HSTS: Set
SECURE_HSTS_SECONDSin production - Cookies: Set
SESSION_COOKIE_SECURE = TrueandCSRF_COOKIE_SECURE = True - Passwords: Use
AUTH_PASSWORD_VALIDATORS(enabled by default) - CORS: Use
django-cors-headerswith explicit allowed origins (neverCORS_ALLOW_ALL_ORIGINSin production)
Testing
Standards
- Use
pytestwithpytest-django(not Django's built-in test runner) - Mark database tests with
@pytest.mark.django_db - Use
factory-boyor fixtures for test data - Use
APIClientfor DRF endpoint testing - Coverage target: >80% for business logic
- Test file naming:
test_models.py,test_views.py,test_services.py
Fixtures
# 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
# 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_relatedandprefetch_relatedfor every queryset - Create indexes for frequently queried fields
- Use
@transaction.atomicfor 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 = Truein production - Use
|safetemplate filter with user-provided data
Advanced Topics
For detailed code examples and advanced patterns, see:
- references/patterns.md -- DRF serializers, middleware, services, signals, Celery tasks, management commands, deployment, and testing patterns
External References
Weekly Installs
10
Repository
ar4mirez/samuelGitHub Stars
3
First Seen
Feb 20, 2026
Security Audits
Installed on
amp10
github-copilot10
codex10
kimi-cli10
gemini-cli10
opencode10