Web后端学习笔记 Flask(8) WTForms 表单验证,文件上传

Flask-WTF是简化了WTForms操作的一个第三方库。WTForms表单的两个主要功能是验证用户提交数据的合法性以及渲染模板。同时还包含一些其他的功能。例如CSRF保护,文件上传等功能,安装flask-wtf也会默认安装WTForms,通过pip方式安装:

pip install flask-wtf

表单验证

1. 自定义一个表单类,继承自wtform.Form类。

2. 定义好需要验证的字段,字段的名字必须和模板中input标签的name字段名字相同

3. 在需要验证的字段上,需要指定好具体的数据类型

4. 在相关的字段上,指定好验证器

5. 在视图中就只需要表单类的对象,进行表单验证。

from wtforms import Form, StringField
from wtforms.validators import Length, EqualTo

# 表单验证类
class RegisterForm(Form):
    # 导入验证器
    # message为验证错误时抛出的错误信息
    username = StringField(validators=[Length(min=3, max=10,
                                              message="用户名长度必须3到10位")])
    password = StringField(validators=[Length(min=6, max=10,
                                              message="密码长度必须6到10位")])
    repeat_password = StringField(validators=[Length(min=6, max=10, message="密码错误"),
                                              EqualTo("password")])

 

视图函数的验证:

@app.route('/register/', methods=["GET", "POST"])
def register():
    """
    表单验证
    :return:
    """
    if request.method == "GET":
        return render_template("html/register.html")
    else:
        form = RegisterForm(request.form)
        if form.validate():
            return "success"
        else:
            print(form.errors)    # 获取具体的错误信息
            return "Fail"

 

WTForm常用的验证器:
Email:验证上传的数据是否为邮箱

EqualTo:验证上传的数据是否和另一个字段相等

InputRequired:原始数据的需要验证,如果不是特殊情况,应该使用InputRequired,即指定这个字段必须是要传的,否则会报错

Length:长度限制

NumberRange:数字的区间

Regexp:自定义正则表达式

URL:必须要是url的形式

UUID:验证uuid

# -*- coding: utf-8 -*-
from wtforms import Form, StringField, IntegerField
from wtforms.validators import Length, EqualTo, InputRequired, Email, NumberRange, URL, UUID, Regexp


# 表单验证类
class RegisterForm(Form):
    # 导入验证器
    # message为验证错误时抛出的错误信息
    username = StringField(validators=[Length(min=3, max=10,
                                              message="用户名长度必须3到10位")])
    password = StringField(validators=[Length(min=6, max=10,
                                              message="密码长度必须6到10位")])
    repeat_password = StringField(validators=[Length(min=6, max=10, message="密码错误"),
                                              EqualTo("password")])


# 表单验证类
class LoginForm(Form):
    # email = StringField(validators=[Email(message="邮箱格式错误")])
    username = StringField(validators=[InputRequired(message="用户名不能为空")])
    age = IntegerField(validators=[NumberRange(20, 55)])
    phone = StringField(validators=[Regexp(r"1[38745]\d{9}")])

 

自定义验证器:

当WTForm中的验证器不能满足业务需求的时候,用户可以自定义自己的验证器:

例如自定义验证码的验证器:

1. 定义一个方法,方法的命名规则是 validate_字段名(self,field)

2. 在方法中使用field.data可以获取到字段的具体值

3. 如果验证成功,那么可以什么都不做

4. 如果验证失败,需要抛出一个异常

# 表单验证类
class LoginForm(Form):
    # email = StringField(validators=[Email(message="邮箱格式错误")])
    # username = StringField(validators=[InputRequired(message="用户名不能为空")])
    # age = IntegerField(validators=[NumberRange(20, 55)])
    # phone = StringField(validators=[Regexp(r"1[38745]\d{9}")])
    captcha = StringField(validators=[Length(4, 4)])  # 首先验证长度

    # 假设验证码为1234, 自定义captcha验证器,函数名必须以validate开头
    # 所以在验证captcha的时候,除了验证Length,还会自动调用下面自定义的函数进行验证
    def validate_captcha(self, field):   # 针对具体的字段做验证
        # 通过field.data获取数据
        if field.data == "1234":
            return "Success"
        else:
            raise ValidationError("验证码错误")

 

WTForm渲染模板(不推荐使用)

WTForm可以结合jinja2模板渲染表单元素,例如可以渲染各种input, radio等标签

1. 首先,在form.py文件中定义相关的表单类,定义相关的字段

class SettingForm(Form):
    username = StringField("用户名", validators=[InputRequired()])
    age = IntegerField("年龄", validators=[NumberRange(20, 60)])
    remember = BooleanField("记住我: ")
    tags = SelectField("标签", choices=[("1", "Java"), ("2", "C++"), ("3", "Android")]

2. 在视图函数中,将实例化的form对象作为参数,传递到模板文件中:

@app.route('/settings/', methods=["GET", "POST"])
def setting():
    if request.method == "GET":
        form = SettingForm()     # 利用form渲染模板
        return render_template("html/settings.html", form=form)   # 传递参数
    else:
        form = SettingForm(request.form)
        pass

3. 在模板文件中,可以通过变量绑定来使用,同时,可以对相应的字段传入样式参数,id, class

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>设置</title>
    <style>
        .username-input
        {
            background: blue;
        }
        #age-input
        {
            background: pink;
        }
    </style>
</head>
<body>
    <form action="" method="post">
        <table>
            <tbody>
                <tr>
                    <!--利用form渲染页面-->
                    <td>{{ form.username.label }}</td>
{#                    <td>用户名</td>#}
{#                    <td><input type="text" name="username"></td>#}
                    <td>{{ form.username(class="username-input") }}</td>
                </tr>
                <tr>
                    <!--传入参数-->
                    <td>{{ form.age.label }}</td>
                    <td>{{ form.age(id="age-input") }}</td>
                </tr>
                <tr>
                    <td>{{ form.remember.label }}</td>
                    <td>{{ form.remember() }}</td>
                </tr>
                <tr>
                    <td>{{ form.tags.label }}</td>
                    <td>{{ form.tags() }}</td>
                </tr>
                <tr>
                    <td></td>
                    <td><input type="submit" value="提交"></td>
                </tr>
            </tbody>
        </table>
    </form>
</body>
</html>

渲染的效果:

文件上传

1. 在模板中,需要指定input标签的enctype才能上传文件。upload.html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>上传</title>
</head>
<body>
    <form action="" method="post" enctype="multipart/form-data">   <!--上传文件要写enctype-->
        <table>
            <tbody>
                <tr>
                    <td>头像: </td>
                    <td><input type="file" name="avatar"></td>
                </tr>
                <tr>
                    <td>描述: </td>
                    <td><input type="text" name="desc"></td>
                </tr>
                <tr>
                    <td></td>
                    <td><input type="submit" value="提交"></td>
                </tr>
            </tbody>
        </table>
    </form>
</body>
</html>

完成上传界面:

2. 在后台如果想要获取上传的文件,那么应该使用request.files.get(name)获取上传的文件,这里的name值表示的是对应的input标签的name属性的值。

@app.route('/upload/', methods=["GET", "POST"])
def upload():
    if request.method == "GET":
        return render_template("html/upload.html")
    else:
        # 接受用户上传的数据
        desc = request.form.get("desc")
        avatar = request.files.get("avatar")        # 获取文件
        # 保存文件
        file_name = secure_filename(avatar.filename)  # 对文件名进行安全检测
        avatar.save("uploaded/" + file_name)
        print(desc)
        return "文件上传成功"

3. 获取到上传的文件后,可以使用save方法保存文件

【注】: 在保存文件之前,需要使用secure_filename对文件名进行过滤,消除安全隐患:

from werkzeug.utils import secure_filename    # 对文件名进行包装

4. 在服务器上读取文件,应该定义一个url来获取指定文件,在视图函数中使用send_from_directory(文件目录,文件名)来获取服务器上的文件:

@app.route('/show/<file_name>/')
def show_image(file_name):
    return send_from_directory("uploaded/", filename=file_name)

完整的app.py文件如下:

from flask import Flask, url_for, request, render_template
import config
from exts import db
from forms import RegisterForm, LoginForm, SettingForm
import os
from werkzeug.utils import secure_filename    # 对文件名进行包装
from flask import send_from_directory

app = Flask(__name__)
app.config.from_object(config)
db.init_app(app)    # db获取app中数据库的连接方式


@app.route('/')
def hello_world():
    return 'Hello World!'


@app.route('/profile/')
def profile():
    return "profile page"


@app.route('/register/', methods=["GET", "POST"])
def register():
    """
    表单验证
    :return:
    """
    if request.method == "GET":
        return render_template("html/register.html")
    else:
        form = RegisterForm(request.form)
        if form.validate():
            return "success"
        else:
            print(form.errors)    # 获取具体的错误信息
            return "Fail"


@app.route('/login/', methods=["GET", "POST"])
def login():
    if request.method == "GET":
        return render_template("html/login.html")
    else:
        form = LoginForm(request.form)
        if form.validate():
            return "success"
        else:
            print(form.errors)
            return "Failed"


@app.route('/settings/', methods=["GET", "POST"])
def setting():
    if request.method == "GET":
        form = SettingForm()     # 利用form渲染模板
        return render_template("html/settings.html", form=form)
    else:
        form = SettingForm(request.form)
        pass


@app.route('/upload/', methods=["GET", "POST"])
def upload():
    if request.method == "GET":
        return render_template("html/upload.html")
    else:
        # 接受用户上传的数据
        desc = request.form.get("desc")
        avatar = request.files.get("avatar")        # 获取文件
        # 保存文件
        file_name = secure_filename(avatar.filename)  # 对文件名进行安全检测
        avatar.save("uploaded/" + file_name)
        print(desc)
        return "文件上传成功"


@app.route('/show/<file_name>/')
def show_image(file_name):
    return send_from_directory("uploaded/", filename=file_name)


if __name__ == '__main__':
    app.run()

利用Flask_wtf验证上传的文件:

1. 对上传的问价使用表单验证,在定义表单的时候,对文件的字段需要采用FileField进行验证

2. 验证器应该从flask_wtf.file中导入,需要使用的是flask_wtf.file.FileRequired验证文件是否为空,flask_wtf.file.FileAllowed

验证文件的格式是否满足要求。

3. 在进行验证的时候,因为上传的字符串需要使用request.form进行获取,上传的文件需要使用request.file进行获取,所以在进行验证的时候,需要对这两个数据进行组合,这两个数据都是immutableDict,不可变字典的形式,所以在视图函数中使用CombineMultiDict将字符串和文件组合在一起,作为表单验证的输入参数。

示例代码:

form.py中定义的验证表单

from flask_wtf.file import FileRequired, FileAllowed    # 验证文件必须上传,以及上传的类型
# 定义上传表单验证
class UploadForm(Form):
    avatar = FileField(validators=[FileRequired(), FileAllowed(["jpg", "png", "gif"])])
    desc = StringField(validators=[InputRequired()])

视图函数:

from werkzeug.datastructures import CombinedMultiDict   # 将两个不可变的字典组合到一起

@app.route('/upload/', methods=["GET", "POST"])
def upload():
    if request.method == "GET":
        return render_template("html/upload.html")
    else:
        # 接受用户上传的数据
        # form = UploadForm(request.form)   # 因为这里字符串是根据request.form获取的 文件时根据request.form获取的,需要组合
        form = UploadForm(CombinedMultiDict([request.form, request.files]))
        if form.validate():
            desc = request.form.get("desc")
            avatar = request.files.get("avatar")  # 获取文件
            
            # 另一种数据获取方法
            # desc_ = form.desc.data
            # avatar_ = form.avatar.data
            
            # 保存文件
            file_name = secure_filename(avatar.filename)  # 对文件名进行安全检测
            avatar.save("uploaded/" + file_name)
            print(desc)
            return "文件上传成功"
        else:
            print(form.errors)
            return "文件上传失败"

 

posted @ 2020-04-16 09:26  Alpha205  阅读(199)  评论(0编辑  收藏  举报