在前三篇中,我们完成了 Django 的环境搭建、模型设计、视图模板、Admin 后台以及 ORM 高级查询。本篇将带你深入 Django 的用户交互与安全机制:Form 组件、Auth 认证系统、Cookie/Session 和中间件。学完本篇,你将能够处理复杂的表单验证、实现用户注册登录、管理用户会话,并理解 Django 的请求/响应处理流程。
第一部分:Django Form 组件
1.1 为什么需要 Form 组件?
在 Web 开发中,处理表单是常见且复杂的任务。你需要:
- 渲染 HTML 表单字段
- 验证用户输入(格式、长度、唯一性等)
- 保留用户上次输入的内容
- 显示友好的错误提示
Django 的 Form 组件正是为了解决这些问题而设计的。它提供了:
- 自动生成 HTML 标签:减少模板中的重复代码
- 数据校验:内置多种验证规则
- 保留上次输入:出错时用户无需重新填写
- 错误提示:自动关联到对应字段
1.2 定义第一个 Form 类
在应用目录下创建 forms.py,定义一个简单的联系人表单:
python
# blog/forms.py
from django import forms
class ContactForm(forms.Form):
name = forms.CharField(
label='您的姓名',
max_length=50,
required=True,
error_messages={'required': '请输入姓名'}
)
email = forms.EmailField(
label='电子邮箱',
required=True,
error_messages={'required': '请输入邮箱', 'invalid': '请输入有效邮箱'}
)
message = forms.CharField(
label='留言内容',
widget=forms.Textarea(attrs={'rows': 4, 'class': 'form-control'}),
max_length=500
)
每个字段对应 HTML 表单的一个 <input> 元素。widget 参数可以控制渲染成哪种 HTML 控件以及添加 CSS 类。
1.3 在视图中使用表单
表单的典型处理流程是:GET 请求显示空表单,POST 请求验证并处理数据。
python
# blog/views.py
from django.shortcuts import render, redirect
from .forms import ContactForm
def contact(request):
if request.method == 'POST':
# 使用 POST 数据实例化表单(绑定表单)
form = ContactForm(request.POST)
# 验证数据
if form.is_valid():
# 获取清洗后的数据
name = form.cleaned_data['name']
email = form.cleaned_data['email']
message = form.cleaned_data['message']
# 处理数据(如发送邮件、保存到数据库)
# send_mail(...) 或 Contact.objects.create(...)
return redirect('contact_success') # 重定向到成功页面
else:
# GET 请求:创建空表单(未绑定)
form = ContactForm()
return render(request, 'blog/contact.html', {'form': form})
关键点:
form.is_valid()执行所有字段的验证规则form.cleaned_data是验证通过后的"干净"数据字典- 未绑定的表单(无数据)和绑定的表单(有数据)使用同一个模板
1.4 在模板中渲染表单
html
{# blog/templates/blog/contact.html #}
{% extends "base.html" %}
{% block content %}
<h2>联系我们</h2>
<form method="post">
{% csrf_token %}
{# 渲染所有字段的默认样式 #}
{{ form.as_p }}
{# 或者手动渲染每个字段,以便更灵活控制 #}
{% comment %}
<div>
{{ form.name.label_tag }}
{{ form.name }}
{{ form.name.errors }}
</div>
{% endcomment %}
<button type="submit" class="btn btn-primary">提交</button>
</form>
{% endblock %}
Django 提供了多种渲染方式:as_p()(段落)、as_table()(表格)、as_ul()(列表)。错误信息会自动显示在对应字段旁边。
1.5 ModelForm:从模型生成表单
如果表单与模型字段基本一致,使用 ModelForm 可以大大减少代码:
python
# blog/forms.py
from .models import Article
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ['title', 'content', 'category', 'tags'] # 或 '__all__'
widgets = {
'content': forms.Textarea(attrs={'rows': 5}),
}
labels = {
'title': '文章标题',
'content': '正文',
}
在视图中,可以直接调用 form.save() 保存到数据库:
python
def create_article(request):
if request.method == 'POST':
form = ArticleForm(request.POST)
if form.is_valid():
form.save() # 自动创建并保存 Article 实例
return redirect('article_list')
else:
form = ArticleForm()
return render(request, 'blog/article_form.html', {'form': form})
1.6 表单验证进阶
Django 支持多层次的验证:
1. 字段级验证 :定义 clean_字段名() 方法
python
class ContactForm(forms.Form):
# ...
def clean_name(self):
name = self.cleaned_data.get('name')
if 'admin' in name.lower():
raise forms.ValidationError('姓名不能包含 "admin"')
return name
2. 表单级验证 :定义 clean() 方法,用于跨字段验证
python
def clean(self):
cleaned_data = super().clean()
name = cleaned_data.get('name')
email = cleaned_data.get('email')
# 示例:禁止某些用户名和邮箱组合
if name and email and 'test' in name and 'test.com' in email:
raise forms.ValidationError('测试用户不能使用测试邮箱')
return cleaned_data
第二部分:Django Auth 认证系统
Django 内置了一个功能完善的认证系统,位于 django.contrib.auth。它处理用户账户、组、权限和会话。
2.1 User 模型基础
Django 默认的 User 模型包含以下核心字段:
username:用户名(必填)password:密码(哈希存储)email:邮箱first_name/last_name:姓名is_active:是否激活is_staff:是否可登录 Adminis_superuser:是否超级用户date_joined:注册日期
2.2 创建用户
方式一:命令行创建超级用户
bash
python manage.py createsuperuser
方式二:编程方式创建普通用户
python
from django.contrib.auth.models import User
# 创建用户(密码会自动哈希)
user = User.objects.create_user(
username='john',
email='john@example.com',
password='johnpassword'
)
user.last_name = 'Lennon'
user.save()
# 创建超级用户
superuser = User.objects.create_superuser(
username='admin',
email='admin@example.com',
password='adminpass'
)
重要 :永远不要直接设置 password 属性,必须使用 set_password() 或 create_user()。
2.3 用户认证与登录
Django 提供了 authenticate() 和 login() 函数来处理用户认证。
python
# blog/views.py
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from django.shortcuts import render, redirect
def user_login(request):
if request.method == 'POST':
form = AuthenticationForm(request, data=request.POST)
if form.is_valid():
username = form.cleaned_data.get('username')
password = form.cleaned_data.get('password')
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
return redirect('article_list')
else:
form = AuthenticationForm()
return render(request, 'blog/login.html', {'form': form})
def user_logout(request):
logout(request)
return redirect('article_list')
authenticate() 验证凭据,成功则返回 User 对象;login() 将用户 ID 存入 session。
2.4 注册新用户
可以使用 Django 内置的 UserCreationForm:
python
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) # 自动登录
return redirect('article_list')
else:
form = UserCreationForm()
return render(request, 'blog/register.html', {'form': form})
模板 register.html 与普通表单类似。
2.5 权限系统
Django 为每个模型自动创建四个权限:添加、修改、删除、查看。
检查权限:
python
# 在视图或模板中
if request.user.has_perm('blog.add_article'):
# 用户有添加文章的权限
pass
# 在模板中
{% if perms.blog.change_article %}
<a href="{% url 'edit_article' article.id %}">编辑</a>
{% endif %}
自定义权限 :在模型的 Meta 类中定义
python
class Article(models.Model):
# ...
class Meta:
permissions = [
("can_publish_article", "可以发布文章"),
("can_review_article", "可以审核文章"),
]
2.6 在视图中限制访问
Django 提供了多种装饰器和混入类来限制访问:
python
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.views.generic import CreateView
# 函数视图
@login_required
def my_view(request):
return render(request, 'blog/profile.html')
# 类视图
class ArticleCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView):
model = Article
form_class = ArticleForm
permission_required = 'blog.add_article'
login_url = '/login/' # 未登录时重定向的URL
第三部分:Cookie 与 Session
3.1 Cookie 基础
Cookie 是存储在客户端浏览器的小段文本,用于跟踪用户状态。Django 通过 HttpResponse 对象设置和读取 cookie。
设置 Cookie:
python
def set_cookie(request):
response = HttpResponse("Cookie 已设置")
response.set_cookie('username', 'john', max_age=3600) # 有效期1小时
return response
读取 Cookie:
python
def get_cookie(request):
username = request.COOKIES.get('username', '匿名用户')
return HttpResponse(f"当前用户:{username}")
删除 Cookie:
python
def delete_cookie(request):
response = HttpResponse("Cookie 已删除")
response.delete_cookie('username')
return response
3.2 Session 基础
Session 将数据存储在服务器端,客户端只保存 session ID(通过 cookie)。Django 默认使用数据库存储 session。
启用 Session:默认项目已启用,需要确保:
INSTALLED_APPS包含django.contrib.sessionsMIDDLEWARE包含SessionMiddleware
使用 Session:
python
# 设置 session
def set_session(request):
request.session['user_id'] = 123
request.session['preferences'] = {'theme': 'dark', 'language': 'zh'}
return HttpResponse("Session 已设置")
# 读取 session
def get_session(request):
user_id = request.session.get('user_id', '未登录')
return HttpResponse(f"用户ID:{user_id}")
# 删除 session
def delete_session(request):
request.session.flush() # 清空 session 数据并删除 cookie
return HttpResponse("Session 已清空")
3.3 Session 常用方法
request.session 是一个类似字典的对象,提供了丰富的方法:
python
# 基础操作
request.session['key'] = value # 设置值
value = request.session.get('key') # 获取值
del request.session['key'] # 删除键
'key' in request.session # 检查存在
# 高级操作
request.session.set_expiry(300) # 设置5分钟后过期
request.session.set_expiry(0) # 浏览器关闭时过期
request.session.clear_expired() # 清理过期session
# 测试 cookie 支持
request.session.set_test_cookie() # 设置测试 cookie
if request.session.test_cookie_worked(): # 检查测试 cookie
request.session.delete_test_cookie() # 删除测试 cookie
3.4 配置 Session 存储
Django 支持多种 session 后端:
数据库 session(默认):
python
SESSION_ENGINE = 'django.contrib.sessions.backends.db'
缓存 session(需要配置缓存):
python
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
SESSION_CACHE_ALIAS = 'default'
文件 session:
python
SESSION_ENGINE = 'django.contrib.sessions.backends.file'
SESSION_FILE_PATH = '/path/to/sessions/dir' # 可选
cookie-based session(数据加密后存在 cookie):
python
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'
3.5 实战:购物车示例
python
def add_to_cart(request, product_id):
# 获取当前购物车(session 中)
cart = request.session.get('cart', [])
# 添加商品
cart.append({
'product_id': product_id,
'quantity': 1,
'added_at': str(timezone.now())
})
# 保存回 session
request.session['cart'] = cart
request.session.modified = True # 确保 Django 知道 session 已修改
return redirect('view_cart')
def view_cart(request):
cart = request.session.get('cart', [])
return render(request, 'shop/cart.html', {'cart': cart})
第四部分:Django 中间件
4.1 什么是中间件?
中间件是 Django 请求/响应处理过程中的钩子框架。它是一个轻量级的、底层的"插件"系统,用于全局修改 Django 的输入或输出。
每个中间件组件负责执行特定功能,例如:
SessionMiddleware:管理用户 sessionAuthenticationMiddleware:将用户与请求关联CsrfViewMiddleware:防止 CSRF 攻击
4.2 中间件的执行顺序
中间件的执行像一个洋葱:
-
请求阶段 :按
MIDDLEWARE列表的顺序从上到下执行 -
响应阶段:按相反的顺序从下到上执行
请求进入 → 中间件1 (process_request) → 中间件2 (process_request) → 视图函数 ← 中间件2 (process_response) ← 中间件1 (process_response) ← 响应返回
4.3 自定义中间件
Django 支持函数式和类式两种中间件写法。
函数式中间件:
python
# blog/middleware.py
def simple_middleware(get_response):
# 初始化代码(服务器启动时执行一次)
def middleware(request):
# 请求处理前(视图之前)
print(f"请求路径: {request.path}")
# 获取响应
response = get_response(request)
# 响应处理后(返回客户端之前)
print(f"响应状态码: {response.status_code}")
return response
return middleware
类式中间件:
python
class StatsMiddleware:
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
print(f"请求 {request.path} 耗时: {duration:.2f}秒")
return response
4.4 中间件的钩子方法
类式中间件可以定义多个钩子方法:
python
class AdvancedMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# 请求处理
response = self.get_response(request)
return response
def process_view(self, request, view_func, view_args, view_kwargs):
"""视图调用前执行"""
print(f"即将调用视图: {view_func.__name__}")
# 可以返回 None 继续处理,或返回 HttpResponse 短路
def process_exception(self, request, exception):
"""视图抛出异常时执行"""
print(f"捕获异常: {exception}")
# 可以返回 HttpResponse 替代错误页面
def process_template_response(self, request, response):
"""模板响应渲染前执行"""
print("准备渲染模板")
return response
4.5 激活中间件
将中间件类或函数的完整 Python 路径添加到 settings.py 的 MIDDLEWARE 列表中:
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',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'blog.middleware.StatsMiddleware', # 自定义中间件
]
顺序很重要!例如,AuthenticationMiddleware 依赖于 SessionMiddleware,因为用户信息存储在 session 中。
4.6 实战:IP 访问限制中间件
python
# blog/middleware.py
from django.http import HttpResponseForbidden
from django.core.cache import cache
class RateLimitMiddleware:
"""
简单的 IP 限流中间件:同一 IP 每分钟最多访问 60 次
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# 获取客户端 IP
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
# 缓存键
cache_key = f'rate_limit_{ip}'
# 获取当前访问次数
count = cache.get(cache_key, 0)
if count >= 60:
return HttpResponseForbidden('访问过于频繁,请稍后再试')
# 增加计数
cache.set(cache_key, count + 1, timeout=60)
# 继续处理请求
response = self.get_response(request)
return response
5. 综合实战:用户认证与表单结合
让我们将所学知识整合起来,实现一个完整的用户资料编辑功能。
5.1 用户资料表单
python
# blog/forms.py
from django import forms
from django.contrib.auth.models import User
class UserProfileForm(forms.ModelForm):
"""用户资料表单"""
class Meta:
model = User
fields = ['first_name', 'last_name', 'email']
widgets = {
'first_name': forms.TextInput(attrs={'class': 'form-control'}),
'last_name': forms.TextInput(attrs={'class': 'form-control'}),
'email': forms.EmailInput(attrs={'class': 'form-control'}),
}
5.2 资料编辑视图
python
# blog/views.py
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from .forms import UserProfileForm
@login_required
def profile_edit(request):
if request.method == 'POST':
form = UserProfileForm(request.POST, instance=request.user)
if form.is_valid():
form.save()
messages.success(request, '资料更新成功!')
return redirect('profile_edit')
else:
form = UserProfileForm(instance=request.user)
# 记录用户最后访问时间到 session
request.session['last_visit'] = str(timezone.now())
return render(request, 'blog/profile_edit.html', {
'form': form,
'last_visit': request.session.get('last_visit')
})
5.3 中间件记录用户活动
python
# blog/middleware.py
import logging
logger = logging.getLogger(__name__)
class UserActivityMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# 仅记录已登录用户的活动
if request.user.is_authenticated:
logger.info(f"用户 {request.user.username} 访问 {request.path}")
# 在 session 中记录最后活动时间
request.session['last_activity'] = str(timezone.now())
response = self.get_response(request)
return response
5.4 模板中使用消息和用户信息
html
{# blog/templates/blog/profile_edit.html #}
{% extends "base.html" %}
{% block content %}
<h2>编辑资料</h2>
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% if last_visit %}
<p class="text-muted">上次访问时间: {{ last_visit }}</p>
{% endif %}
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary">保存修改</button>
</form>
{% endblock %}
6. 小结与练习
本篇总结
我们深入学习了 Django 的四个核心主题:
- Form 组件:表单定义、验证、渲染,以及 ModelForm 的使用
- Auth 认证系统:用户模型、认证登录、权限管理、访问控制
- Cookie 与 Session:状态保持机制、session 存储配置、实战应用
- 中间件:执行流程、自定义中间件、钩子方法
练习
- 注册登录功能 :实现完整的用户注册、登录、注销功能,并使用
login_required保护文章发布页面。 - 表单验证:为文章创建表单添加自定义验证,禁止标题中包含特定敏感词。
- 访问统计:编写中间件统计每个用户的访问次数,并在用户资料页显示。
- 购物车功能:使用 session 实现一个简单的购物车,支持添加、删除商品。
下一篇,我们将学习 Django 的 FBV 与 CBV、Nginx+uwsgi 生产部署配置,完成整个系列的最后篇章。敬请期待!