Ticket #3527: django_debugger.patch
File django_debugger.patch, 17.0 KB (added by , 18 years ago) |
---|
-
django/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 -
django/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, re5 import sys, os, re, 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 … … 90 102 exc_type, exc_value, tb, template_info = get_template_exception_info(exc_type, exc_value, tb) 91 103 frames = [] 92 104 while tb is not None: 105 # support for __traceback_hide__ which is used by a few libraries 106 # to hide internal frames. 107 if tb.tb_frame.f_locals.get('__traceback_hide__'): 108 tb = tb.tb_next 109 continue 110 if debugger_enabled: 111 frame_storage[str(id(tb))] = tb.tb_frame 93 112 filename = tb.tb_frame.f_code.co_filename 94 113 function = tb.tb_frame.f_code.co_name 95 114 lineno = tb.tb_lineno - 1 96 pre_context_lineno, pre_context, context_line, post_context = _get_lines_from_file(filename, lineno, 7) 97 if pre_context_lineno: 115 loader = tb.tb_frame.f_globals.get('__loader__') 116 module_name = tb.tb_frame.f_globals.get('__name__') 117 pre_context_lineno, pre_context, context_line, post_context = _get_lines_from_file(filename, lineno, 7, loader, module_name) 118 if pre_context_lineno is not None: 98 119 frames.append({ 99 120 'tb': tb, 100 121 'filename': filename, … … 117 138 }] 118 139 t = Template(TECHNICAL_500_TEMPLATE, name='Technical 500 template') 119 140 c = Context({ 141 'debugger_id': debugger_id, 142 'debugger_enabled': debugger_enabled, 120 143 'exception_type': exc_type.__name__, 121 144 'exception_value': exc_value, 122 145 'frames': frames, … … 161 184 }) 162 185 return HttpResponseNotFound(t.render(c), mimetype='text/html') 163 186 164 def _get_lines_from_file(filename, lineno, context_lines ):187 def _get_lines_from_file(filename, lineno, context_lines, loader=None, module_name=None): 165 188 """ 166 189 Returns context_lines before and after lineno from file. 167 190 Returns (pre_context_lineno, pre_context, context_line, post_context). 168 191 """ 169 try: 170 source = open(filename).readlines() 171 lower_bound = max(0, lineno - context_lines) 172 upper_bound = lineno + context_lines 192 source = None 193 if loader is not None: 194 source = loader.get_source(module_name).splitlines() 195 else: 196 try: 197 f = open(filename) 198 try: 199 source = f.readlines() 200 finally: 201 f.close() 202 except (OSError, IOError): 203 pass 204 if source is None: 205 return None, [], None, [] 173 206 174 pre_context = [line.strip('\n') for line in source[lower_bound:lineno]] 175 context_line = source[lineno].strip('\n') 176 post_context = [line.strip('\n') for line in source[lineno+1:upper_bound]] 207 lower_bound = max(0, lineno - context_lines) 208 upper_bound = lineno + context_lines 177 209 178 return lower_bound, pre_context, context_line, post_context179 except (OSError, IOError):180 return None, [], None, []210 pre_context = [line.strip('\n') for line in source[lower_bound:lineno]] 211 context_line = source[lineno].strip('\n') 212 post_context = [line.strip('\n') for line in source[lineno+1:upper_bound]] 181 213 214 return lower_bound, pre_context, context_line, post_context 215 216 217 class DebugCapture(object): 218 """ 219 Class that wraps sys.stdout in order to get the output 220 for the debugger. threadsafe but quite slow. 221 """ 222 _orig = None 223 224 def __init__(self): 225 self._buffer = {} 226 227 def install(cls): 228 if cls._orig: 229 return 230 cls._orig = sys.stdout 231 sys.stdout = cls() 232 install = classmethod(install) 233 234 def push(self): 235 from cStringIO import StringIO 236 tid = threading.currentThread() 237 self._buffer[tid] = StringIO() 238 239 def release(self): 240 tid = threading.currentThread() 241 if tid in self._buffer: 242 result = self._buffer[tid].getvalue() 243 del self._buffer[tid] 244 else: 245 result = '' 246 return result 247 248 def write(self, d): 249 tid = threading.currentThread() 250 if tid in self._buffer: 251 self._buffer[tid].write(d) 252 else: 253 self._orig.write(d) 254 255 256 class PlainDebugger(code.InteractiveInterpreter): 257 """ 258 Subclass of the python interactive interpreter that 259 automatically captures stdout and buffers older input. 260 """ 261 262 def __init__(self, locals=None, globals=None): 263 self.globals = globals 264 code.InteractiveInterpreter.__init__(self, locals) 265 self.prompt = '>>> ' 266 self.buffer = [] 267 268 def runsource(self, source): 269 # installs the debug capture on first access 270 DebugCapture.install() 271 prompt = self.prompt 272 sys.stdout.push() 273 try: 274 source_to_eval = ''.join(self.buffer + [source]) 275 if code.InteractiveInterpreter.runsource(self, 276 source_to_eval, '<debugger>', 'single'): 277 self.prompt = '... ' 278 self.buffer.append(source) 279 else: 280 self.prompt = '>>> ' 281 del self.buffer[:] 282 finally: 283 return prompt + source + sys.stdout.release() 284 285 def runcode(self, code): 286 try: 287 exec code in self.globals, self.locals 288 except: 289 self.showtraceback() 290 291 def write(self, data): 292 sys.stdout.write(data) 293 294 295 class AjaxDebugger(object): 296 """ 297 The AJAX Debugger 298 """ 299 300 def __init__(self): 301 self.debug_sessions = {} 302 self.consoles = {} 303 304 def send(self, debugger, frame, cmd): 305 if debugger not in self.debug_sessions: 306 return '!!! expired debugger !!!' 307 session = self.debug_sessions[debugger] 308 if frame not in session: 309 return '!!! unknown frame !!!' 310 key = '%s|%s' % (debugger, frame) 311 if key not in self.consoles: 312 self.consoles[key] = PlainDebugger( 313 session[frame].f_globals, 314 session[frame].f_locals 315 ) 316 return self.consoles[key].runsource(cmd) 317 318 319 console = AjaxDebugger() 320 182 321 # 183 322 # Templates are embedded in the file so that we know the error handler will 184 323 # always work even if the template loader is broken. 185 324 # 186 325 187 TECHNICAL_500_TEMPLATE = """326 TECHNICAL_500_TEMPLATE = r""" 188 327 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 189 328 <html lang="en"> 190 329 <head> … … 197 336 body * * { padding:0; } 198 337 body { font:small sans-serif; } 199 338 body>div { border-bottom:1px solid #ddd; } 339 a { color: #333; } 200 340 h1 { font-weight:normal; } 201 341 h2 { margin-bottom:.8em; } 202 342 h2 span { font-size:80%; color:#666; font-weight:normal; } … … 212 352 table td.code div { overflow:hidden; } 213 353 table.source th { color:#666; } 214 354 table.source td { font-family:monospace; white-space:pre; border-bottom:1px solid #eee; } 355 form.debug { display: block; padding: 10px 20px 10px 40px; } 356 form.debug input { margin-top: 5px; width: 100%; } 215 357 ul.traceback { list-style-type:none; } 216 358 ul.traceback li.frame { margin-bottom:1em; } 217 359 div.context { margin: 10px 0; } … … 219 361 div.context ol li { font-family:monospace; white-space:pre; color:#666; cursor:pointer; } 220 362 div.context ol.context-line li { color:black; background-color:#ccc; } 221 363 div.context ol.context-line li span { float: right; } 222 div.commands { margin-left: 40px; }223 div.commands a { color:black; text-decoration:none; }364 ul.commands { margin-left: 40px; padding: 0; list-style: none; } 365 ul.commands a { color:black; text-decoration:none; } 224 366 #summary { background: #ffc; } 225 367 #summary h2 { font-weight: normal; color: #666; } 226 368 #explanation { background:#eee; } … … 236 378 </style> 237 379 <script type="text/javascript"> 238 380 //<!-- 239 function getElementsByClassName(oElm, strTagName, strClassName){ 240 // Written by Jonathan Snook, http://www.snook.ca/jon; Add-ons by Robert Nyman, http://www.robertnyman.com 241 var arrElements = (strTagName == "*" && document.all)? document.all : 381 var DEBUG_ID = {% if debugger_enabled %}'{{ debugger_id }}'{% else %}null{% endif %}; 382 383 function getElementsByClassName(oElm, strTagName, strClassName) { 384 // Written by Jonathan Snook, http://www.snook.ca/jon; Add-ons by Robert Nyman, http://www.robertnyman.com 385 var arrElements = (strTagName == "*" && document.all) ? document.all : 242 386 oElm.getElementsByTagName(strTagName); 243 var arrReturnElements = new Array(); 244 strClassName = strClassName.replace(/\-/g, "\\-"); 245 var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$)"); 246 var oElement; 247 for(var i=0; i<arrElements.length; i++){ 248 oElement = arrElements[i]; 249 if(oRegExp.test(oElement.className)){ 250 arrReturnElements.push(oElement); 251 } 387 var arrReturnElements = new Array(); 388 strClassName = strClassName.replace(/\-/g, "\\-"); 389 var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$)"); 390 var oElement; 391 for(var i=0; i<arrElements.length; i++) { 392 oElement = arrElements[i]; 393 if(oRegExp.test(oElement.className)) { 394 arrReturnElements.push(oElement); 252 395 } 253 return (arrReturnElements) 396 } 397 return arrReturnElements; 254 398 } 255 399 function hideAll(elems) { 256 400 for (var e = 0; e < elems.length; e++) { … … 259 403 } 260 404 window.onload = function() { 261 405 hideAll(getElementsByClassName(document, 'table', 'vars')); 406 hideAll(getElementsByClassName(document, 'form', 'debug')); 262 407 hideAll(getElementsByClassName(document, 'ol', 'pre-context')); 263 408 hideAll(getElementsByClassName(document, 'ol', 'post-context')); 264 409 hideAll(getElementsByClassName(document, 'div', 'pastebin')); … … 280 425 s.innerHTML = s.innerHTML == uarr ? darr : uarr; 281 426 return false; 282 427 } 428 function debugToggle(link, id) { 429 toggle('d' + id); 430 var s = link.getElementsByTagName('span')[0]; 431 var uarr = String.fromCharCode(0x25b6); 432 var darr = String.fromCharCode(0x25bc); 433 s.innerHTML = s.innerHTML == uarr ? darr : uarr; 434 return false; 435 } 283 436 function switchPastebinFriendly(link) { 284 437 s1 = "Switch to copy-and-paste view"; 285 438 s2 = "Switch back to interactive view"; … … 287 440 toggle('browserTraceback', 'pastebinTraceback'); 288 441 return false; 289 442 } 443 function sendDebugCommand(frameID, command, callback) { 444 var activex = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0']; 445 var con = null; 446 try { 447 con = new XMLHttpRequest(); 448 } 449 catch (e) { 450 for (var i=0; i < activex.length; i++) { 451 try { 452 con = new ActiveXObject(activex[i]); 453 } 454 catch (e) {}; 455 if (con) { 456 break; 457 } 458 } 459 } 460 var data = 'tb=' + DEBUG_ID + '&' + 461 'frame=' + frameID + '&' + 462 'cmd=' + encodeURIComponent(command); 463 con.onreadystatechange = function() { 464 if (con.readyState == 4) { 465 callback(con.responseText); 466 } 467 }; 468 con.open('POST', '?__send_to_django_debugger__=yes'); 469 con.send(data); 470 } 471 function submitCommand(frameID) { 472 var input = document.getElementById('di' + frameID); 473 var output = document.getElementById('do' + frameID); 474 output = (output.firstChild || output.appendChild(document.createTextNode(''))); 475 if (input.value == 'clear') { 476 output.nodeValue = ''; 477 } 478 else { 479 sendDebugCommand(frameID, input.value + '\n', function(value) { 480 output.nodeValue += value; 481 }); 482 } 483 input.value = ''; 484 input.focus(); 485 return false; 486 } 290 487 //--> 291 488 </script> 292 489 </head> … … 314 511 </tr> 315 512 <tr> 316 513 <th>Exception Location:</th> 317 <td>{{ lastframe.filename }} in {{ lastframe.function}}, line {{ lastframe.lineno }}</td>514 <td>{{ lastframe.filename|escape }} in {{ lastframe.function|escape }}, line {{ lastframe.lineno }}</td> 318 515 </tr> 319 516 </table> 320 517 </div> … … 361 558 <ul class="traceback"> 362 559 {% for frame in frames %} 363 560 <li class="frame"> 364 <code>{{ frame.filename }}</code> in <code>{{ frame.function}}</code>561 <code>{{ frame.filename|escape }}</code> in <code>{{ frame.function|escape }}</code> 365 562 366 563 {% if frame.context_line %} 367 564 <div class="context" id="c{{ frame.id }}"> … … 375 572 </div> 376 573 {% endif %} 377 574 378 {% if frame.vars %} 379 <div class="commands"> 380 <a href="#" onclick="return varToggle(this, '{{ frame.id }}')"><span>▶</span> Local vars</a> 381 </div> 382 <table class="vars" id="v{{ frame.id }}"> 383 <thead> 384 <tr> 385 <th>Variable</th> 386 <th>Value</th> 387 </tr> 388 </thead> 389 <tbody> 390 {% for var in frame.vars|dictsort:"0" %} 575 {% if frame.vars or debugger_enabled %} 576 <ul class="commands"> 577 {% if frame.vars %} 578 <li><a href="#" onclick="return varToggle(this, '{{ frame.id }}')"><span>▶</span> Local vars</a></li> 579 {% endif %} 580 {% if debugger_enabled %} 581 <li><a href="#" onclick="return debugToggle(this, '{{ frame.id }}')"><span>▶</span> Debug</a></li> 582 {% endif %} 583 </ul> 584 {% if frame.vars %} 585 <table class="vars" id="v{{ frame.id }}"> 586 <thead> 391 587 <tr> 392 <t d>{{ var.0 }}</td>393 <t d class="code"><div>{{ var.1|pprint|escape }}</div></td>588 <th>Variable</th> 589 <th>Value</th> 394 590 </tr> 395 {% endfor %} 396 </tbody> 397 </table> 591 </thead> 592 <tbody> 593 {% for var in frame.vars|dictsort:"0" %} 594 <tr> 595 <td>{{ var.0 }}</td> 596 <td class="code"><div>{{ var.1|pprint|escape }}</div></td> 597 </tr> 598 {% endfor %} 599 </tbody> 600 </table> 601 {% endif %} 602 {% if debugger_enabled %} 603 <form class="debug" id="d{{ frame.id }}" onsubmit="return submitCommand('{{ frame.id }}')"> 604 <pre id="do{{ frame.id }}"></pre> 605 <input type="text" id="di{{ frame.id }}"> 606 </form> 607 {% endif %} 398 608 {% endif %} 399 609 </li> 400 610 {% endfor %} -
django/middleware/common.py
24 24 settings.APPEND_SLASH and settings.PREPEND_WWW 25 25 """ 26 26 27 # Handle Debugger AJAX Requests 28 if settings.DEBUGGER_ENABLED and \ 29 request.GET.get('__send_to_django_debugger__') == 'yes': 30 from django.views.debug import console 31 rv = console.send( 32 debugger=request.POST.get('tb'), 33 frame=request.POST.get('frame'), 34 cmd=request.POST.get('cmd', '') 35 ) 36 return http.HttpResponse(rv, mimetype='text/plain') 37 27 38 # Check for denied User-Agents 28 39 if request.META.has_key('HTTP_USER_AGENT'): 29 40 for user_agent_regex in settings.DISALLOWED_USER_AGENTS: