flask-admin章节四:flask session的使用

1. 关于sessionhtml

flask session可能不少人根本都没有使用过,却是cookie你们可能使用得比较多。flask cookie使用起来比较简单,就两个函数,读取和设置。python

具体使用方式以下:sql

读取cookie数据库

from flask import request

@app.route('/')
def index():
    username = request.cookies.get('username')
    # 使用 cookies.get(key) 来代替 cookies[key] ,
    # 以免当 cookie 不存在时引起 KeyError 。

设置cookieflask

@app.route('/')
def index():
    resp = make_response(render_template(...))
    resp.set_cookie('username', 'the username')
    return resp

cookie的设置是必须在response返回时,而且是做为response的一部分给client段返回的。浏览器

其实理解了cookie的原理就大概知道为何这么作了,cookie是为了保存用户的一些信息,而这些信息实际上是保存在浏览器的缓存中的,你们平时清理浏览器的访问记录时其实就是在清除对应的cookie。缓存

每次请求的时候,浏览器会根据所访问的网页把对应保存的用户相关的cookie信息放到request中给服务端发送过去。这样就节省了屡次身份认证的过程。安全

 

关于flask cookie的详细使用方法能够参阅:http://dormousehole.readthedocs.io/en/latest/quickstart.html?highlight=cookie。这块就不细讲了。cookie

这节主要讲的是session,具体cookie和session的区别,在网上能够查找到不少相关的资料。其实不少事情不须要刻意理解他们之间的具体差异,实际中遇到就能很好地理解他们之间的差异。session

本文不会具体讲cookie和session概念上的差异,不过经过介绍完我使用的场景,想必你们就很清楚他们之间的区别了。

 

2 遇到的问题

自动化发布平台在用户建立工单表单的时候,须要用户指定其待发布的机器列表。这样OP在具体发布的时候根据发布所处的阶段来选择具体发布哪几台机器。所以,须要根据用户对应的业务从配置服务中拉取对应的机器列表让其选择。

而且这个机器列表是可能变化的。因此,这块单纯使用wtforms.fields.SelectField表单字段是不起做用的,所以须要使用wtforms.ext.sqlalchemy.fields的QuerySelectField字段。至于QuerySelectField的使用能够参考个人

上篇博客。

OP在发布的不一样阶段,须要选定机器列表作具体的发布。具体机器列表的获取,也可使用和建立工单的时候一样的方式,从配置服务中获取当前业务全部的机器列表。而后,让发布者从机器列表中选择机器列表中选择几台具体的机器作发布。

可是,这块会有一个问题。建立工单的时候,须要建立者从一个机器列表中选择几台期待的机器,而发布的各个阶段也都须要从一样的机器列表中选择几台机器作发布。这样就可能存在一个问题,发布者发布的时候应该是从建立工单的人选择的

机器列表中选择机器作发布,而不是从全局的机器列表中选择机器作发布。

所以,须要有种方式在建立表单的时候可以把工单的ID传递过去,这样能够从数据库中把工单的详细信息查询出来,并获取到用户选定的机器列表。

可是,这块用什么方式传递过去呢?

可能这样说不是很形象,具体用代码展现吧,发布过程当中有预发布、灰度发布、以及发布。可是,这块以预发布为例,灰度发布和发布其实都同样。

    @expose('/pre_release', methods = ['GET', 'POST'])
    def pre_release(self):
pre_release_handle_form = PreReleaseHandleForm(request.form) if request.method == 'POST': if helpers.validate_form_on_submit(pre_release_handle_form): # user logic
return self.render('release.html', form = pre_release_handle_form

上面是预发布相关的逻辑,核心逻辑都去掉了,只留个大体的框架,不过这并不影响本文介绍的内容。

而PreReleaseHandleForm表结构以下:

class PreReleaseHandleForm(form.Form):
    def query_factory():
        return [r.task_name for r in db.session.query(Task).all()]

    def get_pk(obj):
        return obj

    def iplist_query_factory():
        # 从配置服务中获取
iplist = get_from_config_server() return iplist def iplist_get_pk(obj): return obj release_task = QuerySelectField(label=u'发布任务', validators=[validators.required()], query_factory=query_factory, get_pk=get_pk) rollback_task = QuerySelectField(label=u'回滚任务', validators=[validators.required()], query_factory=query_factory, get_pk=get_pk) target_machine = QuerySelectMultipleField(label=u'目标机器', validators = [validators.required()], query_factory=iplist_query_factory, get_pk=iplist_get_pk) remark = fields.TextField(label=u'备注', validators=[validators.required()])

此处能够看到QuerySelectMultipleField函数,其可以支持表单的多选以及可以动态获取选项列表。所以,在此处替代MultiSelectField。

上述的代码不作详细介绍,算是比较简单的使用,咱们关注的核心是iplist_query_factory函数,它是本文的核心。预发布的机器列表就是由此函数产生的。

可是,因为咱们在预发布的时候表单并无上下文,咱们不知道其对应哪一个具体的工单,并且表单确定是无状态的。因此,就跟此处有冲突,咱们期待在表单页面显示的时候就可以

根据具体的上下文从建立的工单中获取待发布的机器列表,并展现出来。

 

3 解决的办法

所以,第一个想法是使用cookie,这样咱们在向客户端显示表单页面的时候顺便把工单ID放个response中并给客户端返回。这样iplist_query_factory函数会根据request上下文从cookie中

取出对应的ID,并从数据库中查询对应的工单详细信息,并回去对应的待发布机器列表并给客户端展现。

不过这块须要PreReleaseHandleFormpre_release函数都作稍微的修改,具体修改内容以下:

def iplist_query_factory():
        id = request.cookie.get('ID')
        job = db.session.query(Job).filter_by(id = id).first()
        iplist = job.pre_release_iplist.split(',')
        return iplist

  

    @expose('/pre_release', methods = ['GET', 'POST'])
    def pre_release(self):
        id = request.args.get('id', '')
        pre_release_handle_form = PreReleaseHandleForm(request.form)
        if request.method == 'POST':
            if helpers.validate_form_on_submit(pre_release_handle_form):
                # user logic

        resp = make_response(self.render('release.html', form = pre_release_handle_form))
        resp.set_cookie('ID', id)
        return resp

具体的话,你们能够关注上面加粗的地方。其实仍是蛮容易理解的,在向客户端返回response的时候须要在response的cookie属性中设置对应的ID字段的值,在iplist_query_factory中从cookie中查询对应字段的值。

这块并不会由于多线程而出现问题,由于在flask中request上下文是线程安全的。

 

可是测试的时候发现这块有个问题,就是我第一次展现表单页面的时候IP列表是空的,必须我从新刷新一次才能够。以后的状况就是我从新打开页面,显示的机器列表确实上一次的机器列表。

后来想了想,才发现一个问题。每次的表单显示的时候其实对应的response并无给客户端返回,所以拿到的是上次请求的cookie值,这样就存在着延后,跟真实的状况不一样步。

要是任由这种状况发展仍是蛮危险的。所以,只能使用另一种方式解决了,后来发现另一种解决方式就是session。

 

看了下flask admin session的具体实现,发现flask session实际上是request上下文,而flask的实现其实对于每一个request会分派给一个独立的线程,实际上是线程安全的。

from functools import partial
from werkzeug.local import LocalStack, LocalProxy


def _lookup_req_object(name):
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of request context')
    return getattr(top, name)


def _lookup_app_object(name):
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of application context')
    return getattr(top, name)


def _find_app():
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of application context')
    return top.app


# context locals
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
g = LocalProxy(partial(_lookup_app_object, 'g'))

上面是flask源代码,具体只须要看下上面加粗的代码就能够了。能够看出session实际上是request上下文安全的。

所以,能够直接使用。至于fllask session的使用也是蛮简单的。具体代码以下:

 def iplist_query_factory():
        id = session['ID']
        job = db.session.query(Job).filter_by(id = id).first()
        iplist = job.pre_release_iplist.split(',')
        return iplist

  

@expose('/pre_release', methods = ['GET', 'POST'])
    def pre_release(self):
        id = request.args.get('id', '')
        session['ID'] = id
        pre_release_handle_form = PreReleaseHandleForm(request.form)
        if request.method == 'POST':
            if helpers.validate_form_on_submit(pre_release_handle_form):
                # user logic

        returnn self.render('release.html', form = pre_release_handle_form)

上面加粗的代码就是session的使用,发现没有其实session使用起来比cookie更方便。

测试发现效果彻底符合预期,可是蛮不错的。

 

4 小结

以前关于这个问题想了很久,没有想到具体的解决方案,后来无心间想到了。因此,遇到问题不要放弃,坚持就是胜利!

相关文章
相关标签/搜索