Ticket #3527: django_debugger_r5588.diff

File django_debugger_r5588.diff, 13.9 KB (added by John Shaffer <jshaffer2112@…>, 8 years ago)

Patch against [5588]

  • conf/global_settings.py

     
    1111
    1212DEBUG = False
    1313TEMPLATE_DEBUG = False
     14DEBUGGER_ENABLED = False
    1415
    1516# Whether to use the "Etag" header. This saves bandwidth but slows down performance.
    1617USE_ETAGS = False
  • views/debug.py

     
    22from django.template import Template, Context, TemplateDoesNotExist
    33from django.utils.html import escape
    44from django.http import HttpResponseServerError, HttpResponseNotFound
    5 import os, re, sys
     5import os, re, sys, code, threading
    66
    77HIDDEN_SETTINGS = re.compile('SECRET|PASSWORD|PROFANITIES_LIST')
    88
     9def new_debugger_id():
     10    import time
     11    import md5
     12    return md5.new(str(time.time())).hexdigest()
     13
    914def linebreak_iter(template_source):
    1015    yield 0
    1116    p = template_source.find('\n')
     
    6671    Create a technical server error response. The last three arguments are
    6772    the values returned from sys.exc_info() and friends.
    6873    """
     74    if settings.DEBUGGER_ENABLED:
     75        debugger_id = new_debugger_id()
     76        debugger_enabled = True
     77        frame_storage = console.debug_sessions[debugger_id] = {}
     78    else:
     79        debugger_id = None
     80        debugger_enabled = False
    6981    template_info = None
    7082    template_does_not_exist = False
    7183    loader_debug_info = None
     
    95107        if tb.tb_frame.f_locals.get('__traceback_hide__'):
    96108            tb = tb.tb_next
    97109            continue
     110        if debugger_enabled:
     111            frame_storage[str(id(tb))] = tb.tb_frame
    98112        filename = tb.tb_frame.f_code.co_filename
    99113        function = tb.tb_frame.f_code.co_name
    100114        lineno = tb.tb_lineno - 1
     
    124138        }]
    125139    t = Template(TECHNICAL_500_TEMPLATE, name='Technical 500 template')
    126140    c = Context({
     141        'debugger_id': debugger_id,
     142        'debugger_enabled': debugger_enabled,
    127143        'exception_type': exc_type.__name__,
    128144        'exception_value': exc_value,
    129145        'frames': frames,
     
    199215
    200216    return lower_bound, pre_context, context_line, post_context
    201217
     218class DebugCapture(object):
     219    """
     220    Class that wraps sys.stdout in order to get the output
     221    for the debugger. threadsafe but quite slow.
     222    """
     223    _orig = None
     224
     225    def __init__(self):
     226        self._buffer = {}
     227
     228    def install(cls):
     229        if cls._orig:
     230            return
     231        cls._orig = sys.stdout
     232        sys.stdout = cls()
     233    install = classmethod(install)
     234
     235    def push(self):
     236        from cStringIO import StringIO
     237        tid = threading.currentThread()
     238        self._buffer[tid] = StringIO()
     239
     240    def release(self):
     241        tid = threading.currentThread()
     242        if tid in self._buffer:
     243            result = self._buffer[tid].getvalue()
     244            del self._buffer[tid]
     245        else:
     246            result = ''
     247        return result
     248
     249    def write(self, d):
     250        tid = threading.currentThread()
     251        if tid in self._buffer:
     252            self._buffer[tid].write(d)
     253        else:
     254            self._orig.write(d)
     255
     256
     257class PlainDebugger(code.InteractiveInterpreter):
     258    """
     259    Subclass of the python interactive interpreter that
     260    automatically captures stdout and buffers older input.
     261    """
     262
     263    def __init__(self, locals=None, globals=None):
     264        self.globals = globals
     265        code.InteractiveInterpreter.__init__(self, locals)
     266        self.prompt = '>>> '
     267        self.buffer = []
     268
     269    def runsource(self, source):
     270        # installs the debug capture on first access
     271        DebugCapture.install()
     272        prompt = self.prompt
     273        sys.stdout.push()
     274        try:
     275            source_to_eval = ''.join(self.buffer + [source])
     276            if code.InteractiveInterpreter.runsource(self,
     277               source_to_eval, '<debugger>', 'single'):
     278                self.prompt = '... '
     279                self.buffer.append(source)
     280            else:
     281                self.prompt = '>>> '
     282                del self.buffer[:]
     283        finally:
     284            return prompt + source + sys.stdout.release()
     285
     286    def runcode(self, code):
     287        try:
     288            exec code in self.globals, self.locals
     289        except:
     290            self.showtraceback()
     291
     292    def write(self, data):
     293        sys.stdout.write(data)
     294
     295
     296class AjaxDebugger(object):
     297    """
     298    The AJAX Debugger
     299    """
     300
     301    def __init__(self):
     302        self.debug_sessions = {}
     303        self.consoles = {}
     304
     305    def send(self, debugger, frame, cmd):
     306        if debugger not in self.debug_sessions:
     307            return '!!! expired debugger !!!'
     308        session = self.debug_sessions[debugger]
     309        if frame not in session:
     310            return '!!! unknown frame !!!'
     311        key = '%s|%s' % (debugger, frame)
     312        if key not in self.consoles:
     313            self.consoles[key] = PlainDebugger(
     314                session[frame].f_globals,
     315                session[frame].f_locals
     316            )
     317        return self.consoles[key].runsource(cmd)
     318
     319
     320console = AjaxDebugger()
     321
    202322#
    203323# Templates are embedded in the file so that we know the error handler will
    204324# always work even if the template loader is broken.
    205325#
    206326
    207 TECHNICAL_500_TEMPLATE = """
     327TECHNICAL_500_TEMPLATE = r"""
    208328<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    209329<html lang="en">
    210330<head>
     
    217337    body * * { padding:0; }
    218338    body { font:small sans-serif; }
    219339    body>div { border-bottom:1px solid #ddd; }
     340    a { color: #333; }
    220341    h1 { font-weight:normal; }
    221342    h2 { margin-bottom:.8em; }
    222343    h2 span { font-size:80%; color:#666; font-weight:normal; }
     
    232353    table td.code div { overflow:hidden; }
    233354    table.source th { color:#666; }
    234355    table.source td { font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
     356    form.debug { display: block; padding: 10px 20px 10px 40px; }
     357    form.debug input { margin-top: 5px; width: 100%; }
    235358    ul.traceback { list-style-type:none; }
    236359    ul.traceback li.frame { margin-bottom:1em; }
    237360    div.context { margin: 10px 0; }
     
    239362    div.context ol li { font-family:monospace; white-space:pre; color:#666; cursor:pointer; }
    240363    div.context ol.context-line li { color:black; background-color:#ccc; }
    241364    div.context ol.context-line li span { float: right; }
    242     div.commands { margin-left: 40px; }
    243     div.commands a { color:black; text-decoration:none; }
     365    ul.commands { margin-left: 40px; padding: 0; list-style: none; }
     366    ul.commands a { color:black; text-decoration:none; }
    244367    #summary { background: #ffc; }
    245368    #summary h2 { font-weight: normal; color: #666; }
    246369    #explanation { background:#eee; }
     
    256379  </style>
    257380  <script type="text/javascript">
    258381  //<!--
    259     function getElementsByClassName(oElm, strTagName, strClassName){
    260         // Written by Jonathan Snook, http://www.snook.ca/jon; Add-ons by Robert Nyman, http://www.robertnyman.com
    261         var arrElements = (strTagName == "*" && document.all)? document.all :
     382    var DEBUG_ID = {% if debugger_enabled %}'{{ debugger_id }}'{% else %}null{% endif %};
     383
     384    function getElementsByClassName(oElm, strTagName, strClassName) {
     385      // Written by Jonathan Snook, http://www.snook.ca/jon; Add-ons by Robert Nyman, http://www.robertnyman.com
     386      var arrElements = (strTagName == "*" && document.all) ? document.all :
    262387        oElm.getElementsByTagName(strTagName);
    263         var arrReturnElements = new Array();
    264         strClassName = strClassName.replace(/\-/g, "\\-");
    265         var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$)");
    266         var oElement;
    267         for(var i=0; i<arrElements.length; i++){
    268             oElement = arrElements[i];
    269             if(oRegExp.test(oElement.className)){
    270                 arrReturnElements.push(oElement);
    271             }
     388      var arrReturnElements = new Array();
     389      strClassName = strClassName.replace(/\-/g, "\\-");
     390      var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$)");
     391      var oElement;
     392      for(var i=0; i<arrElements.length; i++) {
     393        oElement = arrElements[i];
     394        if(oRegExp.test(oElement.className)) {
     395          arrReturnElements.push(oElement);
    272396        }
    273         return (arrReturnElements)
     397      }
     398      return arrReturnElements;
    274399    }
    275400    function hideAll(elems) {
    276401      for (var e = 0; e < elems.length; e++) {
     
    279404    }
    280405    window.onload = function() {
    281406      hideAll(getElementsByClassName(document, 'table', 'vars'));
     407      hideAll(getElementsByClassName(document, 'form', 'debug'));
    282408      hideAll(getElementsByClassName(document, 'ol', 'pre-context'));
    283409      hideAll(getElementsByClassName(document, 'ol', 'post-context'));
    284410      hideAll(getElementsByClassName(document, 'div', 'pastebin'));
     
    300426      s.innerHTML = s.innerHTML == uarr ? darr : uarr;
    301427      return false;
    302428    }
     429    function debugToggle(link, id) {
     430      toggle('d' + id);
     431      var s = link.getElementsByTagName('span')[0];
     432      var uarr = String.fromCharCode(0x25b6);
     433      var darr = String.fromCharCode(0x25bc);
     434      s.innerHTML = s.innerHTML == uarr ? darr : uarr;
     435      return false;
     436    }
    303437    function switchPastebinFriendly(link) {
    304438      s1 = "Switch to copy-and-paste view";
    305439      s2 = "Switch back to interactive view";
     
    307441      toggle('browserTraceback', 'pastebinTraceback');
    308442      return false;
    309443    }
     444    function sendDebugCommand(frameID, command, callback) {
     445      var activex = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'];
     446      var con = null;
     447      try {
     448        con = new XMLHttpRequest();
     449      }
     450      catch (e) {
     451        for (var i=0; i < activex.length; i++) {
     452          try {
     453            con = new ActiveXObject(activex[i]);
     454          }
     455          catch (e) {};
     456          if (con) {
     457            break;
     458          }
     459        }
     460      }
     461      var data = 'tb=' + DEBUG_ID + '&' +
     462                 'frame=' + frameID + '&' +
     463                 'cmd=' + encodeURIComponent(command);
     464      con.onreadystatechange = function() {
     465        if (con.readyState == 4) {
     466          callback(con.responseText);
     467        }
     468      };
     469      con.open('POST', '?__send_to_django_debugger__=yes');
     470      con.send(data);
     471    }
     472    function submitCommand(frameID) {
     473      var input = document.getElementById('di' + frameID);
     474      var output = document.getElementById('do' + frameID);
     475      output = (output.firstChild || output.appendChild(document.createTextNode('')));
     476      if (input.value == 'clear') {
     477        output.nodeValue = '';
     478      }
     479      else {
     480        sendDebugCommand(frameID, input.value + '\n', function(value) {
     481          output.nodeValue += value;
     482        });
     483      }
     484      input.value = '';
     485      input.focus();
     486      return false;
     487    }
    310488    //-->
    311489  </script>
    312490</head>
     
    403581            </div>
    404582          {% endif %}
    405583
    406           {% if frame.vars %}
    407             <div class="commands">
    408                 <a href="#" onclick="return varToggle(this, '{{ frame.id }}')"><span>&#x25b6;</span> Local vars</a>
    409             </div>
    410             <table class="vars" id="v{{ frame.id }}">
    411               <thead>
    412                 <tr>
    413                   <th>Variable</th>
    414                   <th>Value</th>
    415                 </tr>
    416               </thead>
    417               <tbody>
    418                 {% for var in frame.vars|dictsort:"0" %}
     584          {% if frame.vars or debugger_enabled %}
     585            <ul class="commands">
     586              {% if frame.vars %}
     587                <li><a href="#" onclick="return varToggle(this, '{{ frame.id }}')"><span>&#x25b6;</span> Local vars</a></li>
     588              {% endif %}
     589              {% if debugger_enabled %}
     590                <li><a href="#" onclick="return debugToggle(this, '{{ frame.id }}')"><span>&#x25b6;</span> Debug</a></li>
     591              {% endif %}
     592            </ul>
     593            {% if frame.vars %}
     594              <table class="vars" id="v{{ frame.id }}">
     595                <thead>
    419596                  <tr>
    420                     <td>{{ var.0 }}</td>
    421                     <td class="code"><div>{{ var.1|pprint|escape }}</div></td>
     597                    <th>Variable</th>
     598                    <th>Value</th>
    422599                  </tr>
    423                 {% endfor %}
    424               </tbody>
    425             </table>
     600                </thead>
     601                <tbody>
     602                  {% for var in frame.vars|dictsort:"0" %}
     603                    <tr>
     604                      <td>{{ var.0 }}</td>
     605                      <td class="code"><div>{{ var.1|pprint|escape }}</div></td>
     606                    </tr>
     607                  {% endfor %}
     608                </tbody>
     609              </table>
     610            {% endif %}
     611            {% if debugger_enabled %}
     612              <form class="debug" id="d{{ frame.id }}" onsubmit="return submitCommand('{{ frame.id }}')">
     613                <pre id="do{{ frame.id }}"></pre>
     614                <input type="text" id="di{{ frame.id }}">
     615              </form>
     616            {% endif %}
    426617          {% endif %}
    427618        </li>
    428619      {% endfor %}
  • middleware/common.py

     
    2525        settings.APPEND_SLASH and settings.PREPEND_WWW
    2626        """
    2727
     28        # Handle Debugger AJAX Requests
     29        if settings.DEBUGGER_ENABLED and \
     30           request.GET.get('__send_to_django_debugger__') == 'yes':
     31            from django.views.debug import console
     32            rv = console.send(
     33                debugger=request.POST.get('tb'),
     34                frame=request.POST.get('frame'),
     35                cmd=request.POST.get('cmd', '')
     36            )
     37            return http.HttpResponse(rv, mimetype='text/plain')
     38
    2839        # Check for denied User-Agents
    2940        if 'HTTP_USER_AGENT' in request.META:
    3041            for user_agent_regex in settings.DISALLOWED_USER_AGENTS:
Back to Top