Debug 404 page now displays names of URL patterns, if they exist.

Thanks to Tobias McNulty for the patch. Fixed #9310.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@13769 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Malcolm Tredinnick 2010-09-12 19:27:33 +00:00
parent 4323c228cc
commit 882cba0f69
6 changed files with 76 additions and 5 deletions

View File

@ -252,9 +252,9 @@ class RegexURLResolver(object):
except Resolver404, e:
sub_tried = e.args[0].get('tried')
if sub_tried is not None:
tried.extend([(pattern.regex.pattern + ' ' + t) for t in sub_tried])
tried.extend([[pattern] + t for t in sub_tried])
else:
tried.append(pattern.regex.pattern)
tried.append([pattern])
else:
if sub_match:
sub_match_dict = dict([(smart_str(k), v) for k, v in match.groupdict().items()])
@ -262,7 +262,7 @@ class RegexURLResolver(object):
for k, v in sub_match.kwargs.iteritems():
sub_match_dict[smart_str(k)] = v
return ResolverMatch(sub_match.func, sub_match.args, sub_match_dict, sub_match.url_name, self.app_name or sub_match.app_name, [self.namespace] + sub_match.namespaces)
tried.append(pattern.regex.pattern)
tried.append([pattern])
raise Resolver404({'tried': tried, 'path': new_path})
raise Resolver404({'path' : path})

View File

@ -778,7 +778,12 @@ TECHNICAL_404_TEMPLATE = """
</p>
<ol>
{% for pattern in urlpatterns %}
<li>{{ pattern }}</li>
<li>
{% for pat in pattern %}
{{ pat.regex.pattern }}
{% if forloop.last and pat.name %}[name='{{ pat.name }}']{% endif %}
{% endfor %}
</li>
{% endfor %}
</ol>
<p>The current URL, <code>{{ request_path|escape }}</code>, didn't match any of these.</p>

View File

@ -0,0 +1,10 @@
from django.conf.urls.defaults import *
from views import empty_view
urlpatterns = patterns('',
url(r'^$', empty_view, name="named-url3"),
url(r'^extra/(?P<extra>\w+)/$', empty_view, name="named-url4"),
url(r'^(?P<one>\d+)|(?P<two>\d+)/$', empty_view),
(r'^included/', include('regressiontests.urlpatterns_reverse.included_named_urls2')),
)

View File

@ -0,0 +1,9 @@
from django.conf.urls.defaults import *
from views import empty_view
urlpatterns = patterns('',
url(r'^$', empty_view, name="named-url5"),
url(r'^extra/(?P<extra>\w+)/$', empty_view, name="named-url6"),
url(r'^(?P<one>\d+)|(?P<two>\d+)/$', empty_view),
)

View File

@ -0,0 +1,9 @@
from django.conf.urls.defaults import *
from views import empty_view
urlpatterns = patterns('',
url(r'^$', empty_view, name="named-url1"),
url(r'^extra/(?P<extra>\w+)/$', empty_view, name="named-url2"),
url(r'^(?P<one>\d+)|(?P<two>\d+)/$', empty_view),
(r'^included/', include('regressiontests.urlpatterns_reverse.included_named_urls')),
)

View File

@ -18,7 +18,9 @@ import unittest
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import reverse, resolve, NoReverseMatch, Resolver404, ResolverMatch
from django.core.urlresolvers import reverse, resolve, NoReverseMatch,\
Resolver404, ResolverMatch,\
RegexURLResolver, RegexURLPattern
from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect
from django.shortcuts import redirect
from django.test import TestCase
@ -172,6 +174,42 @@ class ResolverTests(unittest.TestCase):
self.assertRaises(Resolver404, resolve, 'a')
self.assertRaises(Resolver404, resolve, '\\')
self.assertRaises(Resolver404, resolve, '.')
def test_404_tried_urls_have_names(self):
"""
Verifies that the list of URLs that come back from a Resolver404
exception contains a list in the right format for printing out in
the DEBUG 404 page with both the patterns and URL names, if available.
"""
urls = 'regressiontests.urlpatterns_reverse.named_urls'
# this list matches the expected URL types and names returned when
# you try to resolve a non-existent URL in the first level of included
# URLs in named_urls.py (e.g., '/included/non-existent-url')
url_types_names = [
[{'type': RegexURLPattern, 'name': 'named-url1'}],
[{'type': RegexURLPattern, 'name': 'named-url2'}],
[{'type': RegexURLPattern, 'name': None}],
[{'type': RegexURLResolver}, {'type': RegexURLPattern, 'name': 'named-url3'}],
[{'type': RegexURLResolver}, {'type': RegexURLPattern, 'name': 'named-url4'}],
[{'type': RegexURLResolver}, {'type': RegexURLPattern, 'name': None}],
[{'type': RegexURLResolver}, {'type': RegexURLResolver}],
]
try:
resolve('/included/non-existent-url', urlconf=urls)
self.fail('resolve did not raise a 404')
except Resolver404, e:
# make sure we at least matched the root ('/') url resolver:
self.assertTrue('tried' in e.args[0])
tried = e.args[0]['tried']
self.assertEqual(len(e.args[0]['tried']), len(url_types_names), 'Wrong number of tried URLs returned. Expected %s, got %s.' % (len(url_types_names), len(e.args[0]['tried'])))
for tried, expected in zip(e.args[0]['tried'], url_types_names):
for t, e in zip(tried, expected):
self.assertTrue(isinstance(t, e['type']), '%s is not an instance of %s' % (t, e['type']))
if 'name' in e:
if not e['name']:
self.assertTrue(t.name is None, 'Expected no URL name but found %s.' % t.name)
else:
self.assertEqual(t.name, e['name'], 'Wrong URL name. Expected "%s", got "%s".' % (e['name'], t.name))
class ReverseShortcutTests(TestCase):
urls = 'regressiontests.urlpatterns_reverse.urls'