Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Ajax Requests panel - proof of concept #253

Closed
wants to merge 2 commits into from

5 participants

@tobiasmcnulty

It's certainly not commit ready yet, but I wanted to open this pull request to facilitate feedback on this idea.

The code implements an "Ajax Requests" panel that lists out ajax requests that have occurred. You can then click on one of the requests to replace your current debug toolbar with the toolbar rendered for that ajax request.

Would you consider integrating a panel like this? If so, what sorts of changes would need to be made first? I'll happily write some tests. I'm also not very happy with storing the rendered toolbars in the session, so I'll probably have to come up with some other form of semi-persistent server-side storage.

Apologies if someone else has done something like this already - if they have I didn't find it. I did find an old fork that did something like this (used the cache to store the most recent AJAX request and load that), but nothing that added a panel and allowed debugging multiple AJAX requests at once, which is probably a necessity in most cases.

Anyways, looking forward to your feedback. Thanks!

@dcramer
Owner

This idea has been brought up before, and I like the concept a lot. I'm also not sure the session is the right place to store it. Someone else suggested memcache, which seems a bit more bearable.

@jezdez
Owner

I was just about to implement exactly that, woot! I agree with @dcramer that putting this in a cache (preferable in an own cache backend, falling back to the default one) with the str(uuid.uuid4())-key as the cache key should be better than straining the session API to do the same thing. For local development purposes the locmem backend is totally able to handle that kind of data, too.

@tobiasmcnulty

Thanks for the feedback guys. I think your suggestion to use the cache sounds good. @jezdez I'm not quite sure what you mean by its own cache backend though? I'd hoped to have some code ready when I replied, but I've been completely swamped this week and haven't had a chance to do anything yet. I'll try to work on these improvements soon and update the pull request accordingly.

@jezdez
Owner

@tobiasmcnulty Oh, I meant that the CACHES setting now can be configured to have multiple backends to have different setup, e.g. using locmem in development and different memcache servers in production:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': '127.0.0.1:11211',
    },
    'debug_toolbar': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
        'LOCATION': 'debug_toolbar',
        'TIMEOUT': 60,
    }
}

Later in the code we could check if a debug_toolbar cache has been configured and fall back to the default cache if not:

from django.core.cache import (get_cache, cache as default_cache,
                               InvalidCacheBackendError)

try:
    debug_toolbar_cache = get_cache('debug_toolbar')
except InvalidCacheBackendError:
    # Use the default backend
    debug_toolbar_cache = default_cache
@Pewpewarrows

@tobiasmcnulty Keep in mind that when looking at the toolbar for a specific AJAX request there should still be an easy way to get back to the original page's toolbar. I believe as it stands this only allows switching between other AJAX requests once inside one's toolbar.

@jonahblake

@tobiasmcnulty Thanks for getting this started! I would love to have this working, have there been any recent updates? I tried using the posted changes, but now my toolbar does not show up. Any ideas? Thanks.

@tobiasmcnulty

Sorry, I've not had the time to polish this off - feel free to give it a whirl if you'd like!

@zifot zifot referenced this pull request from a commit in zifot/django-debug-toolbar
@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 referenced this pull request
Closed

Another take on AJAX panel #356

@jezdez
Owner

Closing in favor of #356.

@jezdez jezdez closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jan 16, 2012
  1. @tobiasmcnulty
  2. @tobiasmcnulty

    cleanup

    tobiasmcnulty authored
This page is out of date. Refresh to see the latest.
View
19 debug_toolbar/media/debug_toolbar/js/toolbar.js
@@ -83,6 +83,25 @@ window.djdt = (function(window, document, jQuery) {
});
return;
});
+ function refreshAjaxPanel() {
+ $.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');
+ });
+ }
+ $('#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').wrap('<div>').parent().html(data);
+ refreshAjaxPanel();
+ });
+ });
+ $('#djAjaxRefreshBtn').live('click', function(e) {
+ e.preventDefault();
+ refreshAjaxPanel();
+ });
function getSubcalls(row) {
var id = row.attr('id');
return $('.djDebugProfileRow[id^="'+id+'_"]');
View
6 debug_toolbar/media/debug_toolbar/js/toolbar.min.js
3 additions, 3 deletions not shown
View
22 debug_toolbar/middleware.py
@@ -3,6 +3,7 @@
"""
import imp
import thread
+import datetime
from django.conf import settings
from django.http import HttpResponseRedirect
@@ -127,11 +128,20 @@ 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 = smart_unicode(toolbar.render_toolbar())
+ if self.tag in content:
+ response.content = replace_insensitive(content, self.tag,
+ ddt_html + self.tag)
+ if response.get('Content-Length', None):
+ response['Content-Length'] = len(response.content)
+ if request.is_ajax():
+ 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)
del self.__class__.debug_toolbars[ident]
return response
View
66 debug_toolbar/panels/ajax.py
@@ -0,0 +1,66 @@
+import time
+import uuid
+import inspect
+import datetime
+
+from django.utils.translation import ugettext_lazy as _
+from debug_toolbar.panels import DebugPanel
+
+
+class AjaxDebugPanel(DebugPanel):
+ """
+ Panel that displays recent AJAX requests.
+ """
+ name = 'Ajax'
+ template = 'debug_toolbar/panels/ajax.html'
+ has_content = True
+ session_key = 'debug_toolbar_ajax_requests'
+
+ 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):
+ if self.session_key not in request.session:
+ request.session[self.session_key] = []
+ return request.session[self.session_key]
+
+ def record(self, request, ddt_html):
+ self.storage(request).append({
+ 'id': str(uuid.uuid4()),
+ 'time': datetime.datetime.now(),
+ 'path': request.path,
+ 'html': ddt_html,
+ })
+
+ 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))
+
+ def process_response(self, request, response):
+ self.record_stats(self.get_context(request))
View
2  debug_toolbar/templates/debug_toolbar/base.html
@@ -1,4 +1,5 @@
{% load i18n %}
+<div id="djDebugWrapper">
<style type="text/css">
@media print { #djDebug {display:none;}}
{{ css }}
@@ -52,3 +53,4 @@
{% endfor %}
<div id="djDebugWindow" class="panelContent"></div>
</div>
+</div>
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' %}">
+ <td>{{ ajax_req.time }}</td>
+ <td><a class='djAjaxLoad' data-requestid='{{ ajax_req.id }}' href='' title='Load the toolbar for this request'>{{ ajax_req.path }}</a></td>
+ </tr>
+ {% endfor %}
+ </tbody>
+</table>
+<br/>
+<button id='djAjaxRefreshBtn' type='button'>{% trans 'Refresh' %}</button>
View
1  debug_toolbar/toolbar/loader.py
@@ -38,6 +38,7 @@ def __init__(self, request):
#'debug_toolbar.panels.cache.CacheDebugPanel',
'debug_toolbar.panels.signals.SignalDebugPanel',
'debug_toolbar.panels.logger.LoggingPanel',
+ 'debug_toolbar.panels.ajax.AjaxDebugPanel',
)
self.load_panels()
self.stats = {}
View
2  debug_toolbar/urls.py
@@ -14,4 +14,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 django.utils import simplejson
from django.utils.hashcompat import sha_constructor
@@ -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)
Something went wrong with that request. Please try again.