Index: django/conf/global_settings.py
===================================================================
--- django/conf/global_settings.py	(revision 4988)
+++ django/conf/global_settings.py	(working copy)
@@ -11,6 +11,7 @@
 
 DEBUG = False
 TEMPLATE_DEBUG = False
+DEBUGGER_ENABLED = False
 
 # Whether to use the "Etag" header. This saves bandwidth but slows down performance.
 USE_ETAGS = False
Index: django/views/debug.py
===================================================================
--- django/views/debug.py	(revision 4988)
+++ django/views/debug.py	(working copy)
@@ -2,10 +2,15 @@
 from django.template import Template, Context, TemplateDoesNotExist
 from django.utils.html import escape
 from django.http import HttpResponseServerError, HttpResponseNotFound
-import os, re
+import sys, os, re, code, threading
 
 HIDDEN_SETTINGS = re.compile('SECRET|PASSWORD|PROFANITIES_LIST')
 
+def new_debugger_id():
+    import time
+    import md5
+    return md5.new(str(time.time())).hexdigest()
+
 def linebreak_iter(template_source):
     yield 0
     p = template_source.find('\n')
@@ -66,6 +71,13 @@
     Create a technical server error response. The last three arguments are
     the values returned from sys.exc_info() and friends.
     """
+    if settings.DEBUGGER_ENABLED:
+        debugger_id = new_debugger_id()
+        debugger_enabled = True
+        frame_storage = console.debug_sessions[debugger_id] = {}
+    else:
+        debugger_id = None
+        debugger_enabled = False
     template_info = None
     template_does_not_exist = False
     loader_debug_info = None
@@ -90,11 +102,20 @@
         exc_type, exc_value, tb, template_info = get_template_exception_info(exc_type, exc_value, tb)
     frames = []
     while tb is not None:
+        # support for __traceback_hide__ which is used by a few libraries
+        # to hide internal frames.
+        if tb.tb_frame.f_locals.get('__traceback_hide__'):
+            tb = tb.tb_next
+            continue
+        if debugger_enabled:
+            frame_storage[str(id(tb))] = tb.tb_frame
         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)
-        if pre_context_lineno:
+        loader = tb.tb_frame.f_globals.get('__loader__')
+        module_name = tb.tb_frame.f_globals.get('__name__')
+        pre_context_lineno, pre_context, context_line, post_context = _get_lines_from_file(filename, lineno, 7, loader, module_name)
+        if pre_context_lineno is not None:
             frames.append({
                 'tb': tb,
                 'filename': filename,
@@ -117,6 +138,8 @@
         }]
     t = Template(TECHNICAL_500_TEMPLATE, name='Technical 500 template')
     c = Context({
+        'debugger_id': debugger_id,
+        'debugger_enabled': debugger_enabled,
         'exception_type': exc_type.__name__,
         'exception_value': exc_value,
         'frames': frames,
@@ -161,30 +184,146 @@
     })
     return HttpResponseNotFound(t.render(c), mimetype='text/html')
 
-def _get_lines_from_file(filename, lineno, context_lines):
+def _get_lines_from_file(filename, lineno, context_lines, loader=None, module_name=None):
     """
     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
+    source = None
+    if loader is not None:
+        source = loader.get_source(module_name).splitlines()
+    else:
+        try:
+            f = open(filename)
+            try:
+                source = f.readlines()
+            finally:
+                f.close()
+        except (OSError, IOError):
+            pass
+    if source is None:
+        return None, [], None, []
 
-        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]]
+    lower_bound = max(0, lineno - context_lines)
+    upper_bound = lineno + context_lines
 
-        return lower_bound, pre_context, context_line, post_context
-    except (OSError, IOError):
-        return None, [], None, []
+    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
+
+
+class DebugCapture(object):
+    """
+    Class that wraps sys.stdout in order to get the output
+    for the debugger. threadsafe but quite slow.
+    """
+    _orig = None
+
+    def __init__(self):
+        self._buffer = {}
+
+    def install(cls):
+        if cls._orig:
+            return
+        cls._orig = sys.stdout
+        sys.stdout = cls()
+    install = classmethod(install)
+
+    def push(self):
+        from cStringIO import StringIO
+        tid = threading.currentThread()
+        self._buffer[tid] = StringIO()
+
+    def release(self):
+        tid = threading.currentThread()
+        if tid in self._buffer:
+            result = self._buffer[tid].getvalue()
+            del self._buffer[tid]
+        else:
+            result = ''
+        return result
+
+    def write(self, d):
+        tid = threading.currentThread()
+        if tid in self._buffer:
+            self._buffer[tid].write(d)
+        else:
+            self._orig.write(d)
+
+
+class PlainDebugger(code.InteractiveInterpreter):
+    """
+    Subclass of the python interactive interpreter that
+    automatically captures stdout and buffers older input.
+    """
+
+    def __init__(self, locals=None, globals=None):
+        self.globals = globals
+        code.InteractiveInterpreter.__init__(self, locals)
+        self.prompt = '>>> '
+        self.buffer = []
+
+    def runsource(self, source):
+        # installs the debug capture on first access
+        DebugCapture.install()
+        prompt = self.prompt
+        sys.stdout.push()
+        try:
+            source_to_eval = ''.join(self.buffer + [source])
+            if code.InteractiveInterpreter.runsource(self,
+               source_to_eval, '<debugger>', 'single'):
+                self.prompt = '... '
+                self.buffer.append(source)
+            else:
+                self.prompt = '>>> '
+                del self.buffer[:]
+        finally:
+            return prompt + source + sys.stdout.release()
+
+    def runcode(self, code):
+        try:
+            exec code in self.globals, self.locals
+        except:
+            self.showtraceback()
+
+    def write(self, data):
+        sys.stdout.write(data)
+
+
+class AjaxDebugger(object):
+    """
+    The AJAX Debugger
+    """
+
+    def __init__(self):
+        self.debug_sessions = {}
+        self.consoles = {}
+
+    def send(self, debugger, frame, cmd):
+        if debugger not in self.debug_sessions:
+            return '!!! expired debugger !!!'
+        session = self.debug_sessions[debugger]
+        if frame not in session:
+            return '!!! unknown frame !!!'
+        key = '%s|%s' % (debugger, frame)
+        if key not in self.consoles:
+            self.consoles[key] = PlainDebugger(
+                session[frame].f_globals,
+                session[frame].f_locals
+            )
+        return self.consoles[key].runsource(cmd)
+
+
+console = AjaxDebugger()
+
 #
 # 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 = """
