aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Gaynor2008-09-27 14:18:19 -0400
committerAlex Gaynor2008-09-27 14:18:19 -0400
commit225965ceb490d55207733aa297fd0d8a9c1cbfec (patch)
treef92829b9ac85e860aabf14235441f6cb793e0595
parent9a7fe9e528c72d39efb1954275e1e588b2bb541c (diff)
parent8d32dc2eb8a5284aa668d5b8f4b903cdf67a3e30 (diff)
downloaddjango-debug-toolbar-225965ceb490d55207733aa297fd0d8a9c1cbfec.tar.bz2
fixed merge conflicts
-rw-r--r--README.rst19
-rw-r--r--debug_toolbar/media/toolbar.css158
-rw-r--r--debug_toolbar/media/toolbar.js2
-rw-r--r--debug_toolbar/middleware.py9
-rw-r--r--debug_toolbar/panels/__init__.py20
-rw-r--r--debug_toolbar/panels/cache.py3
-rw-r--r--debug_toolbar/panels/headers.py7
-rw-r--r--debug_toolbar/panels/request_vars.py3
-rw-r--r--debug_toolbar/panels/sql.py26
-rw-r--r--debug_toolbar/panels/template.py13
-rw-r--r--debug_toolbar/panels/timer.py10
-rw-r--r--debug_toolbar/templates/debug_toolbar/panels/cache.html4
-rw-r--r--debug_toolbar/templates/debug_toolbar/panels/sql.html10
-rw-r--r--debug_toolbar/templates/debug_toolbar/panels/templates.html22
-rw-r--r--debug_toolbar/toolbar/loader.py8
-rw-r--r--debug_toolbar/views.py13
16 files changed, 263 insertions, 64 deletions
diff --git a/README.rst b/README.rst
index 2bd8b87..c5e4c5f 100644
--- a/README.rst
+++ b/README.rst
@@ -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 }}&params={{ 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 }}&params={{ 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]