Flask表单验证 #

一、验证概述 #

1.1 为什么需要验证 #

原因 说明
数据完整性 确保必填字段有值
数据格式 确保数据格式正确
安全性 防止恶意输入
用户体验 提供友好的错误提示

1.2 验证时机 #

python
@app.route('/form', methods=['GET', 'POST'])
def form_view():
    form = MyForm()
    
    # validate_on_submit() 会同时检查:
    # 1. 是否是POST请求
    # 2. 表单数据是否通过验证
    if form.validate_on_submit():
        # 验证通过,处理数据
        pass
    
    return render_template('form.html', form=form)

二、内置验证器 #

2.1 DataRequired #

python
from wtforms.validators import DataRequired

username = StringField('用户名', validators=[
    DataRequired(message='用户名不能为空')
])

2.2 Length #

python
from wtforms.validators import Length

username = StringField('用户名', validators=[
    Length(min=3, max=20, message='用户名长度必须在3-20个字符之间')
])

# 只限制最小长度
password = PasswordField('密码', validators=[
    Length(min=6, message='密码至少6个字符')
])

2.3 Email #

python
from wtforms.validators import Email

email = StringField('邮箱', validators=[
    Email(message='请输入有效的邮箱地址')
])

2.4 NumberRange #

python
from wtforms.validators import NumberRange

age = IntegerField('年龄', validators=[
    NumberRange(min=0, max=150, message='年龄必须在0-150之间')
])

price = FloatField('价格', validators=[
    NumberRange(min=0, message='价格不能为负数')
])

2.5 URL #

python
from wtforms.validators import URL

website = StringField('网站', validators=[
    URL(message='请输入有效的URL')
])

# 要求特定协议
website = StringField('网站', validators=[
    URL(require_tld=True, message='请输入有效的URL')
])

2.6 EqualTo #

python
from wtforms.validators import EqualTo

password = PasswordField('密码')
confirm = PasswordField('确认密码', validators=[
    EqualTo('password', message='两次密码输入不一致')
])

2.7 Regexp #

python
from wtforms.validators import Regexp

phone = StringField('手机号', validators=[
    Regexp(r'^1[3-9]\d{9}$', message='请输入有效的手机号')
])

username = StringField('用户名', validators=[
    Regexp(r'^[a-zA-Z][a-zA-Z0-9_]*$', 
           message='用户名必须以字母开头,只能包含字母、数字和下划线')
])

2.8 Optional #

python
from wtforms.validators import Optional

nickname = StringField('昵称', validators=[
    Optional()
])

2.9 AnyOf #

python
from wtforms.validators import AnyOf

status = SelectField('状态', validators=[
    AnyOf(['active', 'inactive', 'pending'], message='无效的状态')
])

2.10 NoneOf #

python
from wtforms.validators import NoneOf

username = StringField('用户名', validators=[
    NoneOf(['admin', 'root', 'system'], message='该用户名不可用')
])

三、组合验证器 #

3.1 多个验证器 #

python
username = StringField('用户名', validators=[
    DataRequired(message='用户名不能为空'),
    Length(min=3, max=20, message='用户名长度3-20个字符'),
    Regexp(r'^[a-zA-Z][a-zA-Z0-9_]*$', message='用户名格式不正确')
])

3.2 条件验证 #

python
from wtforms.validators import Optional, DataRequired

class ProfileForm(FlaskForm):
    nickname = StringField('昵称', validators=[Optional()])
    
    # 如果填写了昵称,则验证长度
    def validate_nickname(self, field):
        if field.data and len(field.data) > 50:
            raise ValidationError('昵称不能超过50个字符')

四、自定义验证器 #

4.1 函数验证器 #

python
from wtforms.validators import ValidationError

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

class RegisterForm(FlaskForm):
    username = StringField('用户名', validators=[
        DataRequired(),
        validate_username_exists
    ])

4.2 类验证器 #

python
class UniqueValidator:
    """唯一性验证器"""
    
    def __init__(self, model, field, message=None):
        self.model = model
        self.field = field
        self.message = message or '该值已存在'
    
    def __call__(self, form, field):
        instance = self.model.query.filter(
            getattr(self.model, self.field) == field.data
        ).first()
        if instance:
            raise ValidationError(self.message)

class RegisterForm(FlaskForm):
    username = StringField('用户名', validators=[
        DataRequired(),
        UniqueValidator(User, 'username', '用户名已存在')
    ])
    
    email = StringField('邮箱', validators=[
        DataRequired(),
        Email(),
        UniqueValidator(User, 'email', '邮箱已注册')
    ])

4.3 类内验证方法 #

python
class RegisterForm(FlaskForm):
    username = StringField('用户名', validators=[DataRequired()])
    email = StringField('邮箱', validators=[DataRequired(), Email()])
    password = PasswordField('密码', validators=[DataRequired()])
    confirm = PasswordField('确认密码', validators=[DataRequired()])
    
    def validate_username(self, field):
        """验证用户名"""
        if User.query.filter_by(username=field.data).first():
            raise ValidationError('用户名已存在')
        
        if field.data.lower() in ['admin', 'root', 'system']:
            raise ValidationError('该用户名不可用')
    
    def validate_email(self, field):
        """验证邮箱"""
        if User.query.filter_by(email=field.data).first():
            raise ValidationError('邮箱已注册')
    
    def validate_confirm(self, field):
        """验证确认密码"""
        if field.data != self.password.data:
            raise ValidationError('两次密码输入不一致')

五、高级验证 #

5.1 密码强度验证 #

python
import re

class PasswordStrength:
    """密码强度验证器"""
    
    def __init__(self, min_strength='medium', message=None):
        self.min_strength = min_strength
        self.message = message or '密码强度不足'
        self.strengths = {
            'weak': 1,
            'medium': 2,
            'strong': 3
        }
    
    def __call__(self, form, field):
        password = field.data
        strength = 0
        
        # 检查长度
        if len(password) >= 8:
            strength += 1
        
        # 检查是否包含数字
        if re.search(r'\d', password):
            strength += 1
        
        # 检查是否包含小写字母
        if re.search(r'[a-z]', password):
            strength += 1
        
        # 检查是否包含大写字母
        if re.search(r'[A-Z]', password):
            strength += 1
        
        # 检查是否包含特殊字符
        if re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
            strength += 1
        
        required = self.strengths.get(self.min_strength, 2)
        if strength < required:
            raise ValidationError(self.message)

class RegisterForm(FlaskForm):
    password = PasswordField('密码', validators=[
        DataRequired(),
        PasswordStrength(min_strength='medium', message='密码必须包含字母和数字,至少8个字符')
    ])

5.2 日期验证 #

python
from datetime import datetime

class DateRange:
    """日期范围验证器"""
    
    def __init__(self, min_date=None, max_date=None, message=None):
        self.min_date = min_date
        self.max_date = max_date
        self.message = message
    
    def __call__(self, form, field):
        date = field.data
        
        if self.min_date and date < self.min_date:
            raise ValidationError(self.message or f'日期不能早于{self.min_date}')
        
        if self.max_date and date > self.max_date:
            raise ValidationError(self.message or f'日期不能晚于{self.max_date}')

class EventForm(FlaskForm):
    start_date = DateField('开始日期', validators=[
        DataRequired(),
        DateRange(min_date=datetime.now().date(), message='开始日期不能早于今天')
    ])
    
    end_date = DateField('结束日期', validators=[
        DataRequired()
    ])
    
    def validate_end_date(self, field):
        if field.data < self.start_date.data:
            raise ValidationError('结束日期不能早于开始日期')

5.3 文件验证 #

python
from flask_wtf.file import FileAllowed, FileRequired, FileSize

class AvatarForm(FlaskForm):
    avatar = FileField('头像', validators=[
        FileRequired(message='请选择文件'),
        FileAllowed(['jpg', 'png', 'gif'], message='只允许jpg、png、gif格式'),
        FileSize(max_size=2*1024*1024, message='文件大小不能超过2MB')
    ])

六、验证错误处理 #

6.1 获取错误信息 #

python
@app.route('/register', methods=['GET', 'POST'])
def register():
    form = RegisterForm()
    
    if form.validate_on_submit():
        # 验证通过
        pass
    else:
        # 获取所有错误
        errors = form.errors
        # {'username': ['用户名已存在'], 'email': ['邮箱格式不正确']}
        
        # 获取特定字段错误
        username_errors = form.username.errors
        
    return render_template('register.html', form=form)

6.2 模板中显示错误 #

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

6.3 Flash消息 #

python
from flask import flash

@app.route('/register', methods=['GET', 'POST'])
def register():
    form = RegisterForm()
    
    if form.validate_on_submit():
        try:
            user = create_user(form.data)
            flash('注册成功!', 'success')
            return redirect(url_for('login'))
        except Exception as e:
            flash(f'注册失败: {str(e)}', 'danger')
    else:
        for field, errors in form.errors.items():
            for error in errors:
                flash(f'{getattr(form, field).label.text}: {error}', 'danger')
    
    return render_template('register.html', form=form)

七、最佳实践 #

7.1 验证器组织 #

python
# validators.py
from wtforms.validators import ValidationError

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

def email_exists(form, field):
    """验证邮箱是否存在"""
    if User.query.filter_by(email=field.data).first():
        raise ValidationError('邮箱已注册')

# forms.py
from validators import username_exists, email_exists

class RegisterForm(FlaskForm):
    username = StringField('用户名', validators=[
        DataRequired(),
        Length(min=3, max=20),
        username_exists
    ])
    email = StringField('邮箱', validators=[
        DataRequired(),
        Email(),
        email_exists
    ])

7.2 错误消息国际化 #

python
class RegisterForm(FlaskForm):
    username = StringField('用户名', validators=[
        DataRequired(message=gettext('用户名不能为空')),
        Length(min=3, max=20, message=gettext('用户名长度必须在%(min)d-%(max)d之间'))
    ])

八、总结 #

8.1 核心要点 #

要点 说明
内置验证器 DataRequired、Email、Length等
自定义验证器 函数或类实现
类内验证 validate_fieldname方法
错误处理 form.errors获取错误
组合验证 多个验证器组合使用

8.2 下一步 #

现在你已经掌握了表单验证,接下来让我们学习 文件上传,了解如何处理文件上传!

最后更新:2026-03-28