+TECHNICAL_500_TEMPLATE = r"""
 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
 <html lang="en">
 <head>
@@ -197,6 +336,7 @@
     body * * { padding:0; }
     body { font:small sans-serif; }
     body>div { border-bottom:1px solid #ddd; }
+    a { color: #333; }
     h1 { font-weight:normal; }
     h2 { margin-bottom:.8em; }
     h2 span { font-size:80%; color:#666; font-weight:normal; }
@@ -212,6 +352,8 @@
     table td.code div { overflow:hidden; }
     table.source th { color:#666; }
     table.source td { font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
+    form.debug { display: block; padding: 10px 20px 10px 40px; }
+    form.debug input { margin-top: 5px; width: 100%; }
     ul.traceback { list-style-type:none; }
     ul.traceback li.frame { margin-bottom:1em; }
     div.context { margin: 10px 0; }
@@ -219,8 +361,8 @@
     div.context ol li { font-family:monospace; white-space:pre; color:#666; cursor:pointer; }
     div.context ol.context-line li { color:black; background-color:#ccc; }
     div.context ol.context-line li span { float: right; }
-    div.commands { margin-left: 40px; }
-    div.commands a { color:black; text-decoration:none; }
+    ul.commands { margin-left: 40px; padding: 0; list-style: none; }
+    ul.commands a { color:black; text-decoration:none; }
     #summary { background: #ffc; }
     #summary h2 { font-weight: normal; color: #666; }
     #explanation { background:#eee; }
@@ -236,21 +378,23 @@
   </style>
   <script type="text/javascript">
   //<!--
-    function getElementsByClassName(oElm, strTagName, strClassName){
-        // Written by Jonathan Snook, http://www.snook.ca/jon; Add-ons by Robert Nyman, http://www.robertnyman.com
-        var arrElements = (strTagName == "*" && document.all)? document.all :
+    var DEBUG_ID = {% if debugger_enabled %}'{{ debugger_id }}'{% else %}null{% endif %};
+
+    function getElementsByClassName(oElm, strTagName, strClassName) {
+      // Written by Jonathan Snook, http://www.snook.ca/jon; Add-ons by Robert Nyman, http://www.robertnyman.com
+      var arrElements = (strTagName == "*" && document.all) ? document.all :
         oElm.getElementsByTagName(strTagName);
-        var arrReturnElements = new Array();
-        strClassName = strClassName.replace(/\-/g, "\\-");
-        var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$)");
-        var oElement;
-        for(var i=0; i<arrElements.length; i++){
-            oElement = arrElements[i];
-            if(oRegExp.test(oElement.className)){
-                arrReturnElements.push(oElement);
-            }
+      var arrReturnElements = new Array();
+      strClassName = strClassName.replace(/\-/g, "\\-");
+      var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$)");
+      var oElement;
+      for(var i=0; i<arrElements.length; i++) {
+        oElement = arrElements[i];
+        if(oRegExp.test(oElement.className)) {
+          arrReturnElements.push(oElement);
         }
-        return (arrReturnElements)
+      }
+      return arrReturnElements;
     }
     function hideAll(elems) {
       for (var e = 0; e < elems.length; e++) {
@@ -259,6 +403,7 @@
     }
     window.onload = function() {
       hideAll(getElementsByClassName(document, 'table', 'vars'));
+      hideAll(getElementsByClassName(document, 'form', 'debug'));
       hideAll(getElementsByClassName(document, 'ol', 'pre-context'));
       hideAll(getElementsByClassName(document, 'ol', 'post-context'));
       hideAll(getElementsByClassName(document, 'div', 'pastebin'));
@@ -280,6 +425,14 @@
       s.innerHTML = s.innerHTML == uarr ? darr : uarr;
       return false;
     }
+    function debugToggle(link, id) {
+      toggle('d' + id);
+      var s = link.getElementsByTagName('span')[0];
+      var uarr = String.fromCharCode(0x25b6);
+      var darr = String.fromCharCode(0x25bc);
+      s.innerHTML = s.innerHTML == uarr ? darr : uarr;
+      return false;
+    }
     function switchPastebinFriendly(link) {
       s1 = "Switch to copy-and-paste view";
       s2 = "Switch back to interactive view";
@@ -287,6 +440,50 @@
       toggle('browserTraceback', 'pastebinTraceback');
       return false;
     }
+    function sendDebugCommand(frameID, command, callback) {
+      var activex = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'];
+      var con = null;
+      try {
+        con = new XMLHttpRequest();
+      }
+      catch (e) {
+        for (var i=0; i < activex.length; i++) {
+          try {
+            con = new ActiveXObject(activex[i]);
+          }
+          catch (e) {};
+          if (con) {
+            break;
+          }
+        }
+      }
+      var data = 'tb=' + DEBUG_ID + '&' +
+                 'frame=' + frameID + '&' +
+                 'cmd=' + encodeURIComponent(command);
+      con.onreadystatechange = function() {
+        if (con.readyState == 4) {
+          callback(con.responseText);
+        }
+      };
+      con.open('POST', '?__send_to_django_debugger__=yes');
+      con.send(data);
+    }
+    function submitCommand(frameID) {
+      var input = document.getElementById('di' + frameID);
+      var output = document.getElementById('do' + frameID);
+      output = (output.firstChild || output.appendChild(document.createTextNode('')));
+      if (input.value == 'clear') {
+        output.nodeValue = '';
+      }
+      else {
+        sendDebugCommand(frameID, input.value + '\n', function(value) {
+          output.nodeValue += value;
+        });
+      }
+      input.value = '';
+      input.focus();
+      return false;
+    }
     //-->
   </script>
 </head>
@@ -314,7 +511,7 @@
     </tr>
     <tr>
       <th>Exception Location:</th>
-      <td>{{ lastframe.filename }} in {{ lastframe.function }}, line {{ lastframe.lineno }}</td>
+      <td>{{ lastframe.filename|escape }} in {{ lastframe.function|escape }}, line {{ lastframe.lineno }}</td>
     </tr>
   </table>
 </div>
@@ -361,7 +558,7 @@
     <ul class="traceback">
       {% for frame in frames %}
         <li class="frame">
-          <code>{{ frame.filename }}</code> in <code>{{ frame.function }}</code>
+          <code>{{ frame.filename|escape }}</code> in <code>{{ frame.function|escape }}</code>
 
           {% if frame.context_line %}
             <div class="context" id="c{{ frame.id }}">
@@ -375,26 +572,39 @@
             </div>
           {% endif %}
 
-          {% if frame.vars %}
-            <div class="commands">
-                <a href="#" onclick="return varToggle(this, '{{ frame.id }}')"><span>&#x25b6;</span> Local vars</a>
-            </div>
-            <table class="vars" id="v{{ frame.id }}">
-              <thead>
-                <tr>
-                  <th>Variable</th>
-                  <th>Value</th>
-                </tr>
-              </thead>
-              <tbody>
-                {% for var in frame.vars|dictsort:"0" %}
+          {% if frame.vars or debugger_enabled %}
+            <ul class="commands">
+              {% if frame.vars %}
+                <li><a href="#" onclick="return varToggle(this, '{{ frame.id }}')"><span>&#x25b6;</span> Local vars</a></li>
+              {% endif %}
+              {% if debugger_enabled %}
+                <li><a href="#" onclick="return debugToggle(this, '{{ frame.id }}')"><span>&#x25b6;</span> Debug</a></li>
+              {% endif %}
+            </ul>
+            {% if frame.vars %}
+              <table class="vars" id="v{{ frame.id }}">
+                <thead>
                   <tr>
-                    <td>{{ var.0 }}</td>
-                    <td class="code"><div>{{ var.1|pprint|escape }}</div></td>
+                    <th>Variable</th>
+                    <th>Value</th>
                   </tr>
-                {% endfor %}
-              </tbody>
-            </table>
+                </thead>
+                <tbody>
+                  {% for var in frame.vars|dictsort:"0" %}
+                    <tr>
+                      <td>{{ var.0 }}</td>
+                      <td class="code"><div>{{ var.1|pprint|escape }}</div></td>
+                    </tr>
+                  {% endfor %}
+                </tbody>
+              </table>
+            {% endif %}
+            {% if debugger_enabled %}
+              <form class="debug" id="d{{ frame.id }}" onsubmit="return submitCommand('{{ frame.id }}')">
+                <pre id="do{{ frame.id }}"></pre>
+                <input type="text" id="di{{ frame.id }}">
+              </form>
+            {% endif %}
           {% endif %}
         </li>
       {% endfor %}
Index: django/middleware/common.py
===================================================================
--- django/middleware/common.py	(revision 4988)
+++ django/middleware/common.py	(working copy)
@@ -24,6 +24,17 @@
         settings.APPEND_SLASH and settings.PREPEND_WWW
         """
 
+        # Handle Debugger AJAX Requests
+        if settings.DEBUGGER_ENABLED and \
+           request.GET.get('__send_to_django_debugger__') == 'yes':
+            from django.views.debug import console
+            rv = console.send(
+                debugger=request.POST.get('tb'),
+                frame=request.POST.get('frame'),
+                cmd=request.POST.get('cmd', '')
+            )
+            return http.HttpResponse(rv, mimetype='text/plain')
+
         # Check for denied User-Agents
         if request.META.has_key('HTTP_USER_AGENT'):
             for user_agent_regex in settings.DISALLOWED_USER_AGENTS:
