前言:本文是学习网易微专业的
《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.数据验证的必要性
为什么要进行验证?
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,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
属性。
2.3.数据验证与错误显示
2.3.1.数据验证在视图函数中验证
form = LoginForm()
# if request.method == 'POST':
# 只有数据验证通过才会执行
if form.validator_on_submit():
pass
else:
print(form.errors)
2.3.2.表单错误输出
可以在前端页面中进行错误的输出显示,显示格式取决于前端设计。比如这里:
实操:
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 %}
×
Warning!
{{ 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 %}
{{ message }}
{% endif %}
{% for field in form %}
{# 隐藏域元素不用显示label #}
{% if field.widget.input_type!="hidden" %}
{{ field.label }}
{{ field }}
{% else %}
{{ field }}
{% endif %}
{% endfor %}
{% endblock %}
{% block footer %}
{{ super() }}
{{ ckeditor.config(name="intro")}}
{% endblock %}