diff --git a/django/middleware/cache.py b/django/middleware/cache.py index 54f6607db1..31a8147eec 100644 --- a/django/middleware/cache.py +++ b/django/middleware/cache.py @@ -85,7 +85,7 @@ class UpdateCacheMiddleware(object): return response patch_response_headers(response, timeout) if timeout: - cache_key = learn_cache_key(request, response, timeout, self.key_prefix) + cache_key = learn_cache_key(request, response, timeout, self.key_prefix, cache=self.cache) self.cache.set(cache_key, response, timeout) return response @@ -120,17 +120,14 @@ class FetchFromCacheMiddleware(object): return None # Don't cache requests from authenticated users. # try and get the cached GET response - cache_key = get_cache_key(request, self.key_prefix, 'GET') - + cache_key = get_cache_key(request, self.key_prefix, 'GET', cache=self.cache) if cache_key is None: request._cache_update_cache = True return None # No cache information available, need to rebuild. - response = self.cache.get(cache_key, None) - # if it wasn't found and we are looking for a HEAD, try looking just for that if response is None and request.method == 'HEAD': - cache_key = get_cache_key(request, self.key_prefix, 'HEAD') + cache_key = get_cache_key(request, self.key_prefix, 'HEAD', cache=self.cache) response = self.cache.get(cache_key, None) if response is None: @@ -149,28 +146,33 @@ class CacheMiddleware(UpdateCacheMiddleware, FetchFromCacheMiddleware): using the decorator-from-middleware utility. """ def __init__(self, cache_timeout=None, cache_anonymous_only=None, **kwargs): - self.cache_timeout = cache_timeout - if cache_timeout is None: - self.cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS - # We need to differentiate between "provided, but using default value", # and "not provided". If the value is provided using a default, then # we fall back to system defaults. If it is not provided at all, # we need to use middleware defaults. + + cache_kwargs = {} + try: + self.key_prefix = kwargs.get('key_prefix') + if self.key_prefix is not None: + cache_kwargs['KEY_PREFIX'] = self.key_prefix + else: + self.key_prefix = '' + except KeyError: + self.key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX + cache_kwargs['KEY_PREFIX'] = self.key_prefix try: cache_alias = kwargs.get('cache_alias') if cache_alias is None: cache_alias = DEFAULT_CACHE_ALIAS + if cache_timeout is not None: + cache_kwargs['TIMEOUT'] = cache_timeout except KeyError: cache_alias = settings.CACHE_MIDDLEWARE_ALIAS - - cache_kwargs = {} - try: - key_prefix = kwargs.get('key_prefix') - if key_prefix is not None: - cache_kwargs['KEY_PREFIX'] = key_prefix - except KeyError: - cache_kwargs['KEY_PREFIX'] = settings.CACHE_MIDDLEWARE_KEY_PREFIX + if cache_timeout is None: + cache_kwargs['TIMEOUT'] = settings.CACHE_MIDDLEWARE_SECONDS + else: + cache_kwargs['TIMEOUT'] = cache_timeout if cache_anonymous_only is None: self.cache_anonymous_only = getattr(settings, 'CACHE_MIDDLEWARE_ANONYMOUS_ONLY', False) @@ -178,3 +180,4 @@ class CacheMiddleware(UpdateCacheMiddleware, FetchFromCacheMiddleware): self.cache_anonymous_only = cache_anonymous_only self.cache = get_cache(cache_alias, **cache_kwargs) + self.cache_timeout = self.cache.default_timeout diff --git a/django/utils/cache.py b/django/utils/cache.py index 04beccaef2..ba647ac420 100644 --- a/django/utils/cache.py +++ b/django/utils/cache.py @@ -21,7 +21,7 @@ import re import time from django.conf import settings -from django.core.cache import cache +from django.core.cache import get_cache from django.utils.encoding import smart_str, iri_to_uri from django.utils.http import http_date from django.utils.hashcompat import md5_constructor @@ -162,7 +162,7 @@ def _generate_cache_header_key(key_prefix, request): key_prefix, path.hexdigest()) return _i18n_cache_key_suffix(request, cache_key) -def get_cache_key(request, key_prefix=None, method='GET'): +def get_cache_key(request, key_prefix=None, method='GET', cache=None): """ Returns a cache key based on the request path. It can be used in the request phase because it pulls the list of headers to take into account @@ -175,13 +175,15 @@ def get_cache_key(request, key_prefix=None, method='GET'): 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): +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 @@ -199,6 +201,8 @@ def learn_cache_key(request, response, cache_timeout=None, key_prefix=None): 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'): headerlist = ['HTTP_'+header.upper().replace('-', '_') for header in cc_delim_re.split(response['Vary'])] diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py index 6c5775830a..f2cfcb37da 100644 --- a/tests/regressiontests/cache/tests.py +++ b/tests/regressiontests/cache/tests.py @@ -14,12 +14,14 @@ from django.core import management from django.core.cache import get_cache, DEFAULT_CACHE_ALIAS from django.core.cache.backends.base import InvalidCacheBackendError, CacheKeyWarning from django.http import HttpResponse, HttpRequest -from django.middleware.cache import FetchFromCacheMiddleware, UpdateCacheMiddleware +from django.middleware.cache import FetchFromCacheMiddleware, UpdateCacheMiddleware, CacheMiddleware +from django.test import RequestFactory from django.test.utils import get_warnings_state, restore_warnings_state from django.utils import translation from django.utils import unittest from django.utils.cache import patch_vary_headers, get_cache_key, learn_cache_key from django.utils.hashcompat import md5_constructor +from django.views.decorators.cache import cache_page from regressiontests.cache.models import Poll, expensive_calculation # functions/classes for complex data type tests @@ -759,6 +761,15 @@ class LocMemCacheTests(unittest.TestCase, BaseCacheTests): self.cache = get_cache('locmem://?max_entries=30&cull_frequency=0') self.perform_cull_test(50, 19) + def test_multiple_caches(self): + "Check that multiple locmem caches are isolated" + mirror_cache = get_cache('django.core.cache.backends.locmem.LocMemCache') + other_cache = get_cache('django.core.cache.backends.locmem.LocMemCache', LOCATION='other') + + self.cache.set('value1', 42) + self.assertEquals(mirror_cache.get('value1'), 42) + self.assertEquals(other_cache.get('value1'), None) + # memcached backend isn't guaranteed to be available. # To check the memcached backend, the test settings file will # need to contain a cache backend setting that points at @@ -1117,5 +1128,146 @@ class PrefixedCacheI18nTest(CacheI18nTest): else: settings.CACHES['default']['KEY_PREFIX'] = self.old_cache_key_prefix +class CacheMiddlewareTest(unittest.TestCase): + + def setUp(self): + self.orig_cache_middleware_alias = settings.CACHE_MIDDLEWARE_ALIAS + self.orig_cache_middleware_key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX + self.orig_caches = settings.CACHES + + settings.CACHE_MIDDLEWARE_ALIAS = 'other' + settings.CACHE_MIDDLEWARE_KEY_PREFIX = 'middlewareprefix' + settings.CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache' + }, + 'other': { + 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + 'LOCATION': 'other', + 'TIMEOUT': '1' + } + } + + def tearDown(self): + settings.CACHE_MIDDLEWARE_ALIAS = self.orig_cache_middleware_alias + settings.CACHE_MIDDLEWARE_KEY_PREFIX = self.orig_cache_middleware_key_prefix + settings.CACHES = self.orig_caches + + def test_middleware(self): + def view(request, value): + return HttpResponse('Hello World %s' % value) + + factory = RequestFactory() + + middleware = CacheMiddleware() + prefix_middleware = CacheMiddleware(key_prefix='prefix1') + timeout_middleware = CacheMiddleware(cache_timeout=1) + + request = factory.get('/view/') + + # Put the request through the request middleware + result = middleware.process_request(request) + self.assertEquals(result, None) + + response = view(request, '1') + + # Now put the response through the response middleware + response = middleware.process_response(request, response) + + # Repeating the request should result in a cache hit + result = middleware.process_request(request) + self.assertNotEquals(result, None) + self.assertEquals(result.content, 'Hello World 1') + + # The same request through a different middleware won't hit + result = prefix_middleware.process_request(request) + self.assertEquals(result, None) + + # The same request with a timeout _will_ hit + result = timeout_middleware.process_request(request) + self.assertNotEquals(result, None) + self.assertEquals(result.content, 'Hello World 1') + + def test_view_decorator(self): + def view(request, value): + return HttpResponse('Hello World %s' % value) + + # decorate the same view with different cache decorators + default_view = cache_page(view) + default_with_prefix_view = cache_page(key_prefix='prefix1')(view) + + explicit_default_view = cache_page(cache='default')(view) + explicit_default_with_prefix_view = cache_page(cache='default', key_prefix='prefix1')(view) + + other_view = cache_page(cache='other')(view) + other_with_prefix_view = cache_page(cache='other', key_prefix='prefix2')(view) + + factory = RequestFactory() + request = factory.get('/view/') + + # Request the view once + response = default_view(request, '1') + self.assertEquals(response.content, 'Hello World 1') + + # Request again -- hit the cache + response = default_view(request, '2') + self.assertEquals(response.content, 'Hello World 1') + + # Requesting the same view with the explicit cache should yield the same result + response = explicit_default_view(request, '3') + self.assertEquals(response.content, 'Hello World 1') + + # Requesting with a prefix will hit a different cache key + response = explicit_default_with_prefix_view(request, '4') + self.assertEquals(response.content, 'Hello World 4') + + # Hitting the same view again gives a cache hit + response = explicit_default_with_prefix_view(request, '5') + self.assertEquals(response.content, 'Hello World 4') + + # And going back to the implicit cache will hit the same cache + response = default_with_prefix_view(request, '6') + self.assertEquals(response.content, 'Hello World 4') + + # Requesting from an alternate cache won't hit cache + response = other_view(request, '7') + self.assertEquals(response.content, 'Hello World 7') + + # But a repeated hit will hit cache + response = other_view(request, '8') + self.assertEquals(response.content, 'Hello World 7') + + # And prefixing the alternate cache yields yet another cache entry + response = other_with_prefix_view(request, '9') + self.assertEquals(response.content, 'Hello World 9') + + # But if we wait a couple of seconds... + time.sleep(2) + + # ... the default cache will still hit + cache = get_cache('default') + response = default_view(request, '10') + self.assertEquals(response.content, 'Hello World 1') + + # ... the default cache with a prefix will still hit + response = default_with_prefix_view(request, '11') + self.assertEquals(response.content, 'Hello World 4') + + # ... the explicit default cache will still hit + response = explicit_default_view(request, '12') + self.assertEquals(response.content, 'Hello World 1') + + # ... the explicit default cache with a prefix will still hit + response = explicit_default_with_prefix_view(request, '13') + self.assertEquals(response.content, 'Hello World 4') + + # .. but a rapidly expiring cache won't hit + response = other_view(request, '14') + self.assertEquals(response.content, 'Hello World 14') + + # .. even if it has a prefix + response = other_with_prefix_view(request, '15') + self.assertEquals(response.content, 'Hello World 15') + if __name__ == '__main__': unittest.main()