Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Another take on AJAX panel #356

Closed
wants to merge 14 commits into from

10 participants

@zifot

Hi,

I supplemented the work of @tobiasmcnulty from django-debug-toolbar/django-debug-toolbar#253 to move the whole idea of an AJAX panel a little bit forward. I'm looking forward to your feedback.

tobiasmcnulty and others added some commits
@tobiasmcnulty tobiasmcnulty First shot at ajax requests panel
This is a rebase+squash of the original commits by Tobias McNulty (@tobiasmcnulty),
taken from pull request django-debug-toolbar/django-debug-toolbar#253, with some
minor changes to resolve merge conflicts.
257017e
@zifot zifot Storing toolbar DOM for AJAX requests in the cache instead of session 695f2b9
@zifot zifot Tests for DebugToolbarMiddleware.process_response
Making incoming changes easier and safer.
275896c
@zifot zifot Refactorization 404d932
@zifot zifot Fixing AJAX request handling
DOM tree of a toolbar for AJAX requests was not stored on
the server.
1247cc9
@zifot zifot Handle lack of session middleware in Ajax panel so that existing test…
…s pass again

Ajax panel requires session middleware (for session id),
but existing tests run without it. So the ones executing
panel.process_request were failing.

TODO: Panel probably shouldn't fall back to no-op silently just
like that, but rather throw an exception or return some diagnostic
information.
7f71ac1
@zifot zifot Don't send js code again with the toolbar DOM for AJAX requests
Sending the whole jQuery and toolbar js code over and over again
doesn't really makes sense, plus:

- it makes debugging js code harder or impossible after toolbar
for first AJAX call is loaded
- in some situations it may require cleaning up callbacks

Changes to toolbar.js:
- actions from djdt.init that should be performed only once (during
initial page load) are moved to djdt.setup
- those that need to be performed with each toolbar reload stays
within djdt.init
- both djdt.setup and djdt.init are called on dom ready
- djdt.init is also called after receiving new toolbar DOM for one of
the AJAX requests selected in the Ajax panel
3d1308a
@zifot zifot Ignore internal AJAX requests d3d6411
@zifot zifot Ajax panel: Allow to jump to initial (non-ajax) request d613e10
@zifot zifot Cleanup 49f7952
@akarl818

This is awesome!
We are developing a completely ajax-driven site and we will definitely use this.

zifot added some commits
@zifot zifot Exempt Ajax panel cache operations from tracking by Cache panel
Cache panel displays arguments that were sent to the cache methods
(they are rendered in the toolbar's template). And because Ajax
panel stores toolbar's templates in the cache (by calling cache.set)
this leads to a geometric grow in the amount of data being stored
as each subsequent template sent to the cache contains content of all the
templates stored previously (as rendered by the Cache panel).

This can quickly lead to template sizes in tens of megabytes after
only a handful of AJAX requests.

On top of that, that data isn't actually interesting from the debugging
point of view.
061d0cb
@zifot zifot Highlight currently displayed request in the Ajax panel f7abefe
@zifot zifot Don't render list of requests for nothing
The list of requests that should be displayed in the Ajax
panel changes dynamically with each request. Toolbar's DOM
trees for the old requests shouldn't hold this list as it
may be obsolete by the time such a tree is loaded into the
browser (e.g. when going back to examine old request) and
up to date list is always fetched via Ajax call anyway.
fb7fb06
@akarl818

The javascript file does not seem to be minified.

I would be nice to be able to see SQL-queries and other panels for the request also. Its nice to see how many and what sql gets generated for ajax-requests. (My bad.)

@zifot

@akarl818 That's the way it's working right now - if you click a specific request on the requests list, all panels will show information regarding that specific request. Or did you mean something like aggregated information for all AJAX requests?

@zifot

OTOH the usability of how it all works right now could be better - I'm thinking about something along the lines of displaying some kind of a drop down menu that would list all the requests available and that would be accessible in other panels or/and around the main sidebar. Any thoughts?

@akarl818

That dropdown could be realy nice :)

On line 142-143 in middleware.py:

if ('gzip' not in response.get('Content-Encoding', '') and
                response.get('Content-Type', '').split(';')[0] in _HTML_TYPES):

_HTML_TYPES is ('text/html', 'application/xhtml+xml') witch means that if the response is for example application/json then it does not get through to the panel.

@jezdez jezdez commented on the diff
debug_toolbar/panels/ajax.py
((3 lines not shown))
+import inspect
+import datetime
+from contextlib import contextmanager
+
+from django.core.cache import cache
+from django.utils.translation import ugettext_lazy as _
+from debug_toolbar.panels import DebugPanel
+
+@contextmanager
+def disable_tracking(cache):
+ cache._django_debug_toolbar_do_not_track = True
+ yield
+ del cache._django_debug_toolbar_do_not_track
+
+
+class Storage:
@jezdez Owner
jezdez added a note

Please use a new style class here for improved subclassing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@rouge8

Would it be worth trying to bring this PR up to date and doing some cleanup?

@tim-schilling

I've brought @zifot's branch up to date here tim-schilling@2020abc

@jezdez I'm not quite sure what you mean by a new style class, could you clarify what you're looking for?

@jezdez
Owner

@tim-schilling The patch uses old-style (class Storage:) instead of new-style Python classes (class Storage(object):. The latter has a vastly improved inheritance scheme and offers other important features (see http://realmike.org/blog/2010/07/18/introduction-to-new-style-classes-in-python/).

@aaugustin
Owner

I just discussed this feature with @jezdez, and given our limited resources for maintaining the toolbar we prefer that this feature live as a 3rd party panel. My recent changes to delay the rendering of panels may help.

If you release it, please send us a pull request adding it to the list of third-party panels in the documentation!

@aaugustin aaugustin closed this
@tomchristie

If you release it, please send us a pull request adding it to the list of third-party panels in the documentation!

Similarly here. If anyone does tackle this then please open an issue for it to be linked too from the Django REST framework documentation.

Likewise, if anyone has input on what they'd like to see in an AJAX panel please feel free to notify the Django REST framework discussion group so it can be discussed there.

@djsutho

Not sure if anyone is still interested in this but I made a third party panel which supports ajax requests:
https://github.com/djsutho/django-debug-toolbar-request-history
Its a bit rough but works well enough for me. I would appreciate any comments.

@tomchristie where in the Django REST framework documentation would this be linked?

@aaugustin
Owner

@djsutho We have a list of third-party panels in the docs. If you want yours to be included there, please submit a pull request!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 18, 2013
  1. @tobiasmcnulty @zifot

    First shot at ajax requests panel

    tobiasmcnulty authored zifot committed
    This is a rebase+squash of the original commits by Tobias McNulty (@tobiasmcnulty),
    taken from pull request django-debug-toolbar/django-debug-toolbar#253, with some
    minor changes to resolve merge conflicts.
  2. @zifot
  3. @zifot

    Tests for DebugToolbarMiddleware.process_response

    zifot authored
    Making incoming changes easier and safer.
  4. @zifot

    Refactorization

    zifot authored
  5. @zifot

    Fixing AJAX request handling

    zifot authored
    DOM tree of a toolbar for AJAX requests was not stored on
    the server.
  6. @zifot

    Handle lack of session middleware in Ajax panel so that existing test…

    zifot authored
    …s pass again
    
    Ajax panel requires session middleware (for session id),
    but existing tests run without it. So the ones executing
    panel.process_request were failing.
    
    TODO: Panel probably shouldn't fall back to no-op silently just
    like that, but rather throw an exception or return some diagnostic
    information.
  7. @zifot

    Don't send js code again with the toolbar DOM for AJAX requests

    zifot authored
    Sending the whole jQuery and toolbar js code over and over again
    doesn't really makes sense, plus:
    
    - it makes debugging js code harder or impossible after toolbar
    for first AJAX call is loaded
    - in some situations it may require cleaning up callbacks
    
    Changes to toolbar.js:
    - actions from djdt.init that should be performed only once (during
    initial page load) are moved to djdt.setup
    - those that need to be performed with each toolbar reload stays
    within djdt.init
    - both djdt.setup and djdt.init are called on dom ready
    - djdt.init is also called after receiving new toolbar DOM for one of
    the AJAX requests selected in the Ajax panel
  8. @zifot

    Ignore internal AJAX requests

    zifot authored
  9. @zifot
  10. @zifot

    Cleanup

    zifot authored
Commits on Feb 24, 2013
  1. @zifot

    Exempt Ajax panel cache operations from tracking by Cache panel

    zifot authored
    Cache panel displays arguments that were sent to the cache methods
    (they are rendered in the toolbar's template). And because Ajax
    panel stores toolbar's templates in the cache (by calling cache.set)
    this leads to a geometric grow in the amount of data being stored
    as each subsequent template sent to the cache contains content of all the
    templates stored previously (as rendered by the Cache panel).
    
    This can quickly lead to template sizes in tens of megabytes after
    only a handful of AJAX requests.
    
    On top of that, that data isn't actually interesting from the debugging
    point of view.
  2. @zifot
  3. @zifot

    Don't render list of requests for nothing

    zifot authored
    The list of requests that should be displayed in the Ajax
    panel changes dynamically with each request. Toolbar's DOM
    trees for the old requests shouldn't hold this list as it
    may be obsolete by the time such a tree is loaded into the
    browser (e.g. when going back to examine old request) and
    up to date list is always fetched via Ajax call anyway.
Commits on Feb 26, 2013
  1. @zifot

    Update toolbar.min.js

    zifot authored
This page is out of date. Refresh to see the latest.
View
31 debug_toolbar/middleware.py
@@ -72,6 +72,16 @@ def _show_toolbar(self, request):
# if not internal ip, and not DEBUG
return remote_addr in settings.INTERNAL_IPS and bool(settings.DEBUG)
+ def _handle_ajax(self, toolbar, ddt_html, request, response):
+ from debug_toolbar.panels.ajax import AjaxDebugPanel
+ try:
+ ajax_panel = toolbar.get_panel(AjaxDebugPanel)
+ except IndexError:
+ ajax_panel = None
+ if ajax_panel:
+ ajax_panel.record(request, ddt_html)
+
+
def process_request(self, request):
__traceback_hide__ = True
if self.show_toolbar(request):
@@ -116,7 +126,7 @@ def process_response(self, request, response):
__traceback_hide__ = True
ident = thread.get_ident()
toolbar = self.__class__.debug_toolbars.get(ident)
- if not toolbar or request.is_ajax():
+ if not toolbar:
return response
if isinstance(response, HttpResponseRedirect):
if not toolbar.config['INTERCEPT_REDIRECTS']:
@@ -133,11 +143,18 @@ def process_response(self, request, response):
response.get('Content-Type', '').split(';')[0] in _HTML_TYPES):
for panel in toolbar.panels:
panel.process_response(request, response)
- response.content = replace_insensitive(
- smart_unicode(response.content),
- self.tag,
- smart_unicode(toolbar.render_toolbar() + self.tag))
- if response.get('Content-Length', None):
- response['Content-Length'] = len(response.content)
+ content = smart_unicode(response.content)
+ ddt_html, ddt_html_js = map(smart_unicode, (toolbar.render_toolbar(include_js = not request.is_ajax())))
+
+ if request.is_ajax():
+ if not request.path.startswith('/' + debug_toolbar.urls._PREFIX):
+ self._handle_ajax(toolbar, ddt_html, request, response)
+ elif self.tag in content:
+ self._handle_ajax(toolbar, ddt_html, request, response)
+ response.content = replace_insensitive(content, self.tag,
+ ddt_html_js + self.tag)
+ if response.get('Content-Length', None):
+ response['Content-Length'] = len(response.content)
+
del self.__class__.debug_toolbars[ident]
return response
View
99 debug_toolbar/panels/ajax.py
@@ -0,0 +1,99 @@
+import time
+import uuid
+import inspect
+import datetime
+from contextlib import contextmanager
+
+from django.core.cache import cache
+from django.utils.translation import ugettext_lazy as _
+from debug_toolbar.panels import DebugPanel
+
+@contextmanager
+def disable_tracking(cache):
+ cache._django_debug_toolbar_do_not_track = True
+ yield
+ del cache._django_debug_toolbar_do_not_track
+
+
+class Storage:
@jezdez Owner
jezdez added a note

Please use a new style class here for improved subclassing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ def __init__(self, request):
+ self.session_key = None
+ if hasattr(request, 'session'):
+ self.session_key = request.session.session_key
+ with disable_tracking(cache):
+ cache.add(self.session_key, [])
+
+ def append(self, what):
+ if self.session_key:
+ with disable_tracking(cache):
+ toset = cache.get(self.session_key) or []
+ toset.append(what)
+ cache.set(self.session_key, toset)
+
+ def __iter__(self):
+ data = []
+ if self.session_key:
+ with disable_tracking(cache):
+ data = cache.get(self.session_key) or []
+ for d in data:
+ yield d
+
+ def __len__(self):
+ if self.session_key:
+ with disable_tracking(cache):
+ return len(cache.get(self.session_key) or [])
+ return 0
+
+class AjaxDebugPanel(DebugPanel):
+ """
+ Panel that displays recent AJAX requests.
+ """
+ name = 'Ajax'
+ template = 'debug_toolbar/panels/ajax.html'
+ has_content = True
+
+ def __init__(self, *args, **kwargs):
+ super(AjaxDebugPanel, self).__init__(*args, **kwargs)
+ self._num_requests = 0
+
+ def nav_title(self):
+ return _('Ajax')
+
+ def nav_subtitle(self):
+ # TODO l10n: use ngettext
+ return "%d request%s" % (
+ self._num_requests,
+ (self._num_requests == 1) and '' or 's'
+ )
+
+ def title(self):
+ return _('Ajax Requests')
+
+ def url(self):
+ return ''
+
+ def storage(self, request):
+ return Storage(request)
+
+ def record(self, request, ddt_html):
+ self.storage(request).append({
+ 'id': str(uuid.uuid4()),
+ 'time': datetime.datetime.now(),
+ 'path': request.path,
+ 'html': ddt_html,
+ 'is_ajax': request.is_ajax()
+ })
+
+ def get_html(self, request, req_id):
+ for ajax_request in self.storage(request):
+ if ajax_request['id'] == req_id:
+ return ajax_request['html']
+
+ def get_context(self, request):
+ return {
+ 'ajax_requests': self.storage(request),
+ }
+
+ def process_request(self, request):
+ self._num_requests = len(self.storage(request))
View
2  debug_toolbar/panels/cache.py
@@ -21,6 +21,8 @@
def send_signal(method):
def wrapped(self, *args, **kwargs):
+ if getattr(self, '_django_debug_toolbar_do_not_track', False):
+ return method(self, *args, **kwargs)
t = time.time()
value = method(self, *args, **kwargs)
t = time.time() - t
View
51 debug_toolbar/static/debug_toolbar/js/toolbar.js
@@ -8,8 +8,7 @@ window.djdt = (function(window, document, jQuery) {
ready: []
},
isReady: false,
- init: function() {
- $('#djDebug').show();
+ setup: function() {
var current = null;
$('#djDebugPanelList li a').live('click', function() {
if (!this.className) {
@@ -83,6 +82,19 @@ window.djdt = (function(window, document, jQuery) {
});
return;
});
+ $('#djDebug a.djAjaxLoad').live('click', function(e) {
+ e.preventDefault();
+ var req_id = $(this).attr('data-requestid');
+ $.get('/__debug__/ajax_request/' + req_id + '/', function(data) {
+ $('#djDebugWrapper').replaceWith(data);
+ djdt.init(req_id);
+ });
+ });
+ $('#djAjaxRefreshBtn').live('click', function(e) {
+ e.preventDefault();
+ var panel = $('#djDebugAjaxPanel');
+ djdt.refreshAjaxPanel(panel.find('tr.highlight').find('.djAjaxLoad').attr('data-requestid'));
+ });
function getSubcalls(row) {
var id = row.attr('id');
return $('.djDebugProfileRow[id^="'+id+'_"]');
@@ -101,14 +113,6 @@ window.djdt = (function(window, document, jQuery) {
subcalls.hide();
}
});
- $('#djHideToolBarButton').click(function() {
- djdt.hide_toolbar(true);
- return false;
- });
- $('#djShowToolBarButton').click(function() {
- djdt.show_toolbar();
- return false;
- });
$(document).bind('close.djDebug', function() {
// If a sub-panel is open, close that
if ($('#djDebugWindow').is(':visible')) {
@@ -126,6 +130,19 @@ window.djdt = (function(window, document, jQuery) {
return;
}
});
+ djdt.init(null);
+ },
+ init: function(req_id) {
+ $('#djDebug').show();
+
+ $('#djHideToolBarButton').click(function() {
+ djdt.hide_toolbar(true);
+ return false;
+ });
+ $('#djShowToolBarButton').click(function() {
+ djdt.show_toolbar();
+ return false;
+ });
if ($.cookie(COOKIE_NAME)) {
djdt.hide_toolbar(false);
} else {
@@ -140,6 +157,18 @@ window.djdt = (function(window, document, jQuery) {
$.each(djdt.events.ready, function(_, callback){
callback(djdt);
});
+ djdt.refreshAjaxPanel(req_id);
+ },
+ refreshAjaxPanel: function(curr_req_id) {
+ $.get('/__debug__/ajax_request/', function(data) {
+ $('#djDebugAjaxPanel .djDebugPanelContent div.scroll').html(data);
+ var num_reqs = $('#djDebugAjaxPanel .djDebugPanelContent tbody tr').length;
+ $('a.djDebugAjaxPanel small').html(num_reqs + ' requests');
+ var panel = $('#djDebugAjaxPanel');
+ curr_req_id = curr_req_id || panel.find('tr.djAjaxReq-initial').last().find('.djAjaxLoad').attr('data-requestid');
+ panel.find('tr.highlight').removeClass('highlight');
+ panel.find('#djAjaxReq-' + curr_req_id).addClass('highlight');
+ });
},
toggle_content: function(elem) {
if (elem.is(':visible')) {
@@ -202,7 +231,7 @@ window.djdt = (function(window, document, jQuery) {
}
};
$(document).ready(function() {
- djdt.init();
+ djdt.setup();
});
return djdt;
}(window, document, jQuery.noConflict(true)));
View
2  debug_toolbar/static/debug_toolbar/js/toolbar.min.js
1 addition, 1 deletion not shown
View
4 debug_toolbar/templates/debug_toolbar/base.html
@@ -1,9 +1,10 @@
{% load i18n %}
+<div id="djDebugWrapper">
<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">
-<script type="text/javascript" src="{{ STATIC_URL }}debug_toolbar/js/toolbar.min.js"></script>
+{% block js %}{% endblock %}
<div id="djDebug" style="display:none;" dir="ltr">
<div style="display:none;" id="djDebugToolbar">
<ul id="djDebugPanelList">
@@ -52,3 +53,4 @@
{% endfor %}
<div id="djDebugWindow" class="panelContent"></div>
</div>
+</div>
View
5 debug_toolbar/templates/debug_toolbar/base_with_js.html
@@ -0,0 +1,5 @@
+{% extends "debug_toolbar/base.html" %}
+
+{% block js %}
+ <script type="text/javascript" src="{{ STATIC_URL }}debug_toolbar/js/toolbar.min.js"></script>
+{% endblock %}
View
19 debug_toolbar/templates/debug_toolbar/panels/ajax.html
@@ -0,0 +1,19 @@
+{% load i18n %}
+<table>
+ <thead>
+ <tr>
+ <th>{% trans "Request Time" %}</th>
+ <th>{% trans "Request Path" %}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for ajax_req in ajax_requests %}
+ <tr id='djAjaxReq-{{ ajax_req.id }}' class="{% cycle 'djDebugOdd' 'djDebugEven' %} {% if not ajax_req.is_ajax %}djAjaxReq-initial{% endif %}">
+ <td class="k">{{ ajax_req.time }}</td>
+ <td class="k"><a class='djAjaxLoad' data-requestid='{{ ajax_req.id }}' href='' title='Load the toolbar for this request'>{{ ajax_req.path }}{% if not ajax_req.is_ajax %} [initial]{% endif %}</a></td>
+ </tr>
+ {% endfor %}
+ </tbody>
+</table>
+<br/>
+<button id='djAjaxRefreshBtn' type='button'>{% trans 'Refresh' %}</button>
View
10 debug_toolbar/toolbar/loader.py
@@ -52,7 +52,7 @@ def load_panels(self):
self._panels[panel_class] = panel_instance
- def render_toolbar(self):
+ def render_toolbar(self, include_js = True):
"""
Renders the overall Toolbar with panels inside.
"""
@@ -60,8 +60,11 @@ def render_toolbar(self):
context.update({
'panels': self.panels,
})
-
- return render_to_string('debug_toolbar/base.html', context)
+ base = render_to_string('debug_toolbar/base.html', context)
+ base_with_js = ''
+ if include_js:
+ base_with_js = render_to_string('debug_toolbar/base_with_js.html', context)
+ return base, base_with_js
panel_classes = []
@@ -83,6 +86,7 @@ def load_panel_classes():
'debug_toolbar.panels.cache.CacheDebugPanel',
'debug_toolbar.panels.signals.SignalDebugPanel',
'debug_toolbar.panels.logger.LoggingPanel',
+ 'debug_toolbar.panels.ajax.AjaxDebugPanel',
))
for panel_path in panels:
try:
View
2  debug_toolbar/urls.py
@@ -16,4 +16,6 @@
url(r'^%s/sql_explain/$' % _PREFIX, 'debug_toolbar.views.sql_explain', name='sql_explain'),
url(r'^%s/sql_profile/$' % _PREFIX, 'debug_toolbar.views.sql_profile', name='sql_profile'),
url(r'^%s/template_source/$' % _PREFIX, 'debug_toolbar.views.template_source', name='template_source'),
+ url(r'^%s/ajax_request/$' % _PREFIX, 'debug_toolbar.views.ajax_list', name='ajax_list'),
+ url(r'^%s/ajax_request/(?P<req_id>[\w-]+)/$' % _PREFIX, 'debug_toolbar.views.ajax_request', name='ajax_request'),
)
View
35 debug_toolbar/views.py
@@ -7,7 +7,7 @@
import os
import django.views.static
from django.conf import settings
-from django.http import HttpResponseBadRequest
+from django.http import HttpResponse, HttpResponseBadRequest
from django.shortcuts import render_to_response
from debug_toolbar.utils.compat.db import connections
@@ -204,3 +204,36 @@ def template_source(request):
'source': source,
'template_name': template_name
})
+
+
+def ajax_request(request, req_id):
+ """
+ Returns the Debug Toolbar HTML for the given request ID.
+ """
+ from debug_toolbar.panels.ajax import AjaxDebugPanel
+ from debug_toolbar.middleware import DebugToolbarMiddleware
+ toolbar = DebugToolbarMiddleware.get_current()
+ try:
+ ajax_panel = toolbar.get_panel(AjaxDebugPanel)
+ except IndexError:
+ raise ValueError('AjaxDebugPanel must be enabled to use this view.')
+ ddt_html = ajax_panel.get_html(request, req_id)
+ if ddt_html:
+ return HttpResponse(ddt_html)
+ else:
+ return HttpResponseBadRequest('No such request {0}'.format(req_id))
+
+
+def ajax_list(request):
+ """
+ Returns the latest list of AJAX requests in HTML format.
+ """
+ from debug_toolbar.panels.ajax import AjaxDebugPanel
+ from debug_toolbar.middleware import DebugToolbarMiddleware
+ toolbar = DebugToolbarMiddleware.get_current()
+ try:
+ ajax_panel = toolbar.get_panel(AjaxDebugPanel)
+ except IndexError:
+ raise ValueError('AjaxDebugPanel must be enabled to use this view.')
+ context = ajax_panel.get_context(request)
+ return render_to_response('debug_toolbar/panels/ajax.html', context)
View
86 tests/tests.py
@@ -1,10 +1,12 @@
import thread
+import types
from django.conf import settings
from django.contrib.auth.models import User
from django.http import HttpResponse
from django.test import TestCase, RequestFactory
from django.template import Template, Context
+from django.core.urlresolvers import reverse
from debug_toolbar.middleware import DebugToolbarMiddleware
from debug_toolbar.panels.sql import SQLDebugPanel
@@ -177,7 +179,18 @@ def test_url_resolving_bad(self):
self.assertEquals(stats['view_kwargs'], 'None')
self.assertEquals(stats['view_func'], '<no view>')
-
+ def test_attaching_debug_toolbar(self):
+ # Smoke test: ensure debug toolbar is attached to the response during a typical scenario
+ request, response = self.request, self.response
+ response.content = '<body></body>'
+ with Settings(INTERNAL_IPS=['127.0.0.1'], DEBUG=True, DEBUG_TOOLBAR_CONFIG = dict(TAG='body')):
+ middleware = DebugToolbarMiddleware()
+ middleware.process_request(request)
+ middleware.process_response(request, response)
+ self.assertIn('djDebug', response.content)
+ self.assertIn('<script', response.content)
+
+
class DebugToolbarNameFromObjectTest(BaseTestCase):
def test_func(self):
def x():
@@ -387,3 +400,74 @@ def test(**kwargs):
self.assertTrue(len(foo['kwargs']), 1)
self.assertTrue('foo' in foo['kwargs'])
self.assertEquals(foo['kwargs']['foo'], 'bar')
+
+
+class MiddlewareAjaxTestCase(BaseTestCase):
+ urls = 'tests.urls'
+
+ def test_response_to_ajax_request_stays_unchanged(self):
+ request = request = rf.get('/')
+ request.META['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
+ response = HttpResponse('<body></body>')
+ with Settings(INTERNAL_IPS=['127.0.0.1'], DEBUG=True, DEBUG_TOOLBAR_CONFIG = dict(TAG='body')):
+ middleware = DebugToolbarMiddleware()
+ middleware.process_request(request)
+ middleware.process_response(request, response)
+ self.assertEquals(response.content, '<body></body>')
+
+ def test_handling_ajax_request(self):
+ request = request = rf.get('/')
+ request.META['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
+ response = self.response
+ with Settings(INTERNAL_IPS=['127.0.0.1'], DEBUG=True):
+ middleware = DebugToolbarMiddleware()
+
+ def handler_mock(self, toolbar, ddt_html, request, response):
+ handler_mock.called = True
+ handler_mock.ddt_html = ddt_html
+ handler_mock.called = False
+ middleware._handle_ajax = types.MethodType(handler_mock, middleware)
+
+ middleware.process_request(request)
+ middleware.process_response(request, response)
+ self.assertTrue(handler_mock.called)
+ self.assertNotIn('<script', handler_mock.ddt_html)
+
+ def test_internal_ajax_requests_are_ignored(self):
+ with Settings(ROOT_URLCONF = 'debug_toolbar.urls'):
+ request = request = rf.get(reverse('ajax_list'))
+ request.META['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
+ response = self.response
+ with Settings(INTERNAL_IPS=['127.0.0.1'], DEBUG=True):
+ middleware = DebugToolbarMiddleware()
+
+ def handler_mock(self, toolbar, ddt_html, request, response):
+ handler_mock.called = True
+ handler_mock.ddt_html = ddt_html
+ handler_mock.called = False
+ middleware._handle_ajax = types.MethodType(handler_mock, middleware)
+
+ middleware.process_request(request)
+ middleware.process_response(request, response)
+ self.assertTrue(not handler_mock.called)
+
+ def test_handling_initial_request(self):
+ request = request = rf.get('/')
+ response = HttpResponse('<body></body>')
+ with Settings(INTERNAL_IPS=['127.0.0.1'], DEBUG=True, DEBUG_TOOLBAR_CONFIG = dict(TAG='body')):
+ middleware = DebugToolbarMiddleware()
+
+ def handler_mock(self, toolbar, ddt_html, request, response):
+ handler_mock.called = True
+ handler_mock.is_ajax = request.is_ajax()
+ handler_mock.ddt_html = ddt_html
+ handler_mock.called = False
+ middleware._handle_ajax = types.MethodType(handler_mock, middleware)
+
+ middleware.process_request(request)
+ middleware.process_response(request, response)
+ self.assertTrue(handler_mock.called)
+ self.assertFalse(handler_mock.is_ajax)
+ self.assertNotIn('<script', handler_mock.ddt_html)
+
+
Something went wrong with that request. Please try again.