Fixed #19200 -- Session expiry with cached_db
Also did a little bit of cleanup.
This commit is contained in:
parent
83ba0a9d4b
commit
04b00b668d
|
@ -170,9 +170,13 @@ class SessionBase(object):
|
||||||
|
|
||||||
_session = property(_get_session)
|
_session = property(_get_session)
|
||||||
|
|
||||||
def get_expiry_age(self):
|
def get_expiry_age(self, expiry=None):
|
||||||
"""Get the number of seconds until the session expires."""
|
"""Get the number of seconds until the session expires.
|
||||||
expiry = self.get('_session_expiry')
|
|
||||||
|
expiry is an optional parameter specifying the datetime of expiry.
|
||||||
|
"""
|
||||||
|
if expiry is None:
|
||||||
|
expiry = self.get('_session_expiry')
|
||||||
if not expiry: # Checks both None and 0 cases
|
if not expiry: # Checks both None and 0 cases
|
||||||
return settings.SESSION_COOKIE_AGE
|
return settings.SESSION_COOKIE_AGE
|
||||||
if not isinstance(expiry, datetime):
|
if not isinstance(expiry, datetime):
|
||||||
|
|
|
@ -2,9 +2,10 @@
|
||||||
Cached, database-backed sessions.
|
Cached, database-backed sessions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib.sessions.backends.db import SessionStore as DBStore
|
from django.contrib.sessions.backends.db import SessionStore as DBStore
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
from django.core.exceptions import SuspiciousOperation
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
KEY_PREFIX = "django.contrib.sessions.cached_db"
|
KEY_PREFIX = "django.contrib.sessions.cached_db"
|
||||||
|
|
||||||
|
@ -28,9 +29,21 @@ class SessionStore(DBStore):
|
||||||
# Some backends (e.g. memcache) raise an exception on invalid
|
# Some backends (e.g. memcache) raise an exception on invalid
|
||||||
# cache keys. If this happens, reset the session. See #17810.
|
# cache keys. If this happens, reset the session. See #17810.
|
||||||
data = None
|
data = None
|
||||||
|
|
||||||
if data is None:
|
if data is None:
|
||||||
data = super(SessionStore, self).load()
|
# Duplicate DBStore.load, because we need to keep track
|
||||||
cache.set(self.cache_key, data, settings.SESSION_COOKIE_AGE)
|
# of the expiry date to set it properly in the cache.
|
||||||
|
try:
|
||||||
|
s = Session.objects.get(
|
||||||
|
session_key=self.session_key,
|
||||||
|
expire_date__gt=timezone.now()
|
||||||
|
)
|
||||||
|
data = self.decode(s.session_data)
|
||||||
|
cache.set(self.cache_key, data,
|
||||||
|
self.get_expiry_age(s.expire_date))
|
||||||
|
except (Session.DoesNotExist, SuspiciousOperation):
|
||||||
|
self.create()
|
||||||
|
data = {}
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def exists(self, session_key):
|
def exists(self, session_key):
|
||||||
|
@ -40,7 +53,7 @@ class SessionStore(DBStore):
|
||||||
|
|
||||||
def save(self, must_create=False):
|
def save(self, must_create=False):
|
||||||
super(SessionStore, self).save(must_create)
|
super(SessionStore, self).save(must_create)
|
||||||
cache.set(self.cache_key, self._session, settings.SESSION_COOKIE_AGE)
|
cache.set(self.cache_key, self._session, self.get_expiry_age())
|
||||||
|
|
||||||
def delete(self, session_key=None):
|
def delete(self, session_key=None):
|
||||||
super(SessionStore, self).delete(session_key)
|
super(SessionStore, self).delete(session_key)
|
||||||
|
@ -58,3 +71,7 @@ class SessionStore(DBStore):
|
||||||
self.clear()
|
self.clear()
|
||||||
self.delete(self.session_key)
|
self.delete(self.session_key)
|
||||||
self.create()
|
self.create()
|
||||||
|
|
||||||
|
|
||||||
|
# At bottom to avoid circular import
|
||||||
|
from django.contrib.sessions.models import Session
|
||||||
|
|
|
@ -14,7 +14,7 @@ class SessionStore(SessionBase):
|
||||||
def load(self):
|
def load(self):
|
||||||
try:
|
try:
|
||||||
s = Session.objects.get(
|
s = Session.objects.get(
|
||||||
session_key = self.session_key,
|
session_key=self.session_key,
|
||||||
expire_date__gt=timezone.now()
|
expire_date__gt=timezone.now()
|
||||||
)
|
)
|
||||||
return self.decode(s.session_data)
|
return self.decode(s.session_data)
|
||||||
|
|
|
@ -32,6 +32,7 @@ class SessionStore(SessionBase):
|
||||||
try:
|
try:
|
||||||
return signing.loads(self.session_key,
|
return signing.loads(self.session_key,
|
||||||
serializer=PickleSerializer,
|
serializer=PickleSerializer,
|
||||||
|
# This doesn't handle non-default expiry dates, see #19201
|
||||||
max_age=settings.SESSION_COOKIE_AGE,
|
max_age=settings.SESSION_COOKIE_AGE,
|
||||||
salt='django.contrib.sessions.backends.signed_cookies')
|
salt='django.contrib.sessions.backends.signed_cookies')
|
||||||
except (signing.BadSignature, ValueError):
|
except (signing.BadSignature, ValueError):
|
||||||
|
|
|
@ -83,7 +83,7 @@ class SessionTestsMixin(object):
|
||||||
self.session['some key'] = 1
|
self.session['some key'] = 1
|
||||||
self.session.modified = False
|
self.session.modified = False
|
||||||
self.session.accessed = False
|
self.session.accessed = False
|
||||||
self.assertTrue('some key' in self.session)
|
self.assertIn('some key', self.session)
|
||||||
self.assertTrue(self.session.accessed)
|
self.assertTrue(self.session.accessed)
|
||||||
self.assertFalse(self.session.modified)
|
self.assertFalse(self.session.modified)
|
||||||
|
|
||||||
|
@ -200,28 +200,28 @@ class SessionTestsMixin(object):
|
||||||
# Using seconds
|
# Using seconds
|
||||||
self.session.set_expiry(10)
|
self.session.set_expiry(10)
|
||||||
delta = self.session.get_expiry_date() - timezone.now()
|
delta = self.session.get_expiry_date() - timezone.now()
|
||||||
self.assertTrue(delta.seconds in (9, 10))
|
self.assertIn(delta.seconds, (9, 10))
|
||||||
|
|
||||||
age = self.session.get_expiry_age()
|
age = self.session.get_expiry_age()
|
||||||
self.assertTrue(age in (9, 10))
|
self.assertIn(age, (9, 10))
|
||||||
|
|
||||||
def test_custom_expiry_timedelta(self):
|
def test_custom_expiry_timedelta(self):
|
||||||
# Using timedelta
|
# Using timedelta
|
||||||
self.session.set_expiry(timedelta(seconds=10))
|
self.session.set_expiry(timedelta(seconds=10))
|
||||||
delta = self.session.get_expiry_date() - timezone.now()
|
delta = self.session.get_expiry_date() - timezone.now()
|
||||||
self.assertTrue(delta.seconds in (9, 10))
|
self.assertIn(delta.seconds, (9, 10))
|
||||||
|
|
||||||
age = self.session.get_expiry_age()
|
age = self.session.get_expiry_age()
|
||||||
self.assertTrue(age in (9, 10))
|
self.assertIn(age, (9, 10))
|
||||||
|
|
||||||
def test_custom_expiry_datetime(self):
|
def test_custom_expiry_datetime(self):
|
||||||
# Using fixed datetime
|
# Using fixed datetime
|
||||||
self.session.set_expiry(timezone.now() + timedelta(seconds=10))
|
self.session.set_expiry(timezone.now() + timedelta(seconds=10))
|
||||||
delta = self.session.get_expiry_date() - timezone.now()
|
delta = self.session.get_expiry_date() - timezone.now()
|
||||||
self.assertTrue(delta.seconds in (9, 10))
|
self.assertIn(delta.seconds, (9, 10))
|
||||||
|
|
||||||
age = self.session.get_expiry_age()
|
age = self.session.get_expiry_age()
|
||||||
self.assertTrue(age in (9, 10))
|
self.assertIn(age, (9, 10))
|
||||||
|
|
||||||
def test_custom_expiry_reset(self):
|
def test_custom_expiry_reset(self):
|
||||||
self.session.set_expiry(None)
|
self.session.set_expiry(None)
|
||||||
|
@ -258,6 +258,21 @@ class SessionTestsMixin(object):
|
||||||
encoded = self.session.encode(data)
|
encoded = self.session.encode(data)
|
||||||
self.assertEqual(self.session.decode(encoded), data)
|
self.assertEqual(self.session.decode(encoded), data)
|
||||||
|
|
||||||
|
def test_actual_expiry(self):
|
||||||
|
# Regression test for #19200
|
||||||
|
old_session_key = None
|
||||||
|
new_session_key = None
|
||||||
|
try:
|
||||||
|
self.session['foo'] = 'bar'
|
||||||
|
self.session.set_expiry(-timedelta(seconds=10))
|
||||||
|
self.session.create()
|
||||||
|
# With an expiry date in the past, the session expires instantly.
|
||||||
|
new_session = self.backend(self.session.session_key)
|
||||||
|
self.assertNotIn('foo', new_session)
|
||||||
|
finally:
|
||||||
|
self.session.delete(old_session_key)
|
||||||
|
self.session.delete(new_session_key)
|
||||||
|
|
||||||
|
|
||||||
class DatabaseSessionTests(SessionTestsMixin, TestCase):
|
class DatabaseSessionTests(SessionTestsMixin, TestCase):
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue