Ticket #17305: diff (2)

File diff (2), 23.1 KB (added by Yeago, 12 years ago)
Line 
1diff --git a/django/middleware/cache.py b/django/middleware/cache.py
2index 34bf0ca..049238e 100644
3--- a/django/middleware/cache.py
4+++ b/django/middleware/cache.py
5@@ -48,12 +48,72 @@ More details about how the caching works:
6
7 """
8
9+import hashlib
10+
11+from django.utils.encoding import iri_to_uri
12 from django.conf import settings
13 from django.core.cache import get_cache, DEFAULT_CACHE_ALIAS
14-from django.utils.cache import get_cache_key, learn_cache_key, patch_response_headers, get_max_age
15+from django.utils.cache import patch_response_headers, get_max_age, cc_delim_re
16+from django.utils.timezone import get_current_timezone_name
17+from django.utils.translation import get_language
18
19+class TwoPartCacheMiddlewareBase(object):
20+ @classmethod
21+ def get_cache_key(cls, request, key_prefix=None, method='GET', cache=None):
22+ """
23+ Returns a cache key based on the request path and query. It can be used
24+ in the request phase because it pulls the list of headers to take into
25+ account from the global path registry and uses those to build a cache key
26+ to check against.
27
28-class UpdateCacheMiddleware(object):
29+ If there is no headerlist stored, the page needs to be rebuilt, so this
30+ function returns None.
31+ """
32+ if key_prefix is None:
33+ key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
34+ cache_key = cls._generate_cache_header_key(key_prefix, request)
35+ if cache is None:
36+ cache = get_cache(settings.CACHE_MIDDLEWARE_ALIAS)
37+ headerlist = cache.get(cache_key, None)
38+ if headerlist is not None:
39+ return cls._generate_cache_key(request, method, headerlist, key_prefix)
40+ else:
41+ return None
42+
43+ @classmethod
44+ def _i18n_cache_key_suffix(cls, request, cache_key):
45+ """If necessary, adds the current locale or time zone to the cache key."""
46+ if settings.USE_I18N or settings.USE_L10N:
47+ # first check if LocaleMiddleware or another middleware added
48+ # LANGUAGE_CODE to request, then fall back to the active language
49+ # which in turn can also fall back to settings.LANGUAGE_CODE
50+ cache_key += '.%s' % getattr(request, 'LANGUAGE_CODE', get_language())
51+ if settings.USE_TZ:
52+ cache_key += '.%s' % get_current_timezone_name()
53+ return cache_key
54+
55+ @classmethod
56+ def _generate_cache_key(cls, request, method, headerlist, key_prefix):
57+ """Returns a cache key from the headers given in the header list."""
58+ ctx = hashlib.md5()
59+ for header in headerlist:
60+ value = request.META.get(header, None)
61+ if value is not None:
62+ ctx.update(value)
63+ path = hashlib.md5(iri_to_uri(request.get_full_path()))
64+ cache_key = 'views.decorators.cache.cache_page.%s.%s.%s.%s' % (
65+ key_prefix, method, path.hexdigest(), ctx.hexdigest())
66+ return cls._i18n_cache_key_suffix(request, cache_key)
67+
68+ @classmethod
69+ def _generate_cache_header_key(cls, key_prefix, request):
70+ """Returns a cache key for the header cache."""
71+ path = hashlib.md5(iri_to_uri(request.get_full_path()))
72+ cache_key = 'views.decorators.cache.cache_header.%s.%s' % (
73+ key_prefix, path.hexdigest())
74+ return cls._i18n_cache_key_suffix(request, cache_key)
75+
76+class UpdateCacheMiddleware(TwoPartCacheMiddlewareBase):
77 """
78 Response-phase cache middleware that updates the cache if the response is
79 cacheable.
80@@ -88,6 +148,38 @@ class UpdateCacheMiddleware(object):
81 return False
82 return True
83
84+ @classmethod
85+ def learn_cache_key(cls, request, response, cache_timeout=None, key_prefix=None, cache=None):
86+ """
87+ Learns what headers to take into account for some request path from the
88+ response object. It stores those headers in a global path registry so that
89+ later access to that path will know what headers to take into account
90+ without building the response object itself. The headers are named in the
91+ Vary header of the response, but we want to prevent response generation.
92+
93+ The list of headers to use for cache key generation is stored in the same
94+ cache as the pages themselves. If the cache ages some data out of the
95+ cache, this just means that we have to build the response once to get at
96+ the Vary header and so at the list of headers to use for the cache key.
97+ """
98+ if key_prefix is None:
99+ key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
100+ if cache_timeout is None:
101+ cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
102+ cache_key = cls._generate_cache_header_key(key_prefix, request)
103+ if cache is None:
104+ cache = get_cache(settings.CACHE_MIDDLEWARE_ALIAS)
105+ if response.has_header('Vary'):
106+ headerlist = ['HTTP_'+header.upper().replace('-', '_')
107+ for header in cc_delim_re.split(response['Vary'])]
108+ cache.set(cache_key, headerlist, cache_timeout)
109+ return cls._generate_cache_key(request, request.method, headerlist, key_prefix)
110+ else:
111+ # if there is no Vary header, we still need a cache key
112+ # for the request.get_full_path()
113+ cache.set(cache_key, [], cache_timeout)
114+ return cls._generate_cache_key(request, request.method, [], key_prefix)
115+
116 def process_response(self, request, response):
117 """Sets the cache, if needed."""
118 if not self._should_update_cache(request, response):
119@@ -106,7 +198,7 @@ class UpdateCacheMiddleware(object):
120 return response
121 patch_response_headers(response, timeout)
122 if timeout:
123- cache_key = learn_cache_key(request, response, timeout, self.key_prefix, cache=self.cache)
124+ cache_key = self.learn_cache_key(request, response, timeout, self.key_prefix, cache=self.cache)
125 if hasattr(response, 'render') and callable(response.render):
126 response.add_post_render_callback(
127 lambda r: self.cache.set(cache_key, r, timeout)
128@@ -115,7 +207,7 @@ class UpdateCacheMiddleware(object):
129 self.cache.set(cache_key, response, timeout)
130 return response
131
132-class FetchFromCacheMiddleware(object):
133+class FetchFromCacheMiddleware(TwoPartCacheMiddlewareBase):
134 """
135 Request-phase cache middleware that fetches a page from the cache.
136
137@@ -140,14 +232,14 @@ class FetchFromCacheMiddleware(object):
138 return None # Don't bother checking the cache.
139
140 # try and get the cached GET response
141- cache_key = get_cache_key(request, self.key_prefix, 'GET', cache=self.cache)
142+ cache_key = self.get_cache_key(request, self.key_prefix, 'GET', cache=self.cache)
143 if cache_key is None:
144 request._cache_update_cache = True
145 return None # No cache information available, need to rebuild.
146 response = self.cache.get(cache_key, None)
147 # if it wasn't found and we are looking for a HEAD, try looking just for that
148 if response is None and request.method == 'HEAD':
149- cache_key = get_cache_key(request, self.key_prefix, 'HEAD', cache=self.cache)
150+ cache_key = self.get_cache_key(request, self.key_prefix, 'HEAD', cache=self.cache)
151 response = self.cache.get(cache_key, None)
152
153 if response is None:
154diff --git a/django/utils/cache.py b/django/utils/cache.py
155index 1015c2f..9448aae 100644
156--- a/django/utils/cache.py
157+++ b/django/utils/cache.py
158@@ -23,10 +23,8 @@ import time
159
160 from django.conf import settings
161 from django.core.cache import get_cache
162-from django.utils.encoding import smart_str, iri_to_uri
163+from django.utils.encoding import smart_str
164 from django.utils.http import http_date
165-from django.utils.timezone import get_current_timezone_name
166-from django.utils.translation import get_language
167
168 cc_delim_re = re.compile(r'\s*,\s*')
169
170@@ -122,12 +120,6 @@ def patch_response_headers(response, cache_timeout=None):
171 response['Expires'] = http_date(time.time() + cache_timeout)
172 patch_cache_control(response, max_age=cache_timeout)
173
174-def add_never_cache_headers(response):
175- """
176- Adds headers to a response to indicate that a page should never be cached.
177- """
178- patch_response_headers(response, cache_timeout=-1)
179-
180 def patch_vary_headers(response, newheaders):
181 """
182 Adds (or updates) the "Vary" header in the given HttpResponse object.
183@@ -157,88 +149,6 @@ def has_vary_header(response, header_query):
184 existing_headers = set([header.lower() for header in vary_headers])
185 return header_query.lower() in existing_headers
186
187-def _i18n_cache_key_suffix(request, cache_key):
188- """If necessary, adds the current locale or time zone to the cache key."""
189- if settings.USE_I18N or settings.USE_L10N:
190- # first check if LocaleMiddleware or another middleware added
191- # LANGUAGE_CODE to request, then fall back to the active language
192- # which in turn can also fall back to settings.LANGUAGE_CODE
193- cache_key += '.%s' % getattr(request, 'LANGUAGE_CODE', get_language())
194- if settings.USE_TZ:
195- cache_key += '.%s' % get_current_timezone_name()
196- return cache_key
197-
198-def _generate_cache_key(request, method, headerlist, key_prefix):
199- """Returns a cache key from the headers given in the header list."""
200- ctx = hashlib.md5()
201- for header in headerlist:
202- value = request.META.get(header, None)
203- if value is not None:
204- ctx.update(value)
205- path = hashlib.md5(iri_to_uri(request.get_full_path()))
206- cache_key = 'views.decorators.cache.cache_page.%s.%s.%s.%s' % (
207- key_prefix, method, path.hexdigest(), ctx.hexdigest())
208- return _i18n_cache_key_suffix(request, cache_key)
209-
210-def _generate_cache_header_key(key_prefix, request):
211- """Returns a cache key for the header cache."""
212- path = hashlib.md5(iri_to_uri(request.get_full_path()))
213- cache_key = 'views.decorators.cache.cache_header.%s.%s' % (
214- key_prefix, path.hexdigest())
215- return _i18n_cache_key_suffix(request, cache_key)
216-
217-def get_cache_key(request, key_prefix=None, method='GET', cache=None):
218- """
219- Returns a cache key based on the request path and query. It can be used
220- in the request phase because it pulls the list of headers to take into
221- account from the global path registry and uses those to build a cache key
222- to check against.
223-
224- If there is no headerlist stored, the page needs to be rebuilt, so this
225- function returns None.
226- """
227- if key_prefix is None:
228- key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
229- cache_key = _generate_cache_header_key(key_prefix, request)
230- if cache is None:
231- cache = get_cache(settings.CACHE_MIDDLEWARE_ALIAS)
232- headerlist = cache.get(cache_key, None)
233- if headerlist is not None:
234- return _generate_cache_key(request, method, headerlist, key_prefix)
235- else:
236- return None
237-
238-def learn_cache_key(request, response, cache_timeout=None, key_prefix=None, cache=None):
239- """
240- Learns what headers to take into account for some request path from the
241- response object. It stores those headers in a global path registry so that
242- later access to that path will know what headers to take into account
243- without building the response object itself. The headers are named in the
244- Vary header of the response, but we want to prevent response generation.
245-
246- The list of headers to use for cache key generation is stored in the same
247- cache as the pages themselves. If the cache ages some data out of the
248- cache, this just means that we have to build the response once to get at
249- the Vary header and so at the list of headers to use for the cache key.
250- """
251- if key_prefix is None:
252- key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
253- if cache_timeout is None:
254- cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
255- cache_key = _generate_cache_header_key(key_prefix, request)
256- if cache is None:
257- cache = get_cache(settings.CACHE_MIDDLEWARE_ALIAS)
258- if response.has_header('Vary'):
259- headerlist = ['HTTP_'+header.upper().replace('-', '_')
260- for header in cc_delim_re.split(response['Vary'])]
261- cache.set(cache_key, headerlist, cache_timeout)
262- return _generate_cache_key(request, request.method, headerlist, key_prefix)
263- else:
264- # if there is no Vary header, we still need a cache key
265- # for the request.get_full_path()
266- cache.set(cache_key, [], cache_timeout)
267- return _generate_cache_key(request, request.method, [], key_prefix)
268-
269
270 def _to_tuple(s):
271 t = s.split('=',1)
272diff --git a/django/views/decorators/cache.py b/django/views/decorators/cache.py
273index a39cc54..2847e05 100644
274--- a/django/views/decorators/cache.py
275+++ b/django/views/decorators/cache.py
276@@ -1,6 +1,6 @@
277 from functools import wraps
278 from django.utils.decorators import decorator_from_middleware_with_args, available_attrs
279-from django.utils.cache import patch_cache_control, add_never_cache_headers
280+from django.utils.cache import patch_cache_control, patch_response_headers
281 from django.middleware.cache import CacheMiddleware
282
283
284@@ -86,6 +86,6 @@ def never_cache(view_func):
285 @wraps(view_func, assigned=available_attrs(view_func))
286 def _wrapped_view_func(request, *args, **kwargs):
287 response = view_func(request, *args, **kwargs)
288- add_never_cache_headers(response)
289+ patch_response_headers(response, cache_timeout=0)
290 return response
291 return _wrapped_view_func
292diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py
293index 307588c..ea13421 100644
294--- a/tests/regressiontests/cache/tests.py
295+++ b/tests/regressiontests/cache/tests.py
296@@ -19,15 +19,15 @@ from django.core.cache.backends.base import (CacheKeyWarning,
297 from django.db import router
298 from django.http import HttpResponse, HttpRequest, QueryDict
299 from django.middleware.cache import (FetchFromCacheMiddleware,
300- UpdateCacheMiddleware, CacheMiddleware)
301+ UpdateCacheMiddleware, CacheMiddleware, TwoPartCacheMiddlewareBase)
302 from django.template import Template
303 from django.template.response import TemplateResponse
304 from django.test import TestCase, TransactionTestCase, RequestFactory
305 from django.test.utils import (get_warnings_state, restore_warnings_state,
306 override_settings)
307 from django.utils import timezone, translation, unittest
308-from django.utils.cache import (patch_vary_headers, get_cache_key,
309- learn_cache_key, patch_cache_control, patch_response_headers)
310+from django.utils.cache import (patch_vary_headers,
311+ patch_cache_control, patch_response_headers)
312 from django.views.decorators.cache import cache_page
313
314 from .models import Poll, expensive_calculation
315@@ -998,22 +998,11 @@ class CacheUtils(TestCase):
316 """TestCase for django.utils.cache functions."""
317
318 def setUp(self):
319- self.path = '/cache/test/'
320 self.cache = get_cache('default')
321
322 def tearDown(self):
323 self.cache.clear()
324
325- def _get_request(self, path, method='GET'):
326- request = HttpRequest()
327- request.META = {
328- 'SERVER_NAME': 'testserver',
329- 'SERVER_PORT': 80,
330- }
331- request.method = method
332- request.path = request.path_info = "/cache/%s" % path
333- return request
334-
335 def test_patch_vary_headers(self):
336 headers = (
337 # Initial vary, new headers, resulting vary.
338@@ -1034,37 +1023,6 @@ class CacheUtils(TestCase):
339 patch_vary_headers(response, newheaders)
340 self.assertEqual(response['Vary'], resulting_vary)
341
342- def test_get_cache_key(self):
343- request = self._get_request(self.path)
344- response = HttpResponse()
345- key_prefix = 'localprefix'
346- # Expect None if no headers have been set yet.
347- self.assertEqual(get_cache_key(request), None)
348- # Set headers to an empty list.
349- learn_cache_key(request, response)
350- self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
351- # Verify that a specified key_prefix is taken into account.
352- learn_cache_key(request, response, key_prefix=key_prefix)
353- self.assertEqual(get_cache_key(request, key_prefix=key_prefix), 'views.decorators.cache.cache_page.localprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
354-
355- def test_get_cache_key_with_query(self):
356- request = self._get_request(self.path + '?test=1')
357- response = HttpResponse()
358- # Expect None if no headers have been set yet.
359- self.assertEqual(get_cache_key(request), None)
360- # Set headers to an empty list.
361- learn_cache_key(request, response)
362- # Verify that the querystring is taken into account.
363- self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.bd889c5a59603af44333ed21504db3cd.d41d8cd98f00b204e9800998ecf8427e')
364-
365- def test_learn_cache_key(self):
366- request = self._get_request(self.path, 'HEAD')
367- response = HttpResponse()
368- response['Vary'] = 'Pony'
369- # Make sure that the Vary header is added to the key hash
370- learn_cache_key(request, response)
371- self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
372-
373 def test_patch_cache_control(self):
374 tests = (
375 # Initial Cache-Control, kwargs to patch_cache_control, expected Cache-Control parts
376@@ -1342,6 +1300,7 @@ class CacheMiddlewareTest(TestCase):
377
378 def setUp(self):
379 self.factory = RequestFactory()
380+ self.path = '/cache/test/'
381 self.default_cache = get_cache('default')
382 self.other_cache = get_cache('other')
383
384@@ -1381,6 +1340,48 @@ class CacheMiddlewareTest(TestCase):
385 self.assertEqual(as_view_decorator_with_custom.cache_alias, 'other')
386 self.assertEqual(as_view_decorator_with_custom.cache_anonymous_only, True)
387
388+ def _get_request(self, path, method='GET'):
389+ request = HttpRequest()
390+ request.META = {
391+ 'SERVER_NAME': 'testserver',
392+ 'SERVER_PORT': 80,
393+ }
394+ request.method = method
395+ request.path = request.path_info = "/cache/%s" % path
396+ return request
397+
398+ def test_get_cache_key(self):
399+ request = self._get_request(self.path)
400+ response = HttpResponse()
401+ key_prefix = 'localprefix'
402+ # Expect None if no headers have been set yet.
403+ self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request), None)
404+ # Set headers to an empty list.
405+ UpdateCacheMiddleware.learn_cache_key(request, response)
406+ self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
407+ # Verify that a specified key_prefix is taken into account.
408+ UpdateCacheMiddleware.learn_cache_key(request, response, key_prefix=key_prefix)
409+ self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request, key_prefix=key_prefix), 'views.decorators.cache.cache_page.localprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
410+
411+ def test_get_cache_key_with_query(self):
412+ request = self._get_request(self.path + '?test=1')
413+ response = HttpResponse()
414+ # Expect None if no headers have been set yet.
415+ self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request), None)
416+ # Set headers to an empty list.
417+ UpdateCacheMiddleware.learn_cache_key(request, response)
418+ # Verify that the querystring is taken into account.
419+ self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.bd889c5a59603af44333ed21504db3cd.d41d8cd98f00b204e9800998ecf8427e')
420+
421+ def test_learn_cache_key(self):
422+ request = self._get_request(self.path, 'HEAD')
423+ response = HttpResponse()
424+ response['Vary'] = 'Pony'
425+ # Make sure that the Vary header is added to the key hash
426+ UpdateCacheMiddleware.learn_cache_key(request, response)
427+ self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
428+
429+
430 def test_middleware(self):
431 middleware = CacheMiddleware()
432 prefix_middleware = CacheMiddleware(key_prefix='prefix1')
433@@ -1624,23 +1625,23 @@ class TestWithTemplateResponse(TestCase):
434 response = TemplateResponse(HttpResponse(), Template("This is a test"))
435 key_prefix = 'localprefix'
436 # Expect None if no headers have been set yet.
437- self.assertEqual(get_cache_key(request), None)
438+ self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request), None)
439 # Set headers to an empty list.
440- learn_cache_key(request, response)
441- self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
442+ UpdateCacheMiddleware.learn_cache_key(request, response)
443+ self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
444 # Verify that a specified key_prefix is taken into account.
445- learn_cache_key(request, response, key_prefix=key_prefix)
446- self.assertEqual(get_cache_key(request, key_prefix=key_prefix), 'views.decorators.cache.cache_page.localprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
447+ UpdateCacheMiddleware.learn_cache_key(request, response, key_prefix=key_prefix)
448+ self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request, key_prefix=key_prefix), 'views.decorators.cache.cache_page.localprefix.GET.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e')
449
450 def test_get_cache_key_with_query(self):
451 request = self._get_request(self.path + '?test=1')
452 response = TemplateResponse(HttpResponse(), Template("This is a test"))
453 # Expect None if no headers have been set yet.
454- self.assertEqual(get_cache_key(request), None)
455+ self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request), None)
456 # Set headers to an empty list.
457- learn_cache_key(request, response)
458+ UpdateCacheMiddleware.learn_cache_key(request, response)
459 # Verify that the querystring is taken into account.
460- self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.bd889c5a59603af44333ed21504db3cd.d41d8cd98f00b204e9800998ecf8427e')
461+ self.assertEqual(TwoPartCacheMiddlewareBase.get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.GET.bd889c5a59603af44333ed21504db3cd.d41d8cd98f00b204e9800998ecf8427e')
462
463 @override_settings(USE_ETAGS=False)
464 def test_without_etag(self):
Back to Top