Python学习笔记(6.3.10 flask-wtf数据验证)

前言:本文是学习网易微专业的《python全栈工程师 - Flask高级建站》课程的笔记,欢迎学习交流。同时感谢老师们的精彩传授!
一、课程目标
  • 表单数据接收
  • 内置数据验证器
  • 错误消息
  • 自定义验证器
二、详情解读 2.1.表单数据接收 2.1.1.数据接收 通过form表单提交的数据可以使用form.data接收
form = LoginForm() username = form.data['username']

form.data会对表单提交的数据进行验证,数据转换
如果有一个表单字段是Integer类型,比如score,那么:
score = form.data['score']

相当于:
if "score" in request.form: score = int(request.form['score']) else: score = 0

实操:
step:修改app.py文件中的login()视图函数:
. . . @app.route('/login', methods=['get', 'post']) def login(): # 新增 form = LoginForm() message = None if request.method == "POST": # 用户名和密码的数据接收用下面这两行代替 username = form.data['username'] password = form.data['password'] user = User.query.filter_by(username=username).first() if user and user.validate_password(password): session['user'] = user.username # 登录成功返回首页 return redirect(url_for("index")) else: message = "用户名与密码不匹配" return render_template("login.html", message = message, form=form ) . . .

2.2.数据验证器 2.2.1.数据验证的必要性 【Python学习笔记(6.3.10 flask-wtf数据验证)】为什么要进行验证?
1.用户填写的时候不了解数据要求
2.别有用心的用户不择手段危害网站
验证手段:
1.前端验证,改进用户体验
2.后端验证,提升网站安全性
由于前端的任何验证都可以被忽略,所以永远不要相信前端提交来的数据是安全的。
2.2.2.flask-wtf提供的数据验证
验证器 说明
InputRequire(message) 必填字段
Length(min, max, message) 输入长度范围
NumberRange(min, max, message) 输入数值范围
Regexp(regex, flags, message) 正则表达式验证
Url(message) 输入字符串为合法的网址结构
Email(message) 输入必须为邮件地址
EqualTo(fieldname, message) 必须与fieldname的值一致
DateRequired(message) 输入有效性
FileRequired(message) 必须是文件
AnyOf(values, message) 输入值必须在values列表中
NoneOf(values, message) 输入值不在values列表中
实操:
修改forms/account_form.py文件中的LoginForm()为以下代码:
from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, SubmitField, SelectMultipleField, \ widgets, RadioField, TextAreaField,SelectFieldfrom flask_ckeditor import CKEditorField # 引入DataRequired模块 from wtforms.validators import DataRequiredclass LoginForm(FlaskForm): # 用户名和密码新增validtors验证器 username = StringField('用户名', validators=[DataRequired(message="必须填写用户名")], render_kw={"class": "form-control", "placeholder": "输入用户名"}) password = PasswordField('密码', validators=[DataRequired(message="必须填写密码")], render_kw={"class": "form-control", "placeholder": "输入密码"}) # 多选框选项 choices = [(1, "一周免登录"), (2, "二周免登录"), (3, "三周免登录")] remember = CheckBoxField('记忆方式', choices=choices) sex_choices = [(1, '女'), (2, '男')] sex = RadioField('性别', choices=sex_choices) # 这里第一个参数为空,因为登录按钮不用显示label的值 submit = SubmitField('', render_kw={"class": "btn btn-default", "value": "立即登录"}) . . .

经过以下修改后,用户登录表单的用户名和密码输入框就会多个required属性。
2.3.数据验证与错误显示 2.3.1.数据验证在视图函数中验证
form = LoginForm() # if request.method == 'POST': # 只有数据验证通过才会执行 if form.validator_on_submit(): pass else: print(form.errors)

2.3.2.表单错误输出 可以在前端页面中进行错误的输出显示,显示格式取决于前端设计。比如这里:
Python学习笔记(6.3.10 flask-wtf数据验证)
文章图片

实操:
Step1:替换forms/account_form.py文件中的LoginForm()为下面代码:
. . . # 引入Length from wtforms.validators import DataRequired, Length . . . class LoginForm(FlaskForm): # 在验证器里新增Lenth长度验证 username = StringField('用户名', validators=[DataRequired(message="必须填写用户名"), Length(min=15, max=25, message="用户名长度6~15")], render_kw={"class": "form-control", "placeholder": "输入用户名"}) password = PasswordField('密码', validators=[DataRequired(message="必须填写密码")], render_kw={"class": "form-control", "placeholder": "输入密码"}) # 多选框选项 choices = [(1, "一周免登录"), (2, "二周免登录"), (3, "三周免登录")] remember = CheckBoxField('记忆方式', choices=choices) sex_choices = [(1, '女'), (2, '男')] sex = RadioField('性别', choices=sex_choices) # 这里第一个参数为空,因为登录按钮不用显示label的值 submit = SubmitField('', render_kw={"class": "btn btn-default", "value": "立即登录"}) . . .

Step2:修改app.py文件中的login()视图函数为下面代码:
. . . @app.route('/login', methods=['get', 'post']) def login(): form = LoginForm() message = None # 原来的判断是否是post,改为下面这个验证判断 if form.validate_on_submit(): username = form.data['username'] password = form.data['password'] user = User.query.filter_by(username=username).first() if user and user.validate_password(password): session['user'] = user.username # 登录成功返回首页 return redirect(url_for("index")) else: message = "用户名与密码不匹配" else: print(form.errors) return render_template("login.html", message = message, form=form ) . . .

Step3:修改templates/login.html
. . .
{% for field in form %} {% if field.widget.input_type != "hidden" %}
{{field.label}} {{field}}
{% if field.errors %} {% endif %} {% else %} {{field}} {% endif %} {% endfor %}
. . .

2.4.自定义数据验证器 2.4.1.内联验证器 内联验证器直接与字段绑定,在form定义类中定义:
class LoginForm(FlaskForm): username = StringField("用户名", validators=[DataRequired()],...) # 为usename定义一个验证器 def validate_username(form, field): if field.data.find("admin") != -1: raise ValidationError("不能包含敏感字")

实操:
Step1:修改forms/account_form.py文件中的注册表单部分内容:
. . . # 引入ValidationError模块 from wtforms.validators import ValidationError # 注册表单 class RegisterForm(FlaskForm): name = StringField('真实姓名', render_kw={"class": "form-control", "placeholder": "请填写真实姓名"}) username = StringField('用户名', validators=[DataRequired(message="必须填写用户名")], render_kw={"class": "form-control", "placeholder": "请填写用户名"}) password = PasswordField('密码', render_kw={"class": "form-control", "placeholder": "请填写密码"}) confirmpassword = PasswordField('确认密码', render_kw={'class': 'form-control', "placeholder": "请填写确认密码"}) # 这里要加上coerce = int,不然会报错:Not a valid choice sex = RadioField('选择性别', choices=[(1, '男'), (0, '女')] ) like = CheckBoxField('选择爱好', choices=[(1, '钓鱼'), (2, '游泳'), (3, '看书'), (4, '旅游')], render_kw={"class": "checkbox-inline"}) city = SelectField('选择城市', choices=[ ('010', '北京'), ('021', '上海'), ('0512', '苏州'), ], render_kw={"class": "form-control"}) intro = TextAreaField('简介') submit = SubmitField('', render_kw={"class": "btn btn-default", "value": "立即注册"}) # 新增内联验证器,验证用户名是否包含admin字符串 def validate_username(self, field): # 查找用户名是否包含admin字符串 if field.data.find("admin") != -1: raise ValidationError('不能包含敏感字')

Step2:修改views/users.py中的register()视图函数:
. . . # 引入 RegisterForm 注册表单模块 from forms.account_form import RegisterForm user_app = Blueprint("user_app", __name__)@user_app.route("/register", methods=['get','post']) def register(): # 实例化注册表单 form = RegisterForm() message = None # 这里改为验证器提交判断,数据接收都改为form.data[] if form.validate_on_submit(): if validate_username(form.data['username']): return render_template("user/register.html", message="用户名重复")realname = form.data['name'] username = form.data['username'] password = form.data['password'] sex = form.data['sex'] mylike= '|'.join(form.data['like']) city= form.data['city'] intro= form.data['intro']user = User( realname=realname, username=username, sex=sex, mylike=mylike, city=city, intro=intro )# 密码加密 user.hash_password(password) try: db.session.add(user) db.session.commit() return redirect(url_for('login')) except Exception as e: message = "注册失败:" + str(e) else: print(form.errors) # 将form 注册表单传给前端模版 return render_template("user/register.html", message=message, form=form) . . .

Step3:替换templates/user/register.html内容为以下代码:
{% extends "base.html" %} {% block content %}
用户注册
{% if message %} {% endif %}
{% for field in form %} {# 隐藏域元素不用显示label #} {% if field.widget.input_type!="hidden" %}
{{ field.label }} {{ field }}
{% else %} {{ field }} {% endif %} {% endfor %}
{%endblock %} {% block footer %}{{ super() }} src="https://www.it610.com/article/{{ url_for('static', filename='ckeditor/ckeditor.js') }}"> {{ ckeditor.config(name="intro")}} {% endblock %}

2.4.2.通用验证器 内联验证器不能重用,可以通过定义一个全局的函数方法来实现一个通用的数据验证器。
class BadWords: def __init__(self, bad_words, message=None): self.bad_words = bad_words if not message: message = "不能包含敏感词" self.message = message def __call__(self, form, field): for word in self.bad_words: if field.data.find(word) != -1: raise ValidationError(self.message) break

实操:
forms/account_form.py文件中添加以下代码:
. . . from wtforms.validators import ValidationError# ++添加下面的代码++# 如果要定义一个数值区间的验证器,那么参数就类似: # def __init__(self, min, max, message) class BadWords: ''' 敏感词检查通用验证器 ''' def __init__(self, bad_words, message=None): ''' :param bad_words: 敏感词列表 :param message: 错误提示 ''' self.bad_words = bad_words if not message: message = "不能包含敏感词" self.message = messagedef __call__(self, form, field): ''' __call__方法可以让实例对象可以像函数一样调用 badwords = BadWords(['admin','kf'], message="敏感了") 验证调用是通过实例调用: badwords(form, field), 好像执行了 badwords.__call__(form,field)方法一样:param form: 验证表单对象 :param field: 验证字段对象 :return: ''' for word in self.bad_words: if field.data.find(word)!= -1: raise ValidationError(self.message) # 注册表单 class RegisterForm(FlaskForm): name = StringField('真实姓名', render_kw={"class": "form-control", "placeholder": "请填写真实姓名"}) # 给用户名添加通用验证器 BadWords username = StringField('用户名', validators=[ DataRequired(message="必须填写用户名"), BadWords(['admin', '客户服务'], message='不能包含敏感字') ], render_kw={"class": "form-control", "placeholder": "请填写用户名"}) . . .

三、课程小结
  • form.data
    通过form.data获取表单提交的数据,而且这些数据会被自动地验证,转换数据类型。
  • 内置验证器
    内置验证器可以帮助我们实现一些常见的数据验证,如果内置验证器不能满足需求,
    我们可以通过自定义验证器来实现验证。
  • 自定义验证器
    自定义验证器可以通过内联验证器,也可以来写一个类来实现全局验证的验证器(即通用验证器)。

    推荐阅读