Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions debug_toolbar/panels/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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)

Expand All @@ -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

Expand Down
17 changes: 17 additions & 0 deletions debug_toolbar/static/debug_toolbar/js/toolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '<div class="djDebugPanelTitle"><a class="djDebugClose djDebugBack" href="">Back</a><h3>'+xhr.status+': '+xhr.statusText+'</h3></div>';
$('#djDebugWindow').html(message).show();
});
}
current.show();
$('#djDebugToolbar li').removeClass('active');
$(this).parent().addClass('active');
Expand Down
13 changes: 7 additions & 6 deletions debug_toolbar/templates/debug_toolbar/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
//]]></script>
<script src="{{ STATIC_URL }}debug_toolbar/js/jquery.cookie.js"></script>
<script src="{{ STATIC_URL }}debug_toolbar/js/toolbar.js"></script>
<div id="djDebug" style="display:none;" dir="ltr" {{ TOOLBAR_ROOT_TAG_ATTRS|safe }}>
<div id="djDebug" style="display:none;" dir="ltr"
data-toolbar-id="{{ toolbar_id }}" data-render-panel-url="/__debug__/render_panel/"
{{ TOOLBAR_ROOT_TAG_ATTRS|safe }}>
<div style="display:none;" id="djDebugToolbar">
<ul id="djDebugPanelList">
{% if panels %}
Expand All @@ -22,7 +24,7 @@
{% if panel.has_content and panel.enabled %}
<a href="{{ panel.url|default:"#" }}" title="{{ panel.title }}" class="{{ panel.dom_id }}">
{% else %}
<div class="contentless{% if panel.disabled %} disabled{% endif %}">
<div class="contentless{% if panel.disabled %} disabled{% endif %}">
{% endif %}
{{ panel.nav_title }}
{% if panel.enabled %}
Expand All @@ -33,7 +35,7 @@
{% if panel.has_content and panel.enabled %}
</a>
{% else %}
</div>
</div>
{% endif %}
</li>
{% endfor %}
Expand All @@ -50,9 +52,8 @@
<h3>{{ panel.title|safe }}</h3>
</div>
<div class="djDebugPanelContent">
<div class="scroll">
{{ panel.content|safe }}
</div>
<div class="scroll">
</div>
</div>
</div>
{% endif %}
Expand Down
25 changes: 22 additions & 3 deletions debug_toolbar/toolbar/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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)


Expand Down Expand Up @@ -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()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No opinion really but what about collections.deque(maxlen=10)? Then as you add more they automatically fall off the other end. You don't really have to keep count.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I first tried using a deque, but I need a dict-like behavior to look up items in get_saved_toolbar.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see it now. Thanks.



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)
1 change: 1 addition & 0 deletions debug_toolbar/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
21 changes: 20 additions & 1 deletion debug_toolbar/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "<p>%s</p>" % 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
Expand Down
10 changes: 10 additions & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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()
30 changes: 14 additions & 16 deletions tests/panels/test_logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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,
Expand Down
29 changes: 29 additions & 0 deletions tests/panels/test_profiling.py
Original file line number Diff line number Diff line change
@@ -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)
35 changes: 35 additions & 0 deletions tests/panels/test_request_vars.py
Original file line number Diff line number Diff line change
@@ -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())
Loading