From 0710243ea12f9f88189197703d4c2e9e2e9fe741 Mon Sep 17 00:00:00 2001 From: Jacob Kaplan-Moss Date: Mon, 14 Nov 2005 17:44:50 +0000 Subject: [PATCH] Added "pretty" error pages to be used when DEBUG is True. git-svn-id: http://code.djangoproject.com/svn/django/trunk@1233 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/handlers/base.py | 34 +-- django/views/debug.py | 438 +++++++++++++++++++++++++++++++++++ 2 files changed, 446 insertions(+), 26 deletions(-) create mode 100644 django/views/debug.py diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index 190a8b02c2..ba2e286721 100644 --- a/django/core/handlers/base.py +++ b/django/core/handlers/base.py @@ -86,14 +86,14 @@ class BaseHandler: return response except exceptions.Http404, e: if DEBUG: - return self.get_technical_error_response(is404=True, exception=e) + return self.get_technical_error_response(request, is404=True, exception=e) else: callback, param_dict = resolver.resolve404() return callback(request, **param_dict) except db.DatabaseError: db.db.rollback() if DEBUG: - return self.get_technical_error_response() + return self.get_technical_error_response(request) else: subject = 'Database error (%s IP): %s' % ((request.META.get('REMOTE_ADDR') in INTERNAL_IPS and 'internal' or 'EXTERNAL'), getattr(request, 'path', '')) message = "%s\n\n%s" % (self._get_traceback(), request) @@ -103,7 +103,7 @@ class BaseHandler: return httpwrappers.HttpResponseForbidden('

Permission denied

') except: # Handle everything else, including SuspiciousOperation, etc. if DEBUG: - return self.get_technical_error_response() + return self.get_technical_error_response(request) else: subject = 'Coding error (%s IP): %s' % ((request.META.get('REMOTE_ADDR') in INTERNAL_IPS and 'internal' or 'EXTERNAL'), getattr(request, 'path', '')) try: @@ -123,35 +123,17 @@ class BaseHandler: callback, param_dict = resolver.resolve500() return callback(request, **param_dict) - def get_technical_error_response(self, is404=False, exception=None): + def get_technical_error_response(self, request, is404=False, exception=None): """ Returns an HttpResponse that displays a TECHNICAL error message for a fundamental database or coding error. """ + import sys + from django.views import debug if is404: - from django.conf.settings import ROOT_URLCONF - from django.utils.html import escape - html = [''] - html.append('Error 404') - # Explicitly tell robots not to archive this, in case this slips - # onto a production site. - html.append('') - html.append('

Error 404

') - try: - tried = exception.args[0]['tried'] - except (IndexError, TypeError): - if exception.args: - html.append('

%s

' % escape(exception.args[0])) - else: - html.append('

Using the URLconf defined in %s, Django tried these URL patterns, in this order:

' % ROOT_URLCONF) - html.append('' % ''.join(['
  • %s
  • ' % escape(t).replace(' ', ' ') for t in tried])) - html.append("

    The current URL, %r, didn't match any of these.

    " % exception.args[0]['path']) - html.append("

    You're seeing this error because you have DEBUG = True in your Django settings file. Change that to False, and Django will display a standard 404 page.

    ") - html.append('') - return httpwrappers.HttpResponseNotFound('\n'.join(html)) + return debug.technical_404_response(request, exception) else: - output = "There's been an error:\n\n%s" % self._get_traceback() - return httpwrappers.HttpResponseServerError(output, mimetype='text/plain') + return debug.technical_500_response(request, *sys.exc_info()) def _get_traceback(self): "Helper function to return the traceback as a string" diff --git a/django/views/debug.py b/django/views/debug.py new file mode 100644 index 0000000000..2d3b7e9150 --- /dev/null +++ b/django/views/debug.py @@ -0,0 +1,438 @@ +import os +import sys +import inspect +from django.conf import settings +from os.path import dirname, join as pathjoin +from django.core.template import Template, Context +from django.utils.httpwrappers import HttpResponseServerError, HttpResponseNotFound + +def technical_500_response(request, exc_type, exc_value, tb): + """ + Create a technical server error response. The last three arguments are + the values returned from sys.exc_info() and friends. + """ + frames = [] + while tb is not None: + filename = tb.tb_frame.f_code.co_filename + function = tb.tb_frame.f_code.co_name + lineno = tb.tb_lineno - 1 + pre_context_lineno, pre_context, context_line, post_context = _get_lines_from_file(filename, lineno, 7) + frames.append({ + 'tb' : tb, + 'filename' : filename, + 'function' : function, + 'lineno' : lineno, + 'vars' : tb.tb_frame.f_locals, + 'id' : id(tb), + 'pre_context' : pre_context, + 'context_line' : context_line, + 'post_context' : post_context, + 'pre_context_lineno' : pre_context_lineno, + }) + tb = tb.tb_next + + t = Template(TECHNICAL_500_TEMPLATE) + c = Context({ + 'exception_type' : exc_type.__name__, + 'exception_value' : exc_value, + 'frames' : frames, + 'lastframe' : frames[-1], + 'request' : request, + 'request_protocol' : os.environ.get("HTTPS") == "on" and "https" or "http", + 'settings' : dict([(k, getattr(settings, k)) for k in dir(settings) if k.isupper()]), + + }) + return HttpResponseServerError(t.render(c)) + +def technical_404_response(request, exception): + """ + Create a technical 404 error response. The exception should be the Http404 + exception. + """ + try: + tried = exception.args[0]['tried'] + except (IndexError, TypeError): + tried = [] + + t = Template(TECHNICAL_404_TEMPLATE) + c = Context({ + 'root_urlconf' : settings.ROOT_URLCONF, + 'urlpatterns' : tried, + 'reason' : str(exception), + 'request' : request, + 'request_protocol' : os.environ.get("HTTPS") == "on" and "https" or "http", + 'settings' : dict([(k, getattr(settings, k)) for k in dir(settings) if k.isupper()]), + }) + return HttpResponseNotFound(t.render(c)) + +def _get_lines_from_file(filename, lineno, context_lines): + """ + Returns context_lines before and after lineno from file. + Returns (pre_context_lineno, pre_context, context_line, post_context). + """ + try: + source = open(filename).readlines() + lower_bound = max(0, lineno - context_lines) + upper_bound = lineno + context_lines + + pre_context = [line.strip('\n') for line in source[lower_bound:lineno]] + context_line = source[lineno].strip('\n') + post_context = [line.strip('\n') for line in source[lineno+1:upper_bound]] + + return lower_bound, pre_context, context_line, post_context + except (OSError, IOError): + return None, [], None, [] + +# +# Templates are embedded in the file so that we know the error handler will +# always work even if the template loader is broken. +# + +TECHNICAL_500_TEMPLATE = """ + + + + + {{ exception_type }} at {{ request.path }} + + + + + +
    +

    {{ exception_type }} at {{ request.path }}

    +

    {{ exception_value }}

    + + + + + + + + + + + + + + + + + + + + + +
    Request Method:{{ request.META.REQUEST_METHOD }}
    Request URL:{{ request_protocol }}://{{ request.META.HTTP_HOST }}{{ request.path }}
    Exception Type:{{ exception_type }}
    Exception Value:{{ exception_value }}
    Exception Location:{{ lastframe.filename }} in {{ lastframe.function }}, line {{ lastframe.lineno }}
    +
    + +
    +

    Traceback (innermost last)

    + +
    + +
    +

    Request information

    + +

    GET

    + {% if request.GET %} + + + + + + + + + {% for var in request.GET.items %} + + + + + {% endfor %} + +
    VariableValue
    {{ var.0 }}
    {{ var.1|pprint|escape }}
    + {% else %} +

    No GET data

    + {% endif %} + +

    POST

    + {% if request.POST %} + + + + + + + + + {% for var in request.POST.items %} + + + + + {% endfor %} + +
    VariableValue
    {{ var.0 }}
    {{ var.1|pprint|escape }}
    + {% else %} +

    No POST data

    + {% endif %} + +

    + {% if request.COOKIES %} + + + + + + + + + {% for var in request.COOKIES.items %} + + + + + {% endfor %} + +
    VariableValue
    {{ var.0 }}
    {{ var.1|pprint|escape }}
    + {% else %} +

    No cookie data

    + {% endif %} + +

    META

    + + + + + + + + + {% for var in request.META.items|dictsort:"0" %} + + + + + {% endfor %} + +
    VariableValue
    {{ var.0 }}
    {{ var.1|pprint|escape }}
    + +

    Settings

    +

    Using settings module {{ settings.SETTINGS_MODULE }}

    + + + + + + + + + {% for var in settings.items|dictsort:"0" %} + + + + + {% endfor %} + +
    SettingValue
    {{ var.0 }}
    {{ var.1|pprint|escape }}
    + +
    + +
    +

    + You're seeing this error because you have DEBUG = True in your + Django settings file. Change that to False, and Django will + display a standard 500 page. +

    +
    + + + +""" + +TECHNICAL_404_TEMPLATE = """ + + + + + Page not found at {{ request.path }} + + + + +
    +

    Page not found (404)

    + + + + + + + + + +
    Request Method:{{ request.META.REQUEST_METHOD }}
    Request URL:{{ request_protocol }}://{{ request.META.HTTP_HOST }}{{ request.path }}
    +
    +
    + {% if urlpatterns %} +

    + Using the URLconf defined in {{ settings.ROOT_URLCONF }}, + Django tried these URL patterns, in this order: +

    +
      + {% for pattern in urlpatterns %} +
    1. {{ pattern|escape }}
    2. + {% endfor %} +
    +

    The current URL, {{ request.path }}, didn't match any of these.

    + {% else %} +

    {{ reason|escape }}

    + {% endif %} +
    + +
    +

    + You're seeing this error because you have DEBUG = True in + your Django settings file. Change that to False, and Django + will display a standard 404 page. +

    +
    + + +""" \ No newline at end of file