优秀的编程知识分享平台

网站首页 > 技术文章 正文

Flask博客实战 - 使用 WTForms 进行表单验证

nanyue 2024-10-15 11:33:36 技术文章 3 ℃

上一章节我们通过在html中直接编写表单的方式进行数据传递,并且在视图中对前端传递的数据进行了简单的认证,但是如果把验证数据的代码与逻辑混合在一起,将使得视图的代码不够清晰,并且难以维护,稍加疏忽就会产生验证漏洞,如果细心的同学其实可以发现,在之前的登录注册中我们一直没有对空表单进行验证,当然这是我故意为之,但如果在生产环境,这将是一个灾难的开始,所以,在编程中无论是前端还是后端都要求要对数据进行验证,作为后端,更要保持一种永远不相信前端传递数据的态度去做数据校验。

本章节我们将使用Flask官方推荐的Flask-WTF扩展来重构我们的登录注册表单!

关于Flask-WTF

Flask-WTF是Flask 和 WTForms 的简单集成,包括 CSRF、文件上传和 reCAPTCHA。

  • 安装Flask-WTF:
pip install Flask-WTF

创建登录注册表单类

app/auth/目录下新建一个forms.py的文件,所有的表单验证代码都放到这个文件当中!

构建登录表单

from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField
from wtforms.validators import DataRequired, Length, ValidationError, EqualTo
from werkzeug.security import check_password_hash
from .models import User

class LoginForm(FlaskForm):
    # 登录表单

    def qs_username(username):
        # 对该字段进行在传递之前处理
        u = f'{username}123456'
        print(u)
        return username

    username = StringField('username', validators=[
        DataRequired(message="不能为空"), 
        Length(max=32, message="不符合字数要求!")
        ], filters=(qs_username,))
    password = PasswordField('password', validators=[
        DataRequired(message="不能为空"),
        Length(max=32, message="不符合字数要求!")
        ])

    def validate_username(form, field):
        user = User.query.filter_by(username=field.data).first()
        if user is None:
            error = '该用户不存在!'
            raise ValidationError(error)
        elif not check_password_hash(user.password, form.password.data):
            raise ValidationError('密码不正确')

代码详解:

class LoginForm(FlaskForm): 创建了一个登录表单类,继承了FlaskForm类

StringField, PasswordField

这些都是wtforms内置的字段,负责呈现和数据转换。

官方文档:https://wtforms.readthedocs.io/en/3.0.x/fields/

他继承自Filed的基类,其中有一些比较重要的参数我们大概在这里理解一下!

第一个字符串其实是该类的label参数,字段的标签,也就是转换到html中的label!

validators传入对该字段的一些验证器,在提交数据之前对数据进行验证!

filters这个参数比较特殊,官方文档并没有对其详细说明,只说是筛选器,其实怎么说就是在额外的方法中对该字段的值提前处理过滤,元组中的每个值都是一个回调函数,函数不需要传入括号,但这个回调函数默认有一个参数,这个参数就是本身该字段的值,所以在定义该函数时就必须传入一个参数!例如:我们定义username之前定义的这个方法!

def qs_username(username):
    # 对该字段进行在传递之前处理
    u = f'{username}123456'
    print(u)
    return username

备注:必须返回处理后的这个参数,否则会触发DataRequired验证器,后端获取不到该表单的值!

  • DataRequired, Length 这是内置的验证器,第一个是验证字段是否为空,第二个Length是验证输入长度,当然内置的还有很多,这里就不一一列举,具体我们可参考文档!

官方文档: https://wtforms.readthedocs.io/en/3.0.x/validators/#custom-validators

自定义验证用户名和密码

在之前的视图函数中我们对用户名和密码都做了校验,现在我们需要把验证的代码全部移动到表单类中,代码如下:

def validate_username(form, field):
    user = User.query.filter_by(username=field.data).first()
    if user is None:
        error = '该用户不存在!'
        raise ValidationError(error)
    elif not check_password_hash(user.password, form.password.data):
        raise ValidationError('密码不正确')
  • validate_username(form, field)

这个函数的写法是固定的validate_{filed},validate_后边的filed是指你需要验证的某个字段名,比如我们这个验证,他主要就是对username字段进行验证,这个函数中参数的filed就是这个字段,通过field.data就可以获取到usernam的值。form参数则指代的是整个表单,可以用form.{filed}.data的方式获取表单类中某个具体字段的值!

构建注册表单

当了解了登录表单后,我们完全就可以参照登录表单去实现注册表单,代码如下:

路径:app/auth/forms.py

class RegisterForm(FlaskForm):
    # 注册表单
    username = StringField('username', validators=[
        DataRequired(message="不能为空"), 
        Length(min=2, max=32, message="超过限制字数!")
        ])
    password = PasswordField('password', validators=[
        DataRequired(message="不能为空"),
        Length(min=2, max=32, message="超过限制字数!"),
        EqualTo('password1', message='两次密码输入不一致!')
        ])
    password1 = PasswordField('password1')

    def validate_username(form, field):
        user = User.query.filter_by(username=field.data).first()
        if user is not None:
            error = '该用户名称已存在!'
            raise ValidationError(error)

这里唯一需要注意的是两次密码是否输入一致,我们用了一个内置的验证器EqualTo,使用方式可完全参照代码,他会自动校验password和password1输入的值是否一致!

重构登录和注册视图

  • 路径:app/auth/views/auth.py
from ..forms import LoginForm, RegisterForm

@bp.route('/login', methods=['GET', 'POST'])
def login():
    # 登录视图
    # form = LoginForm(meta={'csrf': False}) # 禁用csrf
    form = LoginForm()
    if form.validate_on_submit():
        user = auth.User.query.filter_by(username=form.username.data).first()
        session.clear()
        session['user_id'] = user.id
        return redirect(url_for('index'))
    return render_template('login.html', form=form)


@bp.route('/register', methods=['GET', 'POST'])
def register():
    # 注册视图
    form = RegisterForm()
    if form.validate_on_submit():
        user = auth.User(username=form.username.data, password=generate_password_hash(form.password.data))
        db.session.add(user)
        db.session.commit()
        session.clear()
        session['user_id'] = user.id
        return redirect(url_for('index'))
    return render_template('register.html', form=form)

1、首先从forms.py中引入了我们定义的登录(LoginForm)和注册(RegisterForm)表单类!

2、form = RegisterForm() 实例化表单类

3、if form.validate_on_submit(): 验证前端传递的数据是否有效,并且会自动判断是POST请求还是GET请求!

4、 数据验证通过则进入之后的逻辑,未验证通过则返回我们在表单类中传入的验证提示!

模板中调用验证信息

我们以调用username字段的验证提示为例,在模板中加入这段代码即可获得错误提示!

<!-- 表单验证 -->
{% if form.username.errors %}
<b-message type="is-danger">
    <ul class="errors">
        {% for error in form.username.errors %}
            <li>{{ error }}</li>
        {% endfor %}
    </ul>
</b-message>
{% endif %}

重构登录注册html模板

路径:app/auth/templates/login.html 以登陆表单为例,代码如下:

{% block auth_form %}
    <form action="" method="post" style="margin-top: 40%;" class="box">
        <div class=" has-text-centered mb-3">
          <p class=" subtitle">登录</p>
          <h1 class="title">FlaskBlog</h1>
        </div>
        {{ form.csrf_token }}
        <!-- 消息闪现 -->
        {% with messages = get_flashed_messages() %}
        <b-message type="is-danger">
          {% if messages %}
          <ul class=flashes>
            {% for message in messages %}
            <li>{{ message }}</li>
            {% endfor %}
          </ul>
          {% endif %}
        </b-message>
        {% endwith %}

        <!-- 表单验证 -->
        {% if form.username.errors %}
        <b-message type="is-danger">
          <ul class="errors">
            {% for error in form.username.errors %}
            <li>{{ error }}</li>
            {% endfor %}
          </ul>
        </b-message>
        {% endif %}

        <div class="field">
          <p class="control has-icons-left has-icons-right">
            {{ form.username(class='input', placeholder='Username') }}
            <span class="icon is-small is-left">
              <i class="fas fa-envelope"></i>
            </span>
            <span class="icon is-small is-right">
              <i class="fas fa-check"></i>
            </span>
          </p>
        </div>
        <div class="field">
          <p class="control has-icons-left">
            {{ form.password(class='input', placeholder='Password') }}
            <span class="icon is-small is-left">
              <i class="fas fa-lock"></i>
            </span>
          </p>
        </div>
        <div class="field">
          <p class="control">
            <input class="button is-success is-fullwidth" type="submit" value="Login">
          </p>
        </div>
      </form>
    {% endblock auth_form %}

{{ form.csrf_token }} 隐式的创建一个csrftoken的表单

{{ form.username(class='input', placeholder='Username') }} 这样就可以直接获得一个表单html并自动渲染,向该表单增加书香的方式就是像代码中这样传入参数和值即可,当然也可以提前在表单类中定义!

剩下的注册表单,就当是给大家留作的一个作业,大家自行去参照登录表单完善重构一下,加油哦!我相信你可以!

到这里我们的表单验证就大概了解了,之后的章节就是基本的增删改查以及表单验证,都是基于我们这些章节学习的知识点,所以之后的章节就不会过多的去讲解每行代码的意思,重心放在逻辑的展示上,如果基础较差的同学,到这里,可以去反复的把前边所有章节的内容去练习,写代码其实就是写的多了就会了,也就理解了,练习 练习 再练习!

最近发表
标签列表