|$ curl https://forge-ai.dev/api/markdown?path=docs/python/django
$cat docs/django.md
updated Today·22-28 min read·published
Django
Introduction
Django is the batteries-included web framework: ORM, admin, auth, templates, and migrations. Use it for full applications; prefer FastAPI for pure JSON APIs unless you need Django Admin/ORM ecosystem.
ℹ
info
Create a project: django-admin startproject config . then python manage.py startapp shop.
Models
models.py
Python
| 1 | from django.db import models |
| 2 | |
| 3 | class Author(models.Model): |
| 4 | name = models.CharField(max_length=120) |
| 5 | email = models.EmailField(unique=True) |
| 6 | |
| 7 | def __str__(self) -> str: |
| 8 | return self.name |
| 9 | |
| 10 | class Book(models.Model): |
| 11 | class Status(models.TextChoices): |
| 12 | DRAFT = "draft", "Draft" |
| 13 | PUB = "pub", "Published" |
| 14 | |
| 15 | title = models.CharField(max_length=200) |
| 16 | author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name="books") |
| 17 | status = models.CharField(max_length=10, choices=Status.choices, default=Status.DRAFT) |
| 18 | published_at = models.DateTimeField(null=True, blank=True) |
| 19 | created_at = models.DateTimeField(auto_now_add=True) |
| 20 | |
| 21 | class Meta: |
| 22 | indexes = [models.Index(fields=["status", "published_at"])] |
| 23 | ordering = ["-created_at"] |
ORM Queries
orm.py
Python
| 1 | # from shop.models import Author, Book |
| 2 | # Book.objects.filter(status=Book.Status.PUB).select_related("author") |
| 3 | # Author.objects.prefetch_related("books").all() |
| 4 | # Book.objects.filter(title__icontains="python").count() |
| 5 | # Book.objects.create(title="X", author=author) |
| 6 | # |
| 7 | # from django.db.models import Count, Q |
| 8 | # Author.objects.annotate(n=Count("books")).filter(n__gte=1) |
| 9 | # Book.objects.filter(Q(status="pub") | Q(title__startswith="A")) |
Prefer select_related for FK/OneToOne and prefetch_related for reverse/M2M to avoid N+1 queries.
Views — FBV & CBV
views.py
Python
| 1 | from django.http import JsonResponse, HttpRequest, HttpResponse |
| 2 | from django.shortcuts import get_object_or_404, render |
| 3 | from django.views import View |
| 4 | from .models import Book |
| 5 | |
| 6 | def book_list(request: HttpRequest) -> HttpResponse: |
| 7 | books = Book.objects.select_related("author")[:50] |
| 8 | return render(request, "shop/book_list.html", {"books": books}) |
| 9 | |
| 10 | def book_detail_api(request: HttpRequest, pk: int) -> JsonResponse: |
| 11 | book = get_object_or_404(Book.objects.select_related("author"), pk=pk) |
| 12 | return JsonResponse({"id": book.pk, "title": book.title, "author": book.author.name}) |
| 13 | |
| 14 | class BookCreateView(View): |
| 15 | def post(self, request: HttpRequest) -> JsonResponse: |
| 16 | # validate POST / JSON then Book.objects.create(...) |
| 17 | return JsonResponse({"ok": True}, status=201) |
URLconf
urls.py
Python
| 1 | from django.urls import path |
| 2 | from . import views |
| 3 | |
| 4 | app_name = "shop" |
| 5 | urlpatterns = [ |
| 6 | path("books/", views.book_list, name="book-list"), |
| 7 | path("api/books/<int:pk>/", views.book_detail_api, name="book-api"), |
| 8 | ] |
Migrations
migrations.sh
Bash
| 1 | python manage.py makemigrations |
| 2 | python manage.py migrate |
| 3 | python manage.py showmigrations |
| 4 | python manage.py sqlmigrate shop 0001 |
✓
best practice
Never edit applied migrations in shared branches — add a new migration instead.
Admin
admin.py
Python
| 1 | from django.contrib import admin |
| 2 | from .models import Author, Book |
| 3 | |
| 4 | @admin.register(Author) |
| 5 | class AuthorAdmin(admin.ModelAdmin): |
| 6 | list_display = ("name", "email") |
| 7 | search_fields = ("name", "email") |
| 8 | |
| 9 | @admin.register(Book) |
| 10 | class BookAdmin(admin.ModelAdmin): |
| 11 | list_display = ("title", "author", "status", "published_at") |
| 12 | list_filter = ("status",) |
| 13 | autocomplete_fields = ("author",) |
| 14 | prepopulated_fields = {"title": ("title",)} # example; usually slug |
Django REST Framework Overview
DRF adds serializers, viewsets, and browsable APIs on top of Django.
drf.py
Python
| 1 | # pip install djangorestframework |
| 2 | from rest_framework import serializers, viewsets |
| 3 | from .models import Book |
| 4 | |
| 5 | class BookSerializer(serializers.ModelSerializer): |
| 6 | class Meta: |
| 7 | model = Book |
| 8 | fields = ("id", "title", "status", "author") |
| 9 | |
| 10 | class BookViewSet(viewsets.ModelViewSet): |
| 11 | queryset = Book.objects.select_related("author").all() |
| 12 | serializer_class = BookSerializer |
| 13 | |
| 14 | # router.register("books", BookViewSet) |
Settings Patterns
settings_snip.py
Python
| 1 | # config/settings.py (excerpt) |
| 2 | from pathlib import Path |
| 3 | import os |
| 4 | |
| 5 | BASE_DIR = Path(__file__).resolve().parent.parent |
| 6 | SECRET_KEY = os.environ["DJANGO_SECRET_KEY"] |
| 7 | DEBUG = os.getenv("DJANGO_DEBUG", "0") == "1" |
| 8 | ALLOWED_HOSTS = os.getenv("DJANGO_ALLOWED_HOSTS", "localhost").split(",") |
| 9 | DATABASES = { |
| 10 | "default": { |
| 11 | "ENGINE": "django.db.backends.postgresql", |
| 12 | "NAME": os.getenv("POSTGRES_DB", "app"), |
| 13 | "USER": os.getenv("POSTGRES_USER", "app"), |
| 14 | "PASSWORD": os.getenv("POSTGRES_PASSWORD", ""), |
| 15 | "HOST": os.getenv("POSTGRES_HOST", "localhost"), |
| 16 | "PORT": os.getenv("POSTGRES_PORT", "5432"), |
| 17 | } |
| 18 | } |
Django vs FastAPI
| Need | Prefer |
|---|---|
| Admin + CMS-like CRUD | Django |
| Async JSON microservice | FastAPI |
| Batteries-included auth/sessions | Django |
| OpenAPI-first micro APIs | FastAPI |
| Complex relational domain + forms | Django |
Forms (brief)
forms.py
Python
| 1 | from django import forms |
| 2 | from .models import Book |
| 3 | |
| 4 | class BookForm(forms.ModelForm): |
| 5 | class Meta: |
| 6 | model = Book |
| 7 | fields = ("title", "author", "status") |
| 8 | |
| 9 | # if request.method == "POST": |
| 10 | # form = BookForm(request.POST) |
| 11 | # if form.is_valid(): |
| 12 | # form.save() |
Testing
test_django.py
Python
| 1 | from django.test import TestCase, Client |
| 2 | from shop.models import Author, Book |
| 3 | |
| 4 | class BookTests(TestCase): |
| 5 | def setUp(self): |
| 6 | self.author = Author.objects.create(name="Ada", email="a@e.com") |
| 7 | self.book = Book.objects.create(title="Notes", author=self.author) |
| 8 | |
| 9 | def test_list(self): |
| 10 | c = Client() |
| 11 | r = c.get("/books/") |
| 12 | self.assertEqual(r.status_code, 200) |
Middleware & Security
- Keep DEBUG=False in production
- Set SECURE_PROXY_SSL_HEADER / CSRF_TRUSTED_ORIGINS behind proxies
- Use django-environ or pydantic-settings for secrets
- Prefer get_object_or_404 over raw try/except DoesNotExist in views
Signals (use sparingly)
signals.py
Python
| 1 | from django.db.models.signals import post_save |
| 2 | from django.dispatch import receiver |
| 3 | from .models import Book |
| 4 | |
| 5 | @receiver(post_save, sender=Book) |
| 6 | def book_saved(sender, instance: Book, created: bool, **kwargs): |
| 7 | if created: |
| 8 | print("new book", instance.pk) |
⚠
warning
Signals hide control flow. Prefer explicit service functions when possible.
Auth Overview
auth.py
Python
| 1 | from django.contrib.auth.decorators import login_required |
| 2 | from django.contrib.auth.mixins import LoginRequiredMixin |
| 3 | from django.views.generic import ListView |
| 4 | from .models import Book |
| 5 | |
| 6 | @login_required |
| 7 | def private(request): |
| 8 | return render(request, "private.html") |
| 9 | |
| 10 | class MyBooks(LoginRequiredMixin, ListView): |
| 11 | model = Book |
| 12 | template_name = "my_books.html" |
Management Commands
command.py
Python
| 1 | from django.core.management.base import BaseCommand |
| 2 | from shop.models import Book |
| 3 | |
| 4 | class Command(BaseCommand): |
| 5 | help = "List books" |
| 6 | |
| 7 | def add_arguments(self, parser): |
| 8 | parser.add_argument("--status", default="pub") |
| 9 | |
| 10 | def handle(self, *args, **options): |
| 11 | qs = Book.objects.filter(status=options["status"]) |
| 12 | for b in qs: |
| 13 | self.stdout.write(b.title) |
Production Checklist
- DEBUG=False, SECRET_KEY from env
- ALLOWED_HOSTS / CSRF_TRUSTED_ORIGINS set
- Migrations applied in deploy
- Static files via WhiteNoise or CDN
- CONN_MAX_AGE for DB pooling where appropriate
$Blueprint — Engineering Documentation·Section ID: PYTHON-DJANGO·Revision: 1.0
Community
Get help on Slack, Discord or VIP
Stuck on a guide? Join the community and ask.