diff --git a/django/core/checks/urls.py b/django/core/checks/urls.py index 34eff9671d..aef2bfebb0 100644 --- a/django/core/checks/urls.py +++ b/django/core/checks/urls.py @@ -1,6 +1,8 @@ +import inspect from collections import Counter from django.conf import settings +from django.core.exceptions import ViewDoesNotExist from . import Error, Tags, Warning, register @@ -115,3 +117,43 @@ def E006(name): "The {} setting must end with a slash.".format(name), id="urls.E006", ) + + +@register(Tags.urls) +def check_custom_error_handlers(app_configs, **kwargs): + if not getattr(settings, "ROOT_URLCONF", None): + return [] + + from django.urls import get_resolver + + resolver = get_resolver() + + errors = [] + # All handlers take (request, exception) arguments except handler500 + # which takes (request). + for status_code, num_parameters in [(400, 2), (403, 2), (404, 2), (500, 1)]: + try: + handler = resolver.resolve_error_handler(status_code) + except (ImportError, ViewDoesNotExist) as e: + path = getattr(resolver.urlconf_module, "handler%s" % status_code) + msg = ( + "The custom handler{status_code} view '{path}' could not be " + "imported." + ).format(status_code=status_code, path=path) + errors.append(Error(msg, hint=str(e), id="urls.E008")) + continue + signature = inspect.signature(handler) + args = [None] * num_parameters + try: + signature.bind(*args) + except TypeError: + msg = ( + "The custom handler{status_code} view '{path}' does not " + "take the correct number of arguments ({args})." + ).format( + status_code=status_code, + path=handler.__module__ + "." + handler.__qualname__, + args="request, exception" if num_parameters == 2 else "request", + ) + errors.append(Error(msg, id="urls.E007")) + return errors diff --git a/django/urls/resolvers.py b/django/urls/resolvers.py index 3607c84228..5f9941dd65 100644 --- a/django/urls/resolvers.py +++ b/django/urls/resolvers.py @@ -19,7 +19,7 @@ from asgiref.local import Local from django.conf import settings from django.core.checks import Error, Warning from django.core.checks.urls import check_resolver -from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist +from django.core.exceptions import ImproperlyConfigured from django.utils.datastructures import MultiValueDict from django.utils.functional import cached_property from django.utils.http import RFC3986_SUBDELIMS, escape_leading_slashes @@ -518,40 +518,8 @@ class URLResolver: messages = [] for pattern in self.url_patterns: messages.extend(check_resolver(pattern)) - messages.extend(self._check_custom_error_handlers()) return messages or self.pattern.check() - def _check_custom_error_handlers(self): - messages = [] - # All handlers take (request, exception) arguments except handler500 - # which takes (request). - for status_code, num_parameters in [(400, 2), (403, 2), (404, 2), (500, 1)]: - try: - handler = self.resolve_error_handler(status_code) - except (ImportError, ViewDoesNotExist) as e: - path = getattr(self.urlconf_module, "handler%s" % status_code) - msg = ( - "The custom handler{status_code} view '{path}' could not be " - "imported." - ).format(status_code=status_code, path=path) - messages.append(Error(msg, hint=str(e), id="urls.E008")) - continue - signature = inspect.signature(handler) - args = [None] * num_parameters - try: - signature.bind(*args) - except TypeError: - msg = ( - "The custom handler{status_code} view '{path}' does not " - "take the correct number of arguments ({args})." - ).format( - status_code=status_code, - path=handler.__module__ + "." + handler.__qualname__, - args="request, exception" if num_parameters == 2 else "request", - ) - messages.append(Error(msg, id="urls.E007")) - return messages - def _populate(self): # Short-circuit if called recursively in this thread to prevent # infinite recursion. Concurrent threads may call this at the same diff --git a/tests/check_framework/test_urls.py b/tests/check_framework/test_urls.py index 4b6a4a6f3e..a31c5fd856 100644 --- a/tests/check_framework/test_urls.py +++ b/tests/check_framework/test_urls.py @@ -2,6 +2,7 @@ from django.conf import settings from django.core.checks.messages import Error, Warning from django.core.checks.urls import ( E006, + check_custom_error_handlers, check_url_config, check_url_namespaces_unique, check_url_settings, @@ -243,7 +244,7 @@ class CheckCustomErrorHandlersTests(SimpleTestCase): ROOT_URLCONF="check_framework.urls.bad_function_based_error_handlers", ) def test_bad_function_based_handlers(self): - result = check_url_config(None) + result = check_custom_error_handlers(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)): @@ -264,7 +265,7 @@ class CheckCustomErrorHandlersTests(SimpleTestCase): ROOT_URLCONF="check_framework.urls.bad_class_based_error_handlers", ) def test_bad_class_based_handlers(self): - result = check_url_config(None) + result = check_custom_error_handlers(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): @@ -287,7 +288,7 @@ class CheckCustomErrorHandlersTests(SimpleTestCase): ROOT_URLCONF="check_framework.urls.bad_error_handlers_invalid_path" ) def test_bad_handlers_invalid_path(self): - result = check_url_config(None) + result = check_custom_error_handlers(None) paths = [ "django.views.bad_handler", "django.invalid_module.bad_handler", @@ -318,14 +319,14 @@ class CheckCustomErrorHandlersTests(SimpleTestCase): ROOT_URLCONF="check_framework.urls.good_function_based_error_handlers", ) def test_good_function_based_handlers(self): - result = check_url_config(None) + result = check_custom_error_handlers(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_custom_error_handlers(None) self.assertEqual(result, [])