summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--django/views/decorators/vary.py22
-rw-r--r--docs/releases/5.0.txt2
-rw-r--r--docs/topics/async.txt2
-rw-r--r--docs/topics/http/decorators.txt8
-rw-r--r--tests/decorators/test_vary.py69
5 files changed, 97 insertions, 6 deletions
diff --git a/django/views/decorators/vary.py b/django/views/decorators/vary.py
index b1459295d2..9beab8b4db 100644
--- a/django/views/decorators/vary.py
+++ b/django/views/decorators/vary.py
@@ -1,5 +1,7 @@
from functools import wraps
+from asgiref.sync import iscoroutinefunction
+
from django.utils.cache import patch_vary_headers
@@ -16,13 +18,21 @@ def vary_on_headers(*headers):
"""
def decorator(func):
- @wraps(func)
- def inner_func(*args, **kwargs):
- response = func(*args, **kwargs)
- patch_vary_headers(response, headers)
- return response
+ if iscoroutinefunction(func):
+
+ async def _view_wrapper(request, *args, **kwargs):
+ response = await func(request, *args, **kwargs)
+ patch_vary_headers(response, headers)
+ return response
+
+ else:
+
+ def _view_wrapper(request, *args, **kwargs):
+ response = func(request, *args, **kwargs)
+ patch_vary_headers(response, headers)
+ return response
- return inner_func
+ return wraps(func)(_view_wrapper)
return decorator
diff --git a/docs/releases/5.0.txt b/docs/releases/5.0.txt
index 8884886b51..0ad0835c29 100644
--- a/docs/releases/5.0.txt
+++ b/docs/releases/5.0.txt
@@ -268,6 +268,8 @@ Decorators
* :func:`~django.views.decorators.http.require_GET`
* :func:`~django.views.decorators.http.require_POST`
* :func:`~django.views.decorators.http.require_safe`
+ * :func:`~django.views.decorators.vary.vary_on_cookie`
+ * :func:`~django.views.decorators.vary.vary_on_headers`
* ``xframe_options_deny()``
* ``xframe_options_sameorigin()``
* ``xframe_options_exempt()``
diff --git a/docs/topics/async.txt b/docs/topics/async.txt
index 8554e1effb..b16ffe0f78 100644
--- a/docs/topics/async.txt
+++ b/docs/topics/async.txt
@@ -94,6 +94,8 @@ view functions:
* :func:`~django.views.decorators.http.require_GET`
* :func:`~django.views.decorators.http.require_POST`
* :func:`~django.views.decorators.http.require_safe`
+* :func:`~django.views.decorators.vary.vary_on_cookie`
+* :func:`~django.views.decorators.vary.vary_on_headers`
* ``xframe_options_deny()``
* ``xframe_options_sameorigin()``
* ``xframe_options_exempt()``
diff --git a/docs/topics/http/decorators.txt b/docs/topics/http/decorators.txt
index 973eda72fe..38e528ecf5 100644
--- a/docs/topics/http/decorators.txt
+++ b/docs/topics/http/decorators.txt
@@ -115,6 +115,10 @@ caching based on specific request headers.
.. function:: vary_on_cookie(func)
+ .. versionchanged:: 5.0
+
+ Support for wrapping asynchronous view functions was added.
+
.. function:: vary_on_headers(*headers)
The ``Vary`` header defines which request headers a cache mechanism should take
@@ -122,6 +126,10 @@ caching based on specific request headers.
See :ref:`using vary headers <using-vary-headers>`.
+ .. versionchanged:: 5.0
+
+ Support for wrapping asynchronous view functions was added.
+
.. module:: django.views.decorators.cache
Caching
diff --git a/tests/decorators/test_vary.py b/tests/decorators/test_vary.py
new file mode 100644
index 0000000000..ccab18a660
--- /dev/null
+++ b/tests/decorators/test_vary.py
@@ -0,0 +1,69 @@
+from asgiref.sync import iscoroutinefunction
+
+from django.http import HttpRequest, HttpResponse
+from django.test import SimpleTestCase
+from django.views.decorators.vary import vary_on_cookie, vary_on_headers
+
+
+class VaryOnHeadersTests(SimpleTestCase):
+ def test_wrapped_sync_function_is_not_coroutine_function(self):
+ def sync_view(request):
+ return HttpResponse()
+
+ wrapped_view = vary_on_headers()(sync_view)
+ self.assertIs(iscoroutinefunction(wrapped_view), False)
+
+ def test_wrapped_async_function_is_coroutine_function(self):
+ async def async_view(request):
+ return HttpResponse()
+
+ wrapped_view = vary_on_headers()(async_view)
+ self.assertIs(iscoroutinefunction(wrapped_view), True)
+
+ def test_vary_on_headers_decorator(self):
+ @vary_on_headers("Header", "Another-header")
+ def sync_view(request):
+ return HttpResponse()
+
+ response = sync_view(HttpRequest())
+ self.assertEqual(response.get("Vary"), "Header, Another-header")
+
+ async def test_vary_on_headers_decorator_async_view(self):
+ @vary_on_headers("Header", "Another-header")
+ async def async_view(request):
+ return HttpResponse()
+
+ response = await async_view(HttpRequest())
+ self.assertEqual(response.get("Vary"), "Header, Another-header")
+
+
+class VaryOnCookieTests(SimpleTestCase):
+ def test_wrapped_sync_function_is_not_coroutine_function(self):
+ def sync_view(request):
+ return HttpResponse()
+
+ wrapped_view = vary_on_cookie(sync_view)
+ self.assertIs(iscoroutinefunction(wrapped_view), False)
+
+ def test_wrapped_async_function_is_coroutine_function(self):
+ async def async_view(request):
+ return HttpResponse()
+
+ wrapped_view = vary_on_cookie(async_view)
+ self.assertIs(iscoroutinefunction(wrapped_view), True)
+
+ def test_vary_on_cookie_decorator(self):
+ @vary_on_cookie
+ def sync_view(request):
+ return HttpResponse()
+
+ response = sync_view(HttpRequest())
+ self.assertEqual(response.get("Vary"), "Cookie")
+
+ async def test_vary_on_cookie_decorator_async_view(self):
+ @vary_on_cookie
+ async def async_view(request):
+ return HttpResponse()
+
+ response = await async_view(HttpRequest())
+ self.assertEqual(response.get("Vary"), "Cookie")