Skip to content

Commit 3cb0970

Browse files
committed
Create basic memory store for toolbar to reproduce existing functionality.
1 parent e20ac73 commit 3cb0970

File tree

9 files changed

+90
-38
lines changed

9 files changed

+90
-38
lines changed

debug_toolbar/panels/history/panel.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from debug_toolbar.panels import Panel
1313
from debug_toolbar.panels.history import views
1414
from debug_toolbar.panels.history.forms import HistoryStoreForm
15+
from debug_toolbar.store import store
1516

1617

1718
class HistoryPanel(Panel):
@@ -81,7 +82,7 @@ def content(self):
8182
Fetch every store for the toolbar and include it in the template.
8283
"""
8384
stores = OrderedDict()
84-
for id, toolbar in reversed(self.toolbar._store.items()):
85+
for id, toolbar in reversed(store.all()):
8586
stores[id] = {
8687
"toolbar": toolbar,
8788
"form": SignedDataForm(

debug_toolbar/panels/history/views.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from debug_toolbar.decorators import require_show_toolbar, signed_data_view
55
from debug_toolbar.forms import SignedDataForm
66
from debug_toolbar.panels.history.forms import HistoryStoreForm
7-
from debug_toolbar.toolbar import DebugToolbar
7+
from debug_toolbar.store import store
88

99

1010
@require_show_toolbar
@@ -15,7 +15,7 @@ def history_sidebar(request, verified_data):
1515

1616
if form.is_valid():
1717
store_id = form.cleaned_data["store_id"]
18-
toolbar = DebugToolbar.fetch(store_id)
18+
toolbar = store.get(store_id)
1919
context = {}
2020
if toolbar is None:
2121
# When the store_id has been popped already due to
@@ -45,8 +45,7 @@ def history_refresh(request, verified_data):
4545

4646
if form.is_valid():
4747
requests = []
48-
# Convert to list to handle mutations happenening in parallel
49-
for id, toolbar in list(DebugToolbar._store.items())[::-1]:
48+
for id, toolbar in list(reversed(store.all())):
5049
requests.append(
5150
{
5251
"id": id,

debug_toolbar/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"ROOT_TAG_EXTRA_ATTRS": "",
2222
"SHOW_COLLAPSED": False,
2323
"SHOW_TOOLBAR_CALLBACK": "debug_toolbar.middleware.show_toolbar",
24+
"TOOLBAR_STORE_CLASS": "debug_toolbar.store.MemoryStore",
2425
# Panel options
2526
"EXTRA_SIGNALS": [],
2627
"ENABLE_STACKTRACES": True,

debug_toolbar/store.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from collections import OrderedDict
2+
3+
from django.utils.module_loading import import_string
4+
5+
from debug_toolbar import settings as dt_settings
6+
7+
8+
class BaseStore:
9+
config = dt_settings.get_config().copy()
10+
11+
@classmethod
12+
def get(cls, store_id):
13+
raise NotImplementedError
14+
15+
@classmethod
16+
def all(cls):
17+
raise NotImplementedError
18+
19+
@classmethod
20+
def set(cls, store_id, toolbar):
21+
raise NotImplementedError
22+
23+
@classmethod
24+
def delete(cls, store_id):
25+
raise NotImplementedError
26+
27+
28+
class MemoryStore(BaseStore):
29+
_store = OrderedDict()
30+
31+
@classmethod
32+
def get(cls, store_id):
33+
return cls._store.get(store_id)
34+
35+
@classmethod
36+
def all(cls):
37+
return cls._store.items()
38+
39+
@classmethod
40+
def set(cls, store_id, toolbar):
41+
cls._store[store_id] = toolbar
42+
for _ in range(cls.config["RESULTS_CACHE_SIZE"], len(cls._store)):
43+
cls._store.popitem(last=False)
44+
45+
@classmethod
46+
def delete(cls, store_id):
47+
del cls._store[store_id]
48+
49+
50+
store = import_string(dt_settings.get_config()["TOOLBAR_STORE_CLASS"])

debug_toolbar/toolbar.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from django.utils.module_loading import import_string
1515

1616
from debug_toolbar import settings as dt_settings
17+
from debug_toolbar.store import store
1718

1819

1920
class DebugToolbar:
@@ -88,22 +89,12 @@ def should_render_panels(self):
8889
render_panels = self.request.META["wsgi.multiprocess"]
8990
return render_panels
9091

91-
# Handle storing toolbars in memory and fetching them later on
92-
93-
_store = OrderedDict()
94-
9592
def store(self):
9693
# Store already exists.
9794
if self.store_id:
9895
return
9996
self.store_id = uuid.uuid4().hex
100-
self._store[self.store_id] = self
101-
for _ in range(self.config["RESULTS_CACHE_SIZE"], len(self._store)):
102-
self._store.popitem(last=False)
103-
104-
@classmethod
105-
def fetch(cls, store_id):
106-
return cls._store.get(store_id)
97+
store.set(self.store_id, self)
10798

10899
# Manually implement class-level caching of panel classes and url patterns
109100
# because it's more obvious than going through an abstraction.

debug_toolbar/views.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
from django.utils.translation import gettext as _
44

55
from debug_toolbar.decorators import require_show_toolbar
6-
from debug_toolbar.toolbar import DebugToolbar
6+
from debug_toolbar.store import store
77

88

99
@require_show_toolbar
1010
def render_panel(request):
1111
"""Render the contents of a panel"""
12-
toolbar = DebugToolbar.fetch(request.GET["store_id"])
12+
toolbar = store.get(request.GET["store_id"])
1313
if toolbar is None:
1414
content = _(
1515
"Data for this panel isn't available anymore. "

tests/base.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from django.http import HttpResponse
33
from django.test import RequestFactory, TestCase
44

5+
from debug_toolbar.store import store
56
from debug_toolbar.toolbar import DebugToolbar
67

78
rf = RequestFactory()
@@ -52,6 +53,6 @@ def setUp(self):
5253
# The HistoryPanel keeps track of previous stores in memory.
5354
# This bleeds into other tests and violates their idempotency.
5455
# Clear the store before each test.
55-
for key in list(DebugToolbar._store.keys()):
56-
del DebugToolbar._store[key]
56+
for key, _ in list(store.all()):
57+
store.delete(key)
5758
super().setUp()

tests/panels/test_history.py

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from django.urls import resolve, reverse
55

66
from debug_toolbar.forms import SignedDataForm
7-
from debug_toolbar.toolbar import DebugToolbar
7+
from debug_toolbar.store import store
88

99
from ..base import BaseTestCase, IntegrationTestCase
1010

@@ -83,29 +83,31 @@ class HistoryViewsTestCase(IntegrationTestCase):
8383

8484
def test_history_panel_integration_content(self):
8585
"""Verify the history panel's content renders properly.."""
86-
self.assertEqual(len(DebugToolbar._store), 0)
86+
self.assertEqual(len(store.all()), 0)
8787

8888
data = {"foo": "bar"}
8989
self.client.get("/json_view/", data, content_type="application/json")
9090

9191
# Check the history panel's stats to verify the toolbar rendered properly.
92-
self.assertEqual(len(DebugToolbar._store), 1)
93-
toolbar = list(DebugToolbar._store.values())[0]
92+
self.assertEqual(len(store.all()), 1)
93+
toolbar = list(store.all())[0][1]
9494
content = toolbar.get_panel_by_id("HistoryPanel").content
9595
self.assertIn("bar", content)
9696

9797
def test_history_sidebar_invalid(self):
9898
response = self.client.get(reverse("djdt:history_sidebar"))
9999
self.assertEqual(response.status_code, 400)
100100

101-
data = {"signed": SignedDataForm.sign({"store_id": "foo"}) + "invalid"}
101+
self.client.get("/json_view/")
102+
store_id = list(store.all())[0][0]
103+
data = {"signed": SignedDataForm.sign({"store_id": store_id}) + "invalid"}
102104
response = self.client.get(reverse("djdt:history_sidebar"), data=data)
103105
self.assertEqual(response.status_code, 400)
104106

105-
def test_history_sidebar(self):
106-
"""Validate the history sidebar view."""
107+
def test_history_sidebar_hash(self):
108+
"""Validate the hashing mechanism."""
107109
self.client.get("/json_view/")
108-
store_id = list(DebugToolbar._store)[0]
110+
store_id = list(store.all())[0][0]
109111
data = {"signed": SignedDataForm.sign({"store_id": store_id})}
110112
response = self.client.get(reverse("djdt:history_sidebar"), data=data)
111113
self.assertEqual(response.status_code, 200)
@@ -120,7 +122,7 @@ def test_history_sidebar(self):
120122
def test_history_sidebar_expired_store_id(self):
121123
"""Validate the history sidebar view."""
122124
self.client.get("/json_view/")
123-
store_id = list(DebugToolbar._store)[0]
125+
store_id = list(store.all())[0][0]
124126
data = {"signed": SignedDataForm.sign({"store_id": store_id})}
125127
response = self.client.get(reverse("djdt:history_sidebar"), data=data)
126128
self.assertEqual(response.status_code, 200)
@@ -130,14 +132,18 @@ def test_history_sidebar_expired_store_id(self):
130132
)
131133
self.client.get("/json_view/")
132134

133-
# Querying old store_id should return in empty response
135+
# Querying previous store_id should still work
134136
data = {"signed": SignedDataForm.sign({"store_id": store_id})}
135137
response = self.client.get(reverse("djdt:history_sidebar"), data=data)
136138
self.assertEqual(response.status_code, 200)
137-
self.assertEqual(response.json(), {})
139+
self.assertEqual(
140+
set(response.json()),
141+
self.PANEL_KEYS,
142+
)
138143

139144
# Querying with latest store_id
140-
latest_store_id = list(DebugToolbar._store)[0]
145+
latest_store_id = list(store.all())[-1][0]
146+
self.assertNotEqual(latest_store_id, store_id)
141147
data = {"signed": SignedDataForm.sign({"store_id": latest_store_id})}
142148
response = self.client.get(reverse("djdt:history_sidebar"), data=data)
143149
self.assertEqual(response.status_code, 200)
@@ -157,15 +163,14 @@ def test_history_refresh_invalid_signature(self):
157163

158164
def test_history_refresh(self):
159165
"""Verify refresh history response has request variables."""
160-
data = {"foo": "bar"}
161-
self.client.get("/json_view/", data, content_type="application/json")
166+
self.client.get("/json_view/", {"foo": "bar"}, content_type="application/json")
162167
data = {"signed": SignedDataForm.sign({"store_id": "foo"})}
163168
response = self.client.get(reverse("djdt:history_refresh"), data=data)
164169
self.assertEqual(response.status_code, 200)
165170
data = response.json()
166171
self.assertEqual(len(data["requests"]), 1)
167172

168-
store_id = list(DebugToolbar._store)[0]
173+
store_id = list(store.all())[0][0]
169174
signature = SignedDataForm.sign({"store_id": store_id})
170175
self.assertIn(html.escape(signature), data["requests"][0]["content"])
171176

tests/test_integration.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from debug_toolbar.forms import SignedDataForm
1717
from debug_toolbar.middleware import DebugToolbarMiddleware, show_toolbar
1818
from debug_toolbar.panels import Panel
19+
from debug_toolbar.store import store
1920
from debug_toolbar.toolbar import DebugToolbar
2021

2122
from .base import BaseTestCase, IntegrationTestCase
@@ -207,15 +208,15 @@ def get_response(request):
207208

208209
def test_middleware_render_toolbar_json(self):
209210
"""Verify the toolbar is rendered and data is stored for a json request."""
210-
self.assertEqual(len(DebugToolbar._store), 0)
211+
self.assertEqual(len(store.all()), 0)
211212

212213
data = {"foo": "bar"}
213214
response = self.client.get("/json_view/", data, content_type="application/json")
214215
self.assertEqual(response.status_code, 200)
215216
self.assertEqual(response.content.decode("utf-8"), '{"foo": "bar"}')
216217
# Check the history panel's stats to verify the toolbar rendered properly.
217-
self.assertEqual(len(DebugToolbar._store), 1)
218-
toolbar = list(DebugToolbar._store.values())[0]
218+
self.assertEqual(len(store.all()), 1)
219+
toolbar = list(store.all())[0][1]
219220
self.assertEqual(
220221
toolbar.get_panel_by_id("HistoryPanel").get_stats()["data"],
221222
{"foo": ["bar"]},
@@ -523,6 +524,8 @@ def test_rerender_on_history_switch(self):
523524

524525
@override_settings(DEBUG_TOOLBAR_CONFIG={"RESULTS_CACHE_SIZE": 0})
525526
def test_expired_store(self):
527+
original_value = store.config["RESULTS_CACHE_SIZE"]
528+
store.config["RESULTS_CACHE_SIZE"] = 0
526529
self.get("/regular/basic/")
527530
version_panel = self.selenium.find_element_by_id("VersionsPanel")
528531

@@ -534,6 +537,7 @@ def test_expired_store(self):
534537
lambda selenium: version_panel.find_element_by_tag_name("p")
535538
)
536539
self.assertIn("Data for this panel isn't available anymore.", error.text)
540+
store.config["RESULTS_CACHE_SIZE"] = original_value
537541

538542
@override_settings(
539543
TEMPLATES=[

0 commit comments

Comments
 (0)