aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.rst6
-rw-r--r--debug_toolbar/forms.py6
-rw-r--r--debug_toolbar/panels/logger.py9
-rw-r--r--debug_toolbar/panels/template.py3
-rw-r--r--debug_toolbar/templates/debug_toolbar/base.html6
-rw-r--r--debug_toolbar/templates/debug_toolbar/panels/sql.html6
-rw-r--r--debug_toolbar/utils/__init__.py6
-rw-r--r--debug_toolbar/utils/sql.py2
-rw-r--r--example/django-debug-toolbar.pngbin0 -> 96122 bytes
-rw-r--r--tests/templates/basic.html7
-rw-r--r--tests/tests.py101
-rw-r--r--tests/urls.py2
-rw-r--r--tests/views.py23
13 files changed, 149 insertions, 28 deletions
diff --git a/README.rst b/README.rst
index 0f818dc..9a4bfef 100644
--- a/README.rst
+++ b/README.rst
@@ -10,6 +10,12 @@ The Django Debug Toolbar is a configurable set of panels that display various
debug information about the current request/response and when clicked, display
more details about the panel's content.
+Here's a screenshot:
+
+.. image:: https://raw.github.com/django-debug-toolbar/django-debug-toolbar/master/example/django-debug-toolbar.png
+ :width: 908
+ :height: 557
+
Currently, the following panels have been written and are working:
- Django version
diff --git a/debug_toolbar/forms.py b/debug_toolbar/forms.py
index 60d339c..cc05f30 100644
--- a/debug_toolbar/forms.py
+++ b/debug_toolbar/forms.py
@@ -6,7 +6,7 @@ import hashlib
from django import forms
from django.conf import settings
from django.db import connections
-from django.utils.encoding import smart_bytes
+from django.utils.encoding import force_text
from django.utils.functional import cached_property
from django.core.exceptions import ValidationError
@@ -76,8 +76,8 @@ class SQLSelectForm(forms.Form):
return reformat_sql(self.cursor.db.ops.last_executed_query(self.cursor, sql, params))
def make_hash(self, data):
- params = settings.SECRET_KEY + data['sql'] + data['params']
- return hashlib.sha1(smart_bytes(params)).hexdigest()
+ params = force_text(settings.SECRET_KEY) + data['sql'] + data['params']
+ return hashlib.sha1(params.encode('utf-8')).hexdigest()
@property
def connection(self):
diff --git a/debug_toolbar/panels/logger.py b/debug_toolbar/panels/logger.py
index c6c1bd1..2c749ce 100644
--- a/debug_toolbar/panels/logger.py
+++ b/debug_toolbar/panels/logger.py
@@ -9,6 +9,8 @@ except ImportError:
from django.utils.translation import ungettext, ugettext_lazy as _
from debug_toolbar.panels import DebugPanel
+MESSAGE_IF_STRING_REPRESENTATION_INVALID = '[Could not get log message]'
+
class LogCollector(object):
def __init__(self):
@@ -49,8 +51,13 @@ class ThreadTrackingHandler(logging.Handler):
self.collector = collector
def emit(self, record):
+ try:
+ message = record.getMessage()
+ except Exception:
+ message = MESSAGE_IF_STRING_REPRESENTATION_INVALID
+
record = {
- 'message': record.getMessage(),
+ 'message': message,
'time': datetime.datetime.fromtimestamp(record.created),
'level': record.levelname,
'file': record.pathname,
diff --git a/debug_toolbar/panels/template.py b/debug_toolbar/panels/template.py
index 8ecf5d6..f2f813c 100644
--- a/debug_toolbar/panels/template.py
+++ b/debug_toolbar/panels/template.py
@@ -7,6 +7,7 @@ from django import http
from django.conf import settings
from django.template.context import get_standard_processors
from django.test.signals import template_rendered
+from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _
from django.db.models.query import QuerySet, RawQuerySet
from debug_toolbar.panels import DebugPanel
@@ -92,7 +93,7 @@ class TemplateDebugPanel(DebugPanel):
context_list.append(pformat(temp_layer))
except UnicodeEncodeError:
pass
- kwargs['context'] = context_list
+ kwargs['context'] = [force_text(item) for item in context_list]
self.templates.append(kwargs)
def nav_title(self):
diff --git a/debug_toolbar/templates/debug_toolbar/base.html b/debug_toolbar/templates/debug_toolbar/base.html
index a790bd1..7b42c84 100644
--- a/debug_toolbar/templates/debug_toolbar/base.html
+++ b/debug_toolbar/templates/debug_toolbar/base.html
@@ -2,13 +2,13 @@
<style type="text/css">
@media print { #djDebug {display:none;}}
</style>
-<link rel="stylesheet" href="{{ STATIC_URL }}debug_toolbar/css/toolbar.min.css" type="text/css">
+<link rel="stylesheet" href="{{ STATIC_URL }}debug_toolbar/css/toolbar.min.css" type="text/css" />
<script type="text/javascript" src="{{ STATIC_URL }}debug_toolbar/js/toolbar.min.js"></script>
<div id="djDebug" style="display:none;" dir="ltr">
<div style="display:none;" id="djDebugToolbar">
<ul id="djDebugPanelList">
{% if panels %}
- <li><a id="djHideToolBarButton" href="#" title="{% trans "Hide Toolbar" %}">{% trans "Hide" %} &raquo;</a></li>
+ <li><a id="djHideToolBarButton" href="#" title="{% trans "Hide Toolbar" %}">{% trans "Hide" %} &#187;</a></li>
{% else %}
<li id="djDebugButton">DEBUG</li>
{% endif %}
@@ -33,7 +33,7 @@
</ul>
</div>
<div style="display:none;" id="djDebugToolbarHandle">
- <a title="{% trans "Show Toolbar" %}" id="djShowToolBarButton" href="#">&laquo;</a>
+ <a title="{% trans "Show Toolbar" %}" id="djShowToolBarButton" href="#">&#171;</a>
</div>
{% for panel in panels %}
{% if panel.has_content %}
diff --git a/debug_toolbar/templates/debug_toolbar/panels/sql.html b/debug_toolbar/templates/debug_toolbar/panels/sql.html
index 36201fc..064413c 100644
--- a/debug_toolbar/templates/debug_toolbar/panels/sql.html
+++ b/debug_toolbar/templates/debug_toolbar/panels/sql.html
@@ -4,7 +4,7 @@
<ul class="stats">
{% for alias, info in databases %}
<li>
- <strong class="label"><span style="background-color: rgb({{ info.rgb_color|join:", " }})" class="color">&nbsp;</span> {{ alias }}</strong>
+ <strong class="label"><span style="background-color: rgb({{ info.rgb_color|join:", " }})" class="color">&#160;</span> {{ alias }}</strong>
<span class="info">{{ info.time_spent|floatformat:"2" }} ms ({% blocktrans count info.num_queries as num %}{{ num }} query{% plural %}{{ num }} queries{% endblocktrans %})</span>
</li>
{% endfor %}
@@ -15,7 +15,7 @@
<table>
<thead>
<tr>
- <th class="color">&nbsp;</th>
+ <th class="color">&#160;</th>
<th class="query" colspan="2">{% trans 'Query' %}</th>
<th class="timeline">{% trans 'Timeline' %}</th>
<th class="time">{% trans 'Time (ms)' %}</th>
@@ -25,7 +25,7 @@
<tbody>
{% for query in queries %}
<tr class="djDebugHoverable {% cycle 'djDebugOdd' 'djDebugEven' %}{% if query.is_slow %} djDebugRowWarning{% endif %}{% if query.starts_trans %} djDebugStartTransaction{% endif %}{% if query.ends_trans %} djDebugEndTransaction{% endif %}{% if query.in_trans %} djDebugInTransaction{% endif %}" id="sqlMain_{{ forloop.counter }}">
- <td class="color"><span style="background-color: rgb({{ query.rgb_color|join:", " }});">&nbsp;</span></td>
+ <td class="color"><span style="background-color: rgb({{ query.rgb_color|join:", " }});">&#160;</span></td>
<td class="toggle">
<a class="djToggleSwitch" data-toggle-name="sqlMain" data-toggle-id="{{ forloop.counter }}" data-toggle-open="+" data-toggle-close="-" href="javascript:void(0)">+</a>
</td>
diff --git a/debug_toolbar/utils/__init__.py b/debug_toolbar/utils/__init__.py
index 297c901..75f3024 100644
--- a/debug_toolbar/utils/__init__.py
+++ b/debug_toolbar/utils/__init__.py
@@ -7,6 +7,7 @@ import sys
from django.conf import settings
from django.views.debug import linebreak_iter
+from django.utils.encoding import force_text
from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.utils import six
@@ -39,10 +40,7 @@ def tidy_stacktrace(stack):
continue
if socketserver_path in s_path:
continue
- if not text:
- text = ''
- else:
- text = (''.join(text)).strip()
+ text = (''.join(force_text(t) for t in text)).strip() if text else ''
trace.append((path, line_no, func_name, text))
return trace
diff --git a/debug_toolbar/utils/sql.py b/debug_toolbar/utils/sql.py
index 4e0d70d..2810699 100644
--- a/debug_toolbar/utils/sql.py
+++ b/debug_toolbar/utils/sql.py
@@ -29,5 +29,5 @@ def reformat_sql(sql):
def swap_fields(sql):
- return re.sub('SELECT</strong> (.*?) <strong>FROM', 'SELECT</strong> <a class="djDebugUncollapsed djDebugToggle" href="#">&bull;&bull;&bull;</a> ' +
+ return re.sub('SELECT</strong> (.*?) <strong>FROM', 'SELECT</strong> <a class="djDebugUncollapsed djDebugToggle" href="#">&#8226;&#8226;&#8226;</a> ' +
'<a class="djDebugCollapsed djDebugToggle" href="#">\g<1></a> <strong>FROM', sql)
diff --git a/example/django-debug-toolbar.png b/example/django-debug-toolbar.png
new file mode 100644
index 0000000..bd097c2
--- /dev/null
+++ b/example/django-debug-toolbar.png
Binary files differ
diff --git a/tests/templates/basic.html b/tests/templates/basic.html
new file mode 100644
index 0000000..a0ce0d4
--- /dev/null
+++ b/tests/templates/basic.html
@@ -0,0 +1,7 @@
+<html>
+ <head>
+ <title>{{ title }}</title>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/tests/tests.py b/tests/tests.py
index 6883428..86622fc 100644
--- a/tests/tests.py
+++ b/tests/tests.py
@@ -2,7 +2,9 @@
from __future__ import unicode_literals
+import logging
import threading
+from xml.etree import ElementTree as ET
import django
from django.conf import settings
@@ -10,11 +12,14 @@ from django.contrib.auth.models import User
from django.db import connection
from django.http import HttpResponse
from django.test import TestCase, RequestFactory
+from django.test.utils import override_settings
from django.template import Template, Context
from django.utils import six
from django.utils import unittest
from debug_toolbar.middleware import DebugToolbarMiddleware
+from debug_toolbar.panels.logger import (LoggingPanel,
+ MESSAGE_IF_STRING_REPRESENTATION_INVALID)
from debug_toolbar.panels.sql import SQLDebugPanel
from debug_toolbar.panels.request_vars import RequestVarsDebugPanel
from debug_toolbar.panels.template import TemplateDebugPanel
@@ -26,6 +31,7 @@ rf = RequestFactory()
class BaseTestCase(TestCase):
+
def setUp(self):
request = rf.get('/')
response = HttpResponse()
@@ -40,6 +46,7 @@ class BaseTestCase(TestCase):
class DebugToolbarTestCase(BaseTestCase):
+
urls = 'tests.urls'
def test_middleware(self):
@@ -163,21 +170,39 @@ class DebugToolbarTestCase(BaseTestCase):
self.assertEqual(stats['view_kwargs'], 'None')
self.assertEqual(stats['view_func'], '<no view>')
+
+@override_settings(DEBUG=True, INTERNAL_IPS=['127.0.0.1'])
+class DebugToolbarIntegrationTestCase(TestCase):
+
+ urls = 'tests.urls'
+
+ @override_settings(DEFAULT_CHARSET='iso-8859-1')
def test_non_utf8_charset(self):
- with self.settings(DEBUG=True,
- DEFAULT_CHARSET='iso-8859-1',
- INTERNAL_IPS=['127.0.0.1']):
+ response = self.client.get('/regular/ASCII/')
+ self.assertContains(response, 'ASCII') # template
+ self.assertContains(response, 'djDebug') # toolbar
+
+ response = self.client.get('/regular/LÀTÍN/')
+ self.assertContains(response, 'LÀTÍN') # template
+ self.assertContains(response, 'djDebug') # toolbar
+
+ def test_non_ascii_session(self):
+ response = self.client.get('/set_session/')
+ self.assertContains(response, 'où')
+ if not six.PY3:
+ self.assertContains(response, 'là')
- response = self.client.get('/regular/ASCII/')
- self.assertContains(response, 'ASCII') # template
- self.assertContains(response, 'djDebug') # toolbar
+ def test_object_with_non_ascii_repr_in_context(self):
+ response = self.client.get('/non_ascii_context/')
+ self.assertContains(response, 'nôt åscíì')
- response = self.client.get('/regular/LÀTÍN/')
- self.assertContains(response, 'LÀTÍN') # template
- self.assertContains(response, 'djDebug') # toolbar
+ def test_xml_validation(self):
+ response = self.client.get('/regular/XML/')
+ ET.fromstring(response.content) # shouldn't raise ParseError
class DebugToolbarNameFromObjectTest(BaseTestCase):
+
def test_func(self):
def x():
return 1
@@ -196,6 +221,7 @@ class DebugToolbarNameFromObjectTest(BaseTestCase):
class SQLPanelTestCase(BaseTestCase):
+
def test_recording(self):
panel = self.toolbar.get_panel(SQLDebugPanel)
self.assertEqual(len(panel._queries), 0)
@@ -213,6 +239,18 @@ class SQLPanelTestCase(BaseTestCase):
# ensure the stacktrace is populated
self.assertTrue(len(query[1]['stacktrace']) > 0)
+ def test_non_ascii_query(self):
+ panel = self.toolbar.get_panel(SQLDebugPanel)
+ self.assertEqual(len(panel._queries), 0)
+
+ # non-ASCII query
+ list(User.objects.extra(where=["username = 'café'"]))
+ self.assertEqual(len(panel._queries), 1)
+
+ # non-ASCII parameters
+ list(User.objects.filter(username='café'))
+ self.assertEqual(len(panel._queries), 2)
+
@unittest.skipUnless(connection.vendor=='postgresql',
'Test valid only on PostgreSQL')
def test_erroneous_query(self):
@@ -246,6 +284,7 @@ class SQLPanelTestCase(BaseTestCase):
class TemplatePanelTestCase(BaseTestCase):
+
def test_queryset_hook(self):
template_panel = self.toolbar.get_panel(TemplateDebugPanel)
sql_panel = self.toolbar.get_panel(SQLDebugPanel)
@@ -263,3 +302,47 @@ class TemplatePanelTestCase(BaseTestCase):
ctx = template_panel.templates[0]['context'][base_ctx_idx]
self.assertIn('<<queryset of auth.User>>', ctx)
self.assertIn('<<triggers database query>>', ctx)
+
+
+class LoggingPanelTestCase(BaseTestCase):
+ def test_happy_case(self):
+ logger = logging.getLogger(__name__)
+ logger.info('Nothing to see here, move along!')
+
+ logging_panel = self.toolbar.get_panel(LoggingPanel)
+ logging_panel.process_response(None, None)
+ records = logging_panel.get_stats()['records']
+
+ self.assertEqual(1, len(records))
+ self.assertEqual('Nothing to see here, move along!',
+ records[0]['message'])
+
+ def test_formatting(self):
+ logger = logging.getLogger(__name__)
+ logger.info('There are %d %s', 5, 'apples')
+
+ logging_panel = self.toolbar.get_panel(LoggingPanel)
+ logging_panel.process_response(None, None)
+ records = logging_panel.get_stats()['records']
+
+ self.assertEqual(1, len(records))
+ self.assertEqual('There are 5 apples',
+ records[0]['message'])
+
+ def test_failing_formatting(self):
+ class BadClass(object):
+ def __str__(self):
+ raise Exception('Please not stringify me!')
+
+ logger = logging.getLogger(__name__)
+
+ # should not raise exception, but fail silently
+ logger.debug('This class is misbehaving: %s', BadClass())
+
+ logging_panel = self.toolbar.get_panel(LoggingPanel)
+ logging_panel.process_response(None, None)
+ records = logging_panel.get_stats()['records']
+
+ self.assertEqual(1, len(records))
+ self.assertEqual(MESSAGE_IF_STRING_REPRESENTATION_INVALID,
+ records[0]['message'])
diff --git a/tests/urls.py b/tests/urls.py
index 4fb51c9..d9ecb8c 100644
--- a/tests/urls.py
+++ b/tests/urls.py
@@ -13,9 +13,11 @@ from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('tests.views',
+ url(r'^set_session/$', 'set_session'),
url(r'^resolving1/(.+)/(.+)/$', 'resolving_view', name='positional-resolving'),
url(r'^resolving2/(?P<arg1>.+)/(?P<arg2>.+)/$', 'resolving_view'),
url(r'^resolving3/(.+)/$', 'resolving_view', { 'arg2' : 'default' }),
url(r'^regular/(?P<title>.*)/$', 'regular_view'),
+ url(r'^non_ascii_context/$', 'non_ascii_context'),
url(r'^execute_sql/$', 'execute_sql'),
)
diff --git a/tests/views.py b/tests/views.py
index f756023..5bafc97 100644
--- a/tests/views.py
+++ b/tests/views.py
@@ -1,7 +1,11 @@
+# coding: utf-8
+
from __future__ import unicode_literals
from django.contrib.auth.models import User
from django.http import HttpResponse
+from django.shortcuts import render
+from django.utils import six
def execute_sql(request):
@@ -9,11 +13,24 @@ def execute_sql(request):
return HttpResponse()
-def regular_view(request, title='Test'):
- content = '<html><head><title>%s</title><body></body></html>' % title
- return HttpResponse(content)
+def non_ascii_context(request):
+ class NonAsciiRepr(object):
+ def __repr__(self):
+ return 'nôt åscíì' if six.PY3 else 'nôt åscíì'.encode('utf-8')
+ return render(request, 'basic.html', {'title': NonAsciiRepr()})
+
+
+def regular_view(request, title):
+ return render(request, 'basic.html', {'title': title})
def resolving_view(request, arg1, arg2):
# see test_url_resolving in tests.py
return HttpResponse()
+
+
+def set_session(request):
+ request.session['où'] = 'où'
+ if not six.PY3:
+ request.session['là'.encode('utf-8')] = 'là'.encode('utf-8')
+ return render(request, 'basic.html')