Beefed up the tests for multi-cache handling of the cache middleware and view decorators, and made a couple of tweaks for edge cases as a result.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@15021 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Russell Keith-Magee 2010-12-22 07:52:44 +00:00
parent 0fef92f6f0
commit b22415214a
3 changed files with 181 additions and 22 deletions

View File

@ -85,7 +85,7 @@ class UpdateCacheMiddleware(object):
return response return response
patch_response_headers(response, timeout) patch_response_headers(response, timeout)
if 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) self.cache.set(cache_key, response, timeout)
return response return response
@ -120,17 +120,14 @@ class FetchFromCacheMiddleware(object):
return None # Don't cache requests from authenticated users. return None # Don't cache requests from authenticated users.
# try and get the cached GET response # 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: if cache_key is None:
request._cache_update_cache = True request._cache_update_cache = True
return None # No cache information available, need to rebuild. return None # No cache information available, need to rebuild.
response = self.cache.get(cache_key, None) 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 it wasn't found and we are looking for a HEAD, try looking just for that
if response is None and request.method == 'HEAD': 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) response = self.cache.get(cache_key, None)
if response is None: if response is None:
@ -149,28 +146,33 @@ class CacheMiddleware(UpdateCacheMiddleware, FetchFromCacheMiddleware):
using the decorator-from-middleware utility. using the decorator-from-middleware utility.
""" """
def __init__(self, cache_timeout=None, cache_anonymous_only=None, **kwargs): 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", # We need to differentiate between "provided, but using default value",
# and "not provided". If the value is provided using a default, then # 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 fall back to system defaults. If it is not provided at all,
# we need to use middleware defaults. # 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: try:
cache_alias = kwargs.get('cache_alias') cache_alias = kwargs.get('cache_alias')
if cache_alias is None: if cache_alias is None:
cache_alias = DEFAULT_CACHE_ALIAS cache_alias = DEFAULT_CACHE_ALIAS
if cache_timeout is not None:
cache_kwargs['TIMEOUT'] = cache_timeout
except KeyError: except KeyError:
cache_alias = settings.CACHE_MIDDLEWARE_ALIAS cache_alias = settings.CACHE_MIDDLEWARE_ALIAS
if cache_timeout is None:
cache_kwargs = {} cache_kwargs['TIMEOUT'] = settings.CACHE_MIDDLEWARE_SECONDS
try: else:
key_prefix = kwargs.get('key_prefix') cache_kwargs['TIMEOUT'] = cache_timeout
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_anonymous_only is None: if cache_anonymous_only is None:
self.cache_anonymous_only = getattr(settings, 'CACHE_MIDDLEWARE_ANONYMOUS_ONLY', False) 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_anonymous_only = cache_anonymous_only
self.cache = get_cache(cache_alias, **cache_kwargs) self.cache = get_cache(cache_alias, **cache_kwargs)
self.cache_timeout = self.cache.default_timeout

View File

@ -21,7 +21,7 @@ import re
import time import time
from django.conf import settings 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.encoding import smart_str, iri_to_uri
from django.utils.http import http_date from django.utils.http import http_date
from django.utils.hashcompat import md5_constructor from django.utils.hashcompat import md5_constructor
@ -162,7 +162,7 @@ def _generate_cache_header_key(key_prefix, request):
key_prefix, path.hexdigest()) key_prefix, path.hexdigest())
return _i18n_cache_key_suffix(request, cache_key) 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 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 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: if key_prefix is None:
key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
cache_key = _generate_cache_header_key(key_prefix, request) 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) headerlist = cache.get(cache_key, None)
if headerlist is not None: if headerlist is not None:
return _generate_cache_key(request, method, headerlist, key_prefix) return _generate_cache_key(request, method, headerlist, key_prefix)
else: else:
return None 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 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 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: if cache_timeout is None:
cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
cache_key = _generate_cache_header_key(key_prefix, request) 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'): if response.has_header('Vary'):
headerlist = ['HTTP_'+header.upper().replace('-', '_') headerlist = ['HTTP_'+header.upper().replace('-', '_')
for header in cc_delim_re.split(response['Vary'])] for header in cc_delim_re.split(response['Vary'])]

View File

@ -14,12 +14,14 @@ from django.core import management
from django.core.cache import get_cache, DEFAULT_CACHE_ALIAS from django.core.cache import get_cache, DEFAULT_CACHE_ALIAS
from django.core.cache.backends.base import InvalidCacheBackendError, CacheKeyWarning from django.core.cache.backends.base import InvalidCacheBackendError, CacheKeyWarning
from django.http import HttpResponse, HttpRequest 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.test.utils import get_warnings_state, restore_warnings_state
from django.utils import translation from django.utils import translation
from django.utils import unittest from django.utils import unittest
from django.utils.cache import patch_vary_headers, get_cache_key, learn_cache_key from django.utils.cache import patch_vary_headers, get_cache_key, learn_cache_key
from django.utils.hashcompat import md5_constructor from django.utils.hashcompat import md5_constructor
from django.views.decorators.cache import cache_page
from regressiontests.cache.models import Poll, expensive_calculation from regressiontests.cache.models import Poll, expensive_calculation
# functions/classes for complex data type tests # 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.cache = get_cache('locmem://?max_entries=30&cull_frequency=0')
self.perform_cull_test(50, 19) 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. # memcached backend isn't guaranteed to be available.
# To check the memcached backend, the test settings file will # To check the memcached backend, the test settings file will
# need to contain a cache backend setting that points at # need to contain a cache backend setting that points at
@ -1117,5 +1128,146 @@ class PrefixedCacheI18nTest(CacheI18nTest):
else: else:
settings.CACHES['default']['KEY_PREFIX'] = self.old_cache_key_prefix 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__': if __name__ == '__main__':
unittest.main() unittest.main()