diff --git a/django/urls/resolvers.py b/django/urls/resolvers.py index 5e3e6dea16..2a9dc11f28 100644 --- a/django/urls/resolvers.py +++ b/django/urls/resolvers.py @@ -30,12 +30,13 @@ from .utils import get_callable class ResolverMatch: - def __init__(self, func, args, kwargs, url_name=None, app_names=None, namespaces=None, route=None): + def __init__(self, func, args, kwargs, url_name=None, app_names=None, namespaces=None, route=None, tried=None): self.func = func self.args = args self.kwargs = kwargs self.url_name = url_name self.route = route + self.tried = tried # If a URLRegexResolver doesn't have a namespace or app_name, it passes # in an empty value. @@ -525,6 +526,13 @@ class URLResolver: self._populate() return self._app_dict[language_code] + @staticmethod + def _extend_tried(tried, pattern, sub_tried=None): + if sub_tried is None: + tried.append([pattern]) + else: + tried.extend([pattern, *t] for t in sub_tried) + @staticmethod def _join_route(route1, route2): """Join two routes, without the starting ^ in the second route.""" @@ -549,11 +557,7 @@ class URLResolver: try: sub_match = pattern.resolve(new_path) except Resolver404 as e: - sub_tried = e.args[0].get('tried') - if sub_tried is not None: - tried.extend([pattern] + t for t in sub_tried) - else: - tried.append([pattern]) + self._extend_tried(tried, pattern, e.args[0].get('tried')) else: if sub_match: # Merge captured arguments in match with submatch @@ -566,6 +570,7 @@ class URLResolver: if not sub_match_dict: sub_match_args = args + sub_match.args current_route = '' if isinstance(pattern, URLPattern) else str(pattern.pattern) + self._extend_tried(tried, pattern, sub_match.tried) return ResolverMatch( sub_match.func, sub_match_args, @@ -574,8 +579,9 @@ class URLResolver: [self.app_name] + sub_match.app_names, [self.namespace] + sub_match.namespaces, self._join_route(current_route, sub_match.route), + tried, ) - tried.append([pattern]) + self._extend_tried(tried, pattern) raise Resolver404({'tried': tried, 'path': new_path}) raise Resolver404({'path': path}) diff --git a/django/views/debug.py b/django/views/debug.py index e45ef01ace..7a89f7bd15 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -481,8 +481,10 @@ def technical_404_response(request, exception): try: tried = exception.args[0]['tried'] except (IndexError, TypeError, KeyError): - tried = [] + resolved = True + tried = request.resolver_match.tried if request.resolver_match else None else: + resolved = False if (not tried or ( # empty URLconf request.path == '/' and len(tried) == 1 and # default URLconf @@ -520,6 +522,7 @@ def technical_404_response(request, exception): 'root_urlconf': settings.ROOT_URLCONF, 'request_path': error_url, 'urlpatterns': tried, + 'resolved': resolved, 'reason': str(exception), 'request': request, 'settings': reporter_filter.get_safe_settings(), diff --git a/django/views/templates/technical_404.html b/django/views/templates/technical_404.html index 694309aa13..077bb20964 100644 --- a/django/views/templates/technical_404.html +++ b/django/views/templates/technical_404.html @@ -60,8 +60,11 @@
{% if request_path %}
- The current path, {{ request_path }}
,{% else %}
- The empty path{% endif %} didn’t match any of these.
+ The current path, {{ request_path }}
,
+ {% else %}
+ The empty path
+ {% endif %}
+ {% if resolved %}matched the last one.{% else %}didn’t match any of these.{% endif %}
{{ reason }}
diff --git a/docs/ref/urlresolvers.txt b/docs/ref/urlresolvers.txt index 745ee9d6fd..b9af97f790 100644 --- a/docs/ref/urlresolvers.txt +++ b/docs/ref/urlresolvers.txt @@ -137,6 +137,13 @@ If the URL does not resolve, the function raises a For example, if ``path('users/not-in-urls
, didn’t match', status_code=404)
+ self.assertContains(
+ response,
+ 'The current path, not-in-urls
, didn’t match any '
+ 'of these.
not-in-urls
, didn’t match', status_code=404)
+ self.assertContains(
+ response,
+ 'The current path, not-in-urls
, didn’t match any '
+ 'of these.
The empty path didn’t match any of these.
', + status_code=404, + html=True, + ) def test_technical_404(self): response = self.client.get('/technical404/') self.assertContains(response, "Raised by:", status_code=404) self.assertContains(response, "view_tests.views.technical404", status_code=404) + self.assertContains( + response, + 'The current path, technical404/
, matched the '
+ 'last one.