summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMariusz Felisiak <felisiak.mariusz@gmail.com>2021-12-16 20:13:17 +0100
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2021-12-16 20:14:17 +0100
commitc1d2e8b9b8f41d3effef03badc78c8b8995a99b6 (patch)
tree7e51116b27998d63218153f3e0ea36586964d27b
parent267a743bf253a4e0703c0257a5df7774116c3194 (diff)
[4.0.x] Fixed #33350 -- Reallowed using cache decorators with duck-typed HttpRequest.
Regression in 3fd82a62415e748002435e7bad06b5017507777c. Thanks Terence Honles for the report. Backport of 40165eecc40f9e223702a41a0cb0958515bb1f82 from main
-rw-r--r--django/views/decorators/cache.py7
-rw-r--r--docs/releases/4.0.1.txt4
-rw-r--r--tests/decorators/tests.py41
3 files changed, 47 insertions, 5 deletions
diff --git a/django/views/decorators/cache.py b/django/views/decorators/cache.py
index fdc5917738..417f3614f8 100644
--- a/django/views/decorators/cache.py
+++ b/django/views/decorators/cache.py
@@ -1,6 +1,5 @@
from functools import wraps
-from django.http import HttpRequest
from django.middleware.cache import CacheMiddleware
from django.utils.cache import add_never_cache_headers, patch_cache_control
from django.utils.decorators import decorator_from_middleware_with_args
@@ -29,7 +28,8 @@ def cache_control(**kwargs):
def _cache_controller(viewfunc):
@wraps(viewfunc)
def _cache_controlled(request, *args, **kw):
- if not isinstance(request, HttpRequest):
+ # Ensure argument looks like a request.
+ if not hasattr(request, 'META'):
raise TypeError(
"cache_control didn't receive an HttpRequest. If you are "
"decorating a classmethod, be sure to use "
@@ -48,7 +48,8 @@ def never_cache(view_func):
"""
@wraps(view_func)
def _wrapped_view_func(request, *args, **kwargs):
- if not isinstance(request, HttpRequest):
+ # Ensure argument looks like a request.
+ if not hasattr(request, 'META'):
raise TypeError(
"never_cache didn't receive an HttpRequest. If you are "
"decorating a classmethod, be sure to use @method_decorator."
diff --git a/docs/releases/4.0.1.txt b/docs/releases/4.0.1.txt
index 08e5b206bb..50c84bfe17 100644
--- a/docs/releases/4.0.1.txt
+++ b/docs/releases/4.0.1.txt
@@ -15,3 +15,7 @@ Bugfixes
* Fixed a bug in Django 4.0 that caused a crash on booleans with the
``RedisCache`` backend (:ticket:`33361`).
+
+* Relaxed the check added in Django 4.0 to reallow use of a duck-typed
+ ``HttpRequest`` in ``django.views.decorators.cache.cache_control()`` and
+ ``never_cache()`` decorators (:ticket:`33350`).
diff --git a/tests/decorators/tests.py b/tests/decorators/tests.py
index e496e2c790..9da82cc3d2 100644
--- a/tests/decorators/tests.py
+++ b/tests/decorators/tests.py
@@ -493,6 +493,15 @@ class XFrameOptionsDecoratorsTests(TestCase):
self.assertIsNone(r.get('X-Frame-Options', None))
+class HttpRequestProxy:
+ def __init__(self, request):
+ self._request = request
+
+ def __getattr__(self, attr):
+ """Proxy to the underlying HttpRequest object."""
+ return getattr(self._request, attr)
+
+
class NeverCacheDecoratorTest(SimpleTestCase):
def test_never_cache_decorator(self):
@never_cache
@@ -509,12 +518,27 @@ class NeverCacheDecoratorTest(SimpleTestCase):
@never_cache
def a_view(self, request):
return HttpResponse()
+
+ request = HttpRequest()
msg = (
"never_cache didn't receive an HttpRequest. If you are decorating "
"a classmethod, be sure to use @method_decorator."
)
with self.assertRaisesMessage(TypeError, msg):
- MyClass().a_view(HttpRequest())
+ MyClass().a_view(request)
+ with self.assertRaisesMessage(TypeError, msg):
+ MyClass().a_view(HttpRequestProxy(request))
+
+ def test_never_cache_decorator_http_request_proxy(self):
+ class MyClass:
+ @method_decorator(never_cache)
+ def a_view(self, request):
+ return HttpResponse()
+
+ request = HttpRequest()
+ response = MyClass().a_view(HttpRequestProxy(request))
+ self.assertIn('Cache-Control', response.headers)
+ self.assertIn('Expires', response.headers)
class CacheControlDecoratorTest(SimpleTestCase):
@@ -528,5 +552,18 @@ class CacheControlDecoratorTest(SimpleTestCase):
"cache_control didn't receive an HttpRequest. If you are "
"decorating a classmethod, be sure to use @method_decorator."
)
+ request = HttpRequest()
with self.assertRaisesMessage(TypeError, msg):
- MyClass().a_view(HttpRequest())
+ MyClass().a_view(request)
+ with self.assertRaisesMessage(TypeError, msg):
+ MyClass().a_view(HttpRequestProxy(request))
+
+ def test_cache_control_decorator_http_request_proxy(self):
+ class MyClass:
+ @method_decorator(cache_control(a='b'))
+ def a_view(self, request):
+ return HttpResponse()
+
+ request = HttpRequest()
+ response = MyClass().a_view(HttpRequestProxy(request))
+ self.assertEqual(response.headers['Cache-Control'], 'a=b')