前言:本文是学习网易微专业的《python全栈工程师 - Flask高级建站》课程的笔记,欢迎学习交流。同时感谢老师们的精彩传授!
通过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
)
.
.
.
为什么要进行验证?
1.用户填写的时候不了解数据要求
2.别有用心的用户不择手段危害网站
验证手段:
1.前端验证,改进用户体验
2.后端验证,提升网站安全性
由于前端的任何验证都可以被忽略,所以永远不要相信前端提交来的数据是安全的。
验证器 | 说明 |
---|---|
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,SelectField
from flask_ckeditor import CKEditorField
# 引入DataRequired模块
from wtforms.validators import DataRequired
class 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
属性。
form = LoginForm()
# if request.method == 'POST':
# 只有数据验证通过才会执行
if form.validator_on_submit():
pass
else:
print(form.errors)
可以在前端页面中进行错误的输出显示,显示格式取决于前端设计。比如这里:
实操:
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
:
.
.
.
<form action="" method="post">
{% for field in form %}
{% if field.widget.input_type != "hidden" %}
<div class="form-group">
{{field.label}}
{{field}}
div>
{% if field.errors %}
<div class="alert alert-warning alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×span>button>
<strong>Warning!strong>
{{ field.errors }}
div>
{% endif %}
{% else %}
{{field}}
{% endif %}
{% endfor %}
form>
.
.
.
内联验证器直接与字段绑定,在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 %}
<div class="container-fluid">
<div class="row">
<div class="main col-md-12 col-lg-12 col-xs-12 col-sm-12" >
<h3>用户注册h3>
{% if message %}
<div class="alert alert-info" role="alert">
{{ message }}
div>
{% endif %}
<div class="body">
<form action="" method="post">
{% for field in form %}
{# 隐藏域元素不用显示label #}
{% if field.widget.input_type!="hidden" %}
<div class="form-group">
{{ field.label }}
{{ field }}
div>
{% else %}
{{ field }}
{% endif %}
{% endfor %}
form>
div>
div>
div>
div>
{% endblock %}
{% block footer %}
{{ super() }}
<script src="{{ url_for('static', filename='ckeditor/ckeditor.js') }}">script>
{{ ckeditor.config(name="intro")}}
{% endblock %}
body>
html>
内联验证器不能重用,可以通过定义一个全局的函数方法来实现一个通用的数据验证器。
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 = message
def __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
获取表单提交的数据,而且这些数据会被自动地验证,转换数据类型。内联验证器
,也可以来写一个类来实现全局验证的验证器(即通用验证器
)。