Code

Ticket #3527: django_debugger_r5588.diff

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

Patch against [5588]

Line 
1Index: conf/global_settings.py
2===================================================================
3--- conf/global_settings.py     (revision 5588)
4+++ conf/global_settings.py     (working copy)
5@@ -11,6 +11,7 @@
6 
7 DEBUG = False
8 TEMPLATE_DEBUG = False
9+DEBUGGER_ENABLED = False
10 
11 # Whether to use the "Etag" header. This saves bandwidth but slows down performance.
12 USE_ETAGS = False
13Index: views/debug.py
14===================================================================
15--- views/debug.py      (revision 5588)
16+++ views/debug.py      (working copy)
17@@ -2,10 +2,15 @@
18 from django.template import Template, Context, TemplateDoesNotExist
19 from django.utils.html import escape
20 from django.http import HttpResponseServerError, HttpResponseNotFound
21-import os, re, sys
22+import os, re, sys, code, threading
23 
24 HIDDEN_SETTINGS = re.compile('SECRET|PASSWORD|PROFANITIES_LIST')
25 
26+def new_debugger_id():
27+    import time
28+    import md5
29+    return md5.new(str(time.time())).hexdigest()
30+
31 def linebreak_iter(template_source):
32     yield 0
33     p = template_source.find('\n')
34@@ -66,6 +71,13 @@
35     Create a technical server error response. The last three arguments are
36     the values returned from sys.exc_info() and friends.
37     """
38+    if settings.DEBUGGER_ENABLED:
39+        debugger_id = new_debugger_id()
40+        debugger_enabled = True
41+        frame_storage = console.debug_sessions[debugger_id] = {}
42+    else:
43+        debugger_id = None
44+        debugger_enabled = False
45     template_info = None
46     template_does_not_exist = False
47     loader_debug_info = None
48@@ -95,6 +107,8 @@
49         if tb.tb_frame.f_locals.get('__traceback_hide__'):
50             tb = tb.tb_next
51             continue
52+        if debugger_enabled:
53+            frame_storage[str(id(tb))] = tb.tb_frame
54         filename = tb.tb_frame.f_code.co_filename
55         function = tb.tb_frame.f_code.co_name
56         lineno = tb.tb_lineno - 1
57@@ -124,6 +138,8 @@
58         }]
59     t = Template(TECHNICAL_500_TEMPLATE, name='Technical 500 template')
60     c = Context({
61+        'debugger_id': debugger_id,
62+        'debugger_enabled': debugger_enabled,
63         'exception_type': exc_type.__name__,
64         'exception_value': exc_value,
65         'frames': frames,
66@@ -199,12 +215,116 @@
67 
68     return lower_bound, pre_context, context_line, post_context
69 
70+class DebugCapture(object):
71+    """
72+    Class that wraps sys.stdout in order to get the output
73+    for the debugger. threadsafe but quite slow.
74+    """
75+    _orig = None
76+
77+    def __init__(self):
78+        self._buffer = {}
79+
80+    def install(cls):
81+        if cls._orig:
82+            return
83+        cls._orig = sys.stdout
84+        sys.stdout = cls()
85+    install = classmethod(install)
86+
87+    def push(self):
88+        from cStringIO import StringIO
89+        tid = threading.currentThread()
90+        self._buffer[tid] = StringIO()
91+
92+    def release(self):
93+        tid = threading.currentThread()
94+        if tid in self._buffer:
95+            result = self._buffer[tid].getvalue()
96+            del self._buffer[tid]
97+        else:
98+            result = ''
99+        return result
100+
101+    def write(self, d):
102+        tid = threading.currentThread()
103+        if tid in self._buffer:
104+            self._buffer[tid].write(d)
105+        else:
106+            self._orig.write(d)
107+
108+
109+class PlainDebugger(code.InteractiveInterpreter):
110+    """
111+    Subclass of the python interactive interpreter that
112+    automatically captures stdout and buffers older input.
113+    """
114+
115+    def __init__(self, locals=None, globals=None):
116+        self.globals = globals
117+        code.InteractiveInterpreter.__init__(self, locals)
118+        self.prompt = '>>> '
119+        self.buffer = []
120+
121+    def runsource(self, source):
122+        # installs the debug capture on first access
123+        DebugCapture.install()
124+        prompt = self.prompt
125+        sys.stdout.push()
126+        try:
127+            source_to_eval = ''.join(self.buffer + [source])
128+            if code.InteractiveInterpreter.runsource(self,
129+               source_to_eval, '<debugger>', 'single'):
130+                self.prompt = '... '
131+                self.buffer.append(source)
132+            else:
133+                self.prompt = '>>> '
134+                del self.buffer[:]
135+        finally:
136+            return prompt + source + sys.stdout.release()
137+
138+    def runcode(self, code):
139+        try:
140+            exec code in self.globals, self.locals
141+        except:
142+            self.showtraceback()
143+
144+    def write(self, data):
145+        sys.stdout.write(data)
146+
147+
148+class AjaxDebugger(object):
149+    """
150+    The AJAX Debugger
151+    """
152+
153+    def __init__(self):
154+        self.debug_sessions = {}
155+        self.consoles = {}
156+
157+    def send(self, debugger, frame, cmd):
158+        if debugger not in self.debug_sessions:
159+            return '!!! expired debugger !!!'
160+        session = self.debug_sessions[debugger]
161+        if frame not in session:
162+            return '!!! unknown frame !!!'
163+        key = '%s|%s' % (debugger, frame)
164+        if key not in self.consoles:
165+            self.consoles[key] = PlainDebugger(
166+                session[frame].f_globals,
167+                session[frame].f_locals
168+            )
169+        return self.consoles[key].runsource(cmd)
170+
171+
172+console = AjaxDebugger()
173+
174 #
175 # Templates are embedded in the file so that we know the error handler will
176 # always work even if the template loader is broken.
177 #
178 
179-TECHNICAL_500_TEMPLATE = """
180+TECHNICAL_500_TEMPLATE = r"""
181 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
182 <html lang="en">
183 <head>
184@@ -217,6 +337,7 @@
185     body * * { padding:0; }
186     body { font:small sans-serif; }
187     body>div { border-bottom:1px solid #ddd; }
188+    a { color: #333; }
189     h1 { font-weight:normal; }
190     h2 { margin-bottom:.8em; }
191     h2 span { font-size:80%; color:#666; font-weight:normal; }
192@@ -232,6 +353,8 @@
193     table td.code div { overflow:hidden; }
194     table.source th { color:#666; }
195     table.source td { font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
196+    form.debug { display: block; padding: 10px 20px 10px 40px; }
197+    form.debug input { margin-top: 5px; width: 100%; }
198     ul.traceback { list-style-type:none; }
199     ul.traceback li.frame { margin-bottom:1em; }
200     div.context { margin: 10px 0; }
201@@ -239,8 +362,8 @@
202     div.context ol li { font-family:monospace; white-space:pre; color:#666; cursor:pointer; }
203     div.context ol.context-line li { color:black; background-color:#ccc; }
204     div.context ol.context-line li span { float: right; }
205-    div.commands { margin-left: 40px; }
206-    div.commands a { color:black; text-decoration:none; }
207+    ul.commands { margin-left: 40px; padding: 0; list-style: none; }
208+    ul.commands a { color:black; text-decoration:none; }
209     #summary { background: #ffc; }
210     #summary h2 { font-weight: normal; color: #666; }
211     #explanation { background:#eee; }
212@@ -256,21 +379,23 @@
213   </style>
214   <script type="text/javascript">
215   //<!--
216-    function getElementsByClassName(oElm, strTagName, strClassName){
217-        // Written by Jonathan Snook, http://www.snook.ca/jon; Add-ons by Robert Nyman, http://www.robertnyman.com
218-        var arrElements = (strTagName == "*" && document.all)? document.all :
219+    var DEBUG_ID = {% if debugger_enabled %}'{{ debugger_id }}'{% else %}null{% endif %};
220+
221+    function getElementsByClassName(oElm, strTagName, strClassName) {
222+      // Written by Jonathan Snook, http://www.snook.ca/jon; Add-ons by Robert Nyman, http://www.robertnyman.com
223+      var arrElements = (strTagName == "*" && document.all) ? document.all :
224         oElm.getElementsByTagName(strTagName);
225-        var arrReturnElements = new Array();
226-        strClassName = strClassName.replace(/\-/g, "\\-");
227-        var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$)");
228-        var oElement;
229-        for(var i=0; i<arrElements.length; i++){
230-            oElement = arrElements[i];
231-            if(oRegExp.test(oElement.className)){
232-                arrReturnElements.push(oElement);
233-            }
234+      var arrReturnElements = new Array();
235+      strClassName = strClassName.replace(/\-/g, "\\-");
236+      var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$)");
237+      var oElement;
238+      for(var i=0; i<arrElements.length; i++) {
239+        oElement = arrElements[i];
240+        if(oRegExp.test(oElement.className)) {
241+          arrReturnElements.push(oElement);
242         }
243-        return (arrReturnElements)
244+      }
245+      return arrReturnElements;
246     }
247     function hideAll(elems) {
248       for (var e = 0; e < elems.length; e++) {
249@@ -279,6 +404,7 @@
250     }
251     window.onload = function() {
252       hideAll(getElementsByClassName(document, 'table', 'vars'));
253+      hideAll(getElementsByClassName(document, 'form', 'debug'));
254       hideAll(getElementsByClassName(document, 'ol', 'pre-context'));
255       hideAll(getElementsByClassName(document, 'ol', 'post-context'));
256       hideAll(getElementsByClassName(document, 'div', 'pastebin'));
257@@ -300,6 +426,14 @@
258       s.innerHTML = s.innerHTML == uarr ? darr : uarr;
259       return false;
260     }
261+    function debugToggle(link, id) {
262+      toggle('d' + id);
263+      var s = link.getElementsByTagName('span')[0];
264+      var uarr = String.fromCharCode(0x25b6);
265+      var darr = String.fromCharCode(0x25bc);
266+      s.innerHTML = s.innerHTML == uarr ? darr : uarr;
267+      return false;
268+    }
269     function switchPastebinFriendly(link) {
270       s1 = "Switch to copy-and-paste view";
271       s2 = "Switch back to interactive view";
272@@ -307,6 +441,50 @@
273       toggle('browserTraceback', 'pastebinTraceback');
274       return false;
275     }
276+    function sendDebugCommand(frameID, command, callback) {
277+      var activex = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'];
278+      var con = null;
279+      try {
280+        con = new XMLHttpRequest();
281+      }
282+      catch (e) {
283+        for (var i=0; i < activex.length; i++) {
284+          try {
285+            con = new ActiveXObject(activex[i]);
286+          }
287+          catch (e) {};
288+          if (con) {
289+            break;
290+          }
291+        }
292+      }
293+      var data = 'tb=' + DEBUG_ID + '&' +
294+                 'frame=' + frameID + '&' +
295+                 'cmd=' + encodeURIComponent(command);
296+      con.onreadystatechange = function() {
297+        if (con.readyState == 4) {
298+          callback(con.responseText);
299+        }
300+      };
301+      con.open('POST', '?__send_to_django_debugger__=yes');
302+      con.send(data);
303+    }
304+    function submitCommand(frameID) {
305+      var input = document.getElementById('di' + frameID);
306+      var output = document.getElementById('do' + frameID);
307+      output = (output.firstChild || output.appendChild(document.createTextNode('')));
308+      if (input.value == 'clear') {
309+        output.nodeValue = '';
310+      }
311+      else {
312+        sendDebugCommand(frameID, input.value + '\n', function(value) {
313+          output.nodeValue += value;
314+        });
315+      }
316+      input.value = '';
317+      input.focus();
318+      return false;
319+    }
320     //-->
321   </script>
322 </head>
323@@ -403,26 +581,39 @@
324             </div>
325           {% endif %}
326 
327-          {% if frame.vars %}
328-            <div class="commands">
329-                <a href="#" onclick="return varToggle(this, '{{ frame.id }}')"><span>&#x25b6;</span> Local vars</a>
330-            </div>
331-            <table class="vars" id="v{{ frame.id }}">
332-              <thead>
333-                <tr>
334-                  <th>Variable</th>
335-                  <th>Value</th>
336-                </tr>
337-              </thead>
338-              <tbody>
339-                {% for var in frame.vars|dictsort:"0" %}
340+          {% if frame.vars or debugger_enabled %}
341+            <ul class="commands">
342+              {% if frame.vars %}
343+                <li><a href="#" onclick="return varToggle(this, '{{ frame.id }}')"><span>&#x25b6;</span> Local vars</a></li>
344+              {% endif %}
345+              {% if debugger_enabled %}
346+                <li><a href="#" onclick="return debugToggle(this, '{{ frame.id }}')"><span>&#x25b6;</span> Debug</a></li>
347+              {% endif %}
348+            </ul>
349+            {% if frame.vars %}
350+              <table class="vars" id="v{{ frame.id }}">
351+                <thead>
352                   <tr>
353-                    <td>{{ var.0 }}</td>
354-                    <td class="code"><div>{{ var.1|pprint|escape }}</div></td>
355+                    <th>Variable</th>
356+                    <th>Value</th>
357                   </tr>
358-                {% endfor %}
359-              </tbody>
360-            </table>
361+                </thead>
362+                <tbody>
363+                  {% for var in frame.vars|dictsort:"0" %}
364+                    <tr>
365+                      <td>{{ var.0 }}</td>
366+                      <td class="code"><div>{{ var.1|pprint|escape }}</div></td>
367+                    </tr>
368+                  {% endfor %}
369+                </tbody>
370+              </table>
371+            {% endif %}
372+            {% if debugger_enabled %}
373+              <form class="debug" id="d{{ frame.id }}" onsubmit="return submitCommand('{{ frame.id }}')">
374+                <pre id="do{{ frame.id }}"></pre>
375+                <input type="text" id="di{{ frame.id }}">
376+              </form>
377+            {% endif %}
378           {% endif %}
379         </li>
380       {% endfor %}
381Index: middleware/common.py
382===================================================================
383--- middleware/common.py        (revision 5588)
384+++ middleware/common.py        (working copy)
385@@ -25,6 +25,17 @@
386         settings.APPEND_SLASH and settings.PREPEND_WWW
387         """
388 
389+        # Handle Debugger AJAX Requests
390+        if settings.DEBUGGER_ENABLED and \
391+           request.GET.get('__send_to_django_debugger__') == 'yes':
392+            from django.views.debug import console
393+            rv = console.send(
394+                debugger=request.POST.get('tb'),
395+                frame=request.POST.get('frame'),
396+                cmd=request.POST.get('cmd', '')
397+            )
398+            return http.HttpResponse(rv, mimetype='text/plain')
399+
400         # Check for denied User-Agents
401         if 'HTTP_USER_AGENT' in request.META:
402             for user_agent_regex in settings.DISALLOWED_USER_AGENTS: