diff --git a/debug_toolbar/panels/__init__.py b/debug_toolbar/panels/__init__.py
index 8be87e152..eb3b21fff 100644
--- a/debug_toolbar/panels/__init__.py
+++ b/debug_toolbar/panels/__init__.py
@@ -2,7 +2,6 @@
from django.template.defaultfilters import slugify
from django.template.loader import render_to_string
-from debug_toolbar.middleware import DebugToolbarMiddleware
class DebugPanel(object):
@@ -11,14 +10,15 @@ class DebugPanel(object):
"""
# name = 'Base'
# template = 'debug_toolbar/panels/base.html'
- has_content = False # If content returns something, set to true in subclass
+ has_content = False # If content returns something, set to True in subclass
# We'll maintain a local context instance so we can expose our template
# context variables to panels which need them:
context = {}
# Panel methods
- def __init__(self, context={}):
+ def __init__(self, toolbar, context={}):
+ self.toolbar = toolbar
self.context.update(context)
self.slug = slugify(self.name)
@@ -44,16 +44,14 @@ def content(self):
return render_to_string(self.template, context)
def record_stats(self, stats):
- toolbar = DebugToolbarMiddleware.get_current()
- panel_stats = toolbar.stats.get(self.slug)
+ panel_stats = self.toolbar.stats.get(self.slug)
if panel_stats:
panel_stats.update(stats)
else:
- toolbar.stats[self.slug] = stats
+ self.toolbar.stats[self.slug] = stats
def get_stats(self):
- toolbar = DebugToolbarMiddleware.get_current()
- return toolbar.stats.get(self.slug, {})
+ return self.toolbar.stats.get(self.slug, {})
# Standard middleware methods
diff --git a/debug_toolbar/static/debug_toolbar/js/toolbar.js b/debug_toolbar/static/debug_toolbar/js/toolbar.js
index 094c3ac95..0b7d1ecae 100644
--- a/debug_toolbar/static/debug_toolbar/js/toolbar.js
+++ b/debug_toolbar/static/debug_toolbar/js/toolbar.js
@@ -28,6 +28,23 @@
$(this).parent().removeClass('active');
} else {
$('.panelContent').hide(); // Hide any that are already open
+ var inner = current.find('.djDebugPanelContent .scroll').first();
+ if ($(inner).empty()) {
+ var ajax_data = {
+ data: {
+ toolbar_id: $('#djDebug').data('toolbar-id'),
+ panel_id: this.className
+ },
+ type: 'GET',
+ url: $('#djDebug').data('render-panel-url')
+ };
+ $.ajax(ajax_data).done(function(data){
+ inner.html(data);
+ }).fail(function(xhr){
+ var message = '
+
-
- {{ panel.content|safe }}
-
+
+
{% endif %}
diff --git a/debug_toolbar/toolbar/loader.py b/debug_toolbar/toolbar/loader.py
index c8630a981..de4aa9335 100644
--- a/debug_toolbar/toolbar/loader.py
+++ b/debug_toolbar/toolbar/loader.py
@@ -45,8 +45,7 @@ def load_panels(self):
"""
global panel_classes
for panel_class in panel_classes:
- panel_instance = panel_class(context=self.template_context)
-
+ panel_instance = panel_class(self, context=self.template_context)
self._panels[panel_class] = panel_instance
def render_toolbar(self):
@@ -56,8 +55,8 @@ def render_toolbar(self):
context = self.template_context.copy()
context.update({
'panels': self.panels,
+ 'toolbar_id': save_toolbar(self),
})
-
return render_to_string('debug_toolbar/base.html', context)
@@ -101,3 +100,23 @@ def load_panel_classes():
'Toolbar Panel module "%s" does not define a "%s" class' %
(panel_module, panel_classname))
panel_classes.append(panel_class)
+
+
+toolbar_counter = 0
+toolbar_maxsize = 10 # keep data for the last 10 requests
+toolbar_results = SortedDict()
+
+
+def save_toolbar(toolbar):
+ global toolbar_counter, toolbar_results
+ toolbar_counter += 1
+ toolbar_results[toolbar_counter] = toolbar
+ for _ in range(len(toolbar_results) - toolbar_maxsize):
+ # When we drop support for Python 2.6 and switch to
+ # collections.OrderedDict, use popitem(last=False).
+ del toolbar_results[toolbar_results.keyOrder[0]]
+ return toolbar_counter
+
+
+def get_saved_toolbar(toolbar_id):
+ return toolbar_results.get(toolbar_id)
diff --git a/debug_toolbar/urls.py b/debug_toolbar/urls.py
index 1a6daf817..05ef24560 100644
--- a/debug_toolbar/urls.py
+++ b/debug_toolbar/urls.py
@@ -12,6 +12,7 @@
_PREFIX = '__debug__'
urlpatterns = patterns('debug_toolbar.views', # noqa
+ url(r'^%s/render_panel/$' % _PREFIX, 'render_panel', name='render_panel'),
url(r'^%s/sql_select/$' % _PREFIX, 'sql_select', name='sql_select'),
url(r'^%s/sql_explain/$' % _PREFIX, 'sql_explain', name='sql_explain'),
url(r'^%s/sql_profile/$' % _PREFIX, 'sql_profile', name='sql_profile'),
diff --git a/debug_toolbar/views.py b/debug_toolbar/views.py
index 8b3e63cd0..21f474713 100644
--- a/debug_toolbar/views.py
+++ b/debug_toolbar/views.py
@@ -6,11 +6,30 @@
from __future__ import unicode_literals
-from django.http import HttpResponseBadRequest
+from django.http import HttpResponse, HttpResponseBadRequest
from django.shortcuts import render
+from django.utils.html import escape
+from django.utils.translation import ugettext as _
from django.views.decorators.csrf import csrf_exempt
from debug_toolbar.forms import SQLSelectForm
+from debug_toolbar.toolbar.loader import get_saved_toolbar
+
+
+def render_panel(request):
+ """Render the contents of a panel"""
+ toolbar = get_saved_toolbar(int(request.GET['toolbar_id']))
+ if toolbar is None:
+ content = _("Data for this panel isn't available anymore. "
+ "Please reload the page and retry.")
+ content = "
%s
" % escape(content)
+ else:
+ panel_id = request.GET['panel_id']
+ for panel in toolbar.panels:
+ if panel.dom_id() == panel_id:
+ content = panel.content()
+ break
+ return HttpResponse(content)
@csrf_exempt
diff --git a/tests/__init__.py b/tests/__init__.py
index 3b83e4cc4..47fba24df 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,6 +1,8 @@
# Refresh the debug toolbar's configuration when overriding settings.
from debug_toolbar.utils.settings import CONFIG, CONFIG_DEFAULTS
+from debug_toolbar.toolbar.loader import load_panel_classes, panel_classes # noqa
+
from django.dispatch import receiver
from django.test.signals import setting_changed
@@ -10,3 +12,11 @@ def update_toolbar_config(**kwargs):
if kwargs['setting'] == 'DEBUG_TOOLBAR_CONFIG':
CONFIG.update(CONFIG_DEFAULTS)
CONFIG.update(kwargs['value'] or {})
+
+
+@receiver(setting_changed)
+def update_toolbar_panels(**kwargs):
+ if kwargs['setting'] == 'DEBUG_TOOLBAR_PANELS':
+ global panel_classes
+ panel_classes = [] # noqa
+ load_panel_classes()
diff --git a/tests/panels/test_logger.py b/tests/panels/test_logger.py
index 477d10039..9a3ac37d8 100644
--- a/tests/panels/test_logger.py
+++ b/tests/panels/test_logger.py
@@ -10,25 +10,26 @@
class LoggingPanelTestCase(BaseTestCase):
+ def setUp(self):
+ super(LoggingPanelTestCase, self).setUp()
+ self.panel = self.toolbar.get_panel(LoggingPanel)
+ self.logger = logging.getLogger(__name__)
+
def test_happy_case(self):
- logger = logging.getLogger(__name__)
- logger.info('Nothing to see here, move along!')
+ self.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.panel.process_response(self.request, self.response)
+ records = self.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')
+ self.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.panel.process_response(self.request, self.response)
+ records = self.panel.get_stats()['records']
self.assertEqual(1, len(records))
self.assertEqual('There are 5 apples',
@@ -39,14 +40,11 @@ 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())
+ self.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.panel.process_response(self.request, self.response)
+ records = self.panel.get_stats()['records']
self.assertEqual(1, len(records))
self.assertEqual(MESSAGE_IF_STRING_REPRESENTATION_INVALID,
diff --git a/tests/panels/test_profiling.py b/tests/panels/test_profiling.py
new file mode 100644
index 000000000..6a7823ec7
--- /dev/null
+++ b/tests/panels/test_profiling.py
@@ -0,0 +1,29 @@
+from __future__ import unicode_literals
+
+from django.contrib.auth.models import User
+from django.db import IntegrityError, transaction
+from django.test import TestCase
+from django.test.utils import override_settings
+
+
+@override_settings(DEBUG=True,
+ DEBUG_TOOLBAR_PANELS=['debug_toolbar.panels.profiling.ProfilingDebugPanel'])
+class ProfilingPanelIntegrationTestCase(TestCase):
+
+ urls = 'tests.urls'
+
+ def test_view_executed_once(self):
+
+ self.assertEqual(User.objects.count(), 0)
+
+ response = self.client.get('/new_user/')
+ self.assertContains(response, 'Profiling')
+ self.assertEqual(User.objects.count(), 1)
+
+ with self.assertRaises(IntegrityError):
+ if hasattr(transaction, 'atomic'): # Django >= 1.6
+ with transaction.atomic():
+ response = self.client.get('/new_user/')
+ else:
+ response = self.client.get('/new_user/')
+ self.assertEqual(User.objects.count(), 1)
diff --git a/tests/panels/test_request_vars.py b/tests/panels/test_request_vars.py
new file mode 100644
index 000000000..536610515
--- /dev/null
+++ b/tests/panels/test_request_vars.py
@@ -0,0 +1,35 @@
+# coding: utf-8
+
+from __future__ import unicode_literals
+
+from django.utils import six
+
+from debug_toolbar.panels.request_vars import RequestVarsDebugPanel
+
+from ..base import BaseTestCase
+
+
+class RequestVarsDebugPanelTestCase(BaseTestCase):
+
+ def setUp(self):
+ super(RequestVarsDebugPanelTestCase, self).setUp()
+ self.panel = self.toolbar.get_panel(RequestVarsDebugPanel)
+
+ def test_non_ascii_session(self):
+ self.request.session = {'où': 'où'}
+ if not six.PY3:
+ self.request.session['là'.encode('utf-8')] = 'là'.encode('utf-8')
+ self.panel.process_request(self.request)
+ self.panel.process_response(self.request, self.response)
+ content = self.panel.content()
+ if six.PY3:
+ self.assertIn('où', content)
+ else:
+ self.assertIn('o\\xf9', content)
+ self.assertIn('l\\xc3\\xa0', content)
+
+ def test_object_with_non_ascii_repr_in_request_vars(self):
+ self.request.path = '/non_ascii_request/'
+ self.panel.process_request(self.request)
+ self.panel.process_response(self.request, self.response)
+ self.assertIn('nôt åscíì', self.panel.content())
diff --git a/tests/panels/test_sql.py b/tests/panels/test_sql.py
index 10846f5f3..a1de0ea99 100644
--- a/tests/panels/test_sql.py
+++ b/tests/panels/test_sql.py
@@ -14,15 +14,18 @@
class SQLPanelTestCase(BaseTestCase):
+ def setUp(self):
+ super(SQLPanelTestCase, self).setUp()
+ self.panel = self.toolbar.get_panel(SQLDebugPanel)
+
def test_recording(self):
- panel = self.toolbar.get_panel(SQLDebugPanel)
- self.assertEqual(len(panel._queries), 0)
+ self.assertEqual(len(self.panel._queries), 0)
list(User.objects.all())
# ensure query was logged
- self.assertEqual(len(panel._queries), 1)
- query = panel._queries[0]
+ self.assertEqual(len(self.panel._queries), 1)
+ query = self.panel._queries[0]
self.assertEqual(query[0], 'default')
self.assertTrue('sql' in query[1])
self.assertTrue('duration' in query[1])
@@ -32,16 +35,24 @@ def test_recording(self):
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)
+ self.assertEqual(len(self.panel._queries), 0)
+
+ # non-ASCII text query
+ list(User.objects.extra(where=["username = 'apéro'"]))
+ self.assertEqual(len(self.panel._queries), 1)
+
+ # non-ASCII text parameters
+ list(User.objects.filter(username='thé'))
+ self.assertEqual(len(self.panel._queries), 2)
+
+ # non-ASCII bytes parameters
+ list(User.objects.filter(username='café'.encode('utf-8')))
+ self.assertEqual(len(self.panel._queries), 3)
- # non-ASCII query
- list(User.objects.extra(where=["username = 'café'"]))
- self.assertEqual(len(panel._queries), 1)
+ self.panel.process_response(self.request, self.response)
- # non-ASCII parameters
- list(User.objects.filter(username='café'))
- self.assertEqual(len(panel._queries), 2)
+ # ensure the panel renders correctly
+ self.assertIn('café', self.panel.content())
@unittest.skipUnless(connection.vendor == 'postgresql',
'Test valid only on PostgreSQL')
@@ -55,15 +66,14 @@ def test_erroneous_query(self):
self.assertTrue('erroneous query' in str(e))
def test_disable_stacktraces(self):
- panel = self.toolbar.get_panel(SQLDebugPanel)
- self.assertEqual(len(panel._queries), 0)
+ self.assertEqual(len(self.panel._queries), 0)
with self.settings(DEBUG_TOOLBAR_CONFIG={'ENABLE_STACKTRACES': False}):
list(User.objects.all())
# ensure query was logged
- self.assertEqual(len(panel._queries), 1)
- query = panel._queries[0]
+ self.assertEqual(len(self.panel._queries), 1)
+ query = self.panel._queries[0]
self.assertEqual(query[0], 'default')
self.assertTrue('sql' in query[1])
self.assertTrue('duration' in query[1])
diff --git a/tests/panels/test_template.py b/tests/panels/test_template.py
index 4e30a7354..c98521e6a 100644
--- a/tests/panels/test_template.py
+++ b/tests/panels/test_template.py
@@ -1,3 +1,5 @@
+# coding: utf-8
+
from __future__ import unicode_literals
import django
@@ -8,13 +10,16 @@
from debug_toolbar.panels.sql import SQLDebugPanel
from ..base import BaseTestCase
+from ..models import NonAsciiRepr
class TemplateDebugPanelTestCase(BaseTestCase):
+ def setUp(self):
+ super(TemplateDebugPanelTestCase, self).setUp()
+ self.panel = self.toolbar.get_panel(TemplateDebugPanel)
+
def test_queryset_hook(self):
- template_panel = self.toolbar.get_panel(TemplateDebugPanel)
- sql_panel = self.toolbar.get_panel(SQLDebugPanel)
t = Template("No context variables here!")
c = Context({
'queryset': User.objects.all(),
@@ -23,9 +28,20 @@ def test_queryset_hook(self):
}
})
t.render(c)
+
# ensure the query was NOT logged
+ sql_panel = self.toolbar.get_panel(SQLDebugPanel)
self.assertEqual(len(sql_panel._queries), 0)
+
base_ctx_idx = 1 if django.VERSION[:2] >= (1, 5) else 0
- ctx = template_panel.templates[0]['context'][base_ctx_idx]
+ ctx = self.panel.templates[0]['context'][base_ctx_idx]
self.assertIn('<
>', ctx)
self.assertIn('<>', ctx)
+
+ def test_object_with_non_ascii_repr_in_context(self):
+ self.panel.process_request(self.request)
+ t = Template("{{ object }}")
+ c = Context({'object': NonAsciiRepr()})
+ t.render(c)
+ self.panel.process_response(self.request, self.response)
+ self.assertIn('nôt åscíì', self.panel.content())
diff --git a/tests/test_integration.py b/tests/test_integration.py
index 41206fcb8..4bb0d38d7 100644
--- a/tests/test_integration.py
+++ b/tests/test_integration.py
@@ -4,8 +4,6 @@
from xml.etree import ElementTree as ET
-from django.contrib.auth.models import User
-from django.db import IntegrityError, transaction
from django.test import TestCase, RequestFactory
from django.test.utils import override_settings
from django.utils import six
@@ -139,47 +137,6 @@ def test_non_utf8_charset(self):
self.assertContains(response, 'LÀTÍN') # template
self.assertContains(response, 'djDebug') # toolbar
- def test_non_ascii_bytes_in_db_params(self):
- response = self.client.get('/non_ascii_bytes_in_db_params/')
- if six.PY3:
- self.assertContains(response, 'djàngó')
- else:
- self.assertContains(response, 'dj\\xe0ng\\xf3')
-
- def test_non_ascii_session(self):
- response = self.client.get('/set_session/')
- if six.PY3:
- self.assertContains(response, 'où')
- else:
- self.assertContains(response, 'o\\xf9')
- self.assertContains(response, 'l\\xc3\\xa0')
-
- def test_object_with_non_ascii_repr_in_context(self):
- response = self.client.get('/non_ascii_context/')
- self.assertContains(response, 'nôt åscíì')
-
- def test_object_with_non_ascii_repr_in_request_vars(self):
- response = self.client.get('/non_ascii_request/')
- self.assertContains(response, 'nôt åscíì')
-
def test_xml_validation(self):
response = self.client.get('/regular/XML/')
ET.fromstring(response.content) # shouldn't raise ParseError
-
- def test_view_executed_once(self):
- with self.settings(
- DEBUG_TOOLBAR_PANELS=['debug_toolbar.panels.profiling.ProfilingDebugPanel']):
-
- self.assertEqual(User.objects.count(), 0)
-
- response = self.client.get('/new_user/')
- self.assertContains(response, 'Profiling')
- self.assertEqual(User.objects.count(), 1)
-
- with self.assertRaises(IntegrityError):
- if hasattr(transaction, 'atomic'): # Django >= 1.6
- with transaction.atomic():
- response = self.client.get('/new_user/')
- else:
- response = self.client.get('/new_user/')
- self.assertEqual(User.objects.count(), 1)
diff --git a/tests/tests.py b/tests/tests.py
index 10e0a9bda..e373d51e4 100644
--- a/tests/tests.py
+++ b/tests/tests.py
@@ -3,6 +3,8 @@
if django.VERSION[:2] < (1, 6): # unittest-style discovery isn't available
from .commands.test_debugsqlshell import * # noqa
from .panels.test_logger import * # noqa
+ from .panels.test_profiling import * # noqa
+ from .panels.test_request_vars import * # noqa
from .panels.test_sql import * # noqa
from .panels.test_template import * # noqa
from .test_integration import * # noqa
diff --git a/tests/urls.py b/tests/urls.py
index 93e77c4f0..977084b2e 100644
--- a/tests/urls.py
+++ b/tests/urls.py
@@ -11,13 +11,10 @@
admin.autodiscover()
urlpatterns = patterns('tests.views', # noqa
- url(r'^set_session/$', 'set_session'),
url(r'^resolving1/(.+)/(.+)/$', 'resolving_view', name='positional-resolving'),
url(r'^resolving2/(?P.+)/(?P.+)/$', 'resolving_view'),
url(r'^resolving3/(.+)/$', 'resolving_view', {'arg2': 'default'}),
url(r'^regular/(?P.*)/$', 'regular_view'),
- url(r'^non_ascii_context/$', 'non_ascii_context'),
- url(r'^non_ascii_bytes_in_db_params/$', 'new_user', {'username': 'djàngó'.encode('utf-8')}),
url(r'^non_ascii_request/$', 'regular_view', {'title': NonAsciiRepr()}),
url(r'^new_user/$', 'new_user'),
url(r'^execute_sql/$', 'execute_sql'),
diff --git a/tests/views.py b/tests/views.py
index 01c49714b..d7ff16703 100644
--- a/tests/views.py
+++ b/tests/views.py
@@ -5,9 +5,6 @@
from django.contrib.auth.models import User
from django.http import HttpResponse
from django.shortcuts import render
-from django.utils import six
-
-from .models import NonAsciiRepr
def execute_sql(request):
@@ -15,10 +12,6 @@ def execute_sql(request):
return HttpResponse()
-def non_ascii_context(request):
- return render(request, 'basic.html', {'title': NonAsciiRepr()})
-
-
def regular_view(request, title):
return render(request, 'basic.html', {'title': title})
@@ -31,10 +24,3 @@ def new_user(request, username='joe'):
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')