From 28a4aa6f49b11881d43a585f118a3d0537ba9084 Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Sat, 15 Sep 2007 22:00:35 +0000 Subject: [PATCH] queryset-refactor: Merged to [6340] git-svn-id: http://code.djangoproject.com/svn/django/branches/queryset-refactor@6341 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 1 + django/conf/global_settings.py | 14 +- django/contrib/auth/handlers/modpython.py | 4 + django/contrib/auth/models.py | 53 ++-- django/contrib/sessions/backends/__init__.py | 0 django/contrib/sessions/backends/base.py | 143 ++++++++++ django/contrib/sessions/backends/cache.py | 26 ++ django/contrib/sessions/backends/db.py | 49 ++++ django/contrib/sessions/backends/file.py | 67 +++++ django/contrib/sessions/middleware.py | 89 +------ django/contrib/sessions/models.py | 3 +- django/contrib/sessions/tests.py | 64 +++-- django/db/__init__.py | 53 ++-- django/test/client.py | 23 +- django/views/i18n.py | 2 +- docs/apache_auth.txt | 45 +++- docs/cache.txt | 9 + docs/contenttypes.txt | 258 +++++++++++++++++++ docs/db-api.txt | 17 ++ docs/model-api.txt | 3 +- docs/sessions.txt | 93 ++++++- docs/settings.txt | 37 ++- docs/templates_python.txt | 32 +++ 23 files changed, 918 insertions(+), 167 deletions(-) create mode 100644 django/contrib/sessions/backends/__init__.py create mode 100644 django/contrib/sessions/backends/base.py create mode 100644 django/contrib/sessions/backends/cache.py create mode 100644 django/contrib/sessions/backends/db.py create mode 100644 django/contrib/sessions/backends/file.py create mode 100644 docs/contenttypes.txt 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 =============================