您的位置:首页 > 编程语言 > Python开发

Python全栈(七)Flask框架之11.WTForms及其应用和Cookie的简单使用

2020-05-10 04:16 399 查看

文章目录

  • 二、WTF渲染模板
  • 三、WTF文件上传
  • 四、Cookie的使用

  • 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>

    显示:

    显然,可以达到验证表单的效果,但是在

    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()
    的值来判断用户提交的数据是否满足表单的验证。

    显示:

    在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>

    运行主程序,显示:

    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>

    显示:

    必填

    可以通过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>

    显示:

    正则表达式

    可以通过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>

    显示:

    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>

    显示:

    扩展-验证码的验证

    要验证验证码长度和有效性,长度用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>

    显示:

    二、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>

    显示:

    三、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=
    20000
    "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子目录,运行主程序之后,显示:

    显然,文件上传成功。
    我们可以得到:
    在模版中的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)

    显示:

    显然,已经实现了文件上传时的验证,其中:

    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)

    显示:

    很明显,成功访问到了资源。

    四、Cookie的使用

    1.Cookie的基本概念

    在网站中,http请求是无状态的,也就是说即使第一次和服务器连接后并且登录成功后,第二次请求服务器依然不能知道当前请求是哪个用户。
    cookie的出现就是为了解决这个问题,类型为小型文本文件,是某些网站为了辨别用户身份,进行Session跟踪而储存在用户本地终端上的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息。
    第一次登录后服务器返回一些数据(cookie)给浏览器,然后浏览器保存在本地,当该用户发送第二次请求的时候,就会自动将上次请求存储的cookie数据自动传送给服务器,服务器通过浏览器携带的数据来判断用户。
    cookie存储的数据量有限,不同的浏览器有不同的存储大小,但一般不超过4KB。因此使用cookie只能存储一些小量的数据。

    2.Flask中使用Cookie

    在Flask中操作cookie,是通过Response对象来操作,可以在response返回之前,通过

    response.set_cookie()
    方法来设置,这个方法有以下几个参数:

    • key
      cookie的键
      value
      cookie的键对应的值。
    • max_age
      cookie的过期时间,如果不设置,则浏览器关闭后就会自动过期。
    • expires
      过期时间,时间戳的形式(1970到现在的时间)。
    • domain
      该cookie在哪个域名中有效,一般设置子域名,比如cms.example.com。
    • path
      该cookie在哪个路径下有效,即当前主域名。

    cookie简单测试:
    flask_app.py如下:

    from flask import Flask, Response
    
    app = Flask(__name__)
    app.config['TEMPLATE_AUTO_RELOAD'] = True
    
    @app.route('/')
    def index():
    return '首页'
    
    @app.route('/cookietest/')
    def cookietest():
    res = Response('Hello World!!')
    res.set_cookie('username', value='corley', max_age=3)
    return res
    
    if __name__ == '__main__':
    app.run(debug=True)

    显示:

    cutercorley 原创文章 129获赞 1432访问量 34万+ 关注 私信
    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: