Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 18 additions & 9 deletions debug_toolbar/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,9 @@ def _postprocess(
# included in the response.
rendered = toolbar.render_toolbar()

for header, value in self.get_headers(request, toolbar.enabled_panels).items():
for header, value in self.get_headers(
request, response, toolbar.enabled_panels
).items():
response.headers[header] = value

# Check for responses where the toolbar can't be inserted.
Expand All @@ -214,12 +216,19 @@ def _postprocess(
return response

@staticmethod
def get_headers(request: HttpRequest, panels: list[Panel]) -> dict[str, str]:
headers = {}
def get_headers(
request: HttpRequest, response: HttpResponse, panels: list[Panel]
) -> dict[str, str]:
update, append = {}, {}
for panel in panels:
for header, value in panel.get_headers(request).items():
if header in headers:
headers[header] += f", {value}"
else:
headers[header] = value
return headers
panel_headers = panel.get_headers(request)
update.update(panel_headers.update)
for key, value in panel_headers.append.items():
append.setdefault(key, response.headers.get(key, ""))
append[key] += f", {value}"
# Only include the append headers when no panel updated the value
# entirely.
for key, value in append.items():
if key not in update:
update[key] = value
return update
17 changes: 15 additions & 2 deletions debug_toolbar/panels/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from dataclasses import dataclass, field

from django.core.handlers.asgi import ASGIRequest
from django.template.loader import render_to_string
from django.utils.functional import classproperty
Expand All @@ -7,6 +9,17 @@
from debug_toolbar.utils import get_name_from_obj


@dataclass
class PanelHeaders:
"""
A data-structure to allow panels to update headers or append to existing
values.
"""

append: dict[str, str] = field(default_factory=dict)
update: dict[str, str] = field(default_factory=dict)


class Panel:
"""
Base class for panels.
Expand Down Expand Up @@ -230,7 +243,7 @@ def process_request(self, request):
"""
return self.get_response(request)

def get_headers(self, request):
def get_headers(self, request) -> PanelHeaders:
"""
Get headers the panel needs to set.

Expand All @@ -254,7 +267,7 @@ def get_headers(self, request):
)
for key, record in stats.items()
)
return headers
return PanelHeaders(append=headers)

def generate_stats(self, request, response):
"""
Expand Down
8 changes: 4 additions & 4 deletions debug_toolbar/panels/history/panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from django.utils import timezone
from django.utils.translation import gettext_lazy as _

from debug_toolbar.panels import Panel
from debug_toolbar.panels import Panel, PanelHeaders
from debug_toolbar.panels.history import views
from debug_toolbar.panels.history.forms import HistoryStoreForm

Expand All @@ -22,12 +22,12 @@ class HistoryPanel(Panel):
nav_title = _("History")
template = "debug_toolbar/panels/history.html"

def get_headers(self, request: HttpRequest) -> dict:
headers: dict = super().get_headers(request)
def get_headers(self, request: HttpRequest) -> PanelHeaders:
headers: PanelHeaders = super().get_headers(request)
observe_request = self.toolbar.get_observe_request()
request_id = self.toolbar.request_id
if request_id and observe_request(request):
headers["djdt-request-id"] = request_id
headers.update["djdt-request-id"] = request_id
return headers

@property
Expand Down
1 change: 1 addition & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Change log
Pending
-------

* Fix Server-Timing header not being overridden if it exists in header already
* Replaced ``requirements_dev.txt`` file for ``pyproject.toml`` support with
dependency groups.
* Updated ReadTheDocs Python version to 3.13.
Expand Down
15 changes: 15 additions & 0 deletions tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,21 @@ def test_server_timing_headers(self):
for expected in expected_partials:
self.assertTrue(re.compile(expected).search(server_timing))

def test_server_timing_headers_with_existing_value(self):
response = self.client.get("/server-timing/")
server_timing = response["Server-Timing"]
expected_partials = [
'existing_key;dur=100;desc="Details"',
r'TimerPanel_utime;dur=(\d)*(\.(\d)*)?;desc="User CPU time", ',
r'TimerPanel_stime;dur=(\d)*(\.(\d)*)?;desc="System CPU time", ',
r'TimerPanel_total;dur=(\d)*(\.(\d)*)?;desc="Total CPU time", ',
r'TimerPanel_total_time;dur=(\d)*(\.(\d)*)?;desc="Elapsed time", ',
r'SQLPanel_sql_time;dur=(\d)*(\.(\d)*)?;desc="SQL 1 queries", ',
r'CachePanel_total_time;dur=0;desc="Cache 0 Calls"',
]
for expected in expected_partials:
self.assertTrue(re.compile(expected).search(server_timing))

@override_settings(DEBUG_TOOLBAR_CONFIG={"RENDER_PANELS": True})
def test_timer_panel(self):
response = self.client.get("/regular/basic/")
Expand Down
1 change: 1 addition & 0 deletions tests/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
path("ajax/", views.ajax_view),
path("login_without_redirect/", LoginView.as_view(redirect_field_name=None)),
path("csp_view/", views.csp_view),
path("server-timing/", views.server_timing),
path("admin/", admin.site.urls),
path("__debug__/", include("debug_toolbar.urls")),
]
6 changes: 6 additions & 0 deletions tests/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,9 @@ def redirect_view(request):

def ajax_view(request):
return render(request, "ajax/ajax.html")


def server_timing(request):
response = execute_sql(request)
response.headers["Server-Timing"] = 'existing_key;dur=100;desc="Details"'
return response
Loading