Ticket #17305: 17305.diff
File 17305.diff, 23.1 KB (added by , 13 years ago) |
---|
-
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: 48 48 49 49 """ 50 50 51 import hashlib 52 53 from django.utils.encoding import iri_to_uri 51 54 from django.conf import settings 52 55 from 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 56 from django.utils.cache import patch_response_headers, get_max_age, cc_delim_re 57 from django.utils.timezone import get_current_timezone_name 58 from django.utils.translation import get_language 54 59 60 class 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. 55 68 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 116 class UpdateCacheMiddleware(TwoPartCacheMiddlewareBase): 57 117 """ 58 118 Response-phase cache middleware that updates the cache if the response is 59 119 cacheable. … … class UpdateCacheMiddleware(object): 88 148 return False 89 149 return True 90 150 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 91 183 def process_response(self, request, response): 92 184 """Sets the cache, if needed.""" 93 185 if not self._should_update_cache(request, response): … … class UpdateCacheMiddleware(object): 106 198 return response 107 199 patch_response_headers(response, timeout) 108 200 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) 110 202 if hasattr(response, 'render') and callable(response.render): 111 203 response.add_post_render_callback( 112 204 lambda r: self.cache.set(cache_key, r, timeout) … … class UpdateCacheMiddleware(object): 115 207 self.cache.set(cache_key, response, timeout) 116 208 return response 117 209 118 class FetchFromCacheMiddleware( object):210 class FetchFromCacheMiddleware(TwoPartCacheMiddlewareBase): 119 211 """ 120 212 Request-phase cache middleware that fetches a page from the cache. 121 213 … … class FetchFromCacheMiddleware(object): 140 232 return None # Don't bother checking the cache. 141 233 142 234 # 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) 144 236 if cache_key is None: 145 237 request._cache_update_cache = True 146 238 return None # No cache information available, need to rebuild. 147 239 response = self.cache.get(cache_key, None) 148 240 # if it wasn't found and we are looking for a HEAD, try looking just for that 149 241 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) 151 243 response = self.cache.get(cache_key, None) 152 244 153 245 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 23 23 24 24 from django.conf import settings 25 25 from django.core.cache import get_cache 26 from django.utils.encoding import smart_str , iri_to_uri26 from django.utils.encoding import smart_str 27 27 from django.utils.http import http_date 28 from django.utils.timezone import get_current_timezone_name29 from django.utils.translation import get_language30 28 31 29 cc_delim_re = re.compile(r'\s*,\s*') 32 30 … … def patch_response_headers(response, cache_timeout=None): 122 120 response['Expires'] = http_date(time.time() + cache_timeout) 123 121 patch_cache_control(response, max_age=cache_timeout) 124 122 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 131 123 def patch_vary_headers(response, newheaders): 132 124 """ 133 125 Adds (or updates) the "Vary" header in the given HttpResponse object. … … def has_vary_header(response, header_query): 157 149 existing_headers = set([header.lower() for header in vary_headers]) 158 150 return header_query.lower() in existing_headers 159 151 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 added164 # LANGUAGE_CODE to request, then fall back to the active language165 # which in turn can also fall back to settings.LANGUAGE_CODE166 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_key170 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 used193 in the request phase because it pulls the list of headers to take into194 account from the global path registry and uses those to build a cache key195 to check against.196 197 If there is no headerlist stored, the page needs to be rebuilt, so this198 function returns None.199 """200 if key_prefix is None:201 key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX202 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 None210 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 the214 response object. It stores those headers in a global path registry so that215 later access to that path will know what headers to take into account216 without building the response object itself. The headers are named in the217 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 same220 cache as the pages themselves. If the cache ages some data out of the221 cache, this just means that we have to build the response once to get at222 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_PREFIX226 if cache_timeout is None:227 cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS228 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 key238 # for the request.get_full_path()239 cache.set(cache_key, [], cache_timeout)240 return _generate_cache_key(request, request.method, [], key_prefix)241 242 152 243 153 def _to_tuple(s): 244 154 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 1 1 from functools import wraps 2 2 from django.utils.decorators import decorator_from_middleware_with_args, available_attrs 3 from django.utils.cache import patch_cache_control, add_never_cache_headers3 from django.utils.cache import patch_cache_control, patch_response_headers 4 4 from django.middleware.cache import CacheMiddleware 5 5 6 6 … … def never_cache(view_func): 86 86 @wraps(view_func, assigned=available_attrs(view_func)) 87 87 def _wrapped_view_func(request, *args, **kwargs): 88 88 response = view_func(request, *args, **kwargs) 89 add_never_cache_headers(response)89 patch_response_headers(response, cache_timeout=0) 90 90 return response 91 91 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, 19 19 from django.db import router 20 20 from django.http import HttpResponse, HttpRequest, QueryDict 21 21 from django.middleware.cache import (FetchFromCacheMiddleware, 22 UpdateCacheMiddleware, CacheMiddleware )22 UpdateCacheMiddleware, CacheMiddleware, TwoPartCacheMiddlewareBase) 23 23 from django.template import Template 24 24 from django.template.response import TemplateResponse 25 25 from django.test import TestCase, TransactionTestCase, RequestFactory 26 26 from django.test.utils import (get_warnings_state, restore_warnings_state, 27 27 override_settings) 28 28 from 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)29 from django.utils.cache import (patch_vary_headers, 30 patch_cache_control, patch_response_headers) 31 31 from django.views.decorators.cache import cache_page 32 32 33 33 from .models import Poll, expensive_calculation … … class CacheUtils(TestCase): 998 998 """TestCase for django.utils.cache functions.""" 999 999 1000 1000 def setUp(self): 1001 self.path = '/cache/test/'1002 1001 self.cache = get_cache('default') 1003 1002 1004 1003 def tearDown(self): 1005 1004 self.cache.clear() 1006 1005 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 = method1014 request.path = request.path_info = "/cache/%s" % path1015 return request1016 1017 1006 def test_patch_vary_headers(self): 1018 1007 headers = ( 1019 1008 # Initial vary, new headers, resulting vary. … … class CacheUtils(TestCase): 1034 1023 patch_vary_headers(response, newheaders) 1035 1024 self.assertEqual(response['Vary'], resulting_vary) 1036 1025 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 hash1065 learn_cache_key(request, response)1066 self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')1067 1068 1026 def test_patch_cache_control(self): 1069 1027 tests = ( 1070 1028 # Initial Cache-Control, kwargs to patch_cache_control, expected Cache-Control parts … … class CacheMiddlewareTest(TestCase): 1342 1300 1343 1301 def setUp(self): 1344 1302 self.factory = RequestFactory() 1303 self.path = '/cache/test/' 1345 1304 self.default_cache = get_cache('default') 1346 1305 self.other_cache = get_cache('other') 1347 1306 … … class CacheMiddlewareTest(TestCase): 1381 1340 self.assertEqual(as_view_decorator_with_custom.cache_alias, 'other') 1382 1341 self.assertEqual(as_view_decorator_with_custom.cache_anonymous_only, True) 1383 1342 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 1384 1385 def test_middleware(self): 1385 1386 middleware = CacheMiddleware() 1386 1387 prefix_middleware = CacheMiddleware(key_prefix='prefix1') … … class TestWithTemplateResponse(TestCase): 1624 1625 response = TemplateResponse(HttpResponse(), Template("This is a test")) 1625 1626 key_prefix = 'localprefix' 1626 1627 # 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) 1628 1629 # 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') 1631 1632 # 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') 1634 1635 1635 1636 def test_get_cache_key_with_query(self): 1636 1637 request = self._get_request(self.path + '?test=1') 1637 1638 response = TemplateResponse(HttpResponse(), Template("This is a test")) 1638 1639 # 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) 1640 1641 # Set headers to an empty list. 1641 learn_cache_key(request, response)1642 UpdateCacheMiddleware.learn_cache_key(request, response) 1642 1643 # 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') 1644 1645 1645 1646 @override_settings(USE_ETAGS=False) 1646 1647 def test_without_etag(self):