Getting Started
Install & Create Project
Install Django via pip. A project holds settings and URLs; an app is a reusable module with models and views.
pip install django
django-admin startproject myproject
cd myproject
python manage.py startapp myappRun Development Server
Starts the built-in dev server with auto-reload. Never use in production.
python manage.py runserver
# Default: http://127.0.0.1:8000
python manage.py runserver 8080
python manage.py runserver 0.0.0.0:8000Migrations & Superuser
makemigrations generates migration files from model changes. migrate applies them to the DB. createsuperuser creates an admin account.
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuserProject Structure
manage.py is the CLI tool. The inner package holds project config; the app directory holds app-level code.
myproject/
manage.py # CLI utility
myproject/
settings.py # Configuration
urls.py # Root URL dispatcher
wsgi.py # WSGI entry point
asgi.py # ASGI entry point
myapp/
models.py # Database models
views.py # View functions
admin.py # Admin registrationSettings Basics
INSTALLED_APPS lists active apps. Set DEBUG=False and configure ALLOWED_HOSTS in production.
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'myapp',
]
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
DEBUG = True
ALLOWED_HOSTS = []Model Fields
Define a Model
Each model class maps to a DB table. Each field maps to a column. __str__ defines the display name.
from django.db import models
class Article(models.Model):
title = models.CharField(max_length=200)
body = models.TextField()
published = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.titleCommon Field Types
CharField requires max_length. auto_now_add sets on creation; auto_now updates on every save. EmailField/URLField add validation.
name = models.CharField(max_length=100)
description = models.TextField(blank=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
quantity = models.IntegerField(default=0)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
email = models.EmailField(unique=True)
url = models.URLField(blank=True)Field Options
null is DB-level; blank is validation-level. choices creates a dropdown in forms and admin.
title = models.CharField(
max_length=200,
null=True, # DB allows NULL
blank=True, # Form allows empty
default='',
choices=[('draft', 'Draft'), ('pub', 'Published')],
help_text='Article title',
verbose_name='Article Title',
)Meta Options
ordering sets default sort order. db_table overrides the auto-generated table name. constraints enforce DB-level rules.
class Meta:
ordering = ['-created_at']
db_table = 'articles'
verbose_name_plural = 'Articles'
constraints = [
models.UniqueConstraint(
fields=['title', 'author'],
name='unique_title_per_author',
)
]Model Methods & Properties
Override save() for pre-save logic. @property adds computed attributes. Always call super().save().
class Article(models.Model):
title = models.CharField(max_length=200)
views = models.IntegerField(default=0)
def __str__(self):
return self.title
def save(self, *args, **kwargs):
self.title = self.title.strip()
super().save(*args, **kwargs)
@property
def is_popular(self):
return self.views > 1000Model Relationships
ForeignKey (Many-to-One)
ForeignKey creates a many-to-one relationship. related_name enables reverse queries: author.books.all().
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
author = models.ForeignKey(
Author,
on_delete=models.CASCADE,
related_name='books',
)
title = models.CharField(max_length=200)OneToOneField
OneToOneField creates a one-to-one link. Access via user.profile or profile.user.
class Profile(models.Model):
user = models.OneToOneField(
User,
on_delete=models.CASCADE,
)
bio = models.TextField()ManyToManyField
ManyToManyField creates a junction table automatically. Add/remove: course.students.add(student).
class Student(models.Model):
name = models.CharField(max_length=100)
class Course(models.Model):
students = models.ManyToManyField(Student, blank=True)
title = models.CharField(max_length=200)on_delete Options
CASCADE deletes children when parent is deleted. SET_NULL requires null=True. PROTECT raises ProtectedError.
# CASCADE - delete related objects
author = models.ForeignKey(Author, on_delete=models.CASCADE)
# SET_NULL - set to NULL (field must be nullable)
author = models.ForeignKey(Author, on_delete=models.SET_NULL, null=True)
# PROTECT - prevent deletion
author = models.ForeignKey(Author, on_delete=models.PROTECT)
# SET_DEFAULT - set to default value
author = models.ForeignKey(Author, on_delete=models.SET_DEFAULT, default=1)Related Objects Query
Forward access uses the field name. Reverse access uses related_name (or modelname_set by default).
# Forward query
book.author.name
# Reverse query (uses related_name)
author.books.all()
author.books.filter(title__contains='Django')
# Add/remove in M2M
course.students.add(student1, student2)
course.students.remove(student1)
course.students.clear()
# Check membership
student in course.students.all()ORM QuerySet
Create & Save Objects
create() saves immediately. get_or_create returns (object, created_bool) — atomically gets or creates.
# Method 1: Create and save separately
article = Article(title='Hello', body='World')
article.save()
# Method 2: Create in one step
article = Article.objects.create(title='Hello', body='World')
# Method 3: get_or_create
obj, created = Article.objects.get_or_create(
title='Hello', defaults={'body': 'World'}
)Retrieve Single Object
get() raises DoesNotExist if not found, MultipleObjectsReturned if multiple. Use first() to safely get one.
# Get by primary key
article = Article.objects.get(pk=1)
# Get by field
article = Article.objects.get(title='Hello')
# First/last (returns None if empty)
first = Article.objects.first()
last = Article.objects.last()
# Latest/earliest by field
latest = Article.objects.latest('created_at')Filter & Exclude
filter() returns a QuerySet (lazy). Chaining applies AND logic. QuerySets are evaluated only when iterated.
# Filter
Article.objects.filter(title__contains='Django')
Article.objects.filter(published=True)
# Exclude
Article.objects.exclude(views__lt=100)
# Chain filters (AND)
Article.objects.filter(author=1).exclude(draft=True)Field Lookups
Lookups use double-underscore syntax. Common: exact, contains, icontains, gt, gte, lt, lte, in, startswith, range.
Article.objects.filter(title__icontains='django') # case-insensitive
Article.objects.filter(views__gte=100) # >=
Article.objects.filter(views__in=[100, 200, 300]) # IN
Article.objects.filter(created_at__year=2024) # date lookup
Article.objects.filter(title__startswith='Hello') # LIKE 'Hello%'
Article.objects.filter(title__isnull=True) # IS NULLOrdering & Slicing
Slicing applies LIMIT at the DB level. Negative indexing is not supported on QuerySets.
# Order by
Article.objects.order_by('-created_at') # descending
Article.objects.order_by('author', '-views')
# Slice (LIMIT/OFFSET)
Article.objects.all()[:5] # first 5
Article.objects.all()[5:10] # offset 5, limit 5
Article.objects.order_by('-views')[:3] # top 3
# Distinct
Article.objects.values('author').distinct()Update & Delete
update() is efficient for bulk changes. save(update_fields=[...]) avoids race conditions. delete() cascades by default.
# Bulk update
Article.objects.filter(draft=True).update(draft=False)
# Single update (only save this field)
article.title = 'New Title'
article.save(update_fields=['title'])
# Delete
article.delete() # returns (count, {model: count})
Article.objects.filter(views=0).delete() # bulk deleteORM Advanced
F Expressions
F() references a DB column value, enabling atomic updates without loading the object into Python.
from django.db.models import F
# Atomic increment (no race condition)
Article.objects.filter(pk=1).update(views=F('views') + 1)
# Compare two fields
Article.objects.filter(views__gt=F('min_views'))
# Arithmetic on fields
Article.objects.annotate(ratio=F('views') / F('likes'))Q Objects for Complex Queries
Q objects enable OR (|), AND (&), and NOT (~) logic. Without Q, multiple filter() calls are AND-only.
from django.db.models import Q
# OR
Article.objects.filter(Q(title__contains='Django') | Q(title__contains='Flask'))
# AND with NOT
Article.objects.filter(Q(published=True) & ~Q(views=0))
# OR + AND
Article.objects.filter(
(Q(author=1) | Q(author=2)) & Q(published=True)
)Aggregation
aggregate() returns a dict of totals. annotate() adds per-object computed values.
from django.db.models import Count, Sum, Avg, Max, Min
# Single aggregate
total = Article.objects.aggregate(total=Count('id'))
avg_views = Article.objects.aggregate(Avg('views'))
# Per-group aggregate
Author.objects.annotate(
article_count=Count('article'),
total_views=Sum('article__views'),
)Annotate
annotate() adds computed fields per object. You can filter() and order_by() on annotated fields.
from django.db.models import Count, Avg
# Add computed field to each object
authors = Author.objects.annotate(article_count=Count('book'))
# Filter on annotated field
Author.objects.annotate(
avg_rating=Avg('book__rating')
).filter(avg_rating__gte=4.0)select_related & prefetch_related
select_related uses SQL JOIN (forward FK). prefetch_related uses a second query (reverse/M2M). Both prevent N+1 queries.
# select_related (FK/OneToOne - single query with JOIN)
books = Book.objects.select_related('author').all()
# book.author.name -> no extra query
# prefetch_related (M2M/reverse - second query)
authors = Author.objects.prefetch_related('books').all()
# author.books.all() -> no extra query
# Deep prefetch
Author.objects.prefetch_related('books', 'books__reviews').all()Function-Based Views
Basic Function View
A view takes an HttpRequest and returns an HttpResponse. render() loads a template and fills it with context.
from django.shortcuts import render
from django.http import HttpResponse
def hello(request):
return HttpResponse("Hello, World!")
def home(request):
context = {'title': 'Home', 'items': [1, 2, 3]}
return render(request, 'home.html', context)HttpRequest Object
request.GET/POST are QueryDict (use .get() for safety). request.user is the authenticated user or AnonymousUser.
def my_view(request):
method = request.method # 'GET' or 'POST'
get_data = request.GET.get('q') # query param
post_data = request.POST.get('name')
path = request.path # '/articles/1/'
user = request.user # current user
files = request.FILES # uploaded files
meta = request.META.get('HTTP_HOST')HttpResponse Types
JsonResponse serializes to JSON. redirect() takes a URL path or a named URL with args.
from django.http import HttpResponse, JsonResponse
from django.shortcuts import redirect
def json_view(request):
return JsonResponse({'status': 'ok', 'count': 42})
def redirect_view(request):
return redirect('/home/')
return redirect('article_detail', pk=1)
def csv_view(request):
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="data.csv"'
return responseView Decorators
login_required redirects unauthenticated users to LOGIN_URL. require_POST/GET restrict HTTP methods.
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_POST, require_GET
@login_required
def dashboard(request):
return render(request, 'dashboard.html')
@require_POST
def submit(request):
pass # Only accepts POST
@require_GET
def search(request):
pass # Only accepts GETFile Upload View
request.FILES contains uploaded files. Use .chunks() for large files to avoid loading entire file into memory.
def upload_file(request):
if request.method == 'POST':
uploaded = request.FILES['file']
Document.objects.create(file=uploaded)
# Or save manually
with open(f'media/{uploaded.name}', 'wb') as f:
for chunk in uploaded.chunks():
f.write(chunk)
return redirect('success')
return render(request, 'upload.html')Class-Based Views
TemplateView
TemplateView renders a static template. Override get_context_data() to add extra context.
from django.views.generic import TemplateView
class AboutView(TemplateView):
template_name = 'about.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = 'About Us'
return contextListView
ListView auto-paginates and provides object_list and page_obj in context. Override get_queryset() for filtering.
from django.views.generic import ListView
class ArticleListView(ListView):
model = Article
template_name = 'article_list.html'
context_object_name = 'articles'
paginate_by = 10
def get_queryset(self):
return Article.objects.filter(published=True)DetailView
DetailView fetches a single object by pk or slug from the URL. Override get_object() for custom fetching.
from django.views.generic import DetailView
class ArticleDetailView(DetailView):
model = Article
template_name = 'article_detail.html'
context_object_name = 'article'
def get_object(self):
return Article.objects.get(
slug=self.kwargs['slug'], published=True
)CreateView & UpdateView
CreateView/UpdateView handle form display, validation, and saving. Override form_valid() to add logic before save.
from django.views.generic.edit import CreateView, UpdateView
class ArticleCreateView(CreateView):
model = Article
fields = ['title', 'body', 'author']
template_name = 'article_form.html'
success_url = '/articles/'
class ArticleUpdateView(UpdateView):
model = Article
fields = ['title', 'body']
template_name = 'article_form.html'Mixins
Mixins add reusable behavior to class-based views. Django provides LoginRequiredMixin, PermissionRequiredMixin.
from django.contrib.auth.mixins import LoginRequiredMixin
class OwnerRequiredMixin:
def get_queryset(self):
return super().get_queryset().filter(owner=self.request.user)
class ArticleListView(LoginRequiredMixin, OwnerRequiredMixin, ListView):
model = Article
paginate_by = 10URL Routing
Basic URL Configuration
path() maps a URL pattern to a view. name= enables reverse URL lookup. <int:pk> captures an integer.
from django.urls import path
from myapp import views
urlpatterns = [
path('', views.home, name='home'),
path('about/', views.about, name='about'),
path('article/<int:pk>/', views.article_detail, name='article_detail'),
]Including App URLs
include() delegates URL matching to an app's urls.py. app_name creates a namespace for URL reversing.
# myproject/urls.py
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('blog/', include('blog.urls')),
path('api/', include('api.urls')),
]
# blog/urls.py
app_name = 'blog'
urlpatterns = [
path('', views.list, name='list'),
path('<int:pk>/', views.detail, name='detail'),
]Path Converters
Path converters auto-type-cast URL segments. Use re_path for complex regex patterns.
path('article/<int:pk>/') # integer
path('article/<slug:slug>/') # slug string
path('article/<str:title>/') # non-slash string
path('user/<uuid:user_id>/') # UUID
path('file/<path:filepath>/') # any string including /
# Regex path (re_path)
from django.urls import re_path
re_path(r'^article/(?P<year>[0-9]{4})/$', views.year_archive)Named URLs & reverse()
Named URLs let you reference routes by name instead of hardcoding paths. reverse() generates URLs in Python.
# urls.py
path('article/<int:pk>/', views.detail, name='article_detail')
# In views.py
from django.urls import reverse
url = reverse('article_detail', kwargs={'pk': 42})
# Returns '/article/42/'
# In templates
<a href="{% url 'article_detail' pk=article.pk %}">Read</a>URL Namespaces
Namespaces prevent name collisions between apps. The namespace must match the app's app_name.
# myproject/urls.py
path('blog/', include(('blog.urls', 'blog'), namespace='blog'))
# blog/urls.py
app_name = 'blog'
urlpatterns = [
path('<int:pk>/', views.detail, name='detail'),
]
# Usage
reverse('blog:detail', kwargs={'pk': 1})
# Template: {% url 'blog:detail' pk=1 %}Templates (DTL)
Template Inheritance
extends inherits from a base template. Blocks define overridable regions. A template can extend only one parent.
{# base.html #}
<html>
<body>
{% block content %}{% endblock %}
{% block sidebar %}{% endblock %}
</body>
</html>
{# article.html #}
{% extends 'base.html' %}
{% block content %}
<h1>{{ article.title }}</h1>
{% endblock %}Variables & Tags
{{ }} outputs variables. {% %} executes tags. forloop.counter starts at 1; forloop.counter0 at 0.
{# Variables #}
{{ article.title }}
{{ user.get_full_name }}
{{ article.tags.0 }}
{# Tags #}
{% if article.published %}
Published
{% elif article.draft %}
Draft
{% else %}
Archived
{% endif %}
{% for item in items %}
{{ forloop.counter }}: {{ item }}
{% empty %}
No items
{% endfor %}Template Filters
Filters modify variable output using pipe | syntax. Some take arguments after a colon. Filters can be chained.
{{ name|lower }}
{{ text|truncatewords:30 }}
{{ date|date:"Y-m-d" }}
{{ value|default:"N/A" }}
{{ list|length }}
{{ text|striptags }}
{{ price|floatformat:2 }}
{{ value|add:5 }}Built-in Template Tags
csrf_token is required in all POST forms. include renders a sub-template. load imports custom tag libraries.
{% url 'article_detail' pk=1 %}
{% csrf_token %}
{% load static %}
{% include 'header.html' %}
{% with total=items|length %}
{{ total }}
{% endwith %}
{% now "Y-m-d" %}
{% blocktrans %}Hello{% endblocktrans %}Custom Template Tags
Tags live in app/templatetags/ directory. @register.simple_tag for functions; @register.filter for value modifiers.
# templatetags/my_tags.py
from django import template
register = template.Library()
@register.simple_tag
def multiply(a, b):
return a * b
@register.filter
def cut(value, arg):
return value.replace(arg, '')
# In template:
{% load my_tags %}
{% multiply 3 4 %}
{{ "hello world"|cut:" " }}Forms
Define a Form
forms.Form creates a standalone form. Each field auto-generates HTML widgets. Field types provide validation.
from django import forms
class ContactForm(forms.Form):
name = forms.CharField(max_length=100)
email = forms.EmailField()
message = forms.CharField(widget=forms.Textarea)
subject = forms.ChoiceField(
choices=[('general', 'General'), ('bug', 'Bug Report')],
)ModelForm
ModelForm auto-builds a form from a model. fields/exclude control which fields appear. widgets customize HTML.
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ['title', 'body', 'author']
# Or: fields = '__all__'
# Or: exclude = ['created_at']
widgets = {
'body': forms.Textarea(attrs={'rows': 10}),
}
labels = {'title': 'Article Title'}Form Validation
clean_<field>() validates a single field. clean() validates the entire form. Raise ValidationError on failure.
class SignupForm(forms.Form):
username = forms.CharField(max_length=50)
password = forms.CharField(widget=forms.PasswordInput)
def clean_username(self):
username = self.cleaned_data['username']
if User.objects.filter(username=username).exists():
raise forms.ValidationError("Username taken.")
return username
def clean(self):
cleaned = super().clean()
# Cross-field validation
return cleanedRender Form in Template
as_p/as_table/as_ul auto-render all fields. For custom layout, render fields individually. csrf_token is required for POST.
<form method="post">
{% csrf_token %}
{{ form.as_p }}
{{ form.as_table }}
{{ form.as_ul }}
{# Manual rendering #}
{{ form.title.label_tag }}
{{ form.title }}
{{ form.title.errors }}
<button type="submit">Submit</button>
</form>Process Form in View
Pass request.POST to bind the form. is_valid() runs validation. save(commit=False) returns an unsaved object.
def article_create(request):
if request.method == 'POST':
form = ArticleForm(request.POST)
if form.is_valid():
article = form.save(commit=False)
article.author = request.user
article.save()
form.save_m2m()
return redirect('article_detail', pk=article.pk)
else:
form = ArticleForm()
return render(request, 'form.html', {'form': form})Form Widgets
widgets control HTML rendering. Pass attrs dict for CSS classes, placeholders, or HTML5 input types.
class UploadForm(forms.Form):
title = forms.CharField(
widget=forms.TextInput(attrs={'class': 'form-control'})
)
file = forms.FileField(widget=forms.ClearableFileInput)
date = forms.DateField(widget=forms.DateInput(attrs={'type': 'date'}))
tags = forms.MultipleChoiceField(
choices=TAG_CHOICES,
widget=forms.CheckboxSelectMultiple,
)Admin Site
Register Model in Admin
@admin.register is preferred over admin.site.register. Models must be registered to appear in the admin interface.
from django.contrib import admin
from .models import Article
# Simple registration
admin.site.register(Article)
# With custom ModelAdmin
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ('title', 'author', 'published')ModelAdmin Customization
list_display shows columns in list view. list_filter adds sidebar filters. search_fields enables a search box.
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ('title', 'author', 'status')
list_filter = ('status', 'author', 'created_at')
search_fields = ('title', 'body')
ordering = ('-created_at',)
list_editable = ('status',)
list_per_page = 25
date_hierarchy = 'created_at'
readonly_fields = ('views', 'created_at')Inline Admin
Inlines edit related models on the same page as the parent. extra=1 shows one empty row by default.
class ReviewInline(admin.TabularInline):
model = Review
extra = 1
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
inlines = [ReviewInline]
# TabularInline: compact table format
# StackedInline: full form fields stackedCustom Admin Actions
Actions appear in the dropdown on the list page. They receive request and queryset. message_user shows a flash message.
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
actions = ['publish_selected']
@admin.action(description='Publish selected')
def publish_selected(self, request, queryset):
updated = queryset.update(published=True)
self.message_user(request, f'{updated} articles published.')Admin Site Configuration
Override site_header/site_title for branding. For multiple admin sites, create custom AdminSite instances.
admin.site.site_header = 'My Site Admin'
admin.site.site_title = 'Admin Portal'
admin.site.index_title = 'Dashboard'
# Custom admin site
from django.contrib.admin import AdminSite
my_site = AdminSite(name='myadmin')
my_site.register(Article, ArticleAdmin)
# urls.py
path('myadmin/', my_site.urls)Authentication
Built-in User Model
Django's User model has username, password (hashed), email, first_name, last_name, and permission flags.
from django.contrib.auth.models import User
user = User.objects.get(username='admin')
user.is_staff # can access admin
user.is_superuser # has all permissions
user.is_active # account enabled
user.set_password('newpass') # hash password
user.save()
user.check_password('secret') # returns True/FalseLogin & Logout
authenticate() verifies credentials (returns User or None). login() creates the session. logout() clears it.
from django.contrib.auth import authenticate, login, logout
def login_view(request):
user = authenticate(request, username='admin', password='pass')
if user is not None:
login(request, user)
return redirect('dashboard')
return render(request, 'login.html')
def logout_view(request):
logout(request)
return redirect('home')User Registration
UserCreationForm handles username, password, and password confirmation with built-in validation.
from django.contrib.auth.forms import UserCreationForm
def register(request):
if request.method == 'POST':
form = UserCreationForm(request.POST)
if form.is_valid():
user = form.save()
login(request, user) # auto-login after register
return redirect('home')
else:
form = UserCreationForm()
return render(request, 'register.html', {'form': form})Login Required Decorator
login_required redirects to LOGIN_URL (default /accounts/login/). For CBVs, use LoginRequiredMixin.
from django.contrib.auth.decorators import login_required
@login_required
def profile(request):
return render(request, 'profile.html')
@login_required(login_url='/custom-login/')
def settings(request):
return render(request, 'settings.html')
# Class-based view mixin
from django.contrib.auth.mixins import LoginRequiredMixin
class DashboardView(LoginRequiredMixin, ListView):
login_url = '/login/'
model = ArticlePassword Management
update_session_auth_hash prevents logout after password change. Password validators enforce rules site-wide.
from django.contrib.auth import update_session_auth_hash
# Change password
user.set_password('newpass123')
user.save()
update_session_auth_hash(request, user) # keep user logged in
# Using form
from django.contrib.auth.forms import PasswordChangeForm
form = PasswordChangeForm(user, request.POST)
if form.is_valid():
form.save()Custom User Model
Subclass AbstractUser to add fields. Set AUTH_USER_MODEL early — changing it later is difficult.
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
bio = models.TextField(blank=True)
avatar = models.ImageField(upload_to='avatars/', blank=True)
phone = models.CharField(max_length=20, blank=True)
# settings.py
AUTH_USER_MODEL = 'myapp.CustomUser'
# Admin
from django.contrib.auth.admin import UserAdmin
@admin.register(CustomUser)
class CustomUserAdmin(UserAdmin):
passMiddleware
Built-in Middleware
Order matters! SecurityMiddleware first, then sessions, then auth. CSRF checks run before view processing.
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]Custom Function Middleware
Function middleware uses the new-style factory pattern. get_response calls the next middleware or view.
def simple_middleware(get_response):
def middleware(request):
# Code before view (request phase)
response = get_response(request)
# Code after view (response phase)
return response
return middleware
# Example: timing middleware
import time
def timing_middleware(get_response):
def middleware(request):
start = time.time()
response = get_response(request)
response['X-Duration'] = str(time.time() - start)
return response
return middlewareClass-Based Middleware
__init__ runs once at startup. __call__ runs per request. process_view can return a response to bypass the view.
class MyMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Request phase (before view)
response = self.get_response(request)
# Response phase (after view)
return response
def process_view(self, request, view_func, args, kwargs):
return None # or HttpResponse to short-circuitMiddleware Order & Flow
Requests pass through middleware top-to-bottom; responses bubble up bottom-to-top. Put security middleware first.
# Request phase: top to bottom
# Response phase: bottom to top
MIDDLEWARE = [
'A', # req: 1st -> resp: last
'B', # req: 2nd -> resp: 2nd
'C', # req: 3rd -> resp: 1st
]
# Request flows: A -> B -> C -> view
# Response flows: view -> C -> B -> AMiddleware Use Cases
Middleware can short-circuit (return HttpResponse early) or attach data to request for use in views.
# IP restriction
class IPBlockMiddleware:
BLOCKED_IPS = ['192.168.1.100']
def __call__(self, request):
if request.META['REMOTE_ADDR'] in self.BLOCKED_IPS:
return HttpResponseForbidden('Blocked')
return self.get_response(request)
# Add context to request
class UserPrefMiddleware:
def __call__(self, request):
request.is_mobile = 'Mobile' in request.META.get('HTTP_USER_AGENT', '')
return self.get_response(request)Static & Media Files
Static Files Configuration
STATIC_URL is the URL prefix. STATICFILES_DIRS are source locations (dev). STATIC_ROOT is the production collection target.
# settings.py
STATIC_URL = '/static/'
STATICFILES_DIRS = [BASE_DIR / 'static'] # dev location
STATIC_ROOT = BASE_DIR / 'staticfiles' # collectstatic output
# Project structure:
# static/
# css/style.css
# js/app.js
# img/logo.pngServing Static in Development
Django auto-serves static files only when DEBUG=True. In production, use a web server (nginx) or WhiteNoise.
# urls.py (dev only)
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
# Only active when DEBUG=TrueUsing Static in Templates
{% load static %} is required. {% static %} resolves to the hashed filename in production with ManifestStaticFilesStorage.
{% load static %}
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<img src="{% static 'img/logo.png' %}" alt="Logo">
<script src="{% static 'js/app.js' %}"></script>
{# Dynamic static path #}
{% get_static_prefix as STATIC_URL %}
<img src="{{ STATIC_URL }}img/{{ theme }}.png">Media Files Configuration
MEDIA_ROOT is the filesystem path; MEDIA_URL is the URL prefix. upload_to can be a path string or a callable.
# settings.py
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
# urls.py (dev)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# Model with file upload
class Document(models.Model):
file = models.FileField(upload_to='documents/')
image = models.ImageField(upload_to='images/')
# Access
doc.file.url # '/media/documents/file.pdf'
doc.file.path # '/full/path/to/file.pdf'collectstatic Command
Run collectstatic before deployment. WhiteNoise lets Django serve static files in production without nginx.
# Collect all static files into STATIC_ROOT
python manage.py collectstatic
# With clearing
python manage.py collectstatic --clear
# Production with WhiteNoise
# pip install whitenoise
MIDDLEWARE = [..., 'whitenoise.middleware.WhiteNoiseMiddleware', ...]
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'Signals
Built-in Signals
Signals allow decoupled apps to react to actions. post_save fires after save() completes; pre_save fires before.
from django.db.models.signals import (
post_save, pre_save, post_delete, pre_delete,
m2m_changed,
)
from django.contrib.auth.signals import (
user_logged_in, user_logged_out,
)
# Common signals:
# pre_save / post_save - before/after model save
# pre_delete / post_delete - before/after model delete
# m2m_changed - M2M relationship changes
# user_logged_in / user_logged_outConnecting Signal Receivers
@receiver connects the function to the signal. sender limits to a specific model. created is True only on new objects.
from django.db.models.signals import post_save
from django.dispatch import receiver
@receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_profile(sender, instance, **kwargs):
instance.profile.save()Disconnecting Signals
disconnect() is useful in tests or when you need to prevent cascading actions. Always provide both receiver and sender.
from django.db.models.signals import post_save
# Disconnect
post_save.disconnect(
receiver=create_profile,
sender=User,
)
# Temporarily disconnect in tests
post_save.disconnect(handler, sender=Article)
# ... do stuff without signal ...
post_save.connect(handler, sender=Article)Custom Signals
Define signals at module level. send() notifies all receivers. kwargs are passed to all connected receivers.
from django.dispatch import Signal, receiver
# Define a custom signal
order_created = Signal()
# Send the signal
order_created.send(
sender=Order,
order=order_instance,
user=request.user,
)
# Receive
@receiver(order_created, sender=Order)
def send_notification(sender, order, user, **kwargs):
pass # Send email notificationSignal Use Cases & Gotchas
Import signal handlers in AppConfig.ready() to ensure they connect at startup. Avoid slow operations — use Celery for async work.
# Good: Profile auto-creation
@receiver(post_save, sender=User)
def ensure_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.get_or_create(user=instance)
# Register in AppConfig.ready()
# apps.py
class MyAppConfig(AppConfig):
name = 'myapp'
def ready(self):
from . import signals # noqaCaching
Cache Configuration
Redis is recommended for production. LocMemCache is process-local (no sharing between workers).
# settings.py - Memcached
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
'LOCATION': '127.0.0.1:11211',
}
}
# Redis
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379',
}
}
# Local memory (dev)
CACHES = {
'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'}
}Low-Level Cache API
cache.get() returns None for missing keys. Use get_or_set for set-if-not-exists atomicity.
from django.core.cache import cache
# Set
cache.set('key', 'value', timeout=300) # 5 minutes
cache.set_many({'a': 1, 'b': 2}, timeout=60)
# Get
value = cache.get('key') # None if missing
value = cache.get('key', 'default') # with default
data = cache.get_many(['a', 'b']) # dict
# Delete
cache.delete('key')
cache.clear() # delete all
# get_or_set (atomic)
value = cache.get_or_set('key', 'value', 300)Template Caching
{% cache %} stores rendered HTML fragments. Add varying arguments (user ID, page) to avoid wrong cached content.
{% load cache %}
{# Cache block for 500 seconds #}
{% cache 500 sidebar %}
<div class="sidebar">
{{ expensive_data }}
</div>
{% endcache %}
{# Cache with varying key #}
{% cache 500 user_sidebar request.user.id %}
Welcome, {{ request.user.username }}
{% endcache %}Per-View Cache
cache_page caches the entire HttpResponse. The cache key includes the URL, so each URL gets its own cache entry.
from django.views.decorators.cache import cache_page
@cache_page(60 * 15)
def article_list(request):
return render(request, 'list.html', {'articles': Article.objects.all()})
# With cache key prefix
@cache_page(60 * 15, key_prefix='articles')
def article_detail(request, pk):
...
# Class-based view
from django.utils.decorators import method_decorator
class ArticleListView(ListView):
@method_decorator(cache_page(60 * 15))
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)Cache Strategies
vary_on_headers creates separate cache entries per header value. For APIs, vary on Accept-Language or Authorization.
# Per-view cache with Vary headers
from django.views.decorators.vary import vary_on_headers
@cache_page(60 * 15)
@vary_on_headers('User-Agent')
def api_data(request):
...
# Model-level caching
def get_articles():
articles = cache.get('all_articles')
if articles is None:
articles = list(Article.objects.all())
cache.set('all_articles', articles, 300)
return articlesTesting
Writing Tests
TestCase wraps tests in a DB transaction (rolled back). SimpleTestCase is for tests without DB access (faster).
from django.test import TestCase, SimpleTestCase
from .models import Article
class ArticleModelTest(TestCase):
def setUp(self):
self.article = Article.objects.create(
title='Test', body='Content'
)
def test_str(self):
self.assertEqual(str(self.article), 'Test')
def test_field_default(self):
self.assertEqual(self.article.views, 0)
# SimpleTestCase: no DB access
class MathTest(SimpleTestCase):
def test_addition(self):
self.assertEqual(1 + 1, 2)Testing with Test Client
self.client simulates HTTP requests without starting a server. .login() authenticates for subsequent requests.
class ViewTest(TestCase):
def setUp(self):
self.user = User.objects.create_user('test', '[email protected]', 'pass')
def test_login(self):
response = self.client.post('/login/', {
'username': 'test', 'password': 'pass'
})
self.assertEqual(response.status_code, 302)
def test_protected_view(self):
self.client.login(username='test', password='pass')
response = self.client.get('/dashboard/')
self.assertEqual(response.status_code, 200)Model & QuerySet Tests
Test data is created in setUp() and rolled back after each test. Use .count() and indexing to verify results.
class ArticleQuerySetTest(TestCase):
def setUp(self):
Article.objects.create(title='A', views=100, published=True)
Article.objects.create(title='B', views=50, published=False)
def test_published_filter(self):
published = Article.objects.filter(published=True)
self.assertEqual(published.count(), 1)
def test_ordering(self):
articles = Article.objects.order_by('-views')
self.assertEqual(articles[0].title, 'A')URL & Form Tests
reverse() builds URLs from names. resolve() checks which view a URL maps to. Test forms by passing data= dict.
from django.urls import reverse, resolve
class URLTest(TestCase):
def test_article_list_url(self):
url = reverse('article_list')
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
def test_resolve(self):
url = reverse('article_detail', kwargs={'pk': 1})
self.assertEqual(resolve(url).func, views.article_detail)
class FormTest(TestCase):
def test_valid_form(self):
form = ArticleForm(data={'title': 'Hi', 'body': '...'})
self.assertTrue(form.is_valid())Fixtures & Test Commands
Fixtures load test data from JSON files. Run specific tests by app, class, or method path.
# Create fixture
python manage.py dumpdata myapp.Article --indent=2 > fixtures/articles.json
# Use in test
class FixtureTest(TestCase):
fixtures = ['articles.json']
def test_data_loaded(self):
self.assertEqual(Article.objects.count(), 10)
# Run tests
python manage.py test
python manage.py test myapp
python manage.py test myapp.tests.ArticleModelTestDjango REST Framework
Install & Setup DRF
Add rest_framework to INSTALLED_APPS. Configure default auth/permission in REST_FRAMEWORK settings dict.
pip install djangorestframework
# settings.py
INSTALLED_APPS = [..., 'rest_framework', 'rest_framework.authtoken']
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}Serializers
ModelSerializer auto-generates fields from the model. source= pulls from related objects. validate_<field>() adds validation.
from rest_framework import serializers
class ArticleSerializer(serializers.ModelSerializer):
author_name = serializers.CharField(source='author.username', read_only=True)
class Meta:
model = Article
fields = ['id', 'title', 'body', 'author', 'author_name']
read_only_fields = ['id', 'author_name']
def validate_title(self, value):
if len(value) < 5:
raise serializers.ValidationError("Title too short.")
return valueViewSets
ModelViewSet provides CRUD (list, create, retrieve, update, destroy). Override get_queryset() for filtering.
from rest_framework import viewsets, permissions
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def get_queryset(self):
qs = super().get_queryset()
author = self.request.query_params.get('author')
if author:
qs = qs.filter(author__username=author)
return qs
def perform_create(self, serializer):
serializer.save(author=self.request.user)Routers
DefaultRouter auto-generates URL patterns and an API root view. basename is used for URL naming if queryset is dynamic.
from rest_framework.routers import DefaultRouter
from .views import ArticleViewSet
router = DefaultRouter()
router.register(r'articles', ArticleViewSet, basename='article')
# router.register(r'users', UserViewSet)
# urls.py
urlpatterns = [
path('api/', include(router.urls)),
]
# Generates: /api/articles/, /api/articles/{id}/Authentication
Token auth uses a single static token. JWT issues access + refresh tokens. SimpleJWT is the most popular JWT library.
# Token Authentication
from rest_framework.authtoken.views import obtain_auth_token
urlpatterns = [
path('api/token/', obtain_auth_token),
]
# JWT Authentication (pip install djangorestframework-simplejwt)
from rest_framework_simplejwt.views import (
TokenObtainPairView, TokenRefreshView,
)
urlpatterns = [
path('api/token/', TokenObtainPairView.as_view()),
path('api/token/refresh/', TokenRefreshView.as_view()),
]Permissions
SAFE_METHODS are GET/HEAD/OPTIONS. has_object_permission checks per-object access. get_permissions() allows per-action rules.
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True # GET, HEAD, OPTIONS
return obj.author == request.user
# Built-in: IsAuthenticated, IsAdminUser,
# IsAuthenticatedOrReadOnly, AllowAny
# Per-action
class ArticleViewSet(viewsets.ModelViewSet):
permission_classes = [IsOwnerOrReadOnly]
def get_permissions(self):
if self.action == 'create':
return [permissions.IsAuthenticated()]
return super().get_permissions()Deployment & Security
Production Settings
Never commit SECRET_KEY to version control. Use environment variables. DEBUG=False prevents info leaks in error pages.
# settings.py
DEBUG = False
ALLOWED_HOSTS = ['example.com', 'www.example.com']
SECRET_KEY = os.environ['SECRET_KEY']
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'mydb',
'USER': os.environ['DB_USER'],
'PASSWORD': os.environ['DB_PASS'],
'HOST': 'localhost',
'PORT': '5432',
}
}Static Files in Production
collectstatic gathers all static files to STATIC_ROOT. WhiteNoise serves gzipped, hashed files from Django itself.
# Collect static files
python manage.py collectstatic --noinput
# WhiteNoise for serving static
MIDDLEWARE = [..., 'whitenoise.middleware.WhiteNoiseMiddleware', ...]
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
# Or with nginx:
# location /static/ { alias /path/to/staticfiles/; }Security Settings
SSL_REDIRECT forces HTTPS. HSTS tells browsers to always use HTTPS. X_FRAME_OPTIONS prevents clickjacking.
# settings.py
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'
CSRF_TRUSTED_ORIGINS = ['https://example.com']WSGI/ASGI Deployment
Gunicorn is synchronous (traditional Django). Uvicorn supports ASGI (async views, websockets). Use a process manager.
# Gunicorn (WSGI)
# pip install gunicorn
gunicorn myproject.wsgi:application --bind 0.0.0.0:8000 --workers 3
# Uvicorn (ASGI, for async/websockets)
# pip install uvicorn
uvicorn myproject.asgi:application --host 0.0.0.0 --port 8000 --workers 4
# Supervisor config
[program:django]
command=gunicorn myproject.wsgi:application
directory=/app
autostart=true
autorestart=trueProduction Checklist
check --deploy runs Django's deployment checklist. It flags missing security settings and production-readiness issues.
# Run security check
python manage.py check --deploy
# Key items:
# - DEBUG = False
# - ALLOWED_HOSTS set
# - SECRET_KEY from env
# - HTTPS enabled (SECURE_SSL_REDIRECT)
# - HSTS configured
# - collectstatic run
# - Database (PostgreSQL/MySQL)
# - Caching (Redis/Memcached)
# - Error monitoring (Sentry)Snippets Django associés
Copy-paste ready code for common tasks.
Model Definition
Define a Django model with field types and meta options.
ORM QuerySet
Filter, exclude, annotate and chain QuerySets.
Class-Based Views
Use generic class-based views for common CRUD flows.
URL Routing
Wire URLs to views with path converters and includes.
ModelForms
Build forms from models with validation and widgets.
Template Tags
Use built-in tags and filters in Django templates.
Admin Customization
Customize the Django admin with list_display and actions.
Authentication
Login, logout, and protect views with auth decorators.
Was this helpful?