diff --git a/django/urls/resolvers.py b/django/urls/resolvers.py index 8f59313c93..9d3379a821 100644 --- a/django/urls/resolvers.py +++ b/django/urls/resolvers.py @@ -15,7 +15,7 @@ from urllib.parse import quote 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 +from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist from django.utils.datastructures import MultiValueDict from django.utils.functional import cached_property from django.utils.http import RFC3986_SUBDELIMS, escape_leading_slashes @@ -405,7 +405,15 @@ class URLResolver: # 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)]: - handler, param_dict = self.resolve_error_handler(status_code) + try: + handler, param_dict = 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: diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt index 6ec3028c35..9d48138076 100644 --- a/docs/ref/checks.txt +++ b/docs/ref/checks.txt @@ -464,6 +464,8 @@ The following checks are performed on your URL configuration: end with a slash. * **urls.E007**: The custom ``handlerXXX`` view ``'path.to.view'`` does not take the correct number of arguments (…). +* **urls.E008**: The custom ``handlerXXX`` view ``'path.to.view'`` could not be + imported. ``contrib`` app checks ====================== diff --git a/docs/releases/2.2.1.txt b/docs/releases/2.2.1.txt index 48cbfb3a8f..0ca76bcbf8 100644 --- a/docs/releases/2.2.1.txt +++ b/docs/releases/2.2.1.txt @@ -46,3 +46,6 @@ Bugfixes * Fixed a regression in Django 2.2 where :class:`~django.contrib.postgres.search.SearchVector` generates SQL that is not indexable (:ticket:`30385`). + +* Fixed a regression in Django 2.2 that caused an exception to be raised when + a custom error handler could not be imported (:ticket:`30318`). diff --git a/tests/check_framework/test_urls.py b/tests/check_framework/test_urls.py index 67de26c690..217b5e7bad 100644 --- a/tests/check_framework/test_urls.py +++ b/tests/check_framework/test_urls.py @@ -181,6 +181,29 @@ class CheckCustomErrorHandlersTests(SimpleTestCase): 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) + paths = [ + 'django.views.bad_handler', + 'django.invalid_module.bad_handler', + 'invalid_module.bad_handler', + 'django', + ] + hints = [ + "Could not import '{}'. View does not exist in module django.views.", + "Could not import '{}'. Parent module django.invalid_module does not exist.", + "No module named 'invalid_module'", + "Could not import '{}'. The path must be fully qualified.", + ] + for code, path, hint, error in zip([400, 403, 404, 500], paths, hints, result): + with self.subTest('handler{}'.format(code)): + self.assertEqual(error, Error( + "The custom handler{} view '{}' could not be imported.".format(code, path), + hint=hint.format(path), + id='urls.E008', + )) + @override_settings(ROOT_URLCONF='check_framework.urls.good_error_handlers') def test_good_handlers(self): result = check_url_config(None) diff --git a/tests/check_framework/urls/bad_error_handlers_invalid_path.py b/tests/check_framework/urls/bad_error_handlers_invalid_path.py new file mode 100644 index 0000000000..77e0c639e0 --- /dev/null +++ b/tests/check_framework/urls/bad_error_handlers_invalid_path.py @@ -0,0 +1,6 @@ +urlpatterns = [] + +handler400 = 'django.views.bad_handler' +handler403 = 'django.invalid_module.bad_handler' +handler404 = 'invalid_module.bad_handler' +handler500 = 'django'