Skip to content

Commit 3587052

Browse files
authored
Merge pull request #1484 from ashwch/history_refresh_fix
Fixes and improvements to history views
2 parents 961b166 + 8049478 commit 3587052

File tree

3 files changed

+96
-34
lines changed

3 files changed

+96
-34
lines changed

debug_toolbar/panels/history/views.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from django.template.loader import render_to_string
33

44
from debug_toolbar.decorators import require_show_toolbar, signed_data_view
5+
from debug_toolbar.forms import SignedDataForm
56
from debug_toolbar.panels.history.forms import HistoryStoreForm
67
from debug_toolbar.toolbar import DebugToolbar
78

@@ -16,6 +17,10 @@ def history_sidebar(request, verified_data):
1617
store_id = form.cleaned_data["store_id"]
1718
toolbar = DebugToolbar.fetch(store_id)
1819
context = {}
20+
if toolbar is None:
21+
# When the store_id has been popped already due to
22+
# RESULTS_CACHE_SIZE
23+
return JsonResponse(context)
1924
for panel in toolbar.panels:
2025
if not panel.is_historical:
2126
continue
@@ -40,7 +45,8 @@ def history_refresh(request, verified_data):
4045

4146
if form.is_valid():
4247
requests = []
43-
for id, toolbar in reversed(DebugToolbar._store.items()):
48+
# Convert to list to handle mutations happenening in parallel
49+
for id, toolbar in list(DebugToolbar._store.items())[::-1]:
4450
requests.append(
4551
{
4652
"id": id,
@@ -50,7 +56,11 @@ def history_refresh(request, verified_data):
5056
"id": id,
5157
"store_context": {
5258
"toolbar": toolbar,
53-
"form": HistoryStoreForm(initial={"store_id": id}),
59+
"form": SignedDataForm(
60+
initial=HistoryStoreForm(
61+
initial={"store_id": id}
62+
).initial
63+
),
5464
},
5565
},
5666
),

debug_toolbar/static/debug_toolbar/js/history.js

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,34 +7,45 @@ $$.on(djDebug, "click", ".switchHistory", function (event) {
77
const newStoreId = this.dataset.storeId;
88
const tbody = this.closest("tbody");
99

10-
tbody
11-
.querySelector(".djdt-highlighted")
12-
.classList.remove("djdt-highlighted");
10+
const highlighted = tbody.querySelector(".djdt-highlighted");
11+
if (highlighted) {
12+
highlighted.classList.remove("djdt-highlighted");
13+
}
1314
this.closest("tr").classList.add("djdt-highlighted");
1415

1516
ajaxForm(this).then(function (data) {
1617
djDebug.setAttribute("data-store-id", newStoreId);
17-
Object.keys(data).forEach(function (panelId) {
18-
const panel = document.getElementById(panelId);
19-
if (panel) {
20-
panel.outerHTML = data[panelId].content;
21-
document.getElementById("djdt-" + panelId).outerHTML =
22-
data[panelId].button;
23-
}
24-
});
18+
// Check if response is empty, it could be due to an expired store_id.
19+
if (Object.keys(data).length === 0) {
20+
const container = document.getElementById("djdtHistoryRequests");
21+
container.querySelector(
22+
'button[data-store-id="' + newStoreId + '"]'
23+
).innerHTML = "Switch [EXPIRED]";
24+
} else {
25+
Object.keys(data).forEach(function (panelId) {
26+
const panel = document.getElementById(panelId);
27+
if (panel) {
28+
panel.outerHTML = data[panelId].content;
29+
document.getElementById("djdt-" + panelId).outerHTML =
30+
data[panelId].button;
31+
}
32+
});
33+
}
2534
});
2635
});
2736

2837
$$.on(djDebug, "click", ".refreshHistory", function (event) {
2938
event.preventDefault();
3039
const container = document.getElementById("djdtHistoryRequests");
3140
ajaxForm(this).then(function (data) {
41+
// Remove existing rows first then re-populate with new data
42+
container
43+
.querySelectorAll("tr[data-store-id]")
44+
.forEach(function (node) {
45+
node.remove();
46+
});
3247
data.requests.forEach(function (request) {
33-
if (
34-
!container.querySelector('[data-store-id="' + request.id + '"]')
35-
) {
36-
container.innerHTML = request.content + container.innerHTML;
37-
}
48+
container.innerHTML = request.content + container.innerHTML;
3849
});
3950
});
4051
});

