#37065 new Bug

@method_decorator can't be used on async view at class-level with name="dispatch"

Reported by: Jacob Walls Owned by:
Component: Utilities Version: 5.2
Severity: Normal Keywords: async
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

@method_decorator(..., name="dispatch") doesn't work for async views that don't override dispatch(), because dispatch() is sync, which is enough to fool the logic in #35083.

  • tests/decorators/test_cache.py

    diff --git a/tests/decorators/test_cache.py b/tests/decorators/test_cache.py
    index 1aca6967e0..c3b1668a7c 100644
    a b  
     1from http import HTTPStatus
    12from inspect import iscoroutinefunction
    23from unittest import mock
    34
    from django.http import HttpRequest, HttpResponse  
    56from django.test import SimpleTestCase
    67from django.utils.decorators import method_decorator
    78from django.views.decorators.cache import cache_control, cache_page, never_cache
     9from django.views.generic import View
    810
    911
    1012class HttpRequestProxy:
    class NeverCacheDecoratorTest(SimpleTestCase):  
    217219        with self.assertRaisesMessage(TypeError, msg):
    218220            await MyClass().async_view(HttpRequestProxy(request))
    219221
     222    async def test_never_cache_method_decorator_http_request_async_view(self):
     223        @method_decorator(never_cache, name="dispatch")
     224        class MyClass(View):
     225            async def get(self, request):
     226                return HttpResponse()
     227
     228        request = HttpRequest()
     229        request.method = "GET"
     230        response = await MyClass().dispatch(request)
     231        self.assertEqual(response.status_code, HTTPStatus.OK)
     232
    220233    def test_never_cache_decorator_http_request_proxy(self):
    221234        class MyClass:
    222235            @method_decorator(never_cache)
ERROR: test_never_cache_method_decorator_http_request_async_view (decorators.test_cache.NeverCacheDecoratorTest.test_never_cache_method_decorator_http_request_async_view)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/jwalls/my314/lib/python3.14/site-packages/asgiref/sync.py", line 325, in __call__
    return call_result.result()
           ~~~~~~~~~~~~~~~~~~^^
  File "/Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/concurrent/futures/_base.py", line 443, in result
    return self.__get_result()
           ~~~~~~~~~~~~~~~~~^^
  File "/Library/Frameworks/Python.framework/Versions/3.14/lib/python3.14/concurrent/futures/_base.py", line 395, in __get_result
    raise self._exception
  File "/Users/jwalls/my314/lib/python3.14/site-packages/asgiref/sync.py", line 365, in main_wrap
    result = await awaitable
             ^^^^^^^^^^^^^^^
  File "/Users/jwalls/django/tests/decorators/test_cache.py", line 230, in test_never_cache_method_decorator_http_request_async_view
    response = await MyClass().dispatch(request)
                     ~~~~~~~~~~~~~~~~~~^^^^^^^^^
  File "/Users/jwalls/django/django/utils/decorators.py", line 47, in _wrapper
    return bound_method(*args, **kwargs)
  File "/Users/jwalls/django/django/views/decorators/cache.py", line 80, in _view_wrapper
    add_never_cache_headers(response)
    ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^
  File "/Users/jwalls/django/django/utils/cache.py", line 294, in add_never_cache_headers
    patch_response_headers(response, cache_timeout=-1)
    ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/jwalls/django/django/utils/cache.py", line 285, in patch_response_headers
    if not response.has_header("Expires"):
           ^^^^^^^^^^^^^^^^^^^
AttributeError: 'coroutine' object has no attribute 'has_header'

Moving the decorator to the method instead of the class works. We probably will prefer a test inside the test class added in #35083 rather than merging my sketched test.

Change History (0)

Note: See TracTickets for help on using tickets.
Back to Top