1
0
mirror of https://github.com/django/django.git synced 2025-07-04 17:59:13 +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:
Georg Bauer 2005-10-09 10:47:06 +00:00
parent 4fc6d40d00
commit eb7ebb777c
21 changed files with 596 additions and 207 deletions

View File

@ -53,11 +53,15 @@ def main():
parser = DjangoOptionParser(get_usage()) parser = DjangoOptionParser(get_usage())
parser.add_option('--settings', 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.') 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() options, args = parser.parse_args()
# Take care of options. # Take care of options.
if options.settings: if options.settings:
os.environ['DJANGO_SETTINGS_MODULE'] = 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 # Run the appropriate action. Unfortunately, optparse can't handle
# positional arguments, so this has to parse/validate them. # positional arguments, so this has to parse/validate them.

View File

@ -136,6 +136,8 @@ JING_PATH = "/usr/bin/jing"
# response phase the middleware will be applied in reverse order. # response phase the middleware will be applied in reverse order.
MIDDLEWARE_CLASSES = ( MIDDLEWARE_CLASSES = (
"django.middleware.sessions.SessionMiddleware", "django.middleware.sessions.SessionMiddleware",
# "django.middleware.http.ConditionalGetMiddleware",
# "django.middleware.gzip.GZipMiddleware",
"django.middleware.common.CommonMiddleware", "django.middleware.common.CommonMiddleware",
"django.middleware.doc.XViewMiddleware", "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 # The cache backend to use. See the docstring in django.core.cache for the
# possible values. # possible values.
CACHE_BACKEND = 'simple://' CACHE_BACKEND = 'simple://'
CACHE_MIDDLEWARE_KEY_PREFIX = ''
#################### ####################
# COMMENTS # # COMMENTS #

View File

@ -21,6 +21,32 @@ django_conversions.update({
FIELD_TYPE.TIME: typecasts.typecast_time, 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: class DatabaseWrapper:
def __init__(self): def __init__(self):
self.connection = None self.connection = None
@ -32,7 +58,7 @@ class DatabaseWrapper:
self.connection = Database.connect(user=DATABASE_USER, db=DATABASE_NAME, self.connection = Database.connect(user=DATABASE_USER, db=DATABASE_NAME,
passwd=DATABASE_PASSWORD, host=DATABASE_HOST, conv=django_conversions) passwd=DATABASE_PASSWORD, host=DATABASE_HOST, conv=django_conversions)
if DEBUG: if DEBUG:
return base.CursorDebugWrapper(self.connection.cursor(), self) return base.CursorDebugWrapper(MysqlDebugWrapper(self.connection.cursor()), self)
return self.connection.cursor() return self.connection.cursor()
def commit(self): def commit(self):

View File

@ -16,8 +16,8 @@ APP_ARGS = '[modelmodule ...]'
# Use django.__path__[0] because we don't know which directory django into # Use django.__path__[0] because we don't know which directory django into
# which has been installed. # which has been installed.
PROJECT_TEMPLATE_DIR = os.path.join(django.__path__[0], 'conf/%s_template') PROJECT_TEMPLATE_DIR = os.path.join(django.__path__[0], 'conf', '%s_template')
ADMIN_TEMPLATE_DIR = os.path.join(django.__path__[0], 'conf/admin_templates') ADMIN_TEMPLATE_DIR = os.path.join(django.__path__[0], 'conf', 'admin_templates')
def _get_packages_insert(app_label): def _get_packages_insert(app_label):
return "INSERT INTO packages (label, name) VALUES ('%s', '%s');" % (app_label, 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 = [] output = []
app_label = mod._MODELS[0]._meta.app_label app_label = mod._MODELS[0]._meta.app_label
output.append(_get_packages_insert(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: for klass in mod._MODELS:
opts = klass._meta opts = klass._meta
@ -376,14 +376,14 @@ def startproject(project_name, directory):
_start_helper('project', project_name, directory) _start_helper('project', project_name, directory)
# Populate TEMPLATE_DIRS for the admin templates, based on where Django is # Populate TEMPLATE_DIRS for the admin templates, based on where Django is
# installed. # 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() settings_contents = open(admin_settings_file, 'r').read()
fp = open(admin_settings_file, 'w') 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) 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.write(settings_contents)
fp.close() fp.close()
# Create a random SECRET_KEY hash, and put it in the main settings. # 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() settings_contents = open(main_settings_file, 'r').read()
fp = open(main_settings_file, 'w') fp = open(main_settings_file, 'w')
secret_key = ''.join([choice('abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)') for i in range(50)]) 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." "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 # Determine the project_name a bit naively -- by looking at the name of
# the parent directory. # 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) project_name = os.path.basename(project_dir)
_start_helper('app', app_name, directory, project_name) _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." startapp.help_doc = "Creates a Django app directory structure for the given app name in the current directory."

View File

@ -1332,16 +1332,19 @@ def function_get_sql_clause(opts, **kwargs):
if f == '?': # Special case. if f == '?': # Special case.
order_by.append(db.get_random_function_sql()) order_by.append(db.get_random_function_sql())
else: 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, # Use the database table as a column prefix if it wasn't given,
# and if the requested column isn't a custom SELECT. # 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 + '.' table_prefix = opts.db_table + '.'
else: else:
table_prefix = '' table_prefix = ''
if f.startswith('-'): order_by.append('%s%s %s' % (table_prefix, orderfield2column(col_name, opts), order))
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 = ", ".join(order_by) order_by = ", ".join(order_by)
# LIMIT and OFFSET clauses # LIMIT and OFFSET clauses

View File

@ -596,7 +596,11 @@ class ForeignKey(Field):
Field.__init__(self, **kwargs) Field.__init__(self, **kwargs)
def get_manipulator_field_objs(self): 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): class ManyToManyField(Field):
def __init__(self, to, **kwargs): def __init__(self, to, **kwargs):

View File

@ -1,88 +1,70 @@
import copy
from django.conf import settings from django.conf import settings
from django.core.cache import cache 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.httpwrappers import HttpResponseNotModified
from django.utils.text import compress_string
import datetime, md5
class CacheMiddleware: class CacheMiddleware:
""" """
Cache middleware. If this is enabled, each Django-powered page will be Cache middleware. If this is enabled, each Django-powered page will be
cached for CACHE_MIDDLEWARE_SECONDS seconds. Cache is based on URLs. Pages cached for CACHE_MIDDLEWARE_SECONDS seconds. Cache is based on URLs.
with GET or POST parameters are not cached.
If the cache is shared across multiple sites using the same Django Only parameter-less GET or HEAD-requests with status code 200 are cached.
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.
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 When a hit occurs, a shallow copy of the original response object is
gzipped. 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): def process_request(self, request):
""" "Checks whether the page is already cached and returns the cached version if available."
Checks whether the page is already cached. If it is, returns the cached if not request.META['REQUEST_METHOD'] in ('GET', 'HEAD') or request.GET:
version. Also handles ETag stuff. request._cache_update_cache = False
"""
if request.GET or request.POST:
request._cache_middleware_set_cache = False
return None # Don't bother checking the cache. return None # Don't bother checking the cache.
accept_encoding = '' cache_key = get_cache_key(request, self.key_prefix)
if settings.CACHE_MIDDLEWARE_GZIP: if cache_key is None:
try: request._cache_update_cache = True
accept_encoding = request.META['HTTP_ACCEPT_ENCODING'] return None # No cache information available, need to rebuild.
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
response = cache.get(cache_key, None) response = cache.get(cache_key, None)
if response is None: if response is None:
request._cache_middleware_set_cache = True request._cache_update_cache = True
return None return None # No cache information available, need to rebuild.
else:
request._cache_middleware_set_cache = False request._cache_update_cache = False
# Logic is from http://simon.incutio.com/archive/2003/04/23/conditionalGet return copy.copy(response)
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
def process_response(self, request, response): def process_response(self, request, response):
""" "Sets the cache, if needed."
Sets the cache, if needed. if not request._cache_update_cache:
""" # We don't need to update the cache, just return.
if request._cache_middleware_set_cache: return response
content = response.get_content_as_string(settings.DEFAULT_CHARSET) if not request.META['REQUEST_METHOD'] == 'GET':
if request._cache_middleware_accepts_gzip: # This is a stronger requirement than above. It is needed
content = compress_string(content) # because of interactions between this middleware and the
response.content = content # HTTPMiddleware, which throws the body of a HEAD-request
response['Content-Encoding'] = 'gzip' # away before this middleware gets a chance to cache it.
response['ETag'] = md5.new(content).hexdigest() return response
response['Content-Length'] = '%d' % len(content) if not response.status_code == 200:
response['Last-Modified'] = datetime.datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT') return response
cache.set(request._cache_middleware_key, response, settings.CACHE_MIDDLEWARE_SECONDS) 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 return response

24
django/middleware/gzip.py Normal file
View 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
View 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

View File

@ -1,5 +1,6 @@
from django.conf.settings import SESSION_COOKIE_NAME, SESSION_COOKIE_AGE, SESSION_COOKIE_DOMAIN from django.conf.settings import SESSION_COOKIE_NAME, SESSION_COOKIE_AGE, SESSION_COOKIE_DOMAIN
from django.models.core import sessions from django.models.core import sessions
from django.utils.cache import patch_vary_headers
import datetime import datetime
TEST_COOKIE_NAME = 'testcookie' TEST_COOKIE_NAME = 'testcookie'
@ -61,6 +62,7 @@ class SessionMiddleware:
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
# those changes and set a session cookie. # those changes and set a session cookie.
patch_vary_headers(response, ('Cookie',))
try: try:
modified = request.session.modified modified = request.session.modified
except AttributeError: except AttributeError:

121
django/utils/cache.py Normal file
View 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)

View 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

View File

@ -251,7 +251,7 @@ def change_list(request, app_label, module_name):
lookup_val = request.GET.get(lookup_kwarg, None) lookup_val = request.GET.get(lookup_kwarg, None)
lookup_val2 = request.GET.get(lookup_kwarg2, None) lookup_val2 = request.GET.get(lookup_kwarg2, None)
filter_template.append('<h3>By %s:</h3><ul>\n' % f.verbose_name) 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' % \ filter_template.append('<li%s><a href="%s">%s</a></li>\n' % \
(((lookup_val == v and not lookup_val2) and ' class="selected"' or ''), (((lookup_val == v and not lookup_val2) and ' class="selected"' or ''),
get_query_string(params, {lookup_kwarg: v}, [lookup_kwarg2]), k)) get_query_string(params, {lookup_kwarg: v}, [lookup_kwarg2]), k))

