Skip to content

Commit af0d5af

Browse files
committed
Make AJAX views return JSON instead of HTML
Returning JSON allows for arbitrary data to be returned, not only HTML. This in turn allows for returning scripts as a list instead of embedded in the HTML. The script tag embedded in the HTML didn't immediately execute anyway, so handling it as separate data makes more sense. Separating the JavaScript makes it simpler to migrate to JavaScript modules as inserting the new scripts is handled entirely by toolbar.js. Removes the unused $$.executeScripts() call in the .remoteCall AJAX handler. No panels using .remoteCall inject scripts in this way.
1 parent 799f585 commit af0d5af

File tree

9 files changed

+56
-29
lines changed

9 files changed

+56
-29
lines changed

debug_toolbar/panels/__init__.py

+7
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,13 @@ def content(self):
9494
if self.has_content:
9595
return render_to_string(self.template, self.get_stats())
9696

97+
@property
98+
def scripts(self):
99+
"""
100+
Scripts used by the HTML content of the panel when it's displayed.
101+
"""
102+
return []
103+
97104
# URLs for panel-specific views
98105

99106
@classmethod

debug_toolbar/panels/sql/views.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
from django.http import HttpResponseBadRequest
2-
from django.template.response import SimpleTemplateResponse
1+
from django.http import HttpResponseBadRequest, JsonResponse
2+
from django.template.loader import render_to_string
33
from django.views.decorators.csrf import csrf_exempt
44

55
from debug_toolbar.decorators import require_show_toolbar
@@ -27,8 +27,8 @@ def sql_select(request):
2727
"headers": headers,
2828
"alias": form.cleaned_data["alias"],
2929
}
30-
# Using SimpleTemplateResponse avoids running global context processors.
31-
return SimpleTemplateResponse("debug_toolbar/panels/sql_select.html", context)
30+
content = render_to_string("debug_toolbar/panels/sql_select.html", context)
31+
return JsonResponse({"content": content})
3232
return HttpResponseBadRequest("Form errors")
3333

3434

@@ -64,8 +64,8 @@ def sql_explain(request):
6464
"headers": headers,
6565
"alias": form.cleaned_data["alias"],
6666
}
67-
# Using SimpleTemplateResponse avoids running global context processors.
68-
return SimpleTemplateResponse("debug_toolbar/panels/sql_explain.html", context)
67+
content = render_to_string("debug_toolbar/panels/sql_explain.html", context)
68+
return JsonResponse({"content": content})
6969
return HttpResponseBadRequest("Form errors")
7070

7171

@@ -115,6 +115,6 @@ def sql_profile(request):
115115
"headers": headers,
116116
"alias": form.cleaned_data["alias"],
117117
}
118-
# Using SimpleTemplateResponse avoids running global context processors.
119-
return SimpleTemplateResponse("debug_toolbar/panels/sql_profile.html", context)
118+
content = render_to_string("debug_toolbar/panels/sql_profile.html", context)
119+
return JsonResponse({"content": content})
120120
return HttpResponseBadRequest("Form errors")

debug_toolbar/panels/templates/views.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from django.core import signing
2-
from django.http import HttpResponseBadRequest
2+
from django.http import HttpResponseBadRequest, JsonResponse
33
from django.template import Origin, TemplateDoesNotExist
44
from django.template.engine import Engine
5-
from django.template.response import SimpleTemplateResponse
5+
from django.template.loader import render_to_string
66
from django.utils.safestring import mark_safe
77

88
from debug_toolbar.decorators import require_show_toolbar
@@ -57,8 +57,8 @@ def template_source(request):
5757
except ImportError:
5858
pass
5959

60-
# Using SimpleTemplateResponse avoids running global context processors.
61-
return SimpleTemplateResponse(
60+
content = render_to_string(
6261
"debug_toolbar/panels/template_source.html",
6362
{"source": source, "template_name": template_name},
6463
)
64+
return JsonResponse({"content": content})

debug_toolbar/panels/timer.py

+7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import time
22

33
from django.template.loader import render_to_string
4+
from django.templatetags.static import static
45
from django.utils.translation import gettext_lazy as _
56

67
from debug_toolbar.panels import Panel
@@ -51,6 +52,12 @@ def content(self):
5152
)
5253
return render_to_string(self.template, {"rows": rows})
5354

55+
@property
56+
def scripts(self):
57+
scripts = super().scripts
58+
scripts.append(static("debug_toolbar/js/toolbar.timer.js"))
59+
return scripts
60+
5461
def process_request(self, request):
5562
self._start_time = time.time()
5663
if self.has_content:

debug_toolbar/static/debug_toolbar/js/toolbar.js

+12-13
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@
2525
const style = getComputedStyle(element);
2626
return style.display !== 'none';
2727
},
28-
executeScripts: function(root) {
29-
root.querySelectorAll('script').forEach(function(e) {
30-
const clone = document.createElement('script');
31-
clone.src = e.src;
32-
clone.async = true;
33-
root.appendChild(clone);
28+
executeScripts: function(scripts) {
29+
scripts.forEach(function(script) {
30+
const el = document.createElement('script');
31+
el.src = script;
32+
el.async = true;
33+
document.head.appendChild(el);
3434
});
3535
},
3636
};
@@ -45,7 +45,7 @@
4545
init = Object.assign({credentials: 'same-origin'}, init);
4646
return fetch(url, init).then(function(response) {
4747
if (response.ok) {
48-
return response.text();
48+
return response.json();
4949
} else {
5050
const win = document.querySelector('#djDebugWindow');
5151
win.innerHTML = '<div class="djDebugPanelTitle"><a class="djDebugClose" href="">»</a><h3>'+response.status+': '+response.statusText+'</h3></div>';
@@ -82,10 +82,10 @@
8282
url_params.append('store_id', store_id);
8383
url_params.append('panel_id', this.className);
8484
url += '?' + url_params.toString();
85-
ajax(url).then(function(body) {
85+
ajax(url).then(function(data) {
8686
inner.previousElementSibling.remove(); // Remove AJAX loader
87-
inner.innerHTML = body;
88-
$$.executeScripts(inner);
87+
inner.innerHTML = data.content;
88+
$$.executeScripts(data.scripts);
8989
});
9090
}
9191
}
@@ -121,10 +121,9 @@
121121
ajax_data.url = this.getAttribute('href');
122122
}
123123

124-
ajax(ajax_data.url, ajax_data).then(function(body) {
124+
ajax(ajax_data.url, ajax_data).then(function(data) {
125125
const win = djDebug.querySelector('#djDebugWindow');
126-
win.innerHTML = body;
127-
$$.executeScripts(win);
126+
win.innerHTML = data.content;
128127
$$.show(win);
129128
});
130129
});

debug_toolbar/templates/debug_toolbar/panels/timer.html

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{% load i18n %}{% load static %}
1+
{% load i18n %}
22
<h4>{% trans "Resource usage" %}</h4>
33
<table>
44
<colgroup>
@@ -41,4 +41,3 @@ <h4>{% trans "Browser timing" %}</h4>
4141
</tbody>
4242
</table>
4343
</div>
44-
<script src="{% static 'debug_toolbar/js/toolbar.timer.js' %}" aysnc></script>

debug_toolbar/views.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from django.http import HttpResponse
1+
from django.http import JsonResponse
22
from django.utils.html import escape
33
from django.utils.translation import gettext as _
44

@@ -16,7 +16,9 @@ def render_panel(request):
1616
"Please reload the page and retry."
1717
)
1818
content = "<p>%s</p>" % escape(content)
19+
scripts = []
1920
else:
2021
panel = toolbar.get_panel_by_id(request.GET["panel_id"])
2122
content = panel.content
22-
return HttpResponse(content)
23+
scripts = panel.scripts
24+
return JsonResponse({"content": content, "scripts": scripts})

docs/changes.rst

+11
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,18 @@ UNRELEASED
88
* Updated the italian translation.
99
* Added support for Django 3.1a1.
1010
* Pruned unused CSS and removed hacks for ancient browsers.
11+
* Added the new :attr:`Panel.scripts <debug_toolbar.panels.Panel.scripts>`
12+
property. This property should return a list of JavaScript resources to be
13+
loaded in the browser when displaying the panel. Right now, this is used by a
14+
single panel, the Timer panel. Third party panels can use this property to
15+
add scripts rather then embedding them in the content HTML.
1116

17+
**Backwards incompatible changes**
18+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
19+
20+
* Loading panel content no longer executes the scripts elements embedded in the
21+
HTML. Third party panels that require JavaScript resources should now use the
22+
:attr:`Panel.scripts <debug_toolbar.panels.Panel.scripts>` property.
1223

1324
2.2 (2020-01-31)
1425
----------------

docs/panels.rst

+2
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,8 @@ unauthorized access. There is no public CSS API at this time.
319319

320320
.. autoattribute:: debug_toolbar.panels.Panel.content
321321

322+
.. autoattribute:: debug_toolbar.panels.Panel.scripts
323+
322324
.. automethod:: debug_toolbar.panels.Panel.get_urls
323325

324326
.. automethod:: debug_toolbar.panels.Panel.enable_instrumentation

0 commit comments

Comments
 (0)