Flask 第四章 高级(Cookie、Session、Local对象、上下文、钩子函数、信号机制、WTForms介绍和基本使用、安全文件上传和访问)

Cookie

Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。
当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。
浏览器对cookie数量和大小有限制的!如果超过了这个限制,你的信息将丢失。
不同的浏览器存储的Cookie的数量不同!
尽量保证cookie的数量以及相应的大小。cookie个数最好 <20~30个;cookie大小最好 < 4K

设置

设置cookie是在Response的对象上设置。flask.Response 对象有一个 set_cookie 方法,可以通过这个方法来设置 cookie信息。key,value形式设置信息

from flask import Flask, make_response
app = Flask(__name__)

@app.route('/cookie')
def set_cookie():
    resp = make_response('set cookie ok')
    resp.set_cookie('uname', 'IT')
    return resp

查看Cookie

在Chrome浏览器中查看cookie的方式:

方式1:借助于 开发调式工具进行查看
方式2:在Chrome的设置界面->高级设置->内容设置->所有cookie->找到当前域名下的cookie。

from flask import request

@app.route('/get_cookie')
def get_cookie():
    resp = request.cookies.get('uname')
    return resp

删除cookie

方式1:通过 Response对象.delete_cookie ,指定cookie的key,就可以删除cookie。

from flask import request

@app.route('/delete_cookie')
def delete_cookie():
    response = make_response('helloworld')
    response.delete_cookie('uname')
    return response

方式2:在客户端浏览器人为的删除

Cookie的有效期

默认的过期时间:如果没有显示的指定过期时间,那么这个cookie将会在浏览器关闭后过期。

max_age:以秒为单位,距离现在多少秒后cookie会过期。
expires:为datetime类型。这个时间需要设置为格林尼治时间,相对北京时间来说 会自动+8小时
如果max_age和expires都设置了,那么这时候以max_age为标准。

max_age在IE8以下的浏览器是不支持的。expires虽然在新版的HTTP协议中是被废弃了,但是到目前为止,所有的浏览器都还是能够支持,所以如果想要兼容IE8以下
的浏览器,那么应该使用expires,否则可以使用max_age。

# max_age以秒为单位设置cookie的有效期   
age = 60*60*2
resp.set_cookie('uname','itbaizhan',max_age=age)

# expires 以指定时间为cookie的有效期
# 16+8 == 24
tmp_time = datetime(2021, 11,11,hour=18,minute=0,second=0)
resp.set_cookie('uname','python',expires=tmp_time)

# expires 以指定时间为cookie的有效期
tmp_time = datetime.now() + timedelta(days=2)
resp.set_cookie('uname','python_sql',expires=tmp_time)

Session

Session和Cookie的作用有点类似,都是为了存储用户相关的信息,都是为了解决http协议无状态的这个特点。
不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。
客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。
客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。

设置\修改\读取\删除

class DefaultConfig(object):
    SECRET_KEY = '9273498329hiHiasf'
    
app.config.from_object(DefaultConfig)

# 或者直接设置
app.secret_key='xihwidfw9efw'

from flask import session
@app.route('/set_session/')
def set_session():
    session['username'] = 'ITtest'
    return 'set session ok'

@app.route('/get_session/')
def get_session():
    username = session.get('username')
    return 'get session username{}'.format(username)


@app.route('/del_session/')
def delete_session():
    #删除指定的key的session
    session.pop('uname')
    #删除session中的所有的key 【删除所有】
    session.clear()
    return '删除成功''

置Session的有效期

如果没有设置session的有效期。那么默认就是浏览器关闭后过期。
如果设置session.permanent=True,那么就会默认在31天后过期。
如果不想在31天后过期,按如下步骤操作:

session.permanent=True
可以设置
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hour=2) 在两个小时后过期。

from flask import Flask,session
from datetime import timedelta

app = Flask(__name__)
app.secret_key = 'afoasfoo'
app.config['PERMANENT_SESSION_LIFETIME'] =timedelta(days=2)

@app.route('/')
def index():
   return 'Hello!!'
   
@app.route('/set_session/')
def set_session():
    # 设置session的持久化,默认是增加了31天
    session.permanent = True
    session['uname'] = '10001'
    return '设置一个Session的信息'
    
