Fixed #32260 -- Made View.as_view() do not use update_wrapper().
View.as_view() should not use update_wrapper() for copying attributes it's unintended and have side-effects such as adding `self` to the signature. This also fixes system check for arguments of custom error handler views with class-based views. Co-authored-by: Nick Pope <nick.pope@flightdataservices.com>
This commit is contained in:
parent
0c0b87725b
commit
7c08f26bf0
|
@ -1,5 +1,4 @@
|
||||||
import logging
|
import logging
|
||||||
from functools import update_wrapper
|
|
||||||
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.http import (
|
from django.http import (
|
||||||
|
@ -71,12 +70,16 @@ class View:
|
||||||
view.view_class = cls
|
view.view_class = cls
|
||||||
view.view_initkwargs = initkwargs
|
view.view_initkwargs = initkwargs
|
||||||
|
|
||||||
# take name and docstring from class
|
# __name__ and __qualname__ are intentionally left unchanged as
|
||||||
update_wrapper(view, cls, updated=())
|
# view_class should be used to robustly determine the name of the view
|
||||||
|
# instead.
|
||||||
|
view.__doc__ = cls.__doc__
|
||||||
|
view.__module__ = cls.__module__
|
||||||
|
view.__annotations__ = cls.dispatch.__annotations__
|
||||||
|
# Copy possible attributes set by decorators, e.g. @csrf_exempt, from
|
||||||
|
# the dispatch method.
|
||||||
|
view.__dict__.update(cls.dispatch.__dict__)
|
||||||
|
|
||||||
# and possible attributes set by decorators
|
|
||||||
# like csrf_exempt from dispatch
|
|
||||||
update_wrapper(view, cls.dispatch, assigned=())
|
|
||||||
return view
|
return view
|
||||||
|
|
||||||
def setup(self, request, *args, **kwargs):
|
def setup(self, request, *args, **kwargs):
|
||||||
|
|
|
@ -167,20 +167,41 @@ class UpdatedToPathTests(SimpleTestCase):
|
||||||
|
|
||||||
class CheckCustomErrorHandlersTests(SimpleTestCase):
|
class CheckCustomErrorHandlersTests(SimpleTestCase):
|
||||||
|
|
||||||
@override_settings(ROOT_URLCONF='check_framework.urls.bad_error_handlers')
|
@override_settings(
|
||||||
def test_bad_handlers(self):
|
ROOT_URLCONF='check_framework.urls.bad_function_based_error_handlers',
|
||||||
|
)
|
||||||
|
def test_bad_function_based_handlers(self):
|
||||||
result = check_url_config(None)
|
result = check_url_config(None)
|
||||||
self.assertEqual(len(result), 4)
|
self.assertEqual(len(result), 4)
|
||||||
for code, num_params, error in zip([400, 403, 404, 500], [2, 2, 2, 1], result):
|
for code, num_params, error in zip([400, 403, 404, 500], [2, 2, 2, 1], result):
|
||||||
with self.subTest('handler{}'.format(code)):
|
with self.subTest('handler{}'.format(code)):
|
||||||
self.assertEqual(error, Error(
|
self.assertEqual(error, Error(
|
||||||
"The custom handler{} view "
|
"The custom handler{} view 'check_framework.urls."
|
||||||
"'check_framework.urls.bad_error_handlers.bad_handler' "
|
"bad_function_based_error_handlers.bad_handler' "
|
||||||
"does not take the correct number of arguments (request{})."
|
"does not take the correct number of arguments (request{})."
|
||||||
.format(code, ', exception' if num_params == 2 else ''),
|
.format(code, ', exception' if num_params == 2 else ''),
|
||||||
id='urls.E007',
|
id='urls.E007',
|
||||||
))
|
))
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
ROOT_URLCONF='check_framework.urls.bad_class_based_error_handlers',
|
||||||
|
)
|
||||||
|
def test_bad_class_based_handlers(self):
|
||||||
|
result = check_url_config(None)
|
||||||
|
self.assertEqual(len(result), 4)
|
||||||
|
for code, num_params, error in zip([400, 403, 404, 500], [2, 2, 2, 1], result):
|
||||||
|
with self.subTest('handler%s' % code):
|
||||||
|
self.assertEqual(error, Error(
|
||||||
|
"The custom handler%s view 'check_framework.urls."
|
||||||
|
"bad_class_based_error_handlers.HandlerView.as_view."
|
||||||
|
"<locals>.view' does not take the correct number of "
|
||||||
|
"arguments (request%s)." % (
|
||||||
|
code,
|
||||||
|
', exception' if num_params == 2 else '',
|
||||||
|
),
|
||||||
|
id='urls.E007',
|
||||||
|
))
|
||||||
|
|
||||||
@override_settings(ROOT_URLCONF='check_framework.urls.bad_error_handlers_invalid_path')
|
@override_settings(ROOT_URLCONF='check_framework.urls.bad_error_handlers_invalid_path')
|
||||||
def test_bad_handlers_invalid_path(self):
|
def test_bad_handlers_invalid_path(self):
|
||||||
result = check_url_config(None)
|
result = check_url_config(None)
|
||||||
|
@ -204,8 +225,17 @@ class CheckCustomErrorHandlersTests(SimpleTestCase):
|
||||||
id='urls.E008',
|
id='urls.E008',
|
||||||
))
|
))
|
||||||
|
|
||||||
@override_settings(ROOT_URLCONF='check_framework.urls.good_error_handlers')
|
@override_settings(
|
||||||
def test_good_handlers(self):
|
ROOT_URLCONF='check_framework.urls.good_function_based_error_handlers',
|
||||||
|
)
|
||||||
|
def test_good_function_based_handlers(self):
|
||||||
|
result = check_url_config(None)
|
||||||
|
self.assertEqual(result, [])
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
ROOT_URLCONF='check_framework.urls.good_class_based_error_handlers',
|
||||||
|
)
|
||||||
|
def test_good_class_based_handlers(self):
|
||||||
result = check_url_config(None)
|
result = check_url_config(None)
|
||||||
self.assertEqual(result, [])
|
self.assertEqual(result, [])
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
urlpatterns = []
|
||||||
|
|
||||||
|
|
||||||
|
class HandlerView:
|
||||||
|
@classmethod
|
||||||
|
def as_view(cls):
|
||||||
|
def view():
|
||||||
|
pass
|
||||||
|
|
||||||
|
return view
|
||||||
|
|
||||||
|
|
||||||
|
handler400 = HandlerView.as_view()
|
||||||
|
handler403 = HandlerView.as_view()
|
||||||
|
handler404 = HandlerView.as_view()
|
||||||
|
handler500 = HandlerView.as_view()
|
|
@ -0,0 +1,9 @@
|
||||||
|
from django.views.generic import View
|
||||||
|
|
||||||
|
urlpatterns = []
|
||||||
|
|
||||||
|
|
||||||
|
handler400 = View.as_view()
|
||||||
|
handler403 = View.as_view()
|
||||||
|
handler404 = View.as_view()
|
||||||
|
handler500 = View.as_view()
|
|
@ -172,12 +172,16 @@ class ViewTest(SimpleTestCase):
|
||||||
|
|
||||||
def test_class_attributes(self):
|
def test_class_attributes(self):
|
||||||
"""
|
"""
|
||||||
The callable returned from as_view() has proper
|
The callable returned from as_view() has proper special attributes.
|
||||||
docstring, name and module.
|
|
||||||
"""
|
"""
|
||||||
self.assertEqual(SimpleView.__doc__, SimpleView.as_view().__doc__)
|
cls = SimpleView
|
||||||
self.assertEqual(SimpleView.__name__, SimpleView.as_view().__name__)
|
view = cls.as_view()
|
||||||
self.assertEqual(SimpleView.__module__, SimpleView.as_view().__module__)
|
self.assertEqual(view.__doc__, cls.__doc__)
|
||||||
|
self.assertEqual(view.__name__, 'view')
|
||||||
|
self.assertEqual(view.__module__, cls.__module__)
|
||||||
|
self.assertEqual(view.__qualname__, f'{cls.as_view.__qualname__}.<locals>.view')
|
||||||
|
self.assertEqual(view.__annotations__, cls.dispatch.__annotations__)
|
||||||
|
self.assertFalse(hasattr(view, '__wrapped__'))
|
||||||
|
|
||||||
def test_dispatch_decoration(self):
|
def test_dispatch_decoration(self):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue