Skip to content

Commit 9ee6c20

Browse files
committed
Merge remote-tracking branch 'origin/serializable'
* origin/serializable: (25 commits) Removed unnecessary SQLPanel.record_stats Updated replaceToolbarState to use request id. Move serializable changes into the main change log. Fixes #2073 -- Added DatabaseStore for persistent debug data storage. (#2121) Added check for pytest as test runner for IS_RUNNING_TESTS. Improve clarity of record_stats for serialization. (#1965) Hack: Sleep before checking to see if the history panel auto updated. Comment out the async button because it breaks the wsgi app. Fix tests for serializable changes with selenium. Avoid caching the config settings. Make template panel serializable. Rework the alerts panel to be compatible with serialization. Extend example app to have an async version. Update all panels to use data from get_stats on render Support serialization of FunctionCall Force everything to a string if it can't be serialized. Make Panel.panel_id a classmember. Support serializable sql panel Support serializable panels. This is a WIP and needs clean-up. Rename store_id variants to request_id ...
2 parents afcc4e1 + 2fafbb4 commit 9ee6c20

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+1386
-460
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ htmlcov
1212
.tox
1313
geckodriver.log
1414
coverage.xml
15+
venv
1516
.direnv/
1617
.envrc
1718
venv

Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ example: ## Run the example application
77
--noinput --username="$(USER)" --email="$(USER)@mailinator.com"
88
python example/manage.py runserver
99

10+
example_async:
11+
python example/manage.py migrate --noinput
12+
-DJANGO_SUPERUSER_PASSWORD=p python example/manage.py createsuperuser \
13+
--noinput --username="$(USER)" --email="$(USER)@mailinator.com"
14+
daphne example.asgi:application
15+
1016
example_test: ## Run the test suite for the example application
1117
python example/manage.py test example
1218

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from django.db import migrations, models
2+
3+
4+
class Migration(migrations.Migration):
5+
initial = True
6+
7+
operations = [
8+
migrations.CreateModel(
9+
name="HistoryEntry",
10+
fields=[
11+
(
12+
"request_id",
13+
models.UUIDField(primary_key=True, serialize=False),
14+
),
15+
("data", models.JSONField(default=dict)),
16+
("created_at", models.DateTimeField(auto_now_add=True)),
17+
],
18+
options={
19+
"verbose_name": "history entry",
20+
"verbose_name_plural": "history entries",
21+
"ordering": ["-created_at"],
22+
},
23+
),
24+
]

debug_toolbar/migrations/__init__.py

Whitespace-only changes.

debug_toolbar/models.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from django.db import models
2+
from django.utils.translation import gettext_lazy as _
3+
4+
5+
class HistoryEntry(models.Model):
6+
request_id = models.UUIDField(primary_key=True)
7+
data = models.JSONField(default=dict)
8+
created_at = models.DateTimeField(auto_now_add=True)
9+
10+
class Meta:
11+
verbose_name = _("history entry")
12+
verbose_name_plural = _("history entries")
13+
ordering = ["-created_at"]
14+
15+
def __str__(self):
16+
return str(self.request_id)

debug_toolbar/panels/__init__.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from django.core.handlers.asgi import ASGIRequest
22
from django.template.loader import render_to_string
3+
from django.utils.functional import classproperty
34

45
from debug_toolbar import settings as dt_settings
56
from debug_toolbar.utils import get_name_from_obj
@@ -15,19 +16,27 @@ class Panel:
1516
def __init__(self, toolbar, get_response):
1617
self.toolbar = toolbar
1718
self.get_response = get_response
19+
self.from_store = False
1820

1921
# Private panel properties
2022

21-
@property
22-
def panel_id(self):
23-
return self.__class__.__name__
23+
@classproperty
24+
def panel_id(cls):
25+
return cls.__name__
2426

2527
@property
2628
def enabled(self) -> bool:
27-
# check if the panel is async compatible
29+
# Check if the panel is async compatible
2830
if not self.is_async and isinstance(self.toolbar.request, ASGIRequest):
2931
return False
3032

33+
if self.from_store:
34+
# If the toolbar was loaded from the store the existence of
35+
# recorded data indicates whether it was enabled or not.
36+
# We can't use the remainder of the logic since we don't have
37+
# a request to work off of.
38+
return bool(self.get_stats())
39+
3140
# The user's cookies should override the default value
3241
cookie_value = self.toolbar.request.COOKIES.get("djdt" + self.panel_id)
3342
if cookie_value is not None:
@@ -175,9 +184,16 @@ def record_stats(self, stats):
175184
"""
176185
Store data gathered by the panel. ``stats`` is a :class:`dict`.
177186
178-
Each call to ``record_stats`` updates the statistics dictionary.
187+
Each call to ``record_stats`` updates the store's data for
188+
the panel.
189+
190+
To support backwards compatibility, it will also update the
191+
panel's statistics dictionary.
179192
"""
180193
self.toolbar.stats.setdefault(self.panel_id, {}).update(stats)
194+
self.toolbar.store.save_panel(
195+
self.toolbar.request_id, self.panel_id, self.toolbar.stats[self.panel_id]
196+
)
181197

182198
def get_stats(self):
183199
"""
@@ -261,6 +277,15 @@ def generate_server_timing(self, request, response):
261277
Does not return a value.
262278
"""
263279

280+
def load_stats_from_store(self, data):
281+
"""
282+
Instantiate the panel from serialized data.
283+
284+
Return the panel instance.
285+
"""
286+
self.toolbar.stats.setdefault(self.panel_id, {}).update(data)
287+
self.from_store = True
288+
264289
@classmethod
265290
def run_checks(cls):
266291
"""

debug_toolbar/panels/alerts.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,9 @@ def check_invalid_file_form_configuration(self, html_content):
141141
return self.alerts
142142

143143
def generate_stats(self, request, response):
144-
if not is_processable_html_response(response):
145-
return
146-
147-
html_content = response.content.decode(response.charset)
148-
self.check_invalid_file_form_configuration(html_content)
144+
if is_processable_html_response(response):
145+
html_content = response.content.decode(response.charset)
146+
self.check_invalid_file_form_configuration(html_content)
149147

150148
# Further alert checks can go here
151149

debug_toolbar/panels/cache.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -171,16 +171,17 @@ def _record_call(self, cache, name, original_method, args, kwargs):
171171

172172
@property
173173
def nav_subtitle(self):
174-
cache_calls = len(self.calls)
174+
stats = self.get_stats()
175+
cache_calls = len(stats.get("calls"))
175176
return ngettext(
176177
"%(cache_calls)d call in %(time).2fms",
177178
"%(cache_calls)d calls in %(time).2fms",
178179
cache_calls,
179-
) % {"cache_calls": cache_calls, "time": self.total_time}
180+
) % {"cache_calls": cache_calls, "time": stats.get("total_time")}
180181

181182
@property
182183
def title(self):
183-
count = len(getattr(settings, "CACHES", ["default"]))
184+
count = self.get_stats().get("total_caches")
184185
return ngettext(
185186
"Cache calls from %(count)d backend",
186187
"Cache calls from %(count)d backends",
@@ -216,6 +217,7 @@ def generate_stats(self, request, response):
216217
"hits": self.hits,
217218
"misses": self.misses,
218219
"counts": self.counts,
220+
"total_caches": len(getattr(settings, "CACHES", ["default"])),
219221
}
220222
)
221223

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
from debug_toolbar.panels.history.panel import HistoryPanel
22

3-
__all__ = ["HistoryPanel"]
3+
__all__ = [HistoryPanel.panel_id]

debug_toolbar/panels/history/forms.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ class HistoryStoreForm(forms.Form):
55
"""
66
Validate params
77
8-
store_id: The key for the store instance to be fetched.
8+
request_id: The key for the store instance to be fetched.
99
"""
1010

11-
store_id = forms.CharField(widget=forms.HiddenInput())
11+
request_id = forms.CharField(widget=forms.HiddenInput())
1212
exclude_history = forms.BooleanField(widget=forms.HiddenInput(), required=False)

0 commit comments

Comments
 (0)