diff --git a/debug_toolbar/middleware.py b/debug_toolbar/middleware.py
index df4516b4f..f131861fc 100644
--- a/debug_toolbar/middleware.py
+++ b/debug_toolbar/middleware.py
@@ -67,12 +67,13 @@ def __call__(self, request):
panel.generate_stats(request, response)
panel.generate_server_timing(request, response)
- response = self.generate_server_timing_header(response, toolbar.enabled_panels)
-
# Always render the toolbar for the history panel, even if it is not
# included in the response.
rendered = toolbar.render_toolbar()
+ for header, value in self.get_headers(request, toolbar.enabled_panels).items():
+ response.headers[header] = value
+
# Check for responses where the toolbar can't be inserted.
content_encoding = response.get("Content-Encoding", "")
content_type = response.get("Content-Type", "").split(";")[0]
@@ -96,22 +97,12 @@ def __call__(self, request):
return response
@staticmethod
- def generate_server_timing_header(response, panels):
- data = []
-
+ def get_headers(request, panels):
+ headers = {}
for panel in panels:
- stats = panel.get_server_timing_stats()
- if not stats:
- continue
-
- for key, record in stats.items():
- # example: `SQLPanel_sql_time;dur=0;desc="SQL 0 queries"`
- data.append(
- '{}_{};dur={};desc="{}"'.format(
- panel.panel_id, key, record.get("value"), record.get("title")
- )
- )
-
- if data:
- response["Server-Timing"] = ", ".join(data)
- return response
+ for header, value in panel.get_headers(request).items():
+ if header in headers:
+ headers[header] += f", {value}"
+ else:
+ headers[header] = value
+ return headers
diff --git a/debug_toolbar/panels/__init__.py b/debug_toolbar/panels/__init__.py
index 8fd433c63..ce6772ec6 100644
--- a/debug_toolbar/panels/__init__.py
+++ b/debug_toolbar/panels/__init__.py
@@ -192,16 +192,41 @@ def process_request(self, request):
"""
return self.get_response(request)
- def generate_stats(self, request, response):
+ def get_headers(self, request):
"""
+ Get headers the panel needs to set.
+
Called after :meth:`process_request
- `, but may not be executed
- on every request. This will only be called if the toolbar will be
- inserted into the request.
+ ` and
+ :meth:`process_request`
+
+ Header values will be appended if multiple panels need to set it.
+
+ By default it sets the Server-Timing header.
+
+ Return dict of headers to be appended.
+ """
+ headers = {}
+ stats = self.get_server_timing_stats()
+ if stats:
+ headers["Server-Timing"] = ", ".join(
+ # example: `SQLPanel_sql_time;dur=0;desc="SQL 0 queries"`
+ '{}_{};dur={};desc="{}"'.format(
+ self.panel_id, key, record.get("value"), record.get("title")
+ )
+ for key, record in stats.items()
+ )
+ return headers
+ def generate_stats(self, request, response):
+ """
Write panel logic related to the response there. Post-process data
gathered while the view executed. Save data with :meth:`record_stats`.
+ Called after :meth:`process_request
+ `.
+
+
Does not return a value.
"""
diff --git a/debug_toolbar/panels/history/forms.py b/debug_toolbar/panels/history/forms.py
index 9280c3cc9..952b2409d 100644
--- a/debug_toolbar/panels/history/forms.py
+++ b/debug_toolbar/panels/history/forms.py
@@ -9,3 +9,4 @@ class HistoryStoreForm(forms.Form):
"""
store_id = forms.CharField(widget=forms.HiddenInput())
+ exclude_history = forms.BooleanField(widget=forms.HiddenInput(), required=False)
diff --git a/debug_toolbar/panels/history/panel.py b/debug_toolbar/panels/history/panel.py
index bde30e74f..00b350b3c 100644
--- a/debug_toolbar/panels/history/panel.py
+++ b/debug_toolbar/panels/history/panel.py
@@ -20,6 +20,14 @@ class HistoryPanel(Panel):
nav_title = _("History")
template = "debug_toolbar/panels/history.html"
+ def get_headers(self, request):
+ headers = super().get_headers(request)
+ observe_request = self.toolbar.get_observe_request()
+ store_id = getattr(self.toolbar, "store_id")
+ if store_id and observe_request(request):
+ headers["DJDT-STORE-ID"] = store_id
+ return headers
+
@property
def enabled(self):
# Do not show the history panel if the panels are rendered on request
@@ -83,7 +91,9 @@ def content(self):
for id, toolbar in reversed(self.toolbar._store.items()):
stores[id] = {
"toolbar": toolbar,
- "form": HistoryStoreForm(initial={"store_id": id}),
+ "form": HistoryStoreForm(
+ initial={"store_id": id, "exclude_history": True}
+ ),
}
return render_to_string(
@@ -92,7 +102,10 @@ def content(self):
"current_store_id": self.toolbar.store_id,
"stores": stores,
"refresh_form": HistoryStoreForm(
- initial={"store_id": self.toolbar.store_id}
+ initial={
+ "store_id": self.toolbar.store_id,
+ "exclude_history": True,
+ }
),
},
)
diff --git a/debug_toolbar/panels/history/views.py b/debug_toolbar/panels/history/views.py
index d452fd6e0..d50841a53 100644
--- a/debug_toolbar/panels/history/views.py
+++ b/debug_toolbar/panels/history/views.py
@@ -14,13 +14,14 @@ def history_sidebar(request):
if form.is_valid():
store_id = form.cleaned_data["store_id"]
toolbar = DebugToolbar.fetch(store_id)
+ exclude_history = form.cleaned_data["exclude_history"]
context = {}
if toolbar is None:
# When the store_id has been popped already due to
# RESULTS_CACHE_SIZE
return JsonResponse(context)
for panel in toolbar.panels:
- if not panel.is_historical:
+ if exclude_history and not panel.is_historical:
continue
panel_context = {"panel": panel}
context[panel.panel_id] = {
@@ -53,7 +54,12 @@ def history_refresh(request):
"id": id,
"store_context": {
"toolbar": toolbar,
- "form": HistoryStoreForm(initial={"store_id": id}),
+ "form": HistoryStoreForm(
+ initial={
+ "store_id": id,
+ "exclude_history": True,
+ }
+ ),
},
},
),
diff --git a/debug_toolbar/settings.py b/debug_toolbar/settings.py
index aac87e6ba..5bf9bb09f 100644
--- a/debug_toolbar/settings.py
+++ b/debug_toolbar/settings.py
@@ -37,6 +37,7 @@
"SHOW_TEMPLATE_CONTEXT": True,
"SKIP_TEMPLATE_PREFIXES": ("django/forms/widgets/", "admin/widgets/"),
"SQL_WARNING_THRESHOLD": 500, # milliseconds
+ "OBSERVE_REQUEST_CALLBACK": "debug_toolbar.toolbar.observe_request",
}
diff --git a/debug_toolbar/static/debug_toolbar/js/history.js b/debug_toolbar/static/debug_toolbar/js/history.js
index a356c3fcd..b30fcabae 100644
--- a/debug_toolbar/static/debug_toolbar/js/history.js
+++ b/debug_toolbar/static/debug_toolbar/js/history.js
@@ -1,4 +1,4 @@
-import { $$, ajaxForm } from "./utils.js";
+import { $$, ajaxForm, replaceToolbarState } from "./utils.js";
const djDebug = document.getElementById("djDebug");
@@ -12,9 +12,6 @@ function difference(setA, setB) {
/**
* Create an array of dataset properties from a NodeList.
- * @param nodes
- * @param key
- * @returns {[]}
*/
function pluckData(nodes, key) {
const data = [];
@@ -31,7 +28,7 @@ function refreshHistory() {
pluckData(container.querySelectorAll("tr[data-store-id]"), "storeId")
);
- return ajaxForm(formTarget)
+ ajaxForm(formTarget)
.then(function (data) {
// Remove existing rows first then re-populate with new data
container
@@ -75,36 +72,32 @@ function refreshHistory() {
});
}
-$$.on(djDebug, "click", ".switchHistory", function (event) {
- event.preventDefault();
- const newStoreId = this.dataset.storeId;
- const tbody = this.closest("tbody");
+function switchHistory(newStoreId) {
+ const formTarget = djDebug.querySelector(
+ ".switchHistory[data-store-id='" + newStoreId + "']"
+ );
+ const tbody = formTarget.closest("tbody");
const highlighted = tbody.querySelector(".djdt-highlighted");
if (highlighted) {
highlighted.classList.remove("djdt-highlighted");
}
- this.closest("tr").classList.add("djdt-highlighted");
+ formTarget.closest("tr").classList.add("djdt-highlighted");
- ajaxForm(this).then(function (data) {
- djDebug.setAttribute("data-store-id", newStoreId);
- // Check if response is empty, it could be due to an expired store_id.
+ ajaxForm(formTarget).then(function (data) {
if (Object.keys(data).length === 0) {
const container = document.getElementById("djdtHistoryRequests");
container.querySelector(
'button[data-store-id="' + newStoreId + '"]'
).innerHTML = "Switch [EXPIRED]";
- } else {
- Object.keys(data).forEach(function (panelId) {
- const panel = document.getElementById(panelId);
- if (panel) {
- panel.outerHTML = data[panelId].content;
- document.getElementById("djdt-" + panelId).outerHTML =
- data[panelId].button;
- }
- });
}
+ replaceToolbarState(newStoreId, data);
});
+}
+
+$$.on(djDebug, "click", ".switchHistory", function (event) {
+ event.preventDefault();
+ switchHistory(this.dataset.storeId);
});
$$.on(djDebug, "click", ".refreshHistory", function (event) {
diff --git a/debug_toolbar/static/debug_toolbar/js/toolbar.js b/debug_toolbar/static/debug_toolbar/js/toolbar.js
index c17ee3ea2..860c72110 100644
--- a/debug_toolbar/static/debug_toolbar/js/toolbar.js
+++ b/debug_toolbar/static/debug_toolbar/js/toolbar.js
@@ -1,4 +1,4 @@
-import { $$, ajax } from "./utils.js";
+import { $$, ajax, replaceToolbarState, debounce } from "./utils.js";
function onKeyDown(event) {
if (event.keyCode === 27) {
@@ -200,6 +200,9 @@ const djdt = {
} else {
djdt.hide_toolbar();
}
+ if (djDebug.dataset.sidebarUrl !== undefined) {
+ djdt.update_on_ajax();
+ }
},
hide_panels() {
const djDebug = document.getElementById("djDebug");
@@ -253,6 +256,26 @@ const djdt = {
localStorage.setItem("djdt.show", "true");
window.removeEventListener("resize", djdt.ensure_handle_visibility);
},
+ update_on_ajax() {
+ const sidebar_url =
+ document.getElementById("djDebug").dataset.sidebarUrl;
+ const slowjax = debounce(ajax, 200);
+
+ const origOpen = XMLHttpRequest.prototype.open;
+ XMLHttpRequest.prototype.open = function () {
+ this.addEventListener("load", function () {
+ let store_id = this.getResponseHeader("djdt-store-id");
+ if (store_id !== null) {
+ store_id = encodeURIComponent(store_id);
+ const dest = `${sidebar_url}?store_id=${store_id}`;
+ slowjax(dest).then(function (data) {
+ replaceToolbarState(store_id, data);
+ });
+ }
+ });
+ origOpen.apply(this, arguments);
+ };
+ },
cookie: {
get(key) {
if (!document.cookie.includes(key)) {
diff --git a/debug_toolbar/static/debug_toolbar/js/utils.js b/debug_toolbar/static/debug_toolbar/js/utils.js
index da810aad0..72c767fb6 100644
--- a/debug_toolbar/static/debug_toolbar/js/utils.js
+++ b/debug_toolbar/static/debug_toolbar/js/utils.js
@@ -104,4 +104,34 @@ function ajaxForm(element) {
return ajax(url, ajaxData);
}
-export { $$, ajax, ajaxForm };
+function replaceToolbarState(newStoreId, data) {
+ const djDebug = document.getElementById("djDebug");
+ djDebug.setAttribute("data-store-id", newStoreId);
+ // Check if response is empty, it could be due to an expired store_id.
+ Object.keys(data).forEach(function (panelId) {
+ const panel = document.getElementById(panelId);
+ if (panel) {
+ panel.outerHTML = data[panelId].content;
+ document.getElementById("djdt-" + panelId).outerHTML =
+ data[panelId].button;
+ }
+ });
+}
+
+function debounce(func, delay) {
+ let timer = null;
+ let resolves = [];
+
+ return function (...args) {
+ clearTimeout(timer);
+ timer = setTimeout(() => {
+ const result = func(...args);
+ resolves.forEach((r) => r(result));
+ resolves = [];
+ }, delay);
+
+ return new Promise((r) => resolves.push(r));
+ };
+}
+
+export { $$, ajax, ajaxForm, replaceToolbarState, debounce };
diff --git a/debug_toolbar/templates/debug_toolbar/base.html b/debug_toolbar/templates/debug_toolbar/base.html
index 7abc5476f..5447970af 100644
--- a/debug_toolbar/templates/debug_toolbar/base.html
+++ b/debug_toolbar/templates/debug_toolbar/base.html
@@ -11,6 +11,10 @@
data-store-id="{{ toolbar.store_id }}"
data-render-panel-url="{% url 'djdt:render_panel' %}"
{% endif %}
+ {% url 'djdt:history_sidebar' as history_url %}
+ {% if history_url %}
+ data-sidebar-url="{{ history_url }}"
+ {% endif %}
data-default-show="{% if toolbar.config.SHOW_COLLAPSED %}false{% else %}true{% endif %}"
{{ toolbar.config.ROOT_TAG_EXTRA_ATTRS|safe }}>