From cd17a24083f3ef17cf4c40a41c9d03c250d817c6 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Sat, 27 Oct 2012 21:32:50 +0200 Subject: [PATCH] Added optional kwargs to get_expiry_age/date. This change allows for cleaner tests: we can test the exact output. Refs #18194: this change makes it possible to compute session expiry dates at times other than when the session is saved. Fixed #18458: the existence of the `modification` kwarg implies that you must pass it to get_expiry_age/date if you call these functions outside of a short request - response cycle (the intended use case). --- django/contrib/sessions/backends/base.py | 40 ++++++++++++---- django/contrib/sessions/backends/cached_db.py | 2 +- django/contrib/sessions/tests.py | 48 ++++++++++++------- docs/topics/http/sessions.txt | 11 +++++ 4 files changed, 74 insertions(+), 27 deletions(-) diff --git a/django/contrib/sessions/backends/base.py b/django/contrib/sessions/backends/base.py index 65bbe059eb..1f63c3b45a 100644 --- a/django/contrib/sessions/backends/base.py +++ b/django/contrib/sessions/backends/base.py @@ -170,28 +170,52 @@ class SessionBase(object): _session = property(_get_session) - def get_expiry_age(self, expiry=None): + def get_expiry_age(self, **kwargs): """Get the number of seconds until the session expires. - expiry is an optional parameter specifying the datetime of expiry. + Optionally, this function accepts `modification` and `expiry` keyword + arguments specifying the modification and expiry of the session. """ - if expiry is None: + try: + modification = kwargs['modification'] + except KeyError: + modification = timezone.now() + # Make the difference between "expiry=None passed in kwargs" and + # "expiry not passed in kwargs", in order to guarantee not to trigger + # self.load() when expiry is provided. + try: + expiry = kwargs['expiry'] + except KeyError: 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 - timezone.now() + delta = expiry - modification 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') + def get_expiry_date(self, **kwargs): + """Get session the expiry date (as a datetime object). + + Optionally, this function accepts `modification` and `expiry` keyword + arguments specifying the modification and expiry of the session. + """ + try: + modification = kwargs['modification'] + except KeyError: + modification = timezone.now() + # Same comment as in get_expiry_age + try: + expiry = kwargs['expiry'] + except KeyError: + 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 timezone.now() + timedelta(seconds=expiry) + return modification + timedelta(seconds=expiry) def set_expiry(self, value): """ diff --git a/django/contrib/sessions/backends/cached_db.py b/django/contrib/sessions/backends/cached_db.py index 8e4cf8b7e7..31c6fbfce3 100644 --- a/django/contrib/sessions/backends/cached_db.py +++ b/django/contrib/sessions/backends/cached_db.py @@ -40,7 +40,7 @@ class SessionStore(DBStore): ) data = self.decode(s.session_data) cache.set(self.cache_key, data, - self.get_expiry_age(s.expire_date)) + self.get_expiry_age(expiry=s.expire_date)) except (Session.DoesNotExist, SuspiciousOperation): self.create() data = {} diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py index 527e8eb331..44c3b485e9 100644 --- a/django/contrib/sessions/tests.py +++ b/django/contrib/sessions/tests.py @@ -197,31 +197,43 @@ class SessionTestsMixin(object): self.assertEqual(self.session.get_expiry_age(), settings.SESSION_COOKIE_AGE) def test_custom_expiry_seconds(self): - # Using seconds - self.session.set_expiry(10) - delta = self.session.get_expiry_date() - timezone.now() - self.assertIn(delta.seconds, (9, 10)) + modification = timezone.now() - age = self.session.get_expiry_age() - self.assertIn(age, (9, 10)) + self.session.set_expiry(10) + + date = self.session.get_expiry_date(modification=modification) + self.assertEqual(date, modification + timedelta(seconds=10)) + + age = self.session.get_expiry_age(modification=modification) + self.assertEqual(age, 10) def test_custom_expiry_timedelta(self): - # Using timedelta - self.session.set_expiry(timedelta(seconds=10)) - delta = self.session.get_expiry_date() - timezone.now() - self.assertIn(delta.seconds, (9, 10)) + modification = timezone.now() - age = self.session.get_expiry_age() - self.assertIn(age, (9, 10)) + # Mock timezone.now, because set_expiry calls it on this code path. + original_now = timezone.now + try: + timezone.now = lambda: modification + self.session.set_expiry(timedelta(seconds=10)) + finally: + timezone.now = original_now + + date = self.session.get_expiry_date(modification=modification) + self.assertEqual(date, modification + timedelta(seconds=10)) + + age = self.session.get_expiry_age(modification=modification) + self.assertEqual(age, 10) def test_custom_expiry_datetime(self): - # Using fixed datetime - self.session.set_expiry(timezone.now() + timedelta(seconds=10)) - delta = self.session.get_expiry_date() - timezone.now() - self.assertIn(delta.seconds, (9, 10)) + modification = timezone.now() - age = self.session.get_expiry_age() - self.assertIn(age, (9, 10)) + self.session.set_expiry(modification + timedelta(seconds=10)) + + date = self.session.get_expiry_date(modification=modification) + self.assertEqual(date, modification + timedelta(seconds=10)) + + age = self.session.get_expiry_age(modification=modification) + self.assertEqual(age, 10) def test_custom_expiry_reset(self): self.session.set_expiry(None) diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt index 0082b75db1..1e043405f4 100644 --- a/docs/topics/http/sessions.txt +++ b/docs/topics/http/sessions.txt @@ -250,12 +250,23 @@ You can edit it multiple times. with no custom expiration (or those set to expire at browser close), this will equal :setting:`SESSION_COOKIE_AGE`. + This function accepts two optional keyword arguments: + + - ``modification``: last modification of the session, as a + :class:`~datetime.datetime` object. Defaults to the current time. + - ``expiry``: expiry information for the session, as a + :class:`~datetime.datetime` object, an :class:`int` (in seconds), or + ``None``. Defaults to the value stored in the session by + :meth:`set_expiry`, if there is one, or ``None``. + .. method:: get_expiry_date 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 :setting:`SESSION_COOKIE_AGE` seconds from now. + This function accepts the same keyword argumets as :meth:`get_expiry_age`. + .. method:: get_expire_at_browser_close Returns either ``True`` or ``False``, depending on whether the user's