Python 框架 Django 从入门到精通
目录
- [一、Django 简介](#一、Django 简介)
- 二、环境搭建
- 三、项目创建与结构解析
- [四、URL 路由系统](#四、URL 路由系统)
- 五、视图层(Views)
- 六、模板层(Templates)
- [七、模型层(Models)与 ORM](#七、模型层(Models)与 ORM)
- [八、Admin 后台管理](#八、Admin 后台管理)
- 九、表单处理
- 十、中间件(Middleware)
- [十一、REST API 开发(DRF)](#十一、REST API 开发(DRF))
- 十二、认证与权限
- 十三、缓存机制
- 十四、信号(Signals)
- [十五、异步任务(Celery 集成)](#十五、异步任务(Celery 集成))
- 十六、部署与优化
- 十七、最佳实践与常见陷阱
一、Django 简介
1.1 什么是 Django
Django 是一个基于 Python 的高级 Web 框架,遵循 DRY(Don't Repeat Yourself)原则,鼓励快速开发和简洁实用的设计。它由劳伦斯出版集团开发,最初用于管理其新闻网站的内容,于 2005 年开源。
1.2 核心特性
| 特性 | 说明 |
|---|---|
| ORM | 对象关系映射,用 Python 类操作数据库 |
| Admin | 开箱即用的后台管理系统 |
| URL 路由 | 灵活的 URL 配置系统 |
| 模板引擎 | 内置模板系统,支持继承和标签 |
| Middleware | 请求/响应钩子机制 |
| 国际化 | 内置多语言支持 |
| 安全性 | 自动防范 CSRF、XSS、SQL 注入等攻击 |
1.3 MVT 架构
Django 采用 MVT(Model-View-Template)架构:
┌─────────┐ ┌─────────┐ ┌───────────┐ ┌─────────┐
│ 用户 │────>│ URL │────>│ View │────>│ Model │
│ 浏览器 │<────│ Router │<────│ 业务逻辑 │<────│ 数据访问 │
└─────────┘ └─────────┘ └─────┬─────┘ └─────────┘
│
v
┌───────────┐
│ Template │
│ HTML渲染 │
└───────────┘
- Model:数据模型,定义数据库表结构
- View:业务逻辑,处理请求并返回响应
- Template:前端模板,渲染 HTML 页面
二、环境搭建
2.1 创建虚拟环境
bash
# 创建虚拟环境
python -m venv myenv
# 激活(macOS / Linux)
source myenv/bin/activate
# 激活(Windows)
myenv\Scripts\activate
# 退出虚拟环境
deactivate
2.2 安装 Django
bash
pip install django==5.1
# 验证安装
python -m django --version
2.3 项目与应用的关系
myproject/ # 项目容器
├── manage.py # 命令行工具
├── myproject/ # 项目配置目录
│ ├── __init__.py
│ ├── settings.py # 项目配置
│ ├── urls.py # 根 URL 配置
│ ├── asgi.py # ASGI 入口
│ └── wsgi.py # WSGI 入口
├── app1/ # 应用 1
│ ├── models.py
│ ├── views.py
│ ├── urls.py
│ └── ...
└── app2/ # 应用 2
└── ...
一个 Django 项目可以包含多个应用,每个应用负责一个独立的功能模块。
三、项目创建与结构解析
3.1 创建项目
bash
django-admin startproject myproject
cd myproject
3.2 创建应用
bash
python manage.py startapp blog
3.3 注册应用
在 settings.py 中注册:
python
# myproject/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog', # 注册自定义应用
]
3.4 常用管理命令
bash
# 启动开发服务器
python manage.py runserver
# 指定端口
python manage.py runserver 0.0.0.0:8000
# 数据库迁移
python manage.py makemigrations
python manage.py migrate
# 创建超级管理员
python manage.py createsuperuser
# 启动 Shell
python manage.py shell
# 收集静态文件
python manage.py collectstatic
# 查看已注册的 SQL
python manage.py sqlmigrate blog 0001
四、URL 路由系统
4.1 基本路由配置
python
# blog/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('articles/', views.article_list, name='article_list'),
path('articles/<int:id>/', views.article_detail, name='article_detail'),
path('articles/<int:year>/<int:month>/', views.article_archive, name='article_archive'),
]
4.2 根路由分发
python
# myproject/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('blog/', include('blog.urls')),
path('api/', include('api.urls')),
]
4.3 路径转换器
python
urlpatterns = [
path('articles/<int:pk>/', views.detail), # 整数
path('articles/<slug:title>/', views.detail), # slug 字符串
path('articles/<uuid:id>/', views.detail), # UUID
path('files/<path:filepath>/', views.download), # 路径(含 /)
]
4.4 使用 re_path 正则匹配
python
from django.urls import re_path
urlpatterns = [
re_path(r'^articles/(?P<year>\d{4})/$', views.year_archive),
re_path(r'^articles/(?P<year>\d{4})/(?P<month>\d{2})/$', views.month_archive),
]
4.5 反向解析 URL
在模板中:
html
<a href="{% url 'article_detail' id=article.id %}">查看详情</a>
在视图/模型中:
python
from django.urls import reverse
from django.shortcuts import redirect
def create_article(request):
article = Article.objects.create(title="test")
return redirect(reverse('article_detail', args=[article.id]))
# 或简写
return redirect('article_detail', pk=article.id)
4.6 include 的 namespace
python
# myproject/urls.py
urlpatterns = [
path('blog/', include('blog.urls', namespace='blog')),
]
# 模板中使用
<a href="{% url 'blog:article_detail' pk=1 %}">详情</a>
五、视图层(Views)
5.1 函数视图(FBV)
python
from django.shortcuts import render, get_object_or_404, redirect
from django.http import HttpResponse, JsonResponse, Http404
from .models import Article
def article_list(request):
articles = Article.objects.all().order_by('-created_at')
return render(request, 'blog/article_list.html', {'articles': articles})
def article_detail(request, pk):
article = get_object_or_404(Article, pk=pk)
return render(request, 'blog/article_detail.html', {'article': article})
def create_article(request):
if request.method == 'POST':
title = request.POST.get('title')
content = request.POST.get('content')
Article.objects.create(title=title, content=content)
return redirect('article_list')
return render(request, 'blog/article_form.html')
5.2 请求对象(HttpRequest)
python
def demo(request):
# 请求方法
request.method # 'GET', 'POST', 'PUT', 'DELETE'
# GET 参数
request.GET.get('page', 1)
# POST 数据(表单)
request.POST.get('username')
# JSON 请求体
import json
data = json.loads(request.body)
# 文件上传
file = request.FILES.get('avatar')
# 请求头
request.META.get('HTTP_USER_AGENT')
# Cookies
request.COOKIES.get('session_id')
# 用户信息
request.user # 当前登录用户(需要中间件)
# 完整 URL
request.get_full_path()
5.3 响应对象
python
# HTML 响应
from django.shortcuts import render
return render(request, 'template.html', context={'key': 'value'})
# JSON 响应
from django.http import JsonResponse
return JsonResponse({'status': 'ok', 'data': list})
# 重定向
from django.shortcuts import redirect
return redirect('/some/url/')
return redirect('named_url', arg1=value1)
# 文件下载
from django.http import FileResponse
return FileResponse(open('file.pdf', 'rb'), as_attachment=True)
# 自定义响应头
from django.http import HttpResponse
response = HttpResponse('content')
response['X-Custom-Header'] = 'value'
return response
5.4 类视图(CBV)
python
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from django.urls import reverse_lazy
from .models import Article
from .forms import ArticleForm
class ArticleListView(ListView):
model = Article
template_name = 'blog/article_list.html'
context_object_name = 'articles'
paginate_by = 10
ordering = ['-created_at']
class ArticleDetailView(DetailView):
model = Article
template_name = 'blog/article_detail.html'
context_object_name = 'article'
class ArticleCreateView(CreateView):
model = Article
form_class = ArticleForm
template_name = 'blog/article_form.html'
success_url = reverse_lazy('article_list')
class ArticleUpdateView(UpdateView):
model = Article
form_class = ArticleForm
template_name = 'blog/article_form.html'
class ArticleDeleteView(DeleteView):
model = Article
success_url = reverse_lazy('article_list')
CBV 路由配置:
python
urlpatterns = [
path('articles/', ArticleListView.as_view(), name='article_list'),
path('articles/<int:pk>/', ArticleDetailView.as_view(), name='article_detail'),
path('articles/create/', ArticleCreateView.as_view(), name='article_create'),
path('articles/<int:pk>/edit/', ArticleUpdateView.as_view(), name='article_update'),
path('articles/<int:pk>/delete/', ArticleDeleteView.as_view(), name='article_delete'),
]
5.5 CBV Mixin 扩展
python
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.views.generic import UpdateView
class ArticleUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Article
form_class = ArticleForm
def test_func(self):
"""只有文章作者才能编辑"""
return self.get_object().author == self.request.user
def form_valid(self, form):
"""自动设置作者"""
form.instance.author = self.request.user
return super().form_valid(form)
5.6 装饰器
python
from django.contrib.auth.decorators import login_required, permission_required
from django.views.decorators.http import require_http_methods, require_POST
from django.views.decorators.csrf import csrf_exempt
@login_required(login_url='/login/')
def dashboard(request):
...
@permission_required('blog.change_article', raise_exception=True)
def edit_article(request, pk):
...
@require_POST
def like_article(request, pk):
...
@csrf_exempt # 豁免 CSRF 验证(用于 API)
def api_endpoint(request):
...
六、模板层(Templates)
6.1 模板配置
python
# settings.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'], # 模板目录
'APP_DIRS': True, # 自动搜索各 app 的 templates 目录
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
6.2 模板继承
html
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{% block title %}My Site{% endblock %}</title>
{% load static %}
<link rel="stylesheet" href="{% static 'css/style.css' %}">
{% block extra_css %}{% endblock %}
</head>
<body>
<header>{% include 'header.html' %}</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>{% include 'footer.html' %}</footer>
{% block extra_js %}{% endblock %}
</body>
</html>
html
<!-- blog/templates/blog/article_list.html -->
{% extends 'base.html' %}
{% block title %}文章列表 - My Blog{% endblock %}
{% block content %}
<h1>文章列表</h1>
<ul>
{% for article in articles %}
<li>
<a href="{{ article.get_absolute_url }}">{{ article.title }}</a>
<span>{{ article.created_at|date:"Y-m-d" }}</span>
</li>
{% empty %}
<li>暂无文章</li>
{% endfor %}
</ul>
{% endblock %}
6.3 常用模板标签和过滤器
html
<!-- 变量输出 -->
{{ article.title }}
<!-- 过滤器 -->
{{ content|truncatewords:30 }} <!-- 截断为 30 个单词 -->
{{ price|floatformat:2 }} <!-- 保留两位小数 -->
{{ date|date:"Y年m月d日" }} <!-- 日期格式化 -->
{{ name|default:"匿名" }} <!-- 默认值 -->
{{ content|safe }} <!-- 渲染 HTML(不转义) -->
{{ items|length }} <!-- 长度 -->
{{ list|join:", " }} <!-- 用逗号连接 -->
{{ value|upper }} <!-- 转大写 -->
{{ content|linebreaks }} <!-- 换行转 <br> -->
{{ value|filesizeformat }} <!-- 文件大小格式化 -->
<!-- 逻辑标签 -->
{% if user.is_authenticated %}
<p>欢迎, {{ user.username }}</p>
{% else %}
<p>请 <a href="{% url 'login' %}">登录</a></p>
{% endif %}
{% for item in items %}
{{ forloop.counter }}. {{ item.name }}
{% empty %}
暂无数据
{% endfor %}
{% for i in "xxxx" %}
{{ i }}
{% endfor %}
<!-- URL 反向解析 -->
{% url 'article_detail' pk=article.id %}
<!-- 静态文件 -->
{% load static %}
<link href="{% static 'css/style.css' %}" rel="stylesheet">
<img src="{% static 'images/logo.png' %}">
<!-- 注释 -->
{# 单行注释 #}
{% comment %}
多行注释
{% endcomment %}
<!-- 自定义标签/过滤器需要先 load -->
{% load my_tags %}
{{ value|my_filter }}
{% my_tag arg1 arg2 %}
6.4 自定义模板过滤器
python
# blog/templatetags/__init__.py # 空文件
# blog/templatetags/blog_extras.py
from django import template
register = template.Library()
@register.filter
def truncate_chars(value, max_length):
if len(value) > max_length:
return value[:max_length] + '...'
return value
@register.filter(name='add_class')
def add_css_class(value, css_class):
return value.as_widget(attrs={'class': css_class})
@register.simple_tag
def get_recent_articles(count=5):
from blog.models import Article
return Article.objects.order_by('-created_at')[:count]
模板中使用:
html
{% load blog_extras %}
{{ article.content|truncate_chars:100 }}
{{ form.username|add_class:"form-control" }}
{% get_recent_articles 10 as recent %}
七、模型层(Models)与 ORM
7.1 定义模型
python
from django.db import models
from django.contrib.auth.models import User
class Category(models.Model):
name = models.CharField('分类名', max_length=100, unique=True)
description = models.TextField('描述', blank=True, default='')
created_at = models.DateTimeField('创建时间', auto_now_add=True)
class Meta:
verbose_name = '分类'
verbose_name_plural = '分类'
ordering = ['name']
def __str__(self):
return self.name
class Article(models.Model):
STATUS_CHOICES = [
('draft', '草稿'),
('published', '已发布'),
('archived', '已归档'),
]
title = models.CharField('标题', max_length=200)
content = models.TextField('内容')
summary = models.CharField('摘要', max_length=500, blank=True)
author = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='作者')
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True)
tags = models.ManyToManyField('Tag', blank=True, verbose_name='标签')
status = models.CharField('状态', max_length=20, choices=STATUS_CHOICES, default='draft')
view_count = models.PositiveIntegerField('浏览量', default=0)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
updated_at = models.DateTimeField('更新时间', auto_now=True)
is_deleted = models.BooleanField('已删除', default=False)
class Meta:
verbose_name = '文章'
verbose_name_plural = '文章'
ordering = ['-created_at']
indexes = [
models.Index(fields=['-created_at']),
models.Index(fields=['author', 'status']),
]
def __str__(self):
return self.title
def get_absolute_url(self):
from django.urls import reverse
return reverse('article_detail', args=[self.id])
7.2 字段类型大全
python
# 字符串
CharField(max_length=N) # 短字符串
TextField() # 长文本
# 数字
IntegerField() # 整数
BigIntegerField() # 大整数
FloatField() # 浮点数
DecimalField(max_digits=N, decimal_places=M) # 精确小数
PositiveIntegerField() # 正整数
PositiveSmallIntegerField() # 小正整数
# 布尔
BooleanField() # 布尔值
NullBooleanField() # 可为空的布尔值(Django 4+ 已废弃,用 BooleanField(null=True))
# 日期时间
DateField() # 日期
DateTimeField() # 日期时间
TimeField() # 时间
DurationField() # 时间间隔
# 文件
FileField(upload_to='uploads/') # 文件
ImageField(upload_to='images/') # 图片(需要 Pillow)
# 关系字段
ForeignKey(Model, on_delete=...) # 多对一
OneToOneField(Model, on_delete=...) # 一对一
ManyToManyField(Model) # 多对多
# 特殊
UUIDField() # UUID
SlugField() # URL 友好字符串
GenericIPAddressField() # IP 地址
JSONField() # JSON 数据
BinaryField() # 二进制数据
# 常用参数
null=True # 数据库允许 NULL
blank=True # 表单验证允许为空
default=N # 默认值
unique=True # 唯一约束
db_index=True # 数据库索引
choices=[...] # 选项
verbose_name='中文' # 字段人类可读名称
help_text='帮助文本' # 表单帮助文本
auto_now=True # 每次保存自动更新
auto_now_add=True # 创建时自动设置
7.3 关系字段
python
# 一对多:ForeignKey
class Article(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)
# on_delete 选项
CASCADE # 级联删除
SET_NULL # 设为 NULL(需要 null=True)
SET_DEFAULT # 设为默认值(需要 default)
PROTECT # 阻止删除,抛异常
DO_NOTHING # 不做任何操作
# 反向查询
user.articles.all() # 默认: model_name_set
user.article_set.all()
# 自定义反向查询名称
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='articles')
# 多对多:ManyToManyField
class Article(models.Model):
tags = models.ManyToManyField('Tag', blank=True)
article.tags.add(tag)
article.tags.remove(tag)
article.tags.clear()
article.tags.set([tag1, tag2])
# 多对多带额外字段
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
date_joined = models.DateField()
class Person(models.Model):
groups = models.ManyToManyField(Group, through='Membership')
7.4 ORM 查询
python
# 基础查询
Article.objects.all() # 所有记录
Article.objects.filter(status='published') # 过滤
Article.objects.exclude(status='draft') # 排除
Article.objects.get(pk=1) # 获取单个(不存在抛异常)
Article.objects.get_or_create(title='test') # 获取或创建
# 链式查询
Article.objects.filter(status='published').filter(author__username='admin')
# 字段查找
Article.objects.filter(title__contains='Django')
Article.objects.filter(title__icontains='django') # 不区分大小写
Article.objects.filter(title__startswith='Django')
Article.objects.filter(title__endswith='教程')
Article.objects.filter(view_count__gte=100) # 大于等于
Article.objects.filter(view_count__lt=100) # 小于
Article.objects.filter(created_at__year=2024)
Article.objects.filter(created_at__date__gte='2024-01-01')
Article.objects.filter(title__in=['Django', 'Flask'])
Article.objects.filter(title__isnull=True)
# Q 对象(复杂查询)
from django.db.models import Q
Article.objects.filter(Q(title__contains='Django') | Q(title__contains='Flask'))
Article.objects.filter(Q(status='published') & ~Q(author__username='spam'))
# F 对象(字段间比较)
from django.db.models import F
Article.objects.filter(view_count__gt=F('comment_count') * 10)
Article.objects.update(view_count=F('view_count') + 1)
# 排序
Article.objects.order_by('-created_at')
Article.objects.order_by('created_at', '-title')
# 切片(LIMIT / OFFSET)
Article.objects.all()[:10] # 前 10 条
Article.objects.all()[10:20] # 11-20 条
# 聚合
from django.db.models import Count, Sum, Avg, Max, Min
Article.objects.aggregate(avg_views=Avg('view_count'))
Article.objects.annotate(comment_count=Count('comment'))
# 分组统计
from django.db.models import Count
Category.objects.annotate(article_count=Count('article'))
# 去重
Article.objects.filter(tags__name='Python').distinct()
# 日期查询
from django.utils import timezone
Article.objects.filter(created_at__gte=timezone.now() - timedelta(days=7))
# 选择字段
Article.objects.values('id', 'title')
Article.objects.values_list('id', 'title')
Article.objects.values_list('id', flat=True) # 只返回单值列表
# exists
Article.objects.filter(title='test').exists() # 是否存在
# 批量操作
Article.objects.filter(status='draft').delete()
Article.objects.filter(status='draft').update(status='archived')
Article.objects.bulk_create([...]) # 批量创建
7.5 执行原生 SQL
python
# Manager.raw() ------ 返回 Model 实例
for article in Article.objects.raw('SELECT * FROM blog_article WHERE status = %s', ['published']):
print(article.title)
# connection.cursor() ------ 执行任意 SQL
from django.db import connection
with connection.cursor() as cursor:
cursor.execute("SELECT COUNT(*) FROM blog_article WHERE status = %s", ['published'])
count = cursor.fetchone()[0]
7.6 数据库迁移
bash
# 生成迁移文件
python manage.py makemigrations
# 执行迁移
python manage.py migrate
# 查看迁移状态
python manage.py showmigrations
# 回滚到指定迁移
python manage.py migrate blog 0002
# 查看迁移 SQL
python manage.py sqlmigrate blog 0003
自定义迁移操作:
python
# migrations/0004_add_initial_data.py
from django.db import migrations
def create_initial_categories(apps, schema_editor):
Category = apps.get_model('blog', 'Category')
Category.objects.bulk_create([
Category(name='Python'),
Category(name='Django'),
Category(name='数据库'),
])
class Migration(migrations.Migration):
dependencies = [
('blog', '0003_auto_20240101_0000'),
]
operations = [
migrations.RunPython(create_initial_categories),
]
八、Admin 后台管理
8.1 注册模型
python
# blog/admin.py
from django.contrib import admin
from .models import Article, Category, Tag
admin.site.register(Category)
admin.site.register(Tag)
8.2 自定义 Admin 配置
python
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
list_display = ['id', 'title', 'author', 'status', 'view_count', 'created_at']
list_display_links = ['id', 'title']
list_filter = ['status', 'category', 'created_at']
search_fields = ['title', 'content']
list_editable = ['status']
list_per_page = 20
ordering = ['-created_at']
date_hierarchy = 'created_at'
actions = ['make_published', 'make_draft']
filter_horizontal = ['tags']
fieldsets = (
('基本信息', {
'fields': ('title', 'author', 'category', 'tags')
}),
('内容', {
'fields': ('content', 'summary'),
'classes': ('wide',)
}),
('设置', {
'fields': ('status',)
}),
)
readonly_fields = ['view_count', 'created_at', 'updated_at']
@admin.display(description='是否热门')
def is_hot(self, obj):
return obj.view_count > 1000
is_hot.boolean = True
@admin.action(description='发布选中文章')
def make_published(self, request, queryset):
updated = queryset.update(status='published')
self.message_user(request, f'{updated} 篇文章已发布')
@admin.action(description='转为草稿')
def make_draft(self, request, queryset):
updated = queryset.update(status='draft')
self.message_user(request, f'{updated} 篇文章已转为草稿')
九、表单处理
9.1 ModelForm
python
# blog/forms.py
from django import forms
from .models import Article, Comment
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ['title', 'category', 'tags', 'content', 'summary']
widgets = {
'content': forms.Textarea(attrs={'class': 'editor', 'rows': 10}),
'tags': forms.CheckboxSelectMultiple(),
}
def clean_title(self):
title = self.cleaned_data['title']
if len(title) < 5:
raise forms.ValidationError('标题至少 5 个字符')
return title
9.2 在视图中使用表单
python
from django.shortcuts import render, redirect
from .forms import ArticleForm
def create_article(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)
else:
form = ArticleForm()
return render(request, 'blog/article_form.html', {'form': form})
9.3 模板中渲染表单
html
<form method="post">
{% csrf_token %}
{{ form.as_p }}
{# 手动渲染每个字段 #}
<div>
{{ form.title.label_tag }}
{{ form.title }}
{% if form.title.errors %}
<span class="error">{{ form.title.errors }}</span>
{% endif %}
</div>
<button type="submit">保存</button>
</form>
9.4 纯 Django Form(不关联模型)
python
class ContactForm(forms.Form):
name = forms.CharField(max_length=100, label='姓名')
email = forms.EmailField(label='邮箱')
message = forms.CharField(widget=forms.Textarea, label='留言')
def clean_email(self):
email = self.cleaned_data['email']
if not email.endswith('@company.com'):
raise forms.ValidationError('请使用公司邮箱')
return email
十、中间件(Middleware)
10.1 自定义中间件
python
# middleware.py
import time
import logging
logger = logging.getLogger(__name__)
class RequestTimingMiddleware:
"""记录请求耗时"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
start_time = time.time()
response = self.get_response(request)
duration = time.time() - start_time
logger.info(f'{request.method} {request.path} - {duration:.3f}s')
return response
class ApiKeyMiddleware:
"""API Key 验证"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if request.path.startswith('/api/'):
api_key = request.META.get('HTTP_X_API_KEY')
if not api_key or api_key != 'expected-key':
from django.http import JsonResponse
return JsonResponse({'error': 'Invalid API key'}, status=401)
return self.get_response(request)
10.2 注册中间件
python
# settings.py
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',
'middleware.RequestTimingMiddleware',
'middleware.ApiKeyMiddleware',
]
十一、REST API 开发(DRF)
11.1 安装 DRF
bash
pip install djangorestframework
11.2 序列化器(Serializer)
python
# blog/serializers.py
from rest_framework import serializers
from .models import Article, Category
class CategorySerializer(serializers.ModelSerializer):
article_count = serializers.IntegerField(read_only=True)
class Meta:
model = Category
fields = ['id', 'name', 'description', 'article_count']
class ArticleListSerializer(serializers.ModelSerializer):
category_name = serializers.CharField(source='category.name', read_only=True)
author_name = serializers.CharField(source='author.username', read_only=True)
class Meta:
model = Article
fields = ['id', 'title', 'author_name', 'category_name', 'status',
'view_count', 'created_at']
class ArticleDetailSerializer(serializers.ModelSerializer):
category = CategorySerializer(read_only=True)
tags = serializers.StringRelatedField(many=True, read_only=True)
author_name = serializers.CharField(source='author.username', read_only=True)
class Meta:
model = Article
fields = '__all__'
11.3 视图集(ViewSet)
python
# blog/api_views.py
from rest_framework import viewsets, status, permissions
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from .models import Article
from .serializers import ArticleListSerializer, ArticleDetailSerializer
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.select_related('author', 'category').prefetch_related('tags')
permission_classes = [IsAuthenticatedOrReadOnly]
def get_serializer_class(self):
if self.action == 'list':
return ArticleListSerializer
return ArticleDetailSerializer
def perform_create(self, serializer):
serializer.save(author=self.request.user)
@action(detail=False, methods=['get'])
def published(self, request):
articles = self.queryset.filter(status='published')
serializer = self.get_serializer(articles, many=True)
return Response(serializer.data)
@action(detail=True, methods=['post'])
def publish(self, request, pk=None):
article = self.get_object()
article.status = 'published'
article.save()
return Response({'status': 'published'})
11.4 路由配置
python
# myproject/urls.py
from rest_framework.routers import DefaultRouter
from blog.api_views import ArticleViewSet
router = DefaultRouter()
router.register(r'articles', ArticleViewSet, basename='article')
urlpatterns = [
path('api/', include(router.urls)),
path('api-auth/', include('rest_framework.urls')),
]
11.5 DRF 分页与过滤
python
# settings.py
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10,
'DEFAULT_FILTER_BACKENDS': [
'rest_framework.filters.SearchFilter',
'rest_framework.filters.OrderingFilter',
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
],
}
# 视图中启用搜索和排序
class ArticleViewSet(viewsets.ModelViewSet):
search_fields = ['title', 'content']
ordering_fields = ['created_at', 'view_count']
ordering = ['-created_at']
十二、认证与权限
12.1 用户认证
python
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required
def login_view(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
user = authenticate(request, username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
return redirect('dashboard')
return render(request, 'login.html', {'error': '用户名或密码错误'})
return render(request, 'login.html')
@login_required
def dashboard(request):
return render(request, 'dashboard.html')
def logout_view(request):
logout(request)
return redirect('login')
12.2 自定义认证后端
python
# auth/backends.py
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q
class EmailOrUsernameBackend(ModelBackend):
"""支持邮箱或用户名登录"""
def authenticate(self, request, username=None, password=None, **kwargs):
if username is None:
username = kwargs.get('username')
if password is None:
password = kwargs.get('password')
UserModel = get_user_model()
try:
user = UserModel.objects.get(Q(username=username) | Q(email=username))
if user.check_password(password) and self.user_can_authenticate(user):
return user
except UserModel.DoesNotExist:
return None
python
# settings.py
AUTHENTICATION_BACKENDS = [
'auth.backends.EmailOrUsernameBackend',
'django.contrib.auth.backends.ModelBackend',
]
12.3 Token 认证
bash
pip install rest_framework.authtoken
python
# 自动创建 Token
from django.db.models.signals import post_save
from django.dispatch import receiver
from rest_framework.authtoken.models import Token
@receiver(post_save, sender=User)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if created:
Token.objects.create(user=instance)
# 客户端请求时携带 Header: Authorization: Token xxxxxxxx
十三、缓存机制
13.1 缓存配置
python
# settings.py
# 方式 1:本地内存缓存(开发用)
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique-snowflake',
}
}
# 方式 2:Redis 缓存(生产用)
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
}
}
13.2 缓存使用
python
from django.core.cache import cache
# 基础用法
cache.set('key', 'value', timeout=60)
data = cache.get('key')
cache.delete('key')
cache.set_many({'key1': 'val1', 'key2': 'val2'})
data = cache.get_many(['key1', 'key2'])
# 视图缓存装饰器
from django.views.decorators.cache import cache_page
@cache_page(60 * 15) # 缓存 15 分钟
def article_list(request):
...
# 模板片段缓存
{% load cache %}
{% cache 500 sidebar %}
{% get_recent_articles as recent %}
{% for article in recent %}
<li>{{ article.title }}</li>
{% endfor %}
{% endcache %}
十四、信号(Signals)
14.1 内置信号
python
from django.db.models.signals import post_save, pre_delete
from django.dispatch import receiver
from .models import Article
@receiver(post_save, sender=Article)
def on_article_created(sender, instance, created, **kwargs):
if created:
from .tasks import send_notification
send_notification.delay(instance.id)
# 清除相关缓存
from django.core.cache import cache
cache.delete('article_list')
@receiver(pre_delete, sender=Article)
def on_article_delete(sender, instance, **kwargs):
# 删除前清理关联数据
instance.tags.clear()
14.2 自定义信号
python
# signals.py
from django.dispatch import Signal
article_viewed = Signal() # 文章被浏览
# 在视图或模型中发送信号
article_viewed.send(sender=Article, article=article, user=request.user)
# 接收信号
@receiver(article_viewed, sender=Article)
def track_article_view(sender, article, user, **kwargs):
Article.objects.filter(pk=article.pk).update(view_count=F('view_count') + 1)
十五、异步任务(Celery 集成)
15.1 安装配置
bash
pip install celery redis
python
# myproject/celery.py
import os
from celery import Celery
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
app = Celery('myproject')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()
python
# settings.py
CELERY_BROKER_URL = 'redis://localhost:6379/0'
CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TIMEZONE = 'Asia/Shanghai'
15.2 定义任务
python
# blog/tasks.py
from celery import shared_task
from django.core.mail import send_mail
@shared_task
def send_welcome_email(user_id):
from django.contrib.auth.models import User
user = User.objects.get(id=user_id)
send_mail(
'欢迎加入',
f'你好 {user.username},感谢注册!',
'noreply@example.com',
[user.email],
)
@shared_task
def generate_article_summary(article_id):
from .models import Article
article = Article.objects.get(id=article_id)
summary = article.content[:200] + '...'
article.summary = summary
article.save()
# 调用任务
send_welcome_email.delay(user.id)
generate_article_summary.apply_async(args=[article.id], countdown=60) # 60秒后执行
15.3 启动 Celery
bash
# 启动 Worker
celery -A myproject worker -l info
# 启动 Beat(定时任务调度器)
celery -A myproject beat -l info
15.4 定时任务
python
# myproject/celery.py
from celery.schedules import crontab
app.conf.beat_schedule = {
'cleanup-expired-sessions': {
'task': 'myproject.tasks.cleanup_expired_sessions',
'schedule': crontab(hour=3, minute=0), # 每天凌晨 3 点
},
'send-daily-report': {
'task': 'myproject.tasks.send_daily_report',
'schedule': crontab(hour=8, minute=0, day_of_week='mon-fri'),
},
}
十六、部署与优化
16.1 关闭 Debug 模式
python
# settings.py
DEBUG = False
ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com']
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
16.2 静态文件收集
python
# settings.py
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATICFILES_DIRS = [
BASE_DIR / 'static',
]
bash
python manage.py collectstatic
16.3 使用 Gunicorn 部署
bash
pip install gunicorn
gunicorn myproject.wsgi:application \
--bind 0.0.0.0:8000 \
--workers 4 \
--worker-class gthread \
--threads 2 \
--timeout 120 \
--access-logfile - \
--error-logfile -
16.4 使用 Uvicorn + ASGI
bash
pip install uvicorn
uvicorn myproject.asgi:application --host 0.0.0.0 --port 8000 --workers 4
16.5 Nginx 配置
nginx
server {
listen 80;
server_name yourdomain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
server_name yourdomain.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location /static/ {
alias /path/to/staticfiles/;
expires 30d;
}
location /media/ {
alias /path/to/media/;
}
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
16.6 性能优化清单
1. 数据库优化
- 使用 select_related() 减少一对多查询的 SQL 次数
- 使用 prefetch_related() 减少多对多查询的 SQL 次数
- 添加合适的数据库索引
- 使用 only() / defer() 限制查询字段
- 使用 iterator() 处理大数据集,减少内存
2. 缓存策略
- 视图缓存:@cache_page
- 模板片段缓存:{% cache %}
- 数据查询缓存:cache.set/get
- 使用 Redis 作为缓存后端
3. 查询优化
- 避免在循环中执行查询(N+1 问题)
- 使用 bulk_create 批量插入
- 使用 update() 批量更新
- 使用 count() 或 exists() 代替 len()
4. 模板优化
- 使用 {% load cached %} 缓存模板加载
- 使用 compressed 模板加载器
- 避免在模板中执行复杂逻辑
5. 部署优化
- 开启 gzip 压缩
- 配置 CDN 托管静态文件
- 使用多个 Worker 进程
- 设置合适的 Worker 超时时间
十七、最佳实践与常见陷阱
17.1 项目结构推荐
myproject/
├── manage.py
├── myproject/
│ ├── settings/
│ │ ├── __init__.py
│ │ ├── base.py # 公共配置
│ │ ├── development.py # 开发环境
│ │ ├── testing.py # 测试环境
│ │ └── production.py # 生产环境
│ ├── urls.py
│ └── wsgi.py
├── apps/
│ ├── __init__.py
│ ├── blog/ # 博客应用
│ ├── users/ # 用户应用
│ └── common/ # 公共工具
├── config/
│ ├── celery.py
│ └── middleware.py
├── templates/
│ ├── base.html
│ └── blog/
├── static/
│ ├── css/
│ ├── js/
│ └── images/
├── tests/
│ ├── test_blog/
│ └── test_users/
└── requirements/
├── base.txt
├── dev.txt
└── prod.txt
17.2 settings 分环境管理
python
# settings/base.py
import os
BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
INSTALLED_APPS = [...]
DATABASES = {...}
# settings/development.py
from .base import *
DEBUG = True
ALLOWED_HOSTS = ['*']
# settings/production.py
from .base import *
DEBUG = False
ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split(',')
bash
# 启动时指定
DJANGO_SETTINGS_MODULE=myproject.settings.production python manage.py runserver
17.3 常见陷阱
python
# 1. N+1 查询问题
# 错误:循环中执行查询
for article in Article.objects.all():
print(article.category.name) # 每次循环都会查询数据库
# 正确:使用 select_related
for article in Article.objects.select_related('category').all():
print(article.category.name) # 只执行 1 次 SQL
# 2. 修改查询集不会立即执行
qs = Article.objects.all()
qs = qs.filter(status='published') # 不会查数据库
list(qs) # 此时才执行查询
# 3. 时区问题
from django.utils import timezone
now = timezone.now() # 始终使用 aware datetime
# 4. 不要在视图外直接调用 ORM
# models.py 中不要导入 views.py 中的内容,避免循环引用
# 5. 表单中 CSRF 保护
# POST 请求模板中必须包含 {% csrf_token %}
# 6. 静态文件在 DEBUG=False 时需要 collectstatic
# 7. migration 冲突
# 多人开发时,生成 migration 后及时提交
# 如果冲突,删除多余文件,重新生成
17.4 编写测试
python
# tests/test_blog.py
from django.test import TestCase, Client
from django.urls import reverse
from .models import Article
class ArticleModelTest(TestCase):
def setUp(self):
self.article = Article.objects.create(
title='测试文章',
content='测试内容',
)
def test_article_creation(self):
self.assertEqual(self.article.title, '测试文章')
self.assertIsNotNone(self.article.created_at)
def test_article_str(self):
self.assertEqual(str(self.article), '测试文章')
class ArticleViewTest(TestCase):
def setUp(self):
self.client = Client()
Article.objects.create(title='文章1', status='published')
Article.objects.create(title='文章2', status='draft')
def test_article_list(self):
response = self.client.get('/blog/articles/')
self.assertEqual(response.status_code, 200)
self.assertContains(response, '文章1')
def test_article_detail(self):
article = Article.objects.first()
response = self.client.get(f'/blog/articles/{article.id}/')
self.assertEqual(response.status_code, 200)
def test_create_article_requires_login(self):
response = self.client.get('/blog/articles/create/')
self.assertEqual(response.status_code, 302) # 重定向到登录页
# 使用 pytest-django
# pip install pytest-django
# conftest.py
import pytest
from django.contrib.auth.models import User
@pytest.fixture
def user(db):
return User.objects.create_user(username='test', password='pass123')
@pytest.fixture
def auth_client(client, user):
client.force_login(user)
return client
附录
A. 常用第三方包
| 包名 | 用途 |
|---|---|
| djangorestframework | REST API 框架 |
| django-filter | 查询过滤 |
| django-cors-headers | 跨域配置 |
| django-crispy-forms | 表单样式美化 |
| django-debug-toolbar | 调试工具栏 |
| django-extensions | 扩展命令(shell_plus、show_urls 等) |
| django-guardian | 对象级别权限 |
| django-allauth | 社交登录集成 |
| celery | 异步任务队列 |
| pillow | 图片处理 |
| psycopg2-binary | PostgreSQL 驱动 |
| mysqlclient | MySQL 驱动 |
| redis | Redis 缓存客户端 |
| drf-spectacular | OpenAPI 文档生成 |
B. 学习资源
- 官方文档:https://docs.djangoproject.com/zh-hans/
- Django REST Framework:https://www.django-rest-framework.org/
- Two Scoops of Django(最佳实践书籍)
- Django Debug Toolbar 文档:https://django-debug-toolbar.readthedocs.io/
本文档覆盖了 Django 从基础到进阶的核心知识点,建议结合官方文档和实际项目练习,加深理解。