diff options
| author | Alex Gaynor | 2008-09-27 14:18:19 -0400 |
|---|---|---|
| committer | Alex Gaynor | 2008-09-27 14:18:19 -0400 |
| commit | 225965ceb490d55207733aa297fd0d8a9c1cbfec (patch) | |
| tree | f92829b9ac85e860aabf14235441f6cb793e0595 | |
| parent | 9a7fe9e528c72d39efb1954275e1e588b2bb541c (diff) | |
| parent | 8d32dc2eb8a5284aa668d5b8f4b903cdf67a3e30 (diff) | |
| download | django-debug-toolbar-225965ceb490d55207733aa297fd0d8a9c1cbfec.tar.bz2 | |
fixed merge conflicts
| -rw-r--r-- | README.rst | 19 | ||||
| -rw-r--r-- | debug_toolbar/media/toolbar.css | 158 | ||||
| -rw-r--r-- | debug_toolbar/media/toolbar.js | 2 | ||||
| -rw-r--r-- | debug_toolbar/middleware.py | 9 | ||||
| -rw-r--r-- | debug_toolbar/panels/__init__.py | 20 | ||||
| -rw-r--r-- | debug_toolbar/panels/cache.py | 3 | ||||
| -rw-r--r-- | debug_toolbar/panels/headers.py | 7 | ||||
| -rw-r--r-- | debug_toolbar/panels/request_vars.py | 3 | ||||
| -rw-r--r-- | debug_toolbar/panels/sql.py | 26 | ||||
| -rw-r--r-- | debug_toolbar/panels/template.py | 13 | ||||
| -rw-r--r-- | debug_toolbar/panels/timer.py | 10 | ||||
| -rw-r--r-- | debug_toolbar/templates/debug_toolbar/panels/cache.html | 4 | ||||
| -rw-r--r-- | debug_toolbar/templates/debug_toolbar/panels/sql.html | 10 | ||||
| -rw-r--r-- | debug_toolbar/templates/debug_toolbar/panels/templates.html | 22 | ||||
| -rw-r--r-- | debug_toolbar/toolbar/loader.py | 8 | ||||
| -rw-r--r-- | debug_toolbar/views.py | 13 |
16 files changed, 263 insertions, 64 deletions
@@ -3,20 +3,18 @@ Django Debug Toolbar ==================== The Django Debug Toolbar is a configurable set of panels that display various -debug information about the current request/response. It is a small toolbar -that, when activated, situates itself in the top-right location of the browser -window. When particular panels are clicked, more details about that panel's -content are displayed. +debug information about the current request/response and when clicked, display +more details about the panel's content. Currently, the following panels have been written and are working: - Django version -- SQL queries including time to execute - Request timer - Common HTTP headers -- GET/POST variable display +- GET/POST/cookie/session variable display +- SQL queries including time to execute and links to EXPLAIN each query - Cache stats -- Templates used and template directories +- Templates and context used, and their template paths If you have ideas for other panels please let us know. @@ -71,3 +69,10 @@ TODO - Panel idea: Show some commonly used settings from settings.py - Panel idea: AJAX call to show cprofile data similar to the ?prof idea - CSS Stylings +- Restructure panels to popular context that pushes up to the toolbar +- Consider moving panel to the bottom of the HTML document, which would allow us + to use common reset.css for consistent toolbar styling without affecting + anything else. +- Make the trigger whether to display the toolbar configurable with options such + as if: DEBUG is true, IP is in INTERNAL_IPS, authenticated user is_superuser, + etc. diff --git a/debug_toolbar/media/toolbar.css b/debug_toolbar/media/toolbar.css index 7d2ad82..43ac588 100644 --- a/debug_toolbar/media/toolbar.css +++ b/debug_toolbar/media/toolbar.css @@ -1,16 +1,142 @@ -#djDebugToolbar {height:30px; background:orange; color:#000; z-index:100000000; border:1px solid #06171D; border-width:1px 0;} -#djDebugToolbar ul {margin:0; padding:0 10px; list-style:none;} -#djDebugToolbar li {display:inline; width:auto; position:relative; float:none; margin:0 10px 0 0; padding:0; height:20px; line-height:30px; padding:8px 10px 8px 0; border-right: 1px solid #06171D;} -#djDebugToolbar #djDebugButton {color:red; font-weight:bold;} -#djDebug * {margin:0; padding:0; float:none; position:static; } -#djDebug .panelContent {display:none; position:absolute; margin:0; padding:5px; top:35px; width:auto; left:5px; right:5px; bottom:5px; background:white; color:black; border:1px solid black; z-index:1000000; overflow:auto;} -#djDebug .panelContent p {padding: 0 5px;} -#djDebug .panelContent p, #djDebug .panelContent table, #djDebug .panelContent ul, #djDebug .panelContent dl {margin:5px 0;} -#djDebug .close {float:right; font-weight:bold;} -#djDebug .panelContent dt, #djDebug .panelContent dd {display: block;} -#djDebug .panelContent dd {margin-left:10px;} -#djDebug th, #djDebug td {padding: 5px;} -#djDebug .row1 td {background:#fff;} -#djDebug .row2 td {background:#ddd;} -#djDebugWindow {z-index:2000000;} -#djDebugWindow .back {float:right; font-weight:bold;} +#djDebugToolbar { + background: #326342; + height: 30px; + z-index: 100000000; + border-bottom: 2px solid #234f32; +} + +#djDebugToolbar ul { + margin: 0; + padding: 0; + list-style: none; +} + +#djDebugToolbar li { + border-left: 1px solid #487858; + color: #fff; + display: inline; + font-size: 11px; + font-weight: bold; + float: none; + height: 20px; + margin: 0; + padding: 0; + line-height: 30px; + padding: 8px 10px 9px; + position: relative; + width: auto; +} + +#djDebugToolbar li:hover { + background: #487858; +} + +#djDebugToolbar li:hover a { + color: #fff; +} + +#djDebugToolbar li:last-child { + border-right: 1px solid #487858; +} + +#djDebugToolbar #djDebugButton { + color: #92ef3f; +} + +#djDebug * { + color: #fff; + text-shadow: 0 0 2px #333; + float: none; + margin: 0; + padding: 0; + position: static; +} + +#djDebug a { + color: #f7c757; +} + +#djDebug a:hover { + color: #fff; +} + +#djDebug .panelContent { + background: #2a5738; + border-bottom: 2px solid #234f32; + border-top: 2px solid #487858; + display: none; + position: absolute; + margin: 0; + padding: 10px; + top: 35px; + width: auto; + left: 0px; + right: 0px; + bottom: 5px; + color: black; + z-index: 1000000; + overflow: auto; +} + +#djDebug h3 { + border-bottom: 1px solid #40684c; + color: #92ef3f; + padding: 0 0 5px; +} + +#djDebug .panelContent p { + padding: 0 5px; +} + +#djDebug .panelContent p, #djDebug .panelContent table, #djDebug .panelContent ol, #djDebug .panelContent ul, #djDebug .panelContent dl { + margin: 5px 0 15px; +} + +#djDebug .panelContent table { + width: 100%; +} + +#djDebug .close { + float: right; + font-weight: bold; +} + +#djDebug .panelContent dt, #djDebug .panelContent dd { + display: block; +} + +#djDebug .panelContent dd { + margin-left: 10px; +} + +#djDebug th { + font-weight: bold; + text-align: left; + padding: 5px; +} + +#djDebug td { + padding: 5px; +} + +#djDebug .row1 td { + background: #40684c; +} + +#djDebug .row2 td { + background: #356042; +} +#djDebug .syntax { color: #ffffff } +#djDebug .syntax .err { color: #ffffff } /* Error */ +#djDebug .syntax .g { color: #ffffff } /* Generic */ +#djDebug .syntax .k { color: #F7C757; font-weight: bold } /* Keyword */ +#djDebug .syntax .o { color: #ffffff } /* Operator */ +#djDebug .syntax .n { color: #ffffff } /* Name */ +#djDebug .syntax .mi { color: #92ef3f; font-weight: bold } /* Literal.Number.Integer */ +#djDebug .syntax .l { color: #ffffff } /* Literal */ +#djDebug .syntax .x { color: #ffffff } /* Other */ +#djDebug .syntax .p { color: #ffffff } /* Punctuation */ +#djDebug .syntax .m { color: #92ef3f; font-weight: bold } /* Literal.Number */ +#djDebug .syntax .s { color: #0086d2 } /* Literal.String */ +#djDebug .syntax .w { color: #888888 } /* Text.Whitespace */ +#djDebug .syntax .il { color: #92ef3f; font-weight: bold } /* Literal.Number.Integer.Long */ diff --git a/debug_toolbar/media/toolbar.js b/debug_toolbar/media/toolbar.js index 19a6abf..455a2fc 100644 --- a/debug_toolbar/media/toolbar.js +++ b/debug_toolbar/media/toolbar.js @@ -1,3 +1,4 @@ +var _$ = window.$; jQuery.noConflict(); jQuery(function($) { $.djDebug = function(data, klass) { @@ -62,3 +63,4 @@ jQuery(function($) { jQuery(function() { jQuery.djDebug(); }); +$ = _$; diff --git a/debug_toolbar/middleware.py b/debug_toolbar/middleware.py index c17c424..fefab18 100644 --- a/debug_toolbar/middleware.py +++ b/debug_toolbar/middleware.py @@ -54,13 +54,20 @@ class DebugToolbarMiddleware(object): if self.show_toolbar(request): self.debug_toolbar = DebugToolbar(request) - self.debug_toolbar.load_panels() + for panel in self.debug_toolbar.panels: + panel.process_request(request) return None + def process_view(self, request, view_func, view_args, view_kwargs): + for panel in self.debug_toolbar.panels: + panel.process_view(request, view_func, view_args, view_kwargs) + def process_response(self, request, response): if response.status_code != 200: return response + for panel in self.debug_toolbar.panels: + panel.process_response(request, response) if self.show_toolbar(request): if response['Content-Type'].split(';')[0] in _HTML_TYPES: script_loc = request.META.get('SCRIPT_NAME', '') diff --git a/debug_toolbar/panels/__init__.py b/debug_toolbar/panels/__init__.py index 29cec02..54b3318 100644 --- a/debug_toolbar/panels/__init__.py +++ b/debug_toolbar/panels/__init__.py @@ -6,9 +6,10 @@ class DebugPanel(object): """ # name = Base has_content = False # If content returns something, set to true in subclass - - def __init__(self, request): - self.request = request + + # Panel methods + def __init__(self): + pass def dom_id(self): return 'djDebug%sPanel' % (self.name.replace(' ', '')) @@ -20,6 +21,15 @@ class DebugPanel(object): raise NotImplementedError def content(self): - # TODO: This is a bit flaky in that panel.content() returns a string - # that gets inserted into the toolbar HTML template. raise NotImplementedError + + # Standard middleware methods + def process_request(self, request): + pass + + def process_view(self, request, view_func, view_args, view_kwargs): + pass + + def process_response(self, request, response): + pass + diff --git a/debug_toolbar/panels/cache.py b/debug_toolbar/panels/cache.py index 263b62b..613d4d9 100644 --- a/debug_toolbar/panels/cache.py +++ b/debug_toolbar/panels/cache.py @@ -78,7 +78,7 @@ class CacheDebugPanel(DebugPanel): name = 'Cache' has_content = True - def __init__(self, request): + def __init__(self): # This is hackish but to prevent threading issues is somewhat needed if isinstance(cache.cache, CacheStatTracker): cache.cache.reset() @@ -86,7 +86,6 @@ class CacheDebugPanel(DebugPanel): else: self.cache = CacheStatTracker(cache.cache) cache.cache = self.cache - super(CacheDebugPanel, self).__init__(request) def title(self): return 'Cache: %.2fms' % self.cache.total_time diff --git a/debug_toolbar/panels/headers.py b/debug_toolbar/panels/headers.py index 3ab4a95..213198a 100644 --- a/debug_toolbar/panels/headers.py +++ b/debug_toolbar/panels/headers.py @@ -37,8 +37,13 @@ class HeaderDebugPanel(DebugPanel): def url(self): return '' + def process_request(self, request): + self.headers = dict( + [(k, request.META[k]) for k in self.header_filter if k in request.META] + ) + def content(self): context = { - 'headers': dict([(k, self.request.META[k]) for k in self.header_filter if k in self.request.META]), + 'headers': self.headers } return render_to_string('debug_toolbar/panels/headers.html', context)
\ No newline at end of file diff --git a/debug_toolbar/panels/request_vars.py b/debug_toolbar/panels/request_vars.py index a137393..88a7204 100644 --- a/debug_toolbar/panels/request_vars.py +++ b/debug_toolbar/panels/request_vars.py @@ -14,6 +14,9 @@ class RequestVarsDebugPanel(DebugPanel): def url(self): return '' + def process_request(self, request): + self.request = request + def content(self): context = { 'get': [(k, self.request.GET.getlist(k)) for k in self.request.GET.iterkeys()], diff --git a/debug_toolbar/panels/sql.py b/debug_toolbar/panels/sql.py index fd08395..22d65a7 100644 --- a/debug_toolbar/panels/sql.py +++ b/debug_toolbar/panels/sql.py @@ -1,9 +1,11 @@ import time from debug_toolbar.panels import DebugPanel +from django.conf import settings from django.db import connection from django.db.backends import util from django.template.loader import render_to_string from django.utils import simplejson +from django.utils.hashcompat import sha_constructor class DatabaseStatTracker(util.CursorDebugWrapper): """ @@ -16,32 +18,38 @@ class DatabaseStatTracker(util.CursorDebugWrapper): return self.cursor.execute(sql, params) finally: stop = time.time() + _params = None + try: + _params = simplejson.dumps(params) + except TypeError: + pass # object not JSON serializable # We keep `sql` to maintain backwards compatibility self.db.queries.append({ 'sql': self.db.ops.last_executed_query(self.cursor, sql, params), 'time': stop - start, 'raw_sql': sql, - 'params': simplejson.dumps(params), + 'params': _params, + 'hash': sha_constructor(settings.SECRET_KEY + sql + _params).hexdigest(), }) util.CursorDebugWrapper = DatabaseStatTracker class SQLDebugPanel(DebugPanel): """ - Panel that displays information about the SQL queries run while processing the request. + Panel that displays information about the SQL queries run while processing + the request. """ name = 'SQL' has_content = True - - def __init__(self, request): - super(SQLDebugPanel, self).__init__(request) + + def __init__(self): self._offset = len(connection.queries) self._sql_time = 0 def title(self): self._sql_time = sum(map(lambda q: float(q['time']) * 1000, connection.queries)) - return '%d SQL Quer%s (%.2fms)' % ( - len(connection.queries), - len(connection.queries) == 1 and 'y' or 'ies', + return '%d SQL %s (%.2fms)' % ( + len(connection.queries), + (len(connection.queries) == 1) and 'query' or 'queries', self._sql_time ) @@ -72,7 +80,7 @@ def reformat_sql(sql): from pygments import highlight from pygments.lexers import SqlLexer from pygments.formatters import HtmlFormatter - sql = highlight(sql, SqlLexer(), HtmlFormatter(noclasses=True)) + sql = highlight(sql, SqlLexer(), HtmlFormatter()) except ImportError: pass return sql diff --git a/debug_toolbar/panels/template.py b/debug_toolbar/panels/template.py index e08f220..022a41f 100644 --- a/debug_toolbar/panels/template.py +++ b/debug_toolbar/panels/template.py @@ -1,12 +1,12 @@ +from pprint import pformat from django.conf import settings from django.core.signals import request_started from django.dispatch import Signal +from django.template.context import get_standard_processors from django.template.loader import render_to_string from django.test.signals import template_rendered from debug_toolbar.panels import DebugPanel -from pprint import pformat - # Code taken and adapted from Simon Willison and Django Snippets: # http://www.djangosnippets.org/snippets/766/ @@ -32,8 +32,7 @@ class TemplateDebugPanel(DebugPanel): name = 'Template' has_content = True - def __init__(self, request): - super(TemplateDebugPanel, self).__init__(request) + def __init__(self): self.templates = [] template_rendered.connect(self._storeTemplateInfo) @@ -46,6 +45,11 @@ class TemplateDebugPanel(DebugPanel): def url(self): return '' + def process_request(self, request): + self.context_processors = dict( + [("%s.%s" % (k.__module__, k.__name__), pformat(k(request))) for k in get_standard_processors()] + ) + def content(self): template_context = [] for i, d in enumerate(self.templates): @@ -67,5 +71,6 @@ class TemplateDebugPanel(DebugPanel): context = { 'templates': template_context, 'template_dirs': settings.TEMPLATE_DIRS, + 'context_processors': self.context_processors, } return render_to_string('debug_toolbar/panels/templates.html', context) diff --git a/debug_toolbar/panels/timer.py b/debug_toolbar/panels/timer.py index d520191..ea8ed4a 100644 --- a/debug_toolbar/panels/timer.py +++ b/debug_toolbar/panels/timer.py @@ -3,16 +3,18 @@ from debug_toolbar.panels import DebugPanel class TimerDebugPanel(DebugPanel): """ - Panel that displays the time a response took. + Panel that displays the time a response took in milliseconds. """ name = 'Timer' - def __init__(self, request): - super(TimerDebugPanel, self).__init__(request) + def process_request(self, request): self._start_time = time.time() + def process_response(self, request, response): + self.total_time = (time.time() - self._start_time) * 1000 + def title(self): - return 'Time: %0.2fms' % ((time.time() - self._start_time) * 1000) + return 'Time: %0.2fms' % (self.total_time) def url(self): return '' diff --git a/debug_toolbar/templates/debug_toolbar/panels/cache.html b/debug_toolbar/templates/debug_toolbar/panels/cache.html index 21ea173..3142783 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/cache.html +++ b/debug_toolbar/templates/debug_toolbar/panels/cache.html @@ -31,6 +31,7 @@ <td>{{ cache.get_many }}</td> </tr> </table> +{% if cache.calls %} <h3>Breakdown</h3> <table> <thead> @@ -51,4 +52,5 @@ </tr> {% endfor %} </tbody> -</table>
\ No newline at end of file +</table> +{% endif %} diff --git a/debug_toolbar/templates/debug_toolbar/panels/sql.html b/debug_toolbar/templates/debug_toolbar/panels/sql.html index 4663047..e218f22 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/sql.html +++ b/debug_toolbar/templates/debug_toolbar/panels/sql.html @@ -11,9 +11,13 @@ {% for query in queries %} <tr class="{% cycle 'row1' 'row2' %}"> <td>{{ query.time|floatformat:"4" }}</td> - <td><a class="remoteCall" href="/__debug__/sql_explain/?sql={{ query.raw_sql|urlencode }}¶ms={{ query.params|urlencode }}&time={{ query.time|floatformat:"4"|urlencode }}">EXPLAIN</a></td> - <td>{{ query.sql|safe }}</td> + <td> + {% if query.params %} + <a class="remoteCall" href="/__debug__/sql_explain/?sql={{ query.raw_sql|urlencode }}¶ms={{ query.params|urlencode }}&time={{ query.time|floatformat:"4"|urlencode }}&hash={{ query.hash }}">EXPLAIN</a> + {% endif %} + </td> + <td class="syntax">{{ query.sql|safe }}</td> </tr> {% endfor %} </tbody> -</table>
\ No newline at end of file +</table> diff --git a/debug_toolbar/templates/debug_toolbar/panels/templates.html b/debug_toolbar/templates/debug_toolbar/panels/templates.html index 575d507..a92657b 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/templates.html +++ b/debug_toolbar/templates/debug_toolbar/panels/templates.html @@ -1,4 +1,4 @@ -<h3>Template path(s):</h3> +<h3>Template path{{ template_dirs|length|pluralize }}:</h3> {% if template_dirs %} <ol> {% for template in template_dirs %} @@ -6,9 +6,9 @@ {% endfor %} </ol> {% else %} - None + <p>None</p> {% endif %} -<h3>Templates Used</h3> +<h3>Template{{ templates|length|pluralize }}</h3> {% if templates %} <dl> {% for template in templates %} @@ -21,5 +21,19 @@ {% endfor %} </dl> {% else %} - None + <p>None</p> +{% endif %} +<h3>Context processor{{ context_processors|length|pluralize }}</h3> +{% if context_processors %} +<dl> +{% for key, value in context_processors.iteritems %} + <dt><strong>{{ key|escape }}</strong></dt> + <dd> + <div class="djTemplateShowContextDiv"><a class="djTemplateShowContext">Toggle Context</a></div> + <div class="djTemplateHideContextDiv" style="display:none;"><pre>{{ value|escape }}</pre></div> + </dd> +{% endfor %} +</dl> +{% else %} + <p>None</p> {% endif %} diff --git a/debug_toolbar/toolbar/loader.py b/debug_toolbar/toolbar/loader.py index f45cbda..0ac9096 100644 --- a/debug_toolbar/toolbar/loader.py +++ b/debug_toolbar/toolbar/loader.py @@ -8,8 +8,6 @@ class DebugToolbar(object): def __init__(self, request): self.request = request self.panels = [] - self.panel_list = [] - self.content_list = [] # Override this tuple by copying to settings.py as `DEBUG_TOOLBAR_PANELS` self.default_panels = ( 'debug_toolbar.panels.version.VersionDebugPanel', @@ -20,7 +18,8 @@ class DebugToolbar(object): 'debug_toolbar.panels.cache.CacheDebugPanel', 'debug_toolbar.panels.template.TemplateDebugPanel', ) - + self.load_panels() + def load_panels(self): """ Populate debug panels @@ -48,8 +47,9 @@ class DebugToolbar(object): raise exceptions.ImproperlyConfigured, 'Toolbar Panel module "%s" does not define a "%s" class' % (panel_module, panel_classname) try: - panel_instance = panel_class(self.request) + panel_instance = panel_class() except: + print panel_class raise # Bubble up problem loading panel self.panels.append(panel_instance) diff --git a/debug_toolbar/views.py b/debug_toolbar/views.py index 97739e6..b67a70b 100644 --- a/debug_toolbar/views.py +++ b/debug_toolbar/views.py @@ -8,8 +8,10 @@ import os import django.views.static from django.conf import settings from django.db import connection +from django.http import HttpResponse from django.shortcuts import render_to_response from django.utils import simplejson +from django.utils.hashcompat import sha_constructor def debug_media(request, path): root = getattr(settings, 'DEBUG_TOOLBAR_MEDIA_ROOT', None) @@ -21,16 +23,21 @@ def debug_media(request, path): def sql_explain(request): """ Returns the output of the SQL EXPLAIN on the given query. - + Expected GET variables: - sql: urlencoded sql with position arguments + sql: urlencoded sql with positional arguments params: JSON encoded parameter values time: time for SQL to execute passed in from toolbar just for redisplay + hash: the hash of (secret + sql + params) for tamper checking """ from debug_toolbar.panels.sql import reformat_sql sql = request.GET.get('sql', '') + params = request.GET.get('params', '') + hash = sha_constructor(settings.SECRET_KEY + sql + params).hexdigest() + if hash != request.GET.get('hash', ''): + return HttpResponse('<h3>Tamper alert</h3>') # SQL Tampering alert if sql.lower().startswith('select'): - params = simplejson.loads(request.GET.get('params', '')) + params = simplejson.loads(params) cursor = connection.cursor() cursor.execute("EXPLAIN %s" % (sql,), params) headers = [d[0] for d in cursor.description] |
