diff --git a/django/views/decorators/vary.py b/django/views/decorators/vary.py index b1459295d2465..9beab8b4db845 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 index(request): """ 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 8884886b514a4..0ad0835c299b1 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 8554e1effb62a..b16ffe0f786dd 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 973eda72fea3e..38e528ecf53ee 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 `. + .. 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 0000000000000..ccab18a6609ba --- /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")