Skip to content

Django 速查表

用于快速、安全开发的高级 Python Web 框架。

01

入门

安装与创建项目

通过 pip 安装 Django。项目包含设置和 URL 配置;应用是包含模型和视图的可复用模块。

django
pip install django
django-admin startproject myproject
cd myproject
python manage.py startapp myapp

运行开发服务器

启动内置开发服务器并自动重载。切勿在生产环境中使用。

django
python manage.py runserver
# Default: http://127.0.0.1:8000
python manage.py runserver 8080
python manage.py runserver 0.0.0.0:8000

迁移与超级用户

makemigrations 从模型变更生成迁移文件。migrate 将其应用到数据库。createsuperuser 创建管理员账户。

django
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser

项目结构

manage.py 是命令行工具。内部包存放项目配置;应用目录存放应用级代码。

django
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 registration

设置基础

INSTALLED_APPS 列出已激活的应用。在生产中设置 DEBUG=False 并配置 ALLOWED_HOSTS。

django
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 = []
02

模型字段

定义模型

每个模型类映射到数据库表。每个字段映射到列。__str__ 定义显示名称。

django
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.title

常见字段类型

CharField 需要 max_length。auto_now_add 在创建时设置;auto_now 在每次保存时更新。EmailField/URLField 添加验证。

django
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)

字段选项

null 是数据库级别;blank 是验证级别。choices 在表单和管理后台创建下拉选择。

django
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 选项

ordering 设置默认排序。db_table 覆盖自动生成的表名。constraints 强制数据库级规则。

django
class Meta:
    ordering = ['-created_at']
    db_table = 'articles'
    verbose_name_plural = 'Articles'
    constraints = [
        models.UniqueConstraint(
            fields=['title', 'author'],
            name='unique_title_per_author',
        )
    ]

模型方法与属性

重写 save() 添加保存前逻辑。@property 添加计算属性。务必调用 super().save()。

django
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 > 1000
03

模型关系

ForeignKey(多对一)

ForeignKey 创建多对一关系。related_name 启用反向查询:author.books.all()。

django
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 创建一对一链接。通过 user.profile 或 profile.user 访问。

django
class Profile(models.Model):
    user = models.OneToOneField(
        User,
        on_delete=models.CASCADE,
    )
    bio = models.TextField()

ManyToManyField

ManyToManyField 自动创建中间表。添加/移除:course.students.add(student)。

django
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 选项

CASCADE 在父对象删除时删除子对象。SET_NULL 需要 null=True。PROTECT 抛出 ProtectedError。

django
# 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_name(默认为 modelname_set)。

django
# 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()
04

ORM 查询

创建与保存对象

create() 立即保存。get_or_create 返回 (对象, 是否创建布尔值) — 原子性获取或创建。

django
# 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'}
)

检索单个对象

get() 未找到时抛出 DoesNotExist,多个时抛出 MultipleObjectsReturned。使用 first() 安全获取。

django
# 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() 返回 QuerySet(惰性)。链式调用应用 AND 逻辑。QuerySet 仅在迭代时求值。

django
# 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)

字段查找

查找使用双下划线语法。常用:exact, contains, icontains, gt, gte, lt, lte, in, startswith, range。

django
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 NULL

排序与切片

切片在数据库层面应用 LIMIT。QuerySet 不支持负索引。

django
# 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() 适用于批量更改。save(update_fields=[...]) 避免竞态条件。delete() 默认级联删除。

django
# 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 delete
05

ORM 进阶

F 表达式

F() 引用数据库列值,无需将对象加载到 Python 即可实现原子更新。

django
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 对象复杂查询

Q 对象支持 OR (|)、AND (&) 和 NOT (~) 逻辑。不用 Q 时,多个 filter() 调用仅支持 AND。

django
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)
)

聚合

aggregate() 返回总计字典。annotate() 为每个对象添加计算值。

django
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() 为每个对象添加计算字段。可以对注解字段使用 filter() 和 order_by()。

django
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 使用 SQL JOIN(正向 FK)。prefetch_related 使用第二个查询(反向/M2M)。两者都防止 N+1 查询。

django
# 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()
06

函数视图

基本函数视图

视图接收 HttpRequest 并返回 HttpResponse。render() 加载模板并用上下文填充。

django
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 对象

request.GET/POST 是 QueryDict(使用 .get() 更安全)。request.user 是已认证用户或 AnonymousUser。

django
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 类型

JsonResponse 序列化为 JSON。redirect() 接受 URL 路径或带参数的命名 URL。

django
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 response

视图装饰器

login_required 将未认证用户重定向到 LOGIN_URL。require_POST/GET 限制 HTTP 方法。

django
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 GET

文件上传视图

request.FILES 包含上传的文件。对大文件使用 .chunks() 避免将整个文件加载到内存。

django
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')
07

类视图

TemplateView

TemplateView 渲染静态模板。重写 get_context_data() 添加额外上下文。

django
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 context

ListView

ListView 自动分页并在上下文中提供 object_list 和 page_obj。重写 get_queryset() 进行过滤。

django
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 通过 URL 中的 pk 或 slug 获取单个对象。重写 get_object() 自定义获取逻辑。

django
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 处理表单显示、验证和保存。重写 form_valid() 在保存前添加逻辑。

django
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 为类视图添加可复用行为。Django 提供 LoginRequiredMixin、PermissionRequiredMixin。

django
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 = 10
08

URL 路由

基本 URL 配置

path() 将 URL 模式映射到视图。name= 启用反向 URL 查找。<int:pk> 捕获整数。

django
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'),
]

包含应用 URL

include() 将 URL 匹配委托给应用的 urls.py。app_name 为 URL 反向解析创建命名空间。

django
# 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'),
]

路径转换器

路径转换器自动类型转换 URL 片段。复杂正则模式使用 re_path。

django
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)

命名 URL 与 reverse()

命名 URL 允许通过名称引用路由而非硬编码路径。reverse() 在 Python 代码中生成 URL。

django
# 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 命名空间

命名空间防止应用间名称冲突。namespace 必须与应用的 app_name 匹配。

django
# 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 %}
09

模板 (DTL)

模板继承

extends 继承基础模板。block 定义可覆盖区域。一个模板只能继承一个父模板。

django
{# base.html #}
<html>
<body>
  {% block content %}{% endblock %}
  {% block sidebar %}{% endblock %}
</body>
</html>

{# article.html #}
{% extends 'base.html' %}
{% block content %}
  <h1>{{ article.title }}</h1>
{% endblock %}

变量与标签

{{ }} 输出变量。{% %} 执行标签。forloop.counter 从 1 开始;forloop.counter0 从 0 开始。

django
{# 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 %}

模板过滤器

过滤器使用管道 | 语法修改变量输出。有些在冒号后接受参数。过滤器可以链式使用。

django
{{ name|lower }}
{{ text|truncatewords:30 }}
{{ date|date:"Y-m-d" }}
{{ value|default:"N/A" }}
{{ list|length }}
{{ text|striptags }}
{{ price|floatformat:2 }}
{{ value|add:5 }}

内置模板标签

csrf_token 在所有 POST 表单中必需。include 渲染子模板。load 导入自定义标签库。

django
{% 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 %}

自定义模板标签

标签位于 app/templatetags/ 目录。@register.simple_tag 用于函数;@register.filter 用于值修饰器。

django
# 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:" " }}
10

表单

定义表单

forms.Form 创建独立表单。每个字段自动生成 HTML 控件。字段类型提供验证。

django
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 从模型自动构建表单。fields/exclude 控制显示哪些字段。widgets 自定义 HTML。

django
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'}

表单验证

clean_<field>() 验证单个字段。clean() 验证整个表单。验证失败时抛出 ValidationError。

django
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 cleaned

在模板中渲染表单

as_p/as_table/as_ul 自动渲染所有字段。自定义布局时逐个渲染字段。POST 需要 csrf_token。

django
<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>

在视图中处理表单

传入 request.POST 绑定表单。is_valid() 运行验证。save(commit=False) 返回未保存的对象。

django
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})

表单控件

widgets 控制 HTML 渲染。传入 attrs 字典设置 CSS 类、占位符或 HTML5 输入类型。

django
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,
    )
11

Admin 管理站点

在 Admin 中注册模型

@admin.register 优于 admin.site.register。模型必须注册才能在管理界面中显示。

django
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 自定义

list_display 在列表视图中显示列。list_filter 添加侧边栏过滤器。search_fields 启用搜索框。

django
@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')

内联 Admin

内联允许在同一页面编辑关联模型。extra=1 默认显示一个空行。

django
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 stacked

自定义 Admin 操作

操作出现在列表页的下拉菜单中。它们接收 request 和 queryset。message_user 显示闪现消息。

django
@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_header/site_title 进行品牌定制。如需多个管理站点,创建自定义 AdminSite 实例。

django
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)
12

认证

内置 User 模型

Django 的 User 模型包含 username、password(哈希)、email、first_name、last_name 和权限标志。

django
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/False

登录与登出

authenticate() 验证凭据(返回 User 或 None)。login() 创建会话。logout() 清除会话。

django
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')

用户注册

UserCreationForm 处理用户名、密码和密码确认,内置验证。

django
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 装饰器

login_required 重定向到 LOGIN_URL(默认 /accounts/login/)。类视图使用 LoginRequiredMixin。

django
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 = Article

密码管理

update_session_auth_hash 防止修改密码后登出。密码验证器全站强制规则。

django
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()

自定义 User 模型

继承 AbstractUser 添加字段。尽早设置 AUTH_USER_MODEL — 后期修改很困难。

django
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):
    pass
13

中间件

内置中间件

顺序很重要!SecurityMiddleware 在最前,然后是 sessions,再是 auth。CSRF 检查在视图处理前运行。

django
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',
]

自定义函数中间件

函数中间件使用新式工厂模式。get_response 调用下一个中间件或视图。

django
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 middleware

类中间件

__init__ 在启动时运行一次。__call__ 每次请求运行。process_view 可返回响应以绕过视图。

django
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-circuit

中间件顺序与流程

请求从上到下穿过中间件;响应从下到上冒泡。安全中间件放在最前。

django
# 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 -> A

中间件用例

中间件可短路(提前返回 HttpResponse)或将数据附加到 request 供视图使用。

django
# 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)
14

静态与媒体文件

静态文件配置

STATIC_URL 是 URL 前缀。STATICFILES_DIRS 是源位置(开发)。STATIC_ROOT 是生产收集目标。

django
# 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.png

开发中提供静态文件

Django 仅在 DEBUG=True 时自动提供静态文件。生产中使用 Web 服务器(nginx)或 WhiteNoise。

django
# 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=True

在模板中使用静态文件

需要 {% load static %}。{% static %} 在生产中解析为带哈希的文件名(使用 ManifestStaticFilesStorage)。

django
{% 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_ROOT 是文件系统路径;MEDIA_URL 是 URL 前缀。upload_to 可以是路径字符串或可调用对象。

django
# 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 命令

部署前运行 collectstatic。WhiteNoise 让 Django 在生产中无需 nginx 即可提供静态文件。

django
# 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'
15

会话与 Cookie

会话配置

默认会话引擎存储在数据库中。cached_db 写入缓存和数据库。signed_cookies 存储在客户端 cookie 中。

django
# settings.py
SESSION_ENGINE = 'django.contrib.sessions.backends.db'
SESSION_COOKIE_AGE = 86400  # seconds (1 day)
SESSION_SAVE_EVERY_REQUEST = False
SESSION_EXPIRE_AT_BROWSER_CLOSE = False
SESSION_COOKIE_SECURE = True  # HTTPS only (production)
SESSION_COOKIE_HTTPONLY = True  # JS cannot access

# Engines: db, cached_db, cache, file, signed_cookies

设置与获取会话数据

Session 类似字典。.get() 避免 KeyError。flush() 清除数据并创建新的会话密钥(用于登出)。

django
def set_session(request):
    request.session['user_id'] = 42
    request.session['theme'] = 'dark'
    request.session['cart'] = [1, 2, 3]

def get_session(request):
    user_id = request.session.get('user_id')       # None if missing
    user_id = request.session.get('user_id', 0)    # with default

    # Delete
    del request.session['theme']
    request.session.flush()  # clear all + regenerate key

会话后端

cache 最快但不稳定。cached_db 是不错的平衡。signed_cookies 有 4KB 限制但无需服务器存储。

django
# Database (default)
SESSION_ENGINE = 'django.contrib.sessions.backends.db'

# Cache (fastest, lost on restart)
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'

# Cache + DB (fast reads, persistent)
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'

# File-based
SESSION_ENGINE = 'django.contrib.sessions.backends.file'

# Signed cookies (no server storage, 4KB limit)
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'

Cookie

set_cookie 添加到响应。request.COOKIES 读取传入的 cookie。httponly 防止 JS 访问(XSS 防护)。

django
# Set a cookie
response = HttpResponse('Hello')
response.set_cookie('key', 'value', max_age=3600)
response.set_cookie(
    'pref', 'dark',
    max_age=86400, httponly=True,
    secure=True, samesite='Lax',
)

# Read a cookie
value = request.COOKIES.get('key')

# Delete a cookie
response.delete_cookie('key')

会话过期

set_expiry(0) = 浏览器关闭时过期。set_expiry(None) = 使用设置值。运行 clearsessions 作为定时任务清理。

django
# Set expiry for this session
request.session.set_expiry(1800)  # 30 minutes
request.session.set_expiry(0)     # expire on browser close
request.session.set_expiry(None)  # use SESSION_COOKIE_AGE

# Check expiry
request.session.get_expiry_age()
request.session.get_expire_at_browser_close()

# Clear expired sessions (cron job)
python manage.py clearsessions
16

信号

内置信号

信号允许解耦的应用响应操作。post_save 在 save() 完成后触发;pre_save 在之前触发。

django
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_out

连接信号接收器

@receiver 将函数连接到信号。sender 限定到特定模型。created 仅在新对象时为 True。

django
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()

断开信号

disconnect() 在测试中或需要阻止级联操作时很有用。务必同时提供 receiver 和 sender。

django
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)

自定义信号

在模块级别定义信号。send() 通知所有接收器。kwargs 传递给所有已连接的接收器。

django
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 notification

信号用例与注意事项

在 AppConfig.ready() 中导入信号处理器以确保启动时连接。避免慢操作 — 异步工作使用 Celery。

django
# 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  # noqa
17

缓存

缓存配置

生产环境推荐使用 Redis。LocMemCache 是进程本地的(worker 间不共享)。

django
# 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'}
}

低级缓存 API

cache.get() 对缺失的键返回 None。使用 get_or_set 实现原子性的不存在则设置。

django
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)

模板缓存

{% cache %} 存储渲染的 HTML 片段。添加变化参数(用户 ID、页面)避免提供错误的缓存内容。

django
{% 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 %}

视图级缓存

cache_page 缓存整个 HttpResponse。缓存键包含 URL,因此每个 URL 有独立的缓存条目。

django
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)

缓存策略

vary_on_headers 为每个头值创建独立缓存条目。API 应基于 Accept-Language 或 Authorization 变化。

django
# 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 articles
18

测试

编写测试

TestCase 将测试包装在数据库事务中(回滚)。SimpleTestCase 用于无数据库访问的测试(更快)。

django
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)

使用测试客户端

self.client 模拟 HTTP 请求而无需启动服务器。.login() 为后续请求进行认证。

django
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)

模型与 QuerySet 测试

测试数据在 setUp() 中创建并在每个测试后回滚。使用 .count() 和索引验证结果。

django
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 与表单测试

reverse() 从名称构建 URL。resolve() 检查 URL 映射到哪个视图。传入 data= 字典测试表单。

django
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 与测试命令

Fixtures 从 JSON 文件加载测试数据。通过应用、类或方法路径运行特定测试。

django
# 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.ArticleModelTest
19

Django REST Framework

安装与配置 DRF

将 rest_framework 添加到 INSTALLED_APPS。在 REST_FRAMEWORK 设置字典中配置默认认证/权限。

django
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',
    ],
}

序列化器

ModelSerializer 从模型自动生成字段。source= 从关联对象拉取数据。validate_<field>() 添加自定义验证。

django
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 value

ViewSets

ModelViewSet 提供 CRUD(list, create, retrieve, update, destroy)。重写 get_queryset() 进行过滤。

django
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)

路由器

DefaultRouter 自动生成 URL 模式和 API 根视图。queryset 动态时使用 basename 进行 URL 命名。

django
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}/

认证

Token 认证使用单个静态令牌。JWT 签发 access + refresh 令牌。SimpleJWT 是最流行的 JWT 库。

django
# 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()),
]

权限

SAFE_METHODS 为 GET/HEAD/OPTIONS。has_object_permission 检查每对象访问。get_permissions() 允许按操作规则。

django
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()
20

部署与安全

生产环境设置

切勿将 SECRET_KEY 提交到版本控制。使用环境变量。DEBUG=False 防止错误页面中的信息泄露。

django
# 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',
    }
}

生产中的静态文件

collectstatic 将所有静态文件收集到 STATIC_ROOT。WhiteNoise 从 Django 提供 gzip 压缩的哈希文件。

django
# 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/; }

安全设置

SSL_REDIRECT 强制 HTTPS。HSTS 告诉浏览器始终使用 HTTPS。X_FRAME_OPTIONS 防止点击劫持。

django
# 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 部署

Gunicorn 是同步的(传统 Django)。Uvicorn 支持 ASGI(异步视图、WebSocket)。使用进程管理器。

django
# 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=true

生产检查清单

check --deploy 运行 Django 的部署检查。它标记缺失的安全设置和生产就绪问题。

django
# 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)

这篇内容对您有帮助吗?

学习路径

从零开始学习

通过结构化课程从头学习这个语言。