Ticket #17305: 17305.diff

File 17305.diff, 23.1 KB (added by Preston Holmes, 12 years ago)

adding extension to initial diff for proper formatting

  • django/middleware/cache.py

    diff --git a/django/middleware/cache.py b/django/middleware/cache.py
    index 34bf0ca..049238e 100644
    a b More details about how the caching works:  
    4848
    4949"""
    5050
     51import hashlib
     52
     53from django.utils.encoding import iri_to_uri
    5154from django.conf import settings
    5255from django.core.cache import get_cache, DEFAULT_CACHE_ALIAS
    53 from django.utils.cache import get_cache_key, learn_cache_key, patch_response_headers, get_max_age
     56from django.utils.cache import patch_response_headers, get_max_age, cc_delim_re
     57from django.utils.timezone import get_current_timezone_name
     58from django.utils.translation import get_language
    5459
     60class TwoPartCacheMiddlewareBase(object):
     61    @classmethod
     62    def get_cache_key(cls, request, key_prefix=None, method='GET', cache=None):
     63        """
     64        Returns a cache key based on the request path and query. It can be used
     65        in the request phase because it pulls the list of headers to take into
     66        account from the global path registry and uses those to build a cache key
     67        to check against.
    5568
    56 class UpdateCacheMiddleware(object):
     69        If there is no headerlist stored, the page needs to be rebuilt, so this
     70        function returns None.
     71        """
     72        if key_prefix is None:
     73            key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
     74        cache_key = cls._generate_cache_header_key(key_prefix, request)
     75        if cache is None:
     76            cache = get_cache(settings.CACHE_MIDDLEWARE_ALIAS)
     77        headerlist = cache.get(cache_key, None)
     78        if headerlist is not None:
     79            return cls._generate_cache_key(request, method, headerlist, key_prefix)
     80        else:
     81            return None
     82
     83    @classmethod
     84    def _i18n_cache_key_suffix(cls, request, cache_key):
     85        """If necessary, adds the current locale or time zone to the cache key."""
     86        if settings.USE_I18N or settings.USE_L10N:
     87            # first check if LocaleMiddleware or another middleware added
     88            # LANGUAGE_CODE to request, then fall back to the active language
     89            # which in turn can also fall back to settings.LANGUAGE_CODE
     90            cache_key += '.%s' % getattr(request, 'LANGUAGE_CODE', get_language())
     91        if settings.USE_TZ:
     92            cache_key += '.%s' % get_current_timezone_name()
     93        return cache_key
     94
     95    @classmethod
     96    def _generate_cache_key(cls, request, method, headerlist, key_prefix):
     97        """Returns a cache key from the headers given in the header list."""
     98        ctx = hashlib.md5()
     99        for header in headerlist:
     100            value = request.META.get(header, None)
     101            if value is not None:
     102                ctx.update(value)
     103        path = hashlib.md5(iri_to_uri(request.get_full_path()))
     104        cache_key = 'views.decorators.cache.cache_page.%s.%s.%s.%s' % (
     105            key_prefix, method, path.hexdigest(), ctx.hexdigest())
     106        return cls._i18n_cache_key_suffix(request, cache_key)
     107
     108    @classmethod
     109    def _generate_cache_header_key(cls, key_prefix, request):
     110        """Returns a cache key for the header cache."""
     111        path = hashlib.md5(iri_to_uri(request.get_full_path()))
     112        cache_key = 'views.decorators.cache.cache_header.%s.%s' % (
     113            key_prefix, path.hexdigest())
     114        return cls._i18n_cache_key_suffix(request, cache_key)
     115
     116class UpdateCacheMiddleware(TwoPartCacheMiddlewareBase):
    57117    """
    58118    Response-phase cache middleware that updates the cache if the response is
    59119    cacheable.
    class UpdateCacheMiddleware(object):  
    88148                return False
    89149        return True
    90150
     151    @classmethod
     152    def learn_cache_key(cls, request, response, cache_timeout=None, key_prefix=None, cache=None):
     153        """
     154        Learns what headers to take into account for some request path from the
     155        response object. It stores those headers in a global path registry so that
     156        later access to that path will know what headers to take into account
     157        without building the response object itself. The headers are named in the
     158        Vary header of the response, but we want to prevent response generation.
     159
     160        The list of headers to use for cache key generation is stored in the same
     161        cache as the pages themselves. If the cache ages some data out of the
     162        cache, this just means that we have to build the response once to get at
     163        the Vary header and so at the list of headers to use for the cache key.
     164        """
     165        if key_prefix is None:
     166            key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
     167        if cache_timeout is None:
     168            cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
     169        cache_key = cls._generate_cache_header_key(key_prefix, request)
     170        if cache is None:
     171            cache = get_cache(settings.CACHE_MIDDLEWARE_ALIAS)
     172        if response.has_header('Vary'):
     173            headerlist = ['HTTP_'+header.upper().replace('-', '_')
     174                          for header in cc_delim_re.split(response['Vary'])]
     175            cache.set(cache_key, headerlist, cache_timeout)
     176            return cls._generate_cache_key(request, request.method, headerlist, key_prefix)
     177        else:
     178            # if there is no Vary header, we still need a cache key
     179            # for the request.get_full_path()
     180            cache.set(cache_key, [], cache_timeout)
     181            return cls._generate_cache_key(request, request.method, [], key_prefix)
     182
    91183    def process_response(self, request, response):
    92184        """Sets the cache, if needed."""
    93185        if not self._should_update_cache(request, response):
    class UpdateCacheMiddleware(object):  
    106198            return response
    107199        patch_response_headers(response, timeout)
    108200        if timeout:
    109             cache_key = learn_cache_key(request, response, timeout, self.key_prefix, cache=self.cache)
     201            cache_key = self.learn_cache_key(request, response, timeout, self.key_prefix, cache=self.cache)
    110202            if hasattr(response, 'render') and callable(response.render):
    111203                response.add_post_render_callback(
    112204                    lambda r: self.cache.set(cache_key, r, timeout)
    class UpdateCacheMiddleware(object):  
    115207                self.cache.set(cache_key, response, timeout)
    116208        return response
    117209
    118 class FetchFromCacheMiddleware(object):
     210class FetchFromCacheMiddleware(TwoPartCacheMiddlewareBase):
    119211    """
    120212    Request-phase cache middleware that fetches a page from the cache.
    121213
    class FetchFromCacheMiddleware(object):  
    140232            return None # Don't bother checking the cache.
    141233
    142234        # try and get the cached GET response
    143         cache_key = get_cache_key(request, self.key_prefix, 'GET', cache=self.cache)
     235        cache_key = self.get_cache_key(request, self.key_prefix, 'GET', cache=self.cache)
    144236        if cache_key is None:
    145237            request._cache_update_cache = True
    146238            return None # No cache information available, need to rebuild.
    147239        response = self.cache.get(cache_key, None)
    148240        # if it wasn't found and we are looking for a HEAD, try looking just for that
    149241        if response is None and request.method == 'HEAD':
    150             cache_key = get_cache_key(request, self.key_prefix, 'HEAD', cache=self.cache)
     242            cache_key = self.get_cache_key(request, self.key_prefix, 'HEAD', cache=self.cache)
    151243            response = self.cache.get(cache_key, None)
    152244
    153245        if response is None:
  • django/utils/cache.py

    diff --git a/django/utils/cache.py b/django/utils/cache.py
    index 1015c2f..9448aae 100644
    a b import time  
    2323
    2424from django.conf import settings
    2525from django.core.cache import get_cache
    26 from django.utils.encoding import smart_str, iri_to_uri
     26from django.utils.encoding import smart_str
    2727from django.utils.http import http_date
    28 from django.utils.timezone import get_current_timezone_name
    29 from django.utils.translation import get_language
    3028
    3129cc_delim_re = re.compile(r'\s*,\s*')
    3230
    def patch_response_headers(response, cache_timeout=None):  
    122120        response['Expires'] = http_date(time.time() + cache_timeout)
    123121    patch_cache_control(response, max_age=cache_timeout)
    124122
    125 def 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 
    131123def patch_vary_headers(response, newheaders):
    132124    """
    133125    Adds (or updates) the "Vary" header in the given HttpResponse object.
    def has_vary_header(response, header_query):  
    157149    existing_headers = set([header.lower() for header in vary_headers])
    158150    return header_query.lower() in existing_headers
    159151
    160 def _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         cache_key += '.%s' % get_current_timezone_name()
    169     return cache_key
    170 
    171 def _generate_cache_key(request, method, headerlist, key_prefix):
    172     """Returns a cache key from the headers given in the header list."""
    173     ctx = hashlib.md5()
    174     for header in headerlist:
    175         value = request.META.get(header, None)
    176         if value is not None:
    177             ctx.update(value)
    178     path = hashlib.md5(iri_to_uri(request.get_full_path()))
    179     cache_key = 'views.decorators.cache.cache_page.%s.%s.%s.%s' % (
    180         key_prefix, method, path.hexdigest(), ctx.hexdigest())
    181     return _i18n_cache_key_suffix(request, cache_key)
    182 
    183 def _generate_cache_header_key(key_prefix, request):
    184     """Returns a cache key for the header cache."""
    185     path = hashlib.md5(iri_to_uri(request.get_full_path()))
    186     cache_key = 'views.decorators.cache.cache_header.%s.%s' % (
    187         key_prefix, path.hexdigest())
    188     return _i18n_cache_key_suffix(request, cache_key)
    189 
    190 def get_cache_key(request, key_prefix=None, method='GET', cache=None):
    191     """
    192     Returns a cache key based on the request path and query. It can be used
    193     in the request phase because it pulls the list of headers to take into
    194     account from the global path registry and uses those to build a cache key
    195     to check against.
    196 
    197     If there is no headerlist stored, the page needs to be rebuilt, so this
    198     function returns None.
    199     """
    200     if key_prefix is None:
    201         key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
    202     cache_key = _generate_cache_header_key(key_prefix, request)
    203     if cache is None:
    204         cache = get_cache(settings.CACHE_MIDDLEWARE_ALIAS)
    205     headerlist = cache.get(cache_key, None)
    206     if headerlist is not None:
    207         return _generate_cache_key(request, method, headerlist, key_prefix)
    208     else:
    209         return None
    210 
    211 def learn_cache_key(request, response, cache_timeout=None, key_prefix=None, cache=None):
    212     """
    213     Learns what headers to take into account for some request path from the
    214     response object. It stores those headers in a global path registry so that
    215     later access to that path will know what headers to take into account
    216     without building the response object itself. The headers are named in the
    217     Vary header of the response, but we want to prevent response generation.
    218 
    219     The list of headers to use for cache key generation is stored in the same
    220     cache as the pages themselves. If the cache ages some data out of the
    221     cache, this just means that we have to build the response once to get at
    222     the Vary header and so at the list of headers to use for the cache key.
    223     """
    224     if key_prefix is None:
    225         key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
    226     if cache_timeout is None:
    227         cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
    228     cache_key = _generate_cache_header_key(key_prefix, request)
    229     if cache is None:
    230         cache = get_cache(settings.CACHE_MIDDLEWARE_ALIAS)
    231     if response.has_header('Vary'):
    232         headerlist = ['HTTP_'+header.upper().replace('-', '_')
    233                       for header in cc_delim_re.split(response['Vary'])]
    234         cache.set(cache_key, headerlist, cache_timeout)
    235         return _generate_cache_key(request, request.method, headerlist, key_prefix)
    236     else:
    237         # if there is no Vary header, we still need a cache key
    238         # for the request.get_full_path()
    239         cache.set(cache_key, [], cache_timeout)
    240         return _generate_cache_key(request, request.method, [], key_prefix)
    241 
    242152
    243153def _to_tuple(s):
    244154    t = s.split('=',1)
  • django/views/decorators/cache.py

    diff --git a/django/views/decorators/cache.py b/django/views/decorators/cache.py
    index a39cc54..2847e05 100644
    a b  
    11from functools import wraps
    22from django.utils.decorators import decorator_from_middleware_with_args, available_attrs
    3 from django.utils.cache import patch_cache_control, add_never_cache_headers
     3from django.utils.cache import patch_cache_control, patch_response_headers
    44from django.middleware.cache import CacheMiddleware
    55
    66
    def never_cache(view_func):  
    8686    @wraps(view_func, assigned=available_attrs(view_func))
    8787    def _wrapped_view_func(request, *args, **kwargs):
    8888        response = view_func(request, *args, **kwargs)
    89         add_never_cache_headers(response)
     89        patch_response_headers(response, cache_timeout=0)
    9090        return response
    9191    return _wrapped_view_func
  • tests/regressiontests/cache/tests.py

    diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py
    index 307588c..ea13421 100644
    a b from django.core.cache.backends.base import (CacheKeyWarning,  
    1919from django.db import router
    2020from django.http import HttpResponse, HttpRequest, QueryDict
    2121from django.middleware.cache import (FetchFromCacheMiddleware,
    22     UpdateCacheMiddleware, CacheMiddleware)
     22    UpdateCacheMiddleware, CacheMiddleware, TwoPartCacheMiddlewareBase)
    2323from django.template import Template
    2424from django.template.response import TemplateResponse
    2525from django.test import TestCase, TransactionTestCase, RequestFactory
    2626from django.test.utils import (get_warnings_state, restore_warnings_state,
    2727    override_settings)
    2828from django.utils import timezone, translation, unittest
    29 from django.utils.cache import (patch_vary_headers, get_cache_key,
    30     learn_cache_key, patch_cache_control, patch_response_headers)
     29from django.utils.cache import (patch_vary_headers,
     30    patch_cache_control, patch_response_headers)
    3131from django.views.decorators.cache import cache_page
    3232
    3333from .models import Poll, expensive_calculation
    class CacheUtils(TestCase):  
    998998    """TestCase for django.utils.cache functions."""
    999999
    10001000    def setUp(self):
    1001         self.path = '/cache/test/'
    10021001        self.cache = get_cache('default')
    10031002
    10041003    def tearDown(self):
    10051004        self.cache.clear()
    10061005
    1007     def _get_request(self, path, method='GET'):
    1008         request = HttpRequest()
    1009         request.META = {
    1010             'SERVER_NAME': 'testserver',
    1011             'SERVER_PORT': 80,
    1012         }
    1013         request.method = method
    1014         request.path = request.path_info = "/cache/%s" % path
    1015         return request
    1016 
    10171006    def test_patch_vary_headers(self):
    10181007        headers = (
    10191008            # Initial vary, new headers, resulting vary.
    class CacheUtils(TestCase):  
    10341023            patch_vary_headers(response, newheaders)
    10351024            self.assertEqual(response['Vary'], resulting_vary)
    10361025
    1037     def test_get_cache_key(self):
    1038         request = self._get_request(self.path)
    1039         response = HttpResponse()
    1040         key_prefix = 'localprefix'
    1041         # Expect None if no headers have been set yet.
    1042         self.assertEqual(get_cache_key(request), None)
    1043         # Set headers to an empty list.
    1044         learn_cache_key(request, response)
    1045         self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
    1046         # Verify that a specified key_prefix is taken into account.
    1047         learn_cache_key(request, response, key_prefix=key_prefix)
    1048         self.assertEqual(get_cache_key(request, key_prefix=key_prefix), 'views.decorators.cache.cache_page.localprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
    1049 
    1050     def test_get_cache_key_with_query(self):
    1051         request = self._get_request(self.path + '?test=1')
    1052         response = HttpResponse()
    1053         # Expect None if no headers have been set yet.
    1054         self.assertEqual(get_cache_key(request), None)
    1055         # Set headers to an empty list.
    1056         learn_cache_key(request, response)
    1057         # Verify that the querystring is taken into account.
    1058         self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.bd889c5a59603af44333ed21504db3cd.d41d8cd98f00b204e9800998ecf8427e')
    1059 
    1060     def test_learn_cache_key(self):
    1061         request = self._get_request(self.path, 'HEAD')
    1062         response = HttpResponse()
    1063         response['Vary'] = 'Pony'
    1064         # Make sure that the Vary header is added to the key hash
    1065         learn_cache_key(request, response)
    1066         self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
    1067 
    10681026    def test_patch_cache_control(self):
    10691027        tests = (
    10701028            # Initial Cache-Control, kwargs to patch_cache_control, expected Cache-Control parts
    class CacheMiddlewareTest(TestCase):  
    13421300
    13431301    def setUp(self):
    13441302        self.factory = RequestFactory()
     1303        self.path = '/cache/test/'
    13451304        self.default_cache = get_cache('default')
    13461305        self.other_cache = get_cache('other')
    13471306
    class CacheMiddlewareTest(TestCase):  
    13811340        self.assertEqual(as_view_decorator_with_custom.cache_alias, 'other')
    13821341        self.assertEqual(as_view_decorator_with_custom.cache_anonymous_only, True)
    13831342
     1343    def _get_request(self, path, method='GET'):
     1344        request = HttpRequest()
     1345        request.META = {
     1346            'SERVER_NAME': 'testserver',
     1347            'SERVER_PORT': 80,
     1348        }
     1349        request.method = method
     1350        request.path = request.path_info = "/cache/%s" % path
     1351        return request
     1352
     1353    def test_get_cache_key(self):
     1354        request = self._get_request(self.path)
     1355        response = HttpResponse()
     1356        key_prefix = 'localprefix'
     1357        # Expect None if no headers have been set yet.
     1358        self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request), None)
     1359        # Set headers to an empty list.
     1360        UpdateCacheMiddleware.learn_cache_key(request, response)
     1361        self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
     1362        # Verify that a specified key_prefix is taken into account.
     1363        UpdateCacheMiddleware.learn_cache_key(request, response, key_prefix=key_prefix)
     1364        self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request, key_prefix=key_prefix), 'views.decorators.cache.cache_page.localprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
     1365
     1366    def test_get_cache_key_with_query(self):
     1367        request = self._get_request(self.path + '?test=1')
     1368        response = HttpResponse()
     1369        # Expect None if no headers have been set yet.
     1370        self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request), None)
     1371        # Set headers to an empty list.
     1372        UpdateCacheMiddleware.learn_cache_key(request, response)
     1373        # Verify that the querystring is taken into account.
     1374        self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.bd889c5a59603af44333ed21504db3cd.d41d8cd98f00b204e9800998ecf8427e')
     1375
     1376    def test_learn_cache_key(self):
     1377        request = self._get_request(self.path, 'HEAD')
     1378        response = HttpResponse()
     1379        response['Vary'] = 'Pony'
     1380        # Make sure that the Vary header is added to the key hash
     1381        UpdateCacheMiddleware.learn_cache_key(request, response)
     1382        self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
     1383
     1384
    13841385    def test_middleware(self):
    13851386        middleware = CacheMiddleware()
    13861387        prefix_middleware = CacheMiddleware(key_prefix='prefix1')
    class TestWithTemplateResponse(TestCase):  
    16241625        response = TemplateResponse(HttpResponse(), Template("This is a test"))
    16251626        key_prefix = 'localprefix'
    16261627        # Expect None if no headers have been set yet.
    1627         self.assertEqual(get_cache_key(request), None)
     1628        self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request), None)
    16281629        # Set headers to an empty list.
    1629         learn_cache_key(request, response)
    1630         self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
     1630        UpdateCacheMiddleware.learn_cache_key(request, response)
     1631        self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
    16311632        # Verify that a specified key_prefix is taken into account.
    1632         learn_cache_key(request, response, key_prefix=key_prefix)
    1633         self.assertEqual(get_cache_key(request, key_prefix=key_prefix), 'views.decorators.cache.cache_page.localprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
     1633        UpdateCacheMiddleware.learn_cache_key(request, response, key_prefix=key_prefix)
     1634        self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request, key_prefix=key_prefix), 'views.decorators.cache.cache_page.localprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
    16341635
    16351636    def test_get_cache_key_with_query(self):
    16361637        request = self._get_request(self.path + '?test=1')
    16371638        response = TemplateResponse(HttpResponse(), Template("This is a test"))
    16381639        # Expect None if no headers have been set yet.
    1639         self.assertEqual(get_cache_key(request), None)
     1640        self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request), None)
    16401641        # Set headers to an empty list.
    1641         learn_cache_key(request, response)
     1642        UpdateCacheMiddleware.learn_cache_key(request, response)
    16421643        # Verify that the querystring is taken into account.
    1643         self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.bd889c5a59603af44333ed21504db3cd.d41d8cd98f00b204e9800998ecf8427e')
     1644        self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.bd889c5a59603af44333ed21504db3cd.d41d8cd98f00b204e9800998ecf8427e')
    16441645
    16451646    @override_settings(USE_ETAGS=False)
    16461647    def test_without_etag(self):
Back to Top