Flask - WTF扩展 #

一、Flask-WTF概述 #

1.1 什么是Flask-WTF #

Flask-WTF是Flask的表单处理扩展,集成了WTForms库,提供了表单验证、CSRF保护等功能。

1.2 安装 #

bash
pip install flask-wtf

1.3 配置 #

python
from flask import Flask
from flask_wtf import FlaskForm

app = Flask(__name__)

# 必须设置SECRET_KEY用于CSRF保护
app.config['SECRET_KEY'] = 'your-secret-key'

二、创建表单类 #

2.1 基本表单 #

python
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Length

class LoginForm(FlaskForm):
    username = StringField('用户名', validators=[DataRequired()])
    password = PasswordField('密码', validators=[DataRequired()])
    submit = SubmitField('登录')

2.2 在视图中使用 #

python
from flask import render_template, redirect, url_for
from forms import LoginForm

@app.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    
    if form.validate_on_submit():
        # 表单验证通过
        username = form.username.data
        password = form.password.data
        # 处理登录逻辑
        return redirect(url_for('index'))
    
    return render_template('login.html', form=form)

三、字段类型 #

3.1 基本字段 #

python
from wtforms import (
    StringField,      # 文本输入
    PasswordField,    # 密码输入
    TextAreaField,    # 多行文本
    HiddenField,      # 隐藏字段
    IntegerField,     # 整数
    FloatField,       # 浮点数
    DecimalField,     # 十进制数
    BooleanField,     # 复选框
    DateField,        # 日期
    DateTimeField,    # 日期时间
    TimeField,        # 时间
    EmailField,       # 邮箱
    TelField,         # 电话
    URLField,         # URL
    SearchField,      # 搜索框
    FileField,        # 文件上传
    MultipleFileField,# 多文件上传
    SelectField,      # 下拉选择
    SelectMultipleField, # 多选下拉
    RadioField,       # 单选框
    SubmitField,      # 提交按钮
)

3.2 字段示例 #

python
class ProfileForm(FlaskForm):
    # 文本字段
    username = StringField('用户名')
    bio = TextAreaField('个人简介')
    
    # 密码字段
    password = PasswordField('密码')
    
    # 数字字段
    age = IntegerField('年龄')
    price = FloatField('价格')
    
    # 日期时间字段
    birthday = DateField('生日', format='%Y-%m-%d')
    created_at = DateTimeField('创建时间')
    
    # 邮箱和URL
    email = EmailField('邮箱')
    website = URLField('网站')
    
    # 布尔字段
    active = BooleanField('激活')
    
    # 选择字段
    country = SelectField('国家', choices=[
        ('cn', '中国'),
        ('us', '美国'),
        ('uk', '英国')
    ])
    
    # 单选字段
    gender = RadioField('性别', choices=[
        ('male', '男'),
        ('female', '女')
    ])
    
    # 文件字段
    avatar = FileField('头像')
    
    # 提交按钮
    submit = SubmitField('保存')

3.3 字段参数 #

python
username = StringField(
    '用户名',                          # 标签
    validators=[DataRequired()],       # 验证器
    default='',                        # 默认值
    description='请输入用户名',         # 描述
    render_kw={                        # HTML属性
        'class': 'form-control',
        'placeholder': '用户名',
        'required': True
    }
)

四、验证器 #

4.1 内置验证器 #

python
from wtforms.validators import (
    DataRequired,      # 必填
    Email,             # 邮箱格式
    Length,            # 长度限制
    NumberRange,       # 数值范围
    URL,               # URL格式
    EqualTo,           # 等于另一个字段
    Regexp,            # 正则表达式
    Optional,          # 可选
    InputRequired,     # 输入必填
    AnyOf,             # 必须是指定值之一
    NoneOf,            # 不能是指定值
)

4.2 验证器示例 #

python
class RegisterForm(FlaskForm):
    username = StringField('用户名', validators=[
        DataRequired(message='用户名不能为空'),
        Length(min=3, max=20, message='用户名长度3-20个字符')
    ])
    
    email = StringField('邮箱', validators=[
        DataRequired(message='邮箱不能为空'),
        Email(message='邮箱格式不正确')
    ])
    
    password = PasswordField('密码', validators=[
        DataRequired(message='密码不能为空'),
        Length(min=6, message='密码至少6个字符')
    ])
    
    confirm = PasswordField('确认密码', validators=[
        DataRequired(message='请确认密码'),
        EqualTo('password', message='两次密码不一致')
    ])
    
    age = IntegerField('年龄', validators=[
        NumberRange(min=0, max=150, message='年龄必须在0-150之间')
    ])
    
    website = StringField('网站', validators=[
        Optional(),
        URL(message='URL格式不正确')
    ])
    
    phone = StringField('手机号', validators=[
        Regexp(r'^1[3-9]\d{9}$', message='手机号格式不正确')
    ])
    
    submit = SubmitField('注册')

4.3 自定义验证器 #

python
from wtforms.validators import ValidationError

def validate_username(form, field):
    """验证用户名是否已存在"""
    if User.query.filter_by(username=field.data).first():
        raise ValidationError('用户名已存在')

class RegisterForm(FlaskForm):
    username = StringField('用户名', validators=[
        DataRequired(),
        validate_username  # 自定义验证器
    ])

4.4 类内验证器 #

python
class RegisterForm(FlaskForm):
    username = StringField('用户名', validators=[DataRequired()])
    email = StringField('邮箱', validators=[DataRequired(), Email()])
    
    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('邮箱已注册')

五、表单渲染 #

5.1 基本渲染 #

html
<form method="POST">
    {{ form.hidden_tag() }}
    
    <div>
        {{ form.username.label }}
        {{ form.username() }}
    </div>
    
    <div>
        {{ form.password.label }}
        {{ form.password() }}
    </div>
    
    {{ form.submit() }}
</form>

5.2 添加CSS类 #

html
<!-- 方式1:在字段定义时 -->
username = StringField('用户名', render_kw={'class': 'form-control'})

<!-- 方式2:在模板中 -->
{{ form.username(class='form-control') }}

<!-- 方式3:使用class_参数 -->
{{ form.username(class_='form-control') }}

5.3 显示错误信息 #

html
<form method="POST">
    {{ form.hidden_tag() }}
    
    <div class="form-group">
        {{ form.username.label(class='form-label') }}
        {{ form.username(class='form-control') }}
        {% if form.username.errors %}
            {% for error in form.username.errors %}
                <span class="text-danger">{{ error }}</span>
            {% endfor %}
        {% endif %}
    </div>
    
    {{ form.submit(class='btn btn-primary') }}
</form>

5.4 Bootstrap样式表单 #

html
<form method="POST">
    {{ form.hidden_tag() }}
    
    {% for field in form %}
        {% if field.type == 'SubmitField' %}
            {{ field(class='btn btn-primary') }}
        {% elif field.type == 'BooleanField' %}
            <div class="form-check">
                {{ field(class='form-check-input') }}
                {{ field.label(class='form-check-label') }}
            </div>
        {% else %}
            <div class="form-group">
                {{ field.label(class='form-label') }}
                {{ field(class='form-control' + (' is-invalid' if field.errors else '')) }}
                {% if field.errors %}
                    <div class="invalid-feedback">
                        {% for error in field.errors %}
                            {{ error }}
                        {% endfor %}
                    </div>
                {% endif %}
            </div>
        {% endif %}
    {% endfor %}
</form>

六、文件上传 #

6.1 文件上传表单 #

python
from flask_wtf.file import FileField, FileAllowed, FileRequired

class UploadForm(FlaskForm):
    avatar = FileField('头像', validators=[
        FileRequired(message='请选择文件'),
        FileAllowed(['jpg', 'png', 'gif'], message='只允许jpg、png、gif格式')
    ])
    submit = SubmitField('上传')

6.2 处理文件上传 #

python
@app.route('/upload', methods=['GET', 'POST'])
def upload():
    form = UploadForm()
    
    if form.validate_on_submit():
        file = form.avatar.data
        filename = secure_filename(file.filename)
        file.save(os.path.join('uploads', filename))
        return '上传成功'
    
    return render_template('upload.html', form=form)

6.3 多文件上传 #

python
from flask_wtf.file import MultipleFileField

class MultiUploadForm(FlaskForm):
    photos = MultipleFileField('照片', validators=[
        FileAllowed(['jpg', 'png'], '只允许jpg、png格式')
    ])
    submit = SubmitField('上传')

@app.route('/multi-upload', methods=['GET', 'POST'])
def multi_upload():
    form = MultiUploadForm()
    
    if form.validate_on_submit():
        for file in form.photos.data:
            filename = secure_filename(file.filename)
            file.save(os.path.join('uploads', filename))
        return '上传成功'
    
    return render_template('multi_upload.html', form=form)

七、表单宏 #

7.1 创建表单宏 #

templates/macros/forms.html:

html
{% macro render_field(field) %}
<div class="form-group">
    {{ field.label(class='form-label') }}
    {{ field(class='form-control' + (' is-invalid' if field.errors else '')) }}
    {% if field.errors %}
        <div class="invalid-feedback">
            {% for error in field.errors %}
                {{ error }}
            {% endfor %}
        </div>
    {% endif %}
</div>
{% endmacro %}

{% macro render_form(form, action='', method='POST') %}
<form action="{{ action }}" method="{{ method }}" enctype="multipart/form-data">
    {{ form.hidden_tag() }}
    {% for field in form %}
        {% if field.type == 'SubmitField' %}
            {{ field(class='btn btn-primary') }}
        {% elif field.type == 'BooleanField' %}
            <div class="form-check">
                {{ field(class='form-check-input') }}
                {{ field.label(class='form-check-label') }}
            </div>
        {% elif field.type == 'CSRFTokenField' %}
        {% else %}
            {{ render_field(field) }}
        {% endif %}
    {% endfor %}
</form>
{% endmacro %}

7.2 使用表单宏 #

html
{% from 'macros/forms.html' import render_form %}

{{ render_form(form, action=url_for('register')) }}

八、表单继承 #

8.1 继承表单 #

python
class BaseForm(FlaskForm):
    name = StringField('名称', validators=[DataRequired()])
    description = TextAreaField('描述')

class ProductForm(BaseForm):
    price = FloatField('价格', validators=[DataRequired()])
    stock = IntegerField('库存', validators=[NumberRange(min=0)])
    submit = SubmitField('保存')

class CategoryForm(BaseForm):
    parent_id = SelectField('父分类', coerce=int)
    submit = SubmitField('保存')

九、总结 #

9.1 核心要点 #

要点 说明
表单类 继承FlaskForm
字段类型 StringField、PasswordField等
验证器 DataRequired、Email、Length等
表单渲染 使用模板渲染
文件上传 FileField、FileAllowed
CSRF保护 自动启用

9.2 下一步 #

现在你已经掌握了Flask-WTF,接下来让我们学习 表单验证,深入了解验证机制!

最后更新:2026-03-28