tests/panels/test_history.py

Lines changed: 57 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import html
2+
13
from django.test import RequestFactory, override_settings
24
from django.urls import resolve, reverse
35

@@ -64,6 +66,21 @@ def test_urls(self):
6466

6567
@override_settings(DEBUG=True)
6668
class HistoryViewsTestCase(IntegrationTestCase):
69+
PANEL_KEYS = {
70+
"VersionsPanel",
71+
"TimerPanel",
72+
"SettingsPanel",
73+
"HeadersPanel",
74+
"RequestPanel",
75+
"SQLPanel",
76+
"StaticFilesPanel",
77+
"TemplatesPanel",
78+
"CachePanel",
79+
"SignalsPanel",
80+
"LoggingPanel",
81+
"ProfilingPanel",
82+
}
83+
6784
def test_history_panel_integration_content(self):
6885
"""Verify the history panel's content renders properly.."""
6986
self.assertEqual(len(DebugToolbar._store), 0)
@@ -88,26 +105,45 @@ def test_history_sidebar_invalid(self):
88105
def test_history_sidebar(self):
89106
"""Validate the history sidebar view."""
90107
self.client.get("/json_view/")
91-
store_id = list(DebugToolbar._store.keys())[0]
108+
store_id = list(DebugToolbar._store)[0]
109+
data = {"signed": SignedDataForm.sign({"store_id": store_id})}
110+
response = self.client.get(reverse("djdt:history_sidebar"), data=data)
111+
self.assertEqual(response.status_code, 200)
112+
self.assertEqual(
113+
set(response.json()),
114+
self.PANEL_KEYS,
115+
)
116+
117+
@override_settings(
118+
DEBUG_TOOLBAR_CONFIG={"RESULTS_CACHE_SIZE": 1, "RENDER_PANELS": False}
119+
)
120+
def test_history_sidebar_expired_store_id(self):
121+
"""Validate the history sidebar view."""
122+
self.client.get("/json_view/")
123+
store_id = list(DebugToolbar._store)[0]
124+
data = {"signed": SignedDataForm.sign({"store_id": store_id})}
125+
response = self.client.get(reverse("djdt:history_sidebar"), data=data)
126+
self.assertEqual(response.status_code, 200)
127+
self.assertEqual(
128+
set(response.json()),
129+
self.PANEL_KEYS,
130+
)
131+
self.client.get("/json_view/")
132+
133+
# Querying old store_id should return in empty response
92134
data = {"signed": SignedDataForm.sign({"store_id": store_id})}
93135
response = self.client.get(reverse("djdt:history_sidebar"), data=data)
94136
self.assertEqual(response.status_code, 200)
137+
self.assertEqual(response.json(), {})
138+
139+
# Querying with latest store_id
140+
latest_store_id = list(DebugToolbar._store)[0]
141+
data = {"signed": SignedDataForm.sign({"store_id": latest_store_id})}
142+
response = self.client.get(reverse("djdt:history_sidebar"), data=data)
143+
self.assertEqual(response.status_code, 200)
95144
self.assertEqual(
96-
set(response.json().keys()),
97-
{
98-
"VersionsPanel",
99-
"TimerPanel",
100-
"SettingsPanel",
101-
"HeadersPanel",
102-
"RequestPanel",
103-
"SQLPanel",
104-
"StaticFilesPanel",
105-
"TemplatesPanel",
106-
"CachePanel",
107-
"SignalsPanel",
108-
"LoggingPanel",
109-
"ProfilingPanel",
110-
},
145+
set(response.json()),
146+
self.PANEL_KEYS,
111147
)
112148

113149
def test_history_refresh_invalid_signature(self):
@@ -128,5 +164,10 @@ def test_history_refresh(self):
128164
self.assertEqual(response.status_code, 200)
129165
data = response.json()
130166
self.assertEqual(len(data["requests"]), 1)
167+
168+
store_id = list(DebugToolbar._store)[0]
169+
signature = SignedDataForm.sign({"store_id": store_id})
170+
self.assertIn(html.escape(signature), data["requests"][0]["content"])
171+
131172
for val in ["foo", "bar"]:
132173
self.assertIn(val, data["requests"][0]["content"])

0 commit comments

Comments
 (0)