summaryrefslogtreecommitdiff
path: root/lib/python2.7/site-packages/django/utils/cache.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/python2.7/site-packages/django/utils/cache.py')
-rw-r--r--lib/python2.7/site-packages/django/utils/cache.py264
1 files changed, 264 insertions, 0 deletions
diff --git a/lib/python2.7/site-packages/django/utils/cache.py b/lib/python2.7/site-packages/django/utils/cache.py
new file mode 100644
index 0000000..1c8587d
--- /dev/null
+++ b/lib/python2.7/site-packages/django/utils/cache.py
@@ -0,0 +1,264 @@
+"""
+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.
+
+An example: i18n middleware would need to distinguish caches by the
+"Accept-language" header.
+"""
+from __future__ import unicode_literals
+
+import hashlib
+import re
+import time
+
+from django.conf import settings
+from django.core.cache import get_cache
+from django.utils.encoding import iri_to_uri, force_bytes, force_text
+from django.utils.http import http_date
+from django.utils.timezone import get_current_timezone_name
+from django.utils.translation import get_language
+
+cc_delim_re = re.compile(r'\s*,\s*')
+
+def patch_cache_control(response, **kwargs):
+ """
+ This function patches the Cache-Control header by adding all
+ keyword arguments to it. The transformation is as follows:
+
+ * All keyword parameter names are turned to lowercase, and underscores
+ are converted to hyphens.
+ * If the value of a parameter is True (exactly True, not just a
+ true value), only the parameter name is added to the header.
+ * All other parameters are added with their value, after applying
+ str() to it.
+ """
+ def dictitem(s):
+ t = s.split('=', 1)
+ if len(t) > 1:
+ return (t[0].lower(), t[1])
+ else:
+ return (t[0].lower(), True)
+
+ def dictvalue(t):
+ if t[1] is True:
+ return t[0]
+ else:
+ return '%s=%s' % (t[0], t[1])
+
+ if response.has_header('Cache-Control'):
+ cc = cc_delim_re.split(response['Cache-Control'])
+ cc = dict([dictitem(el) for el in cc])
+ else:
+ cc = {}
+
+ # If there's already a max-age header but we're being asked to set a new
+ # max-age, use the minimum of the two ages. In practice this happens when
+ # a decorator and a piece of middleware both operate on a given view.
+ if 'max-age' in cc and 'max_age' in kwargs:
+ kwargs['max_age'] = min(int(cc['max-age']), kwargs['max_age'])
+
+ # Allow overriding private caching and vice versa
+ if 'private' in cc and 'public' in kwargs:
+ del cc['private']
+ elif 'public' in cc and 'private' in kwargs:
+ del cc['public']
+
+ for (k, v) in kwargs.items():
+ cc[k.replace('_', '-')] = v
+ cc = ', '.join([dictvalue(el) for el in cc.items()])
+ response['Cache-Control'] = cc
+
+def get_max_age(response):
+ """
+ Returns the max-age from the response Cache-Control header as an integer
+ (or ``None`` if it wasn't found or wasn't an integer.
+ """
+ if not response.has_header('Cache-Control'):
+ return
+ cc = dict([_to_tuple(el) for el in
+ cc_delim_re.split(response['Cache-Control'])])
+ if 'max-age' in cc:
+ try:
+ return int(cc['max-age'])
+ except (ValueError, TypeError):
+ pass
+
+def _set_response_etag(response):
+ if not response.streaming:
+ response['ETag'] = '"%s"' % hashlib.md5(response.content).hexdigest()
+ return response
+
+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
+ if cache_timeout < 0:
+ cache_timeout = 0 # Can't have max-age negative
+ if settings.USE_ETAGS and not response.has_header('ETag'):
+ if hasattr(response, 'render') and callable(response.render):
+ response.add_post_render_callback(_set_response_etag)
+ else:
+ response = _set_response_etag(response)
+ if not response.has_header('Last-Modified'):
+ response['Last-Modified'] = http_date()
+ if not response.has_header('Expires'):
+ response['Expires'] = http_date(time.time() + cache_timeout)
+ patch_cache_control(response, max_age=cache_timeout)
+
+def add_never_cache_headers(response):
+ """
+ Adds headers to a response to indicate that a page should never be cached.
+ """
+ patch_response_headers(response, cache_timeout=-1)
+
+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.
+ if response.has_header('Vary'):
+ vary_headers = cc_delim_re.split(response['Vary'])
+ else:
+ vary_headers = []
+ # Use .lower() here so we treat headers as case-insensitive.
+ existing_headers = set([header.lower() for header in vary_headers])
+ additional_headers = [newheader for newheader in newheaders
+ if newheader.lower() not in existing_headers]
+ response['Vary'] = ', '.join(vary_headers + additional_headers)
+
+def has_vary_header(response, header_query):
+ """
+ Checks to see if the response has a given header name in its Vary header.
+ """
+ if not response.has_header('Vary'):
+ return False
+ vary_headers = cc_delim_re.split(response['Vary'])
+ existing_headers = set([header.lower() for header in vary_headers])
+ return header_query.lower() in existing_headers
+
+def _i18n_cache_key_suffix(request, cache_key):
+ """If necessary, adds the current locale or time zone to the cache key."""
+ if settings.USE_I18N or settings.USE_L10N:
+ # first check if LocaleMiddleware or another middleware added
+ # LANGUAGE_CODE to request, then fall back to the active language
+ # which in turn can also fall back to settings.LANGUAGE_CODE
+ cache_key += '.%s' % getattr(request, 'LANGUAGE_CODE', get_language())
+ if settings.USE_TZ:
+ # The datetime module doesn't restrict the output of tzname().
+ # Windows is known to use non-standard, locale-dependent names.
+ # User-defined tzinfo classes may return absolutely anything.
+ # Hence this paranoid conversion to create a valid cache key.
+ tz_name = force_text(get_current_timezone_name(), errors='ignore')
+ cache_key += '.%s' % tz_name.encode('ascii', 'ignore').decode('ascii').replace(' ', '_')
+ return cache_key
+
+def _generate_cache_key(request, method, headerlist, key_prefix):
+ """Returns a cache key from the headers given in the header list."""
+ ctx = hashlib.md5()
+ for header in headerlist:
+ value = request.META.get(header, None)
+ if value is not None:
+ ctx.update(force_bytes(value))
+ path = hashlib.md5(force_bytes(iri_to_uri(request.get_full_path())))
+ cache_key = 'views.decorators.cache.cache_page.%s.%s.%s.%s' % (
+ key_prefix, method, path.hexdigest(), ctx.hexdigest())
+ return _i18n_cache_key_suffix(request, cache_key)
+
+def _generate_cache_header_key(key_prefix, request):
+ """Returns a cache key for the header cache."""
+ path = hashlib.md5(force_bytes(iri_to_uri(request.get_full_path())))
+ cache_key = 'views.decorators.cache.cache_header.%s.%s' % (
+ key_prefix, path.hexdigest())
+ return _i18n_cache_key_suffix(request, cache_key)
+
+def get_cache_key(request, key_prefix=None, method='GET', cache=None):
+ """
+ Returns a cache key based on the request path and query. 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 = _generate_cache_header_key(key_prefix, request)
+ if cache is None:
+ cache = get_cache(settings.CACHE_MIDDLEWARE_ALIAS)
+ headerlist = cache.get(cache_key, None)
+ if headerlist is not None:
+ return _generate_cache_key(request, method, headerlist, key_prefix)
+ else:
+ return None
+
+def learn_cache_key(request, response, cache_timeout=None, key_prefix=None, cache=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 = _generate_cache_header_key(key_prefix, request)
+ if cache is None:
+ cache = get_cache(settings.CACHE_MIDDLEWARE_ALIAS)
+ if response.has_header('Vary'):
+ is_accept_language_redundant = settings.USE_I18N or settings.USE_L10N
+ # If i18n or l10n are used, the generated cache key will be suffixed
+ # with the current locale. Adding the raw value of Accept-Language is
+ # redundant in that case and would result in storing the same content
+ # under multiple keys in the cache. See #18191 for details.
+ headerlist = []
+ for header in cc_delim_re.split(response['Vary']):
+ header = header.upper().replace('-', '_')
+ if header == 'ACCEPT_LANGUAGE' and is_accept_language_redundant:
+ continue
+ headerlist.append('HTTP_' + header)
+ headerlist.sort()
+ cache.set(cache_key, headerlist, cache_timeout)
+ return _generate_cache_key(request, request.method, headerlist, key_prefix)
+ else:
+ # if there is no Vary header, we still need a cache key
+ # for the request.get_full_path()
+ cache.set(cache_key, [], cache_timeout)
+ return _generate_cache_key(request, request.method, [], key_prefix)
+
+
+def _to_tuple(s):
+ t = s.split('=',1)
+ if len(t) == 2:
+ return t[0].lower(), t[1]
+ return t[0].lower(), True