1、WTForms表单验证
Flask-WTF
Flask-WTF是简化了WTForms操作的一个第三方库。WTForms表单的两个主要功能是验证用户提交数据的合法性以及渲染模板。当然还包括一些其他的功能:CSRF保护,文件上传等。安装Flask-WTF默认也会安装WTForms,因此使用以下命令来安装Flask-WTF。
pip install flask-wtf
安装完Flask-WTF后。来看下第一个功能,就是用表单来做数据验证,现在有一个forms.py文件,然后在里面创建一个RegistForm的注册验证表单.
表单验证–邮箱验证等常用验证器
邮箱验证需要先导入新的库:email_validator
主程序文件app.py
from flask import Flask, request, render_template
from forms import RegistForm # 设置为根目录之后,导入forms.py中的RegistForm进行表单验证
app = Flask(__name__)
@app.route("/")
def index():
return "123"
from forms import LoginForm
# login 登录验证
@app.route("/login/", methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template('login.html')
else:
form = LoginForm(request.form) # 将forms.py中的验证信息都传入form中
if form.validate(): # 调用validate方法后,会将form传入forms.py中用RegistForm做个验证
return "success"
else:
# 验证错误信息
print(form.errors)
print(form.errors.get('captcha')[1]) # 输出错误信息message,可以传回前端进行渲染
return "fail"
if __name__ == '__main__':
app.run(debug=True)
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post">
<table>
<!-- 邮箱验证 -->
<tr>
<td>邮箱:</td>
<!-- name="email"必须要和forms.py中的email = StringField(validators=[Email()])一样才能使用 -->
<td><input type="text" name="email"></td>
</tr>
<!-- 年龄验证 -->
<tr>
<td>年龄:</td>
<!-- name="age"必须要和forms.py中的一样才能使用 -->
<td><input type="text" name="age"></td>
</tr>
<!-- 用户名验证 -->
<tr>
<td>用户名:</td>
<td><input type="text" name="username"></td>
</tr>
<!-- 手机号验证 -->
<tr>
<td>手机号:</td>
<td><input type="text" name="phone"></td>
</tr>
<!-- url验证 -->
<tr>
<td>链接:</td>
<td><input type="text" name="info"></td>
</tr>
<!-- 验证码验证 -->
<tr>
<td>验证码:</td>
<td><input type="text" name="captcha"></td>
</tr>
<tr>
<td><input type="submit" value="提交"></td>
</tr>
</table>
</form>
</body>
</html>
验证文件 forms.py
# -*- encoding: utf-8 -*-
"""
@File : forms.py
@Time : 2020/4/29 17:07
@Author : chen
"""
from wtforms import Form, StringField, validators, IntegerField, ValidationError
from wtforms.validators import Length, Regexp, EqualTo, Email, number_range, InputRequired, URL
# Regexp 正则表达式模块
# wtforms.validators包含很多内置验证器
# EqualTo 验证器:密码不一致
# 表单验证采用wtforms模块
class RegistForm(Form):
# 实现判断username的长度验证3-10
username = StringField(validators=[Length(min=3, max=10, message="用户名长度不正确")])
# 判断password的长度验证3-10
password = StringField(validators=[Length(min=3, max=10)])
# 判断 password_repate 的长度验证3-10 EqualTo("password")验证password_repate是否一致
password_repate = StringField(validators=[Length(min=3, max=10), EqualTo("password", message="两次密码不一致")])
# 表单验证--新的验证器
class LoginForm(Form):
email = StringField(validators=[Email()]) # 注意是方法
age = IntegerField(validators=[number_range(3, 120, message="年龄输入有误")]) # 年龄类型是int,number_range(min, max)
username = StringField(validators=[InputRequired()]) # InputRequired()必须输入
# 手机号验证 正则表达Regexp(r'1[8|3|7]\d{9}')代表 开头是1,第二位数字是[8,3,7]中的一个,\d{9}代表九个数字
phone = StringField(validators=[Regexp(r'1[8|3|7]\d{9}')])
# 网页链接 URL() 源码中有判断的正则表达式
info = StringField(validators=[URL()])
# 验证码验证
captcha = StringField(validators=[Length(min=4, max=4)])
'''
# 验证内容 无效写法
if captcha == '1234':
print("验证码正确")
else:
print("验证码错误")
'''
# 验证码内容验证 必须定义一个 validate_+验证名称 的函数进行验证,即validate_captcha
def validate_captcha(self, field):
print(type(field))
print(field)
if field.data != '1234':
raise ValidationError('验证码错误') # 主动抛出异常
2、文件上传
- 在模版中,form表单中,需要指定encotype='multipart/form-data’才能上传文件。
- 在后台如果想要获取上传的文件,那么应该使用request.files.get(‘avatar’)来获取。
- 保存文件之前,先要使用werkzeug.utils.secure_filename来对上传上来的文件名进行一个过滤。这样才能保证不会有安全问题。
- 获取到上传上来的文件后,使用avatar.save(路径)方法来保存文件。、
- 从服务器上读取文件,应该定义一个url与视图函数,来获取指定的文件。在这个视图函数中,使用send_from_directory(文件的目录,文件名)来获取。
程序主文件flask_upload_demo.py
# -*- encoding: utf-8 -*-
"""
@File : flask_upload_demo.py
@Time : 2020/4/30 20:53
@Author : chen
"""
from flask import Flask, render_template, request
import os # 导入文件地址模块
app = Flask(__name__)
upload_path = os.path.join(os.path.dirname(__file__), 'images')
print(upload_path) # 'images'文件的绝对路径
@app.route('/')
def index():
return '123'
@app.route("/upload/", methods=['GET', 'POST'])
def upload():
# return render_template('upload.html')
if request.method == 'GET': # get请求用request.args.get()接收
return render_template('upload.html')
else:
desc = request.form.get('desc') # post请求用request.form.get()接收
image_file = request.files.get('image_file') # request.files.get()接收文件类型
# image_file.filename 保存文件的名字 对应html中的name="image_file" 'images'保存的文件夹名称
image_file.save(os.path.join('images', image_file.filename)) # os.path.join('images', image_file.filename)是两个参数在join()中
print(os.path.join('images')) # 'images'文件的路径
return '文件上传成功'
if __name__ == '__main__':
app.run(debug=True)
upload.html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!-- enctype="multipart/form-data" 文件上传必须要添加的属性 -->
<form action="" method="post" enctype="multipart/form-data">
<table>
<tr>
<td>头像</td>
<td><input type="file" name="image_file"></td>
</tr>
<tr>
<td>描述</td>
<td><input type="text" name="desc"></td>
</tr>
<tr>
<td><input type="submit" value="上传"></td>
</tr>
</table>
</form>
</body>
</html>
image_file.filename 保存文件的名字 对应html中的name="image_file"
’images’新创建的保存的文件夹名称
3、上传文件名称中文问题
参考CSDN博主「kler」的博客:https://blog.csdn.net/qq_36390239/article/details/98847888
1、修改源码;
…Python37\Lib\site-packages\werkzeug\utils.py,找到secure_filename(filename)函数。
修改secure_filename(filename)函数源码后
if isinstance(filename, text_type):
from unicodedata import normalize
filename = normalize('NFKD', filename).encode('utf-8', 'ignore') # 转码
if not PY2:
filename = filename.decode('utf-8') # 解码
for sep in os.path.sep, os.path.altsep:
if sep:
filename = filename.replace(sep, ' ')
# myself define
# 正则增加对汉字的过滤
# \u4E00-\u9FBF 中文
#构建新正则
_filename_ascii_add_strip_re = re.compile(r'[^A-Za-z0-9_\u4E00-\u9FBF.-]')
# 使用正则
filename = str(_filename_ascii_add_strip_re.sub('', '_'.join(
filename.split()))).strip('._')
2、使用第三方库(pypinyin),将中文名转换成拼音;
from pypinyin import lazy_pinyin
filename = secure_filename(''.join(lazy_pinyin(file.filename)))
3、使用uuid模块重命名文件名
python的uuid模块提供UUID类和函数uuid1(), uuid3(), uuid4(), uuid5() 来生成1, 3, 4, 5各个版本的UUID
( 需要注意的是: python中没有uuid2()这个函数)。
a) uuid.uuid1([node[, clock_seq]]) : 基于时间戳
使用主机ID, 序列号, 和当前时间来生成UUID, 可保证全球范围的唯一性. 但由于使用该方法生成的UUID中包含有主机
的网络地址, 因此可能危及隐私. 该函数有两个参数, 如果 node 参数未指定, 系统将会自动调用 getnode() 函数来
获取主机的硬件地址. 如果 clock_seq 参数未指定系统会使用一个随机产生的14位序列号来代替.
b) uuid.uuid3(namespace, name) : 基于名字的MD5散列值
通过计算命名空间和名字的MD5散列值来生成UUID, 可以保证同一命名空间中不同名字的唯一性和不同命名空间的唯一性,
但同一命名空间的同一名字生成的UUID相同.
c) uuid.uuid4() : 基于随机数
通过随机数来生成UUID. 使用的是伪随机数有一定的重复概率.
d) uuid.uuid5(namespace, name) : 基于名字的SHA-1散列值
通过计算命名空间和名字的SHA-1散列值来生成UUID, 算法与 uuid.uuid3() 相同.
4、验证上传文件格式
验证代码forms.py
# -*- encoding: utf-8 -*-
"""
@File : forms.py
@Time : 2020/5/1 14:15
@Author : chen
"""
from wtforms import Form, FileField, StringField
from flask_wtf.file import FileAllowed, FileRequired
from wtforms.validators import InputRequired
# 验证传输文件的类型
class UploadForm(Form):
# FileRequired()文件必须上传 FileAllowed(['jpg', 'png', 'gif'])允许接收的文件类型
image_file = FileField(validators=[FileRequired(), FileAllowed(['jpg', 'png', 'gif'])])
desc = StringField(validators=[InputRequired()])
主程序文件flask_upload_demo.py
# -*- encoding: utf-8 -*-
"""
@File : flask_upload_demo.py
@Time : 2020/4/30 20:53
@Author : chen
"""
from flask import Flask, render_template, request, send_from_directory # send_from_directory 通过路由访问上传的文件
import os # 导入文件地址模块
from werkzeug.utils import secure_filename # 上传文件中文问题解决
from forms import UploadForm # 导入文件的类型判断验证
from werkzeug.datastructures import CombinedMultiDict # 合并参数进行传递
app = Flask(__name__)
upload_path = os.path.join(os.path.dirname(__file__), 'images')
print(upload_path) # 'images'文件的绝对路径
@app.route('/')
def index():
return '123'
@app.route("/upload/", methods=['GET', 'POST'])
def upload():
# return render_template('upload.html')
if request.method == 'GET': # get请求用request.args.get()接收
return render_template('upload.html')
else:
'''
desc = request.form.get('desc') # post请求用request.form.get()接收
image_file = request.files.get('image_file') # request.files.get()接收文件类型
# 转换文件名称,中文的问题
filename = secure_filename(image_file.filename)
# image_file.filename 保存文件的名字 对应html中的name="image_file" 'images'保存的文件夹名称
image_file.save(os.path.join('images', filename)) # os.path.join('images', image_file.filename)是两个参数在join()中
print(os.path.join('images')) # 'images'文件的路径
'''
# 验证上传文件类型
form = UploadForm(CombinedMultiDict([request.form, request.files]))
if form.validate():
# desc = request.form.get('desc') # post请求用request.form.get()接收
# image_file = request.files.get('image_file') # request.files.get()接收文件类型
# 简化写法,功能与上面相同
desc = form.desc.data
image_file = form.image_file.data
# 转换文件名称,中文的问题
filename = secure_filename(image_file.filename)
image_file.save(os.path.join('images', filename)) # os.path.join('images', image_file.filename)是两个参数在join()中
return '文件上传成功'
else:
print(form.errors) # 输出错误信息
return '文件上传失败'
# 通过路由访问上传的文件
@app.route("/image/<filename>")
def get_image(filename):
return send_from_directory('images', filename) # 'images'文件夹名称,filename文件名称
if __name__ == '__main__':
app.run(debug=True)
注意:
通过路由访问上传的文件
# 通过路由访问上传的文件
@app.route("/image/<filename>")
def get_image(filename):
return send_from_directory('images', filename) # 'images'文件夹名称,filename文件名称