mirror of
https://github.com/django/django.git
synced 2025-04-21 07:44:36 +00:00
i18n: merged r787:r814 from trunk
git-svn-id: http://code.djangoproject.com/svn/django/branches/i18n@815 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
4fc6d40d00
commit
eb7ebb777c
@ -53,11 +53,15 @@ def main():
|
||||
parser = DjangoOptionParser(get_usage())
|
||||
parser.add_option('--settings',
|
||||
help='Python path to settings module, e.g. "myproject.settings.main". If this isn\'t provided, the DJANGO_SETTINGS_MODULE environment variable will be used.')
|
||||
parser.add_option('--pythonpath',
|
||||
help='Lets you manually add a directory the Python path, e.g. "/home/djangoprojects/myproject".')
|
||||
options, args = parser.parse_args()
|
||||
|
||||
# Take care of options.
|
||||
if options.settings:
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = options.settings
|
||||
if options.pythonpath:
|
||||
sys.path.insert(0, options.pythonpath)
|
||||
|
||||
# Run the appropriate action. Unfortunately, optparse can't handle
|
||||
# positional arguments, so this has to parse/validate them.
|
||||
|
@ -136,6 +136,8 @@ JING_PATH = "/usr/bin/jing"
|
||||
# response phase the middleware will be applied in reverse order.
|
||||
MIDDLEWARE_CLASSES = (
|
||||
"django.middleware.sessions.SessionMiddleware",
|
||||
# "django.middleware.http.ConditionalGetMiddleware",
|
||||
# "django.middleware.gzip.GZipMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.doc.XViewMiddleware",
|
||||
)
|
||||
@ -155,6 +157,7 @@ SESSION_COOKIE_DOMAIN = None # A string like ".lawrence.com", or No
|
||||
# The cache backend to use. See the docstring in django.core.cache for the
|
||||
# possible values.
|
||||
CACHE_BACKEND = 'simple://'
|
||||
CACHE_MIDDLEWARE_KEY_PREFIX = ''
|
||||
|
||||
####################
|
||||
# COMMENTS #
|
||||
|
@ -21,6 +21,32 @@ django_conversions.update({
|
||||
FIELD_TYPE.TIME: typecasts.typecast_time,
|
||||
})
|
||||
|
||||
# This is an extra debug layer over MySQL queries, to display warnings.
|
||||
# It's only used when DEBUG=True.
|
||||
class MysqlDebugWrapper:
|
||||
def __init__(self, cursor):
|
||||
self.cursor = cursor
|
||||
|
||||
def execute(self, sql, params=()):
|
||||
try:
|
||||
return self.cursor.execute(sql, params)
|
||||
except Database.Warning, w:
|
||||
self.cursor.execute("SHOW WARNINGS")
|
||||
raise Database.Warning, "%s: %s" % (w, self.cursor.fetchall())
|
||||
|
||||
def executemany(self, sql, param_list):
|
||||
try:
|
||||
return self.cursor.executemany(sql, param_list)
|
||||
except Database.Warning:
|
||||
self.cursor.execute("SHOW WARNINGS")
|
||||
raise Database.Warning, "%s: %s" % (w, self.cursor.fetchall())
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if self.__dict__.has_key(attr):
|
||||
return self.__dict__[attr]
|
||||
else:
|
||||
return getattr(self.cursor, attr)
|
||||
|
||||
class DatabaseWrapper:
|
||||
def __init__(self):
|
||||
self.connection = None
|
||||
@ -32,7 +58,7 @@ class DatabaseWrapper:
|
||||
self.connection = Database.connect(user=DATABASE_USER, db=DATABASE_NAME,
|
||||
passwd=DATABASE_PASSWORD, host=DATABASE_HOST, conv=django_conversions)
|
||||
if DEBUG:
|
||||
return base.CursorDebugWrapper(self.connection.cursor(), self)
|
||||
return base.CursorDebugWrapper(MysqlDebugWrapper(self.connection.cursor()), self)
|
||||
return self.connection.cursor()
|
||||
|
||||
def commit(self):
|
||||
|
@ -16,8 +16,8 @@ APP_ARGS = '[modelmodule ...]'
|
||||
|
||||
# Use django.__path__[0] because we don't know which directory django into
|
||||
# which has been installed.
|
||||
PROJECT_TEMPLATE_DIR = os.path.join(django.__path__[0], 'conf/%s_template')
|
||||
ADMIN_TEMPLATE_DIR = os.path.join(django.__path__[0], 'conf/admin_templates')
|
||||
PROJECT_TEMPLATE_DIR = os.path.join(django.__path__[0], 'conf', '%s_template')
|
||||
ADMIN_TEMPLATE_DIR = os.path.join(django.__path__[0], 'conf', 'admin_templates')
|
||||
|
||||
def _get_packages_insert(app_label):
|
||||
return "INSERT INTO packages (label, name) VALUES ('%s', '%s');" % (app_label, app_label)
|
||||
@ -160,7 +160,7 @@ def get_sql_initial_data(mod):
|
||||
output = []
|
||||
app_label = mod._MODELS[0]._meta.app_label
|
||||
output.append(_get_packages_insert(app_label))
|
||||
app_dir = os.path.normpath(os.path.join(os.path.dirname(mod.__file__), '../sql'))
|
||||
app_dir = os.path.normpath(os.path.join(os.path.dirname(mod.__file__), '..', 'sql'))
|
||||
for klass in mod._MODELS:
|
||||
opts = klass._meta
|
||||
|
||||
@ -376,14 +376,14 @@ def startproject(project_name, directory):
|
||||
_start_helper('project', project_name, directory)
|
||||
# Populate TEMPLATE_DIRS for the admin templates, based on where Django is
|
||||
# installed.
|
||||
admin_settings_file = os.path.join(directory, project_name, 'settings/admin.py')
|
||||
admin_settings_file = os.path.join(directory, project_name, 'settings', 'admin.py')
|
||||
settings_contents = open(admin_settings_file, 'r').read()
|
||||
fp = open(admin_settings_file, 'w')
|
||||
settings_contents = re.sub(r'(?s)\b(TEMPLATE_DIRS\s*=\s*\()(.*?)\)', "\\1\n r%r,\\2)" % ADMIN_TEMPLATE_DIR, settings_contents)
|
||||
fp.write(settings_contents)
|
||||
fp.close()
|
||||
# Create a random SECRET_KEY hash, and put it in the main settings.
|
||||
main_settings_file = os.path.join(directory, project_name, 'settings/main.py')
|
||||
main_settings_file = os.path.join(directory, project_name, 'settings', 'main.py')
|
||||
settings_contents = open(main_settings_file, 'r').read()
|
||||
fp = open(main_settings_file, 'w')
|
||||
secret_key = ''.join([choice('abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)') for i in range(50)])
|
||||
@ -397,7 +397,7 @@ def startapp(app_name, directory):
|
||||
"Creates a Django app for the given app_name in the given directory."
|
||||
# Determine the project_name a bit naively -- by looking at the name of
|
||||
# the parent directory.
|
||||
project_dir = os.path.normpath(os.path.join(directory, '../'))
|
||||
project_dir = os.path.normpath(os.path.join(directory, '..'))
|
||||
project_name = os.path.basename(project_dir)
|
||||
_start_helper('app', app_name, directory, project_name)
|
||||
startapp.help_doc = "Creates a Django app directory structure for the given app name in the current directory."
|
||||
|
@ -1332,16 +1332,19 @@ def function_get_sql_clause(opts, **kwargs):
|
||||
if f == '?': # Special case.
|
||||
order_by.append(db.get_random_function_sql())
|
||||
else:
|
||||
if f.startswith('-'):
|
||||
col_name = f[1:]
|
||||
order = "DESC"
|
||||
else:
|
||||
col_name = f
|
||||
order = "ASC"
|
||||
# Use the database table as a column prefix if it wasn't given,
|
||||
# and if the requested column isn't a custom SELECT.
|
||||
if "." not in f and f not in [k[0] for k in kwargs.get('select', [])]:
|
||||
if "." not in col_name and col_name not in [k[0] for k in kwargs.get('select', [])]:
|
||||
table_prefix = opts.db_table + '.'
|
||||
else:
|
||||
table_prefix = ''
|
||||
if f.startswith('-'):
|
||||
order_by.append('%s%s DESC' % (table_prefix, orderfield2column(f[1:], opts)))
|
||||
else:
|
||||
order_by.append('%s%s ASC' % (table_prefix, orderfield2column(f, opts)))
|
||||
order_by.append('%s%s %s' % (table_prefix, orderfield2column(col_name, opts), order))
|
||||
order_by = ", ".join(order_by)
|
||||
|
||||
# LIMIT and OFFSET clauses
|
||||
|
@ -596,7 +596,11 @@ class ForeignKey(Field):
|
||||
Field.__init__(self, **kwargs)
|
||||
|
||||
def get_manipulator_field_objs(self):
|
||||
return [formfields.IntegerField]
|
||||
rel_field = self.rel.get_related_field()
|
||||
if self.rel.raw_id_admin and not isinstance(rel_field, AutoField):
|
||||
return rel_field.get_manipulator_field_objs()
|
||||
else:
|
||||
return [formfields.IntegerField]
|
||||
|
||||
class ManyToManyField(Field):
|
||||
def __init__(self, to, **kwargs):
|
||||
|
@ -1,88 +1,70 @@
|
||||
import copy
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.utils.cache import get_cache_key, learn_cache_key, patch_response_headers
|
||||
from django.utils.httpwrappers import HttpResponseNotModified
|
||||
from django.utils.text import compress_string
|
||||
import datetime, md5
|
||||
|
||||
class CacheMiddleware:
|
||||
"""
|
||||
Cache middleware. If this is enabled, each Django-powered page will be
|
||||
cached for CACHE_MIDDLEWARE_SECONDS seconds. Cache is based on URLs. Pages
|
||||
with GET or POST parameters are not cached.
|
||||
cached for CACHE_MIDDLEWARE_SECONDS seconds. Cache is based on URLs.
|
||||
|
||||
If the cache is shared across multiple sites using the same Django
|
||||
installation, set the CACHE_MIDDLEWARE_KEY_PREFIX to the name of the site,
|
||||
or some other string that is unique to this Django instance, to prevent key
|
||||
collisions.
|
||||
Only parameter-less GET or HEAD-requests with status code 200 are cached.
|
||||
|
||||
This middleware will also make the following optimizations:
|
||||
This middleware expects that a HEAD request is answered with a response
|
||||
exactly like the corresponding GET request.
|
||||
|
||||
* If the CACHE_MIDDLEWARE_GZIP setting is True, the content will be
|
||||
gzipped.
|
||||
When a hit occurs, a shallow copy of the original response object is
|
||||
returned from process_request.
|
||||
|
||||
* ETags will be added, using a simple MD5 hash of the page's content.
|
||||
Pages will be cached based on the contents of the request headers
|
||||
listed in the response's "Vary" header. This means that pages shouldn't
|
||||
change their "Vary" header.
|
||||
|
||||
This middleware also sets ETag, Last-Modified, Expires and Cache-Control
|
||||
headers on the response object.
|
||||
"""
|
||||
def __init__(self, cache_timeout=None, key_prefix=None):
|
||||
self.cache_timeout = cache_timeout
|
||||
if cache_timeout is None:
|
||||
self.cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
|
||||
self.key_prefix = key_prefix
|
||||
if key_prefix is None:
|
||||
self.key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
|
||||
|
||||
def process_request(self, request):
|
||||
"""
|
||||
Checks whether the page is already cached. If it is, returns the cached
|
||||
version. Also handles ETag stuff.
|
||||
"""
|
||||
if request.GET or request.POST:
|
||||
request._cache_middleware_set_cache = False
|
||||
"Checks whether the page is already cached and returns the cached version if available."
|
||||
if not request.META['REQUEST_METHOD'] in ('GET', 'HEAD') or request.GET:
|
||||
request._cache_update_cache = False
|
||||
return None # Don't bother checking the cache.
|
||||
|
||||
accept_encoding = ''
|
||||
if settings.CACHE_MIDDLEWARE_GZIP:
|
||||
try:
|
||||
accept_encoding = request.META['HTTP_ACCEPT_ENCODING']
|
||||
except KeyError:
|
||||
pass
|
||||
accepts_gzip = 'gzip' in accept_encoding
|
||||
request._cache_middleware_accepts_gzip = accepts_gzip
|
||||
|
||||
# This uses the same cache_key as views.decorators.cache.cache_page,
|
||||
# so the cache can be shared.
|
||||
cache_key = 'views.decorators.cache.cache_page.%s.%s.%s' % \
|
||||
(settings.CACHE_MIDDLEWARE_KEY_PREFIX, request.path, accepts_gzip)
|
||||
request._cache_middleware_key = cache_key
|
||||
cache_key = get_cache_key(request, self.key_prefix)
|
||||
if cache_key is None:
|
||||
request._cache_update_cache = True
|
||||
return None # No cache information available, need to rebuild.
|
||||
|
||||
response = cache.get(cache_key, None)
|
||||
if response is None:
|
||||
request._cache_middleware_set_cache = True
|
||||
return None
|
||||
else:
|
||||
request._cache_middleware_set_cache = False
|
||||
# Logic is from http://simon.incutio.com/archive/2003/04/23/conditionalGet
|
||||
try:
|
||||
if_none_match = request.META['HTTP_IF_NONE_MATCH']
|
||||
except KeyError:
|
||||
if_none_match = None
|
||||
try:
|
||||
if_modified_since = request.META['HTTP_IF_MODIFIED_SINCE']
|
||||
except KeyError:
|
||||
if_modified_since = None
|
||||
if if_none_match is None and if_modified_since is None:
|
||||
pass
|
||||
elif if_none_match is not None and response['ETag'] != if_none_match:
|
||||
pass
|
||||
elif if_modified_since is not None and response['Last-Modified'] != if_modified_since:
|
||||
pass
|
||||
else:
|
||||
return HttpResponseNotModified()
|
||||
return response
|
||||
request._cache_update_cache = True
|
||||
return None # No cache information available, need to rebuild.
|
||||
|
||||
request._cache_update_cache = False
|
||||
return copy.copy(response)
|
||||
|
||||
def process_response(self, request, response):
|
||||
"""
|
||||
Sets the cache, if needed.
|
||||
"""
|
||||
if request._cache_middleware_set_cache:
|
||||
content = response.get_content_as_string(settings.DEFAULT_CHARSET)
|
||||
if request._cache_middleware_accepts_gzip:
|
||||
content = compress_string(content)
|
||||
response.content = content
|
||||
response['Content-Encoding'] = 'gzip'
|
||||
response['ETag'] = md5.new(content).hexdigest()
|
||||
response['Content-Length'] = '%d' % len(content)
|
||||
response['Last-Modified'] = datetime.datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT')
|
||||
cache.set(request._cache_middleware_key, response, settings.CACHE_MIDDLEWARE_SECONDS)
|
||||
"Sets the cache, if needed."
|
||||
if not request._cache_update_cache:
|
||||
# We don't need to update the cache, just return.
|
||||
return response
|
||||
if not request.META['REQUEST_METHOD'] == 'GET':
|
||||
# This is a stronger requirement than above. It is needed
|
||||
# because of interactions between this middleware and the
|
||||
# HTTPMiddleware, which throws the body of a HEAD-request
|
||||
# away before this middleware gets a chance to cache it.
|
||||
return response
|
||||
if not response.status_code == 200:
|
||||
return response
|
||||
patch_response_headers(response, self.cache_timeout)
|
||||
cache_key = learn_cache_key(request, response, self.cache_timeout, self.key_prefix)
|
||||
cache.set(cache_key, response, self.cache_timeout)
|
||||
return response
|
||||
|
24
django/middleware/gzip.py
Normal file
24
django/middleware/gzip.py
Normal file
@ -0,0 +1,24 @@
|
||||
import re
|
||||
from django.utils.text import compress_string
|
||||
from django.utils.cache import patch_vary_headers
|
||||
|
||||
re_accepts_gzip = re.compile(r'\bgzip\b')
|
||||
|
||||
class GZipMiddleware:
|
||||
"""
|
||||
This middleware compresses content if the browser allows gzip compression.
|
||||
It sets the Vary header accordingly, so that caches will base their storage
|
||||
on the Accept-Encoding header.
|
||||
"""
|
||||
def process_response(self, request, response):
|
||||
patch_vary_headers(response, ('Accept-Encoding',))
|
||||
if response.has_header('Content-Encoding'):
|
||||
return response
|
||||
|
||||
ae = request.META.get('HTTP_ACCEPT_ENCODING', '')
|
||||
if not re_accepts_gzip.search(ae):
|
||||
return response
|
||||
|
||||
response.content = compress_string(response.content)
|
||||
response['Content-Encoding'] = 'gzip'
|
||||
return response
|
37
django/middleware/http.py
Normal file
37
django/middleware/http.py
Normal file
@ -0,0 +1,37 @@
|
||||
import datetime
|
||||
|
||||
class ConditionalGetMiddleware:
|
||||
"""
|
||||
Handles conditional GET operations. If the response has a ETag or
|
||||
Last-Modified header, and the request has If-None-Match or
|
||||
If-Modified-Since, the response is replaced by an HttpNotModified.
|
||||
|
||||
Removes the content from any response to a HEAD request.
|
||||
|
||||
Also sets the Date and Content-Length response-headers.
|
||||
"""
|
||||
def process_response(self, request, response):
|
||||
now = datetime.datetime.utcnow()
|
||||
response['Date'] = now.strftime('%a, %d %b %Y %H:%M:%S GMT')
|
||||
if not response.has_header('Content-Length'):
|
||||
response['Content-Length'] = str(len(response.content))
|
||||
|
||||
if response.has_header('ETag'):
|
||||
if_none_match = request.META.get('HTTP_IF_NONE_MATCH', None)
|
||||
if if_none_match == response['ETag']:
|
||||
response.status_code = 304
|
||||
response.content = ''
|
||||
response['Content-Length'] = '0'
|
||||
|
||||
if response.has_header('Last-Modified'):
|
||||
last_mod = response['Last-Modified']
|
||||
if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE', None)
|
||||
if if_modified_since == response['Last-Modified']:
|
||||
response.status_code = 304
|
||||
response.content = ''
|
||||
response['Content-Length'] = '0'
|
||||
|
||||
if request.META['REQUEST_METHOD'] == 'HEAD':
|
||||
response.content = ''
|
||||
|
||||
return response
|
@ -1,5 +1,6 @@
|
||||
from django.conf.settings import SESSION_COOKIE_NAME, SESSION_COOKIE_AGE, SESSION_COOKIE_DOMAIN
|
||||
from django.models.core import sessions
|
||||
from django.utils.cache import patch_vary_headers
|
||||
import datetime
|
||||
|
||||
TEST_COOKIE_NAME = 'testcookie'
|
||||
@ -61,6 +62,7 @@ class SessionMiddleware:
|
||||
def process_response(self, request, response):
|
||||
# If request.session was modified, or if response.session was set, save
|
||||
# those changes and set a session cookie.
|
||||
patch_vary_headers(response, ('Cookie',))
|
||||
try:
|
||||
modified = request.session.modified
|
||||
except AttributeError:
|
||||
|
121
django/utils/cache.py
Normal file
121
django/utils/cache.py
Normal file
@ -0,0 +1,121 @@
|
||||
"""
|
||||
This module contains helper functions for controlling caching. It does so by
|
||||
managing the "Vary" header of responses. It includes functions to patch the
|
||||
header of response objects directly and decorators that change functions to do
|
||||
that header-patching themselves.
|
||||
|
||||
For information on the Vary header, see:
|
||||
|
||||
http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.44
|
||||
|
||||
Essentially, the "Vary" HTTP header defines which headers a cache should take
|
||||
into account when building its cache key. Requests with the same path but
|
||||
different header content for headers named in "Vary" need to get different
|
||||
cache keys to prevent delivery of wrong content.
|
||||
|
||||
A example: i18n middleware would need to distinguish caches by the
|
||||
"Accept-language" header.
|
||||
"""
|
||||
|
||||
import datetime, md5, re
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
|
||||
vary_delim_re = re.compile(r',\s*')
|
||||
|
||||
def patch_response_headers(response, cache_timeout=None):
|
||||
"""
|
||||
Adds some useful headers to the given HttpResponse object:
|
||||
ETag, Last-Modified, Expires and Cache-Control
|
||||
|
||||
Each header is only added if it isn't already set.
|
||||
|
||||
cache_timeout is in seconds. The CACHE_MIDDLEWARE_SECONDS setting is used
|
||||
by default.
|
||||
"""
|
||||
if cache_timeout is None:
|
||||
cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
|
||||
now = datetime.datetime.utcnow()
|
||||
expires = now + datetime.timedelta(0, cache_timeout)
|
||||
if not response.has_header('ETag'):
|
||||
response['ETag'] = md5.new(response.content).hexdigest()
|
||||
if not response.has_header('Last-Modified'):
|
||||
response['Last-Modified'] = now.strftime('%a, %d %b %Y %H:%M:%S GMT')
|
||||
if not response.has_header('Expires'):
|
||||
response['Expires'] = expires.strftime('%a, %d %b %Y %H:%M:%S GMT')
|
||||
if not response.has_header('Cache-Control'):
|
||||
response['Cache-Control'] = 'max-age=%d' % cache_timeout
|
||||
|
||||
def patch_vary_headers(response, newheaders):
|
||||
"""
|
||||
Adds (or updates) the "Vary" header in the given HttpResponse object.
|
||||
newheaders is a list of header names that should be in "Vary". Existing
|
||||
headers in "Vary" aren't removed.
|
||||
"""
|
||||
# Note that we need to keep the original order intact, because cache
|
||||
# implementations may rely on the order of the Vary contents in, say,
|
||||
# computing an MD5 hash.
|
||||
vary = []
|
||||
if response.has_header('Vary'):
|
||||
vary = vary_delim_re.split(response['Vary'])
|
||||
oldheaders = dict([(el.lower(), 1) for el in vary])
|
||||
for newheader in newheaders:
|
||||
if not newheader.lower() in oldheaders:
|
||||
vary.append(newheader)
|
||||
response['Vary'] = ', '.join(vary)
|
||||
|
||||
def _generate_cache_key(request, headerlist, key_prefix):
|
||||
"Returns a cache key from the headers given in the header list."
|
||||
ctx = md5.new()
|
||||
for header in headerlist:
|
||||
value = request.META.get(header, None)
|
||||
if value is not None:
|
||||
ctx.update(value)
|
||||
return 'views.decorators.cache.cache_page.%s.%s.%s' % (key_prefix, request.path, ctx.hexdigest())
|
||||
|
||||
def get_cache_key(request, key_prefix=None):
|
||||
"""
|
||||
Returns a cache key based on the request path. It can be used in the
|
||||
request phase because it pulls the list of headers to take into account
|
||||
from the global path registry and uses those to build a cache key to check
|
||||
against.
|
||||
|
||||
If there is no headerlist stored, the page needs to be rebuilt, so this
|
||||
function returns None.
|
||||
"""
|
||||
if key_prefix is None:
|
||||
key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
|
||||
cache_key = 'views.decorators.cache.cache_header.%s.%s' % (key_prefix, request.path)
|
||||
headerlist = cache.get(cache_key, None)
|
||||
if headerlist is not None:
|
||||
return _generate_cache_key(request, headerlist, key_prefix)
|
||||
else:
|
||||
return None
|
||||
|
||||
def learn_cache_key(request, response, cache_timeout=None, key_prefix=None):
|
||||
"""
|
||||
Learns what headers to take into account for some request path from the
|
||||
response object. It stores those headers in a global path registry so that
|
||||
later access to that path will know what headers to take into account
|
||||
without building the response object itself. The headers are named in the
|
||||
Vary header of the response, but we want to prevent response generation.
|
||||
|
||||
The list of headers to use for cache key generation is stored in the same
|
||||
cache as the pages themselves. If the cache ages some data out of the
|
||||
cache, this just means that we have to build the response once to get at
|
||||
the Vary header and so at the list of headers to use for the cache key.
|
||||
"""
|
||||
if key_prefix is None:
|
||||
key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
|
||||
if cache_timeout is None:
|
||||
cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
|
||||
cache_key = 'views.decorators.cache.cache_header.%s.%s' % (key_prefix, request.path)
|
||||
if response.has_header('Vary'):
|
||||
headerlist = ['HTTP_'+header.upper().replace('-', '_') for header in vary_delim_re.split(response['Vary'])]
|
||||
cache.set(cache_key, headerlist, cache_timeout)
|
||||
return _generate_cache_key(request, headerlist, key_prefix)
|
||||
else:
|
||||
# if there is no Vary header, we still need a cache key
|
||||
# for the request.path
|
||||
cache.set(cache_key, [], cache_timeout)
|
||||
return _generate_cache_key(request, [], key_prefix)
|
22
django/utils/decorators.py
Normal file
22
django/utils/decorators.py
Normal file
@ -0,0 +1,22 @@
|
||||
"Functions that help with dynamically creating decorators for views."
|
||||
|
||||
def decorator_from_middleware(middleware_class):
|
||||
"""
|
||||
Given a middleware class (not an instance), returns a view decorator. This
|
||||
lets you use middleware functionality on a per-view basis.
|
||||
"""
|
||||
def _decorator_from_middleware(view_func, *args, **kwargs):
|
||||
middleware = middleware_class(*args, **kwargs)
|
||||
def _wrapped_view(request, *args, **kwargs):
|
||||
if hasattr(middleware, 'process_request'):
|
||||
result = middleware.process_request(request)
|
||||
if result is not None:
|
||||
return result
|
||||
response = view_func(request, *args, **kwargs)
|
||||
if hasattr(middleware, 'process_response'):
|
||||
result = middleware.process_response(request, response)
|
||||
if result is not None:
|
||||
return result
|
||||
return response
|
||||
return _wrapped_view
|
||||
return _decorator_from_middleware
|
@ -251,7 +251,7 @@ def change_list(request, app_label, module_name):
|
||||
lookup_val = request.GET.get(lookup_kwarg, None)
|
||||
lookup_val2 = request.GET.get(lookup_kwarg2, None)
|
||||
filter_template.append('<h3>By %s:</h3><ul>\n' % f.verbose_name)
|
||||
for k, v in (('All', None), ('Yes', 'True'), ('No', 'False')):
|
||||
for k, v in (('All', None), ('Yes', '1'), ('No', '0')):
|
||||
filter_template.append('<li%s><a href="%s">%s</a></li>\n' % \
|
||||
(((lookup_val == v and not lookup_val2) and ' class="selected"' or ''),
|
||||
get_query_string(params, {lookup_kwarg: v}, [lookup_kwarg2]), k))
|
||||
|
@ -1,57 +1,17 @@
|
||||
from django.core.cache import cache
|
||||
from django.utils.httpwrappers import HttpResponseNotModified
|
||||
from django.utils.text import compress_string
|
||||
from django.conf.settings import DEFAULT_CHARSET
|
||||
import datetime, md5
|
||||
"""
|
||||
Decorator for views that tries getting the page from the cache and
|
||||
populates the cache if the page isn't in the cache yet.
|
||||
|
||||
def cache_page(view_func, cache_timeout, key_prefix=''):
|
||||
"""
|
||||
Decorator for views that tries getting the page from the cache and
|
||||
populates the cache if the page isn't in the cache yet. Also takes care
|
||||
of ETags and gzips the page if the client supports it.
|
||||
The cache is keyed by the URL and some data from the headers. Additionally
|
||||
there is the key prefix that is used to distinguish different cache areas
|
||||
in a multi-site setup. You could use the sites.get_current().domain, for
|
||||
example, as that is unique across a Django project.
|
||||
|
||||
The cache is keyed off of the page's URL plus the optional key_prefix
|
||||
variable. Use key_prefix if your Django setup has multiple sites that
|
||||
use cache; otherwise the cache for one site would affect the other. A good
|
||||
example of key_prefix is to use sites.get_current().domain, because that's
|
||||
unique across all Django instances on a particular server.
|
||||
"""
|
||||
def _check_cache(request, *args, **kwargs):
|
||||
try:
|
||||
accept_encoding = request.META['HTTP_ACCEPT_ENCODING']
|
||||
except KeyError:
|
||||
accept_encoding = ''
|
||||
accepts_gzip = 'gzip' in accept_encoding
|
||||
cache_key = 'views.decorators.cache.cache_page.%s.%s.%s' % (key_prefix, request.path, accepts_gzip)
|
||||
response = cache.get(cache_key, None)
|
||||
if response is None:
|
||||
response = view_func(request, *args, **kwargs)
|
||||
content = response.get_content_as_string(DEFAULT_CHARSET)
|
||||
if accepts_gzip:
|
||||
content = compress_string(content)
|
||||
response.content = content
|
||||
response['Content-Encoding'] = 'gzip'
|
||||
response['ETag'] = md5.new(content).hexdigest()
|
||||
response['Content-Length'] = '%d' % len(content)
|
||||
response['Last-Modified'] = datetime.datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT')
|
||||
cache.set(cache_key, response, cache_timeout)
|
||||
else:
|
||||
# Logic is from http://simon.incutio.com/archive/2003/04/23/conditionalGet
|
||||
try:
|
||||
if_none_match = request.META['HTTP_IF_NONE_MATCH']
|
||||
except KeyError:
|
||||
if_none_match = None
|
||||
try:
|
||||
if_modified_since = request.META['HTTP_IF_MODIFIED_SINCE']
|
||||
except KeyError:
|
||||
if_modified_since = None
|
||||
if if_none_match is None and if_modified_since is None:
|
||||
pass
|
||||
elif if_none_match is not None and response['ETag'] != if_none_match:
|
||||
pass
|
||||
elif if_modified_since is not None and response['Last-Modified'] != if_modified_since:
|
||||
pass
|
||||
else:
|
||||
return HttpResponseNotModified()
|
||||
return response
|
||||
return _check_cache
|
||||
Additionally, all headers from the response's Vary header will be taken into
|
||||
account on caching -- just like the middleware does.
|
||||
"""
|
||||
|
||||
from django.utils.decorators import decorator_from_middleware
|
||||
from django.middleware.cache import CacheMiddleware
|
||||
|
||||
cache_page = decorator_from_middleware(CacheMiddleware)
|
||||
|
6
django/views/decorators/gzip.py
Normal file
6
django/views/decorators/gzip.py
Normal file
@ -0,0 +1,6 @@
|
||||
"Decorator for views that gzips pages if the client supports it."
|
||||
|
||||
from django.utils.decorators import decorator_from_middleware
|
||||
from django.middleware.gzip import GZipMiddleware
|
||||
|
||||
gzip_page = decorator_from_middleware(GZipMiddleware)
|
9
django/views/decorators/http.py
Normal file
9
django/views/decorators/http.py
Normal file
@ -0,0 +1,9 @@
|
||||
"""
|
||||
Decorator for views that supports conditional get on ETag and Last-Modified
|
||||
headers.
|
||||
"""
|
||||
|
||||
from django.utils.decorators import decorator_from_middleware
|
||||
from django.middleware.http import ConditionalGetMiddleware
|
||||
|
||||
conditional_page = decorator_from_middleware(ConditionalGetMiddleware)
|
35
django/views/decorators/vary.py
Normal file
35
django/views/decorators/vary.py
Normal file
@ -0,0 +1,35 @@
|
||||
from django.utils.cache import patch_vary_headers
|
||||
|
||||
def vary_on_headers(*headers):
|
||||
"""
|
||||
A view decorator that adds the specified headers to the Vary header of the
|
||||
response. Usage:
|
||||
|
||||
@vary_on_headers('Cookie', 'Accept-language')
|
||||
def index(request):
|
||||
...
|
||||
|
||||
Note that the header names are not case-sensitive.
|
||||
"""
|
||||
def decorator(func):
|
||||
def inner_func(*args, **kwargs):
|
||||
response = func(*args, **kwargs)
|
||||
patch_vary_headers(response, headers)
|
||||
return response
|
||||
return inner_func
|
||||
return decorator
|
||||
|
||||
def vary_on_cookie(func):
|
||||
"""
|
||||
A view decorator that adds "Cookie" to the Vary header of a response. This
|
||||
indicates that a page's contents depends on cookies. Usage:
|
||||
|
||||
@vary_on_cookie
|
||||
def index(request):
|
||||
...
|
||||
"""
|
||||
def inner_func(*args, **kwargs):
|
||||
response = func(*args, **kwargs)
|
||||
patch_vary_headers(response, ('Cookie',))
|
||||
return response
|
||||
return inner_func
|
174
docs/cache.txt
174
docs/cache.txt
@ -2,25 +2,27 @@
|
||||
Django's cache framework
|
||||
========================
|
||||
|
||||
So, you got slashdotted. Now what?
|
||||
So, you got slashdotted_. Now what?
|
||||
|
||||
Django's cache framework gives you three methods of caching dynamic pages in
|
||||
memory or in a database. You can cache the output of entire pages, you can
|
||||
cache only the pieces that are difficult to produce, or you can cache your
|
||||
entire site.
|
||||
|
||||
.. _slashdotted: http://en.wikipedia.org/wiki/Slashdot_effect
|
||||
|
||||
Setting up the cache
|
||||
====================
|
||||
|
||||
The cache framework is split into a set of "backends" that provide different
|
||||
methods of caching data. There's a simple single-process memory cache (mostly
|
||||
useful as a fallback) and a memcached_ backend (the fastest option, by far, if
|
||||
you've got the RAM).
|
||||
The cache framework allows for different "backends" -- different methods of
|
||||
caching data. There's a simple single-process memory cache (mostly useful as a
|
||||
fallback) and a memcached_ backend (the fastest option, by far, if you've got
|
||||
the RAM).
|
||||
|
||||
Before using the cache, you'll need to tell Django which cache backend you'd
|
||||
like to use. Do this by setting the ``CACHE_BACKEND`` in your settings file.
|
||||
|
||||
The CACHE_BACKEND setting is a "fake" URI (really an unregistered scheme).
|
||||
The ``CACHE_BACKEND`` setting is a "fake" URI (really an unregistered scheme).
|
||||
Examples:
|
||||
|
||||
============================== ===========================================
|
||||
@ -39,7 +41,7 @@ Examples:
|
||||
simple:/// A simple single-process memory cache; you
|
||||
probably don't want to use this except for
|
||||
testing. Note that this cache backend is
|
||||
NOT threadsafe!
|
||||
NOT thread-safe!
|
||||
|
||||
locmem:/// A more sophisticated local memory cache;
|
||||
this is multi-process- and thread-safe.
|
||||
@ -72,22 +74,24 @@ For example::
|
||||
Invalid arguments are silently ignored, as are invalid values of known
|
||||
arguments.
|
||||
|
||||
.. _memcached: http://www.danga.com/memcached/
|
||||
|
||||
The per-site cache
|
||||
==================
|
||||
|
||||
Once the cache is set up, the simplest way to use the cache is to simply
|
||||
cache your entire site. Just add ``django.middleware.cache.CacheMiddleware``
|
||||
to your ``MIDDLEWARE_CLASSES`` setting, as in this example::
|
||||
Once the cache is set up, the simplest way to use the cache is to cache your
|
||||
entire site. Just add ``django.middleware.cache.CacheMiddleware`` to your
|
||||
``MIDDLEWARE_CLASSES`` setting, as in this example::
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
"django.middleware.cache.CacheMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
)
|
||||
|
||||
Make sure it's the first entry in ``MIDDLEWARE_CLASSES``. (The order of
|
||||
``MIDDLEWARE_CLASSES`` matters.)
|
||||
(The order of ``MIDDLEWARE_CLASSES`` matters. See "Order of MIDDLEWARE_CLASSES"
|
||||
below.)
|
||||
|
||||
Then, add the following three required settings:
|
||||
Then, add the following three required settings to your Django settings file:
|
||||
|
||||
* ``CACHE_MIDDLEWARE_SECONDS`` -- The number of seconds each page should be
|
||||
cached.
|
||||
@ -102,16 +106,20 @@ Then, add the following three required settings:
|
||||
in the cache. That means subsequent requests won't have the overhead of
|
||||
zipping, and the cache will hold more pages because each one is smaller.
|
||||
|
||||
Pages with GET or POST parameters won't be cached.
|
||||
The cache middleware caches every page that doesn't have GET or POST
|
||||
parameters. Additionally, ``CacheMiddleware`` automatically sets a few headers
|
||||
in each ``HttpResponse``:
|
||||
|
||||
The cache middleware also makes a few more optimizations:
|
||||
|
||||
* Sets and deals with ``ETag`` headers.
|
||||
* Sets the ``Content-Length`` header.
|
||||
* Sets the ``Last-Modified`` header to the current date/time when a fresh
|
||||
(uncached) version of the page is requested.
|
||||
* Sets the ``Expires`` header to the current date/time plus the defined
|
||||
``CACHE_MIDDLEWARE_SECONDS``.
|
||||
* Sets the ``Cache-Control`` header to give a max age for the page -- again,
|
||||
from the ``CACHE_MIDDLEWARE_SECONDS`` setting.
|
||||
|
||||
It doesn't matter where in the middleware stack you put the cache middleware.
|
||||
See the `middleware documentation`_ for more on middleware.
|
||||
|
||||
.. _`middleware documentation`: http://www.djangoproject.com/documentation/middleware/
|
||||
|
||||
The per-page cache
|
||||
==================
|
||||
@ -134,25 +142,25 @@ Or, using Python 2.4's decorator syntax::
|
||||
def slashdot_this(request):
|
||||
...
|
||||
|
||||
This will cache the result of that view for 15 minutes. (The cache timeout is
|
||||
in seconds.)
|
||||
``cache_page`` takes a single argument: the cache timeout, in seconds. In the
|
||||
above example, the result of the ``slashdot_this()`` view will be cached for 15
|
||||
minutes.
|
||||
|
||||
The low-level cache API
|
||||
=======================
|
||||
|
||||
There are times, however, that caching an entire rendered page doesn't gain
|
||||
you very much. The Django developers have found it's only necessary to cache a
|
||||
list of object IDs from an intensive database query, for example. In cases like
|
||||
these, you can use the cache API to store objects in the cache with any level
|
||||
of granularity you like.
|
||||
Sometimes, however, caching an entire rendered page doesn't gain you very much.
|
||||
For example, you may find it's only necessary to cache the result of an
|
||||
intensive database. In cases like this, you can use the low-level cache API to
|
||||
store objects in the cache with any level of granularity you like.
|
||||
|
||||
The cache API is simple::
|
||||
|
||||
# the cache module exports a cache object that's automatically
|
||||
# created from the CACHE_BACKEND setting
|
||||
# The cache module exports a cache object that's automatically
|
||||
# created from the CACHE_BACKEND setting.
|
||||
>>> from django.core.cache import cache
|
||||
|
||||
# The basic interface is set(key, value, timeout_seconds) and get(key)
|
||||
# The basic interface is set(key, value, timeout_seconds) and get(key).
|
||||
>>> cache.set('my_key', 'hello, world!', 30)
|
||||
>>> cache.get('my_key')
|
||||
'hello, world!'
|
||||
@ -161,7 +169,7 @@ The cache API is simple::
|
||||
>>> cache.get('my_key')
|
||||
None
|
||||
|
||||
# get() can take a default argument
|
||||
# get() can take a default argument.
|
||||
>>> cache.get('my_key', 'has_expired')
|
||||
'has_expired'
|
||||
|
||||
@ -183,4 +191,108 @@ The cache API is simple::
|
||||
That's it. The cache has very few restrictions: You can cache any object that
|
||||
can be pickled safely, although keys must be strings.
|
||||
|
||||
.. _memcached: http://www.danga.com/memcached/
|
||||
Controlling cache: Using Vary headers
|
||||
=====================================
|
||||
|
||||
The Django cache framework works with `HTTP Vary headers`_ to allow developers
|
||||
to instruct caching mechanisms to differ their cache contents depending on
|
||||
request HTTP headers.
|
||||
|
||||
Essentially, the ``Vary`` response HTTP header defines which request headers a
|
||||
cache mechanism should take into account when building its cache key.
|
||||
|
||||
By default, Django's cache system creates its cache keys using the requested
|
||||
path -- e.g., ``"/stories/2005/jun/23/bank_robbed/"``. This means every request
|
||||
to that URL will use the same cached version, regardless of user-agent
|
||||
differences such as cookies or language preferences.
|
||||
|
||||
That's where ``Vary`` comes in.
|
||||
|
||||
If your Django-powered page outputs different content based on some difference
|
||||
in request headers -- such as a cookie, or language, or user-agent -- you'll
|
||||
need to use the ``Vary`` header to tell caching mechanisms that the page output
|
||||
depends on those things.
|
||||
|
||||
To do this in Django, use the convenient ``vary_on_headers`` view decorator,
|
||||
like so::
|
||||
|
||||
from django.views.decorators.vary import vary_on_headers
|
||||
|
||||
# Python 2.3 syntax.
|
||||
def my_view(request):
|
||||
...
|
||||
my_view = vary_on_headers(my_view, 'User-Agent')
|
||||
|
||||
# Python 2.4 decorator syntax.
|
||||
@vary_on_headers('User-Agent')
|
||||
def my_view(request):
|
||||
...
|
||||
|
||||
In this case, a caching mechanism (such as Django's own cache middleware) will
|
||||
cache a separate version of the page for each unique user-agent.
|
||||
|
||||
The advantage to using the ``vary_on_headers`` decorator rather than manually
|
||||
setting the ``Vary`` header (using something like
|
||||
``response['Vary'] = 'user-agent'``) is that the decorator adds to the ``Vary``
|
||||
header (which may already exist) rather than setting it from scratch.
|
||||
|
||||
Note that you can pass multiple headers to ``vary_on_headers()``::
|
||||
|
||||
@vary_on_headers('User-Agent', 'Cookie')
|
||||
def my_view(request):
|
||||
...
|
||||
|
||||
Because varying on cookie is such a common case, there's a ``vary_on_cookie``
|
||||
decorator. These two views are equivalent::
|
||||
|
||||
@vary_on_cookie
|
||||
def my_view(request):
|
||||
...
|
||||
|
||||
@vary_on_headers('Cookie')
|
||||
def my_view(request):
|
||||
...
|
||||
|
||||
Also note that the headers you pass to ``vary_on_headers`` are not case
|
||||
sensitive. ``"User-Agent"`` is the same thing as ``"user-agent"``.
|
||||
|
||||
You can also use a helper function, ``patch_vary_headers()``, directly::
|
||||
|
||||
from django.utils.cache import patch_vary_headers
|
||||
def my_view(request):
|
||||
...
|
||||
response = render_to_response('template_name', context)
|
||||
patch_vary_headers(response, ['Cookie'])
|
||||
return response
|
||||
|
||||
``patch_vary_headers`` takes an ``HttpResponse`` instance as its first argument
|
||||
and a list/tuple of header names as its second argument.
|
||||
|
||||
.. _`HTTP Vary headers`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.44
|
||||
|
||||
Other optimizations
|
||||
===================
|
||||
|
||||
Django comes with a few other pieces of middleware that can help optimize your
|
||||
apps' performance:
|
||||
|
||||
* ``django.middleware.http.ConditionalGetMiddleware`` adds support for
|
||||
conditional GET. This makes use of ``ETag`` and ``Last-Modified``
|
||||
headers.
|
||||
|
||||
* ``django.middleware.gzip.GZipMiddleware`` compresses content for browsers
|
||||
that understand gzip compression (all modern browsers).
|
||||
|
||||
Order of MIDDLEWARE_CLASSES
|
||||
===========================
|
||||
|
||||
If you use ``CacheMiddleware``, it's important to put it in the right place
|
||||
within the ``MIDDLEWARE_CLASSES`` setting, because the cache middleware needs
|
||||
to know which headers by which to vary the cache storage. Middleware always
|
||||
adds something the ``Vary`` response header when it can.
|
||||
|
||||
Put the ``CacheMiddleware`` after any middlewares that might add something to
|
||||
the ``Vary`` header. The following middlewares do so:
|
||||
|
||||
* ``SessionMiddleware`` adds ``Cookie``
|
||||
* ``GZipMiddleware`` adds ``Accept-Encoding``
|
||||
|
@ -183,7 +183,7 @@ Available options
|
||||
=================
|
||||
|
||||
--settings
|
||||
==========
|
||||
----------
|
||||
|
||||
Example usage::
|
||||
|
||||
@ -193,8 +193,21 @@ Explicitly specifies the settings module to use. The settings module should be
|
||||
in Python path syntax, e.g. "myproject.settings.main". If this isn't provided,
|
||||
``django-admin.py`` will use the DJANGO_SETTINGS_MODULE environment variable.
|
||||
|
||||
--pythonpath
|
||||
------------
|
||||
|
||||
Example usage::
|
||||
|
||||
django-admin.py init --pythonpath='/home/djangoprojects/myproject'
|
||||
|
||||
Adds the given filesystem path to the Python `import search path`_. If this
|
||||
isn't provided, ``django-admin.py`` will use the ``PYTHONPATH`` environment
|
||||
variable.
|
||||
|
||||
.. _import search path: http://diveintopython.org/getting_to_know_python/everything_is_an_object.html
|
||||
|
||||
--help
|
||||
======
|
||||
------
|
||||
|
||||
Displays a help message that includes a terse list of all available actions and
|
||||
options.
|
||||
|
@ -45,53 +45,79 @@ required.
|
||||
Available middleware
|
||||
====================
|
||||
|
||||
``django.middleware.admin.AdminUserRequired``
|
||||
Limits site access to valid users with the ``is_staff`` flag set. This is
|
||||
required by Django's admin, and this middleware requires ``SessionMiddleware``.
|
||||
django.middleware.admin.AdminUserRequired
|
||||
-----------------------------------------
|
||||
|
||||
``django.middleware.cache.CacheMiddleware``
|
||||
Enables site-wide cache. If this is enabled, each Django-powered page will be
|
||||
cached for as long as the ``CACHE_MIDDLEWARE_SECONDS`` setting defines. See
|
||||
the `cache documentation`_.
|
||||
Limits site access to valid users with the ``is_staff`` flag set. This is
|
||||
required by Django's admin, and this middleware requires ``SessionMiddleware``.
|
||||
|
||||
.. _`cache documentation`: http://www.djangoproject.com/documentation/cache/#the-per-site-cache
|
||||
django.middleware.cache.CacheMiddleware
|
||||
---------------------------------------
|
||||
|
||||
``django.middleware.common.CommonMiddleware``
|
||||
Adds a few conveniences for perfectionists:
|
||||
Enables site-wide cache. If this is enabled, each Django-powered page will be
|
||||
cached for as long as the ``CACHE_MIDDLEWARE_SECONDS`` setting defines. See
|
||||
the `cache documentation`_.
|
||||
|
||||
* Forbids access to user agents in the ``DISALLOWED_USER_AGENTS`` setting,
|
||||
which should be a list of strings.
|
||||
.. _`cache documentation`: http://www.djangoproject.com/documentation/cache/#the-per-site-cache
|
||||
|
||||
* Performs URL rewriting based on the ``APPEND_SLASH`` and ``PREPEND_WWW``
|
||||
settings. If ``APPEND_SLASH`` is ``True``, URLs that lack a trailing
|
||||
slash will be redirected to the same URL with a trailing slash. If
|
||||
``PREPEND_WWW`` is ``True``, URLs that lack a leading "www." will be
|
||||
redirected to the same URL with a leading "www."
|
||||
django.middleware.common.CommonMiddleware
|
||||
-----------------------------------------
|
||||
|
||||
Both of these options are meant to normalize URLs. The philosophy is that
|
||||
each URL should exist in one, and only one, place. Technically a URL
|
||||
``foo.com/bar`` is distinct from ``foo.com/bar/`` -- a search-engine
|
||||
indexer would treat them as separate URLs -- so it's best practice to
|
||||
normalize URLs.
|
||||
Adds a few conveniences for perfectionists:
|
||||
|
||||
* Handles ETags based on the ``USE_ETAGS`` setting. If ``USE_ETAGS`` is set
|
||||
to ``True``, Django will calculate an ETag for each request by
|
||||
MD5-hashing the page content, and it'll take care of sending
|
||||
``Not Modified`` responses, if appropriate.
|
||||
* Forbids access to user agents in the ``DISALLOWED_USER_AGENTS`` setting,
|
||||
which should be a list of strings.
|
||||
|
||||
* Handles flat pages. Every time Django encounters a 404 -- either within
|
||||
a view or as a result of no URLconfs matching -- it will check the
|
||||
database of flat pages based on the current URL.
|
||||
* Performs URL rewriting based on the ``APPEND_SLASH`` and ``PREPEND_WWW``
|
||||
settings. If ``APPEND_SLASH`` is ``True``, URLs that lack a trailing
|
||||
slash will be redirected to the same URL with a trailing slash. If
|
||||
``PREPEND_WWW`` is ``True``, URLs that lack a leading "www." will be
|
||||
redirected to the same URL with a leading "www."
|
||||
|
||||
``django.middleware.doc.XViewMiddleware``
|
||||
Sends custom ``X-View`` HTTP headers to HEAD requests that come from IP
|
||||
addresses defined in the ``INTERNAL_IPS`` setting. This is used by Django's
|
||||
automatic documentation system.
|
||||
Both of these options are meant to normalize URLs. The philosophy is that
|
||||
each URL should exist in one, and only one, place. Technically a URL
|
||||
``foo.com/bar`` is distinct from ``foo.com/bar/`` -- a search-engine
|
||||
indexer would treat them as separate URLs -- so it's best practice to
|
||||
normalize URLs.
|
||||
|
||||
``django.middleware.sessions.SessionMiddleware``
|
||||
Enables session support. See the `session documentation`_.
|
||||
* Handles ETags based on the ``USE_ETAGS`` setting. If ``USE_ETAGS`` is set
|
||||
to ``True``, Django will calculate an ETag for each request by
|
||||
MD5-hashing the page content, and it'll take care of sending
|
||||
``Not Modified`` responses, if appropriate.
|
||||
|
||||
.. _`session documentation`: http://www.djangoproject.com/documentation/sessions/
|
||||
* Handles flat pages. Every time Django encounters a 404 -- either within
|
||||
a view or as a result of no URLconfs matching -- it will check the
|
||||
database of flat pages based on the current URL.
|
||||
|
||||
django.middleware.doc.XViewMiddleware
|
||||
-------------------------------------
|
||||
|
||||
Sends custom ``X-View`` HTTP headers to HEAD requests that come from IP
|
||||
addresses defined in the ``INTERNAL_IPS`` setting. This is used by Django's
|
||||
automatic documentation system.
|
||||
|
||||
django.middleware.gzip.GZipMiddleware
|
||||
-------------------------------------
|
||||
|
||||
Compresses content for browsers that understand gzip compression (all modern
|
||||
browsers).
|
||||
|
||||
django.middleware.http.ConditionalGetMiddleware
|
||||
-----------------------------------------------
|
||||
|
||||
Handles conditional GET operations. If the response has a ``ETag`` or
|
||||
``Last-Modified`` header, and the request has ``If-None-Match`` or
|
||||
``If-Modified-Since``, the response is replaced by an HttpNotModified.
|
||||
|
||||
Also removes the content from any response to a HEAD request and sets the
|
||||
``Date`` and ``Content-Length`` response-headers.
|
||||
|
||||
django.middleware.sessions.SessionMiddleware
|
||||
--------------------------------------------
|
||||
|
||||
Enables session support. See the `session documentation`_.
|
||||
|
||||
.. _`session documentation`: http://www.djangoproject.com/documentation/sessions/
|
||||
|
||||
Writing your own middleware
|
||||
===========================
|
||||
|
@ -94,7 +94,7 @@ class TestRunner:
|
||||
# within transactions.
|
||||
cursor = db.cursor()
|
||||
try:
|
||||
db.connection.autocommit()
|
||||
db.connection.autocommit(1)
|
||||
except AttributeError:
|
||||
pass
|
||||
self.output(1, "Creating test database")
|
||||
@ -180,7 +180,7 @@ class TestRunner:
|
||||
cursor = db.cursor()
|
||||
self.output(1, "Deleting test database")
|
||||
try:
|
||||
db.connection.autocommit()
|
||||
db.connection.autocommit(1)
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
|
Loading…
x
Reference in New Issue
Block a user