Index: conf/global_settings.py
===================================================================
--- conf/global_settings.py	(revision 5588)
+++ 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: views/debug.py
===================================================================
--- views/debug.py	(revision 5588)
+++ 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, sys
+import os, re, sys, 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
@@ -95,6 +107,8 @@
         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
@@ -124,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,
@@ -199,12 +215,116 @@
 
     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>
@@ -217,6 +337,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; }
@@ -232,6 +353,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; }
@@ -239,8 +362,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; }
@@ -256,21 +379,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++) {
@@ -279,6 +404,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'));
@@ -300,6 +426,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";
@@ -307,6 +441,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>
@@ -403,26 +581,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: middleware/common.py
===================================================================
--- middleware/common.py	(revision 5588)
+++ middleware/common.py	(working copy)
@@ -25,6 +25,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 'HTTP_USER_AGENT' in request.META:
             for user_agent_regex in settings.DISALLOWED_USER_AGENTS:
