Flask会话属性在初始加载后丢失键入

时间:2015-07-28 17:38:05

标签: python session flask

Flask的session对象似乎正在将OrderedDict更改为dict。这很重要,因为我希望在OrderedDict中存储有关当前用户可访问页面的信息,以帮助自动创建导航栏。

我可能会昏昏欲睡,但我不认为我在这里写了任何会覆盖现有数据的内容,所以AFAICT它只是转换为dict而没有明显的理由。

以下是在OrderedDict对象中设置session的代码。

def safe_route(page_request):
    """Returns the correct page for the user's request.

    If a user requests a non-existent page, 404.
    If user wants a page they shouldn't see, but somehow
    they know about it, redirects to an access-denied page.

    If everything is in order, returns the page the user requests.
    """
    if page_request not in ordered_urls:
        return abort(404)      # early return for 404s

    # get current user and build their navi-dict.
    # this dict will be used to build the navibar in the webpage.
    if not 'registered' in session:
        CurrentUser = get_user(request)
        session['name'] = CurrentUser.name
        session['navi'] = build_user_navi(CurrentUser)
        session['registered'] = True
    if page_request in session['navi']:
        tpl = "/{}.html".format(page_request)
    else:
        # if here, the user requested a page that DOES exist
        # but they do NOT have access to. therefore, they are being
        # goons and require a trip to boxxytown
        tpl = "accessdenied.html"
    return render_template(tpl, on_page=page_request)

检查以确保会话具有'registered'标志;如果没有,它会获取用户信息,然后调用返回build_user_navi的{​​{1}}。这是第一次工作;初始页面加载显示了我最初打算在navibar中的链接。在随后点击时,订单将丢失,OrderedDict上的类型显然也是如此。

OrderedDict来电之前,我在代码中添加了这一行,看看它是否只是在改变我的类型:

render_template

果然,它会在print type(session['navi']) <class 'collections.OrderedDict'>之间切换。

是否存在将<type 'dict'>正确存储为会话数据的方法,或者是否有一些完全正确的理由为什么这只会在第一次起作用时才能阻止我使用这种方法?

2 个答案:

答案 0 :(得分:2)

会话中的所有数据都必须是JSON可序列化的。 OrderedDict是可序列化的,因为它是dict的子类,但JSON中没有有序的dict,因此在序列化过程中会丢失顺序。

您可以覆盖how Flask serializes the session以支持有序的dicts。这不是最方便的过程,因为你必须复制该类的代码。

from base64 import b64encode, b64decode
from collections import OrderedDict
from datetime import datetime
import uuid
from flask import json
from flask._compat import text_type, iteritems
from flask.debughelpers import UnexpectedUnicodeError
from markupsafe import Markup
from werkzeug.http import http_date, parse_date


class TaggedJSONSerializer(object):
    def dumps(self, value):
        def _tag(value):
            if isinstance(value, tuple):
                return {' t': [_tag(x) for x in value]}
            elif isinstance(value, uuid.UUID):
                return {' u': value.hex}
            elif isinstance(value, bytes):
                return {' b': b64encode(value).decode('ascii')}
            elif callable(getattr(value, '__html__', None)):
                return {' m': text_type(value.__html__())}
            elif isinstance(value, list):
                return [_tag(x) for x in value]
            elif isinstance(value, datetime):
                return {' d': http_date(value)}
            elif isinstance(value, OrderedDict):
                return {'OrderedDict': [[k, _tag(v)] for k, v in iteritems(value)]}
            elif isinstance(value, dict):
                return dict((k, _tag(v)) for k, v in iteritems(value))
            elif isinstance(value, str):
                try:
                    return text_type(value)
                except UnicodeError:
                    raise UnexpectedUnicodeError(u'A byte string with '
                        u'non-ASCII data was passed to the session system '
                        u'which can only store unicode strings.  Consider '
                        u'base64 encoding your string (String was %r)' % value)
            return value
        return json.dumps(_tag(value), separators=(',', ':'))

    def loads(self, value):
        def object_hook(obj):
            if len(obj) != 1:
                return obj
            the_key, the_value = next(iteritems(obj))
            if the_key == ' t':
                return tuple(the_value)
            elif the_key == ' u':
                return uuid.UUID(the_value)
            elif the_key == ' b':
                return b64decode(the_value)
            elif the_key == ' m':
                return Markup(the_value)
            elif the_key == ' d':
                return parse_date(the_value)
            elif the_key == 'OrderedDict':
                return OrderedDict(the_value)
            return obj
        return json.loads(value, object_hook=object_hook)

以下是新标签的演示。 OrderedDict被正确反序列化。

s = TaggedJSONSerializer()
data = OrderedDict((c, i) for i, c in enumerate('abcd'))
print(data)  # OrderedDict([('a', 0), ('b', 1), ('c', 2), ('d', 3)])
data = s.dumps(data)
print(data)  # {"OrderedDict":[["a",0],["b",1],["c",2],["d",3]]}
data = s.loads(data)
print(data)  # OrderedDict([('a', 0), ('b', 1), ('c', 2), ('d', 3)])

然后告诉您的应用使用此序列化程序。

app.session_interface.serializer = TaggedJSONSerializer()

答案 1 :(得分:1)

我怀疑会话对象是在某处进行了深层复制,OrderedDict被复制为普通dict。这可以通过例如咨询来源。

我想出的最简单的解决方法是存储键值对的简单列表,而不是OrderedDict,这很容易迭代,并且很容易转换为{的任何一个王。 {1}}您是否需要重复快速查找。