aboutsummaryrefslogtreecommitdiffstats
path: root/debug_toolbar/panels
diff options
context:
space:
mode:
Diffstat (limited to 'debug_toolbar/panels')
-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/logger.py70
-rw-r--r--debug_toolbar/panels/request_vars.py3
-rw-r--r--debug_toolbar/panels/sql.py52
-rw-r--r--debug_toolbar/panels/template.py42
-rw-r--r--debug_toolbar/panels/timer.py10
8 files changed, 170 insertions, 37 deletions
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/logger.py b/debug_toolbar/panels/logger.py
new file mode 100644
index 0000000..cb88148
--- /dev/null
+++ b/debug_toolbar/panels/logger.py
@@ -0,0 +1,70 @@
+import datetime
+import logging
+try:
+ import threading
+except ImportError:
+ threading = None
+from django.template.loader import render_to_string
+from debug_toolbar.panels import DebugPanel
+
+class ThreadTrackingHandler(logging.Handler):
+ def __init__(self):
+ if threading is None:
+ raise NotImplementedError("threading module is not available, \
+ the logging panel cannot be used without it")
+ logging.Handler.__init__(self)
+ self.records = {} # a dictionary that maps threads to log records
+
+ def emit(self, record):
+ self.get_records().append(record)
+
+ def get_records(self, thread=None):
+ """
+ Returns a list of records for the provided thread, of if none is provided,
+ returns a list for the current thread.
+ """
+ if thread is None:
+ thread = threading.currentThread()
+ if thread not in self.records:
+ self.records[thread] = []
+ return self.records[thread]
+
+ def clear_records(self, thread=None):
+ if thread is None:
+ thread = threading.currentThread()
+ if thread in self.records:
+ del self.records[thread]
+
+handler = ThreadTrackingHandler()
+logging.root.setLevel(logging.NOTSET)
+logging.root.addHandler(handler)
+
+class LoggingPanel(DebugPanel):
+ name = 'Logging'
+ has_content = True
+
+ def process_request(self, request):
+ handler.clear_records()
+
+ def get_and_delete(self):
+ records = handler.get_records()
+ handler.clear_records()
+ return records
+
+ def title(self):
+ return "Logging (%s message%s)" % (len(handler.get_records()), (len(handler.get_records()) == 1) and '' or 's')
+
+ def url(self):
+ return ''
+
+ def content(self):
+ records = []
+ for record in self.get_and_delete():
+ records.append({
+ 'message': record.getMessage(),
+ 'time': datetime.datetime.fromtimestamp(record.created),
+ 'level': record.levelname,
+ 'file': record.pathname,
+ 'line': record.lineno,
+ })
+ return render_to_string('debug_toolbar/panels/logger.html', {'records': records})
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 f23e317..22d65a7 100644
--- a/debug_toolbar/panels/sql.py
+++ b/debug_toolbar/panels/sql.py
@@ -1,8 +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):
"""
@@ -15,37 +18,40 @@ 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': 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 _reformat_sql(self, sql):
- sql = sql.replace('`,`', '`, `')
- sql = sql.replace('` FROM `', '` \n FROM `')
- sql = sql.replace('` WHERE ', '` \n WHERE ')
- sql = sql.replace(' ORDER BY ', ' \n ORDER BY ')
- return sql
-
def title(self):
self._sql_time = sum(map(lambda q: float(q['time']) * 1000, connection.queries))
- return '%d SQL Queries (%.2fms)' % (len(connection.queries), self._sql_time)
+ return '%d SQL %s (%.2fms)' % (
+ len(connection.queries),
+ (len(connection.queries) == 1) and 'query' or 'queries',
+ self._sql_time
+ )
def url(self):
return ''
@@ -53,10 +59,28 @@ class SQLDebugPanel(DebugPanel):
def content(self):
sql_queries = connection.queries[self._offset:]
for query in sql_queries:
- query['sql'] = self._reformat_sql(query['sql'])
+ query['sql'] = reformat_sql(query['sql'])
context = {
'queries': sql_queries,
'sql_time': self._sql_time,
}
return render_to_string('debug_toolbar/panels/sql.html', context)
+
+def reformat_sql(sql):
+ sql = sql.replace('`,`', '`, `')
+ sql = sql.replace('SELECT ', 'SELECT\n\t')
+ sql = sql.replace('` FROM ', '`\nFROM\n\t')
+ sql = sql.replace('` WHERE ', '`\nWHERE\n\t')
+ sql = sql.replace('` INNER JOIN ', '`\nINNER JOIN\n\t')
+ sql = sql.replace('` OUTER JOIN ', '`\nOUTER JOIN\n\t')
+ sql = sql.replace(' ORDER BY ', '\nORDER BY\n\t')
+ # Use Pygments to highlight SQL if it's available
+ try:
+ from pygments import highlight
+ from pygments.lexers import SqlLexer
+ from pygments.formatters import HtmlFormatter
+ 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 f1cf65b..022a41f 100644
--- a/debug_toolbar/panels/template.py
+++ b/debug_toolbar/panels/template.py
@@ -1,6 +1,8 @@
+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
@@ -30,13 +32,12 @@ class TemplateDebugPanel(DebugPanel):
name = 'Template'
has_content = True
- def __init__(self, request):
- super(TemplateDebugPanel, self).__init__(request)
- self.templates_used = []
- template_rendered.connect(self._storeRenderedTemplates)
+ def __init__(self):
+ self.templates = []
+ template_rendered.connect(self._storeTemplateInfo)
- def _storeRenderedTemplates(self, sender, **kwargs):
- self.templates_used.append(kwargs['template'])
+ def _storeTemplateInfo(self, sender, **kwargs):
+ self.templates.append(kwargs)
def title(self):
return 'Templates'
@@ -44,13 +45,32 @@ 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):
- templates = [
- (t.name, t.origin and t.origin.name or 'No origin')
- for t in self.templates_used
- ]
+ template_context = []
+ for i, d in enumerate(self.templates):
+ info = {}
+ # Clean up some info about templates
+ t = d.get('template', None)
+ # Skip templates that we are generating through the debug toolbar.
+ if t.name.startswith('debug_toolbar/'):
+ continue
+ if t.origin and t.origin.name:
+ t.origin_name = t.origin.name
+ else:
+ t.origin_name = 'No origin'
+ info['template'] = t
+ # Clean up context for better readability
+ c = d.get('context', None)
+ info['context'] = '\n'.join([pformat(_d) for _d in c.dicts])
+ template_context.append(info)
context = {
- 'templates': templates,
+ '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 ''