Fixed #2066: session data can now be stored in the cache or on the filesystem. This should be fully backwards-compatible (the database cache store is still the default). A big thanks to John D'Agostino for the bulk of this code.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@6333 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
e6460e4134
commit
bcf7e9a9fe
1
AUTHORS
1
AUTHORS
|
@ -87,6 +87,7 @@ answer newbie questions, and generally made Django that much better:
|
||||||
Matt Croydon <http://www.postneo.com/>
|
Matt Croydon <http://www.postneo.com/>
|
||||||
flavio.curella@gmail.com
|
flavio.curella@gmail.com
|
||||||
Jure Cuhalev <gandalf@owca.info>
|
Jure Cuhalev <gandalf@owca.info>
|
||||||
|
John D'Agostino <john.dagostino@gmail.com>
|
||||||
dackze+django@gmail.com
|
dackze+django@gmail.com
|
||||||
David Danier <goliath.mailinglist@gmx.de>
|
David Danier <goliath.mailinglist@gmx.de>
|
||||||
Dirk Datzert <dummy@habmalnefrage.de>
|
Dirk Datzert <dummy@habmalnefrage.de>
|
||||||
|
|
|
@ -271,12 +271,14 @@ MIDDLEWARE_CLASSES = (
|
||||||
# SESSIONS #
|
# SESSIONS #
|
||||||
############
|
############
|
||||||
|
|
||||||
SESSION_COOKIE_NAME = 'sessionid' # Cookie name. This can be whatever you want.
|
SESSION_COOKIE_NAME = 'sessionid' # Cookie name. This can be whatever you want.
|
||||||
SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2 # Age of cookie, in seconds (default: 2 weeks).
|
SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2 # Age of cookie, in seconds (default: 2 weeks).
|
||||||
SESSION_COOKIE_DOMAIN = None # A string like ".lawrence.com", or None for standard domain cookie.
|
SESSION_COOKIE_DOMAIN = None # A string like ".lawrence.com", or None for standard domain cookie.
|
||||||
SESSION_COOKIE_SECURE = False # Whether the session cookie should be secure (https:// only).
|
SESSION_COOKIE_SECURE = False # Whether the session cookie should be secure (https:// only).
|
||||||
SESSION_SAVE_EVERY_REQUEST = False # Whether to save the session data on every request.
|
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 sessions expire when a user closes his browser.
|
||||||
|
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # The module to store session data
|
||||||
|
SESSION_FILE_PATH = '/tmp/' # Directory to store session files if using the file session module
|
||||||
|
|
||||||
#########
|
#########
|
||||||
# CACHE #
|
# CACHE #
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
import base64
|
||||||
|
import md5
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.exceptions import SuspiciousOperation
|
||||||
|
|
||||||
|
try:
|
||||||
|
import cPickle as pickle
|
||||||
|
except ImportError:
|
||||||
|
import pickle
|
||||||
|
|
||||||
|
class SessionBase(object):
|
||||||
|
"""
|
||||||
|
Base class for all Session classes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
TEST_COOKIE_NAME = 'testcookie'
|
||||||
|
TEST_COOKIE_VALUE = 'worked'
|
||||||
|
|
||||||
|
def __init__(self, session_key=None):
|
||||||
|
self._session_key = session_key
|
||||||
|
self.accessed = False
|
||||||
|
self.modified = False
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
return key in self._session
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self._session[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
self._session[key] = value
|
||||||
|
self.modified = True
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
del self._session[key]
|
||||||
|
self.modified = True
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
return self._session.keys()
|
||||||
|
|
||||||
|
def items(self):
|
||||||
|
return self._session.items()
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
return self._session.get(key, default)
|
||||||
|
|
||||||
|
def pop(self, key, *args):
|
||||||
|
return self._session.pop(key, *args)
|
||||||
|
|
||||||
|
def set_test_cookie(self):
|
||||||
|
self[self.TEST_COOKIE_NAME] = self.TEST_COOKIE_VALUE
|
||||||
|
|
||||||
|
def test_cookie_worked(self):
|
||||||
|
return self.get(self.TEST_COOKIE_NAME) == self.TEST_COOKIE_VALUE
|
||||||
|
|
||||||
|
def delete_test_cookie(self):
|
||||||
|
del self[self.TEST_COOKIE_NAME]
|
||||||
|
|
||||||
|
def encode(self, session_dict):
|
||||||
|
"Returns the given session dictionary pickled and encoded as a string."
|
||||||
|
pickled = pickle.dumps(session_dict, pickle.HIGHEST_PROTOCOL)
|
||||||
|
pickled_md5 = md5.new(pickled + settings.SECRET_KEY).hexdigest()
|
||||||
|
return base64.encodestring(pickled + pickled_md5)
|
||||||
|
|
||||||
|
def decode(self, session_data):
|
||||||
|
encoded_data = base64.decodestring(session_data)
|
||||||
|
pickled, tamper_check = encoded_data[:-32], encoded_data[-32:]
|
||||||
|
if md5.new(pickled + settings.SECRET_KEY).hexdigest() != tamper_check:
|
||||||
|
raise SuspiciousOperation("User tampered with session cookie.")
|
||||||
|
try:
|
||||||
|
return pickle.loads(pickled)
|
||||||
|
# Unpickling can cause a variety of exceptions. If something happens,
|
||||||
|
# just return an empty dictionary (an empty session).
|
||||||
|
except:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def _get_new_session_key(self):
|
||||||
|
"Returns session key that isn't being used."
|
||||||
|
# The random module is seeded when this Apache child is created.
|
||||||
|
# Use settings.SECRET_KEY as added salt.
|
||||||
|
while 1:
|
||||||
|
session_key = md5.new("%s%s%s%s" % (random.randint(0, sys.maxint - 1),
|
||||||
|
os.getpid(), time.time(), settings.SECRET_KEY)).hexdigest()
|
||||||
|
if not self.exists(session_key):
|
||||||
|
break
|
||||||
|
return session_key
|
||||||
|
|
||||||
|
def _get_session_key(self):
|
||||||
|
if self._session_key:
|
||||||
|
return self._session_key
|
||||||
|
else:
|
||||||
|
self._session_key = self._get_new_session_key()
|
||||||
|
return self._session_key
|
||||||
|
|
||||||
|
def _set_session_key(self, session_key):
|
||||||
|
self._session_key = session_key
|
||||||
|
|
||||||
|
session_key = property(_get_session_key, _set_session_key)
|
||||||
|
|
||||||
|
def _get_session(self):
|
||||||
|
# Lazily loads session from storage.
|
||||||
|
self.accessed = True
|
||||||
|
try:
|
||||||
|
return self._session_cache
|
||||||
|
except AttributeError:
|
||||||
|
if self.session_key is None:
|
||||||
|
self._session_cache = {}
|
||||||
|
else:
|
||||||
|
self._session_cache = self.load()
|
||||||
|
return self._session_cache
|
||||||
|
|
||||||
|
_session = property(_get_session)
|
||||||
|
|
||||||
|
# Methods that child classes must implement.
|
||||||
|
|
||||||
|
def exists(self, session_key):
|
||||||
|
"""
|
||||||
|
Returns True if the given session_key already exists.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
"""
|
||||||
|
Saves the session data.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def delete(self, session_key):
|
||||||
|
"""
|
||||||
|
Clears out the session data under this key.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
"""
|
||||||
|
Loads the session data and returns a dictionary.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.sessions.backends.base import SessionBase
|
||||||
|
from django.core.cache import cache
|
||||||
|
|
||||||
|
class SessionStore(SessionBase):
|
||||||
|
"""
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
|
@ -0,0 +1,49 @@
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.sessions.models import Session
|
||||||
|
from django.contrib.sessions.backends.base import SessionBase
|
||||||
|
from django.core.exceptions import SuspiciousOperation
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
class SessionStore(SessionBase):
|
||||||
|
"""
|
||||||
|
Implements database session store
|
||||||
|
"""
|
||||||
|
def __init__(self, session_key=None):
|
||||||
|
super(SessionStore, self).__init__(session_key)
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
try:
|
||||||
|
s = Session.objects.get(
|
||||||
|
session_key = self.session_key,
|
||||||
|
expire_date__gt=datetime.datetime.now()
|
||||||
|
)
|
||||||
|
return self.decode(s.session_data)
|
||||||
|
except (Session.DoesNotExist, SuspiciousOperation):
|
||||||
|
|
||||||
|
# Create a new session_key for extra security.
|
||||||
|
self.session_key = self._get_new_session_key()
|
||||||
|
self._session_cache = {}
|
||||||
|
|
||||||
|
# Save immediately to minimize collision
|
||||||
|
self.save()
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def exists(self, session_key):
|
||||||
|
try:
|
||||||
|
Session.objects.get(session_key=session_key)
|
||||||
|
except Session.DoesNotExist:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
|
||||||
|
def delete(self, session_key):
|
||||||
|
try:
|
||||||
|
Session.objects.get(session_key=session_key).delete()
|
||||||
|
except Session.DoesNotExist:
|
||||||
|
pass
|
|
@ -0,0 +1,67 @@
|
||||||
|
import os
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.sessions.backends.base import SessionBase
|
||||||
|
from django.core.exceptions import SuspiciousOperation
|
||||||
|
|
||||||
|
class SessionStore(SessionBase):
|
||||||
|
"""
|
||||||
|
Implements a file based session store.
|
||||||
|
"""
|
||||||
|
def __init__(self, session_key=None):
|
||||||
|
self.storage_path = settings.SESSION_FILE_PATH
|
||||||
|
self.file_prefix = settings.SESSION_COOKIE_NAME
|
||||||
|
super(SessionStore, self).__init__(session_key)
|
||||||
|
|
||||||
|
def _key_to_file(self, session_key=None):
|
||||||
|
"""
|
||||||
|
Get the file associated with this session key.
|
||||||
|
"""
|
||||||
|
if session_key is None:
|
||||||
|
session_key = self.session_key
|
||||||
|
|
||||||
|
# Make sure we're not vulnerable to directory traversal. Session keys
|
||||||
|
# should always be md5s, so they should never contain directory components.
|
||||||
|
if os.path.sep in session_key:
|
||||||
|
raise SuspiciousOperation("Invalid characters (directory components) in session key")
|
||||||
|
|
||||||
|
return os.path.join(self.storage_path, self.file_prefix + session_key)
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
session_data = {}
|
||||||
|
try:
|
||||||
|
session_file = open(self._key_to_file(), "rb")
|
||||||
|
try:
|
||||||
|
session_data = self.decode(session_file.read())
|
||||||
|
except(EOFError, SuspiciousOperation):
|
||||||
|
self._session_key = self._get_new_session_key()
|
||||||
|
self._session_cache = {}
|
||||||
|
self.save()
|
||||||
|
finally:
|
||||||
|
session_file.close()
|
||||||
|
except(IOError):
|
||||||
|
pass
|
||||||
|
return session_data
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
try:
|
||||||
|
f = open(self._key_to_file(self.session_key), "wb")
|
||||||
|
try:
|
||||||
|
f.write(self.encode(self._session))
|
||||||
|
finally:
|
||||||
|
f.close()
|
||||||
|
except(IOError, EOFError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def exists(self, session_key):
|
||||||
|
if os.path.exists(self._key_to_file(session_key)):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def delete(self, session_key):
|
||||||
|
try:
|
||||||
|
os.unlink(self._key_to_file(session_key))
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
pass
|
|
@ -1,6 +1,4 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.sessions.models import Session
|
|
||||||
from django.core.exceptions import SuspiciousOperation
|
|
||||||
from django.utils.cache import patch_vary_headers
|
from django.utils.cache import patch_vary_headers
|
||||||
from email.Utils import formatdate
|
from email.Utils import formatdate
|
||||||
import datetime
|
import datetime
|
||||||
|
@ -9,73 +7,11 @@ import time
|
||||||
TEST_COOKIE_NAME = 'testcookie'
|
TEST_COOKIE_NAME = 'testcookie'
|
||||||
TEST_COOKIE_VALUE = 'worked'
|
TEST_COOKIE_VALUE = 'worked'
|
||||||
|
|
||||||
class SessionWrapper(object):
|
|
||||||
def __init__(self, session_key):
|
|
||||||
self.session_key = session_key
|
|
||||||
self.accessed = False
|
|
||||||
self.modified = False
|
|
||||||
|
|
||||||
def __contains__(self, key):
|
|
||||||
return key in self._session
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
return self._session[key]
|
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
|
||||||
self._session[key] = value
|
|
||||||
self.modified = True
|
|
||||||
|
|
||||||
def __delitem__(self, key):
|
|
||||||
del self._session[key]
|
|
||||||
self.modified = True
|
|
||||||
|
|
||||||
def keys(self):
|
|
||||||
return self._session.keys()
|
|
||||||
|
|
||||||
def items(self):
|
|
||||||
return self._session.items()
|
|
||||||
|
|
||||||
def get(self, key, default=None):
|
|
||||||
return self._session.get(key, default)
|
|
||||||
|
|
||||||
def pop(self, key, *args):
|
|
||||||
self.modified = self.modified or key in self._session
|
|
||||||
return self._session.pop(key, *args)
|
|
||||||
|
|
||||||
def set_test_cookie(self):
|
|
||||||
self[TEST_COOKIE_NAME] = TEST_COOKIE_VALUE
|
|
||||||
|
|
||||||
def test_cookie_worked(self):
|
|
||||||
return self.get(TEST_COOKIE_NAME) == TEST_COOKIE_VALUE
|
|
||||||
|
|
||||||
def delete_test_cookie(self):
|
|
||||||
del self[TEST_COOKIE_NAME]
|
|
||||||
|
|
||||||
def _get_session(self):
|
|
||||||
# Lazily loads session from storage.
|
|
||||||
self.accessed = True
|
|
||||||
try:
|
|
||||||
return self._session_cache
|
|
||||||
except AttributeError:
|
|
||||||
if self.session_key is None:
|
|
||||||
self._session_cache = {}
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
s = Session.objects.get(session_key=self.session_key,
|
|
||||||
expire_date__gt=datetime.datetime.now())
|
|
||||||
self._session_cache = s.get_decoded()
|
|
||||||
except (Session.DoesNotExist, SuspiciousOperation):
|
|
||||||
self._session_cache = {}
|
|
||||||
# Set the session_key to None to force creation of a new
|
|
||||||
# key, for extra security.
|
|
||||||
self.session_key = None
|
|
||||||
return self._session_cache
|
|
||||||
|
|
||||||
_session = property(_get_session)
|
|
||||||
|
|
||||||
class SessionMiddleware(object):
|
class SessionMiddleware(object):
|
||||||
|
|
||||||
def process_request(self, request):
|
def process_request(self, request):
|
||||||
request.session = SessionWrapper(request.COOKIES.get(settings.SESSION_COOKIE_NAME, None))
|
engine = __import__(settings.SESSION_ENGINE, {}, {}, [''])
|
||||||
|
request.session = engine.SessionStore(request.COOKIES.get(settings.SESSION_COOKIE_NAME, None))
|
||||||
|
|
||||||
def process_response(self, request, response):
|
def process_response(self, request, response):
|
||||||
# If request.session was modified, or if response.session was set, save
|
# If request.session was modified, or if response.session was set, save
|
||||||
|
@ -89,25 +25,22 @@ class SessionMiddleware(object):
|
||||||
if accessed:
|
if accessed:
|
||||||
patch_vary_headers(response, ('Cookie',))
|
patch_vary_headers(response, ('Cookie',))
|
||||||
if modified or settings.SESSION_SAVE_EVERY_REQUEST:
|
if modified or settings.SESSION_SAVE_EVERY_REQUEST:
|
||||||
if request.session.session_key:
|
|
||||||
session_key = request.session.session_key
|
|
||||||
else:
|
|
||||||
obj = Session.objects.get_new_session_object()
|
|
||||||
session_key = obj.session_key
|
|
||||||
|
|
||||||
if settings.SESSION_EXPIRE_AT_BROWSER_CLOSE:
|
if settings.SESSION_EXPIRE_AT_BROWSER_CLOSE:
|
||||||
max_age = None
|
max_age = None
|
||||||
expires = None
|
expires = None
|
||||||
else:
|
else:
|
||||||
max_age = settings.SESSION_COOKIE_AGE
|
max_age = settings.SESSION_COOKIE_AGE
|
||||||
rfcdate = formatdate(time.time() + settings.SESSION_COOKIE_AGE)
|
rfcdate = formatdate(time.time() + settings.SESSION_COOKIE_AGE)
|
||||||
|
|
||||||
# Fixed length date must have '-' separation in the format
|
# Fixed length date must have '-' separation in the format
|
||||||
# DD-MMM-YYYY for compliance with Netscape cookie standard
|
# DD-MMM-YYYY for compliance with Netscape cookie standard
|
||||||
expires = (rfcdate[:7] + "-" + rfcdate[8:11]
|
expires = datetime.datetime.strftime(datetime.datetime.utcnow() + \
|
||||||
+ "-" + rfcdate[12:26] + "GMT")
|
datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE), "%a, %d-%b-%Y %H:%M:%S GMT")
|
||||||
new_session = Session.objects.save(session_key, request.session._session,
|
|
||||||
datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE))
|
# Save the seesion data and refresh the client cookie.
|
||||||
response.set_cookie(settings.SESSION_COOKIE_NAME, session_key,
|
request.session.save()
|
||||||
|
response.set_cookie(settings.SESSION_COOKIE_NAME, request.session.session_key,
|
||||||
max_age=max_age, expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
|
max_age=max_age, expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
|
||||||
secure=settings.SESSION_COOKIE_SECURE or None)
|
secure=settings.SESSION_COOKIE_SECURE or None)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import base64, md5, random, sys, datetime, os, time
|
import base64, md5, random, sys, datetime
|
||||||
import cPickle as pickle
|
import cPickle as pickle
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
@ -74,6 +74,7 @@ class Session(models.Model):
|
||||||
session_data = models.TextField(_('session data'))
|
session_data = models.TextField(_('session data'))
|
||||||
expire_date = models.DateTimeField(_('expire date'))
|
expire_date = models.DateTimeField(_('expire date'))
|
||||||
objects = SessionManager()
|
objects = SessionManager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = 'django_session'
|
db_table = 'django_session'
|
||||||
verbose_name = _('session')
|
verbose_name = _('session')
|
||||||
|
|
|
@ -1,35 +1,59 @@
|
||||||
r"""
|
r"""
|
||||||
>>> s = SessionWrapper(None)
|
|
||||||
|
|
||||||
Inject data into the session cache.
|
>>> from django.contrib.sessions.backends.db import SessionStore as DatabaseSession
|
||||||
>>> s._session_cache = {}
|
>>> from django.contrib.sessions.backends.cache import SessionStore as CacheSession
|
||||||
>>> s._session_cache['some key'] = 'exists'
|
>>> from django.contrib.sessions.backends.file import SessionStore as FileSession
|
||||||
|
|
||||||
>>> s.accessed
|
>>> db_session = DatabaseSession()
|
||||||
|
>>> db_session.modified
|
||||||
False
|
False
|
||||||
>>> s.modified
|
>>> db_session['cat'] = "dog"
|
||||||
False
|
>>> db_session.modified
|
||||||
|
True
|
||||||
>>> s.pop('non existant key', 'does not exist')
|
>>> db_session.pop('cat')
|
||||||
|
'dog'
|
||||||
|
>>> db_session.pop('some key', 'does not exist')
|
||||||
'does not exist'
|
'does not exist'
|
||||||
>>> s.accessed
|
>>> db_session.save()
|
||||||
|
>>> db_session.exists(db_session.session_key)
|
||||||
True
|
True
|
||||||
>>> s.modified
|
>>> db_session.delete(db_session.session_key)
|
||||||
|
>>> db_session.exists(db_session.session_key)
|
||||||
False
|
False
|
||||||
|
|
||||||
>>> s.pop('some key')
|
>>> file_session = FileSession()
|
||||||
'exists'
|
>>> file_session.modified
|
||||||
>>> s.accessed
|
False
|
||||||
|
>>> file_session['cat'] = "dog"
|
||||||
|
>>> file_session.modified
|
||||||
True
|
True
|
||||||
>>> s.modified
|
>>> file_session.pop('cat')
|
||||||
True
|
'dog'
|
||||||
|
>>> file_session.pop('some key', 'does not exist')
|
||||||
>>> s.pop('some key', 'does not exist')
|
|
||||||
'does not exist'
|
'does not exist'
|
||||||
|
>>> file_session.save()
|
||||||
|
>>> file_session.exists(file_session.session_key)
|
||||||
|
True
|
||||||
|
>>> file_session.delete(file_session.session_key)
|
||||||
|
>>> file_session.exists(file_session.session_key)
|
||||||
|
False
|
||||||
|
|
||||||
|
>>> cache_session = CacheSession()
|
||||||
|
>>> cache_session.modified
|
||||||
|
False
|
||||||
|
>>> cache_session['cat'] = "dog"
|
||||||
|
>>> cache_session.modified
|
||||||
|
True
|
||||||
|
>>> cache_session.pop('cat')
|
||||||
|
'dog'
|
||||||
|
>>> cache_session.pop('some key', 'does not exist')
|
||||||
|
'does not exist'
|
||||||
|
>>> cache_session.save()
|
||||||
|
>>> cache_session.delete(cache_session.session_key)
|
||||||
|
>>> cache_session.exists(cache_session.session_key)
|
||||||
|
False
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.contrib.sessions.middleware import SessionWrapper
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import doctest
|
import doctest
|
||||||
doctest.testmod()
|
doctest.testmod()
|
||||||
|
|
|
@ -4,8 +4,6 @@ from cStringIO import StringIO
|
||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import authenticate, login
|
from django.contrib.auth import authenticate, login
|
||||||
from django.contrib.sessions.models import Session
|
|
||||||
from django.contrib.sessions.middleware import SessionWrapper
|
|
||||||
from django.core.handlers.base import BaseHandler
|
from django.core.handlers.base import BaseHandler
|
||||||
from django.core.handlers.wsgi import WSGIRequest
|
from django.core.handlers.wsgi import WSGIRequest
|
||||||
from django.core.signals import got_request_exception
|
from django.core.signals import got_request_exception
|
||||||
|
@ -132,9 +130,10 @@ class Client:
|
||||||
def _session(self):
|
def _session(self):
|
||||||
"Obtain the current session variables"
|
"Obtain the current session variables"
|
||||||
if 'django.contrib.sessions' in settings.INSTALLED_APPS:
|
if 'django.contrib.sessions' in settings.INSTALLED_APPS:
|
||||||
|
engine = __import__(settings.SESSION_ENGINE, {}, {}, [''])
|
||||||
cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None)
|
cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None)
|
||||||
if cookie:
|
if cookie:
|
||||||
return SessionWrapper(cookie.value)
|
return engine.SessionClass(cookie.value)
|
||||||
return {}
|
return {}
|
||||||
session = property(_session)
|
session = property(_session)
|
||||||
|
|
||||||
|
@ -247,24 +246,23 @@ class Client:
|
||||||
"""
|
"""
|
||||||
user = authenticate(**credentials)
|
user = authenticate(**credentials)
|
||||||
if user and user.is_active and 'django.contrib.sessions' in settings.INSTALLED_APPS:
|
if user and user.is_active and 'django.contrib.sessions' in settings.INSTALLED_APPS:
|
||||||
obj = Session.objects.get_new_session_object()
|
engine = __import__(settings.SESSION_ENGINE, {}, {}, [''])
|
||||||
|
|
||||||
# Create a fake request to store login details
|
# Create a fake request to store login details
|
||||||
request = HttpRequest()
|
request = HttpRequest()
|
||||||
request.session = SessionWrapper(obj.session_key)
|
request.session = engine.SessionClass()
|
||||||
login(request, user)
|
login(request, user)
|
||||||
|
|
||||||
# Set the cookie to represent the session
|
# Set the cookie to represent the session
|
||||||
self.cookies[settings.SESSION_COOKIE_NAME] = obj.session_key
|
self.cookies[settings.SESSION_COOKIE_NAME] = request.session.session_key
|
||||||
self.cookies[settings.SESSION_COOKIE_NAME]['max-age'] = None
|
self.cookies[settings.SESSION_COOKIE_NAME]['max-age'] = None
|
||||||
self.cookies[settings.SESSION_COOKIE_NAME]['path'] = '/'
|
self.cookies[settings.SESSION_COOKIE_NAME]['path'] = '/'
|
||||||
self.cookies[settings.SESSION_COOKIE_NAME]['domain'] = settings.SESSION_COOKIE_DOMAIN
|
self.cookies[settings.SESSION_COOKIE_NAME]['domain'] = settings.SESSION_COOKIE_DOMAIN
|
||||||
self.cookies[settings.SESSION_COOKIE_NAME]['secure'] = settings.SESSION_COOKIE_SECURE or None
|
self.cookies[settings.SESSION_COOKIE_NAME]['secure'] = settings.SESSION_COOKIE_SECURE or None
|
||||||
self.cookies[settings.SESSION_COOKIE_NAME]['expires'] = None
|
self.cookies[settings.SESSION_COOKIE_NAME]['expires'] = None
|
||||||
|
|
||||||
# Set the session values
|
# Save the session values
|
||||||
Session.objects.save(obj.session_key, request.session._session,
|
request.session.save()
|
||||||
datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE))
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -10,18 +10,21 @@ Cookies contain a session ID -- not the data itself.
|
||||||
Enabling sessions
|
Enabling sessions
|
||||||
=================
|
=================
|
||||||
|
|
||||||
Sessions are implemented via a piece of middleware_ and a Django model.
|
Sessions are implemented via a piece of middleware_.
|
||||||
|
|
||||||
To enable session functionality, do these two things:
|
To enable session functionality, do the following:
|
||||||
|
|
||||||
* Edit the ``MIDDLEWARE_CLASSES`` setting and make sure
|
* Edit the ``MIDDLEWARE_CLASSES`` setting and make sure
|
||||||
``MIDDLEWARE_CLASSES`` contains ``'django.contrib.sessions.middleware.SessionMiddleware'``.
|
``MIDDLEWARE_CLASSES`` contains ``'django.contrib.sessions.middleware.SessionMiddleware'``.
|
||||||
The default ``settings.py`` created by ``django-admin.py startproject`` has
|
The default ``settings.py`` created by ``django-admin.py startproject`` has
|
||||||
``SessionMiddleware`` activated.
|
``SessionMiddleware`` activated.
|
||||||
|
|
||||||
* Add ``'django.contrib.sessions'`` to your ``INSTALLED_APPS`` setting, and
|
* Add ``'django.contrib.sessions'`` to your ``INSTALLED_APPS`` setting,
|
||||||
run ``manage.py syncdb`` to install the single database table that stores
|
and run ``manage.py syncdb`` to install the single database table
|
||||||
session data.
|
that stores session data.
|
||||||
|
|
||||||
|
**New in development version**: this step is optional if you're not using
|
||||||
|
the database session backend; see `configuring the session engine`_.
|
||||||
|
|
||||||
If you don't want to use sessions, you might as well remove the
|
If you don't want to use sessions, you might as well remove the
|
||||||
``SessionMiddleware`` line from ``MIDDLEWARE_CLASSES`` and ``'django.contrib.sessions'``
|
``SessionMiddleware`` line from ``MIDDLEWARE_CLASSES`` and ``'django.contrib.sessions'``
|
||||||
|
@ -29,6 +32,44 @@ from your ``INSTALLED_APPS``. It'll save you a small bit of overhead.
|
||||||
|
|
||||||
.. _middleware: ../middleware/
|
.. _middleware: ../middleware/
|
||||||
|
|
||||||
|
Configuring the session engine
|
||||||
|
==============================
|
||||||
|
|
||||||
|
**New in development version**.
|
||||||
|
|
||||||
|
By default, Django stores sessions in your database (using the model
|
||||||
|
``django.contrib.sessions.models.Session``). Though this is convenient, in
|
||||||
|
some setups it's faster to store session data elsewhere, so Django can be
|
||||||
|
configured to store session data on your filesystem or in your cache.
|
||||||
|
|
||||||
|
Using file-based sessions
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
To use file-based sessions, set the ``SESSION_ENGINE`` setting to
|
||||||
|
``"django.contrib.sessions.backends.file"``.
|
||||||
|
|
||||||
|
You might also want to set the ``SESSION_FILE_PATH`` setting (which
|
||||||
|
defaults to ``/tmp``) to control where Django stores session files. Be
|
||||||
|
sure to check that your web server has permissions to read and write to
|
||||||
|
this location.
|
||||||
|
|
||||||
|
Using cache-based sessions
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
To store session data using Django's cache system, set ``SESSION_ENGINE``
|
||||||
|
to ``"django.contrib.sessions.backends.cache"``. You'll want to make sure
|
||||||
|
you've configured your cache; see the `cache documentation`_ for details.
|
||||||
|
|
||||||
|
.. _cache documentation: ../cache/
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
You probably don't want to use cache-based sessions if you're not using
|
||||||
|
the memcached cache backend. The local memory and simple cache backends
|
||||||
|
don't retain data long enough to be good choices, and it'll be faster
|
||||||
|
to use file or database sessions directly instead of sending everything
|
||||||
|
through the file or database cache backends.
|
||||||
|
|
||||||
Using sessions in views
|
Using sessions in views
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
|
@ -153,14 +194,25 @@ Here's a typical usage example::
|
||||||
Using sessions out of views
|
Using sessions out of views
|
||||||
===========================
|
===========================
|
||||||
|
|
||||||
Internally, each session is just a normal Django model. The ``Session`` model
|
The ``SessionStore`` which implements the session storage method can be imported
|
||||||
|
and a API is available to manipulate the session data outside of a view::
|
||||||
|
|
||||||
|
>>> from django.contrib.sessions.engines.db import SessionStore
|
||||||
|
>>> s = SessionStore(session_key='2b1189a188b44ad18c35e113ac6ceead')
|
||||||
|
>>> s['last_login'] = datetime.datetime(2005, 8, 20, 13, 35, 10)
|
||||||
|
>>> s['last_login']
|
||||||
|
datetime.datetime(2005, 8, 20, 13, 35, 0)
|
||||||
|
>>> s.save()
|
||||||
|
|
||||||
|
Or if you are using the ``django.contrib.sessions.engine.db`` each
|
||||||
|
session is just a normal Django model. The ``Session`` model
|
||||||
is defined in ``django/contrib/sessions/models.py``. Because it's a normal
|
is defined in ``django/contrib/sessions/models.py``. Because it's a normal
|
||||||
model, you can access sessions using the normal Django database API::
|
model, you can access sessions using the normal Django database API::
|
||||||
|
|
||||||
>>> from django.contrib.sessions.models import Session
|
>>> from django.contrib.sessions.models import Session
|
||||||
>>> s = Session.objects.get(pk='2b1189a188b44ad18c35e113ac6ceead')
|
>>> s = Session.objects.get(pk='2b1189a188b44ad18c35e113ac6ceead')
|
||||||
>>> s.expire_date
|
>>> s.expire_date
|
||||||
datetime.datetime(2005, 8, 20, 13, 35, 12)
|
datetime.datetime(2005, 8, 20, 13, 35, 12)
|
||||||
|
|
||||||
Note that you'll need to call ``get_decoded()`` to get the session dictionary.
|
Note that you'll need to call ``get_decoded()`` to get the session dictionary.
|
||||||
This is necessary because the dictionary is stored in an encoded format::
|
This is necessary because the dictionary is stored in an encoded format::
|
||||||
|
@ -245,6 +297,31 @@ Settings
|
||||||
|
|
||||||
A few `Django settings`_ give you control over session behavior:
|
A few `Django settings`_ give you control over session behavior:
|
||||||
|
|
||||||
|
SESSION_ENGINE
|
||||||
|
--------------
|
||||||
|
|
||||||
|
**New in Django development version**
|
||||||
|
|
||||||
|
Default: ``django.contrib.sessions.backends.db``
|
||||||
|
|
||||||
|
Controls where Django stores session data. Valid values are:
|
||||||
|
|
||||||
|
* ``'django.contrib.sessions.backends.db'``
|
||||||
|
* ``'django.contrib.sessions.backends.file'``
|
||||||
|
* ``'django.contrib.sessions.backends.cache'``
|
||||||
|
|
||||||
|
See `configuring the session engine`_ for more details.
|
||||||
|
|
||||||
|
SESSION_FILE_PATH
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
**New in Django development version**
|
||||||
|
|
||||||
|
Default: ``/tmp/``
|
||||||
|
|
||||||
|
If you're using file-based session storage, this sets the directory in
|
||||||
|
which Django will store session data.
|
||||||
|
|
||||||
SESSION_COOKIE_AGE
|
SESSION_COOKIE_AGE
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
|
|
@ -733,6 +733,21 @@ Default: ``'root@localhost'``
|
||||||
The e-mail address that error messages come from, such as those sent to
|
The e-mail address that error messages come from, such as those sent to
|
||||||
``ADMINS`` and ``MANAGERS``.
|
``ADMINS`` and ``MANAGERS``.
|
||||||
|
|
||||||
|
SESSION_ENGINE
|
||||||
|
--------------
|
||||||
|
|
||||||
|
**New in Django development version**
|
||||||
|
|
||||||
|
Default: ``django.contrib.sessions.backends.db``
|
||||||
|
|
||||||
|
Controls where Django stores session data. Valid values are:
|
||||||
|
|
||||||
|
* ``'django.contrib.sessions.backends.db'``
|
||||||
|
* ``'django.contrib.sessions.backends.file'``
|
||||||
|
* ``'django.contrib.sessions.backends.cache'``
|
||||||
|
|
||||||
|
See the `session docs`_ for more details.
|
||||||
|
|
||||||
SESSION_COOKIE_AGE
|
SESSION_COOKIE_AGE
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
@ -775,6 +790,17 @@ Default: ``False``
|
||||||
Whether to expire the session when the user closes his or her browser.
|
Whether to expire the session when the user closes his or her browser.
|
||||||
See the `session docs`_.
|
See the `session docs`_.
|
||||||
|
|
||||||
|
SESSION_FILE_PATH
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
**New in Django development version**
|
||||||
|
|
||||||
|
Default: ``/tmp/``
|
||||||
|
|
||||||
|
If you're using file-based session storage, this sets the directory in
|
||||||
|
which Django will store session data. See the `session docs`_ for
|
||||||
|
more details.
|
||||||
|
|
||||||
SESSION_SAVE_EVERY_REQUEST
|
SESSION_SAVE_EVERY_REQUEST
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue