Django projects, models, views, templates, forms, authentication, testing, and deployment reference.
Django Developer Guide
Section titled “Django Developer Guide”Django is a batteries-included Python web framework for database-backed websites and applications. It provides URL routing, views, templates, forms, an ORM, migrations, authentication, an administration site, security protections, and testing tools.
Getting Started
Section titled “Getting Started”Installation
Section titled “Installation”Create an isolated environment and install Django:
python -m venv .venv
# macOS/Linuxsource .venv/bin/activate
# PowerShell.\.venv\Scripts\Activate.ps1
python -m pip install Djangopython -m django --versionCreate a Project and App
Section titled “Create a Project and App”A project contains site-wide configuration. An app is a focused feature package such as blog, accounts, or payments.
django-admin startproject config .python manage.py startapp blogpython manage.py migratepython manage.py runserverTypical structure:
manage.pyconfig/ __init__.py settings.py urls.py asgi.py wsgi.pyblog/ admin.py apps.py migrations/ models.py tests.py views.pyRegister the App
Section titled “Register the App”Add the application to INSTALLED_APPS:
INSTALLED_APPS = [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", "blog",]Requests and Responses
Section titled “Requests and Responses”First View
Section titled “First View”from django.http import HttpResponse
def home(request): return HttpResponse("Hello, Django")Map a URL to the view:
from django.urls import path
from . import views
app_name = "blog"
urlpatterns = [ path("", views.home, name="home"),]Include application URLs in the project:
from django.contrib import adminfrom django.urls import include, path
urlpatterns = [ path("admin/", admin.site.urls), path("", include("blog.urls")),]python manage.py runserverOpen http://127.0.0.1:8000/ in a browser.
Route Parameters
Section titled “Route Parameters”urlpatterns = [ path("posts/<int:post_id>/", views.post_detail, name="post-detail"), path("authors/<slug:username>/", views.author_detail, name="author-detail"),]from django.http import JsonResponse
def post_detail(request, post_id): return JsonResponse({"post_id": post_id})Common path converters include str, int, slug, uuid, and path.
Models and Databases
Section titled “Models and Databases”Define Models
Section titled “Define Models”Models describe stored data and relationships:
from django.conf import settingsfrom django.db import modelsfrom django.urls import reverse
class Category(models.Model): name = models.CharField(max_length=100, unique=True) slug = models.SlugField(unique=True)
def __str__(self): return self.name
class Post(models.Model): class Status(models.TextChoices): DRAFT = "draft", "Draft" PUBLISHED = "published", "Published"
title = models.CharField(max_length=200) slug = models.SlugField(unique=True) body = models.TextField() author = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="posts", ) category = models.ForeignKey( Category, on_delete=models.PROTECT, related_name="posts", ) status = models.CharField( max_length=10, choices=Status.choices, default=Status.DRAFT, ) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True)
class Meta: ordering = ["-created_at"]
def __str__(self): return self.title
def get_absolute_url(self): return reverse("blog:post-detail", kwargs={"slug": self.slug})Migrations
Section titled “Migrations”Create and apply migration files whenever model structure changes:
python manage.py makemigrationspython manage.py migratepython manage.py showmigrationspython manage.py sqlmigrate blog 0001Commit migrations with the code change that requires them. Do not manually edit a production database schema instead of applying migrations.
QuerySets
Section titled “QuerySets”Use the Django ORM to create, retrieve, update, and delete rows:
python manage.py shellfrom blog.models import Category, Post
category = Category.objects.create(name="Python", slug="python")
Post.objects.all()Post.objects.filter(status=Post.Status.PUBLISHED)Post.objects.filter(title__icontains="django")Post.objects.get(slug="getting-started")Post.objects.order_by("-created_at")[:5]Post.objects.filter(category__slug="python").count()
Post.objects.filter(status=Post.Status.DRAFT).update(status=Post.Status.PUBLISHED)Post.objects.filter(slug="old-post").delete()Efficient Relationships
Section titled “Efficient Relationships”Reduce repeated database queries when loading related objects:
posts = Post.objects.select_related("author", "category")categories = Category.objects.prefetch_related("posts")Use select_related() for foreign-key or one-to-one relationships and prefetch_related() for many-to-many or reverse relationships.
Templates and Rendering
Section titled “Templates and Rendering”Render HTML
Section titled “Render HTML”from django.shortcuts import get_object_or_404, render
from .models import Post
def post_list(request): posts = Post.objects.filter(status=Post.Status.PUBLISHED) return render(request, "blog/post_list.html", {"posts": posts})
def post_detail(request, slug): post = get_object_or_404(Post, slug=slug, status=Post.Status.PUBLISHED) return render(request, "blog/post_detail.html", {"post": post})urlpatterns = [ path("posts/", views.post_list, name="post-list"), path("posts/<slug:slug>/", views.post_detail, name="post-detail"),]Place templates in an app-scoped directory:
blog/ templates/ blog/ base.html post_list.html post_detail.htmlBase Template
Section titled “Base Template”<!doctype html><html lang="en"> <head> <meta charset="utf-8"> <title>{% block title %}Blog{% endblock %}</title> </head> <body> <nav><a href="{% url 'blog:post-list' %}">Posts</a></nav> <main>{% block content %}{% endblock %}</main> </body></html>{% extends "blog/base.html" %}
{% block title %}Posts{% endblock %}
{% block content %} <h1>Posts</h1> {% for post in posts %} <article> <a href="{{ post.get_absolute_url }}">{{ post.title }}</a> </article> {% empty %} <p>No posts yet.</p> {% endfor %}{% endblock %}Templates escape variable output by default. Avoid marking user-supplied content as safe unless it has been sanitized deliberately.
Forms and Validation
Section titled “Forms and Validation”Model Form
Section titled “Model Form”from django import forms
from .models import Post
class PostForm(forms.ModelForm): class Meta: model = Post fields = ["title", "slug", "body", "category", "status"]
def clean_title(self): title = self.cleaned_data["title"].strip() if len(title) < 5: raise forms.ValidationError("Title must contain at least 5 characters.") return titleCreate View
Section titled “Create View”from django.contrib.auth.decorators import login_requiredfrom django.shortcuts import redirect, render
from .forms import PostForm
@login_requireddef post_create(request): if request.method == "POST": form = PostForm(request.POST) if form.is_valid(): post = form.save(commit=False) post.author = request.user post.save() return redirect(post) else: form = PostForm()
return render(request, "blog/post_form.html", {"form": form}){% extends "blog/base.html" %}
{% block content %} <h1>New post</h1> <form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit">Save</button> </form>{% endblock %}For internal HTML forms that submit POST, include {% csrf_token %} and keep Django’s CSRF middleware enabled.
Admin Site
Section titled “Admin Site”Create an Administrator
Section titled “Create an Administrator”python manage.py createsuperuserpython manage.py runserverVisit http://127.0.0.1:8000/admin/.
Register and Customize Models
Section titled “Register and Customize Models”from django.contrib import admin
from .models import Category, Post
@admin.register(Post)class PostAdmin(admin.ModelAdmin): list_display = ["title", "author", "status", "created_at"] list_filter = ["status", "created_at", "category"] search_fields = ["title", "body"] prepopulated_fields = {"slug": ("title",)} date_hierarchy = "created_at"
@admin.register(Category)class CategoryAdmin(admin.ModelAdmin): prepopulated_fields = {"slug": ("name",)}The admin is excellent for trusted internal management. Build dedicated public views and authorization rules for end users.
Class-Based Views
Section titled “Class-Based Views”Generic views reduce repeated list, detail, create, update, and delete logic:
from django.contrib.auth.mixins import LoginRequiredMixinfrom django.urls import reverse_lazyfrom django.views.generic import CreateView, DetailView, ListView, UpdateView
from .models import Post
class PostListView(ListView): model = Post template_name = "blog/post_list.html" context_object_name = "posts" paginate_by = 20
def get_queryset(self): return Post.objects.filter(status=Post.Status.PUBLISHED)
class PostDetailView(DetailView): model = Post template_name = "blog/post_detail.html"
class PostCreateView(LoginRequiredMixin, CreateView): model = Post fields = ["title", "slug", "body", "category", "status"] success_url = reverse_lazy("blog:post-list")
def form_valid(self, form): form.instance.author = self.request.user return super().form_valid(form)from .views import PostCreateView, PostDetailView, PostListView
urlpatterns = [ path("posts/", PostListView.as_view(), name="post-list"), path("posts/new/", PostCreateView.as_view(), name="post-create"), path("posts/<slug:slug>/", PostDetailView.as_view(), name="post-detail"),]Use function views when they make behavior clearer; use generic views when their lifecycle fits the feature naturally.
Authentication and Permissions
Section titled “Authentication and Permissions”Built-In Authentication URLs
Section titled “Built-In Authentication URLs”urlpatterns = [ path("accounts/", include("django.contrib.auth.urls")), path("", include("blog.urls")), path("admin/", admin.site.urls),]Provide templates such as registration/login.html, then use authentication helpers:
from django.contrib.auth.decorators import login_required, permission_required
@login_requireddef profile(request): ...
@permission_required("blog.change_post", raise_exception=True)def moderate_post(request, post_id): ...from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
class PostUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView): permission_required = "blog.change_post"Custom User Models
Section titled “Custom User Models”For a new project that may need additional user fields, define a custom user model before the first migration:
from django.contrib.auth.models import AbstractUser
class User(AbstractUser): passAUTH_USER_MODEL = "accounts.User"Changing the user model after tables and relationships already exist is considerably more involved.
Static and Uploaded Files
Section titled “Static and Uploaded Files”Static Assets
Section titled “Static Assets”STATIC_URL = "static/"STATIC_ROOT = BASE_DIR / "staticfiles"{% load static %}<link rel="stylesheet" href="{% static 'blog/site.css' %}">python manage.py collectstaticIn production, serve collected static assets through an appropriate static-file strategy rather than relying on Django’s development server.
Media Uploads
Section titled “Media Uploads”MEDIA_URL = "media/"MEDIA_ROOT = BASE_DIR / "media"class Post(models.Model): image = models.ImageField(upload_to="posts/", blank=True)python -m pip install Pillow # Required for ImageField processing<form method="post" enctype="multipart/form-data"> {% csrf_token %} {{ form.as_p }} <button type="submit">Save</button></form>form = PostForm(request.POST, request.FILES)Validate uploaded file types and sizes, and use a suitable storage backend in production.
Settings and Environment Variables
Section titled “Settings and Environment Variables”Keep secrets outside source control:
import os
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]DEBUG = os.environ.get("DJANGO_DEBUG", "false").lower() == "true"ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "").split(",")Database example:
DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", "NAME": os.environ["POSTGRES_DB"], "USER": os.environ["POSTGRES_USER"], "PASSWORD": os.environ["POSTGRES_PASSWORD"], "HOST": os.environ.get("POSTGRES_HOST", "localhost"), "PORT": os.environ.get("POSTGRES_PORT", "5432"), }}SQLite is convenient locally. Production applications commonly use a database server such as PostgreSQL.
Testing
Section titled “Testing”Model and View Tests
Section titled “Model and View Tests”from django.contrib.auth import get_user_modelfrom django.test import TestCasefrom django.urls import reverse
from .models import Category, Post
class PostTests(TestCase): def setUp(self): user = get_user_model().objects.create_user( username="author", password="test-password", ) category = Category.objects.create(name="Python", slug="python") self.post = Post.objects.create( title="Django basics", slug="django-basics", body="A post", author=user, category=category, status=Post.Status.PUBLISHED, )
def test_post_string(self): self.assertEqual(str(self.post), "Django basics")
def test_published_post_is_listed(self): response = self.client.get(reverse("blog:post-list"))
self.assertEqual(response.status_code, 200) self.assertContains(response, "Django basics")python manage.py testpython manage.py test blogUseful Test Client Requests
Section titled “Useful Test Client Requests”response = self.client.get("/posts/")response = self.client.post("/posts/new/", {"title": "New post"})self.client.login(username="author", password="test-password")self.client.force_login(user)Management Commands
Section titled “Management Commands”Common built-in commands:
python manage.py check # Validate project configurationpython manage.py shell # Interactive shell with Django configuredpython manage.py dbshell # Open the database command-line clientpython manage.py dumpdata blog # Export app data as JSONpython manage.py loaddata seed.json # Load fixture datapython manage.py changepassword adminpython manage.py clearsessionsCreate a custom command for repeatable operational tasks:
blog/ management/ __init__.py commands/ __init__.py publish_scheduled.pyfrom django.core.management.base import BaseCommand
class Command(BaseCommand): help = "Publish scheduled posts"
def handle(self, *args, **options): self.stdout.write(self.style.SUCCESS("Scheduled posts published"))python manage.py publish_scheduledPerformance and Advanced ORM
Section titled “Performance and Advanced ORM”Aggregation and Transactions
Section titled “Aggregation and Transactions”from django.db import transactionfrom django.db.models import Count
popular_categories = Category.objects.annotate(post_count=Count("posts"))
@transaction.atomicdef publish_post(post): post.status = Post.Status.PUBLISHED post.save(update_fields=["status"])Pagination
Section titled “Pagination”from django.core.paginator import Paginator
def post_list(request): posts = Post.objects.filter(status=Post.Status.PUBLISHED) page = Paginator(posts, 20).get_page(request.GET.get("page")) return render(request, "blog/post_list.html", {"page": page})Optimize after inspecting query behavior: reduce unnecessary columns, avoid repeated related-object queries, add indexes for real lookup patterns, and paginate large result sets.
Security and Deployment
Section titled “Security and Deployment”Deployment Checklist
Section titled “Deployment Checklist”Do not deploy with development defaults:
DEBUG = FalseALLOWED_HOSTS = ["www.example.com"]CSRF_TRUSTED_ORIGINS = ["https://www.example.com"]SECURE_SSL_REDIRECT = TrueSESSION_COOKIE_SECURE = TrueCSRF_COOKIE_SECURE = TrueBefore a release:
python manage.py check --deploypython manage.py migratepython manage.py collectstatic --noinputpython manage.py testUse an ASGI or WSGI production server, configure HTTPS at the application or reverse-proxy layer, keep SECRET_KEY private, and arrange logging, backups, and error monitoring.
ASGI and WSGI Entry Points
Section titled “ASGI and WSGI Entry Points”The generated project includes:
config/asgi.py # ASGI application for asynchronous-capable serversconfig/wsgi.py # WSGI application for traditional serversRun through an installed production server according to the chosen hosting stack; python manage.py runserver is only for development.
Common Mistakes
Section titled “Common Mistakes”| Problem | Better approach |
|---|---|
| Editing models without migrations | Run makemigrations and commit migration files |
Hardcoding secrets in settings.py | Load secrets from environment or secret storage |
Using DEBUG=True in production | Set production security settings and run check --deploy |
| Submitting forms without CSRF tokens | Include {% csrf_token %} for internal POST forms |
| Querying related data in a loop | Use select_related() or prefetch_related() |
| Trusting admin access for public permissions | Add explicit authentication and authorization checks |
Quick Reference
Section titled “Quick Reference”python -m pip install Django # Installdjango-admin startproject config . # Create projectpython manage.py startapp blog # Create apppython manage.py makemigrations # Generate schema migrationpython manage.py migrate # Apply migrationspython manage.py createsuperuser # Create admin loginpython manage.py runserver # Development serverpython manage.py shell # Django shellpython manage.py test # Test suitepython manage.py collectstatic # Gather static files for deploymentpython manage.py check --deploy # Production setting checksfrom django.shortcuts import get_object_or_404, redirect, renderfrom django.urls import path, reverse
from .models import Post
Post.objects.create(...)Post.objects.filter(status="published")Post.objects.get(pk=1)Post.objects.select_related("author")Post.objects.prefetch_related("tags")