View File

@ -1,57 +1,17 @@
from django.core.cache import cache """
from django.utils.httpwrappers import HttpResponseNotModified Decorator for views that tries getting the page from the cache and
from django.utils.text import compress_string populates the cache if the page isn't in the cache yet.
from django.conf.settings import DEFAULT_CHARSET
import datetime, md5
def cache_page(view_func, cache_timeout, key_prefix=''): 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
Decorator for views that tries getting the page from the cache and in a multi-site setup. You could use the sites.get_current().domain, for
populates the cache if the page isn't in the cache yet. Also takes care example, as that is unique across a Django project.
of ETags and gzips the page if the client supports it.
The cache is keyed off of the page's URL plus the optional key_prefix Additionally, all headers from the response's Vary header will be taken into
variable. Use key_prefix if your Django setup has multiple sites that account on caching -- just like the middleware does.
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. from django.utils.decorators import decorator_from_middleware
""" from django.middleware.cache import CacheMiddleware
def _check_cache(request, *args, **kwargs):
try: cache_page = decorator_from_middleware(CacheMiddleware)
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

View 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)

View 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)

View 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

View File

@ -2,25 +2,27 @@
Django's cache framework 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 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 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 cache only the pieces that are difficult to produce, or you can cache your
entire site. entire site.
.. _slashdotted: http://en.wikipedia.org/wiki/Slashdot_effect
Setting up the cache Setting up the cache
==================== ====================
The cache framework is split into a set of "backends" that provide different The cache framework allows for different "backends" -- different methods of
methods of caching data. There's a simple single-process memory cache (mostly caching data. There's a simple single-process memory cache (mostly useful as a
useful as a fallback) and a memcached_ backend (the fastest option, by far, if fallback) and a memcached_ backend (the fastest option, by far, if you've got
you've got the RAM). the RAM).
Before using the cache, you'll need to tell Django which cache backend you'd 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. 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: Examples:
============================== =========================================== ============================== ===========================================
@ -39,7 +41,7 @@ Examples:
simple:/// A simple single-process memory cache; you simple:/// A simple single-process memory cache; you
probably don't want to use this except for probably don't want to use this except for
testing. Note that this cache backend is testing. Note that this cache backend is
NOT threadsafe! NOT thread-safe!
locmem:/// A more sophisticated local memory cache; locmem:/// A more sophisticated local memory cache;
this is multi-process- and thread-safe. this is multi-process- and thread-safe.
@ -72,22 +74,24 @@ For example::
Invalid arguments are silently ignored, as are invalid values of known Invalid arguments are silently ignored, as are invalid values of known
arguments. arguments.
.. _memcached: http://www.danga.com/memcached/
The per-site cache The per-site cache
================== ==================
Once the cache is set up, the simplest way to use the cache is to simply Once the cache is set up, the simplest way to use the cache is to cache your
cache your entire site. Just add ``django.middleware.cache.CacheMiddleware`` entire site. Just add ``django.middleware.cache.CacheMiddleware`` to your
to your ``MIDDLEWARE_CLASSES`` setting, as in this example:: ``MIDDLEWARE_CLASSES`` setting, as in this example::
MIDDLEWARE_CLASSES = ( MIDDLEWARE_CLASSES = (
"django.middleware.cache.CacheMiddleware", "django.middleware.cache.CacheMiddleware",
"django.middleware.common.CommonMiddleware", "django.middleware.common.CommonMiddleware",
) )
Make sure it's the first entry in ``MIDDLEWARE_CLASSES``. (The order of (The order of ``MIDDLEWARE_CLASSES`` matters. See "Order of MIDDLEWARE_CLASSES"
``MIDDLEWARE_CLASSES`` matters.) 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 * ``CACHE_MIDDLEWARE_SECONDS`` -- The number of seconds each page should be
cached. 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 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. 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 * Sets the ``Last-Modified`` header to the current date/time when a fresh
(uncached) version of the page is requested. (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 The per-page cache
================== ==================
@ -134,25 +142,25 @@ Or, using Python 2.4's decorator syntax::
def slashdot_this(request): def slashdot_this(request):
... ...
This will cache the result of that view for 15 minutes. (The cache timeout is ``cache_page`` takes a single argument: the cache timeout, in seconds. In the
in seconds.) above example, the result of the ``slashdot_this()`` view will be cached for 15
minutes.
The low-level cache API The low-level cache API
======================= =======================
There are times, however, that caching an entire rendered page doesn't gain Sometimes, however, caching an entire rendered page doesn't gain you very much.
you very much. The Django developers have found it's only necessary to cache a For example, you may find it's only necessary to cache the result of an
list of object IDs from an intensive database query, for example. In cases like intensive database. In cases like this, you can use the low-level cache API to
these, you can use the cache API to store objects in the cache with any level store objects in the cache with any level of granularity you like.
of granularity you like.
The cache API is simple:: The cache API is simple::
# the cache module exports a cache object that's automatically # The cache module exports a cache object that's automatically
# created from the CACHE_BACKEND setting # created from the CACHE_BACKEND setting.
>>> from django.core.cache import cache >>> 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.set('my_key', 'hello, world!', 30)
>>> cache.get('my_key') >>> cache.get('my_key')
'hello, world!' 'hello, world!'
@ -161,7 +169,7 @@ The cache API is simple::
>>> cache.get('my_key') >>> cache.get('my_key')
None None
# get() can take a default argument # get() can take a default argument.
>>> cache.get('my_key', 'has_expired') >>> cache.get('my_key', 'has_expired')
'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 That's it. The cache has very few restrictions: You can cache any object that
can be pickled safely, although keys must be strings. 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``

View File

@ -183,7 +183,7 @@ Available options
================= =================
--settings --settings
========== ----------
Example usage:: 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, 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. ``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 --help
====== ------
Displays a help message that includes a terse list of all available actions and Displays a help message that includes a terse list of all available actions and
options. options.

View File

@ -45,53 +45,79 @@ required.
Available middleware Available middleware
==================== ====================
``django.middleware.admin.AdminUserRequired`` 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.cache.CacheMiddleware`` Limits site access to valid users with the ``is_staff`` flag set. This is
Enables site-wide cache. If this is enabled, each Django-powered page will be required by Django's admin, and this middleware requires ``SessionMiddleware``.
cached for as long as the ``CACHE_MIDDLEWARE_SECONDS`` setting defines. See
the `cache documentation`_.
.. _`cache documentation`: http://www.djangoproject.com/documentation/cache/#the-per-site-cache django.middleware.cache.CacheMiddleware
---------------------------------------
``django.middleware.common.CommonMiddleware`` Enables site-wide cache. If this is enabled, each Django-powered page will be
Adds a few conveniences for perfectionists: 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, .. _`cache documentation`: http://www.djangoproject.com/documentation/cache/#the-per-site-cache
which should be a list of strings.
* Performs URL rewriting based on the ``APPEND_SLASH`` and ``PREPEND_WWW`` django.middleware.common.CommonMiddleware
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."
Both of these options are meant to normalize URLs. The philosophy is that Adds a few conveniences for perfectionists:
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.
* Handles ETags based on the ``USE_ETAGS`` setting. If ``USE_ETAGS`` is set * Forbids access to user agents in the ``DISALLOWED_USER_AGENTS`` setting,
to ``True``, Django will calculate an ETag for each request by which should be a list of strings.
MD5-hashing the page content, and it'll take care of sending
``Not Modified`` responses, if appropriate.
* Handles flat pages. Every time Django encounters a 404 -- either within * Performs URL rewriting based on the ``APPEND_SLASH`` and ``PREPEND_WWW``
a view or as a result of no URLconfs matching -- it will check the settings. If ``APPEND_SLASH`` is ``True``, URLs that lack a trailing
database of flat pages based on the current URL. 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`` Both of these options are meant to normalize URLs. The philosophy is that
Sends custom ``X-View`` HTTP headers to HEAD requests that come from IP each URL should exist in one, and only one, place. Technically a URL
addresses defined in the ``INTERNAL_IPS`` setting. This is used by Django's ``foo.com/bar`` is distinct from ``foo.com/bar/`` -- a search-engine
automatic documentation system. indexer would treat them as separate URLs -- so it's best practice to
normalize URLs.
``django.middleware.sessions.SessionMiddleware`` * Handles ETags based on the ``USE_ETAGS`` setting. If ``USE_ETAGS`` is set
Enables session support. See the `session documentation`_. 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 Writing your own middleware
=========================== ===========================

View File

@ -94,7 +94,7 @@ class TestRunner:
# within transactions. # within transactions.
cursor = db.cursor() cursor = db.cursor()
try: try:
db.connection.autocommit() db.connection.autocommit(1)
except AttributeError: except AttributeError:
pass pass
self.output(1, "Creating test database") self.output(1, "Creating test database")
@ -180,7 +180,7 @@ class TestRunner:
cursor = db.cursor() cursor = db.cursor()
self.output(1, "Deleting test database") self.output(1, "Deleting test database")
try: try:
db.connection.autocommit() db.connection.autocommit(1)
except AttributeError: except AttributeError:
pass pass
else: else: