From 59c3ebc6dddab2a52b5c4079fb78871b35b5dcaa Mon Sep 17 00:00:00 2001
From: Adrian Holovaty <adrian@holovaty.com>
Date: Fri, 5 Aug 2005 22:22:41 +0000
Subject: [PATCH] Greatly improved the 404 error message when DEBUG=True. If
 none of the urlpatterns matches, Django now displays a list of all the
 urlpatterns it tried. This should catch a lot of newbie errors, and it's
 helpful even for power users.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@414 bcc190cf-cafb-0310-a4f2-bffc1f526a37
---
 django/core/handlers/base.py | 33 +++++++++++++++++++++++++++------
 django/core/urlresolvers.py  | 18 +++++++++++++-----
 2 files changed, 40 insertions(+), 11 deletions(-)

diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py
index 115b6ad56f..356e9c45a9 100644
--- a/django/core/handlers/base.py
+++ b/django/core/handlers/base.py
@@ -60,9 +60,9 @@ class BaseHandler:
                 if response:
                     return response
             return callback(request, **param_dict)
-        except exceptions.Http404:
+        except exceptions.Http404, e:
             if DEBUG:
-                return self.get_technical_error_response(is404=True)
+                return self.get_technical_error_response(is404=True, exception=e)
             else:
                 callback, param_dict = resolver.resolve404()
                 return callback(request, **param_dict)
@@ -99,14 +99,35 @@ class BaseHandler:
         callback, param_dict = resolver.resolve500()
         return callback(request, **param_dict)
 
-    def get_technical_error_response(self, is404=False):
+    def get_technical_error_response(self, is404=False, exception=None):
         """
         Returns an HttpResponse that displays a TECHNICAL error message for a
         fundamental database or coding error.
         """
-        error_string = "There's been an error:\n\n%s" % self._get_traceback()
-        responseClass = is404 and httpwrappers.HttpResponseNotFound or httpwrappers.HttpResponseServerError
-        return responseClass(error_string, mimetype='text/plain')
+        if is404:
+            from django.conf.settings import ROOT_URLCONF
+            from django.utils.html import escape
+            html = ['<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">']
+            html.append('<html><head><title>Error 404</title>')
+            # Explicitly tell robots not to archive this, in case this slips
+            # onto a production site.
+            html.append('<meta name="robots" content="NONE,NOARCHIVE" />')
+            html.append('</head><body><h1>Error 404</h1>')
+            try:
+                tried = exception.args[0]['tried']
+            except (IndexError, TypeError):
+                if exception.args:
+                    html.append('<p>%s</p>' % escape(exception.args[0]))
+            else:
+                html.append('<p>Using the URLconf defined in <code>%s</code>, Django tried these URL patterns, in this order:</p>' % ROOT_URLCONF)
+                html.append('<ul>%s</ul>' % ''.join(['<li><code>%s</code></li>' % escape(t).replace(' ', '&nbsp;') for t in tried]))
+                html.append("<p>The current URL, <code><strong>%r</strong></code>, didn't match any of these.</p>" % exception.args[0]['path'])
+            html.append("<hr /><p>You're seeing this error because you have <code>DEBUG = True</code> in your Django settings file. Change that to <code>False</code>, and Django will display a standard 404 page.</p>")
+            html.append('</body></html>')
+            return httpwrappers.HttpResponseNotFound('\n'.join(html))
+        else:
+            output = "There's been an error:\n\n%s" % self._get_traceback()
+            return httpwrappers.HttpResponseServerError(output, mimetype='text/plain')
 
     def _get_traceback(self):
         "Helper function to return the traceback as a string"
diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py
index 50f9336171..178c2d139a 100644
--- a/django/core/urlresolvers.py
+++ b/django/core/urlresolvers.py
@@ -10,6 +10,9 @@ a string) and returns a tuple in this format:
 from django.core.exceptions import Http404, ViewDoesNotExist
 import re
 
+class Resolver404(Http404):
+    pass
+
 def get_mod_func(callback):
     # Converts 'django.views.news.stories.story_detail' to
     # ['django.views.news.stories', 'story_detail']
@@ -52,15 +55,20 @@ class RegexURLResolver:
         self.urlconf_name = urlconf_name
 
     def resolve(self, path):
+        tried = []
         match = self.regex.search(path)
         if match:
             new_path = path[match.end():]
             for pattern in self.url_patterns:
-                sub_match = pattern.resolve(new_path)
-                if sub_match:
-                    return sub_match
-            # None of the regexes matched, so raise a 404.
-            raise Http404, "Tried all URL patterns but didn't find a match for %r" % path
+                try:
+                    sub_match = pattern.resolve(new_path)
+                except Resolver404, e:
+                    tried.extend([(pattern.regex.pattern + '   ' + t) for t in e.args[0]['tried']])
+                else:
+                    if sub_match:
+                        return sub_match
+                    tried.append(pattern.regex.pattern)
+            raise Resolver404, {'tried': tried, 'path': new_path}
 
     def _get_urlconf_module(self):
         self.urlconf_module = __import__(self.urlconf_name, '', '', [''])