Python全栈(七)Flask框架之11.WTForms及其常见应用


flask-wtf是一个简化了WTFForms操作的第三方库,WTForms表单的两个主要功能是 验证用户提交数据的合法性渲染模板,还包括一些其他的功能,如CSRF保护、文件上传等。
安装flask-wtf使用命令 pip install flask-wtf

一、WTForms表单验证

1.表单验证的基本使用

不使用WTF进行表单验证测试:
创建程序主入口wtf_demo.py:

from flask import Flask, request, render_template

app = Flask(__name__)


@app.route('/')
def index():
    return '首页'


@app.route('/register/', methods=['GET', 'POST'])
def register():
    if request.method == 'GET':
        return render_template('register.html')
    else:
        username = request.form.get('username')
        password = request.form.get('password')
        pwd_confirm = request.form.get('pwd_confirm')
        if len(username) < 3 or len(username) > 15:
            return '用户名长度不正确'
        if password != pwd_confirm:
            return '两次密码输入不一致'
        if len(username) < 6 or len(username) > 20:
            return '密码长度不正确'


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

模板目录templates下创建register.html如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册</title>
</head>
<body>
<form action="" method="post">
    用户名:<input type="text" name="username">
    <br>
    密码:<input type="text" name="password">
    <br>
    确认密码:<input type="text" name="pwd_confirm">
    <br>
    <input type="submit" value="注册">
</form>

</body>
</html>

显示:
flask form normal verification

显然,可以达到验证表单的效果,但是在register()视图函数中一般只是得到验证是否成功的结果即可,可以将验证单独抽离出来,使用flask-wtf进行专门验证,新建forms.py如下:

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

class RegisterForm(Form):
    username = StringField(validators=[Length(min=3, max=15, message='用户名长度不正确')])
    password = StringField(validators=[Length(min=6, max=20, message='密码长度不正确')])
    pwd_confirm = StringField(validators=[Length(min=6, max=20), EqualTo('password', message='两次密码不一致')])

修改wtf_demo.py如下:

from flask import Flask, request, render_template
from forms import RegisterForm

app = Flask(__name__)


@app.route('/')
def index():
    return '首页'


@app.route('/register/', methods=['GET', 'POST'])
def register():
    if request.method == 'GET':
        return render_template('register.html')
    else:
        form = RegisterForm(request.form)
        if form.validate():
            return '验证成功'
        else:
            return '验证失败:\n' + str(form.errors)


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

RegisterForm初始化时传入request.form,并且根据form.validate()的值来判断用户提交的数据是否满足表单的验证。

显示:
flask form wtf verification
在forms.py中指定了需要上传的参数,并且指定了验证器,比如username的长度应该在3-15之间,password长度必须在6-20之间,并且应该和pwd_confirm相等才能通过验证。

2.Flask-WTF常用的验证器

Email

可以直接使用Email类进行右键地址的验证。
在forms.py中新增LoginForm如下:

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

class RegisterForm(Form):
    username = StringField(validators=[Length(min=3, max=15, message='用户名长度不正确')])
    password = StringField(validators=[Length(min=6, max=20, message='密码长度不正确')])
    pwd_confirm = StringField(validators=[Length(min=6, max=20), EqualTo('password', message='两次密码不一致')])


class LoginForm(Form):
    email = StringField(validators=[Email(message='邮箱格式不正确')])

主程序中增加视图函数:

from flask import Flask, request, render_template
from forms import RegisterForm, LoginForm

app = Flask(__name__)

app.config['TEMPLATE_AUTO_RELOAD'] = True


@app.route('/')
def index():
    return '首页'


@app.route('/register/', methods=['GET', 'POST'])
def register():
    if request.method == 'GET':
        return render_template('register.html')
    else:
        form = RegisterForm(request.form)
        if form.validate():
            return '验证成功'
        else:
            return '验证失败:\n' + str(form.errors)


@app.route('/login/', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        return render_template('login.html')
    else:
        form = LoginForm(request.form)
        if form.validate():
            return '验证成功'
        else:
            return '验证失败:\n' + str(form.errors)

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

新建login.html如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
    <form action="" method="post">
        <table>
            <tr>
                <td>邮箱:</td>
                <td><input type="text" name="email"></td>
            </tr>
            <tr>
                <td><input type="submit" value="提交"></td>
            </tr>
        </table>
    </form>
</body>
</html>

运行主程序,显示:
flask wtf email

Number

可以通过NumberRange类对数的范围进行验证。
修改forms.py如下;

from wtforms import Form, StringField, IntegerField
from wtforms.validators import Length, EqualTo, Email, NumberRange


class RegisterForm(Form):
    username = StringField(validators=[Length(min=3, max=15, message='用户名长度不正确')])
    password = StringField(validators=[Length(min=6, max=20, message='密码长度不正确')])
    pwd_confirm = StringField(validators=[Length(min=6, max=20), EqualTo('password', message='两次密码不一致')])


class LoginForm(Form):
    age = IntegerField(validators=[NumberRange(1, 120, message='年龄范围有误!!!')])

修改login.html如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
    <form action="" method="post">
        <table>
            <tr>
                <td>年龄:</td>
                <td><input type="text" name="age"></td>
            </tr>
            <tr>
                <td><input type="submit" value="提交"></td>
            </tr>
        </table>
    </form>
</body>
</html>

显示:
flask wtf number

必填

可以通过InputRequired类限制某些字段必填。
forms.py修改如下:

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


class RegisterForm(Form):
    username = StringField(validators=[Length(min=3, max=15, message='用户名长度不正确')])
    password = StringField(validators=[Length(min=6, max=20, message='密码长度不正确')])
    pwd_confirm = StringField(validators=[Length(min=6, max=20), EqualTo('password', message='两次密码不一致')])


class LoginForm(Form):
    username = StringField(validators=[InputRequired(message='用户名必填')])

login.html修改如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
    <form action="" method="post">
        <table>
            <tr>
                <td>用户名:</td>
                <td><input type="text" name="username"></td>
            </tr>
            <tr>
                <td><input type="submit" value="提交"></td>
            </tr>
        </table>
    </form>
</body>
</html>

显示:
flask wtf inputrequired

正则表达式

可以通过Regexp类自定义正则表达式进行验证。
forms.py修改如下:

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


class RegisterForm(Form):
    username = StringField(validators=[Length(min=3, max=15, message='用户名长度不正确')])
    password = StringField(validators=[Length(min=6, max=20, message='密码长度不正确')])
    pwd_confirm = StringField(validators=[Length(min=6, max=20), EqualTo('password', message='两次密码不一致')])


class LoginForm(Form):
    phone_number = StringField(validators=[Regexp(r'1[35789]\d{9}', message='手机号码不正确')])

login.py修改如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
    <form action="" method="post">
        <table>
            <tr>
                <td>手机号:</td>
                <td><input type="text" name="phone_number"></td>
            </tr>
            <tr>
                <td><input type="submit" value="提交"></td>
            </tr>
        </table>
    </form>
</body>
</html>

显示:
flask wtf regexp

URL

可以使用URL类验证某个字符串是否是标准的URL格式。
forms.py修改如下:

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


class RegisterForm(Form):
    username = StringField(validators=[Length(min=3, max=15, message='用户名长度不正确')])
    password = StringField(validators=[Length(min=6, max=20, message='密码长度不正确')])
    pwd_confirm = StringField(validators=[Length(min=6, max=20), EqualTo('password', message='两次密码不一致')])


class LoginForm(Form):
    info_page = StringField(validators=[URL(message='地址格式不正确')])

login.html修改如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
    <form action="" method="post">
        <table>
            <tr>
                <td>个人主页:</td>
                <td><input type="text" name="info_page"></td>
            </tr>
            <tr>
                <td><input type="submit" value="提交"></td>
            </tr>
        </table>
    </form>
</body>
</html>

显示:
flask wtf url

扩展-验证码的验证

要验证验证码长度和有效性,长度用Length验证,有效性可以在表单类中自定义方法即可。

forms.py修改如下:

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


class RegisterForm(Form):
    username = StringField(validators=[Length(min=3, max=15, message='用户名长度不正确')])
    password = StringField(validators=[Length(min=6, max=20, message='密码长度不正确')])
    pwd_confirm = StringField(validators=[Length(min=6, max=20), EqualTo('password', message='两次密码不一致')])


class LoginForm(Form):
    captcha = StringField(validators=[Length(min=4, max=4, message='验证码不正确')])

    def validate_captcha(self, field):
        '''自定义验证,方法名为固定格式,即validate_加要验证的变量名'''
        if field.data != '1234':
            raise ValidationError("验证码错误")

login.html修改如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
    <form action="" method="post">
        <table>
            <tr>
                <td>验证码:</td>
                <td><input type="text" name="captcha"></td>
            </tr>
            <tr>
                <td><input type="submit" value="提交"></td>
            </tr>
        </table>
    </form>
</body>
</html>

显示:
flask wtf captcha
可以得到,自定义验证需要在表单类中定义方法,方法名为固定格式,即validate_加要验证的变量名。

二、WTF渲染模板

wtforms可以渲染模板,省去一部分代码。
测试:
wtf_demo.py如下:

from flask import Flask, request, render_template
from forms import RegisterForm

app = Flask(__name__)

app.config['TEMPLATE_AUTO_RELOAD'] = True


@app.route('/')
def index():
    return '首页'


@app.route('/register/', methods=['GET', 'POST'])
def register():
    form = RegisterForm(request.form)
    if request.method == 'GET':
        return render_template('register.html', form=form)
    else:
        username = form.username.data
        password = form.password.data
        pwd_confirm = form.pwd_confirm.data
        if form.validate():

            return '验证成功--User: ' + username + ' Password: ' + password
        else:
            return '验证失败:\n' + str(form.errors)


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

在渲染模板的时候传入了form表单参数,这样在模板中就可以使用表单form变量了。
forms.py如下:

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


class RegisterForm(Form):
    username = StringField('用户名', validators=[Length(min=3, max=15, message='用户名长度不正确')])
    password = StringField('密码', validators=[Length(min=6, max=20, message='密码长度不正确')])
    pwd_confirm = StringField('密码确认', validators=[Length(min=6, max=20), EqualTo('password', message='两次密码不一致')])

每个变量都增加了一个位置参数,用于在html文件中做标签提示。
register.html如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册</title>
</head>
<body>
<form action="" method="post">
    <table>
        <tr>
            <td>{{ form.username.label }}</td>
            <td>{{ form.username() }}</td>
        </tr>
        <tr>
            <td>{{ form.password.label }}</td>
            <td>{{ form.password() }}</td>
        </tr>
        <tr>
            <td>{{ form.pwd_confirm.label }}</td>
            <td>{{ form.pwd_confirm() }}</td>
        </tr>
        <tr>
            <td></td>
            <td><input type="submit" value="提交"></td>
        </tr>
    </table>
</form>

</body>
</html>

显示:
flask wtf render template

三、WTF文件上传

1.文件上传的基本使用

先创建项目目录flask_upload,创建主文件flask_app.py如下:

from flask import Flask, request,render_template
from werkzeug.utils import secure_filename

app = Flask(__name__)
app.config['TEMPLATE_AUTO_RELOAD'] = True

@app.route('/')
def index():
    return '首页'


@app.route('/upload/', methods=['GET', 'POST'])
def upload():
    if request.method == 'GET':
        return render_template('upload.html')
    else:
        desc = request.form.get('desc')
        image_file = request.files.get('image_file')
        file_name = secure_filename(image_file.filename)
        image_file.save('files/images/' + file_name)
        return '文件上传成功--' + desc


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

创建templates目录,下面创建upload.html如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>文件上传</title>
</head>
<body>
{# 文件上传,form表单必须要添加enctype属性 #}
<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>

在项目目录下创建files文件夹用于保存上传的文件,下面创建images子目录,运行主程序之后,显示:
flask wtf upload
显然,文件上传成功。
我们可以得到:
在模版中的form标签中,需要指定enctype='multipart/form-data'才能上传文件;
在后台如果想要获取上传的文件,应该使用request.files.get(file_name)来获取;
保存文件之前,先要使用werkzeug.utils类中的secure_filename()方法来对上传上来的文件名进行过滤,以确保安全性;
获取到上传上来的文件后,使用filename.save(路径)方法来保存文件。

可以看到,在上传的文件中,纯英文文件名保留不变,有中文的将中文去掉,这是由于werkzeug.utils模块中的secure_filename方法只支持ASCII编码的字符,这个问题是可以解决的,可参考https://blog.csdn.net/qq_36390239/article/details/98847888解决。

2.文件上传的表单验证

上面虽然上传文件成功,但是并未对文件进行验证,存在很大的风险。

进行文件验证测试:
新建forms.py如下:

from wtforms import Form, FileField, StringField
from wtforms.validators import InputRequired
from flask_wtf.file import FileAllowed, FileRequired

class UploadForm(Form):
    image_file = FileField(validators=[FileRequired(message='文件必须上传'), FileAllowed(['jpg', 'png', 'gif'], message='上传文件格式有误')])
    desc = StringField(validators=[InputRequired('必须填写描述')])

修改flask_app.py如下:

from flask import Flask, request,render_template
from werkzeug.utils import secure_filename
from werkzeug.datastructures import CombinedMultiDict
from forms import UploadForm

app = Flask(__name__)
app.config['TEMPLATE_AUTO_RELOAD'] = True

@app.route('/')
def index():
    return '首页'


@app.route('/upload/', methods=['GET', 'POST'])
def upload():
    if request.method == 'GET':
        return render_template('upload.html')
    else:
        form = UploadForm(CombinedMultiDict([request.form, request.files]))
        if form.validate():
            desc = request.form.get('desc')     # 等价于desc = form.desc.data
            image_file = request.files.get('image_file')    # 等价于image_file = form.image_file.data
            file_name = secure_filename(image_file.filename)
            image_file.save('files/images/' + file_name)
            return '文件上传成功--' + desc
        else:
            return '文件上传失败--' + str(form.errors)


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

显示:
flask wtf upload validate
显然,已经实现了文件上传时的验证,其中:

desc = request.form.get('desc')
image_file = request.files.get('image_file')

等价于

desc = form.desc.data
image_file = form.image_file.data

可以得到:
定义表单的时候,对文件的字段需要使用FileField
验证器从flask_wtf.file中导入,FileRequired是用来验证文件上传是否为空,FileAllowed用来验证上传的文件的后缀名;
在视图文件中,使用werkzeug.datastructures类中的CombinedMultiDict()方法将request.form与request.files合并,再传给表单进行验证。

3.访问上传资源

想要读取上传的文件时,需要定义一个视图函数,来获取指定的文件。
在这个视图函数中,使用send_from_directory(文件的目录, 文件名)来获取。

flask_app.py如下:

from flask import Flask, request, render_template, send_from_directory
from werkzeug.utils import secure_filename
from werkzeug.datastructures import CombinedMultiDict
from forms import UploadForm

app = Flask(__name__)
app.config['TEMPLATE_AUTO_RELOAD'] = True


@app.route('/')
def index():
    return '首页'


@app.route('/upload/', methods=['GET', 'POST'])
def upload():
    if request.method == 'GET':
        return render_template('upload.html')
    else:
        form = UploadForm(CombinedMultiDict([request.form, request.files]))
        if form.validate():
            desc = request.form.get('desc')  # 等价于desc = form.desc.data
            image_file = request.files.get('image_file')  # 等价于image_file = form.image_file.data
            file_name = secure_filename(image_file.filename)
            image_file.save('files/images/' + file_name)
            return '文件上传成功--' + desc
        else:
            return '文件上传失败--' + str(form.errors)


@app.route('/images/<filename>')
def get_image(filename):
    return send_from_directory('files/images/', filename)


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

显示:
flask wtf upload access resources
很明显,成功访问到了资源。

原创文章 132 获赞 1491 访问量 36万+

猜你喜欢

转载自blog.csdn.net/CUFEECR/article/details/106026660