Ticket #3527: django_debugger_r5588.diff
File django_debugger_r5588.diff, 13.9 KB (added by , 17 years ago) |
---|
-
conf/global_settings.py
11 11 12 12 DEBUG = False 13 13 TEMPLATE_DEBUG = False 14 DEBUGGER_ENABLED = False 14 15 15 16 # Whether to use the "Etag" header. This saves bandwidth but slows down performance. 16 17 USE_ETAGS = False -
views/debug.py
2 2 from django.template import Template, Context, TemplateDoesNotExist 3 3 from django.utils.html import escape 4 4 from django.http import HttpResponseServerError, HttpResponseNotFound 5 import os, re, sys 5 import os, re, sys, code, threading 6 6 7 7 HIDDEN_SETTINGS = re.compile('SECRET|PASSWORD|PROFANITIES_LIST') 8 8 9 def new_debugger_id(): 10 import time 11 import md5 12 return md5.new(str(time.time())).hexdigest() 13 9 14 def linebreak_iter(template_source): 10 15 yield 0 11 16 p = template_source.find('\n') … … 66 71 Create a technical server error response. The last three arguments are 67 72 the values returned from sys.exc_info() and friends. 68 73 """ 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 69 81 template_info = None 70 82 template_does_not_exist = False 71 83 loader_debug_info = None … … 95 107 if tb.tb_frame.f_locals.get('__traceback_hide__'): 96 108 tb = tb.tb_next 97 109 continue 110 if debugger_enabled: 111 frame_storage[str(id(tb))] = tb.tb_frame 98 112 filename = tb.tb_frame.f_code.co_filename 99 113 function = tb.tb_frame.f_code.co_name 100 114 lineno = tb.tb_lineno - 1 … … 124 138 }] 125 139 t = Template(TECHNICAL_500_TEMPLATE, name='Technical 500 template') 126 140 c = Context({ 141 'debugger_id': debugger_id, 142 'debugger_enabled': debugger_enabled, 127 143 'exception_type': exc_type.__name__, 128 144 'exception_value': exc_value, 129 145 'frames': frames, … … 199 215 200 216 return lower_bound, pre_context, context_line, post_context 201 217 218 class 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 257 class 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 296 class 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 320 console = AjaxDebugger() 321 202 322 # 203 323 # Templates are embedded in the file so that we know the error handler will 204 324 # always work even if the template loader is broken. 205 325 # 206 326 207 TECHNICAL_500_TEMPLATE = """327 TECHNICAL_500_TEMPLATE = r""" 208 328 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 209 329 <html lang="en"> 210 330 <head> … … 217 337 body * * { padding:0; } 218 338 body { font:small sans-serif; } 219 339 body>div { border-bottom:1px solid #ddd; } 340 a { color: #333; } 220 341 h1 { font-weight:normal; } 221 342 h2 { margin-bottom:.8em; } 222 343 h2 span { font-size:80%; color:#666; font-weight:normal; } … … 232 353 table td.code div { overflow:hidden; } 233 354 table.source th { color:#666; } 234 355 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%; } 235 358 ul.traceback { list-style-type:none; } 236 359 ul.traceback li.frame { margin-bottom:1em; } 237 360 div.context { margin: 10px 0; } … … 239 362 div.context ol li { font-family:monospace; white-space:pre; color:#666; cursor:pointer; } 240 363 div.context ol.context-line li { color:black; background-color:#ccc; } 241 364 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; } 244 367 #summary { background: #ffc; } 245 368 #summary h2 { font-weight: normal; color: #666; } 246 369 #explanation { background:#eee; } … … 256 379 </style> 257 380 <script type="text/javascript"> 258 381 //<!-- 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 : 262 387 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); 272 396 } 273 return (arrReturnElements) 397 } 398 return arrReturnElements; 274 399 } 275 400 function hideAll(elems) { 276 401 for (var e = 0; e < elems.length; e++) { … … 279 404 } 280 405 window.onload = function() { 281 406 hideAll(getElementsByClassName(document, 'table', 'vars')); 407 hideAll(getElementsByClassName(document, 'form', 'debug')); 282 408 hideAll(getElementsByClassName(document, 'ol', 'pre-context')); 283 409 hideAll(getElementsByClassName(document, 'ol', 'post-context')); 284 410 hideAll(getElementsByClassName(document, 'div', 'pastebin')); … … 300 426 s.innerHTML = s.innerHTML == uarr ? darr : uarr; 301 427 return false; 302 428 } 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 } 303 437 function switchPastebinFriendly(link) { 304 438 s1 = "Switch to copy-and-paste view"; 305 439 s2 = "Switch back to interactive view"; … … 307 441 toggle('browserTraceback', 'pastebinTraceback'); 308 442 return false; 309 443 } 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 } 310 488 //--> 311 489 </script> 312 490 </head> … … 403 581 </div> 404 582 {% endif %} 405 583 406 {% if frame.vars %} 407 <div class="commands"> 408 <a href="#" onclick="return varToggle(this, '{{ frame.id }}')"><span>▶</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>▶</span> Local vars</a></li> 588 {% endif %} 589 {% if debugger_enabled %} 590 <li><a href="#" onclick="return debugToggle(this, '{{ frame.id }}')"><span>▶</span> Debug</a></li> 591 {% endif %} 592 </ul> 593 {% if frame.vars %} 594 <table class="vars" id="v{{ frame.id }}"> 595 <thead> 419 596 <tr> 420 <t d>{{ var.0 }}</td>421 <t d class="code"><div>{{ var.1|pprint|escape }}</div></td>597 <th>Variable</th> 598 <th>Value</th> 422 599 </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 %} 426 617 {% endif %} 427 618 </li> 428 619 {% endfor %} -
middleware/common.py
25 25 settings.APPEND_SLASH and settings.PREPEND_WWW 26 26 """ 27 27 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 28 39 # Check for denied User-Agents 29 40 if 'HTTP_USER_AGENT' in request.META: 30 41 for user_agent_regex in settings.DISALLOWED_USER_AGENTS: