Fixed #31791 -- Made technical 404 debug page display the tried URL patterns for Http404.

This commit is contained in:
Jon Dufresne 2020-07-07 16:23:54 -07:00 committed by Mariusz Felisiak
parent 83dea65ed6
commit 11ebc6479f
5 changed files with 56 additions and 13 deletions

View File

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

View File

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

View File

@ -60,8 +60,11 @@
</ol>
<p>
{% if request_path %}
The current path, <code>{{ request_path }}</code>,{% else %}
The empty path{% endif %} didnt match any of these.
The current path, <code>{{ request_path }}</code>,
{% else %}
The empty path
{% endif %}
{% if resolved %}matched the last one.{% else %}didnt match any of these.{% endif %}
</p>
{% else %}
<p>{{ reason }}</p>

View File

@ -137,6 +137,13 @@ If the URL does not resolve, the function raises a
For example, if ``path('users/<id>/', ...)`` is the matching pattern,
``route`` will contain ``'users/<id>/'``.
.. attribute:: ResolverMatch.tried
.. versionadded:: 3.2
The list of URL patterns tried before the URL either matched one or
exhausted available patterns.
.. attribute:: ResolverMatch.app_name
The application namespace for the URL pattern that matches the

View File

@ -113,13 +113,25 @@ class DebugViewTests(SimpleTestCase):
def test_404(self):
response = self.client.get('/raises404/')
self.assertEqual(response.status_code, 404)
self.assertContains(response, '<code>not-in-urls</code>, didnt match', status_code=404)
self.assertContains(
response,
'<p>The current path, <code>not-in-urls</code>, didnt match any '
'of these.</p>',
status_code=404,
html=True,
)
def test_404_not_in_urls(self):
response = self.client.get('/not-in-urls')
self.assertNotContains(response, "Raised by:", status_code=404)
self.assertContains(response, "Django tried these URL patterns", status_code=404)
self.assertContains(response, '<code>not-in-urls</code>, didnt match', status_code=404)
self.assertContains(
response,
'<p>The current path, <code>not-in-urls</code>, didnt match any '
'of these.</p>',
status_code=404,
html=True,
)
# Pattern and view name of a RegexURLPattern appear.
self.assertContains(response, r"^regex-post/(?P&lt;pk&gt;[0-9]+)/$", status_code=404)
self.assertContains(response, "[name='regex-post']", status_code=404)
@ -130,12 +142,24 @@ class DebugViewTests(SimpleTestCase):
@override_settings(ROOT_URLCONF=WithoutEmptyPathUrls)
def test_404_empty_path_not_in_urls(self):
response = self.client.get('/')
self.assertContains(response, 'The empty path didnt match any of these.', status_code=404)
self.assertContains(
response,
'<p>The empty path didnt match any of these.</p>',
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,
'<p>The current path, <code>technical404/</code>, matched the '
'last one.</p>',
status_code=404,
html=True,
)
def test_classbased_technical_404(self):
response = self.client.get('/classbased404/')