diff --git a/django/utils/cache.py b/django/utils/cache.py index d5c8720e98..c7fcb7b733 100644 --- a/django/utils/cache.py +++ b/django/utils/cache.py @@ -376,17 +376,15 @@ def _generate_cache_header_key(key_prefix, request): return _i18n_cache_key_suffix(request, cache_key) -def get_cache_key( - request, key_prefix=None, method="GET", cache=None, ignore_headers=False -): +def get_cache_key(request, key_prefix=None, method="GET", cache=None): """ Return a cache key based on the request URL and query. It can be used in the request phase because it pulls the list of headers to take into account from the global URL registry and uses those to build a cache key to check against. - If there isn't a headerlist stored and `ignore_headers` argument is False, - return None, indicating that the page needs to be rebuilt. + If there is no headerlist stored, the page needs to be rebuilt, so this + function returns ``None``. """ if key_prefix is None: key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX @@ -394,8 +392,8 @@ def get_cache_key( if cache is None: cache = caches[settings.CACHE_MIDDLEWARE_ALIAS] headerlist = cache.get(cache_key) - if headerlist is not None or ignore_headers: - return _generate_cache_key(request, method, headerlist or [], key_prefix) + if headerlist is not None: + return _generate_cache_key(request, method, headerlist, key_prefix) else: return None @@ -448,40 +446,32 @@ def _to_tuple(s): return t[0].lower(), True -def invalidate_view_cache( - path=None, request=None, vary_headers=None, key_prefix=None, cache=None -): +def invalidate_view_cache(path=None, request=None, key_prefix=None, cache=None): """ - This function first creates a fake WSGIRequest to compute the cache key. - The key looks like: - views.decorators.cache.cache_page.key_prefix.GET.0fcb3cd9d5b34c8fe83f615913d8509b.c4ca4238a0b923820dcc509a6f75849b.en-us.UTC - The first hash corresponds to the full url (including query params), - the second to the header values + Delete a view cache key based on either a relative URL (``path``) + or a request object (``request``). - vary_headers should be a dict of every header used for this particular view - In local environment, we have two defined renderers (default of DRF), - thus DRF adds `Accept` to the Vary headers + The cache key is reconstructed in two steps: + 1. A headers cache key is built using the absolute URL, + key prefix, and locale code. + 2. A response cache key is then built using the absolute URL, + key prefix, HTTP method, and recovered headers. - either `path` or `request` arguments should be passed; - if both are passed `path` will be ignored + The ``key_prefix`` must match the value used in the ``cache_page`` + decorator for the corresponding view. - Note: If LocaleMiddleware is used, - we'll need to use the same language code as the one in the cached request + Either the ``path`` or ``request`` parameter must be provided. + If both are given, ``path`` takes precedence. """ if not request: assert path is not None, "either `path` or `request` arguments needed" factory = RequestFactory() request = factory.get(path) - if vary_headers: - request.META.update(vary_headers) - if cache is None: cache = caches[settings.CACHE_MIDDLEWARE_ALIAS] - cache_key = get_cache_key( - request, key_prefix=key_prefix, ignore_headers=True, cache=cache - ) + cache_key = get_cache_key(request, key_prefix=key_prefix, cache=cache) if cache_key is None: return 0 diff --git a/tests/cache/tests.py b/tests/cache/tests.py index b10940ff72..92968d7827 100644 --- a/tests/cache/tests.py +++ b/tests/cache/tests.py @@ -2632,9 +2632,7 @@ class CacheMiddlewareTest(SimpleTestCase): view = cache_page(10)(hello_world_view) request = self.factory.get("/view/") _ = view(request, "0") - cache_key = get_cache_key( - request=request, key_prefix="", ignore_headers=True, cache=cache - ) + cache_key = get_cache_key(request=request, key_prefix="", cache=cache) cached_response = cache.get(cache_key) # Verify request.content has been chached @@ -2653,9 +2651,7 @@ class CacheMiddlewareTest(SimpleTestCase): path = "/view/" request = self.factory.get(path) _ = view(request, "0") - cache_key = get_cache_key( - request=request, key_prefix="", ignore_headers=True, cache=cache - ) + cache_key = get_cache_key(request=request, key_prefix="", cache=cache) cached_response = cache.get(cache_key) # Verify request.content has been chached @@ -2673,7 +2669,7 @@ class CacheMiddlewareTest(SimpleTestCase): # Cache view and inject Vary headers to Response object view = cache_page(10, key_prefix="")( - vary_on_headers("Accept-Encoding")(hello_world_view) + vary_on_headers("Accept-Encoding", "Accept")(hello_world_view) ) path = "/view/" request = self.factory.get(path) @@ -2682,9 +2678,7 @@ class CacheMiddlewareTest(SimpleTestCase): # Check response headers self.assertTrue(response.has_header("Vary")) - cache_key = get_cache_key( - request=request, key_prefix="", ignore_headers=False, cache=cache - ) + cache_key = get_cache_key(request=request, key_prefix="", cache=cache) cached_response = cache.get(cache_key) # Verify request.content has been chached @@ -2697,6 +2691,32 @@ class CacheMiddlewareTest(SimpleTestCase): # Confirm key/value has been deleted from cache self.assertIsNone(cached_response) + def test_cache_key_prefix_missmatch(self): + # Wrap the view with the cache_page decorator (no key_prefix specified) + view = cache_page(10)(hello_world_view) + path = "/view/" + request = self.factory.get(path) + _ = view(request, "0") + + # Attempt to retrieve the cache key without specifying key_prefix + cache_key = get_cache_key(request=request, cache=cache) + + # Because get_cache_key defaults to using + # settings.CACHE_MIDDLEWARE_KEY_PREFIX when key_prefix is None, + # this should not match the cached key + self.assertIsNone(cache_key) + + # Try again, explicitly passing the default cache_page key_prefix + # (empty string) + cache_key = get_cache_key(request=request, cache=cache, key_prefix="") + + # The key should now be found + self.assertIsNotNone(cache_key) + + # Confirm that the cached response content matches the view output + cached_response = cache.get(cache_key) + self.assertEqual(cached_response.content, b"Hello World 0") + def test_view_decorator(self): # decorate the same view with different cache decorators default_view = cache_page(3)(hello_world_view)