现在位置: 首页 > Flask 教程 > 正文

表单处理 — Flask-WTF

本章你将学会用 Flask-WTF 规范化处理表单:定义表单类、自动 CSRF 保护、字段校验与错误提示。


为什么要用 Flask-WTF?

上一章的注册和登录表单是手写的:手动从 request.form.get() 取值、手动校验、手动显示错误。

Flask-WTF 基于 WTForms 为 Flask 封装,提供:

  • 表单类定义,字段 + 校验规则集中管理
  • 自动 CSRF 保护
  • 模板中渲染字段和错误信息
  • form.validate_on_submit() 统一处理 GET/POST 判断与校验

安装与配置

(venv) $ pip install flask-wtf

在 app.py 中配置 SECRET_KEY(CSRF Token 依赖此密钥):

实例

# 文件路径:app.py
app.config['SECRET_KEY'] = 'your-secret-key'    # 生产环境用环境变量
app.config['WTF_CSRF_ENABLED'] = True            # 默认开启,显式声明一下

定义表单类

实例

# 文件路径:app/forms.py(新建文件)
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, EmailField, BooleanField
from wtforms.validators import DataRequired, Length, Email, EqualTo, ValidationError
from app.models import User

class RegisterForm(FlaskForm):
    """注册表单"""
    username = StringField('用户名', validators=[
        DataRequired(message='用户名不能为空'),
        Length(min=3, max=50, message='用户名长度 3-50 个字符')
    ])
    email = EmailField('邮箱', validators=[
        DataRequired(message='邮箱不能为空'),
        Email(message='请输入有效的邮箱地址')
    ])
    password = PasswordField('密码', validators=[
        DataRequired(message='密码不能为空'),
        Length(min=6, message='密码至少 6 位')
    ])
    confirm_password = PasswordField('确认密码', validators=[
        DataRequired(message='请再次输入密码'),
        EqualTo('password', message='两次输入的密码不一致')
    ])

    # 自定义验证器:检查用户名是否已被注册
    def validate_username(self, field):
        if User.query.filter_by(username=field.data).first():
            raise ValidationError('该用户名已被使用。')

    def validate_email(self, field):
        if User.query.filter_by(email=field.data).first():
            raise ValidationError('该邮箱已被注册。')


class LoginForm(FlaskForm):
    """登录表单"""
    username = StringField('用户名', validators=[
        DataRequired(message='用户名不能为空')
    ])
    password = PasswordField('密码', validators=[
        DataRequired(message='密码不能为空')
    ])
    remember = BooleanField('记住我')

常用字段类型

字段类HTML 标签说明
StringField<input type="text">单行文本
PasswordField<input type="password">密码输入框
EmailField<input type="email">邮箱输入框
TextAreaField<textarea>多行文本
BooleanField<input type="checkbox">复选框
SelectField<select>下拉选择

常用验证器

验证器作用
DataRequired()字段不能为空
Length(min=, max=)字符长度限制
Email()邮箱格式校验
EqualTo('field_name')与另一个字段值一致
Optional()允许字段为空(跳过后续验证器)

在视图函数中使用表单

实例

# 文件路径:app/blueprints/auth.py(重构后的 register 和 login)
from flask import Blueprint, render_template, redirect, url_for, flash
from flask_login import login_user, logout_user, login_required, current_user
from app.forms import RegisterForm, LoginForm
from app.models import User, db

auth_bp = Blueprint('auth', __name__)

@auth_bp.route("/register", methods=['GET', 'POST'])
def register():
    if current_user.is_authenticated:
        return redirect(url_for('main.index'))

    form = RegisterForm()
    # form.validate_on_submit() 在 POST 请求时校验表单
    # GET 请求或校验失败时返回 False
    if form.validate_on_submit():
        user = User(username=form.username.data, email=form.email.data)
        user.set_password(form.password.data)
        db.session.add(user)
        db.session.commit()
        login_user(user)
        flash(f'注册成功,欢迎你,{user.username}!', 'success')
        return redirect(url_for('main.index'))

    # GET 请求或校验失败时,渲染带表单的模板
    return render_template('register.html', form=form)


@auth_bp.route("/login", methods=['GET', 'POST'])
def login():
    if current_user.is_authenticated:
        return redirect(url_for('main.index'))

    form = LoginForm()
    if form.validate_on_submit():
        user = User.query.filter_by(username=form.username.data).first()
        if user is None or not user.check_password(form.password.data):
            flash('用户名或密码错误。', 'error')
            return render_template('login.html', form=form)

        login_user(user, remember=form.remember.data)
        flash(f'欢迎回来,{user.username}!', 'success')
        next_page = request.args.get('next')
        return redirect(next_page or url_for('main.index'))

    return render_template('login.html', form=form)

模板中渲染表单

实例

<!-- 文件路径:app/templates/register.html -->
{% extends 'base.html' %}

{% block title %}注册 - RUNOOB 博客{% endblock %}

{% block content %}
<div class="auth-form">
    <h2>注册</h2>
    <form method="post">
        <!-- form.hidden_tag() 自动生成 CSRF Token 隐藏字段 -->
        {{ form.hidden_tag() }}

        <div class="form-group">
            {{ form.username.label }}
            {{ form.username(class="form-input") }}
            {% if form.username.errors %}
                {% for error in form.username.errors %}
                <span class="field-error">{{ error }}</span>
                {% endfor %}
            {% endif %}
        </div>

        <div class="form-group">
            {{ form.email.label }}
            {{ form.email(class="form-input") }}
            {% for error in form.email.errors %}
            <span class="field-error">{{ error }}</span>
            {% endfor %}
        </div>

        <div class="form-group">
            {{ form.password.label }}
            {{ form.password(class="form-input") }}
            {% for error in form.password.errors %}
            <span class="field-error">{{ error }}</span>
            {% endfor %}
        </div>

        <div class="form-group">
            {{ form.confirm_password.label }}
            {{ form.confirm_password(class="form-input") }}
            {% for error in form.confirm_password.errors %}
            <span class="field-error">{{ error }}</span>
            {% endfor %}
        </div>

        {{ form.submit(class="btn-submit") }}
    </form>
    <p class="form-footer">
        已有账号?<a href="{{ url_for('auth.login') }}">立即登录</a>
    </p>
</div>
{% endblock %}

Flask-WTF 的 CSRF 保护是自动的:form.hidden_tag() 会生成一个隐藏的 <input> 包含随机 Token,服务器在接受 POST 请求前验证此 Token。任何外站提交的表单都因为没有正确的 Token 而被拒绝。


手动 request.form vs Flask-WTF 对比

方面手动 request.formFlask-WTF
代码行数每个字段重复取/验/报三步表单类定义一次
CSRF 保护需手动实现自动
自定义验证手动 if/elsevalidate_xxx 方法
错误展示手动拼接form.field.errors 自动收集
适用场景简单表单(如搜索框)复杂表单(注册、登录、资料编辑)

本章小结

本章你用 Flask-WTF 重构了认证表单:FlaskForm 定义字段和验证器、validate_xxx 自定义验证、form.validate_on_submit() 统一处理 POST 校验、form.hidden_tag() 自动 CSRF 保护、模板中渲染错误信息。

表单处理现在更规范、更安全。