summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlavio Curella <flavio.curella@gmail.com>2019-09-26 11:41:38 -0700
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2020-01-16 13:39:16 +0100
commitd08d4f464ab11cc226d4e197c0f43e26a7fd2961 (patch)
tree568fce9e1eee95192ad4bf2e24edc3b3573cda3d
parent1e0dcd6c8bfa4519c21014c73eb510620dd1a000 (diff)
Fixed #30765 -- Made cache_page decorator take precedence over max-age Cache-Control directive.
-rw-r--r--django/middleware/cache.py23
-rw-r--r--django/views/decorators/cache.py2
-rw-r--r--docs/releases/3.1.txt4
-rw-r--r--docs/topics/cache.txt8
-rw-r--r--tests/cache/tests.py23
5 files changed, 50 insertions, 10 deletions
diff --git a/django/middleware/cache.py b/django/middleware/cache.py
index 6b320f1db5..9705270b59 100644
--- a/django/middleware/cache.py
+++ b/django/middleware/cache.py
@@ -63,6 +63,7 @@ class UpdateCacheMiddleware(MiddlewareMixin):
"""
def __init__(self, get_response=None):
self.cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
+ self.page_timeout = None
self.key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
self.cache_alias = settings.CACHE_MIDDLEWARE_ALIAS
self.cache = caches[self.cache_alias]
@@ -89,15 +90,18 @@ class UpdateCacheMiddleware(MiddlewareMixin):
if 'private' in response.get('Cache-Control', ()):
return response
- # Try to get the timeout from the "max-age" section of the "Cache-
- # Control" header before reverting to using the default cache_timeout
- # length.
- timeout = get_max_age(response)
+ # Page timeout takes precedence over the "max-age" and the default
+ # cache timeout.
+ timeout = self.page_timeout
if timeout is None:
- timeout = self.cache_timeout
- elif timeout == 0:
- # max-age was set to 0, don't bother caching.
- return response
+ # The timeout from the "max-age" section of the "Cache-Control"
+ # header takes precedence over the default cache timeout.
+ timeout = get_max_age(response)
+ if timeout is None:
+ timeout = self.cache_timeout
+ elif timeout == 0:
+ # max-age was set to 0, don't cache.
+ return response
patch_response_headers(response, timeout)
if timeout and response.status_code == 200:
cache_key = learn_cache_key(request, response, timeout, self.key_prefix, cache=self.cache)
@@ -160,7 +164,7 @@ class CacheMiddleware(UpdateCacheMiddleware, FetchFromCacheMiddleware):
Also used as the hook point for the cache decorator, which is generated
using the decorator-from-middleware utility.
"""
- def __init__(self, get_response=None, cache_timeout=None, **kwargs):
+ def __init__(self, get_response=None, cache_timeout=None, page_timeout=None, **kwargs):
self.get_response = get_response
# We need to differentiate between "provided, but using default value",
# and "not provided". If the value is provided using a default, then
@@ -186,4 +190,5 @@ class CacheMiddleware(UpdateCacheMiddleware, FetchFromCacheMiddleware):
if cache_timeout is None:
cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
self.cache_timeout = cache_timeout
+ self.page_timeout = page_timeout
self.cache = caches[self.cache_alias]
diff --git a/django/views/decorators/cache.py b/django/views/decorators/cache.py
index 9658bd6ba2..773cf0c2c6 100644
--- a/django/views/decorators/cache.py
+++ b/django/views/decorators/cache.py
@@ -20,7 +20,7 @@ def cache_page(timeout, *, cache=None, key_prefix=None):
into account on caching -- just like the middleware does.
"""
return decorator_from_middleware_with_args(CacheMiddleware)(
- cache_timeout=timeout, cache_alias=cache, key_prefix=key_prefix
+ page_timeout=timeout, cache_alias=cache, key_prefix=key_prefix,
)
diff --git a/docs/releases/3.1.txt b/docs/releases/3.1.txt
index 9e8dff3456..064aadd34a 100644
--- a/docs/releases/3.1.txt
+++ b/docs/releases/3.1.txt
@@ -430,6 +430,10 @@ Miscellaneous
used with :setting:`DEFAULT_EXCEPTION_REPORTER_FILTER` needs to inherit from
:class:`django.views.debug.SafeExceptionReporterFilter`.
+* The cache timeout set by :func:`~django.views.decorators.cache.cache_page`
+ decorator now takes precedence over the ``max-age`` directive from the
+ ``Cache-Control`` header.
+
.. _deprecated-features-3.1:
Features deprecated in 3.1
diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt
index 29ea8fb001..bc6b96ecfe 100644
--- a/docs/topics/cache.txt
+++ b/docs/topics/cache.txt
@@ -570,6 +570,9 @@ minutes. (Note that we've written it as ``60 * 15`` for the purpose of
readability. ``60 * 15`` will be evaluated to ``900`` -- that is, 15 minutes
multiplied by 60 seconds per minute.)
+The cache timeout set by ``cache_page`` takes precedence over the ``max-age``
+directive from the ``Cache-Control`` header.
+
The per-view cache, like the per-site cache, is keyed off of the URL. If
multiple URLs point at the same view, each URL will be cached separately.
Continuing the ``my_view`` example, if your URLconf looks like this::
@@ -605,6 +608,11 @@ The ``key_prefix`` and ``cache`` arguments may be specified together. The
``key_prefix`` argument and the :setting:`KEY_PREFIX <CACHES-KEY_PREFIX>`
specified under :setting:`CACHES` will be concatenated.
+.. versionchanged:: 3.1
+
+ In older versions, the ``max-age`` directive from the ``Cache-Control``
+ header had precedence over the cache timeout set by ``cache_page``.
+
Specifying per-view cache in the URLconf
----------------------------------------
diff --git a/tests/cache/tests.py b/tests/cache/tests.py
index e99ab408a1..141d782203 100644
--- a/tests/cache/tests.py
+++ b/tests/cache/tests.py
@@ -2188,6 +2188,29 @@ class CacheMiddlewareTest(SimpleTestCase):
response = other_with_prefix_view(request, '16')
self.assertEqual(response.content, b'Hello World 16')
+ def test_cache_page_timeout(self):
+ # Page timeout takes precedence over the "max-age" section of the
+ # "Cache-Control".
+ tests = [
+ (1, 3), # max_age < page_timeout.
+ (3, 1), # max_age > page_timeout.
+ ]
+ for max_age, page_timeout in tests:
+ with self.subTest(max_age=max_age, page_timeout=page_timeout):
+ view = cache_page(timeout=page_timeout)(
+ cache_control(max_age=max_age)(hello_world_view)
+ )
+ request = self.factory.get('/view/')
+ response = view(request, '1')
+ self.assertEqual(response.content, b'Hello World 1')
+ time.sleep(1)
+ response = view(request, '2')
+ self.assertEqual(
+ response.content,
+ b'Hello World 1' if page_timeout > max_age else b'Hello World 2',
+ )
+ cache.clear()
+
def test_cached_control_private_not_cached(self):
"""Responses with 'Cache-Control: private' are not cached."""
view_with_private_cache = cache_page(3)(cache_control(private=True)(hello_world_view))