@app.route('/get_session/')
def get_session():
    # 如果服务器关闭掉了,session的有效期,依然是之前系统保存日期
    # 如果secret_key设置是一个固定的值,那么服务器重启不会影响session的有效期
    # 如果secret_key设置不是一个固定的值,那么服务器之前设置的session将全部过期
    return session.get('uname')
if __name__ == '__main__':
    app.run(debug=True)

Local对象

在Flask中,类似于 request 对象,其实是绑定到了一个 werkzeug.local.Local对象上。
这样,即使是同一个对象,那么在多个线程中都是隔离的。类似的对象还有 session 对象。

from threading import Thread,local
local =local()
local.request = '具体用户的请求对象'
class MyThread(Thread):
    def run(self):
        local.request = 'sxt'
        print('子线程:',local.request)
mythread = MyThread()
mythread.start()
mythread.join()
print('主线程:',local.request)

from werkzeug.local import Local
local = Local()
local.request = '具体用户的请求对象'
class MyThread(Thread):
    def run(self):
        local.request = 'sxt'
        print('子线程:',local.request)
        
mythread = MyThread()
mythread.start()
mythread.join()
print('主线程:',local.request)

Flask_app上下文

上下文的一个典型应用场景就是用来缓存一些我们需要在发生请求之前或者要使用的资源。
应用上下文是存放到一个 LocalStack 的栈中。和应用app相关的操作就必须要用到应用上下文.
在视图函数中,不用担心应用上下文的问题。因为视图函数要执行,那么肯定是通过访问url的方式执行的,那么这种情况下,Flask底层就已经自动的帮我们把应用上下文都推入到了相应的栈中。
如果想要在视图函数外面执行相关的操作:

手动方式

from flask import Flask,current_app
app = Flask(__name__)
#app上下文
app_context = app.app_context()
app_context.push()

print(current_app.name)
@app.route('/')
def hello_world():
    print(current_app.name) #获取应用的名称
    return 'Hello World!'
if __name__ == '__main__':
    app.run(debug=True)

使用with

from flask import Flask,current_app

app = Flask(__name__)
#app上下文
#换一种写法
with app.app_context():
   print(current_app.name)
   
@app.route('/')
def hello_world():
    print(current_app.name) #获取应用的名称
    return 'Hello World!'
if __name__ == '__main__':
    app.run(debug=True)

Flask_request上下文详解

请求上下文也是存放到一个 LocalStack 的栈中。和请求相关的操作就必须用到请求上下文,比如使用 url_for 反转视图函数。

在视图函数中,不用担心请求上下文的问题。因为视图函数要执行,那么肯定是通过访问url的方式执行的,那么这种情况下,Flask底层就已经自动的帮我们把应用上下文和请求上下文都推入到了相应的栈中。
如果想要在视图函数外面执行相关的操作,比如反转url,那么就必须要手动推入请求上下文:

底层代码执行说明:

  1. 推入请求上下文到栈中,会首先判断有没有应用上下文
  2. 如果没有那么就会先推入应用上下文到栈中
  3. 然后再推入请求上下文到栈中
from flask import Flask,url_for

app = Flask(__name__)

@app.route('/')
def index():
    url = url_for('test_url')
    return f'Hello!==={url}'

@app.route('/test/')
def test_url():
    return '这个是为了测试请求上下文'
    
# RuntimeError: Attempted to generate a URL without the application context being pushed.
# This has to be executed when application context is available.
# with app.app_context():
#     url = url_for('test_url')
#     print(url)
# RuntimeError: Application was not able to create a URL adapter for request independent URL generation.
# You might be able to fix this by setting the SERVER_NAME config variable.
with app.test_request_context():
    url = url_for('test_url')
    print(url)
if __name__ == '__main__':
    app.run(debug = True)

1.应用上下文:Flask底层是基于werkzeug,werkzeug是可以包含多个app的,所以这时候用一个栈来保存。如果你在使用app1,那么app1应该是要在栈的顶部,如果用完了app1,那么app1应该从栈中删除。方便其他代码使用下面的app。
2. 如果在写测试代码,或者离线脚本的时候,我们有时候可能需要创建多个请求上下,这时候就需要存放到一个栈中了。使用哪个请求上下文的时候,就把对应的请求上下文放到栈的顶部,用完了就要把这个请求上下文从栈中移除掉。

线程隔离的g对象

g对象是在整个Flask应用运行期间都是可以使用的。并且也跟request一样,是线程隔离的。这个对象是专门用来存储开发者自己定义的一些数据,方便在整个Flask程序中都可以使用。一般使用就是,将一些经常会用到的数据绑定到上面,以后就直接从g上面取就可以了,而不需要通过传参的形式,这样更加方便

钩子函数

在Flask中钩子函数是使用特定的装饰器装饰的函数。为什么叫做钩子函数呢,是因为钩子函数可以在正常执行的代码中,插入一段自己想要执行的代码。那么这种函数就叫做钩子函数

before_first_request

处理项目的第一次请求之前执行。

@app.before_first_request    
def first_request():      
    print('first time request')

before_request

在每次请求之前执行。通常可以用这个装饰器来给视图函数增加一些变量。请求已经到达了Flask,但是还没有进入到具体的视图函数之前调用。一般这个就是在视图函数
之前,我们可以把一些后面需要用到的数据先处理好,方便视图函数使用。

@app.before_request    
def before_request():      
    if not hasattr(g,'glo1'):          
        setattr(g,'glo1','想要设置的')

teardown_appcontext

不管是否有异常,注册的函数都会在每次请求之后执行。

@app.teardown_appcontext    
def teardown(exc=None):      
    if exc is None:        
        db.session.commit()      
    else:        
        db.session.rollback()      
        db.session.remove()

template_filter

在使用Jinja2模板的时候自定义过滤器。

@app.template_filter("upper")  
def upper_filter(s):    
    return s.upper()

context_processor

上下文处理器。使用这个钩子函数,必须返回一个字典。这个字典中的值在所有模版中都可以使用。这个钩子函数的函数是,如果一些在很多模版中都要用到的变量,那么就可以使用这个钩子函数来返回,而不用在每个视图函数中的render_template中去写,这样可以让代码更加简洁和好维护。

@app.context_processor  
def context_processor():
 if hasattr(g,'user'):
 return {"current_user":g.user}
    else:
 return {}

errorhandler

errorhandler接收状态码,可以自定义返回这种状态码的响应的处理方法。在发生一些异常的时候,比如404错误,比如500错误,那么如果想要优雅的处理这些错误,就可以使用 errorhandler 来出来。

在errorhandler装饰的钩子函数下,记得要返回相应的状态码。
在errorhandler装饰的钩子函数中,必须要写一个参数,来接收错误的信息,如果没有参数,就会直接报错。
使用 flask.abort 可以手动的抛出相应的错误,比如开发者在发现参数不正确的时候可以自己手动的抛出一个400错误。

@app.errorhandler(404)  
def page_not_found(error):    
    return 'This page does not exist',404

信号机制

flask中的信号使用的是一个第三方插件,叫做blinker。通过piplist看一下,如果没有安装,通过以下命令即可安装blinker
pip install blinker

自定义信号步骤

创建信号:定义信号需要使用到blinker这个包的Namespace类来创建一个命名空间。比如定义一个在访问了某个视图函数的时候的信号。示例代码如下:

# Namespace的作用:为了防止多人开发的时候,信号名字
冲突的问题
from blinker import Namespace
mysignal = Namespace()
signal1 = mysignal.signal('信号名称')

听信号:监听信号使用signal1对象的connect方法,在这个方法中需要传递一个函数,用来监听到这个信号后做该做的事情。示例代码如下:

def func1(sender,uname):
    print(sender)
    print(uname)
signal1.connect(func1)

发送信号:发送信号使用signal1对象的send方法,这个方法可以传递一些其他参数过去。示例代码如下:

signal1.send(uname='momo')

内置信号

template_rendered:模版渲染完成后的信号。
before_render_template:模版渲染之前的信号。
request_started:请求开始之前,在到达视图函数之前发送信号。
request_finished:请求结束时,在响应发送给客户端之前发送信号。
request_tearing_down:请求对象被销毁时发送的信号,即使在请求过程中发生异常也送信号。
got_request_exception:在请求过程中抛出异常时发送信号,异常本身会通过exception传递到订阅(监听)的函数中。一般可以监听这个信号,来记录网站异常信息。
appcontext_tearing_down:应用上下文被销毁时发送的信号。
appcontext_pushed:应用上下文被推入到栈上时发送的信号。
appcontext_popped:应用上下文被推出栈时发送的信号。
message_flashed:调用了Flask的 flash 方法时发送的信号。

WTForms介绍和基本使用

这个插件库主要有两个作用。第一个是做表单验证,将用户提交上来的数据进行验证是否符合系统要求。第二个是做模版渲染。 (了解)
Flask-WTF是简化了WTForms操作的一个第三方库。WTForms表单的两个主要功能是验证用户提交数据的合法性以及渲染模板。而Flask-WTF还包括一些其他的功能:CSRF保护,文件上传等。
安装Flask-WTF默认也会安装WTForms,因此使用以下命令来安装
Flask-WTF和WTForms:

pip install flask-wtf

WTForms表单验证的基本使用

自定义一个表单类,继承自wtforms.Form类。
定义好需要验证的字段,字段的名字必须和模版中那些需要验证的input标签的name属性值保持一致。
在需要验证的字段上,需要指定好具体的数据类型。
在相关的字段上,指定验证器。
以后在视图函数中,只需要使用这个表单类的对象,并且把需要验证的数据,也就是request.form传给这个表单类,再调用表单类对象.validate()方法进行,如果返回True,那么代表用户输入的数据都是符合格式要求的,Flase则代表用户输入的数据是有问题的。如果验证失败了,那么可以通过表单类对象.errors来获取具体的错误信息。

from flask import Flask,render_template,request
from wtforms import Form,StringField
from wtforms.validators import Length,EqualTo


app = Flask(__name__)

@app.route('/')
def index():
    return 'Hello! '
    
class RegisterForm(Form):
    uname = StringField(validators=[Length(min=2,max=10,message='用户名长度2-10之
间')])
	pwd = StringField(validators=[Length(min=2,max=10)])
    pwd2 = StringField(validators=[Length(min=2,max=10),EqualTo('pwd',message=
'2次密码不一致')])

@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():  # 验证成功:True,失败:False
            return '验证成功!'
        else:
            return f'验证失败!{form.errors}'
            
if __name__ == '__main__':
    app.run(debug=True)

WTForms常用验证器

页面把数据提交上来,需要经过表单验证,进而需要借助验证器来
进行验证,以下是常用的内置验证器:

# Length:字符串长度限制,有min和max两个值进行限制
username = StringField(validators=[Length(min=3,max=10,message="用户名长度必须在3到10位之间")])

# EqualTo:验证数据是否和另外一个字段相等,常用的就是密码和确认密码两个字段是否相等。
password_repeat = StringField(validators[Length(min=6,max=10),EqualTo("password")])
#  邮箱验证器
email = StringField(validators=[Email()])

# InputRequired:验证该项数据为必填项,即要求该项非空。
username = StringField(validators=[input_required()])

#  NumberRange:数值的区间,有min和max两个值限制,如果处在这两个数字之间则满足。
age = IntegerField(validators= [NumberRange(12,18)])

# Regexp:定义正则表达式进行验证,如验证手机号码。
phone = StringField(validators=[Regexp(r'1[34578]\d{9}')])

# URL:必须是URL的形式
home_page = StringField(validators=[URL()])

# UUID:验证数据是UUID类型
uuid = StringField(validators=[UUID()])

# 数据项的类型
from wtforms import Form,StringField,IntegerField

class RegisterForm2(Form):
    uname = StringField(validators=[InputRequired()])
    age = IntegerField(validators=[NumberRange(18,40)])

WTForms自定义验证器

只有当WTForms内置的验证器不够使的时候,才需要使用自定义验证器。如果想要对表单中的某个字段进行更细化的验证,那么可以针对这个字段进行单独的验证。

1 定义一个方法,方法的名字规则是: validate_字段名(self,field) 。
2 在方法中,使用 field.data 可以获取到这个字段的具体的值。
3 验证时,如果数据满足条件,那么可以什么都不做。如果验证失败
那么应该抛出一个 wtforms.validators.ValidationError 的异常,并且把验证失败的信息传到这个异常类中。

验证码实现:

from  flask import session
from wtforms import Form,StringField,IntegerField
from wtforms.validators import Length,EqualTo,Email,InputRequired,NumberRan
ge,Regexp,URL,UUID,ValidationError

class RegisterForm2(Form):
    email = StringField(validators=[Email()])
    uname = StringField(validators=[InputRequired()])
    age = IntegerField(validators=[NumberRange(18,40)])
    phone = StringField(validators=[Regexp(r'1[34578]\d{9}')])
    phomepage = StringField(validators=[URL()])
    uuid = StringField(validators=[UUID()])
    code = StringField(validators=[Length(4,4)])
    #取到的值 和服务器上 session上存储的值对比
    def validate_code(self,field):
      print(field.data,session.get('code'))
      if field.data !=session.get('code'):
            raise ValidationError('验证码不一致!')

Flask安全上传文件_访问文件

安全上传

  1. 在模版html中,表单需要指定 enctype=‘multipart/form-data’ 才能上传文件。
  2. 在后台如果想要获取上传的文件,那么应该使用request.files.get('文件名')来获取。
  3. 保存文件之前,先要使用 werkzeug.utils.secure_filename 来对上传上来的文
    件名进行一个过滤。能保证不会有安全问题。
  4. 获取到上传上来的文件后,使用 文件对象.save(路径=完整路径=路径名+文件名) 方法来保存文件。

访问文件

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

from flask import Flask
import os from flask import send_from_directory

app = Flask(__name__)

UPLOAD_PATH = os.path.join(os.path.dirname(__file__),'images')

@app.route('/images/<filename>/')
def get_image(filename):
 return  send_from_directory(UPLOAD_PATH,filename)
 
if __name__ == '__main__':
    app.run(debug=True)

利用flask-wtf验证上传的文件

1 定义验证表单类的时候,对文件类型的字段,需要采用 FileField 这个类型,即wtforms.FileField
2 验证器需要从 flask_wtf.file 中导入。flask_wtf.file.FileRequiredflask_wtf.file.FileAllowed
3 flask_wtf.file.FileRequired 是用来验证文件上传不能为空。
4 flask_wtf.file.FileAllowed 用来验证上传的文件的后缀名, 如常见图片后缀 .jpg 和.png以及.gif等。
5 在视图函数中,需要使用from werkzeug.datastructures import CombinedMultiDict来把
request.form 与 request.files 来进行合并。
6 最后使用 表单验证对象.validate()进行验证。

# formscheck.py文件
from wtforms import Form,FileField,StringField
from wtforms.validators import InputRequired
# flask_wtf
from flask_wtf.file import FileRequired,FileAllowed

class UploadForm(Form):
    pichead = FileField(validators=[FileRequired(),FileAllowed(['jpg','png','gif'])])
    desc = StringField(validators=[InputRequired()])
# app.py
from flask import
Flask,request,render_template
import os
from werkzeug.utils import secure_filename
from formscheck import UploadForm
from werkzeug.datastructures import
CombinedMultiDict
app = Flask(__name__)
UPLOAD_PATH =
os.path.join(os.path.dirname(__file__),'imag
es')
#利用flask-wtf验证上传的文件
@app.route('/upload/',methods=
['GET','POST'])
def upload():
    if request.method == 'GET':
        return
render_template('upload.html')
    else:
        form =
UploadForm(CombinedMultiDict([request.form,r
equest.files]))
        if form.validate():
            # desc =
request.form.get("desc")
            # pichead =
request.files.get("pichead")
            desc = form.desc.data
            pichead = form.pichead.data
            filename =
secure_filename(pichead.filename)
          
pichead.save(os.path.join(UPLOAD_PATH,filen
ame))
            print(desc)
            return '文件上传成功'
        else:
            print(form.errors)
            return "文件上传失败"
if __name__ == '__main__':
    app.run(debug=True)
  • 7
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值