diff --git a/AUTHORS b/AUTHORS
index 1e30399735..9d47f2c876 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -87,6 +87,7 @@ answer newbie questions, and generally made Django that much better:
Matt Croydon
flavio.curella@gmail.com
Jure Cuhalev
+ John D'Agostino
dackze+django@gmail.com
David Danier
Dirk Datzert
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
index c8420f3307..b3cbf095c3 100644
--- a/django/conf/global_settings.py
+++ b/django/conf/global_settings.py
@@ -271,12 +271,14 @@ MIDDLEWARE_CLASSES = (
# SESSIONS #
############
-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_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_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_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_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_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_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 #
diff --git a/django/contrib/auth/handlers/modpython.py b/django/contrib/auth/handlers/modpython.py
index c7d921313d..de961fa4dd 100644
--- a/django/contrib/auth/handlers/modpython.py
+++ b/django/contrib/auth/handlers/modpython.py
@@ -10,6 +10,10 @@ def authenhandler(req, **kwargs):
# that so that the following import works
os.environ.update(req.subprocess_env)
+ # apache 2.2 requires a call to req.get_basic_auth_pw() before
+ # req.user and friends are available.
+ req.get_basic_auth_pw()
+
# check for PythonOptions
_str_to_bool = lambda s: s.lower() in ('1', 'true', 'on', 'yes')
diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py
index 7cbeb26af6..2f9954e742 100644
--- a/django/contrib/auth/models.py
+++ b/django/contrib/auth/models.py
@@ -15,25 +15,43 @@ try:
except NameError:
from sets import Set as set # Python 2.3 fallback
+def get_hexdigest(algorithm, salt, raw_password):
+ """
+ Returns a string of the hexdigest of the given plaintext password and salt
+ using the given algorithm ('md5', 'sha1' or 'crypt').
+ """
+ raw_password, salt = smart_str(raw_password), smart_str(salt)
+ if algorithm == 'crypt':
+ try:
+ import crypt
+ except ImportError:
+ raise ValueError('"crypt" password algorithm not supported in this environment')
+ return crypt.crypt(raw_password, salt)
+ # The rest of the supported algorithms are supported by hashlib, but
+ # hashlib is only available in Python 2.5.
+ try:
+ import hashlib
+ except ImportError:
+ if algorithm == 'md5':
+ import md5
+ return md5.new(salt + raw_password).hexdigest()
+ elif algorithm == 'sha1':
+ import sha
+ return sha.new(salt + raw_password).hexdigest()
+ else:
+ if algorithm == 'md5':
+ return hashlib.md5(salt + raw_password).hexdigest()
+ elif algorithm == 'sha1':
+ return hashlib.sha1(salt + raw_password).hexdigest()
+ raise ValueError("Got unknown password algorithm type in password.")
+
def check_password(raw_password, enc_password):
"""
Returns a boolean of whether the raw_password was correct. Handles
encryption formats behind the scenes.
"""
algo, salt, hsh = enc_password.split('$')
- if algo == 'md5':
- import md5
- return hsh == md5.new(smart_str(salt + raw_password)).hexdigest()
- elif algo == 'sha1':
- import sha
- return hsh == sha.new(smart_str(salt + raw_password)).hexdigest()
- elif algo == 'crypt':
- try:
- import crypt
- except ImportError:
- raise ValueError, "Crypt password algorithm not supported in this environment."
- return hsh == crypt.crypt(smart_str(raw_password), smart_str(salt))
- raise ValueError, "Got unknown password algorithm type in password."
+ return hsh == get_hexdigest(algo, salt, raw_password)
class SiteProfileNotAvailable(Exception):
pass
@@ -162,10 +180,10 @@ class User(models.Model):
return full_name.strip()
def set_password(self, raw_password):
- import sha, random
+ import random
algo = 'sha1'
- salt = sha.new(str(random.random())).hexdigest()[:5]
- hsh = sha.new(salt + smart_str(raw_password)).hexdigest()
+ salt = get_hexdigest(algo, str(random.random()), str(random.random()))[:5]
+ hsh = get_hexdigest(algo, salt, raw_password)
self.password = '%s$%s$%s' % (algo, salt, hsh)
def check_password(self, raw_password):
@@ -176,8 +194,7 @@ class User(models.Model):
# Backwards-compatibility check. Older passwords won't include the
# algorithm or salt.
if '$' not in self.password:
- import md5
- is_correct = (self.password == md5.new(smart_str(raw_password)).hexdigest())
+ is_correct = (self.password == get_hexdigest('md5', '', raw_password))
if is_correct:
# Convert the password to the new, more secure format.
self.set_password(raw_password)
diff --git a/django/contrib/sessions/backends/__init__.py b/django/contrib/sessions/backends/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/django/contrib/sessions/backends/base.py b/django/contrib/sessions/backends/base.py
new file mode 100644
index 0000000000..9471706363
--- /dev/null
+++ b/django/contrib/sessions/backends/base.py
@@ -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
+
diff --git a/django/contrib/sessions/backends/cache.py b/django/contrib/sessions/backends/cache.py
new file mode 100644
index 0000000000..c3e641e691
--- /dev/null
+++ b/django/contrib/sessions/backends/cache.py
@@ -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)
\ No newline at end of file
diff --git a/django/contrib/sessions/backends/db.py b/django/contrib/sessions/backends/db.py
new file mode 100644
index 0000000000..d1496d63bf
--- /dev/null
+++ b/django/contrib/sessions/backends/db.py
@@ -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
\ No newline at end of file
diff --git a/django/contrib/sessions/backends/file.py b/django/contrib/sessions/backends/file.py
new file mode 100644
index 0000000000..062acca323
--- /dev/null
+++ b/django/contrib/sessions/backends/file.py
@@ -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
\ No newline at end of file
diff --git a/django/contrib/sessions/middleware.py b/django/contrib/sessions/middleware.py
index 2531c8e244..4c3c5acc43 100644
--- a/django/contrib/sessions/middleware.py
+++ b/django/contrib/sessions/middleware.py
@@ -1,6 +1,4 @@
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 email.Utils import formatdate
import datetime
@@ -9,73 +7,11 @@ import time
TEST_COOKIE_NAME = 'testcookie'
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):
+
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):
# If request.session was modified, or if response.session was set, save
@@ -89,25 +25,22 @@ class SessionMiddleware(object):
if accessed:
patch_vary_headers(response, ('Cookie',))
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:
max_age = None
expires = None
else:
max_age = settings.SESSION_COOKIE_AGE
rfcdate = formatdate(time.time() + settings.SESSION_COOKIE_AGE)
+
# Fixed length date must have '-' separation in the format
# DD-MMM-YYYY for compliance with Netscape cookie standard
- expires = (rfcdate[:7] + "-" + rfcdate[8:11]
- + "-" + rfcdate[12:26] + "GMT")
- new_session = Session.objects.save(session_key, request.session._session,
- datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE))
- response.set_cookie(settings.SESSION_COOKIE_NAME, session_key,
+ expires = datetime.datetime.strftime(datetime.datetime.utcnow() + \
+ datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE), "%a, %d-%b-%Y %H:%M:%S GMT")
+
+ # Save the seesion data and refresh the client cookie.
+ 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,
secure=settings.SESSION_COOKIE_SECURE or None)
+
return response
diff --git a/django/contrib/sessions/models.py b/django/contrib/sessions/models.py
index fda10c9743..c086396947 100644
--- a/django/contrib/sessions/models.py
+++ b/django/contrib/sessions/models.py
@@ -1,4 +1,4 @@
-import base64, md5, random, sys, datetime, os, time
+import base64, md5, random, sys, datetime
import cPickle as pickle
from django.db import models
from django.utils.translation import ugettext_lazy as _
@@ -74,6 +74,7 @@ class Session(models.Model):
session_data = models.TextField(_('session data'))
expire_date = models.DateTimeField(_('expire date'))
objects = SessionManager()
+
class Meta:
db_table = 'django_session'
verbose_name = _('session')
diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py
index e83442123e..cfb475fc49 100644
--- a/django/contrib/sessions/tests.py
+++ b/django/contrib/sessions/tests.py
@@ -1,35 +1,59 @@
r"""
->>> s = SessionWrapper(None)
-Inject data into the session cache.
->>> s._session_cache = {}
->>> s._session_cache['some key'] = 'exists'
+>>> from django.contrib.sessions.backends.db import SessionStore as DatabaseSession
+>>> from django.contrib.sessions.backends.cache import SessionStore as CacheSession
+>>> from django.contrib.sessions.backends.file import SessionStore as FileSession
->>> s.accessed
+>>> db_session = DatabaseSession()
+>>> db_session.modified
False
->>> s.modified
-False
-
->>> s.pop('non existant key', 'does not exist')
+>>> db_session['cat'] = "dog"
+>>> db_session.modified
+True
+>>> db_session.pop('cat')
+'dog'
+>>> db_session.pop('some key', 'does not exist')
'does not exist'
->>> s.accessed
+>>> db_session.save()
+>>> db_session.exists(db_session.session_key)
True
->>> s.modified
+>>> db_session.delete(db_session.session_key)
+>>> db_session.exists(db_session.session_key)
False
->>> s.pop('some key')
-'exists'
->>> s.accessed
+>>> file_session = FileSession()
+>>> file_session.modified
+False
+>>> file_session['cat'] = "dog"
+>>> file_session.modified
True
->>> s.modified
-True
-
->>> s.pop('some key', 'does not exist')
+>>> file_session.pop('cat')
+'dog'
+>>> file_session.pop('some key', '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__':
import doctest
doctest.testmod()
diff --git a/django/db/__init__.py b/django/db/__init__.py
index 33223d200a..d4ea1403bd 100644
--- a/django/db/__init__.py
+++ b/django/db/__init__.py
@@ -1,6 +1,9 @@
+import os
from django.conf import settings
from django.core import signals
+from django.core.exceptions import ImproperlyConfigured
from django.dispatch import dispatcher
+from django.utils.functional import curry
__all__ = ('backend', 'connection', 'DatabaseError', 'IntegrityError')
@@ -8,25 +11,43 @@ if not settings.DATABASE_ENGINE:
settings.DATABASE_ENGINE = 'dummy'
try:
- backend = __import__('django.db.backends.%s.base' % settings.DATABASE_ENGINE, {}, {}, [''])
+ # Most of the time, the database backend will be one of the official
+ # backends that ships with Django, so look there first.
+ _import_path = 'django.db.backends.'
+ backend = __import__('%s%s.base' % (_import_path, settings.DATABASE_ENGINE), {}, {}, [''])
except ImportError, e:
- # The database backend wasn't found. Display a helpful error message
- # listing all possible database backends.
- from django.core.exceptions import ImproperlyConfigured
- import os
- backend_dir = os.path.join(__path__[0], 'backends')
- available_backends = [f for f in os.listdir(backend_dir) if not f.startswith('_') and not f.startswith('.') and not f.endswith('.py') and not f.endswith('.pyc')]
- available_backends.sort()
- if settings.DATABASE_ENGINE not in available_backends:
- raise ImproperlyConfigured, "%r isn't an available database backend. Available options are: %s" % \
- (settings.DATABASE_ENGINE, ", ".join(map(repr, available_backends)))
- else:
- raise # If there's some other error, this must be an error in Django itself.
+ # If the import failed, we might be looking for a database backend
+ # distributed external to Django. So we'll try that next.
+ try:
+ _import_path = ''
+ backend = __import__('%s.base' % settings.DATABASE_ENGINE, {}, {}, [''])
+ except ImportError, e_user:
+ # The database backend wasn't found. Display a helpful error message
+ # listing all possible (built-in) database backends.
+ backend_dir = os.path.join(__path__[0], 'backends')
+ available_backends = [f for f in os.listdir(backend_dir) if not f.startswith('_') and not f.startswith('.') and not f.endswith('.py') and not f.endswith('.pyc')]
+ available_backends.sort()
+ if settings.DATABASE_ENGINE not in available_backends:
+ raise ImproperlyConfigured, "%r isn't an available database backend. Available options are: %s" % \
+ (settings.DATABASE_ENGINE, ", ".join(map(repr, available_backends)))
+ else:
+ raise # If there's some other error, this must be an error in Django itself.
-get_introspection_module = lambda: __import__('django.db.backends.%s.introspection' % settings.DATABASE_ENGINE, {}, {}, [''])
-get_creation_module = lambda: __import__('django.db.backends.%s.creation' % settings.DATABASE_ENGINE, {}, {}, [''])
-runshell = lambda: __import__('django.db.backends.%s.client' % settings.DATABASE_ENGINE, {}, {}, ['']).runshell()
+def _import_database_module(import_path='', module_name=''):
+ """Lazyily import a database module when requested."""
+ return __import__('%s%s.%s' % (_import_path, settings.DATABASE_ENGINE, module_name), {}, {}, [''])
+# We don't want to import the introspect/creation modules unless
+# someone asks for 'em, so lazily load them on demmand.
+get_introspection_module = curry(_import_database_module, _import_path, 'introspection')
+get_creation_module = curry(_import_database_module, _import_path, 'creation')
+
+# We want runshell() to work the same way, but we have to treat it a
+# little differently (since it just runs instead of returning a module like
+# the above) and wrap the lazily-loaded runshell() method.
+runshell = lambda: _import_database_module(_import_path, "client").runshell()
+
+# Convenient aliases for backend bits.
connection = backend.DatabaseWrapper(**settings.DATABASE_OPTIONS)
DatabaseError = backend.DatabaseError
IntegrityError = backend.IntegrityError
diff --git a/django/test/client.py b/django/test/client.py
index faacc5bf9e..6a05d9dd9c 100644
--- a/django/test/client.py
+++ b/django/test/client.py
@@ -4,8 +4,6 @@ from cStringIO import StringIO
from urlparse import urlparse
from django.conf import settings
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.wsgi import WSGIRequest
from django.core.signals import got_request_exception
@@ -132,9 +130,10 @@ class Client:
def _session(self):
"Obtain the current session variables"
if 'django.contrib.sessions' in settings.INSTALLED_APPS:
+ engine = __import__(settings.SESSION_ENGINE, {}, {}, [''])
cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None)
if cookie:
- return SessionWrapper(cookie.value)
+ return engine.SessionStore(cookie.value)
return {}
session = property(_session)
@@ -247,24 +246,23 @@ class Client:
"""
user = authenticate(**credentials)
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
request = HttpRequest()
- request.session = SessionWrapper(obj.session_key)
+ request.session = engine.SessionStore()
login(request, user)
# 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]['path'] = '/'
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]['expires'] = None
- # Set the session values
- Session.objects.save(obj.session_key, request.session._session,
- datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE))
+ # Save the session values
+ request.session.save()
return True
else:
@@ -275,9 +273,6 @@ class Client:
Causes the authenticated user to be logged out.
"""
- try:
- Session.objects.get(session_key=self.cookies['sessionid'].value).delete()
- except KeyError:
- pass
-
+ session = __import__(settings.SESSION_ENGINE, {}, {}, ['']).SessionStore()
+ session.delete(session_key=self.cookies['sessionid'].value)
self.cookies = SimpleCookie()
diff --git a/django/views/i18n.py b/django/views/i18n.py
index 5b50f75d23..cccec13941 100644
--- a/django/views/i18n.py
+++ b/django/views/i18n.py
@@ -16,7 +16,7 @@ def set_language(request):
redirect to the page in the request (the 'next' parameter) without changing
any state.
"""
- next = request.GET.get('next', None)
+ next = request.REQUEST.get('next', None)
if not next:
next = request.META.get('HTTP_REFERER', None)
if not next:
diff --git a/docs/apache_auth.txt b/docs/apache_auth.txt
index 583cb96b39..9beb1ba43a 100644
--- a/docs/apache_auth.txt
+++ b/docs/apache_auth.txt
@@ -21,14 +21,57 @@ file, you'll need to use mod_python's ``PythonAuthenHandler`` directive along
with the standard ``Auth*`` and ``Require`` directives::
- AuthType basic
+ AuthType Basic
AuthName "example.com"
Require valid-user
SetEnv DJANGO_SETTINGS_MODULE mysite.settings
PythonAuthenHandler django.contrib.auth.handlers.modpython
+
+.. admonition:: Using the authentication handler with Apache 2.2
+ If you're using Apache 2.2, you'll need to take a couple extra steps.
+
+ You'll need to ensure that ``mod_auth_basic`` and ``mod_authz_user``
+ are loaded. These might be compiled staticly into Apache, or you might
+ need to use ``LoadModule`` to load them dynamically (as shown in the
+ example at the bottom of this note).
+
+ You'll also need to insert configuration directives that prevent Apache
+ from trying to use other authentication modules. Depnding on which other
+ authentication modules you have loaded, you might need one or more of
+ the following directives::
+
+ AuthBasicAuthoritative Off
+ AuthDefaultAuthoritative Off
+ AuthzLDAPAuthoritative Off
+ AuthzDBMAuthoritative Off
+ AuthzDefaultAuthoritative Off
+ AuthzGroupFileAuthoritative Off
+ AuthzOwnerAuthoritative Off
+ AuthzUserAuthoritative Off
+
+ A complete configuration, with differences between Apache 2.0 and
+ Apache 2.2 marked in bold, would look something like:
+
+ .. parsed-literal::
+
+ **LoadModule auth_basic_module modules/mod_auth_basic.so**
+ **LoadModule authz_user_module modules/mod_authz_user.so**
+
+ ...
+
+
+ AuthType Basic
+ AuthName "example.com"
+ **AuthBasicAuthoritative Off**
+ Require valid-user
+
+ SetEnv DJANGO_SETTINGS_MODULE mysite.settings
+ PythonAuthenHandler django.contrib.auth.handlers.modpython
+
+
By default, the authentication handler will limit access to the ``/example/``
location to users marked as staff members. You can use a set of
``PythonOption`` directives to modify this behavior:
diff --git a/docs/cache.txt b/docs/cache.txt
index 92b5c1b43d..8ba0383909 100644
--- a/docs/cache.txt
+++ b/docs/cache.txt
@@ -524,6 +524,15 @@ the value of the ``CACHE_MIDDLEWARE_SETTINGS`` setting. If you use a custom
``max_age`` in a ``cache_control`` decorator, the decorator will take
precedence, and the header values will be merged correctly.)
+If you want to use headers to disable caching altogether,
+``django.views.decorators.never_cache`` is a view decorator that adds
+headers to ensure the response won't be cached by browsers or other caches. Example::
+
+ from django.views.decorators.cache import never_cache
+ @never_cache
+ def myview(request):
+ ...
+
.. _`Cache-Control spec`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
Other optimizations
diff --git a/docs/contenttypes.txt b/docs/contenttypes.txt
new file mode 100644
index 0000000000..3ef83f2066
--- /dev/null
+++ b/docs/contenttypes.txt
@@ -0,0 +1,258 @@
+==========================
+The contenttypes framework
+==========================
+
+Django includes a "contenttypes" application that can track all of
+the models installed in your Django-powered project, providing a
+high-level, generic interface for working with your models.
+
+Overview
+========
+
+At the heart of the contenttypes application is the ``ContentType``
+model, which lives at
+``django.contrib.contenttypes.models.ContentType``. Instances of
+``ContentType`` represent and store information about the models
+installed in your project, and new instances of ``ContentType`` are
+automatically created whenever new models are installed.
+
+Instances of ``ContentType`` have methods for returning the model
+classes they represent and for querying objects from those models.
+``ContentType`` also has a `custom manager`_ that adds methods for
+working with ``ContentType`` and for obtaining instances of
+``ContentType`` for a particular model.
+
+Relations between your models and ``ContentType`` can also be used to
+enable "generic" relationships between an instance of one of your
+models and instances of any model you have installed.
+
+.. _custom manager: ../model-api/#custom-managers
+
+Installing the contenttypes framework
+=====================================
+
+The contenttypes framework is included in the default
+``INSTALLED_APPS`` list created by ``django-admin.py startproject``,
+but if you've removed it or if you manually set up your
+``INSTALLED_APPS`` list, you can enable it by adding
+``'django.contrib.contenttypes'`` to your ``INSTALLED_APPS`` setting.
+
+It's generally a good idea to have the contenttypes framework
+installed; several of Django's other bundled applications require it:
+
+ * The admin application uses it to log the history of each object
+ added or changed through the admin interface.
+
+ * Django's `authentication framework`_ uses it to tie user permissions
+ to specific models.
+
+ * Django's comments system (``django.contrib.comments``) uses it to
+ "attach" comments to any installed model.
+
+.. _authentication framework: ../authentication/
+
+The ``ContentType`` model
+=========================
+
+Each instance of ``ContentType`` has three fields which, taken
+together, uniquely describe an installed model:
+
+ ``app_label``
+ The name of the application the model is part of. This is taken from
+ the ``app_label`` attribute of the model, and includes only the *last*
+ part of the application's Python import path;
+ "django.contrib.contenttypes", for example, becomes an ``app_label``
+ of "contenttypes".
+
+ ``model``
+ The name of the model class.
+
+ ``name``
+ The human-readable name of the model. This is taken from
+ `the verbose_name attribute`_ of the model.
+
+Let's look at an example to see how this works. If you already have
+the contenttypes application installed, and then add `the sites
+application`_ to your ``INSTALLED_APPS`` setting and run ``manage.py
+syncdb`` to install it, the model ``django.contrib.sites.models.Site``
+will be installed into your database. Along with it a new instance
+of ``ContentType`` will be created with the following values:
+
+ * ``app_label`` will be set to ``'sites'`` (the last part of the Python
+ path "django.contrib.sites").
+
+ * ``model`` will be set to ``'site'``.
+
+ * ``name`` will be set to ``'site'``.
+
+.. _the verbose_name attribute: ../model-api/#verbose_name
+.. _the sites application: ../sites/
+
+Methods on ``ContentType`` instances
+====================================
+
+Each ``ContentType`` instance has methods that allow you to get from a
+``ContentType`` instance to the model it represents, or to retrieve objects
+from that model:
+
+ ``get_object_for_this_type(**kwargs)``
+ Takes a set of valid `lookup arguments`_ for the model the
+ ``ContentType`` represents, and does `a get() lookup`_ on that
+ model, returning the corresponding object.
+
+ ``model_class()``
+ Returns the model class represented by this ``ContentType``
+ instance.
+
+For example, we could look up the ``ContentType`` for the ``User`` model::
+
+ >>> from django.contrib.contenttypes.models import ContentType
+ >>> user_type = ContentType.objects.get(app_label="auth", model="user")
+ >>> user_type
+
+
+And then use it to query for a particular ``User``, or to get access
+to the ``User`` model class::
+
+ >>> user_type.model_class()
+
+ >>> user_type.get_object_for_this_type(username='Guido')
+
+
+Together, ``get_object_for_this_type`` and ``model_class`` enable two
+extremely important use cases:
+
+ 1. Using these methods, you can write high-level generic code that
+ performs queries on any installed model -- instead of importing and
+ using a single specific model class, you can pass an ``app_label``
+ and ``model`` into a ``ContentType`` lookup at runtime, and then
+ work with the model class or retrieve objects from it.
+
+ 2. You can relate another model to ``ContentType`` as a way of tying
+ instances of it to particular model classes, and use these methods
+ to get access to those model classes.
+
+Several of Django's bundled applications make use of the latter
+technique. For example, `the permissions system`_ in Django's
+authentication framework uses a ``Permission`` model with a foreign
+key to ``ContentType``; this lets ``Permission`` represent concepts
+like "can add blog entry" or "can delete news story".
+
+.. _lookup arguments: ../db-api/#field-lookups
+.. _a get() lookup: ../db-api/#get-kwargs
+.. _the permissions system: ../authentication/#permissions
+
+The ``ContentTypeManager``
+--------------------------
+
+``ContentType`` also has a custom manager, ``ContentTypeManager``,
+which adds the following methods:
+
+ ``clear_cache()``
+ Clears an internal cache used by ``ContentType`` to keep track of which
+ models for which it has created ``ContentType`` instances. You probably
+ won't ever need to call this method yourself; Django will call it
+ automatically when it's needed.
+
+ ``get_for_model(model)``
+ Takes either a model class or an instance of a model, and returns the
+ ``ContentType`` instance representing that model.
+
+The ``get_for_model`` method is especially useful when you know you
+need to work with a ``ContentType`` but don't want to go to the
+trouble of obtaining the model's metadata to perform a manual lookup::
+
+ >>> from django.contrib.auth.models import User
+ >>> user_type = ContentType.objects.get_for_model(User)
+ >>> user_type
+
+
+Generic relations
+=================
+
+Adding a foreign key from one of your own models to ``ContentType``
+allows your model to effectively tie itself to another model class, as
+in the example of the ``Permission`` model above. But it's possible to
+go one step further and use ``ContentType`` to enable truly generic
+(sometimes called "polymorphic") relationships between models.
+
+A simple example is a tagging system, which might look like this::
+
+ from django.db import models
+ from django.contrib.contenttypes.models import ContentType
+ from django.contrib.contenttypes import generic
+
+ class TaggedItem(models.Model):
+ tag = models.SlugField()
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ content_object = generic.GenericForeignKey('content_type', 'object_id')
+
+ def __unicode__(self):
+ return self.tag
+
+A normal ``ForeignKey`` can only "point to" one other model, which
+means that if the ``TaggedItem`` model used a ``ForeignKey`` it would have to
+choose one and only one model to store tags for. The contenttypes
+application provides a special field type --
+``django.contrib.contenttypes.generic.GenericForeignKey`` -- which
+works around this and allows the relationship to be with any
+model. There are three parts to setting up a ``GenericForeignKey``:
+
+ 1. Give your model a ``ForeignKey`` to ``ContentType``.
+
+ 2. Give your model a field that can store a primary-key value from the
+ models you'll be relating to. (For most models, this means an
+ ``IntegerField`` or ``PositiveIntegerField``.)
+
+ 3. Give your model a ``GenericForeignKey``, and pass it the names of
+ the two fields described above. If these fields are named
+ "content_type" and "object_id", you can omit this -- those are the
+ default field names ``GenericForeignKey`` will look for.
+
+This will enable an API similar to the one used for a normal ``ForeignKey``;
+each ``TaggedItem`` will have a ``content_object`` field that returns the
+object it's related to, and you can also assign to that field or use it when
+creating a ``TaggedItem``::
+
+ >>> from django.contrib.models.auth import User
+ >>> guido = User.objects.get(username='Guido')
+ >>> t = TaggedItem(content_object=guido, tag='bdfl')
+ >>> t.save()
+ >>> t.content_object
+
+
+Reverse generic relations
+-------------------------
+
+If you know which models you'll be using most often, you can also add
+a "reverse" generic relationship to enable an additional API. For example::
+
+ class Bookmark(models.Model):
+ url = models.URLField()
+ tags = generic.GenericRelation(TaggedItem)
+
+``Bookmark`` instances will each have a ``tags`` attribute, which can
+be used to retrieve their associated ``TaggedItems``::
+
+ >>> b = Bookmark('http://www.djangoproject.com/')
+ >>> b.save()
+ >>> t1 = TaggedItem(content_object=b, tag='django')
+ >>> t1.save()
+ >>> t2 = TaggedItem(content_object=b, tag='python')
+ >>> t2.save()
+ >>> b.tags.all()
+ [, ]
+
+If you don't add the reverse relationship, you can do the lookup manually::
+
+ >>> b = Bookmark.objects.get(url='http://www.djangoproject.com/)
+ >>> bookmark_type = ContentType.objects.get_for_model(b)
+ >>> TaggedItem.objects.filter(content_type__pk=bookmark_type.id,
+ ... object_id=b.id)
+ [, ]
+
+Note that if you delete an object that has a ``GenericRelation``, any objects
+which have a ``GenericForeignKey`` pointing at it will be deleted as well. In
+the example above, this means that if a ``Bookmark`` object were deleted, any
+``TaggedItem`` objects pointing at it would be deleted at the same time.
diff --git a/docs/db-api.txt b/docs/db-api.txt
index cc235b79ed..61cf2d4ffd 100644
--- a/docs/db-api.txt
+++ b/docs/db-api.txt
@@ -951,6 +951,23 @@ Example::
If you pass ``in_bulk()`` an empty list, you'll get an empty dictionary.
+``iterator()``
+~~~~~~~~~~~~
+
+Evaluates the ``QuerySet`` (by performing the query) and returns an
+`iterator`_ over the results. A ``QuerySet`` typically reads all of
+its results and instantiates all of the corresponding objects the
+first time you access it; ``iterator()`` will instead read results and
+instantiate objects in discrete chunks, yielding them one at a
+time. For a ``QuerySet`` which returns a large number of objects, this
+often results in better performance and a significant reduction in
+memory use.
+
+Note that using ``iterator()`` on a ``QuerySet`` which has already
+been evaluated will force it to evaluate again, repeating the query.
+
+.. _iterator: http://www.python.org/dev/peps/pep-0234/
+
``latest(field_name=None)``
~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/docs/model-api.txt b/docs/model-api.txt
index a8af05f676..1f0bb60285 100644
--- a/docs/model-api.txt
+++ b/docs/model-api.txt
@@ -178,7 +178,8 @@ A date field. Has a few extra optional arguments:
====================== ===================================================
The admin represents this as an ```` with a JavaScript
-calendar and a shortcut for "Today."
+calendar, and a shortcut for "Today." The JavaScript calendar will always start
+the week on a Sunday.
``DateTimeField``
~~~~~~~~~~~~~~~~~
diff --git a/docs/sessions.txt b/docs/sessions.txt
index 96a88c617a..023285f704 100644
--- a/docs/sessions.txt
+++ b/docs/sessions.txt
@@ -10,18 +10,21 @@ Cookies contain a session ID -- not the data itself.
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
``MIDDLEWARE_CLASSES`` contains ``'django.contrib.sessions.middleware.SessionMiddleware'``.
The default ``settings.py`` created by ``django-admin.py startproject`` has
``SessionMiddleware`` activated.
-
- * Add ``'django.contrib.sessions'`` to your ``INSTALLED_APPS`` setting, and
- run ``manage.py syncdb`` to install the single database table that stores
- session data.
+
+ * Add ``'django.contrib.sessions'`` to your ``INSTALLED_APPS`` setting,
+ and run ``manage.py syncdb`` to install the single database table
+ 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
``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/
+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
=======================
@@ -153,14 +194,25 @@ Here's a typical usage example::
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
model, you can access sessions using the normal Django database API::
>>> from django.contrib.sessions.models import Session
>>> s = Session.objects.get(pk='2b1189a188b44ad18c35e113ac6ceead')
>>> 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.
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:
+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
------------------
diff --git a/docs/settings.txt b/docs/settings.txt
index 97fc985175..46fdd70258 100644
--- a/docs/settings.txt
+++ b/docs/settings.txt
@@ -253,9 +253,14 @@ DATABASE_ENGINE
Default: ``''`` (Empty string)
-The database backend to use. Either ``'postgresql_psycopg2'``,
-``'postgresql'``, ``'mysql'``, ``'mysql_old'``, ``'sqlite3'``,
-``'oracle'``, or ``'ado_mssql'``.
+The database backend to use. The build-in database backends are
+``'postgresql_psycopg2'``, ``'postgresql'``, ``'mysql'``, ``'mysql_old'``,
+``'sqlite3'``, ``'oracle'``, or ``'ado_mssql'``.
+
+You can also use a database backend that doesn't ship with Django by
+setting ``DATABASE_ENGINE`` to a fully-qualified path (i.e.
+``mypackage.backends.whatever``). Writing a whole new database backend from
+scratch is left as an exercise to the reader.
DATABASE_HOST
-------------
@@ -728,6 +733,21 @@ Default: ``'root@localhost'``
The e-mail address that error messages come from, such as those sent to
``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
------------------
@@ -770,6 +790,17 @@ Default: ``False``
Whether to expire the session when the user closes his or her browser.
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
--------------------------
diff --git a/docs/templates_python.txt b/docs/templates_python.txt
index 3399639611..232f54061f 100644
--- a/docs/templates_python.txt
+++ b/docs/templates_python.txt
@@ -555,6 +555,38 @@ template loaders that come with Django:
Django uses the template loaders in order according to the ``TEMPLATE_LOADERS``
setting. It uses each loader until a loader finds a match.
+The ``render_to_string()`` shortcut
+===================================
+
+To cut down on the repetitive nature of loading and rendering
+templates, Django provides a shortcut function which largely
+automates the process: ``render_to_string()`` in
+``django.template.loader``, which loads a template, renders it and
+returns the resulting string::
+
+ from django.template.loader import render_to_string
+ rendered = render_to_string('my_template.html', { 'foo': 'bar' })
+
+The ``render_to_string`` shortcut takes one required argument --
+``template_name``, which should be the name of the template to load
+and render -- and two optional arguments::
+
+ dictionary
+ A dictionary to be used as variables and values for the
+ template's context. This can also be passed as the second
+ positional argument.
+
+ context_instance
+ An instance of ``Context`` or a subclass (e.g., an instance of
+ ``RequestContext``) to use as the template's context. This can
+ also be passed as the third positional argument.
+
+See also the `render_to_response()`_ shortcut, which calls
+``render_to_string`` and feeds the result into an ``HttpResponse``
+suitable for returning directly from a view.
+
+.. _render_to_response(): ../shortcuts/#render-to-response
+
Extending the template system
=============================