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:
Daniyal 2020-12-12 01:00:50 +05:30 committed by Mariusz Felisiak
parent 0c0b87725b
commit 7c08f26bf0
7 changed files with 79 additions and 17 deletions

View File

@ -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):

View File

@ -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, [])

View File

@ -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()

View File

@ -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()

View File

@ -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):
""" """