Skip to content

Commit 54ff730

Browse files
committed
Support stats only version of toolbar.
Removes storing the entire toolbar in the Store.
1 parent 9cf5bfd commit 54ff730

File tree

11 files changed

+94
-62
lines changed

11 files changed

+94
-62
lines changed

debug_toolbar/panels/__init__.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,13 @@ def enabled(self):
3838
else:
3939
default = "on"
4040
# The user's cookies should override the default value
41-
return self.toolbar.request.COOKIES.get("djdt" + self.panel_id, default) == "on"
41+
if self.toolbar.request is not None:
42+
return (
43+
self.toolbar.request.COOKIES.get("djdt" + self.panel_id, default)
44+
== "on"
45+
)
46+
else:
47+
return bool(store.panel(self.toolbar.store_id, self.panel_id))
4248

4349
# Titles and content
4450

@@ -151,15 +157,17 @@ def disable_instrumentation(self):
151157

152158
# Store and retrieve stats (shared between panels for no good reason)
153159

154-
def deserialize_stats(self, data):
160+
@classmethod
161+
def deserialize_stats(cls, data):
155162
"""
156163
Deserialize stats coming from the store.
157164
158165
Provided to support future store mechanisms overriding a panel's content.
159166
"""
160167
return data
161168

162-
def serialize_stats(self, stats):
169+
@classmethod
170+
def serialize_stats(cls, stats):
163171
"""
164172
Serialize stats for the store.
165173

debug_toolbar/panels/history/panel.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,10 @@ def content(self):
8181
8282
Fetch every store for the toolbar and include it in the template.
8383
"""
84-
stores = OrderedDict()
85-
for id, toolbar in reversed(store.all()):
86-
stores[id] = {
87-
"toolbar": toolbar,
84+
histories = OrderedDict()
85+
for id in reversed(store.ids()):
86+
histories[id] = {
87+
"stats": self.deserialize_stats(store.panel(id, self.panel_id)),
8888
"form": SignedDataForm(
8989
initial=HistoryStoreForm(initial={"store_id": id}).initial
9090
),
@@ -94,10 +94,10 @@ def content(self):
9494
self.template,
9595
{
9696
"current_store_id": self.toolbar.store_id,
97-
"stores": stores,
97+
"histories": histories,
9898
"refresh_form": SignedDataForm(
9999
initial=HistoryStoreForm(
100-
initial={"store_id": self.toolbar.store_id}
100+
initial={"store_id": str(self.toolbar.store_id)}
101101
).initial
102102
),
103103
},

debug_toolbar/panels/history/views.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from debug_toolbar.forms import SignedDataForm
66
from debug_toolbar.panels.history.forms import HistoryStoreForm
77
from debug_toolbar.store import store
8+
from debug_toolbar.toolbar import stats_only_toolbar
89

910

1011
@require_show_toolbar
@@ -15,7 +16,8 @@ def history_sidebar(request, verified_data):
1516

1617
if form.is_valid():
1718
store_id = form.cleaned_data["store_id"]
18-
toolbar = store.get(store_id)
19+
toolbar = stats_only_toolbar(store_id)
20+
1921
context = {}
2022
if toolbar is None:
2123
# When the store_id has been popped already due to
@@ -25,6 +27,7 @@ def history_sidebar(request, verified_data):
2527
if not panel.is_historical:
2628
continue
2729
panel_context = {"panel": panel}
30+
2831
context[panel.panel_id] = {
2932
"button": render_to_string(
3033
"debug_toolbar/includes/panel_button.html", panel_context
@@ -45,16 +48,19 @@ def history_refresh(request, verified_data):
4548

4649
if form.is_valid():
4750
requests = []
48-
for id, toolbar in list(reversed(store.all())):
51+
for id in reversed(store.ids()):
52+
toolbar = stats_only_toolbar(id)
4953
requests.append(
5054
{
5155
"id": id,
5256
"content": render_to_string(
5357
"debug_toolbar/panels/history_tr.html",
5458
{
5559
"id": id,
56-
"store_context": {
57-
"toolbar": toolbar,
60+
"history": {
61+
"stats": toolbar.get_panel_by_id(
62+
"HistoryPanel"
63+
).get_stats(),
5864
"form": SignedDataForm(
5965
initial=HistoryStoreForm(
6066
initial={"store_id": id}

debug_toolbar/store.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import json
2-
from collections import OrderedDict, defaultdict
2+
from collections import defaultdict
33

44
from django.core.serializers.json import DjangoJSONEncoder
55
from django.utils.module_loading import import_string
@@ -31,47 +31,47 @@ class BaseStore:
3131
config = dt_settings.get_config().copy()
3232

3333
@classmethod
34-
def get(cls, store_id):
34+
def ids(cls):
3535
raise NotImplementedError
3636

3737
@classmethod
38-
def all(cls):
38+
def exists(cls, store_id):
3939
raise NotImplementedError
4040

4141
@classmethod
42-
def set(cls, store_id, toolbar):
42+
def set(cls, store_id):
4343
raise NotImplementedError
4444

4545
@classmethod
4646
def delete(cls, store_id):
4747
raise NotImplementedError
4848

49-
@classmethod
50-
def record_stats(cls, store_id, panel_id, stats):
51-
raise NotImplementedError
52-
5349

5450
class MemoryStore(BaseStore):
55-
_store = OrderedDict()
51+
_ids = list()
5652
_stats = defaultdict(dict)
5753

5854
@classmethod
59-
def get(cls, store_id):
60-
return cls._store.get(store_id)
55+
def ids(cls):
56+
return cls._ids
6157

6258
@classmethod
63-
def all(cls):
64-
return cls._store.items()
59+
def exists(cls, store_id):
60+
return store_id in cls._ids
6561

6662
@classmethod
67-
def set(cls, store_id, toolbar):
68-
cls._store[store_id] = toolbar
69-
for _ in range(cls.config["RESULTS_CACHE_SIZE"], len(cls._store)):
70-
cls._store.popitem(last=False)
63+
def set(cls, store_id):
64+
if store_id not in cls._ids:
65+
cls._ids.append(store_id)
66+
if len(cls._ids) > cls.config["RESULTS_CACHE_SIZE"]:
67+
cls.delete(cls._ids[0])
7168

7269
@classmethod
7370
def delete(cls, store_id):
74-
del cls._store[store_id]
71+
if store_id in cls._stats:
72+
del cls._stats[store_id]
73+
if store_id in cls._ids:
74+
cls._ids.remove(store_id)
7575

7676
@classmethod
7777
def save_panel(cls, store_id, panel_id, stats=None):

debug_toolbar/templates/debug_toolbar/panels/history.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
</tr>
1616
</thead>
1717
<tbody id="djdtHistoryRequests">
18-
{% for id, store_context in stores.items %}
18+
{% for id, history in histories.items %}
1919
{% include "debug_toolbar/panels/history_tr.html" %}
2020
{% endfor %}
2121
</tbody>

debug_toolbar/templates/debug_toolbar/panels/history_tr.html

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
{% load i18n %}
22
<tr class="{% if id == current_store_id %}djdt-highlighted{% endif %}" id="historyMain_{{ id }}" data-store-id="{{ id }}">
33
<td>
4-
{{ store_context.toolbar.stats.HistoryPanel.time|escape }}
4+
{{ history.stats.time|escape }}
55
</td>
66
<td>
7-
<p>{{ store_context.toolbar.stats.HistoryPanel.request_method|escape }}</p>
7+
<p>{{ history.stats.request_method|escape }}</p>
88
</td>
99
<td>
10-
<p>{{ store_context.toolbar.stats.HistoryPanel.request_url|truncatechars:100|escape }}</p>
10+
<p>{{ history.stats.request_url|truncatechars:100|escape }}</p>
1111
</td>
1212
<td>
1313
<button type="button" class="djToggleSwitch" data-toggle-name="historyMain" data-toggle-id="{{ id }}">+</button>
@@ -24,7 +24,7 @@
2424
</tr>
2525
</thead>
2626
<tbody>
27-
{% for key, value in store_context.toolbar.stats.HistoryPanel.data.items %}
27+
{% for key, value in history.stats.data.items %}
2828
<tr>
2929
<td><code>{{ key|pprint }}</code></td>
3030
<td><code>{{ value|pprint }}</code></td>
@@ -43,7 +43,7 @@
4343
</td>
4444
<td class="djdt-actions">
4545
<form method="get" action="{% url 'djdt:history_sidebar' %}">
46-
{{ store_context.form }}
46+
{{ history.form }}
4747
<button data-store-id="{{ id }}" class="switchHistory">Switch</button>
4848
</form>
4949
</td>

debug_toolbar/toolbar.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818

1919

2020
class DebugToolbar:
21-
def __init__(self, request, get_response):
21+
def __init__(self, request, get_response, store_id=None):
22+
self.store_id = store_id or uuid.uuid4().hex
2223
self.request = request
2324
self.config = dt_settings.get_config().copy()
2425
panels = []
@@ -34,7 +35,6 @@ def __init__(self, request, get_response):
3435
self._panels[panel.panel_id] = panel
3536
self.stats = {}
3637
self.server_timing_stats = {}
37-
self.store_id = uuid.uuid4().hex
3838

3939
# Manage panels
4040

@@ -64,8 +64,7 @@ def render_toolbar(self):
6464
"""
6565
Renders the overall Toolbar with panels inside.
6666
"""
67-
if not self.should_render_panels():
68-
self.store()
67+
self.store()
6968
try:
7069
context = {"toolbar": self}
7170
return render_to_string("debug_toolbar/base.html", context)
@@ -90,7 +89,7 @@ def should_render_panels(self):
9089
return render_panels
9190

9291
def store(self):
93-
store.set(self.store_id, self)
92+
store.set(self.store_id)
9493

9594
# Manually implement class-level caching of panel classes and url patterns
9695
# because it's more obvious than going through an abstraction.
@@ -141,5 +140,9 @@ def is_toolbar_request(cls, request):
141140
return resolver_match.namespaces and resolver_match.namespaces[-1] == app_name
142141

143142

143+
def stats_only_toolbar(store_id):
144+
return DebugToolbar(request=None, get_response=lambda r: r, store_id=store_id)
145+
146+
144147
app_name = "djdt"
145148
urlpatterns = DebugToolbar.get_urls()

debug_toolbar/views.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,22 @@
44

55
from debug_toolbar.decorators import require_show_toolbar
66
from debug_toolbar.store import store
7+
from debug_toolbar.toolbar import stats_only_toolbar
78

89

910
@require_show_toolbar
1011
def render_panel(request):
1112
"""Render the contents of a panel"""
12-
toolbar = store.get(request.GET["store_id"])
13-
if toolbar is None:
13+
store_id = request.GET["store_id"]
14+
if not store.exists(store_id):
1415
content = _(
1516
"Data for this panel isn't available anymore. "
1617
"Please reload the page and retry."
1718
)
1819
content = "<p>%s</p>" % escape(content)
1920
scripts = []
2021
else:
22+
toolbar = stats_only_toolbar(store_id)
2123
panel = toolbar.get_panel_by_id(request.GET["panel_id"])
2224
content = panel.content
2325
scripts = panel.scripts

tests/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,6 @@ def setUp(self):
5353
# The HistoryPanel keeps track of previous stores in memory.
5454
# This bleeds into other tests and violates their idempotency.
5555
# Clear the store before each test.
56-
for key, _ in list(store.all()):
56+
for key in list(store.ids()):
5757
store.delete(key)
5858
super().setUp()

tests/panels/test_history.py

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from debug_toolbar.forms import SignedDataForm
77
from debug_toolbar.store import store
8+
from debug_toolbar.toolbar import stats_only_toolbar
89

910
from ..base import BaseTestCase, IntegrationTestCase
1011

@@ -83,14 +84,14 @@ class HistoryViewsTestCase(IntegrationTestCase):
8384

8485
def test_history_panel_integration_content(self):
8586
"""Verify the history panel's content renders properly.."""
86-
self.assertEqual(len(store.all()), 0)
87+
self.assertEqual(len(store.ids()), 0)
8788

8889
data = {"foo": "bar"}
8990
self.client.get("/json_view/", data, content_type="application/json")
9091

9192
# Check the history panel's stats to verify the toolbar rendered properly.
92-
self.assertEqual(len(store.all()), 1)
93-
toolbar = list(store.all())[0][1]
93+
self.assertEqual(len(store.ids()), 1)
94+
toolbar = stats_only_toolbar(store.ids()[0])
9495
content = toolbar.get_panel_by_id("HistoryPanel").content
9596
self.assertIn("bar", content)
9697

@@ -99,21 +100,34 @@ def test_history_sidebar_invalid(self):
99100
self.assertEqual(response.status_code, 400)
100101

101102
self.client.get("/json_view/")
102-
store_id = list(store.all())[0][0]
103+
store_id = store.ids()[0]
103104
data = {"signed": SignedDataForm.sign({"store_id": store_id}) + "invalid"}
104105
response = self.client.get(reverse("djdt:history_sidebar"), data=data)
105106
self.assertEqual(response.status_code, 400)
106107

107108
def test_history_sidebar_hash(self):
108109
"""Validate the hashing mechanism."""
109110
self.client.get("/json_view/")
110-
store_id = list(store.all())[0][0]
111+
store_id = store.ids()[0]
111112
data = {"signed": SignedDataForm.sign({"store_id": store_id})}
112113
response = self.client.get(reverse("djdt:history_sidebar"), data=data)
113114
self.assertEqual(response.status_code, 200)
114115
self.assertEqual(
115-
set(response.json()),
116-
self.PANEL_KEYS,
116+
list(response.json().keys()),
117+
[
118+
"VersionsPanel",
119+
"TimerPanel",
120+
"SettingsPanel",
121+
"HeadersPanel",
122+
"RequestPanel",
123+
"SQLPanel",
124+
"StaticFilesPanel",
125+
"TemplatesPanel",
126+
"CachePanel",
127+
"SignalsPanel",
128+
"LoggingPanel",
129+
"ProfilingPanel",
130+
],
117131
)
118132

119133
@override_settings(
@@ -122,7 +136,7 @@ def test_history_sidebar_hash(self):
122136
def test_history_sidebar_expired_store_id(self):
123137
"""Validate the history sidebar view."""
124138
self.client.get("/json_view/")
125-
store_id = list(store.all())[0][0]
139+
store_id = list(store.ids())[0]
126140
data = {"signed": SignedDataForm.sign({"store_id": store_id})}
127141
response = self.client.get(reverse("djdt:history_sidebar"), data=data)
128142
self.assertEqual(response.status_code, 200)
@@ -142,7 +156,7 @@ def test_history_sidebar_expired_store_id(self):
142156
)
143157

144158
# Querying with latest store_id
145-
latest_store_id = list(store.all())[-1][0]
159+
latest_store_id = store.ids()[-1]
146160
self.assertNotEqual(latest_store_id, store_id)
147161
data = {"signed": SignedDataForm.sign({"store_id": latest_store_id})}
148162
response = self.client.get(reverse("djdt:history_sidebar"), data=data)
@@ -170,7 +184,7 @@ def test_history_refresh(self):
170184
data = response.json()
171185
self.assertEqual(len(data["requests"]), 1)
172186

173-
store_id = list(store.all())[0][0]
187+
store_id = store.ids()[0]
174188
signature = SignedDataForm.sign({"store_id": store_id})
175189
self.assertIn(html.escape(signature), data["requests"][0]["content"])
176190

0 commit comments

Comments
 (0)