From af0d5af82a29ae39955331be6ca50b575b7b1466 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Mon, 18 May 2020 07:44:22 -0700 Subject: [PATCH] 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. --- debug_toolbar/panels/__init__.py | 7 ++++++ debug_toolbar/panels/sql/views.py | 16 ++++++------ debug_toolbar/panels/templates/views.py | 8 +++--- debug_toolbar/panels/timer.py | 7 ++++++ .../static/debug_toolbar/js/toolbar.js | 25 +++++++++---------- .../templates/debug_toolbar/panels/timer.html | 3 +-- debug_toolbar/views.py | 6 +++-- docs/changes.rst | 11 ++++++++ docs/panels.rst | 2 ++ 9 files changed, 56 insertions(+), 29 deletions(-) diff --git a/debug_toolbar/panels/__init__.py b/debug_toolbar/panels/__init__.py index 579e9221a..6133f65c8 100644 --- a/debug_toolbar/panels/__init__.py +++ b/debug_toolbar/panels/__init__.py @@ -94,6 +94,13 @@ def content(self): if self.has_content: return render_to_string(self.template, self.get_stats()) + @property + def scripts(self): + """ + Scripts used by the HTML content of the panel when it's displayed. + """ + return [] + # URLs for panel-specific views @classmethod diff --git a/debug_toolbar/panels/sql/views.py b/debug_toolbar/panels/sql/views.py index de525a87c..4d86128e2 100644 --- a/debug_toolbar/panels/sql/views.py +++ b/debug_toolbar/panels/sql/views.py @@ -1,5 +1,5 @@ -from django.http import HttpResponseBadRequest -from django.template.response import SimpleTemplateResponse +from django.http import HttpResponseBadRequest, JsonResponse +from django.template.loader import render_to_string from django.views.decorators.csrf import csrf_exempt from debug_toolbar.decorators import require_show_toolbar @@ -27,8 +27,8 @@ def sql_select(request): "headers": headers, "alias": form.cleaned_data["alias"], } - # Using SimpleTemplateResponse avoids running global context processors. - return SimpleTemplateResponse("debug_toolbar/panels/sql_select.html", context) + content = render_to_string("debug_toolbar/panels/sql_select.html", context) + return JsonResponse({"content": content}) return HttpResponseBadRequest("Form errors") @@ -64,8 +64,8 @@ def sql_explain(request): "headers": headers, "alias": form.cleaned_data["alias"], } - # Using SimpleTemplateResponse avoids running global context processors. - return SimpleTemplateResponse("debug_toolbar/panels/sql_explain.html", context) + content = render_to_string("debug_toolbar/panels/sql_explain.html", context) + return JsonResponse({"content": content}) return HttpResponseBadRequest("Form errors") @@ -115,6 +115,6 @@ def sql_profile(request): "headers": headers, "alias": form.cleaned_data["alias"], } - # Using SimpleTemplateResponse avoids running global context processors. - return SimpleTemplateResponse("debug_toolbar/panels/sql_profile.html", context) + content = render_to_string("debug_toolbar/panels/sql_profile.html", context) + return JsonResponse({"content": content}) return HttpResponseBadRequest("Form errors") diff --git a/debug_toolbar/panels/templates/views.py b/debug_toolbar/panels/templates/views.py index 2b3089798..524f36e9c 100644 --- a/debug_toolbar/panels/templates/views.py +++ b/debug_toolbar/panels/templates/views.py @@ -1,8 +1,8 @@ from django.core import signing -from django.http import HttpResponseBadRequest +from django.http import HttpResponseBadRequest, JsonResponse from django.template import Origin, TemplateDoesNotExist from django.template.engine import Engine -from django.template.response import SimpleTemplateResponse +from django.template.loader import render_to_string from django.utils.safestring import mark_safe from debug_toolbar.decorators import require_show_toolbar @@ -57,8 +57,8 @@ def template_source(request): except ImportError: pass - # Using SimpleTemplateResponse avoids running global context processors. - return SimpleTemplateResponse( + content = render_to_string( "debug_toolbar/panels/template_source.html", {"source": source, "template_name": template_name}, ) + return JsonResponse({"content": content}) diff --git a/debug_toolbar/panels/timer.py b/debug_toolbar/panels/timer.py index 130462ba0..b9daf9956 100644 --- a/debug_toolbar/panels/timer.py +++ b/debug_toolbar/panels/timer.py @@ -1,6 +1,7 @@ import time from django.template.loader import render_to_string +from django.templatetags.static import static from django.utils.translation import gettext_lazy as _ from debug_toolbar.panels import Panel @@ -51,6 +52,12 @@ def content(self): ) return render_to_string(self.template, {"rows": rows}) + @property + def scripts(self): + scripts = super().scripts + scripts.append(static("debug_toolbar/js/toolbar.timer.js")) + return scripts + def process_request(self, request): self._start_time = time.time() if self.has_content: diff --git a/debug_toolbar/static/debug_toolbar/js/toolbar.js b/debug_toolbar/static/debug_toolbar/js/toolbar.js index 49e31aac4..44a9977d2 100644 --- a/debug_toolbar/static/debug_toolbar/js/toolbar.js +++ b/debug_toolbar/static/debug_toolbar/js/toolbar.js @@ -25,12 +25,12 @@ const style = getComputedStyle(element); return style.display !== 'none'; }, - executeScripts: function(root) { - root.querySelectorAll('script').forEach(function(e) { - const clone = document.createElement('script'); - clone.src = e.src; - clone.async = true; - root.appendChild(clone); + executeScripts: function(scripts) { + scripts.forEach(function(script) { + const el = document.createElement('script'); + el.src = script; + el.async = true; + document.head.appendChild(el); }); }, }; @@ -45,7 +45,7 @@ init = Object.assign({credentials: 'same-origin'}, init); return fetch(url, init).then(function(response) { if (response.ok) { - return response.text(); + return response.json(); } else { const win = document.querySelector('#djDebugWindow'); win.innerHTML = '
ยป

