diff --git a/django/views/generic/base.py b/django/views/generic/base.py index ab800ebce8..ca14156510 100644 --- a/django/views/generic/base.py +++ b/django/views/generic/base.py @@ -1,5 +1,4 @@ import logging -from functools import update_wrapper from django.core.exceptions import ImproperlyConfigured from django.http import ( @@ -71,12 +70,16 @@ class View: view.view_class = cls view.view_initkwargs = initkwargs - # take name and docstring from class - update_wrapper(view, cls, updated=()) + # __name__ and __qualname__ are intentionally left unchanged as + # 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 def setup(self, request, *args, **kwargs): diff --git a/tests/check_framework/test_urls.py b/tests/check_framework/test_urls.py index f0c257bc77..2ef3d5b07f 100644 --- a/tests/check_framework/test_urls.py +++ b/tests/check_framework/test_urls.py @@ -167,20 +167,41 @@ class UpdatedToPathTests(SimpleTestCase): class CheckCustomErrorHandlersTests(SimpleTestCase): - @override_settings(ROOT_URLCONF='check_framework.urls.bad_error_handlers') - def test_bad_handlers(self): + @override_settings( + ROOT_URLCONF='check_framework.urls.bad_function_based_error_handlers', + ) + def test_bad_function_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{}'.format(code)): self.assertEqual(error, Error( - "The custom handler{} view " - "'check_framework.urls.bad_error_handlers.bad_handler' " + "The custom handler{} view 'check_framework.urls." + "bad_function_based_error_handlers.bad_handler' " "does not take the correct number of arguments (request{})." .format(code, ', exception' if num_params == 2 else ''), 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." + ".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') def test_bad_handlers_invalid_path(self): result = check_url_config(None) @@ -204,8 +225,17 @@ class CheckCustomErrorHandlersTests(SimpleTestCase): id='urls.E008', )) - @override_settings(ROOT_URLCONF='check_framework.urls.good_error_handlers') - def test_good_handlers(self): + @override_settings( + 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) self.assertEqual(result, []) diff --git a/tests/check_framework/urls/bad_class_based_error_handlers.py b/tests/check_framework/urls/bad_class_based_error_handlers.py new file mode 100644 index 0000000000..346e7f59be --- /dev/null +++ b/tests/check_framework/urls/bad_class_based_error_handlers.py @@ -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() diff --git a/tests/check_framework/urls/bad_error_handlers.py b/tests/check_framework/urls/bad_function_based_error_handlers.py similarity index 100% rename from tests/check_framework/urls/bad_error_handlers.py rename to tests/check_framework/urls/bad_function_based_error_handlers.py diff --git a/tests/check_framework/urls/good_class_based_error_handlers.py b/tests/check_framework/urls/good_class_based_error_handlers.py new file mode 100644 index 0000000000..56e6408a71 --- /dev/null +++ b/tests/check_framework/urls/good_class_based_error_handlers.py @@ -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() diff --git a/tests/check_framework/urls/good_error_handlers.py b/tests/check_framework/urls/good_function_based_error_handlers.py similarity index 100% rename from tests/check_framework/urls/good_error_handlers.py rename to tests/check_framework/urls/good_function_based_error_handlers.py diff --git a/tests/generic_views/test_base.py b/tests/generic_views/test_base.py index f4b8a487ff..5872ecf3db 100644 --- a/tests/generic_views/test_base.py +++ b/tests/generic_views/test_base.py @@ -172,12 +172,16 @@ class ViewTest(SimpleTestCase): def test_class_attributes(self): """ - The callable returned from as_view() has proper - docstring, name and module. + The callable returned from as_view() has proper special attributes. """ - self.assertEqual(SimpleView.__doc__, SimpleView.as_view().__doc__) - self.assertEqual(SimpleView.__name__, SimpleView.as_view().__name__) - self.assertEqual(SimpleView.__module__, SimpleView.as_view().__module__) + cls = SimpleView + view = cls.as_view() + 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__}..view') + self.assertEqual(view.__annotations__, cls.dispatch.__annotations__) + self.assertFalse(hasattr(view, '__wrapped__')) def test_dispatch_decoration(self): """