From 8d4f79a799136edf8190c357e3e0497d7db3ad77 Mon Sep 17 00:00:00 2001 From: Jacob Kaplan-Moss Date: Sat, 7 Jun 2008 20:28:06 +0000 Subject: [PATCH] Fixed #2548: added get/set_expiry methods to session objects. Thanks, Amit Upadhyay and SmileyChris. git-svn-id: http://code.djangoproject.com/svn/django/trunk@7586 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 2 +- django/conf/global_settings.py | 2 +- django/contrib/sessions/backends/base.py | 57 ++++++++++++++ django/contrib/sessions/backends/cache.py | 8 +- django/contrib/sessions/backends/db.py | 2 +- django/contrib/sessions/middleware.py | 8 +- django/contrib/sessions/tests.py | 94 +++++++++++++++++++++++ docs/sessions.txt | 60 ++++++++++++++- 8 files changed, 221 insertions(+), 12 deletions(-) diff --git a/AUTHORS b/AUTHORS index 8fa87d07bc..0c0fed8e4a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -360,7 +360,7 @@ answer newbie questions, and generally made Django that much better: Makoto Tsuyuki tt@gurgle.no David Tulig - Amit Upadhyay + Amit Upadhyay Geert Vanderkelen I.S. van Oostveen viestards.lists@gmail.com diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 8eea31b0db..cdf71c00dc 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -289,7 +289,7 @@ SESSION_COOKIE_DOMAIN = None # A string like ".lawren SESSION_COOKIE_SECURE = False # Whether the session cookie should be secure (https:// only). SESSION_COOKIE_PATH = '/' # The path of the session cookie. SESSION_SAVE_EVERY_REQUEST = False # Whether to save the session data on every request. -SESSION_EXPIRE_AT_BROWSER_CLOSE = False # Whether sessions expire when a user closes his browser. +SESSION_EXPIRE_AT_BROWSER_CLOSE = False # Whether a user's session cookie expires when they close their browser. SESSION_ENGINE = 'django.contrib.sessions.backends.db' # The module to store session data SESSION_FILE_PATH = None # Directory to store session files if using the file session module. If None, the backend will use a sensible default. diff --git a/django/contrib/sessions/backends/base.py b/django/contrib/sessions/backends/base.py index b8726fd2bd..1063760915 100644 --- a/django/contrib/sessions/backends/base.py +++ b/django/contrib/sessions/backends/base.py @@ -4,6 +4,7 @@ import os import random import sys import time +from datetime import datetime, timedelta from django.conf import settings from django.core.exceptions import SuspiciousOperation @@ -128,6 +129,62 @@ class SessionBase(object): _session = property(_get_session) + def get_expiry_age(self): + """Get the number of seconds until the session expires.""" + expiry = self.get('_session_expiry') + if not expiry: # Checks both None and 0 cases + return settings.SESSION_COOKIE_AGE + if not isinstance(expiry, datetime): + return expiry + delta = expiry - datetime.now() + return delta.days * 86400 + delta.seconds + + def get_expiry_date(self): + """Get session the expiry date (as a datetime object).""" + expiry = self.get('_session_expiry') + if isinstance(expiry, datetime): + return expiry + if not expiry: # Checks both None and 0 cases + expiry = settings.SESSION_COOKIE_AGE + return datetime.now() + timedelta(seconds=expiry) + + def set_expiry(self, value): + """ + Sets a custom expiration for the session. ``value`` can be an integer, a + Python ``datetime`` or ``timedelta`` object or ``None``. + + If ``value`` is an integer, the session will expire after that many + seconds of inactivity. If set to ``0`` then the session will expire on + browser close. + + If ``value`` is a ``datetime`` or ``timedelta`` object, the session + will expire at that specific future time. + + If ``value`` is ``None``, the session uses the global session expiry + policy. + """ + if value is None: + # Remove any custom expiration for this session. + try: + del self['_session_expiry'] + except KeyError: + pass + return + if isinstance(value, timedelta): + value = datetime.now() + value + self['_session_expiry'] = value + + def get_expire_at_browser_close(self): + """ + Returns ``True`` if the session is set to expire when the browser + closes, and ``False`` if there's an expiry date. Use + ``get_expiry_date()`` or ``get_expiry_age()`` to find the actual expiry + date/age, if there is one. + """ + if self.get('_session_expiry') is None: + return settings.SESSION_EXPIRE_AT_BROWSER_CLOSE + return self.get('_session_expiry') == 0 + # Methods that child classes must implement. def exists(self, session_key): diff --git a/django/contrib/sessions/backends/cache.py b/django/contrib/sessions/backends/cache.py index c3e641e691..7626163a13 100644 --- a/django/contrib/sessions/backends/cache.py +++ b/django/contrib/sessions/backends/cache.py @@ -4,23 +4,23 @@ from django.core.cache import cache class SessionStore(SessionBase): """ - A cache-based session store. + A cache-based session store. """ def __init__(self, session_key=None): self._cache = cache super(SessionStore, self).__init__(session_key) - + def load(self): session_data = self._cache.get(self.session_key) return session_data or {} def save(self): - self._cache.set(self.session_key, self._session, settings.SESSION_COOKIE_AGE) + self._cache.set(self.session_key, self._session, self.get_expiry_age()) def exists(self, session_key): if self._cache.get(session_key): return True return False - + def delete(self, session_key): self._cache.delete(session_key) \ No newline at end of file diff --git a/django/contrib/sessions/backends/db.py b/django/contrib/sessions/backends/db.py index 0f79d9ee1a..b1c1097865 100644 --- a/django/contrib/sessions/backends/db.py +++ b/django/contrib/sessions/backends/db.py @@ -41,7 +41,7 @@ class SessionStore(SessionBase): Session.objects.create( session_key = self.session_key, session_data = self.encode(self._session), - expire_date = datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE) + expire_date = self.get_expiry_date() ) def delete(self, session_key): diff --git a/django/contrib/sessions/middleware.py b/django/contrib/sessions/middleware.py index 2af2312e76..a7b376dde0 100644 --- a/django/contrib/sessions/middleware.py +++ b/django/contrib/sessions/middleware.py @@ -26,14 +26,14 @@ class SessionMiddleware(object): if accessed: patch_vary_headers(response, ('Cookie',)) if modified or settings.SESSION_SAVE_EVERY_REQUEST: - if settings.SESSION_EXPIRE_AT_BROWSER_CLOSE: + if request.session.get_expire_at_browser_close(): max_age = None expires = None else: - max_age = settings.SESSION_COOKIE_AGE - expires_time = time.time() + settings.SESSION_COOKIE_AGE + max_age = request.session.get_expiry_age() + expires_time = time.time() + max_age expires = cookie_date(expires_time) - # Save the seesion data and refresh the client cookie. + # Save the session data and refresh the client cookie. request.session.save() response.set_cookie(settings.SESSION_COOKIE_NAME, request.session.session_key, max_age=max_age, diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py index b2c664ce7b..0f162b211f 100644 --- a/django/contrib/sessions/tests.py +++ b/django/contrib/sessions/tests.py @@ -88,6 +88,100 @@ False >>> s.pop('some key', 'does not exist') 'does not exist' + +######################### +# Custom session expiry # +######################### + +>>> from django.conf import settings +>>> from datetime import datetime, timedelta + +>>> td10 = timedelta(seconds=10) + +# A normal session has a max age equal to settings +>>> s.get_expiry_age() == settings.SESSION_COOKIE_AGE +True + +# So does a custom session with an idle expiration time of 0 (but it'll expire +# at browser close) +>>> s.set_expiry(0) +>>> s.get_expiry_age() == settings.SESSION_COOKIE_AGE +True + +# Custom session idle expiration time +>>> s.set_expiry(10) +>>> delta = s.get_expiry_date() - datetime.now() +>>> delta.seconds in (9, 10) +True +>>> age = s.get_expiry_age() +>>> age in (9, 10) +True + +# Custom session fixed expiry date (timedelta) +>>> s.set_expiry(td10) +>>> delta = s.get_expiry_date() - datetime.now() +>>> delta.seconds in (9, 10) +True +>>> age = s.get_expiry_age() +>>> age in (9, 10) +True + +# Custom session fixed expiry date (fixed datetime) +>>> s.set_expiry(datetime.now() + td10) +>>> delta = s.get_expiry_date() - datetime.now() +>>> delta.seconds in (9, 10) +True +>>> age = s.get_expiry_age() +>>> age in (9, 10) +True + +# Set back to default session age +>>> s.set_expiry(None) +>>> s.get_expiry_age() == settings.SESSION_COOKIE_AGE +True + +# Allow to set back to default session age even if no alternate has been set +>>> s.set_expiry(None) + + +# We're changing the setting then reverting back to the original setting at the +# end of these tests. +>>> original_expire_at_browser_close = settings.SESSION_EXPIRE_AT_BROWSER_CLOSE +>>> settings.SESSION_EXPIRE_AT_BROWSER_CLOSE = False + +# Custom session age +>>> s.set_expiry(10) +>>> s.get_expire_at_browser_close() +False + +# Custom expire-at-browser-close +>>> s.set_expiry(0) +>>> s.get_expire_at_browser_close() +True + +# Default session age +>>> s.set_expiry(None) +>>> s.get_expire_at_browser_close() +False + +>>> settings.SESSION_EXPIRE_AT_BROWSER_CLOSE = True + +# Custom session age +>>> s.set_expiry(10) +>>> s.get_expire_at_browser_close() +False + +# Custom expire-at-browser-close +>>> s.set_expiry(0) +>>> s.get_expire_at_browser_close() +True + +# Default session age +>>> s.set_expiry(None) +>>> s.get_expire_at_browser_close() +True + +>>> settings.SESSION_EXPIRE_AT_BROWSER_CLOSE = original_expire_at_browser_close """ if __name__ == '__main__': diff --git a/docs/sessions.txt b/docs/sessions.txt index d8bac5b8d4..86ed49f135 100644 --- a/docs/sessions.txt +++ b/docs/sessions.txt @@ -80,19 +80,24 @@ attribute, which is a dictionary-like object. You can read it and write to it. It implements the following standard dictionary methods: * ``__getitem__(key)`` + Example: ``fav_color = request.session['fav_color']`` * ``__setitem__(key, value)`` + Example: ``request.session['fav_color'] = 'blue'`` * ``__delitem__(key)`` + Example: ``del request.session['fav_color']``. This raises ``KeyError`` if the given ``key`` isn't already in the session. * ``__contains__(key)`` + Example: ``'fav_color' in request.session`` * ``get(key, default=None)`` + Example: ``fav_color = request.session.get('fav_color', 'red')`` * ``keys()`` @@ -101,23 +106,70 @@ It implements the following standard dictionary methods: * ``setdefault()`` (**New in Django development version**) -It also has these three methods: +It also has these methods: * ``set_test_cookie()`` + Sets a test cookie to determine whether the user's browser supports cookies. Due to the way cookies work, you won't be able to test this until the user's next page request. See "Setting test cookies" below for more information. * ``test_cookie_worked()`` + Returns either ``True`` or ``False``, depending on whether the user's browser accepted the test cookie. Due to the way cookies work, you'll have to call ``set_test_cookie()`` on a previous, separate page request. See "Setting test cookies" below for more information. * ``delete_test_cookie()`` + Deletes the test cookie. Use this to clean up after yourself. + * ``set_expiry(value)`` + + **New in Django development version** + + Sets the expiration time for the session. You can pass a number of + different values: + + * If ``value`` is an integer, the session will expire after that + many seconds of inactivity. For example, calling + ``request.session.set_expiry(300)`` would make the session expire + in 5 minutes. + + * If ``value`` is a ``datetime`` or ``timedelta`` object, the + session will expire at that specific time. + + * If ``value`` is ``0`` then the user's session cookie will expire + when their browser is closed. + + * If ``value`` is ``None``, the session reverts to using the global + session expiry policy. + + * ``get_expiry_age()`` + + **New in Django development version** + + Returns the number of seconds until this session expires. For sessions + with no custom expiration (or those set to expire at browser close), this + will equal ``settings.SESSION_COOKIE_AGE``. + + * ``get_expiry_date()`` + + **New in Django development version** + + Returns the date this session will expire. For sessions with no custom + expiration (or those set to expire at browser close), this will equal the + date ``settings.SESSION_COOKIE_AGE`` seconds from now. + + * ``get_expire_at_browser_close()`` + + **New in Django development version** + + Returns either ``True`` or ``False``, depending on whether the user's + session cookie will expire when their browser is closed. + You can edit ``request.session`` at any point in your view. You can edit it multiple times. @@ -278,6 +330,12 @@ browser-length cookies -- cookies that expire as soon as the user closes his or her browser. Use this if you want people to have to log in every time they open a browser. +**New in Django development version** + +This setting is a global default and can be overwritten at a per-session level +by explicitly calling ``request.session.set_expiry()`` as described above in +`using sessions in views`_. + Clearing the session table ==========================