'+response.status+': '+response.statusText+'

'; @@ -82,10 +82,10 @@ url_params.append('store_id', store_id); url_params.append('panel_id', this.className); url += '?' + url_params.toString(); - ajax(url).then(function(body) { + ajax(url).then(function(data) { inner.previousElementSibling.remove(); // Remove AJAX loader - inner.innerHTML = body; - $$.executeScripts(inner); + inner.innerHTML = data.content; + $$.executeScripts(data.scripts); }); } } @@ -121,10 +121,9 @@ ajax_data.url = this.getAttribute('href'); } - ajax(ajax_data.url, ajax_data).then(function(body) { + ajax(ajax_data.url, ajax_data).then(function(data) { const win = djDebug.querySelector('#djDebugWindow'); - win.innerHTML = body; - $$.executeScripts(win); + win.innerHTML = data.content; $$.show(win); }); }); diff --git a/debug_toolbar/templates/debug_toolbar/panels/timer.html b/debug_toolbar/templates/debug_toolbar/panels/timer.html index a80aed242..676a0743b 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/timer.html +++ b/debug_toolbar/templates/debug_toolbar/panels/timer.html @@ -1,4 +1,4 @@ -{% load i18n %}{% load static %} +{% load i18n %}

{% trans "Resource usage" %}

@@ -41,4 +41,3 @@

{% trans "Browser timing" %}

- diff --git a/debug_toolbar/views.py b/debug_toolbar/views.py index 9d61b54ff..1d319027d 100644 --- a/debug_toolbar/views.py +++ b/debug_toolbar/views.py @@ -1,4 +1,4 @@ -from django.http import HttpResponse +from django.http import JsonResponse from django.utils.html import escape from django.utils.translation import gettext as _ @@ -16,7 +16,9 @@ def render_panel(request): "Please reload the page and retry." ) content = "

%s

" % escape(content) + scripts = [] else: panel = toolbar.get_panel_by_id(request.GET["panel_id"]) content = panel.content - return HttpResponse(content) + scripts = panel.scripts + return JsonResponse({"content": content, "scripts": scripts}) diff --git a/docs/changes.rst b/docs/changes.rst index a2c06c1d1..e17fd33e1 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -8,7 +8,18 @@ UNRELEASED * Updated the italian translation. * Added support for Django 3.1a1. * Pruned unused CSS and removed hacks for ancient browsers. +* Added the new :attr:`Panel.scripts ` + property. This property should return a list of JavaScript resources to be + loaded in the browser when displaying the panel. Right now, this is used by a + single panel, the Timer panel. Third party panels can use this property to + add scripts rather then embedding them in the content HTML. +**Backwards incompatible changes** +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* Loading panel content no longer executes the scripts elements embedded in the + HTML. Third party panels that require JavaScript resources should now use the + :attr:`Panel.scripts ` property. 2.2 (2020-01-31) ---------------- diff --git a/docs/panels.rst b/docs/panels.rst index 143407935..cb6fa55eb 100644 --- a/docs/panels.rst +++ b/docs/panels.rst @@ -319,6 +319,8 @@ unauthorized access. There is no public CSS API at this time. .. autoattribute:: debug_toolbar.panels.Panel.content + .. autoattribute:: debug_toolbar.panels.Panel.scripts + .. automethod:: debug_toolbar.panels.Panel.get_urls .. automethod:: debug_toolbar.panels.Panel.enable_instrumentation