mirror of
https://github.com/django/django.git
synced 2025-07-06 18:59:13 +00:00
queryset-refactor: Merged to [6340]
git-svn-id: http://code.djangoproject.com/svn/django/branches/queryset-refactor@6341 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
ca33d307de
commit
28a4aa6f49
1
AUTHORS
1
AUTHORS
@ -87,6 +87,7 @@ answer newbie questions, and generally made Django that much better:
|
|||||||
Matt Croydon <http://www.postneo.com/>
|
Matt Croydon <http://www.postneo.com/>
|
||||||
flavio.curella@gmail.com
|
flavio.curella@gmail.com
|
||||||
Jure Cuhalev <gandalf@owca.info>
|
Jure Cuhalev <gandalf@owca.info>
|
||||||
|
John D'Agostino <john.dagostino@gmail.com>
|
||||||
dackze+django@gmail.com
|
dackze+django@gmail.com
|
||||||
David Danier <goliath.mailinglist@gmx.de>
|
David Danier <goliath.mailinglist@gmx.de>
|
||||||
Dirk Datzert <dummy@habmalnefrage.de>
|
Dirk Datzert <dummy@habmalnefrage.de>
|
||||||
|
@ -277,6 +277,8 @@ SESSION_COOKIE_DOMAIN = None # A string like ".lawrence.com", or No
|
|||||||
SESSION_COOKIE_SECURE = False # Whether the session cookie should be secure (https:// only).
|
SESSION_COOKIE_SECURE = False # Whether the session cookie should be secure (https:// only).
|
||||||
SESSION_SAVE_EVERY_REQUEST = False # Whether to save the session data on every request.
|
SESSION_SAVE_EVERY_REQUEST = False # Whether to save the session data on every request.
|
||||||
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # Whether sessions expire when a user closes his browser.
|
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # Whether sessions expire when a user closes his browser.
|
||||||
|
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # The module to store session data
|
||||||
|
SESSION_FILE_PATH = '/tmp/' # Directory to store session files if using the file session module
|
||||||
|
|
||||||
#########
|
#########
|
||||||
# CACHE #
|
# CACHE #
|
||||||
|
@ -10,6 +10,10 @@ def authenhandler(req, **kwargs):
|
|||||||
# that so that the following import works
|
# that so that the following import works
|
||||||
os.environ.update(req.subprocess_env)
|
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
|
# check for PythonOptions
|
||||||
_str_to_bool = lambda s: s.lower() in ('1', 'true', 'on', 'yes')
|
_str_to_bool = lambda s: s.lower() in ('1', 'true', 'on', 'yes')
|
||||||
|
|
||||||
|
@ -15,25 +15,43 @@ try:
|
|||||||
except NameError:
|
except NameError:
|
||||||
from sets import Set as set # Python 2.3 fallback
|
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):
|
def check_password(raw_password, enc_password):
|
||||||
"""
|
"""
|
||||||
Returns a boolean of whether the raw_password was correct. Handles
|
Returns a boolean of whether the raw_password was correct. Handles
|
||||||
encryption formats behind the scenes.
|
encryption formats behind the scenes.
|
||||||
"""
|
"""
|
||||||
algo, salt, hsh = enc_password.split('$')
|
algo, salt, hsh = enc_password.split('$')
|
||||||
if algo == 'md5':
|
return hsh == get_hexdigest(algo, salt, raw_password)
|
||||||
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."
|
|
||||||
|
|
||||||
class SiteProfileNotAvailable(Exception):
|
class SiteProfileNotAvailable(Exception):
|
||||||
pass
|
pass
|
||||||
@ -162,10 +180,10 @@ class User(models.Model):
|
|||||||
return full_name.strip()
|
return full_name.strip()
|
||||||
|
|
||||||
def set_password(self, raw_password):
|
def set_password(self, raw_password):
|
||||||
import sha, random
|
import random
|
||||||
algo = 'sha1'
|
algo = 'sha1'
|
||||||
salt = sha.new(str(random.random())).hexdigest()[:5]
|
salt = get_hexdigest(algo, str(random.random()), str(random.random()))[:5]
|
||||||
hsh = sha.new(salt + smart_str(raw_password)).hexdigest()
|
hsh = get_hexdigest(algo, salt, raw_password)
|
||||||
self.password = '%s$%s$%s' % (algo, salt, hsh)
|
self.password = '%s$%s$%s' % (algo, salt, hsh)
|
||||||
|
|
||||||
def check_password(self, raw_password):
|
def check_password(self, raw_password):
|
||||||
@ -176,8 +194,7 @@ class User(models.Model):
|
|||||||
# Backwards-compatibility check. Older passwords won't include the
|
# Backwards-compatibility check. Older passwords won't include the
|
||||||
# algorithm or salt.
|
# algorithm or salt.
|
||||||
if '$' not in self.password:
|
if '$' not in self.password:
|
||||||
import md5
|
is_correct = (self.password == get_hexdigest('md5', '', raw_password))
|
||||||
is_correct = (self.password == md5.new(smart_str(raw_password)).hexdigest())
|
|
||||||
if is_correct:
|
if is_correct:
|
||||||
# Convert the password to the new, more secure format.
|
# Convert the password to the new, more secure format.
|
||||||
self.set_password(raw_password)
|
self.set_password(raw_password)
|
||||||
|
0
django/contrib/sessions/backends/__init__.py
Normal file
0
django/contrib/sessions/backends/__init__.py
Normal file
143
django/contrib/sessions/backends/base.py
Normal file
143
django/contrib/sessions/backends/base.py
Normal file
@ -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
|
||||||
|
|
26
django/contrib/sessions/backends/cache.py
Normal file
26
django/contrib/sessions/backends/cache.py
Normal file
@ -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)
|
49
django/contrib/sessions/backends/db.py
Normal file
49
django/contrib/sessions/backends/db.py
Normal file
@ -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
|
67
django/contrib/sessions/backends/file.py
Normal file
67
django/contrib/sessions/backends/file.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import os
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.sessions.backends.base import SessionBase
|
||||||
|
from django.core.exceptions import SuspiciousOperation
|
||||||
|
|
||||||
|
class SessionStore(SessionBase):
|
||||||
|
"""
|
||||||
|
Implements a file based session store.
|
||||||
|
"""
|
||||||
|
def __init__(self, session_key=None):
|
||||||
|
self.storage_path = settings.SESSION_FILE_PATH
|
||||||
|
self.file_prefix = settings.SESSION_COOKIE_NAME
|
||||||
|
super(SessionStore, self).__init__(session_key)
|
||||||
|
|
||||||
|
def _key_to_file(self, session_key=None):
|
||||||
|
"""
|
||||||
|
Get the file associated with this session key.
|
||||||
|
"""
|
||||||
|
if session_key is None:
|
||||||
|
session_key = self.session_key
|
||||||
|
|
||||||
|
# Make sure we're not vulnerable to directory traversal. Session keys
|
||||||
|
# should always be md5s, so they should never contain directory components.
|
||||||
|
if os.path.sep in session_key:
|
||||||
|
raise SuspiciousOperation("Invalid characters (directory components) in session key")
|
||||||
|
|
||||||
|
return os.path.join(self.storage_path, self.file_prefix + session_key)
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
session_data = {}
|
||||||
|
try:
|
||||||
|
session_file = open(self._key_to_file(), "rb")
|
||||||
|
try:
|
||||||
|
session_data = self.decode(session_file.read())
|
||||||
|
except(EOFError, SuspiciousOperation):
|
||||||
|
self._session_key = self._get_new_session_key()
|
||||||
|
self._session_cache = {}
|
||||||
|
self.save()
|
||||||
|
finally:
|
||||||
|
session_file.close()
|
||||||
|
except(IOError):
|
||||||
|
pass
|
||||||
|
return session_data
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
try:
|
||||||
|
f = open(self._key_to_file(self.session_key), "wb")
|
||||||
|
try:
|
||||||
|
f.write(self.encode(self._session))
|
||||||
|
finally:
|
||||||
|
f.close()
|
||||||
|
except(IOError, EOFError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def exists(self, session_key):
|
||||||
|
if os.path.exists(self._key_to_file(session_key)):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def delete(self, session_key):
|
||||||
|
try:
|
||||||
|
os.unlink(self._key_to_file(session_key))
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
pass
|
@ -1,6 +1,4 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.sessions.models import Session
|
|
||||||
from django.core.exceptions import SuspiciousOperation
|
|
||||||
from django.utils.cache import patch_vary_headers
|
from django.utils.cache import patch_vary_headers
|
||||||
from email.Utils import formatdate
|
from email.Utils import formatdate
|
||||||
import datetime
|
import datetime
|
||||||
@ -9,73 +7,11 @@ import time
|
|||||||
TEST_COOKIE_NAME = 'testcookie'
|
TEST_COOKIE_NAME = 'testcookie'
|
||||||
TEST_COOKIE_VALUE = 'worked'
|
TEST_COOKIE_VALUE = 'worked'
|
||||||
|
|
||||||
class SessionWrapper(object):
|
|
||||||
def __init__(self, session_key):
|
|
||||||
self.session_key = session_key
|
|
||||||
self.accessed = False
|
|
||||||
self.modified = False
|
|
||||||
|
|
||||||
def __contains__(self, key):
|
|
||||||
return key in self._session
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
return self._session[key]
|
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
|
||||||
self._session[key] = value
|
|
||||||
self.modified = True
|
|
||||||
|
|
||||||
def __delitem__(self, key):
|
|
||||||
del self._session[key]
|
|
||||||
self.modified = True
|
|
||||||
|
|
||||||
def keys(self):
|
|
||||||
return self._session.keys()
|
|
||||||
|
|
||||||
def items(self):
|
|
||||||
return self._session.items()
|
|
||||||
|
|
||||||
def get(self, key, default=None):
|
|
||||||
return self._session.get(key, default)
|
|
||||||
|
|
||||||
def pop(self, key, *args):
|
|
||||||
self.modified = self.modified or key in self._session
|
|
||||||
return self._session.pop(key, *args)
|
|
||||||
|
|
||||||
def set_test_cookie(self):
|
|
||||||
self[TEST_COOKIE_NAME] = TEST_COOKIE_VALUE
|
|
||||||
|
|
||||||
def test_cookie_worked(self):
|
|
||||||
return self.get(TEST_COOKIE_NAME) == TEST_COOKIE_VALUE
|
|
||||||
|
|
||||||
def delete_test_cookie(self):
|
|
||||||
del self[TEST_COOKIE_NAME]
|
|
||||||
|
|
||||||
def _get_session(self):
|
|
||||||
# Lazily loads session from storage.
|
|
||||||
self.accessed = True
|
|
||||||
try:
|
|
||||||
return self._session_cache
|
|
||||||
except AttributeError:
|
|
||||||
if self.session_key is None:
|
|
||||||
self._session_cache = {}
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
s = Session.objects.get(session_key=self.session_key,
|
|
||||||
expire_date__gt=datetime.datetime.now())
|
|
||||||
self._session_cache = s.get_decoded()
|
|
||||||
except (Session.DoesNotExist, SuspiciousOperation):
|
|
||||||
self._session_cache = {}
|
|
||||||
# Set the session_key to None to force creation of a new
|
|
||||||
# key, for extra security.
|
|
||||||
self.session_key = None
|
|
||||||
return self._session_cache
|
|
||||||
|
|
||||||
_session = property(_get_session)
|
|
||||||
|
|
||||||
class SessionMiddleware(object):
|
class SessionMiddleware(object):
|
||||||
|
|
||||||
def process_request(self, request):
|
def process_request(self, request):
|
||||||
request.session = SessionWrapper(request.COOKIES.get(settings.SESSION_COOKIE_NAME, None))
|
engine = __import__(settings.SESSION_ENGINE, {}, {}, [''])
|
||||||
|
request.session = engine.SessionStore(request.COOKIES.get(settings.SESSION_COOKIE_NAME, None))
|
||||||
|
|
||||||
def process_response(self, request, response):
|
def process_response(self, request, response):
|
||||||
# If request.session was modified, or if response.session was set, save
|
# If request.session was modified, or if response.session was set, save
|
||||||
@ -89,25 +25,22 @@ class SessionMiddleware(object):
|
|||||||
if accessed:
|
if accessed:
|
||||||
patch_vary_headers(response, ('Cookie',))
|
patch_vary_headers(response, ('Cookie',))
|
||||||
if modified or settings.SESSION_SAVE_EVERY_REQUEST:
|
if modified or settings.SESSION_SAVE_EVERY_REQUEST:
|
||||||
if request.session.session_key:
|
|
||||||
session_key = request.session.session_key
|
|
||||||
else:
|
|
||||||
obj = Session.objects.get_new_session_object()
|
|
||||||
session_key = obj.session_key
|
|
||||||
|
|
||||||
if settings.SESSION_EXPIRE_AT_BROWSER_CLOSE:
|
if settings.SESSION_EXPIRE_AT_BROWSER_CLOSE:
|
||||||
max_age = None
|
max_age = None
|
||||||
expires = None
|
expires = None
|
||||||
else:
|
else:
|
||||||
max_age = settings.SESSION_COOKIE_AGE
|
max_age = settings.SESSION_COOKIE_AGE
|
||||||
rfcdate = formatdate(time.time() + settings.SESSION_COOKIE_AGE)
|
rfcdate = formatdate(time.time() + settings.SESSION_COOKIE_AGE)
|
||||||
|
|
||||||
# Fixed length date must have '-' separation in the format
|
# Fixed length date must have '-' separation in the format
|
||||||
# DD-MMM-YYYY for compliance with Netscape cookie standard
|
# DD-MMM-YYYY for compliance with Netscape cookie standard
|
||||||
expires = (rfcdate[:7] + "-" + rfcdate[8:11]
|
expires = datetime.datetime.strftime(datetime.datetime.utcnow() + \
|
||||||
+ "-" + rfcdate[12:26] + "GMT")
|
datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE), "%a, %d-%b-%Y %H:%M:%S GMT")
|
||||||
new_session = Session.objects.save(session_key, request.session._session,
|
|
||||||
datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE))
|
# Save the seesion data and refresh the client cookie.
|
||||||
response.set_cookie(settings.SESSION_COOKIE_NAME, session_key,
|
request.session.save()
|
||||||
|
response.set_cookie(settings.SESSION_COOKIE_NAME, request.session.session_key,
|
||||||
max_age=max_age, expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
|
max_age=max_age, expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
|
||||||
secure=settings.SESSION_COOKIE_SECURE or None)
|
secure=settings.SESSION_COOKIE_SECURE or None)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import base64, md5, random, sys, datetime, os, time
|
import base64, md5, random, sys, datetime
|
||||||
import cPickle as pickle
|
import cPickle as pickle
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
@ -74,6 +74,7 @@ class Session(models.Model):
|
|||||||
session_data = models.TextField(_('session data'))
|
session_data = models.TextField(_('session data'))
|
||||||
expire_date = models.DateTimeField(_('expire date'))
|
expire_date = models.DateTimeField(_('expire date'))
|
||||||
objects = SessionManager()
|
objects = SessionManager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = 'django_session'
|
db_table = 'django_session'
|
||||||
verbose_name = _('session')
|
verbose_name = _('session')
|
||||||
|
@ -1,35 +1,59 @@
|
|||||||
r"""
|
r"""
|
||||||
>>> s = SessionWrapper(None)
|
|
||||||
|
|
||||||
Inject data into the session cache.
|
>>> from django.contrib.sessions.backends.db import SessionStore as DatabaseSession
|
||||||
>>> s._session_cache = {}
|
>>> from django.contrib.sessions.backends.cache import SessionStore as CacheSession
|
||||||
>>> s._session_cache['some key'] = 'exists'
|
>>> from django.contrib.sessions.backends.file import SessionStore as FileSession
|
||||||
|
|
||||||
>>> s.accessed
|
>>> db_session = DatabaseSession()
|
||||||
|
>>> db_session.modified
|
||||||
False
|
False
|
||||||
>>> s.modified
|
>>> db_session['cat'] = "dog"
|
||||||
False
|
>>> db_session.modified
|
||||||
|
True
|
||||||
>>> s.pop('non existant key', 'does not exist')
|
>>> db_session.pop('cat')
|
||||||
|
'dog'
|
||||||
|
>>> db_session.pop('some key', 'does not exist')
|
||||||
'does not exist'
|
'does not exist'
|
||||||
>>> s.accessed
|
>>> db_session.save()
|
||||||
|
>>> db_session.exists(db_session.session_key)
|
||||||
True
|
True
|
||||||
>>> s.modified
|
>>> db_session.delete(db_session.session_key)
|
||||||
|
>>> db_session.exists(db_session.session_key)
|
||||||
False
|
False
|
||||||
|
|
||||||
>>> s.pop('some key')
|
>>> file_session = FileSession()
|
||||||
'exists'
|
>>> file_session.modified
|
||||||
>>> s.accessed
|
False
|
||||||
|
>>> file_session['cat'] = "dog"
|
||||||
|
>>> file_session.modified
|
||||||
True
|
True
|
||||||
>>> s.modified
|
>>> file_session.pop('cat')
|
||||||
True
|
'dog'
|
||||||
|
>>> file_session.pop('some key', 'does not exist')
|
||||||
>>> s.pop('some key', 'does not exist')
|
|
||||||
'does not exist'
|
'does not exist'
|
||||||
|
>>> file_session.save()
|
||||||
|
>>> file_session.exists(file_session.session_key)
|
||||||
|
True
|
||||||
|
>>> file_session.delete(file_session.session_key)
|
||||||
|
>>> file_session.exists(file_session.session_key)
|
||||||
|
False
|
||||||
|
|
||||||
|
>>> cache_session = CacheSession()
|
||||||
|
>>> cache_session.modified
|
||||||
|
False
|
||||||
|
>>> cache_session['cat'] = "dog"
|
||||||
|
>>> cache_session.modified
|
||||||
|
True
|
||||||
|
>>> cache_session.pop('cat')
|
||||||
|
'dog'
|
||||||
|
>>> cache_session.pop('some key', 'does not exist')
|
||||||
|
'does not exist'
|
||||||
|
>>> cache_session.save()
|
||||||
|
>>> cache_session.delete(cache_session.session_key)
|
||||||
|
>>> cache_session.exists(cache_session.session_key)
|
||||||
|
False
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.contrib.sessions.middleware import SessionWrapper
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import doctest
|
import doctest
|
||||||
doctest.testmod()
|
doctest.testmod()
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
|
import os
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core import signals
|
from django.core import signals
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.dispatch import dispatcher
|
from django.dispatch import dispatcher
|
||||||
|
from django.utils.functional import curry
|
||||||
|
|
||||||
__all__ = ('backend', 'connection', 'DatabaseError', 'IntegrityError')
|
__all__ = ('backend', 'connection', 'DatabaseError', 'IntegrityError')
|
||||||
|
|
||||||
@ -8,12 +11,19 @@ if not settings.DATABASE_ENGINE:
|
|||||||
settings.DATABASE_ENGINE = 'dummy'
|
settings.DATABASE_ENGINE = 'dummy'
|
||||||
|
|
||||||
try:
|
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:
|
except ImportError, e:
|
||||||
|
# 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
|
# The database backend wasn't found. Display a helpful error message
|
||||||
# listing all possible database backends.
|
# listing all possible (built-in) database backends.
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
|
||||||
import os
|
|
||||||
backend_dir = os.path.join(__path__[0], '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 = [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()
|
available_backends.sort()
|
||||||
@ -23,10 +33,21 @@ except ImportError, e:
|
|||||||
else:
|
else:
|
||||||
raise # If there's some other error, this must be an error in Django itself.
|
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, {}, {}, [''])
|
def _import_database_module(import_path='', module_name=''):
|
||||||
get_creation_module = lambda: __import__('django.db.backends.%s.creation' % settings.DATABASE_ENGINE, {}, {}, [''])
|
"""Lazyily import a database module when requested."""
|
||||||
runshell = lambda: __import__('django.db.backends.%s.client' % settings.DATABASE_ENGINE, {}, {}, ['']).runshell()
|
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)
|
connection = backend.DatabaseWrapper(**settings.DATABASE_OPTIONS)
|
||||||
DatabaseError = backend.DatabaseError
|
DatabaseError = backend.DatabaseError
|
||||||
IntegrityError = backend.IntegrityError
|
IntegrityError = backend.IntegrityError
|
||||||
|
@ -4,8 +4,6 @@ from cStringIO import StringIO
|
|||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import authenticate, login
|
from django.contrib.auth import authenticate, login
|
||||||
from django.contrib.sessions.models import Session
|
|
||||||
from django.contrib.sessions.middleware import SessionWrapper
|
|
||||||
from django.core.handlers.base import BaseHandler
|
from django.core.handlers.base import BaseHandler
|
||||||
from django.core.handlers.wsgi import WSGIRequest
|
from django.core.handlers.wsgi import WSGIRequest
|
||||||
from django.core.signals import got_request_exception
|
from django.core.signals import got_request_exception
|
||||||
@ -132,9 +130,10 @@ class Client:
|
|||||||
def _session(self):
|
def _session(self):
|
||||||
"Obtain the current session variables"
|
"Obtain the current session variables"
|
||||||
if 'django.contrib.sessions' in settings.INSTALLED_APPS:
|
if 'django.contrib.sessions' in settings.INSTALLED_APPS:
|
||||||
|
engine = __import__(settings.SESSION_ENGINE, {}, {}, [''])
|
||||||
cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None)
|
cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None)
|
||||||
if cookie:
|
if cookie:
|
||||||
return SessionWrapper(cookie.value)
|
return engine.SessionStore(cookie.value)
|
||||||
return {}
|
return {}
|
||||||
session = property(_session)
|
session = property(_session)
|
||||||
|
|
||||||
@ -247,24 +246,23 @@ class Client:
|
|||||||
"""
|
"""
|
||||||
user = authenticate(**credentials)
|
user = authenticate(**credentials)
|
||||||
if user and user.is_active and 'django.contrib.sessions' in settings.INSTALLED_APPS:
|
if user and user.is_active and 'django.contrib.sessions' in settings.INSTALLED_APPS:
|
||||||
obj = Session.objects.get_new_session_object()
|
engine = __import__(settings.SESSION_ENGINE, {}, {}, [''])
|
||||||
|
|
||||||
# Create a fake request to store login details
|
# Create a fake request to store login details
|
||||||
request = HttpRequest()
|
request = HttpRequest()
|
||||||
request.session = SessionWrapper(obj.session_key)
|
request.session = engine.SessionStore()
|
||||||
login(request, user)
|
login(request, user)
|
||||||
|
|
||||||
# Set the cookie to represent the session
|
# Set the cookie to represent the session
|
||||||
self.cookies[settings.SESSION_COOKIE_NAME] = obj.session_key
|
self.cookies[settings.SESSION_COOKIE_NAME] = request.session.session_key
|
||||||
self.cookies[settings.SESSION_COOKIE_NAME]['max-age'] = None
|
self.cookies[settings.SESSION_COOKIE_NAME]['max-age'] = None
|
||||||
self.cookies[settings.SESSION_COOKIE_NAME]['path'] = '/'
|
self.cookies[settings.SESSION_COOKIE_NAME]['path'] = '/'
|
||||||
self.cookies[settings.SESSION_COOKIE_NAME]['domain'] = settings.SESSION_COOKIE_DOMAIN
|
self.cookies[settings.SESSION_COOKIE_NAME]['domain'] = settings.SESSION_COOKIE_DOMAIN
|
||||||
self.cookies[settings.SESSION_COOKIE_NAME]['secure'] = settings.SESSION_COOKIE_SECURE or None
|
self.cookies[settings.SESSION_COOKIE_NAME]['secure'] = settings.SESSION_COOKIE_SECURE or None
|
||||||
self.cookies[settings.SESSION_COOKIE_NAME]['expires'] = None
|
self.cookies[settings.SESSION_COOKIE_NAME]['expires'] = None
|
||||||
|
|
||||||
# Set the session values
|
# Save the session values
|
||||||
Session.objects.save(obj.session_key, request.session._session,
|
request.session.save()
|
||||||
datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE))
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
@ -275,9 +273,6 @@ class Client:
|
|||||||
|
|
||||||
Causes the authenticated user to be logged out.
|
Causes the authenticated user to be logged out.
|
||||||
"""
|
"""
|
||||||
try:
|
session = __import__(settings.SESSION_ENGINE, {}, {}, ['']).SessionStore()
|
||||||
Session.objects.get(session_key=self.cookies['sessionid'].value).delete()
|
session.delete(session_key=self.cookies['sessionid'].value)
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
self.cookies = SimpleCookie()
|
self.cookies = SimpleCookie()
|
||||||
|
@ -16,7 +16,7 @@ def set_language(request):
|
|||||||
redirect to the page in the request (the 'next' parameter) without changing
|
redirect to the page in the request (the 'next' parameter) without changing
|
||||||
any state.
|
any state.
|
||||||
"""
|
"""
|
||||||
next = request.GET.get('next', None)
|
next = request.REQUEST.get('next', None)
|
||||||
if not next:
|
if not next:
|
||||||
next = request.META.get('HTTP_REFERER', None)
|
next = request.META.get('HTTP_REFERER', None)
|
||||||
if not next:
|
if not next:
|
||||||
|
@ -21,7 +21,7 @@ file, you'll need to use mod_python's ``PythonAuthenHandler`` directive along
|
|||||||
with the standard ``Auth*`` and ``Require`` directives::
|
with the standard ``Auth*`` and ``Require`` directives::
|
||||||
|
|
||||||
<Location /example/>
|
<Location /example/>
|
||||||
AuthType basic
|
AuthType Basic
|
||||||
AuthName "example.com"
|
AuthName "example.com"
|
||||||
Require valid-user
|
Require valid-user
|
||||||
|
|
||||||
@ -29,6 +29,49 @@ with the standard ``Auth*`` and ``Require`` directives::
|
|||||||
PythonAuthenHandler django.contrib.auth.handlers.modpython
|
PythonAuthenHandler django.contrib.auth.handlers.modpython
|
||||||
</Location>
|
</Location>
|
||||||
|
|
||||||
|
.. 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**
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
<Location /exmaple/>
|
||||||
|
AuthType Basic
|
||||||
|
AuthName "example.com"
|
||||||
|
**AuthBasicAuthoritative Off**
|
||||||
|
Require valid-user
|
||||||
|
|
||||||
|
SetEnv DJANGO_SETTINGS_MODULE mysite.settings
|
||||||
|
PythonAuthenHandler django.contrib.auth.handlers.modpython
|
||||||
|
</Location>
|
||||||
|
|
||||||
By default, the authentication handler will limit access to the ``/example/``
|
By default, the authentication handler will limit access to the ``/example/``
|
||||||
location to users marked as staff members. You can use a set of
|
location to users marked as staff members. You can use a set of
|
||||||
``PythonOption`` directives to modify this behavior:
|
``PythonOption`` directives to modify this behavior:
|
||||||
|
@ -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
|
``max_age`` in a ``cache_control`` decorator, the decorator will take
|
||||||
precedence, and the header values will be merged correctly.)
|
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
|
.. _`Cache-Control spec`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
|
||||||
|
|
||||||
Other optimizations
|
Other optimizations
|
||||||
|
258
docs/contenttypes.txt
Normal file
258
docs/contenttypes.txt
Normal file
@ -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
|
||||||
|
<ContentType: user>
|
||||||
|
|
||||||
|
And then use it to query for a particular ``User``, or to get access
|
||||||
|
to the ``User`` model class::
|
||||||
|
|
||||||
|
>>> user_type.model_class()
|
||||||
|
<class 'django.contrib.auth.models.User'>
|
||||||
|
>>> user_type.get_object_for_this_type(username='Guido')
|
||||||
|
<User: 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
|
||||||
|
<ContentType: user>
|
||||||
|
|
||||||
|
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
|
||||||
|
<User: Guido>
|
||||||
|
|
||||||
|
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()
|
||||||
|
[<TaggedItem: django>, <TaggedItem: python>]
|
||||||
|
|
||||||
|
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)
|
||||||
|
[<TaggedItem: django>, <TaggedItem: python>]
|
||||||
|
|
||||||
|
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.
|
@ -951,6 +951,23 @@ Example::
|
|||||||
|
|
||||||
If you pass ``in_bulk()`` an empty list, you'll get an empty dictionary.
|
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)``
|
``latest(field_name=None)``
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -178,7 +178,8 @@ A date field. Has a few extra optional arguments:
|
|||||||
====================== ===================================================
|
====================== ===================================================
|
||||||
|
|
||||||
The admin represents this as an ``<input type="text">`` with a JavaScript
|
The admin represents this as an ``<input type="text">`` 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``
|
``DateTimeField``
|
||||||
~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~
|
||||||
|
@ -10,18 +10,21 @@ Cookies contain a session ID -- not the data itself.
|
|||||||
Enabling sessions
|
Enabling sessions
|
||||||
=================
|
=================
|
||||||
|
|
||||||
Sessions are implemented via a piece of middleware_ and a Django model.
|
Sessions are implemented via a piece of middleware_.
|
||||||
|
|
||||||
To enable session functionality, do these two things:
|
To enable session functionality, do the following:
|
||||||
|
|
||||||
* Edit the ``MIDDLEWARE_CLASSES`` setting and make sure
|
* Edit the ``MIDDLEWARE_CLASSES`` setting and make sure
|
||||||
``MIDDLEWARE_CLASSES`` contains ``'django.contrib.sessions.middleware.SessionMiddleware'``.
|
``MIDDLEWARE_CLASSES`` contains ``'django.contrib.sessions.middleware.SessionMiddleware'``.
|
||||||
The default ``settings.py`` created by ``django-admin.py startproject`` has
|
The default ``settings.py`` created by ``django-admin.py startproject`` has
|
||||||
``SessionMiddleware`` activated.
|
``SessionMiddleware`` activated.
|
||||||
|
|
||||||
* Add ``'django.contrib.sessions'`` to your ``INSTALLED_APPS`` setting, and
|
* Add ``'django.contrib.sessions'`` to your ``INSTALLED_APPS`` setting,
|
||||||
run ``manage.py syncdb`` to install the single database table that stores
|
and run ``manage.py syncdb`` to install the single database table
|
||||||
session data.
|
that stores session data.
|
||||||
|
|
||||||
|
**New in development version**: this step is optional if you're not using
|
||||||
|
the database session backend; see `configuring the session engine`_.
|
||||||
|
|
||||||
If you don't want to use sessions, you might as well remove the
|
If you don't want to use sessions, you might as well remove the
|
||||||
``SessionMiddleware`` line from ``MIDDLEWARE_CLASSES`` and ``'django.contrib.sessions'``
|
``SessionMiddleware`` line from ``MIDDLEWARE_CLASSES`` and ``'django.contrib.sessions'``
|
||||||
@ -29,6 +32,44 @@ from your ``INSTALLED_APPS``. It'll save you a small bit of overhead.
|
|||||||
|
|
||||||
.. _middleware: ../middleware/
|
.. _middleware: ../middleware/
|
||||||
|
|
||||||
|
Configuring the session engine
|
||||||
|
==============================
|
||||||
|
|
||||||
|
**New in development version**.
|
||||||
|
|
||||||
|
By default, Django stores sessions in your database (using the model
|
||||||
|
``django.contrib.sessions.models.Session``). Though this is convenient, in
|
||||||
|
some setups it's faster to store session data elsewhere, so Django can be
|
||||||
|
configured to store session data on your filesystem or in your cache.
|
||||||
|
|
||||||
|
Using file-based sessions
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
To use file-based sessions, set the ``SESSION_ENGINE`` setting to
|
||||||
|
``"django.contrib.sessions.backends.file"``.
|
||||||
|
|
||||||
|
You might also want to set the ``SESSION_FILE_PATH`` setting (which
|
||||||
|
defaults to ``/tmp``) to control where Django stores session files. Be
|
||||||
|
sure to check that your web server has permissions to read and write to
|
||||||
|
this location.
|
||||||
|
|
||||||
|
Using cache-based sessions
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
To store session data using Django's cache system, set ``SESSION_ENGINE``
|
||||||
|
to ``"django.contrib.sessions.backends.cache"``. You'll want to make sure
|
||||||
|
you've configured your cache; see the `cache documentation`_ for details.
|
||||||
|
|
||||||
|
.. _cache documentation: ../cache/
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
You probably don't want to use cache-based sessions if you're not using
|
||||||
|
the memcached cache backend. The local memory and simple cache backends
|
||||||
|
don't retain data long enough to be good choices, and it'll be faster
|
||||||
|
to use file or database sessions directly instead of sending everything
|
||||||
|
through the file or database cache backends.
|
||||||
|
|
||||||
Using sessions in views
|
Using sessions in views
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
@ -153,7 +194,18 @@ Here's a typical usage example::
|
|||||||
Using sessions out of views
|
Using sessions out of views
|
||||||
===========================
|
===========================
|
||||||
|
|
||||||
Internally, each session is just a normal Django model. The ``Session`` model
|
The ``SessionStore`` which implements the session storage method can be imported
|
||||||
|
and a API is available to manipulate the session data outside of a view::
|
||||||
|
|
||||||
|
>>> from django.contrib.sessions.engines.db import SessionStore
|
||||||
|
>>> s = SessionStore(session_key='2b1189a188b44ad18c35e113ac6ceead')
|
||||||
|
>>> s['last_login'] = datetime.datetime(2005, 8, 20, 13, 35, 10)
|
||||||
|
>>> s['last_login']
|
||||||
|
datetime.datetime(2005, 8, 20, 13, 35, 0)
|
||||||
|
>>> s.save()
|
||||||
|
|
||||||
|
Or if you are using the ``django.contrib.sessions.engine.db`` each
|
||||||
|
session is just a normal Django model. The ``Session`` model
|
||||||
is defined in ``django/contrib/sessions/models.py``. Because it's a normal
|
is defined in ``django/contrib/sessions/models.py``. Because it's a normal
|
||||||
model, you can access sessions using the normal Django database API::
|
model, you can access sessions using the normal Django database API::
|
||||||
|
|
||||||
@ -245,6 +297,31 @@ Settings
|
|||||||
|
|
||||||
A few `Django settings`_ give you control over session behavior:
|
A few `Django settings`_ give you control over session behavior:
|
||||||
|
|
||||||
|
SESSION_ENGINE
|
||||||
|
--------------
|
||||||
|
|
||||||
|
**New in Django development version**
|
||||||
|
|
||||||
|
Default: ``django.contrib.sessions.backends.db``
|
||||||
|
|
||||||
|
Controls where Django stores session data. Valid values are:
|
||||||
|
|
||||||
|
* ``'django.contrib.sessions.backends.db'``
|
||||||
|
* ``'django.contrib.sessions.backends.file'``
|
||||||
|
* ``'django.contrib.sessions.backends.cache'``
|
||||||
|
|
||||||
|
See `configuring the session engine`_ for more details.
|
||||||
|
|
||||||
|
SESSION_FILE_PATH
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
**New in Django development version**
|
||||||
|
|
||||||
|
Default: ``/tmp/``
|
||||||
|
|
||||||
|
If you're using file-based session storage, this sets the directory in
|
||||||
|
which Django will store session data.
|
||||||
|
|
||||||
SESSION_COOKIE_AGE
|
SESSION_COOKIE_AGE
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
@ -253,9 +253,14 @@ DATABASE_ENGINE
|
|||||||
|
|
||||||
Default: ``''`` (Empty string)
|
Default: ``''`` (Empty string)
|
||||||
|
|
||||||
The database backend to use. Either ``'postgresql_psycopg2'``,
|
The database backend to use. The build-in database backends are
|
||||||
``'postgresql'``, ``'mysql'``, ``'mysql_old'``, ``'sqlite3'``,
|
``'postgresql_psycopg2'``, ``'postgresql'``, ``'mysql'``, ``'mysql_old'``,
|
||||||
``'oracle'``, or ``'ado_mssql'``.
|
``'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
|
DATABASE_HOST
|
||||||
-------------
|
-------------
|
||||||
@ -728,6 +733,21 @@ Default: ``'root@localhost'``
|
|||||||
The e-mail address that error messages come from, such as those sent to
|
The e-mail address that error messages come from, such as those sent to
|
||||||
``ADMINS`` and ``MANAGERS``.
|
``ADMINS`` and ``MANAGERS``.
|
||||||
|
|
||||||
|
SESSION_ENGINE
|
||||||
|
--------------
|
||||||
|
|
||||||
|
**New in Django development version**
|
||||||
|
|
||||||
|
Default: ``django.contrib.sessions.backends.db``
|
||||||
|
|
||||||
|
Controls where Django stores session data. Valid values are:
|
||||||
|
|
||||||
|
* ``'django.contrib.sessions.backends.db'``
|
||||||
|
* ``'django.contrib.sessions.backends.file'``
|
||||||
|
* ``'django.contrib.sessions.backends.cache'``
|
||||||
|
|
||||||
|
See the `session docs`_ for more details.
|
||||||
|
|
||||||
SESSION_COOKIE_AGE
|
SESSION_COOKIE_AGE
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
@ -770,6 +790,17 @@ Default: ``False``
|
|||||||
Whether to expire the session when the user closes his or her browser.
|
Whether to expire the session when the user closes his or her browser.
|
||||||
See the `session docs`_.
|
See the `session docs`_.
|
||||||
|
|
||||||
|
SESSION_FILE_PATH
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
**New in Django development version**
|
||||||
|
|
||||||
|
Default: ``/tmp/``
|
||||||
|
|
||||||
|
If you're using file-based session storage, this sets the directory in
|
||||||
|
which Django will store session data. See the `session docs`_ for
|
||||||
|
more details.
|
||||||
|
|
||||||
SESSION_SAVE_EVERY_REQUEST
|
SESSION_SAVE_EVERY_REQUEST
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
|
@ -555,6 +555,38 @@ template loaders that come with Django:
|
|||||||
Django uses the template loaders in order according to the ``TEMPLATE_LOADERS``
|
Django uses the template loaders in order according to the ``TEMPLATE_LOADERS``
|
||||||
setting. It uses each loader until a loader finds a match.
|
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
|
Extending the template system
|
||||||
=============================
|
=============================
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user