From 9a82eb6ff109e95dbb6cf26ee3d2ce1c548714bb Mon Sep 17 00:00:00 2001
From: Russell Keith-Magee <russell@keith-magee.com>
Date: Tue, 1 Feb 2011 14:18:07 +0000
Subject: [PATCH] Fixed #14972 -- Ensure that the HTML email logger always
 produces useful output, regardless of whether it has been given an exception
 or a request. Thanks to jamstooks for the report, and bpeschier for the
 initial patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@15383 bcc190cf-cafb-0310-a4f2-bffc1f526a37
---
 django/utils/log.py                        |  2 +-
 django/views/debug.py                      | 45 ++++++-----
 tests/regressiontests/views/tests/debug.py | 89 +++++++++++++++++++++-
 3 files changed, 116 insertions(+), 20 deletions(-)

diff --git a/django/utils/log.py b/django/utils/log.py
index 1f6dc52849..784035b3cf 100644
--- a/django/utils/log.py
+++ b/django/utils/log.py
@@ -83,7 +83,7 @@ class AdminEmailHandler(logging.Handler):
             exc_info = record.exc_info
             stack_trace = '\n'.join(traceback.format_exception(*record.exc_info))
         else:
-            exc_info = ()
+            exc_info = (None, record.msg, None)
             stack_trace = 'No stack trace available'
 
         message = "%s\n\n%s" % (stack_trace, request_repr)
diff --git a/django/views/debug.py b/django/views/debug.py
index 6bb6a0bd93..569cec18df 100644
--- a/django/views/debug.py
+++ b/django/views/debug.py
@@ -59,7 +59,7 @@ def technical_500_response(request, exc_type, exc_value, tb):
     html = reporter.get_traceback_html()
     return HttpResponseServerError(html, mimetype='text/html')
 
-class ExceptionReporter:
+class ExceptionReporter(object):
     """
     A class to organize and coordinate reporting on exceptions.
     """
@@ -82,7 +82,7 @@ class ExceptionReporter:
     def get_traceback_html(self):
         "Return HTML code for traceback."
 
-        if issubclass(self.exc_type, TemplateDoesNotExist):
+        if self.exc_type and issubclass(self.exc_type, TemplateDoesNotExist):
             from django.template.loader import template_source_loaders
             self.template_does_not_exist = True
             self.loader_debug_info = []
@@ -113,11 +113,12 @@ class ExceptionReporter:
 
         frames = self.get_traceback_frames()
         for i, frame in enumerate(frames):
-            frame['vars'] = [(k, force_escape(pprint(v))) for k, v in frame['vars']]
+            if 'vars' in frame:
+                frame['vars'] = [(k, force_escape(pprint(v))) for k, v in frame['vars']]
             frames[i] = frame
 
         unicode_hint = ''
-        if issubclass(self.exc_type, UnicodeError):
+        if self.exc_type and issubclass(self.exc_type, UnicodeError):
             start = getattr(self.exc_value, 'start', None)
             end = getattr(self.exc_value, 'end', None)
             if start is not None and end is not None:
@@ -127,11 +128,8 @@ class ExceptionReporter:
         t = Template(TECHNICAL_500_TEMPLATE, name='Technical 500 template')
         c = Context({
             'is_email': self.is_email,
-            'exception_type': self.exc_type.__name__,
-            'exception_value': smart_unicode(self.exc_value, errors='replace'),
             'unicode_hint': unicode_hint,
             'frames': frames,
-            'lastframe': frames[-1],
             'request': self.request,
             'settings': get_safe_settings(),
             'sys_executable': sys.executable,
@@ -143,6 +141,13 @@ class ExceptionReporter:
             'template_does_not_exist': self.template_does_not_exist,
             'loader_debug_info': self.loader_debug_info,
         })
+        # Check whether exception info is available
+        if self.exc_type:
+            c['exception_type'] = self.exc_type.__name__
+        if self.exc_value:
+            c['exception_value'] = smart_unicode(self.exc_value, errors='replace')
+        if frames:
+            c['lastframe'] = frames[-1]
         return t.render(c)
 
     def get_template_exception_info(self):
@@ -250,14 +255,6 @@ class ExceptionReporter:
                 })
             tb = tb.tb_next
 
-        if not frames:
-            frames = [{
-                'filename': '&lt;unknown&gt;',
-                'function': '?',
-                'lineno': '?',
-                'context_line': '???',
-            }]
-
         return frames
 
     def format_exception(self):
@@ -319,7 +316,7 @@ TECHNICAL_500_TEMPLATE = """
 <head>
   <meta http-equiv="content-type" content="text/html; charset=utf-8">
   <meta name="robots" content="NONE,NOARCHIVE">
-  <title>{{ exception_type }} at {{ request.path_info|escape }}</title>
+  <title>{% if exception_type %}{{ exception_type }}{% else %}Report{% endif %}{% if request %} at {{ request.path_info|escape }}{% endif %}</title>
   <style type="text/css">
     html * { padding:0; margin:0; }
     body * { padding:10px 20px; }
@@ -429,9 +426,10 @@ TECHNICAL_500_TEMPLATE = """
 </head>
 <body>
 <div id="summary">
-  <h1>{{ exception_type }}{% if request %} at {{ request.path_info|escape }}{% endif %}</h1>
-  <pre class="exception_value">{{ exception_value|force_escape }}</pre>
+  <h1>{% if exception_type %}{{ exception_type }}{% else %}Report{% endif %}{% if request %} at {{ request.path_info|escape }}{% endif %}</h1>
+  <pre class="exception_value">{% if exception_value %}{{ exception_value|force_escape }}{% else %}No exception supplied{% endif %}</pre>
   <table class="meta">
+{% if request %}
     <tr>
       <th>Request Method:</th>
       <td>{{ request.META.REQUEST_METHOD }}</td>
@@ -440,22 +438,29 @@ TECHNICAL_500_TEMPLATE = """
       <th>Request URL:</th>
       <td>{{ request.build_absolute_uri|escape }}</td>
     </tr>
+{% endif %}
     <tr>
       <th>Django Version:</th>
       <td>{{ django_version_info }}</td>
     </tr>
+{% if exception_type %}
     <tr>
       <th>Exception Type:</th>
       <td>{{ exception_type }}</td>
     </tr>
+{% endif %}
+{% if exception_type and exception_value %}
     <tr>
       <th>Exception Value:</th>
       <td><pre>{{ exception_value|force_escape }}</pre></td>
     </tr>
+{% endif %}
+{% if lastframe %}
     <tr>
       <th>Exception Location:</th>
       <td>{{ lastframe.filename|escape }} in {{ lastframe.function|escape }}, line {{ lastframe.lineno }}</td>
     </tr>
+{% endif %}
     <tr>
       <th>Python Executable:</th>
       <td>{{ sys_executable|escape }}</td>
@@ -515,6 +520,7 @@ TECHNICAL_500_TEMPLATE = """
    </table>
 </div>
 {% endif %}
+{% if frames %}
 <div id="traceback">
   <h2>Traceback <span class="commands">{% if not is_email %}<a href="#" onclick="return switchPastebinFriendly(this);">Switch to copy-and-paste view</a></span>{% endif %}</h2>
   {% autoescape off %}
@@ -615,6 +621,7 @@ Exception Value: {{ exception_value|force_escape }}
 </form>
 </div>
 {% endif %}
+{% endif %}
 
 <div id="requestinfo">
   <h2>Request information</h2>
@@ -725,6 +732,8 @@ Exception Value: {{ exception_value|force_escape }}
       {% endfor %}
     </tbody>
   </table>
+{% else %}
+  <p>Request data not supplied</p>
 {% endif %}
 
   <h3 id="settings-info">Settings</h3>
diff --git a/tests/regressiontests/views/tests/debug.py b/tests/regressiontests/views/tests/debug.py
index c71e8c502d..0b07b406d0 100644
--- a/tests/regressiontests/views/tests/debug.py
+++ b/tests/regressiontests/views/tests/debug.py
@@ -1,13 +1,16 @@
 import inspect
+import sys
 
 from django.conf import settings
 from django.core.files.uploadedfile import SimpleUploadedFile
-from django.test import TestCase
+from django.test import TestCase, RequestFactory
 from django.core.urlresolvers import reverse
 from django.template import TemplateSyntaxError
+from django.views.debug import ExceptionReporter
 
 from regressiontests.views import BrokenException, except_args
 
+
 class DebugViewTests(TestCase):
     def setUp(self):
         self.old_debug = settings.DEBUG
@@ -52,3 +55,87 @@ class DebugViewTests(TestCase):
     def test_template_loader_postmortem(self):
         response = self.client.get(reverse('raises_template_does_not_exist'))
         self.assertContains(response, 'templates/i_dont_exist.html</code> (File does not exist)</li>', status_code=500)
+
+
+class ExceptionReporterTests(TestCase):
+    rf = RequestFactory()
+
+    def test_request_and_exception(self):
+        "A simple exception report can be generated"
+        try:
+            request = self.rf.get('/test_view/')
+            raise KeyError("Can't find my keys")
+        except KeyError:
+            exc_type, exc_value, tb = sys.exc_info()
+        reporter = ExceptionReporter(request, exc_type, exc_value, tb)
+        html = reporter.get_traceback_html()
+        self.assertIn('<h1>KeyError at /test_view/</h1>', html)
+        self.assertIn('<pre class="exception_value">Can&#39;t find my keys</pre>', html)
+        self.assertIn('<th>Request Method:</th>', html)
+        self.assertIn('<th>Request URL:</th>', html)
+        self.assertIn('<th>Exception Type:</th>', html)
+        self.assertIn('<th>Exception Value:</th>', html)
+        self.assertIn('<h2>Traceback ', html)
+        self.assertIn('<h2>Request information</h2>', html)
+        self.assertNotIn('<p>Request data not supplied</p>', html)
+
+    def test_no_request(self):
+        "An exception report can be generated without request"
+        try:
+            raise KeyError("Can't find my keys")
+        except KeyError:
+            exc_type, exc_value, tb = sys.exc_info()
+        reporter = ExceptionReporter(None, exc_type, exc_value, tb)
+        html = reporter.get_traceback_html()
+        self.assertIn('<h1>KeyError</h1>', html)
+        self.assertIn('<pre class="exception_value">Can&#39;t find my keys</pre>', html)
+        self.assertNotIn('<th>Request Method:</th>', html)
+        self.assertNotIn('<th>Request URL:</th>', html)
+        self.assertIn('<th>Exception Type:</th>', html)
+        self.assertIn('<th>Exception Value:</th>', html)
+        self.assertIn('<h2>Traceback ', html)
+        self.assertIn('<h2>Request information</h2>', html)
+        self.assertIn('<p>Request data not supplied</p>', html)
+
+    def test_no_exception(self):
+        "An exception report can be generated for just a request"
+        request = self.rf.get('/test_view/')
+        reporter = ExceptionReporter(request, None, None, None)
+        html = reporter.get_traceback_html()
+        self.assertIn('<h1>Report at /test_view/</h1>', html)
+        self.assertIn('<pre class="exception_value">No exception supplied</pre>', html)
+        self.assertIn('<th>Request Method:</th>', html)
+        self.assertIn('<th>Request URL:</th>', html)
+        self.assertNotIn('<th>Exception Type:</th>', html)
+        self.assertNotIn('<th>Exception Value:</th>', html)
+        self.assertNotIn('<h2>Traceback ', html)
+        self.assertIn('<h2>Request information</h2>', html)
+        self.assertNotIn('<p>Request data not supplied</p>', html)
+
+    def test_request_and_message(self):
+        "A message can be provided in addition to a request"
+        request = self.rf.get('/test_view/')
+        reporter = ExceptionReporter(request, None, "I'm a little teapot", None)
+        html = reporter.get_traceback_html()
+        self.assertIn('<h1>Report at /test_view/</h1>', html)
+        self.assertIn('<pre class="exception_value">I&#39;m a little teapot</pre>', html)
+        self.assertIn('<th>Request Method:</th>', html)
+        self.assertIn('<th>Request URL:</th>', html)
+        self.assertNotIn('<th>Exception Type:</th>', html)
+        self.assertNotIn('<th>Exception Value:</th>', html)
+        self.assertNotIn('<h2>Traceback ', html)
+        self.assertIn('<h2>Request information</h2>', html)
+        self.assertNotIn('<p>Request data not supplied</p>', html)
+
+    def test_message_only(self):
+        reporter = ExceptionReporter(None, None, "I'm a little teapot", None)
+        html = reporter.get_traceback_html()
+        self.assertIn('<h1>Report</h1>', html)
+        self.assertIn('<pre class="exception_value">I&#39;m a little teapot</pre>', html)
+        self.assertNotIn('<th>Request Method:</th>', html)
+        self.assertNotIn('<th>Request URL:</th>', html)
+        self.assertNotIn('<th>Exception Type:</th>', html)
+        self.assertNotIn('<th>Exception Value:</th>', html)
+        self.assertNotIn('<h2>Traceback ', html)
+        self.assertIn('<h2>Request information</h2>', html)
+        self.assertIn('<p>Request data not supplied</p>', html)