Django
Django projects, models, views, templates, forms, authentication, testing, and deployment reference.
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
Installation
Create an isolated environment and install Django:
python -m venv .venv
# macOS/Linux
source .venv/bin/activate
# PowerShell
.\.venv\Scripts\Activate.ps1
python -m pip install Django
python -m django --versionCreate 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 blog
python manage.py migrate
python manage.py runserverTypical structure:
manage.py
config/
__init__.py
settings.py
urls.py
asgi.py
wsgi.py
blog/
admin.py
apps.py
migrations/
models.py
tests.py
views.pyRegister the App
Add the application to INSTALLED_APPS:
# config/settings.py
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"blog",
]Requests and Responses
First View
# blog/views.py
from django.http import HttpResponse
def home(request):
return HttpResponse("Hello, Django")Map a URL to the view:
# blog/urls.py
from django.urls import path
from . import views
app_name = "blog"
urlpatterns = [
path("", views.home, name="home"),
]Include application URLs in the project:
# config/urls.py
from django.contrib import admin
from 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
# blog/urls.py
urlpatterns = [
path("posts/<int:post_id>/", views.post_detail, name="post-detail"),
path("authors/<slug:username>/", views.author_detail, name="author-detail"),
]# blog/views.py
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
Define Models
Models describe stored data and relationships:
# blog/models.py
from django.conf import settings
from django.db import models
from 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
Create and apply migration files whenever model structure changes:
python manage.py makemigrations
python manage.py migrate
python manage.py showmigrations
python 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
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
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
Render HTML
# blog/views.py
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})# blog/urls.py
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
<!-- blog/templates/blog/base.html -->
<!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><!-- blog/templates/blog/post_list.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
Model Form
# blog/forms.py
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
# blog/views.py
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect, render
from .forms import PostForm
@login_required
def 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})<!-- blog/templates/blog/post_form.html -->
{% 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
Create an Administrator
python manage.py createsuperuser
python manage.py runserverVisit http://127.0.0.1:8000/admin/.
Register and Customize Models
# blog/admin.py
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
Generic views reduce repeated list, detail, create, update, and delete logic:
# blog/views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse_lazy
from 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)# blog/urls.py
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
Built-In Authentication URLs
# config/urls.py
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_required
def 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
For a new project that may need additional user fields, define a custom user model before the first migration:
# accounts/models.py
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
pass# config/settings.py
AUTH_USER_MODEL = "accounts.User"Changing the user model after tables and relationships already exist is considerably more involved.
Static and Uploaded Files
Static Assets
# config/settings.py
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
# config/settings.py
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
Keep secrets outside source control:
# config/settings.py
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
Model and View Tests
# blog/tests.py
from django.contrib.auth import get_user_model
from django.test import TestCase
from 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 test
python manage.py test blogUseful 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
Common built-in commands:
python manage.py check # Validate project configuration
python manage.py shell # Interactive shell with Django configured
python manage.py dbshell # Open the database command-line client
python manage.py dumpdata blog # Export app data as JSON
python manage.py loaddata seed.json # Load fixture data
python manage.py changepassword admin
python manage.py clearsessionsCreate a custom command for repeatable operational tasks:
blog/
management/
__init__.py
commands/
__init__.py
publish_scheduled.py# blog/management/commands/publish_scheduled.py
from 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
Aggregation and Transactions
from django.db import transaction
from django.db.models import Count
popular_categories = Category.objects.annotate(post_count=Count("posts"))
@transaction.atomic
def publish_post(post):
post.status = Post.Status.PUBLISHED
post.save(update_fields=["status"])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
Deployment Checklist
Do not deploy with development defaults:
DEBUG = False
ALLOWED_HOSTS = ["www.example.com"]
CSRF_TRUSTED_ORIGINS = ["https://www.example.com"]
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = TrueBefore a release:
python manage.py check --deploy
python manage.py migrate
python manage.py collectstatic --noinput
python 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
The generated project includes:
config/asgi.py # ASGI application for asynchronous-capable servers
config/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
| 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
python -m pip install Django # Install
django-admin startproject config . # Create project
python manage.py startapp blog # Create app
python manage.py makemigrations # Generate schema migration
python manage.py migrate # Apply migrations
python manage.py createsuperuser # Create admin login
python manage.py runserver # Development server
python manage.py shell # Django shell
python manage.py test # Test suite
python manage.py collectstatic # Gather static files for deployment
python manage.py check --deploy # Production setting checksfrom django.shortcuts import get_object_or_404, redirect, render
from 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")