PageRenderTime 28ms CodeModel.GetById 9ms app.highlight 16ms RepoModel.GetById 0ms app.codeStats 0ms

/bangkokhotel/lib/python2.5/site-packages/django/utils/cache.py

https://bitbucket.org/luisrodriguez/bangkokhotel
Python | 252 lines | 250 code | 0 blank | 2 comment | 0 complexity | d4c8f2681ffdebdc93f1b8428a00379e MD5 | raw file
  1"""
  2This module contains helper functions for controlling caching. It does so by
  3managing the "Vary" header of responses. It includes functions to patch the
  4header of response objects directly and decorators that change functions to do
  5that header-patching themselves.
  6
  7For information on the Vary header, see:
  8
  9    http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.44
 10
 11Essentially, the "Vary" HTTP header defines which headers a cache should take
 12into account when building its cache key. Requests with the same path but
 13different header content for headers named in "Vary" need to get different
 14cache keys to prevent delivery of wrong content.
 15
 16An example: i18n middleware would need to distinguish caches by the
 17"Accept-language" header.
 18"""
 19
 20import hashlib
 21import re
 22import time
 23
 24from django.conf import settings
 25from django.core.cache import get_cache
 26from django.utils.encoding import smart_str, iri_to_uri, force_unicode
 27from django.utils.http import http_date
 28from django.utils.timezone import get_current_timezone_name
 29from django.utils.translation import get_language
 30
 31cc_delim_re = re.compile(r'\s*,\s*')
 32
 33def patch_cache_control(response, **kwargs):
 34    """
 35    This function patches the Cache-Control header by adding all
 36    keyword arguments to it. The transformation is as follows:
 37
 38    * All keyword parameter names are turned to lowercase, and underscores
 39      are converted to hyphens.
 40    * If the value of a parameter is True (exactly True, not just a
 41      true value), only the parameter name is added to the header.
 42    * All other parameters are added with their value, after applying
 43      str() to it.
 44    """
 45    def dictitem(s):
 46        t = s.split('=', 1)
 47        if len(t) > 1:
 48            return (t[0].lower(), t[1])
 49        else:
 50            return (t[0].lower(), True)
 51
 52    def dictvalue(t):
 53        if t[1] is True:
 54            return t[0]
 55        else:
 56            return t[0] + '=' + smart_str(t[1])
 57
 58    if response.has_header('Cache-Control'):
 59        cc = cc_delim_re.split(response['Cache-Control'])
 60        cc = dict([dictitem(el) for el in cc])
 61    else:
 62        cc = {}
 63
 64    # If there's already a max-age header but we're being asked to set a new
 65    # max-age, use the minimum of the two ages. In practice this happens when
 66    # a decorator and a piece of middleware both operate on a given view.
 67    if 'max-age' in cc and 'max_age' in kwargs:
 68        kwargs['max_age'] = min(cc['max-age'], kwargs['max_age'])
 69
 70    # Allow overriding private caching and vice versa
 71    if 'private' in cc and 'public' in kwargs:
 72        del cc['private']
 73    elif 'public' in cc and 'private' in kwargs:
 74        del cc['public']
 75
 76    for (k, v) in kwargs.items():
 77        cc[k.replace('_', '-')] = v
 78    cc = ', '.join([dictvalue(el) for el in cc.items()])
 79    response['Cache-Control'] = cc
 80
 81def get_max_age(response):
 82    """
 83    Returns the max-age from the response Cache-Control header as an integer
 84    (or ``None`` if it wasn't found or wasn't an integer.
 85    """
 86    if not response.has_header('Cache-Control'):
 87        return
 88    cc = dict([_to_tuple(el) for el in
 89        cc_delim_re.split(response['Cache-Control'])])
 90    if 'max-age' in cc:
 91        try:
 92            return int(cc['max-age'])
 93        except (ValueError, TypeError):
 94            pass
 95
 96def _set_response_etag(response):
 97    response['ETag'] = '"%s"' % hashlib.md5(response.content).hexdigest()
 98    return response
 99
100def patch_response_headers(response, cache_timeout=None):
101    """
102    Adds some useful headers to the given HttpResponse object:
103        ETag, Last-Modified, Expires and Cache-Control
104
105    Each header is only added if it isn't already set.
106
107    cache_timeout is in seconds. The CACHE_MIDDLEWARE_SECONDS setting is used
108    by default.
109    """
110    if cache_timeout is None:
111        cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
112    if cache_timeout < 0:
113        cache_timeout = 0 # Can't have max-age negative
114    if settings.USE_ETAGS and not response.has_header('ETag'):
115        if hasattr(response, 'render') and callable(response.render):
116            response.add_post_render_callback(_set_response_etag)
117        else:
118            response = _set_response_etag(response)
119    if not response.has_header('Last-Modified'):
120        response['Last-Modified'] = http_date()
121    if not response.has_header('Expires'):
122        response['Expires'] = http_date(time.time() + cache_timeout)
123    patch_cache_control(response, max_age=cache_timeout)
124
125def add_never_cache_headers(response):
126    """
127    Adds headers to a response to indicate that a page should never be cached.
128    """
129    patch_response_headers(response, cache_timeout=-1)
130
131def patch_vary_headers(response, newheaders):
132    """
133    Adds (or updates) the "Vary" header in the given HttpResponse object.
134    newheaders is a list of header names that should be in "Vary". Existing
135    headers in "Vary" aren't removed.
136    """
137    # Note that we need to keep the original order intact, because cache
138    # implementations may rely on the order of the Vary contents in, say,
139    # computing an MD5 hash.
140    if response.has_header('Vary'):
141        vary_headers = cc_delim_re.split(response['Vary'])
142    else:
143        vary_headers = []
144    # Use .lower() here so we treat headers as case-insensitive.
145    existing_headers = set([header.lower() for header in vary_headers])
146    additional_headers = [newheader for newheader in newheaders
147                          if newheader.lower() not in existing_headers]
148    response['Vary'] = ', '.join(vary_headers + additional_headers)
149
150def has_vary_header(response, header_query):
151    """
152    Checks to see if the response has a given header name in its Vary header.
153    """
154    if not response.has_header('Vary'):
155        return False
156    vary_headers = cc_delim_re.split(response['Vary'])
157    existing_headers = set([header.lower() for header in vary_headers])
158    return header_query.lower() in existing_headers
159
160def _i18n_cache_key_suffix(request, cache_key):
161    """If necessary, adds the current locale or time zone to the cache key."""
162    if settings.USE_I18N or settings.USE_L10N:
163        # first check if LocaleMiddleware or another middleware added
164        # LANGUAGE_CODE to request, then fall back to the active language
165        # which in turn can also fall back to settings.LANGUAGE_CODE
166        cache_key += '.%s' % getattr(request, 'LANGUAGE_CODE', get_language())
167    if settings.USE_TZ:
168        # The datetime module doesn't restrict the output of tzname().
169        # Windows is known to use non-standard, locale-dependant names.
170        # User-defined tzinfo classes may return absolutely anything.
171        # Hence this paranoid conversion to create a valid cache key.
172        tz_name = force_unicode(get_current_timezone_name(), errors='ignore')
173        cache_key += '.%s' % tz_name.encode('ascii', 'ignore').replace(' ', '_')
174    return cache_key
175
176def _generate_cache_key(request, method, headerlist, key_prefix):
177    """Returns a cache key from the headers given in the header list."""
178    ctx = hashlib.md5()
179    for header in headerlist:
180        value = request.META.get(header, None)
181        if value is not None:
182            ctx.update(value)
183    path = hashlib.md5(iri_to_uri(request.get_full_path()))
184    cache_key = 'views.decorators.cache.cache_page.%s.%s.%s.%s' % (
185        key_prefix, method, path.hexdigest(), ctx.hexdigest())
186    return _i18n_cache_key_suffix(request, cache_key)
187
188def _generate_cache_header_key(key_prefix, request):
189    """Returns a cache key for the header cache."""
190    path = hashlib.md5(iri_to_uri(request.get_full_path()))
191    cache_key = 'views.decorators.cache.cache_header.%s.%s' % (
192        key_prefix, path.hexdigest())
193    return _i18n_cache_key_suffix(request, cache_key)
194
195def get_cache_key(request, key_prefix=None, method='GET', cache=None):
196    """
197    Returns a cache key based on the request path and query. It can be used
198    in the request phase because it pulls the list of headers to take into
199    account from the global path registry and uses those to build a cache key
200    to check against.
201
202    If there is no headerlist stored, the page needs to be rebuilt, so this
203    function returns None.
204    """
205    if key_prefix is None:
206        key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
207    cache_key = _generate_cache_header_key(key_prefix, request)
208    if cache is None:
209        cache = get_cache(settings.CACHE_MIDDLEWARE_ALIAS)
210    headerlist = cache.get(cache_key, None)
211    if headerlist is not None:
212        return _generate_cache_key(request, method, headerlist, key_prefix)
213    else:
214        return None
215
216def learn_cache_key(request, response, cache_timeout=None, key_prefix=None, cache=None):
217    """
218    Learns what headers to take into account for some request path from the
219    response object. It stores those headers in a global path registry so that
220    later access to that path will know what headers to take into account
221    without building the response object itself. The headers are named in the
222    Vary header of the response, but we want to prevent response generation.
223
224    The list of headers to use for cache key generation is stored in the same
225    cache as the pages themselves. If the cache ages some data out of the
226    cache, this just means that we have to build the response once to get at
227    the Vary header and so at the list of headers to use for the cache key.
228    """
229    if key_prefix is None:
230        key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
231    if cache_timeout is None:
232        cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
233    cache_key = _generate_cache_header_key(key_prefix, request)
234    if cache is None:
235        cache = get_cache(settings.CACHE_MIDDLEWARE_ALIAS)
236    if response.has_header('Vary'):
237        headerlist = ['HTTP_'+header.upper().replace('-', '_')
238                      for header in cc_delim_re.split(response['Vary'])]
239        cache.set(cache_key, headerlist, cache_timeout)
240        return _generate_cache_key(request, request.method, headerlist, key_prefix)
241    else:
242        # if there is no Vary header, we still need a cache key
243        # for the request.get_full_path()
244        cache.set(cache_key, [], cache_timeout)
245        return _generate_cache_key(request, request.method, [], key_prefix)
246
247
248def _to_tuple(s):
249    t = s.split('=',1)
250    if len(t) == 2:
251        return t[0].lower(), t[1]
252    return t[0].lower(), True