Django 基础入门教程(第四篇):Form组件、Auth认证、Cookie/Session与中间件

在前三篇中,我们完成了 Django 的环境搭建、模型设计、视图模板、Admin 后台以及 ORM 高级查询。本篇将带你深入 Django 的用户交互与安全机制:Form 组件、Auth 认证系统、Cookie/Session 和中间件。学完本篇,你将能够处理复杂的表单验证、实现用户注册登录、管理用户会话,并理解 Django 的请求/响应处理流程。


第一部分:Django Form 组件

1.1 为什么需要 Form 组件?

在 Web 开发中,处理表单是常见且复杂的任务。你需要:

  • 渲染 HTML 表单字段
  • 验证用户输入(格式、长度、唯一性等)
  • 保留用户上次输入的内容
  • 显示友好的错误提示

Django 的 Form 组件正是为了解决这些问题而设计的。它提供了:

  1. 自动生成 HTML 标签:减少模板中的重复代码
  2. 数据校验:内置多种验证规则
  3. 保留上次输入:出错时用户无需重新填写
  4. 错误提示:自动关联到对应字段

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:是否可登录 Admin
  • is_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

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.sessions
  • MIDDLEWARE 包含 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:管理用户 session
  • AuthenticationMiddleware:将用户与请求关联
  • 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.pyMIDDLEWARE 列表中:

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 的四个核心主题:

  1. Form 组件:表单定义、验证、渲染,以及 ModelForm 的使用
  2. Auth 认证系统:用户模型、认证登录、权限管理、访问控制
  3. Cookie 与 Session:状态保持机制、session 存储配置、实战应用
  4. 中间件:执行流程、自定义中间件、钩子方法

练习

  1. 注册登录功能 :实现完整的用户注册、登录、注销功能,并使用 login_required 保护文章发布页面。
  2. 表单验证:为文章创建表单添加自定义验证,禁止标题中包含特定敏感词。
  3. 访问统计:编写中间件统计每个用户的访问次数,并在用户资料页显示。
  4. 购物车功能:使用 session 实现一个简单的购物车,支持添加、删除商品。

下一篇,我们将学习 Django 的 FBV 与 CBV、Nginx+uwsgi 生产部署配置,完成整个系列的最后篇章。敬请期待!

相关推荐
王夏奇2 小时前
Python-对excel文件操作的总览
开发语言·python·excel
2501_941982052 小时前
Python 实现企业微信外部群机器人:轻量化消息推送方案
python·机器人·企业微信
骇客野人2 小时前
python爬虫例子,且处理反爬的网站也能爬
开发语言·爬虫·python
hutengyi2 小时前
SpringBoot项目中读取resource目录下的文件(六种方法)
spring boot·python·pycharm
老天文学家了2 小时前
蓝桥杯:直线
python
铁手飞鹰2 小时前
eBUS SDK Python环境安装
开发语言·python
放下华子我只抽RuiKe52 小时前
智聊机器人进阶:从 API 调试到全功能交互界面的完美落地
开发语言·人工智能·python·机器学习·分类·机器人·交互
放下华子我只抽RuiKe52 小时前
构建企业级私有化 AI:从大模型原理到本地智聊机器人全栈部署指南
开发语言·人工智能·python·深度学习·机器学习·分类·机器人
PD我是你的真爱粉2 小时前
Django MVT vs FastAPI DDD架构
架构·django·fastapi