summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Lomax <lomax.on.the.run@gmail.com>2023-07-08 21:00:42 +0100
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2023-07-10 07:55:02 +0200
commit953f81e078fb7299b6d40d4e599e5f559e03952b (patch)
tree66cafa352d883e72240a188b1f27ea22d4d79d03
parent6d427288e410e9cad39b1aa1b4d4893fbd391282 (diff)
Refs #31949 -- Made @csrf_exempt decorator to work with async functions.
-rw-r--r--django/views/decorators/csrf.py20
-rw-r--r--docs/ref/csrf.txt4
-rw-r--r--docs/releases/5.0.txt1
-rw-r--r--docs/topics/async.txt1
-rw-r--r--tests/decorators/test_csrf.py37
5 files changed, 58 insertions, 5 deletions
diff --git a/django/views/decorators/csrf.py b/django/views/decorators/csrf.py
index 92771c56df..4b8b8804b0 100644
--- a/django/views/decorators/csrf.py
+++ b/django/views/decorators/csrf.py
@@ -1,5 +1,7 @@
from functools import wraps
+from asgiref.sync import iscoroutinefunction
+
from django.middleware.csrf import CsrfViewMiddleware, get_token
from django.utils.decorators import decorator_from_middleware
@@ -51,9 +53,17 @@ def csrf_exempt(view_func):
# view_func.csrf_exempt = True would also work, but decorators are nicer
# if they don't have side effects, so return a new function.
- @wraps(view_func)
- def wrapper_view(*args, **kwargs):
- return view_func(*args, **kwargs)
- wrapper_view.csrf_exempt = True
- return wrapper_view
+ if iscoroutinefunction(view_func):
+
+ async def _view_wrapper(request, *args, **kwargs):
+ return await view_func(request, *args, **kwargs)
+
+ else:
+
+ def _view_wrapper(request, *args, **kwargs):
+ return view_func(request, *args, **kwargs)
+
+ _view_wrapper.csrf_exempt = True
+
+ return wraps(view_func)(_view_wrapper)
diff --git a/docs/ref/csrf.txt b/docs/ref/csrf.txt
index 583f78472c..d52539e6b6 100644
--- a/docs/ref/csrf.txt
+++ b/docs/ref/csrf.txt
@@ -150,6 +150,10 @@ class-based views<decorating-class-based-views>`.
def my_view(request):
return HttpResponse("Hello world")
+ .. versionchanged:: 5.0
+
+ Support for wrapping asynchronous view functions was added.
+
.. function:: csrf_protect(view)
Decorator that provides the protection of ``CsrfViewMiddleware`` to a view.
diff --git a/docs/releases/5.0.txt b/docs/releases/5.0.txt
index 722d3bcf0b..8884886b51 100644
--- a/docs/releases/5.0.txt
+++ b/docs/releases/5.0.txt
@@ -258,6 +258,7 @@ Decorators
* :func:`~django.views.decorators.cache.cache_control`
* :func:`~django.views.decorators.cache.never_cache`
* :func:`~django.views.decorators.common.no_append_slash`
+ * :func:`~django.views.decorators.csrf.csrf_exempt`
* :func:`~django.views.decorators.debug.sensitive_variables`
* :func:`~django.views.decorators.debug.sensitive_post_parameters`
* :func:`~django.views.decorators.http.condition`
diff --git a/docs/topics/async.txt b/docs/topics/async.txt
index 6713914283..fb50f007e2 100644
--- a/docs/topics/async.txt
+++ b/docs/topics/async.txt
@@ -84,6 +84,7 @@ view functions:
* :func:`~django.views.decorators.cache.cache_control`
* :func:`~django.views.decorators.cache.never_cache`
* :func:`~django.views.decorators.common.no_append_slash`
+* :func:`~django.views.decorators.csrf.csrf_exempt`
* :func:`~django.views.decorators.http.condition`
* :func:`~django.views.decorators.http.etag`
* :func:`~django.views.decorators.http.last_modified`
diff --git a/tests/decorators/test_csrf.py b/tests/decorators/test_csrf.py
new file mode 100644
index 0000000000..275b12ee9d
--- /dev/null
+++ b/tests/decorators/test_csrf.py
@@ -0,0 +1,37 @@
+from asgiref.sync import iscoroutinefunction
+
+from django.http import HttpRequest, HttpResponse
+from django.test import SimpleTestCase
+from django.views.decorators.csrf import csrf_exempt
+
+
+class CsrfExemptTests(SimpleTestCase):
+ def test_wrapped_sync_function_is_not_coroutine_function(self):
+ def sync_view(request):
+ return HttpResponse()
+
+ wrapped_view = csrf_exempt(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 = csrf_exempt(async_view)
+ self.assertIs(iscoroutinefunction(wrapped_view), True)
+
+ def test_csrf_exempt_decorator(self):
+ @csrf_exempt
+ def sync_view(request):
+ return HttpResponse()
+
+ self.assertIs(sync_view.csrf_exempt, True)
+ self.assertIsInstance(sync_view(HttpRequest()), HttpResponse)
+
+ async def test_csrf_exempt_decorator_async_view(self):
+ @csrf_exempt
+ async def async_view(request):
+ return HttpResponse()
+
+ self.assertIs(async_view.csrf_exempt, True)
+ self.assertIsInstance(await async_view(HttpRequest()), HttpResponse)