Added support for long running sessions. This closes #16.
This commit is contained in:
parent
2ba88eefb5
commit
36717b0273
4 changed files with 62 additions and 5 deletions
2
Makefile
2
Makefile
|
|
@ -1,4 +1,4 @@
|
||||||
.PHONY: clean-pyc test
|
.PHONY: clean-pyc test upload-docs
|
||||||
|
|
||||||
all: clean-pyc test
|
all: clean-pyc test
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,13 @@ To access the current session you can use the :class:`session` object:
|
||||||
# so mark it as modified yourself
|
# so mark it as modified yourself
|
||||||
session.modified = True
|
session.modified = True
|
||||||
|
|
||||||
|
.. attribute:: permanent
|
||||||
|
|
||||||
|
If set to `True` the session life for
|
||||||
|
:attr:`~flask.Flask.permanent_session_lifetime` seconds. The
|
||||||
|
default is 31 days. If set to `False` (which is the default) the
|
||||||
|
session will be deleted when the user closes the browser.
|
||||||
|
|
||||||
|
|
||||||
Application Globals
|
Application Globals
|
||||||
-------------------
|
-------------------
|
||||||
|
|
|
||||||
31
flask.py
31
flask.py
|
|
@ -13,6 +13,7 @@ from __future__ import with_statement
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import types
|
import types
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from jinja2 import Environment, PackageLoader, FileSystemLoader
|
from jinja2 import Environment, PackageLoader, FileSystemLoader
|
||||||
from werkzeug import Request as RequestBase, Response as ResponseBase, \
|
from werkzeug import Request as RequestBase, Response as ResponseBase, \
|
||||||
|
|
@ -86,7 +87,20 @@ class _RequestGlobals(object):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class _NullSession(SecureCookie):
|
class Session(SecureCookie):
|
||||||
|
"""Expands the session for support for switching between permanent
|
||||||
|
and non-permanent sessions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _get_permanent(self):
|
||||||
|
return self.get('_permanent', False)
|
||||||
|
def _set_permanent(self, value):
|
||||||
|
self['_permanent'] = bool(value)
|
||||||
|
permanent = property(_get_permanent, _set_permanent)
|
||||||
|
del _get_permanent, _set_permanent
|
||||||
|
|
||||||
|
|
||||||
|
class _NullSession(Session):
|
||||||
"""Class used to generate nicer error messages if sessions are not
|
"""Class used to generate nicer error messages if sessions are not
|
||||||
available. Will still allow read-only access to the empty session
|
available. Will still allow read-only access to the empty session
|
||||||
but fail on setting.
|
but fail on setting.
|
||||||
|
|
@ -317,6 +331,11 @@ class Flask(object):
|
||||||
#: The secure cookie uses this for the name of the session cookie
|
#: The secure cookie uses this for the name of the session cookie
|
||||||
session_cookie_name = 'session'
|
session_cookie_name = 'session'
|
||||||
|
|
||||||
|
#: A :class:`~datetime.timedelta` which is used to set the expiration
|
||||||
|
#: date of a permanent session. The default is 31 days which makes a
|
||||||
|
#: permanent session survive for roughly one month.
|
||||||
|
permanent_session_lifetime = timedelta(days=31)
|
||||||
|
|
||||||
#: options that are passed directly to the Jinja2 environment
|
#: options that are passed directly to the Jinja2 environment
|
||||||
jinja_options = ImmutableDict(
|
jinja_options = ImmutableDict(
|
||||||
autoescape=True,
|
autoescape=True,
|
||||||
|
|
@ -493,8 +512,8 @@ class Flask(object):
|
||||||
"""
|
"""
|
||||||
key = self.secret_key
|
key = self.secret_key
|
||||||
if key is not None:
|
if key is not None:
|
||||||
return SecureCookie.load_cookie(request, self.session_cookie_name,
|
return Session.load_cookie(request, self.session_cookie_name,
|
||||||
secret_key=key)
|
secret_key=key)
|
||||||
|
|
||||||
def save_session(self, session, response):
|
def save_session(self, session, response):
|
||||||
"""Saves the session if it needs updates. For the default
|
"""Saves the session if it needs updates. For the default
|
||||||
|
|
@ -505,7 +524,11 @@ class Flask(object):
|
||||||
object)
|
object)
|
||||||
:param response: an instance of :attr:`response_class`
|
:param response: an instance of :attr:`response_class`
|
||||||
"""
|
"""
|
||||||
session.save_cookie(response, self.session_cookie_name)
|
expires = None
|
||||||
|
if session.permanent:
|
||||||
|
expires = datetime.utcnow() + self.permanent_session_lifetime
|
||||||
|
session.save_cookie(response, self.session_cookie_name,
|
||||||
|
expires=expires, httponly=True)
|
||||||
|
|
||||||
def add_url_rule(self, rule, endpoint, view_func=None, **options):
|
def add_url_rule(self, rule, endpoint, view_func=None, **options):
|
||||||
"""Connects a URL rule. Works exactly like the :meth:`route`
|
"""Connects a URL rule. Works exactly like the :meth:`route`
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,14 @@
|
||||||
"""
|
"""
|
||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
import flask
|
import flask
|
||||||
import unittest
|
import unittest
|
||||||
import tempfile
|
import tempfile
|
||||||
import warnings
|
import warnings
|
||||||
|
from datetime import datetime
|
||||||
|
from werkzeug import parse_date
|
||||||
|
|
||||||
|
|
||||||
example_path = os.path.join(os.path.dirname(__file__), '..', 'examples')
|
example_path = os.path.join(os.path.dirname(__file__), '..', 'examples')
|
||||||
|
|
@ -118,6 +121,30 @@ class BasicFunctionalityTestCase(unittest.TestCase):
|
||||||
expect_exception(flask.session.__setitem__, 'foo', 42)
|
expect_exception(flask.session.__setitem__, 'foo', 42)
|
||||||
expect_exception(flask.session.pop, 'foo')
|
expect_exception(flask.session.pop, 'foo')
|
||||||
|
|
||||||
|
def test_session_expiration(self):
|
||||||
|
permanent = True
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
app.secret_key = 'testkey'
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
flask.session['test'] = 42
|
||||||
|
flask.session.permanent = permanent
|
||||||
|
return ''
|
||||||
|
rv = app.test_client().get('/')
|
||||||
|
assert 'set-cookie' in rv.headers
|
||||||
|
match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie'])
|
||||||
|
expires = parse_date(match.group())
|
||||||
|
expected = datetime.utcnow() + app.permanent_session_lifetime
|
||||||
|
assert expires.year == expected.year
|
||||||
|
assert expires.month == expected.month
|
||||||
|
assert expires.day == expected.day
|
||||||
|
|
||||||
|
permanent = False
|
||||||
|
rv = app.test_client().get('/')
|
||||||
|
assert 'set-cookie' in rv.headers
|
||||||
|
match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie'])
|
||||||
|
assert match is None
|
||||||
|
|
||||||
def test_flashes(self):
|
def test_flashes(self):
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
app.secret_key = 'testkey'
|
app.secret_key = 'testkey'
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue