diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ee54d2d5d..852048216 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,10 +14,15 @@ repos: hooks: - id: doc8 - repo: https://github.com/adamchainz/django-upgrade - rev: 1.23.1 + rev: 1.24.0 hooks: - id: django-upgrade args: [--target-version, "4.2"] +- repo: https://github.com/adamchainz/djade-pre-commit + rev: "1.4.0" + hooks: + - id: djade + args: [--target-version, "4.2"] - repo: https://github.com/pre-commit/pygrep-hooks rev: v1.10.0 hooks: @@ -29,7 +34,7 @@ repos: - id: biome-check verbose: true - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 'v0.11.0' + rev: 'v0.11.7' hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] @@ -39,6 +44,6 @@ repos: hooks: - id: pyproject-fmt - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.24 + rev: v0.24.1 hooks: - id: validate-pyproject diff --git a/README.rst b/README.rst index 3c831efa7..6c7da3615 100644 --- a/README.rst +++ b/README.rst @@ -40,7 +40,7 @@ Here's a screenshot of the toolbar in action: In addition to the built-in panels, a number of third-party panels are contributed by the community. -The current stable version of the Debug Toolbar is 5.1.0. It works on +The current stable version of the Debug Toolbar is 5.2.0. It works on Django ≥ 4.2.0. The Debug Toolbar has experimental support for `Django's asynchronous views diff --git a/debug_toolbar/__init__.py b/debug_toolbar/__init__.py index 5bdaa2dd1..770c5eeed 100644 --- a/debug_toolbar/__init__.py +++ b/debug_toolbar/__init__.py @@ -4,7 +4,7 @@ # Do not use pkg_resources to find the version but set it here directly! # see issue #1446 -VERSION = "5.1.0" +VERSION = "5.2.0" # Code that discovers files or modules in INSTALLED_APPS imports this module. urls = "debug_toolbar.urls", APP_NAME diff --git a/debug_toolbar/panels/redirects.py b/debug_toolbar/panels/redirects.py index 27cce4c17..8055c67ad 100644 --- a/debug_toolbar/panels/redirects.py +++ b/debug_toolbar/panels/redirects.py @@ -22,20 +22,8 @@ def _process_response(self, response): Common response processing logic. """ if 300 <= response.status_code < 400: - redirect_to = response.get("Location") - if redirect_to: - status_line = f"{response.status_code} {response.reason_phrase}" - cookies = response.cookies - context = { - "redirect_to": redirect_to, - "status_line": status_line, - "toolbar": self.toolbar, - } - # Using SimpleTemplateResponse avoids running global context processors. - response = SimpleTemplateResponse( - "debug_toolbar/redirect.html", context - ) - response.cookies = cookies + if redirect_to := response.get("Location"): + response = self.get_interception_response(response, redirect_to) response.render() return response @@ -53,3 +41,22 @@ def process_request(self, request): if iscoroutine(response): return self.aprocess_request(request, response) return self._process_response(response) + + def get_interception_response(self, response, redirect_to): + """ + Hook method to allow subclasses to customize the interception response. + """ + status_line = f"{response.status_code} {response.reason_phrase}" + cookies = response.cookies + original_response = response + context = { + "redirect_to": redirect_to, + "status_line": status_line, + "toolbar": self.toolbar, + "original_response": original_response, + } + # Using SimpleTemplateResponse avoids running global context processors. + response = SimpleTemplateResponse("debug_toolbar/redirect.html", context) + response.cookies = cookies + response.original_response = original_response + return response diff --git a/debug_toolbar/panels/request.py b/debug_toolbar/panels/request.py index b77788637..5a24d6179 100644 --- a/debug_toolbar/panels/request.py +++ b/debug_toolbar/panels/request.py @@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _ from debug_toolbar.panels import Panel -from debug_toolbar.utils import get_name_from_obj, get_sorted_request_variable +from debug_toolbar.utils import get_name_from_obj, sanitize_and_sort_request_vars class RequestPanel(Panel): @@ -26,9 +26,9 @@ def nav_subtitle(self): def generate_stats(self, request, response): self.record_stats( { - "get": get_sorted_request_variable(request.GET), - "post": get_sorted_request_variable(request.POST), - "cookies": get_sorted_request_variable(request.COOKIES), + "get": sanitize_and_sort_request_vars(request.GET), + "post": sanitize_and_sort_request_vars(request.POST), + "cookies": sanitize_and_sort_request_vars(request.COOKIES), } ) @@ -59,13 +59,5 @@ def generate_stats(self, request, response): self.record_stats(view_info) if hasattr(request, "session"): - try: - session_list = [ - (k, request.session.get(k)) for k in sorted(request.session.keys()) - ] - except TypeError: - session_list = [ - (k, request.session.get(k)) - for k in request.session.keys() # (it's not a dict) - ] - self.record_stats({"session": {"list": session_list}}) + session_data = dict(request.session) + self.record_stats({"session": sanitize_and_sort_request_vars(session_data)}) diff --git a/debug_toolbar/panels/templates/views.py b/debug_toolbar/panels/templates/views.py index 898639c54..b8a0a376f 100644 --- a/debug_toolbar/panels/templates/views.py +++ b/debug_toolbar/panels/templates/views.py @@ -27,15 +27,18 @@ def template_source(request): template_name = request.GET.get("template", template_origin_name) final_loaders = [] - loaders = Engine.get_default().template_loaders + loaders = list(Engine.get_default().template_loaders) + + while loaders: + loader = loaders.pop(0) - for loader in loaders: if loader is not None: - # When the loader has loaders associated with it, - # append those loaders to the list. This occurs with - # django.template.loaders.cached.Loader + # Recursively unwrap loaders until we get to loaders which do not + # themselves wrap other loaders. This adds support for + # django.template.loaders.cached.Loader and the + # django-template-partials loader (possibly among others) if hasattr(loader, "loaders"): - final_loaders += loader.loaders + loaders.extend(loader.loaders) else: final_loaders.append(loader) diff --git a/debug_toolbar/settings.py b/debug_toolbar/settings.py index e0be35ea8..59d538a0b 100644 --- a/debug_toolbar/settings.py +++ b/debug_toolbar/settings.py @@ -45,7 +45,6 @@ "TOOLBAR_LANGUAGE": None, "IS_RUNNING_TESTS": "test" in sys.argv, "UPDATE_ON_FETCH": False, - "DEFAULT_THEME": "auto", } diff --git a/debug_toolbar/static/debug_toolbar/css/toolbar.css b/debug_toolbar/static/debug_toolbar/css/toolbar.css index 3d0d34e6c..f147bcdff 100644 --- a/debug_toolbar/static/debug_toolbar/css/toolbar.css +++ b/debug_toolbar/static/debug_toolbar/css/toolbar.css @@ -35,31 +35,8 @@ --djdt-raw-border-color: var(--djdt-table-border-color); } -@media (prefers-color-scheme: dark) { - :root { - --djdt-font-color: #f8f8f2; - --djdt-background-color: #1e293bff; - --djdt-panel-content-background-color: #0f1729ff; - --djdt-panel-title-background-color: #242432; - --djdt-djdt-panel-content-table-strip-background-color: #324154ff; - --djdt--highlighted-background-color: #2c2a7dff; - --djdt-toggle-template-background-color: #282755; - - --djdt-sql-font-color: var(--djdt-font-color); - --djdt-pre-text-color: var(--djdt-font-color); - --djdt-path-and-locals: #65758cff; - --djdt-stack-span-color: #7c8fa4; - --djdt-template-highlight-color: var(--djdt-stack-span-color); - - --djdt-table-border-color: #324154ff; - --djdt-button-border-color: var(--djdt-table-border-color); - --djdt-pre-border-color: var(--djdt-table-border-color); - --djdt-raw-border-color: var(--djdt-table-border-color); - } -} - #djDebug[data-theme="dark"] { - --djdt-font-color: #8393a7; + --djdt-font-color: #f8f8f2; --djdt-background-color: #1e293bff; --djdt-panel-content-background-color: #0f1729ff; --djdt-panel-content-table-background-color: var(--djdt-background-color); @@ -150,8 +127,10 @@ #djDebug button { margin: 0; padding: 0; - min-width: 0; + min-width: auto; width: auto; + min-height: auto; + height: auto; border: 0; outline: 0; font-size: 12px; @@ -569,266 +548,512 @@ To regenerate: from pygments.formatters import HtmlFormatter print(HtmlFormatter(wrapcode=True).get_style_defs()) */ -#djDebug .highlight pre { +#djDebug[data-theme="light"] .highlight pre { line-height: 125%; } -#djDebug .highlight td.linenos .normal { +#djDebug[data-theme="light"] .highlight td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } -#djDebug .highlight span.linenos { +#djDebug[data-theme="light"] .highlight span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } -#djDebug .highlight td.linenos .special { +#djDebug[data-theme="light"] .highlight td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } -#djDebug .highlight span.linenos.special { +#djDebug[data-theme="light"] .highlight span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } -#djDebug .highlight .hll { +#djDebug[data-theme="light"] .highlight .hll { background-color: #ffffcc; } -#djDebug .highlight .c { +#djDebug[data-theme="light"] .highlight .c { color: #3d7b7b; font-style: italic; } /* Comment */ -#djDebug .highlight .err { +#djDebug[data-theme="light"] .highlight .err { border: 1px solid #ff0000; } /* Error */ -#djDebug .highlight .k { +#djDebug[data-theme="light"] .highlight .k { color: #008000; font-weight: bold; } /* Keyword */ -#djDebug .highlight .o { +#djDebug[data-theme="light"] .highlight .o { color: #666666; } /* Operator */ -#djDebug .highlight .ch { +#djDebug[data-theme="light"] .highlight .ch { color: #3d7b7b; font-style: italic; } /* Comment.Hashbang */ -#djDebug .highlight .cm { +#djDebug[data-theme="light"] .highlight .cm { color: #3d7b7b; font-style: italic; } /* Comment.Multiline */ -#djDebug .highlight .cp { +#djDebug[data-theme="light"] .highlight .cp { color: #9c6500; } /* Comment.Preproc */ -#djDebug .highlight .cpf { +#djDebug[data-theme="light"] .highlight .cpf { color: #3d7b7b; font-style: italic; } /* Comment.PreprocFile */ -#djDebug .highlight .c1 { +#djDebug[data-theme="light"] .highlight .c1 { color: #3d7b7b; font-style: italic; } /* Comment.Single */ -#djDebug .highlight .cs { +#djDebug[data-theme="light"] .highlight .cs { color: #3d7b7b; font-style: italic; } /* Comment.Special */ -#djDebug .highlight .gd { +#djDebug[data-theme="light"] .highlight .gd { color: #a00000; } /* Generic.Deleted */ -#djDebug .highlight .ge { +#djDebug[data-theme="light"] .highlight .ge { font-style: italic; } /* Generic.Emph */ -#djDebug .highlight .ges { +#djDebug[data-theme="light"] .highlight .ges { font-weight: bold; font-style: italic; } /* Generic.EmphStrong */ -#djDebug .highlight .gr { +#djDebug[data-theme="light"] .highlight .gr { color: #e40000; } /* Generic.Error */ -#djDebug .highlight .gh { +#djDebug[data-theme="light"] .highlight .gh { color: #000080; font-weight: bold; } /* Generic.Heading */ -#djDebug .highlight .gi { +#djDebug[data-theme="light"] .highlight .gi { color: #008400; } /* Generic.Inserted */ -#djDebug .highlight .go { +#djDebug[data-theme="light"] .highlight .go { color: #717171; } /* Generic.Output */ -#djDebug .highlight .gp { +#djDebug[data-theme="light"] .highlight .gp { color: #000080; font-weight: bold; } /* Generic.Prompt */ -#djDebug .highlight .gs { +#djDebug[data-theme="light"] .highlight .gs { font-weight: bold; } /* Generic.Strong */ -#djDebug .highlight .gu { +#djDebug[data-theme="light"] .highlight .gu { color: #800080; font-weight: bold; } /* Generic.Subheading */ -#djDebug .highlight .gt { +#djDebug[data-theme="light"] .highlight .gt { color: #0044dd; } /* Generic.Traceback */ -#djDebug .highlight .kc { +#djDebug[data-theme="light"] .highlight .kc { color: #008000; font-weight: bold; } /* Keyword.Constant */ -#djDebug .highlight .kd { +#djDebug[data-theme="light"] .highlight .kd { color: #008000; font-weight: bold; } /* Keyword.Declaration */ -#djDebug .highlight .kn { +#djDebug[data-theme="light"] .highlight .kn { color: #008000; font-weight: bold; } /* Keyword.Namespace */ -#djDebug .highlight .kp { +#djDebug[data-theme="light"] .highlight .kp { color: #008000; } /* Keyword.Pseudo */ -#djDebug .highlight .kr { +#djDebug[data-theme="light"] .highlight .kr { color: #008000; font-weight: bold; } /* Keyword.Reserved */ -#djDebug .highlight .kt { +#djDebug[data-theme="light"] .highlight .kt { color: #b00040; } /* Keyword.Type */ -#djDebug .highlight .m { +#djDebug[data-theme="light"] .highlight .m { color: #666666; } /* Literal.Number */ -#djDebug .highlight .s { +#djDebug[data-theme="light"] .highlight .s { color: #ba2121; } /* Literal.String */ -#djDebug .highlight .na { +#djDebug[data-theme="light"] .highlight .na { color: #687822; } /* Name.Attribute */ -#djDebug .highlight .nb { +#djDebug[data-theme="light"] .highlight .nb { color: #008000; } /* Name.Builtin */ -#djDebug .highlight .nc { +#djDebug[data-theme="light"] .highlight .nc { color: #0000ff; font-weight: bold; } /* Name.Class */ -#djDebug .highlight .no { +#djDebug[data-theme="light"] .highlight .no { color: #880000; } /* Name.Constant */ -#djDebug .highlight .nd { +#djDebug[data-theme="light"] .highlight .nd { color: #aa22ff; } /* Name.Decorator */ -#djDebug .highlight .ni { +#djDebug[data-theme="light"] .highlight .ni { color: #717171; font-weight: bold; } /* Name.Entity */ -#djDebug .highlight .ne { +#djDebug[data-theme="light"] .highlight .ne { color: #cb3f38; font-weight: bold; } /* Name.Exception */ -#djDebug .highlight .nf { +#djDebug[data-theme="light"] .highlight .nf { color: #0000ff; } /* Name.Function */ -#djDebug .highlight .nl { +#djDebug[data-theme="light"] .highlight .nl { color: #767600; } /* Name.Label */ -#djDebug .highlight .nn { +#djDebug[data-theme="light"] .highlight .nn { color: #0000ff; font-weight: bold; } /* Name.Namespace */ -#djDebug .highlight .nt { +#djDebug[data-theme="light"] .highlight .nt { color: #008000; font-weight: bold; } /* Name.Tag */ -#djDebug .highlight .nv { +#djDebug[data-theme="light"] .highlight .nv { color: #19177c; } /* Name.Variable */ -#djDebug .highlight .ow { +#djDebug[data-theme="light"] .highlight .ow { color: #aa22ff; font-weight: bold; } /* Operator.Word */ -#djDebug .highlight .w { +#djDebug[data-theme="light"] .highlight .w { color: #bbbbbb; white-space: pre-wrap; } /* Text.Whitespace */ -#djDebug .highlight .mb { +#djDebug[data-theme="light"] .highlight .mb { color: #666666; } /* Literal.Number.Bin */ -#djDebug .highlight .mf { +#djDebug[data-theme="light"] .highlight .mf { color: #666666; } /* Literal.Number.Float */ -#djDebug .highlight .mh { +#djDebug[data-theme="light"] .highlight .mh { color: #666666; } /* Literal.Number.Hex */ -#djDebug .highlight .mi { +#djDebug[data-theme="light"] .highlight .mi { color: #666666; } /* Literal.Number.Integer */ -#djDebug .highlight .mo { +#djDebug[data-theme="light"] .highlight .mo { color: #666666; } /* Literal.Number.Oct */ -#djDebug .highlight .sa { +#djDebug[data-theme="light"] .highlight .sa { color: #ba2121; } /* Literal.String.Affix */ -#djDebug .highlight .sb { +#djDebug[data-theme="light"] .highlight .sb { color: #ba2121; } /* Literal.String.Backtick */ -#djDebug .highlight .sc { +#djDebug[data-theme="light"] .highlight .sc { color: #ba2121; } /* Literal.String.Char */ -#djDebug .highlight .dl { +#djDebug[data-theme="light"] .highlight .dl { color: #ba2121; } /* Literal.String.Delimiter */ -#djDebug .highlight .sd { +#djDebug[data-theme="light"] .highlight .sd { color: #ba2121; font-style: italic; } /* Literal.String.Doc */ -#djDebug .highlight .s2 { +#djDebug[data-theme="light"] .highlight .s2 { color: #ba2121; } /* Literal.String.Double */ -#djDebug .highlight .se { +#djDebug[data-theme="light"] .highlight .se { color: #aa5d1f; font-weight: bold; } /* Literal.String.Escape */ -#djDebug .highlight .sh { +#djDebug[data-theme="light"] .highlight .sh { color: #ba2121; } /* Literal.String.Heredoc */ -#djDebug .highlight .si { +#djDebug[data-theme="light"] .highlight .si { color: #a45a77; font-weight: bold; } /* Literal.String.Interpol */ -#djDebug .highlight .sx { +#djDebug[data-theme="light"] .highlight .sx { color: #008000; } /* Literal.String.Other */ -#djDebug .highlight .sr { +#djDebug[data-theme="light"] .highlight .sr { color: #a45a77; } /* Literal.String.Regex */ -#djDebug .highlight .s1 { +#djDebug[data-theme="light"] .highlight .s1 { color: #ba2121; } /* Literal.String.Single */ -#djDebug .highlight .ss { +#djDebug[data-theme="light"] .highlight .ss { color: #19177c; } /* Literal.String.Symbol */ -#djDebug .highlight .bp { +#djDebug[data-theme="light"] .highlight .bp { color: #008000; } /* Name.Builtin.Pseudo */ -#djDebug .highlight .fm { +#djDebug[data-theme="light"] .highlight .fm { color: #0000ff; } /* Name.Function.Magic */ -#djDebug .highlight .vc { +#djDebug[data-theme="light"] .highlight .vc { color: #19177c; } /* Name.Variable.Class */ -#djDebug .highlight .vg { +#djDebug[data-theme="light"] .highlight .vg { color: #19177c; } /* Name.Variable.Global */ -#djDebug .highlight .vi { +#djDebug[data-theme="light"] .highlight .vi { color: #19177c; } /* Name.Variable.Instance */ -#djDebug .highlight .vm { +#djDebug[data-theme="light"] .highlight .vm { color: #19177c; } /* Name.Variable.Magic */ -#djDebug .highlight .il { +#djDebug[data-theme="light"] .highlight .il { color: #666666; } /* Literal.Number.Integer.Long */ +#djDebug[data-theme="dark"] .highlight .hll { + background-color: #f1fa8c; +} +#djDebug[data-theme="dark"] .highlight { + background: #282a36; + color: #f8f8f2; +} +#djDebug[data-theme="dark"] .highlight .c { + color: #6272a4; +} /* Comment */ +#djDebug[data-theme="dark"] .highlight .err { + color: #f8f8f2; +} /* Error */ +#djDebug[data-theme="dark"] .highlight .g { + color: #f8f8f2; +} /* Generic */ +#djDebug[data-theme="dark"] .highlight .k { + color: #ff79c6; +} /* Keyword */ +#djDebug[data-theme="dark"] .highlight .l { + color: #f8f8f2; +} /* Literal */ +#djDebug[data-theme="dark"] .highlight .n { + color: #f8f8f2; +} /* Name */ +#djDebug[data-theme="dark"] .highlight .o { + color: #ff79c6; +} /* Operator */ +#djDebug[data-theme="dark"] .highlight .x { + color: #f8f8f2; +} /* Other */ +#djDebug[data-theme="dark"] .highlight .p { + color: #f8f8f2; +} /* Punctuation */ +#djDebug[data-theme="dark"] .highlight .ch { + color: #6272a4; +} /* Comment.Hashbang */ +#djDebug[data-theme="dark"] .highlight .cm { + color: #6272a4; +} /* Comment.Multiline */ +#djDebug[data-theme="dark"] .highlight .cp { + color: #ff79c6; +} /* Comment.Preproc */ +#djDebug[data-theme="dark"] .highlight .cpf { + color: #6272a4; +} /* Comment.PreprocFile */ +#djDebug[data-theme="dark"] .highlight .c1 { + color: #6272a4; +} /* Comment.Single */ +#djDebug[data-theme="dark"] .highlight .cs { + color: #6272a4; +} /* Comment.Special */ +#djDebug[data-theme="dark"] .highlight .gd { + color: #8b080b; +} /* Generic.Deleted */ +#djDebug[data-theme="dark"] .highlight .ge { + color: #f8f8f2; + text-decoration: underline; +} /* Generic.Emph */ +#djDebug[data-theme="dark"] .highlight .gr { + color: #f8f8f2; +} /* Generic.Error */ +#djDebug[data-theme="dark"] .highlight .gh { + color: #f8f8f2; + font-weight: bold; +} /* Generic.Heading */ +#djDebug[data-theme="dark"] .highlight .gi { + color: #f8f8f2; + font-weight: bold; +} /* Generic.Inserted */ +#djDebug[data-theme="dark"] .highlight .go { + color: #44475a; +} /* Generic.Output */ +#djDebug[data-theme="dark"] .highlight .gp { + color: #f8f8f2; +} /* Generic.Prompt */ +#djDebug[data-theme="dark"] .highlight .gs { + color: #f8f8f2; +} /* Generic.Strong */ +#djDebug[data-theme="dark"] .highlight .gu { + color: #f8f8f2; + font-weight: bold; +} /* Generic.Subheading */ +#djDebug[data-theme="dark"] .highlight .gt { + color: #f8f8f2; +} /* Generic.Traceback */ +#djDebug[data-theme="dark"] .highlight .kc { + color: #ff79c6; +} /* Keyword.Constant */ +#djDebug[data-theme="dark"] .highlight .kd { + color: #8be9fd; + font-style: italic; +} /* Keyword.Declaration */ +#djDebug[data-theme="dark"] .highlight .kn { + color: #ff79c6; +} /* Keyword.Namespace */ +#djDebug[data-theme="dark"] .highlight .kp { + color: #ff79c6; +} /* Keyword.Pseudo */ +#djDebug[data-theme="dark"] .highlight .kr { + color: #ff79c6; +} /* Keyword.Reserved */ +#djDebug[data-theme="dark"] .highlight .kt { + color: #8be9fd; +} /* Keyword.Type */ +#djDebug[data-theme="dark"] .highlight .ld { + color: #f8f8f2; +} /* Literal.Date */ +#djDebug[data-theme="dark"] .highlight .m { + color: #bd93f9; +} /* Literal.Number */ +#djDebug[data-theme="dark"] .highlight .s { + color: #f1fa8c; +} /* Literal.String */ +#djDebug[data-theme="dark"] .highlight .na { + color: #50fa7b; +} /* Name.Attribute */ +#djDebug[data-theme="dark"] .highlight .nb { + color: #8be9fd; + font-style: italic; +} /* Name.Builtin */ +#djDebug[data-theme="dark"] .highlight .nc { + color: #50fa7b; +} /* Name.Class */ +#djDebug[data-theme="dark"] .highlight .no { + color: #f8f8f2; +} /* Name.Constant */ +#djDebug[data-theme="dark"] .highlight .nd { + color: #f8f8f2; +} /* Name.Decorator */ +#djDebug[data-theme="dark"] .highlight .ni { + color: #f8f8f2; +} /* Name.Entity */ +#djDebug[data-theme="dark"] .highlight .ne { + color: #f8f8f2; +} /* Name.Exception */ +#djDebug[data-theme="dark"] .highlight .nf { + color: #50fa7b; +} /* Name.Function */ +#djDebug[data-theme="dark"] .highlight .nl { + color: #8be9fd; + font-style: italic; +} /* Name.Label */ +#djDebug[data-theme="dark"] .highlight .nn { + color: #f8f8f2; +} /* Name.Namespace */ +#djDebug[data-theme="dark"] .highlight .nx { + color: #f8f8f2; +} /* Name.Other */ +#djDebug[data-theme="dark"] .highlight .py { + color: #f8f8f2; +} /* Name.Property */ +#djDebug[data-theme="dark"] .highlight .nt { + color: #ff79c6; +} /* Name.Tag */ +#djDebug[data-theme="dark"] .highlight .nv { + color: #8be9fd; + font-style: italic; +} /* Name.Variable */ +#djDebug[data-theme="dark"] .highlight .ow { + color: #ff79c6; +} /* Operator.Word */ +#djDebug[data-theme="dark"] .highlight .w { + color: #f8f8f2; +} /* Text.Whitespace */ +#djDebug[data-theme="dark"] .highlight .mb { + color: #bd93f9; +} /* Literal.Number.Bin */ +#djDebug[data-theme="dark"] .highlight .mf { + color: #bd93f9; +} /* Literal.Number.Float */ +#djDebug[data-theme="dark"] .highlight .mh { + color: #bd93f9; +} /* Literal.Number.Hex */ +#djDebug[data-theme="dark"] .highlight .mi { + color: #bd93f9; +} /* Literal.Number.Integer */ +#djDebug[data-theme="dark"] .highlight .mo { + color: #bd93f9; +} /* Literal.Number.Oct */ +#djDebug[data-theme="dark"] .highlight .sa { + color: #f1fa8c; +} /* Literal.String.Affix */ +#djDebug[data-theme="dark"] .highlight .sb { + color: #f1fa8c; +} /* Literal.String.Backtick */ +#djDebug[data-theme="dark"] .highlight .sc { + color: #f1fa8c; +} /* Literal.String.Char */ +#djDebug[data-theme="dark"] .highlight .dl { + color: #f1fa8c; +} /* Literal.String.Delimiter */ +#djDebug[data-theme="dark"] .highlight .sd { + color: #f1fa8c; +} /* Literal.String.Doc */ +#djDebug[data-theme="dark"] .highlight .s2 { + color: #f1fa8c; +} /* Literal.String.Double */ +#djDebug[data-theme="dark"] .highlight .se { + color: #f1fa8c; +} /* Literal.String.Escape */ +#djDebug[data-theme="dark"] .highlight .sh { + color: #f1fa8c; +} /* Literal.String.Heredoc */ +#djDebug[data-theme="dark"] .highlight .si { + color: #f1fa8c; +} /* Literal.String.Interpol */ +#djDebug[data-theme="dark"] .highlight .sx { + color: #f1fa8c; +} /* Literal.String.Other */ +#djDebug[data-theme="dark"] .highlight .sr { + color: #f1fa8c; +} /* Literal.String.Regex */ +#djDebug[data-theme="dark"] .highlight .s1 { + color: #f1fa8c; +} /* Literal.String.Single */ +#djDebug[data-theme="dark"] .highlight .ss { + color: #f1fa8c; +} /* Literal.String.Symbol */ +#djDebug[data-theme="dark"] .highlight .bp { + color: #f8f8f2; + font-style: italic; +} /* Name.Builtin.Pseudo */ +#djDebug[data-theme="dark"] .highlight .fm { + color: #50fa7b; +} /* Name.Function.Magic */ +#djDebug[data-theme="dark"] .highlight .vc { + color: #8be9fd; + font-style: italic; +} /* Name.Variable.Class */ +#djDebug[data-theme="dark"] .highlight .vg { + color: #8be9fd; + font-style: italic; +} /* Name.Variable.Global */ +#djDebug[data-theme="dark"] .highlight .vi { + color: #8be9fd; + font-style: italic; +} /* Name.Variable.Instance */ +#djDebug[data-theme="dark"] .highlight .vm { + color: #8be9fd; + font-style: italic; +} /* Name.Variable.Magic */ +#djDebug[data-theme="dark"] .highlight .il { + color: #bd93f9; +} /* Literal.Number.Integer.Long */ + #djDebug svg.djDebugLineChart { width: 100%; height: 1.5em; @@ -947,9 +1172,9 @@ To regenerate: #djToggleThemeButton > svg { margin-left: auto; } -#djDebug[data-theme="light"] #djToggleThemeButton svg.theme-light, -#djDebug[data-theme="dark"] #djToggleThemeButton svg.theme-dark, -#djDebug[data-theme="auto"] #djToggleThemeButton svg.theme-auto { +#djDebug[data-user-theme="light"] #djToggleThemeButton svg.theme-light, +#djDebug[data-user-theme="dark"] #djToggleThemeButton svg.theme-dark, +#djDebug[data-user-theme="auto"] #djToggleThemeButton svg.theme-auto { display: block; height: 1rem; width: 1rem; diff --git a/debug_toolbar/static/debug_toolbar/js/toolbar.js b/debug_toolbar/static/debug_toolbar/js/toolbar.js index 077bc930a..19658f76e 100644 --- a/debug_toolbar/static/debug_toolbar/js/toolbar.js +++ b/debug_toolbar/static/debug_toolbar/js/toolbar.js @@ -212,27 +212,30 @@ const djdt = { djdt.updateOnAjax(); } + const prefersDark = window.matchMedia( + "(prefers-color-scheme: dark)" + ).matches; + const themeList = prefersDark + ? ["auto", "light", "dark"] + : ["auto", "dark", "light"]; + const setTheme = (theme) => { + djDebug.setAttribute( + "data-theme", + theme === "auto" ? (prefersDark ? "dark" : "light") : theme + ); + djDebug.setAttribute("data-user-theme", theme); + }; + // Updates the theme using user settings - const userTheme = localStorage.getItem("djdt.user-theme"); - if (userTheme !== null) { - djDebug.setAttribute("data-theme", userTheme); - } + let userTheme = localStorage.getItem("djdt.user-theme") || "auto"; + setTheme(userTheme); + // Adds the listener to the Theme Toggle Button $$.on(djDebug, "click", "#djToggleThemeButton", () => { - switch (djDebug.getAttribute("data-theme")) { - case "auto": - djDebug.setAttribute("data-theme", "light"); - localStorage.setItem("djdt.user-theme", "light"); - break; - case "light": - djDebug.setAttribute("data-theme", "dark"); - localStorage.setItem("djdt.user-theme", "dark"); - break; - default: /* dark is the default */ - djDebug.setAttribute("data-theme", "auto"); - localStorage.setItem("djdt.user-theme", "auto"); - break; - } + const index = themeList.indexOf(userTheme); + userTheme = themeList[(index + 1) % themeList.length]; + localStorage.setItem("djdt.user-theme", userTheme); + setTheme(userTheme); }); }, hidePanels() { diff --git a/debug_toolbar/static/debug_toolbar/js/utils.js b/debug_toolbar/static/debug_toolbar/js/utils.js index c42963fe3..0cfa80474 100644 --- a/debug_toolbar/static/debug_toolbar/js/utils.js +++ b/debug_toolbar/static/debug_toolbar/js/utils.js @@ -90,7 +90,7 @@ function ajax(url, init) { }) .catch((error) => { const win = document.getElementById("djDebugWindow"); - win.innerHTML = `

${error.message}

`; + win.innerHTML = `

${error.message}

`; $$.show(win); throw error; }); diff --git a/debug_toolbar/templates/debug_toolbar/base.html b/debug_toolbar/templates/debug_toolbar/base.html index a9983250d..607863104 100644 --- a/debug_toolbar/templates/debug_toolbar/base.html +++ b/debug_toolbar/templates/debug_toolbar/base.html @@ -2,10 +2,10 @@ {% block css %} -{% endblock %} +{% endblock css %} {% block js %} -{% endblock %} +{% endblock js %}
+ {{ toolbar.config.ROOT_TAG_EXTRA_ATTRS|safe }} data-update-on-fetch="{{ toolbar.config.UPDATE_ON_FETCH }}">
-
+
DJDT
diff --git a/debug_toolbar/templates/debug_toolbar/includes/panel_button.html b/debug_toolbar/templates/debug_toolbar/includes/panel_button.html index 344331d8d..bc6f03ad9 100644 --- a/debug_toolbar/templates/debug_toolbar/includes/panel_button.html +++ b/debug_toolbar/templates/debug_toolbar/includes/panel_button.html @@ -1,7 +1,7 @@ {% load i18n %}
  • - + {% if panel.has_content and panel.enabled %} {% else %} @@ -9,7 +9,7 @@ {% endif %} {{ panel.nav_title }} {% if panel.enabled %} - {% with panel.nav_subtitle as subtitle %} + {% with subtitle=panel.nav_subtitle %} {% if subtitle %}
    {{ subtitle }}{% endif %} {% endwith %} {% endif %} diff --git a/debug_toolbar/templates/debug_toolbar/includes/panel_content.html b/debug_toolbar/templates/debug_toolbar/includes/panel_content.html index b0f4af704..d797421a5 100644 --- a/debug_toolbar/templates/debug_toolbar/includes/panel_content.html +++ b/debug_toolbar/templates/debug_toolbar/includes/panel_content.html @@ -3,8 +3,8 @@ {% if panel.has_content and panel.enabled %}
    -

    {{ panel.title }}

    +
    {% if toolbar.should_render_panels %} diff --git a/debug_toolbar/templates/debug_toolbar/panels/alerts.html b/debug_toolbar/templates/debug_toolbar/panels/alerts.html index df208836d..6665033fb 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/alerts.html +++ b/debug_toolbar/templates/debug_toolbar/panels/alerts.html @@ -1,12 +1,12 @@ {% load i18n %} {% if alerts %} -

    {% trans "Alerts found" %}

    +

    {% translate "Alerts found" %}

    {% for alert in alerts %}
    • {{ alert.alert }}
    {% endfor %} {% else %} -

    {% trans "No alerts found" %}

    +

    {% translate "No alerts found" %}

    {% endif %} diff --git a/debug_toolbar/templates/debug_toolbar/panels/cache.html b/debug_toolbar/templates/debug_toolbar/panels/cache.html index 0e1ec2a4c..fe882750b 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/cache.html +++ b/debug_toolbar/templates/debug_toolbar/panels/cache.html @@ -1,12 +1,12 @@ {% load i18n %} -

    {% trans "Summary" %}

    +

    {% translate "Summary" %}

    - - - - + + + + @@ -18,7 +18,7 @@

    {% trans "Summary" %}

    {% trans "Total calls" %}{% trans "Total time" %}{% trans "Cache hits" %}{% trans "Cache misses" %}{% translate "Total calls" %}{% translate "Total time" %}{% translate "Cache hits" %}{% translate "Cache misses" %}
    -

    {% trans "Commands" %}

    +

    {% translate "Commands" %}

    @@ -36,15 +36,15 @@

    {% trans "Commands" %}

    {% if calls %} -

    {% trans "Calls" %}

    +

    {% translate "Calls" %}

    - - - - - + + + + + diff --git a/debug_toolbar/templates/debug_toolbar/panels/headers.html b/debug_toolbar/templates/debug_toolbar/panels/headers.html index f4146e8dd..db33f1b59 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/headers.html +++ b/debug_toolbar/templates/debug_toolbar/panels/headers.html @@ -1,12 +1,12 @@ {% load i18n %} -

    {% trans "Request headers" %}

    +

    {% translate "Request headers" %}

    {% trans "Time (ms)" %}{% trans "Type" %}{% trans "Arguments" %}{% trans "Keyword arguments" %}{% trans "Backend" %}{% translate "Time (ms)" %}{% translate "Type" %}{% translate "Arguments" %}{% translate "Keyword arguments" %}{% translate "Backend" %}
    - - + + @@ -19,13 +19,13 @@

    {% trans "Request headers" %}

    {% trans "Key" %}{% trans "Value" %}{% translate "Key" %}{% translate "Value" %}
    -

    {% trans "Response headers" %}

    +

    {% translate "Response headers" %}

    - - + + @@ -38,15 +38,15 @@

    {% trans "Response headers" %}

    {% trans "Key" %}{% trans "Value" %}{% translate "Key" %}{% translate "Value" %}
    -

    {% trans "WSGI environ" %}

    +

    {% translate "WSGI environ" %}

    -

    {% trans "Since the WSGI environ inherits the environment of the server, only a significant subset is shown below." %}

    +

    {% translate "Since the WSGI environ inherits the environment of the server, only a significant subset is shown below." %}

    - - + + diff --git a/debug_toolbar/templates/debug_toolbar/panels/history.html b/debug_toolbar/templates/debug_toolbar/panels/history.html index 840f6c9f4..ba7823d22 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/history.html +++ b/debug_toolbar/templates/debug_toolbar/panels/history.html @@ -1,4 +1,4 @@ -{% load i18n %}{% load static %} +{% load i18n static %} {{ refresh_form.as_div }} @@ -6,12 +6,12 @@
    {% trans "Key" %}{% trans "Value" %}{% translate "Key" %}{% translate "Value" %}
    - - - - - - + + + + + + diff --git a/debug_toolbar/templates/debug_toolbar/panels/history_tr.html b/debug_toolbar/templates/debug_toolbar/panels/history_tr.html index eff544f1a..1642b4a47 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/history_tr.html +++ b/debug_toolbar/templates/debug_toolbar/panels/history_tr.html @@ -19,8 +19,8 @@ - - + + diff --git a/debug_toolbar/templates/debug_toolbar/panels/profiling.html b/debug_toolbar/templates/debug_toolbar/panels/profiling.html index 4c1c3acd3..0c2206a13 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/profiling.html +++ b/debug_toolbar/templates/debug_toolbar/panels/profiling.html @@ -2,12 +2,12 @@
    {% trans "Time" %}{% trans "Method" %}{% trans "Path" %}{% trans "Request Variables" %}{% trans "Status" %}{% trans "Action" %}{% translate "Time" %}{% translate "Method" %}{% translate "Path" %}{% translate "Request Variables" %}{% translate "Status" %}{% translate "Action" %}
    {% trans "Variable" %}{% trans "Value" %}{% translate "Variable" %}{% translate "Value" %}
    - - - - - - + + + + + + diff --git a/debug_toolbar/templates/debug_toolbar/panels/request.html b/debug_toolbar/templates/debug_toolbar/panels/request.html index 076d5f74f..4a16468b5 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/request.html +++ b/debug_toolbar/templates/debug_toolbar/panels/request.html @@ -1,13 +1,13 @@ {% load i18n %} -

    {% trans "View information" %}

    +

    {% translate "View information" %}

    {% trans "Call" %}{% trans "CumTime" %}{% trans "Per" %}{% trans "TotTime" %}{% trans "Per" %}{% trans "Count" %}{% translate "Call" %}{% translate "CumTime" %}{% translate "Per" %}{% translate "TotTime" %}{% translate "Per" %}{% translate "Count" %}
    - - - - + + + + @@ -21,29 +21,29 @@

    {% trans "View information" %}

    {% trans "View function" %}{% trans "Arguments" %}{% trans "Keyword arguments" %}{% trans "URL name" %}{% translate "View function" %}{% translate "Arguments" %}{% translate "Keyword arguments" %}{% translate "URL name" %}
    {% if cookies.list or cookies.raw %} -

    {% trans "Cookies" %}

    +

    {% translate "Cookies" %}

    {% include 'debug_toolbar/panels/request_variables.html' with variables=cookies %} {% else %} -

    {% trans "No cookies" %}

    +

    {% translate "No cookies" %}

    {% endif %} {% if session.list or session.raw %} -

    {% trans "Session data" %}

    +

    {% translate "Session data" %}

    {% include 'debug_toolbar/panels/request_variables.html' with variables=session %} {% else %} -

    {% trans "No session data" %}

    +

    {% translate "No session data" %}

    {% endif %} {% if get.list or get.raw %} -

    {% trans "GET data" %}

    +

    {% translate "GET data" %}

    {% include 'debug_toolbar/panels/request_variables.html' with variables=get %} {% else %} -

    {% trans "No GET data" %}

    +

    {% translate "No GET data" %}

    {% endif %} {% if post.list or post.raw %} -

    {% trans "POST data" %}

    +

    {% translate "POST data" %}

    {% include 'debug_toolbar/panels/request_variables.html' with variables=post %} {% else %} -

    {% trans "No POST data" %}

    +

    {% translate "No POST data" %}

    {% endif %} diff --git a/debug_toolbar/templates/debug_toolbar/panels/request_variables.html b/debug_toolbar/templates/debug_toolbar/panels/request_variables.html index 92200f867..26b487ab0 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/request_variables.html +++ b/debug_toolbar/templates/debug_toolbar/panels/request_variables.html @@ -8,8 +8,8 @@ - {% trans "Variable" %} - {% trans "Value" %} + {% translate "Variable" %} + {% translate "Value" %} diff --git a/debug_toolbar/templates/debug_toolbar/panels/settings.html b/debug_toolbar/templates/debug_toolbar/panels/settings.html index 14763e4e6..5214c1b42 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/settings.html +++ b/debug_toolbar/templates/debug_toolbar/panels/settings.html @@ -2,8 +2,8 @@ - - + + diff --git a/debug_toolbar/templates/debug_toolbar/panels/signals.html b/debug_toolbar/templates/debug_toolbar/panels/signals.html index cd9f42c4a..abd648924 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/signals.html +++ b/debug_toolbar/templates/debug_toolbar/panels/signals.html @@ -2,8 +2,8 @@
    {% trans "Setting" %}{% trans "Value" %}{% translate "Setting" %}{% translate "Value" %}
    - - + + diff --git a/debug_toolbar/templates/debug_toolbar/panels/sql.html b/debug_toolbar/templates/debug_toolbar/panels/sql.html index e5bf0b7f6..63cf293c1 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/sql.html +++ b/debug_toolbar/templates/debug_toolbar/panels/sql.html @@ -3,15 +3,15 @@ {% for alias, info in databases %}
  • {{ alias }} - {{ info.time_spent|floatformat:"2" }} ms ({% blocktrans count info.num_queries as num %}{{ num }} query{% plural %}{{ num }} queries{% endblocktrans %} + {{ info.time_spent|floatformat:"2" }} ms ({% blocktranslate count num=info.num_queries %}{{ num }} query{% plural %}{{ num }} queries{% endblocktranslate %} {% if info.similar_count %} - {% blocktrans with count=info.similar_count trimmed %} + {% blocktranslate with count=info.similar_count trimmed %} including {{ count }} similar - {% endblocktrans %} + {% endblocktranslate %} {% if info.duplicate_count %} - {% blocktrans with dupes=info.duplicate_count trimmed %} + {% blocktranslate with dupes=info.duplicate_count trimmed %} and {{ dupes }} duplicates - {% endblocktrans %} + {% endblocktranslate %} {% endif %} {% endif %})
  • @@ -31,16 +31,16 @@ - - - - + + + + {% for query in queries %} - + @@ -49,13 +49,13 @@ {% if query.similar_count %} - {% blocktrans with count=query.similar_count %}{{ count }} similar queries.{% endblocktrans %} + {% blocktranslate with count=query.similar_count %}{{ count }} similar queries.{% endblocktranslate %} {% endif %} {% if query.duplicate_count %} - {% blocktrans with dupes=query.duplicate_count %}Duplicated {{ dupes }} times.{% endblocktrans %} + {% blocktranslate with dupes=query.duplicate_count %}Duplicated {{ dupes }} times.{% endblocktranslate %} {% endif %} @@ -92,12 +92,12 @@
    {% trans "Signal" %}{% trans "Receivers" %}{% translate "Signal" %}{% translate "Receivers" %}
    {% trans "Query" %}{% trans "Timeline" %}{% trans "Time (ms)" %}{% trans "Action" %}{% translate "Query" %}{% translate "Timeline" %}{% translate "Time (ms)" %}{% translate "Action" %}
    -

    {% trans "Connection:" %} {{ query.alias }}

    +

    {% translate "Connection:" %} {{ query.alias }}

    {% if query.iso_level %} -

    {% trans "Isolation level:" %} {{ query.iso_level }}

    +

    {% translate "Isolation level:" %} {{ query.iso_level }}

    {% endif %} {% if query.trans_status %} -

    {% trans "Transaction status:" %} {{ query.trans_status }}

    +

    {% translate "Transaction status:" %} {{ query.trans_status }}

    {% endif %} {% if query.stacktrace %}
    {{ query.stacktrace }}
    @@ -120,5 +120,5 @@
    {% else %} -

    {% trans "No SQL queries were recorded during this request." %}

    +

    {% translate "No SQL queries were recorded during this request." %}

    {% endif %} diff --git a/debug_toolbar/templates/debug_toolbar/panels/sql_explain.html b/debug_toolbar/templates/debug_toolbar/panels/sql_explain.html index 61dadbda6..b9ff2911d 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/sql_explain.html +++ b/debug_toolbar/templates/debug_toolbar/panels/sql_explain.html @@ -1,16 +1,16 @@ {% load i18n %}
    +

    {% translate "SQL explained" %}

    -

    {% trans "SQL explained" %}

    -
    {% trans "Executed SQL" %}
    +
    {% translate "Executed SQL" %}
    {{ sql|safe }}
    -
    {% trans "Time" %}
    +
    {% translate "Time" %}
    {{ duration }} ms
    -
    {% trans "Database" %}
    +
    {% translate "Database" %}
    {{ alias }}
    diff --git a/debug_toolbar/templates/debug_toolbar/panels/sql_profile.html b/debug_toolbar/templates/debug_toolbar/panels/sql_profile.html index 57f20b619..d18a309c6 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/sql_profile.html +++ b/debug_toolbar/templates/debug_toolbar/panels/sql_profile.html @@ -1,17 +1,17 @@ {% load i18n %}
    +

    {% translate "SQL profiled" %}

    -

    {% trans "SQL profiled" %}

    {% if result %}
    -
    {% trans "Executed SQL" %}
    +
    {% translate "Executed SQL" %}
    {{ sql|safe }}
    -
    {% trans "Time" %}
    +
    {% translate "Time" %}
    {{ duration }} ms
    -
    {% trans "Database" %}
    +
    {% translate "Database" %}
    {{ alias }}
    @@ -34,7 +34,7 @@

    {% trans "SQL profiled" %}

    {% else %}
    -
    {% trans "Error" %}
    +
    {% translate "Error" %}
    {{ result_error }}
    {% endif %} diff --git a/debug_toolbar/templates/debug_toolbar/panels/sql_select.html b/debug_toolbar/templates/debug_toolbar/panels/sql_select.html index 699c18d87..9360cde05 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/sql_select.html +++ b/debug_toolbar/templates/debug_toolbar/panels/sql_select.html @@ -1,16 +1,16 @@ {% load i18n %}
    +

    {% translate "SQL selected" %}

    -

    {% trans "SQL selected" %}

    -
    {% trans "Executed SQL" %}
    +
    {% translate "Executed SQL" %}
    {{ sql|safe }}
    -
    {% trans "Time" %}
    +
    {% translate "Time" %}
    {{ duration }} ms
    -
    {% trans "Database" %}
    +
    {% translate "Database" %}
    {{ alias }}
    {% if result %} @@ -33,7 +33,7 @@

    {% trans "SQL selected" %}

    {% else %} -

    {% trans "Empty set" %}

    +

    {% translate "Empty set" %}

    {% endif %}
    diff --git a/debug_toolbar/templates/debug_toolbar/panels/staticfiles.html b/debug_toolbar/templates/debug_toolbar/panels/staticfiles.html index 9aa519f67..aaa7c78ab 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/staticfiles.html +++ b/debug_toolbar/templates/debug_toolbar/panels/staticfiles.html @@ -1,17 +1,17 @@ {% load i18n %} -

    {% blocktrans count staticfiles_dirs|length as dirs_count %}Static file path{% plural %}Static file paths{% endblocktrans %}

    +

    {% blocktranslate count dirs_count=staticfiles_dirs|length %}Static file path{% plural %}Static file paths{% endblocktranslate %}

    {% if staticfiles_dirs %}
      {% for prefix, staticfiles_dir in staticfiles_dirs %} -
    1. {{ staticfiles_dir }}{% if prefix %} {% blocktrans %}(prefix {{ prefix }}){% endblocktrans %}{% endif %}
    2. +
    3. {{ staticfiles_dir }}{% if prefix %} {% blocktranslate %}(prefix {{ prefix }}){% endblocktranslate %}{% endif %}
    4. {% endfor %}
    {% else %} -

    {% trans "None" %}

    +

    {% translate "None" %}

    {% endif %} -

    {% blocktrans count staticfiles_apps|length as apps_count %}Static file app{% plural %}Static file apps{% endblocktrans %}

    +

    {% blocktranslate count apps_count=staticfiles_apps|length %}Static file app{% plural %}Static file apps{% endblocktranslate %}

    {% if staticfiles_apps %}
      {% for static_app in staticfiles_apps %} @@ -19,10 +19,10 @@

      {% blocktrans count staticfiles_apps|length as apps_count %}Static file app{ {% endfor %}

    {% else %} -

    {% trans "None" %}

    +

    {% translate "None" %}

    {% endif %} -

    {% blocktrans count staticfiles|length as staticfiles_count %}Static file{% plural %}Static files{% endblocktrans %}

    +

    {% blocktranslate count staticfiles_count=staticfiles|length %}Static file{% plural %}Static files{% endblocktranslate %}

    {% if staticfiles %}
    {% for staticfile in staticfiles %} @@ -31,17 +31,17 @@

    {% blocktrans count staticfiles|length as staticfiles_count %}Static file{% {% endfor %}

    {% else %} -

    {% trans "None" %}

    +

    {% translate "None" %}

    {% endif %} {% for finder, payload in staticfiles_finders.items %} -

    {{ finder }} ({% blocktrans count payload|length as payload_count %}{{ payload_count }} file{% plural %}{{ payload_count }} files{% endblocktrans %})

    +

    {{ finder }} ({% blocktranslate count payload_count=payload|length %}{{ payload_count }} file{% plural %}{{ payload_count }} files{% endblocktranslate %})

    - - + + diff --git a/debug_toolbar/templates/debug_toolbar/panels/template_source.html b/debug_toolbar/templates/debug_toolbar/panels/template_source.html index 397c44b24..4d47fd3c3 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/template_source.html +++ b/debug_toolbar/templates/debug_toolbar/panels/template_source.html @@ -1,7 +1,7 @@ {% load i18n %}
    +

    {% translate "Template source:" %} {{ template_name }}

    -

    {% trans "Template source:" %} {{ template_name }}

    diff --git a/debug_toolbar/templates/debug_toolbar/panels/templates.html b/debug_toolbar/templates/debug_toolbar/panels/templates.html index 121c086a8..4ceae12e7 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/templates.html +++ b/debug_toolbar/templates/debug_toolbar/panels/templates.html @@ -1,5 +1,5 @@ {% load i18n %} -

    {% blocktrans count template_dirs|length as template_count %}Template path{% plural %}Template paths{% endblocktrans %}

    +

    {% blocktranslate count template_count=template_dirs|length %}Template path{% plural %}Template paths{% endblocktranslate %}

    {% if template_dirs %}
      {% for template in template_dirs %} @@ -7,10 +7,10 @@

      {% blocktrans count template_dirs|length as template_count %}Template path{% {% endfor %}

    {% else %} -

    {% trans "None" %}

    +

    {% translate "None" %}

    {% endif %} -

    {% blocktrans count templates|length as template_count %}Template{% plural %}Templates{% endblocktrans %}

    +

    {% blocktranslate count template_count=templates|length %}Template{% plural %}Templates{% endblocktranslate %}

    {% if templates %}
    {% for template in templates %} @@ -19,7 +19,7 @@

    {% blocktrans count templates|length as template_count %}Template{% plural % {% if template.context %}
    - {% trans "Toggle context" %} + {% translate "Toggle context" %} {{ template.context }}
    @@ -27,22 +27,22 @@

    {% blocktrans count templates|length as template_count %}Template{% plural % {% endfor %}

    {% else %} -

    {% trans "None" %}

    +

    {% translate "None" %}

    {% endif %} -

    {% blocktrans count context_processors|length as context_processors_count %}Context processor{% plural %}Context processors{% endblocktrans %}

    +

    {% blocktranslate count context_processors_count=context_processors|length %}Context processor{% plural %}Context processors{% endblocktranslate %}

    {% if context_processors %}
    {% for key, value in context_processors.items %}
    {{ key|escape }}
    - {% trans "Toggle context" %} + {% translate "Toggle context" %} {{ value|escape }}
    {% endfor %}
    {% else %} -

    {% trans "None" %}

    +

    {% translate "None" %}

    {% endif %} diff --git a/debug_toolbar/templates/debug_toolbar/panels/timer.html b/debug_toolbar/templates/debug_toolbar/panels/timer.html index 11483c107..b85720483 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/timer.html +++ b/debug_toolbar/templates/debug_toolbar/panels/timer.html @@ -1,5 +1,5 @@ {% load i18n %} -

    {% trans "Resource usage" %}

    +

    {% translate "Resource usage" %}

    {% trans 'Path' %}{% trans 'Location' %}{% translate 'Path' %}{% translate 'Location' %}
    @@ -7,8 +7,8 @@

    {% trans "Resource usage" %}

    - - + + @@ -23,7 +23,7 @@

    {% trans "Resource usage" %}

    -

    {% trans "Browser timing" %}

    +

    {% translate "Browser timing" %}

    {% trans "Resource" %}{% trans "Value" %}{% translate "Resource" %}{% translate "Value" %}
    @@ -32,9 +32,9 @@

    {% trans "Browser timing" %}

    - - - + + + diff --git a/debug_toolbar/templates/debug_toolbar/panels/versions.html b/debug_toolbar/templates/debug_toolbar/panels/versions.html index d0ade6cfb..3428c0561 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/versions.html +++ b/debug_toolbar/templates/debug_toolbar/panels/versions.html @@ -7,9 +7,9 @@ - - - + + + diff --git a/debug_toolbar/templates/debug_toolbar/redirect.html b/debug_toolbar/templates/debug_toolbar/redirect.html index 9d8966ed7..46897846d 100644 --- a/debug_toolbar/templates/debug_toolbar/redirect.html +++ b/debug_toolbar/templates/debug_toolbar/redirect.html @@ -7,9 +7,9 @@

    {{ status_line }}

    -

    {% trans "Location:" %} {{ redirect_to }}

    +

    {% translate "Location:" %} {{ redirect_to }}

    - {% trans "The Django Debug Toolbar has intercepted a redirect to the above URL for debug viewing purposes. You can click the above link to continue with the redirect as normal." %} + {% translate "The Django Debug Toolbar has intercepted a redirect to the above URL for debug viewing purposes. You can click the above link to continue with the redirect as normal." %}

    diff --git a/debug_toolbar/utils.py b/debug_toolbar/utils.py index dc3cc1adc..f4b3eac38 100644 --- a/debug_toolbar/utils.py +++ b/debug_toolbar/utils.py @@ -14,10 +14,12 @@ from django.template import Node from django.utils.html import format_html from django.utils.safestring import SafeString, mark_safe +from django.views.debug import get_default_exception_reporter_filter from debug_toolbar import _stubs as stubs, settings as dt_settings _local_data = Local() +safe_filter = get_default_exception_reporter_filter() def _is_excluded_frame(frame: Any, excluded_modules: Sequence[str] | None) -> bool: @@ -215,20 +217,50 @@ def getframeinfo(frame: Any, context: int = 1) -> inspect.Traceback: return inspect.Traceback(filename, lineno, frame.f_code.co_name, lines, index) -def get_sorted_request_variable( +def sanitize_and_sort_request_vars( variable: dict[str, Any] | QueryDict, ) -> dict[str, list[tuple[str, Any]] | Any]: """ Get a data structure for showing a sorted list of variables from the - request data. + request data with sensitive values redacted. """ + if not isinstance(variable, (dict, QueryDict)): + return {"raw": variable} + + # Get sorted keys if possible, otherwise just list them + keys = _get_sorted_keys(variable) + + # Process the variable based on its type + if isinstance(variable, QueryDict): + result = _process_query_dict(variable, keys) + else: + result = _process_dict(variable, keys) + + return {"list": result} + + +def _get_sorted_keys(variable): + """Helper function to get sorted keys if possible.""" try: - if isinstance(variable, dict): - return {"list": [(k, variable.get(k)) for k in sorted(variable)]} - else: - return {"list": [(k, variable.getlist(k)) for k in sorted(variable)]} + return sorted(variable) except TypeError: - return {"raw": variable} + return list(variable) + + +def _process_query_dict(query_dict, keys): + """Process a QueryDict into a list of (key, sanitized_value) tuples.""" + result = [] + for k in keys: + values = query_dict.getlist(k) + # Return single value if there's only one, otherwise keep as list + value = values[0] if len(values) == 1 else values + result.append((k, safe_filter.cleanse_setting(k, value))) + return result + + +def _process_dict(dictionary, keys): + """Process a dictionary into a list of (key, sanitized_value) tuples.""" + return [(k, safe_filter.cleanse_setting(k, dictionary.get(k))) for k in keys] def get_stack(context=1) -> list[stubs.InspectStack]: diff --git a/docs/changes.rst b/docs/changes.rst index dd52a09e1..bf1998de8 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -4,8 +4,28 @@ Change log Pending ------- +5.2.0 (2025-04-29) +------------------ + +* Added hook to RedirectsPanel for subclass customization. +* Added feature to sanitize sensitive data in the Request Panel. +* Fixed dark mode conflict in code block toolbar CSS. +* Properly allowed overriding the system theme preference by using the theme + selector. Removed the ``DEFAULT_THEME`` setting, we should always default to + system-level defaults where possible. +* Added support for using django-template-partials with the template panel's + source view functionality. The same change possibly adds support for other + template loaders. +* Introduced `djade `__ to format Django + templates. +* Swapped display order of panel header and close button to prevent style + conflicts +* Added CSS for resetting the height of elements too to avoid problems with + global CSS of a website where the toolbar is used. + 5.1.0 (2025-03-20) ------------------ + * Added Django 5.2 to the tox matrix. * Updated package metadata to include well-known labels. * Added resources section to the documentation. diff --git a/docs/conf.py b/docs/conf.py index 4cb37988e..6e67aac2e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -25,7 +25,7 @@ copyright = copyright.format(datetime.date.today().year) # The full version, including alpha/beta/rc tags -release = "5.1.0" +release = "5.2.0" # -- General configuration --------------------------------------------------- diff --git a/docs/configuration.rst b/docs/configuration.rst index 7cd6bc11b..d9e7ff342 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -201,13 +201,6 @@ Toolbar options request when it occurs. This is especially useful when using htmx boosting or similar JavaScript techniques. -.. _DEFAULT_THEME: - -* ``DEFAULT_THEME`` - - Default: ``"auto"`` - - This controls which theme will use the toolbar by default. Panel options ~~~~~~~~~~~~~ diff --git a/docs/panels.rst b/docs/panels.rst index be481fb6e..a116bff1e 100644 --- a/docs/panels.rst +++ b/docs/panels.rst @@ -122,6 +122,11 @@ Since this behavior is annoying when you aren't debugging a redirect, this panel is included but inactive by default. You can activate it by default with the ``DISABLE_PANELS`` configuration option. +To further customize the behavior, you can subclass the ``RedirectsPanel`` +and override the ``get_interception_response`` method to manipulate the +response directly. To use a custom ``RedirectsPanel``, you need to replace +the original one in ``DEBUG_TOOLBAR_PANELS`` in your ``settings.py``. + .. _profiling-panel: Profiling diff --git a/example/templates/htmx/boost.html b/example/templates/htmx/boost.html index 782303b4e..7153a79ee 100644 --- a/example/templates/htmx/boost.html +++ b/example/templates/htmx/boost.html @@ -7,7 +7,7 @@ -

    Index of Tests (htmx) - Page {{page_num|default:"1"}}

    +

    Index of Tests (htmx) - Page {{ page_num|default:"1" }}

    For the debug panel to remain through page navigation, add the setting: diff --git a/example/templates/turbo/index.html b/example/templates/turbo/index.html index 143054e37..16ca9f2c6 100644 --- a/example/templates/turbo/index.html +++ b/example/templates/turbo/index.html @@ -7,7 +7,7 @@ -

    Turbo Index - Page {{page_num|default:"1"}}

    +

    Turbo Index - Page {{ page_num|default:"1" }}

    For the debug panel to remain through page navigation, add the setting: diff --git a/tests/panels/test_custom.py b/tests/panels/test_custom.py index f13c4ef62..661a5cc53 100644 --- a/tests/panels/test_custom.py +++ b/tests/panels/test_custom.py @@ -33,8 +33,8 @@ def test_escapes_panel_title(self): """

    -

    Title with special chars &"'<>

    +
    diff --git a/tests/panels/test_redirects.py b/tests/panels/test_redirects.py index 2abed9fd0..7d6d5ac06 100644 --- a/tests/panels/test_redirects.py +++ b/tests/panels/test_redirects.py @@ -85,3 +85,16 @@ async def get_response(request): response = await self.panel.process_request(self.request) self.assertIsInstance(response, HttpResponse) self.assertTrue(response is await_response) + + def test_original_response_preserved(self): + redirect = HttpResponse(status=302) + redirect["Location"] = "http://somewhere/else/" + self._get_response = lambda request: redirect + response = self.panel.process_request(self.request) + self.assertFalse(response is redirect) + self.assertTrue(hasattr(response, "original_response")) + self.assertTrue(response.original_response is redirect) + self.assertIsNone(response.get("Location")) + self.assertEqual( + response.original_response.get("Location"), "http://somewhere/else/" + ) diff --git a/tests/panels/test_request.py b/tests/panels/test_request.py index 707b50bb4..2eb7ba610 100644 --- a/tests/panels/test_request.py +++ b/tests/panels/test_request.py @@ -136,3 +136,76 @@ def test_session_list_sorted_or_not(self): self.panel.generate_stats(self.request, response) panel_stats = self.panel.get_stats() self.assertEqual(panel_stats["session"], data) + + def test_sensitive_post_data_sanitized(self): + """Test that sensitive POST data is redacted.""" + self.request.POST = {"username": "testuser", "password": "secret123"} + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) + + # Check that password is redacted in panel content + content = self.panel.content + self.assertIn("username", content) + self.assertIn("testuser", content) + self.assertIn("password", content) + self.assertNotIn("secret123", content) + self.assertIn("********************", content) + + def test_sensitive_get_data_sanitized(self): + """Test that sensitive GET data is redacted.""" + self.request.GET = {"api_key": "abc123", "q": "search term"} + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) + + # Check that api_key is redacted in panel content + content = self.panel.content + self.assertIn("api_key", content) + self.assertNotIn("abc123", content) + self.assertIn("********************", content) + self.assertIn("q", content) + self.assertIn("search term", content) + + def test_sensitive_cookie_data_sanitized(self): + """Test that sensitive cookie data is redacted.""" + self.request.COOKIES = {"session_id": "abc123", "auth_token": "xyz789"} + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) + + # Check that auth_token is redacted in panel content + content = self.panel.content + self.assertIn("session_id", content) + self.assertIn("abc123", content) + self.assertIn("auth_token", content) + self.assertNotIn("xyz789", content) + self.assertIn("********************", content) + + def test_sensitive_session_data_sanitized(self): + """Test that sensitive session data is redacted.""" + self.request.session = {"user_id": 123, "auth_token": "xyz789"} + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) + + # Check that auth_token is redacted in panel content + content = self.panel.content + self.assertIn("user_id", content) + self.assertIn("123", content) + self.assertIn("auth_token", content) + self.assertNotIn("xyz789", content) + self.assertIn("********************", content) + + def test_querydict_sanitized(self): + """Test that sensitive data in QueryDict objects is properly redacted.""" + query_dict = QueryDict("username=testuser&password=secret123&token=abc456") + self.request.GET = query_dict + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) + + # Check that sensitive data is redacted in panel content + content = self.panel.content + self.assertIn("username", content) + self.assertIn("testuser", content) + self.assertIn("password", content) + self.assertNotIn("secret123", content) + self.assertIn("token", content) + self.assertNotIn("abc456", content) + self.assertIn("********************", content) diff --git a/tests/panels/test_settings.py b/tests/panels/test_settings.py index 5bf29d322..89b016dc0 100644 --- a/tests/panels/test_settings.py +++ b/tests/panels/test_settings.py @@ -24,8 +24,8 @@ def test_panel_title(self): """
    -

    Settings from None

    +
    diff --git a/tests/panels/test_sql.py b/tests/panels/test_sql.py index 8e105657b..a411abb5d 100644 --- a/tests/panels/test_sql.py +++ b/tests/panels/test_sql.py @@ -655,8 +655,8 @@ def test_flat_template_information(self): template_info = query["template_info"] template_name = os.path.basename(template_info["name"]) self.assertEqual(template_name, "flat.html") - self.assertEqual(template_info["context"][2]["content"].strip(), "{{ users }}") - self.assertEqual(template_info["context"][2]["highlight"], True) + self.assertEqual(template_info["context"][3]["content"].strip(), "{{ users }}") + self.assertEqual(template_info["context"][3]["highlight"], True) @override_settings( DEBUG=True, diff --git a/tests/panels/test_template.py b/tests/panels/test_template.py index 636e88a23..44ac4ff0d 100644 --- a/tests/panels/test_template.py +++ b/tests/panels/test_template.py @@ -132,6 +132,24 @@ def test_lazyobject_eval(self): self.panel.generate_stats(self.request, response) self.assertIn("lazy_value", self.panel.content) + @override_settings( + DEBUG=True, + DEBUG_TOOLBAR_PANELS=["debug_toolbar.panels.templates.TemplatesPanel"], + ) + def test_template_source(self): + from django.core import signing + from django.template.loader import get_template + + template = get_template("basic.html") + url = "/__debug__/template_source/" + data = { + "template": template.template.name, + "template_origin": signing.dumps(template.template.origin.name), + } + + response = self.client.get(url, data) + self.assertEqual(response.status_code, 200) + @override_settings( DEBUG=True, DEBUG_TOOLBAR_PANELS=["debug_toolbar.panels.templates.TemplatesPanel"] diff --git a/tests/settings.py b/tests/settings.py index 0bf88bec1..12561fb11 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -7,6 +7,7 @@ # Quick-start development settings - unsuitable for production +DEBUG = False SECRET_KEY = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" INTERNAL_IPS = ["127.0.0.1"] @@ -27,6 +28,10 @@ "django.contrib.messages", "django.contrib.staticfiles", "debug_toolbar", + # We are not actively using template-partials; we just want more nesting + # in our template loader configuration, see + # https://github.com/django-commons/django-debug-toolbar/issues/2109 + "template_partials", "tests", ] diff --git a/tests/templates/ajax/ajax.html b/tests/templates/ajax/ajax.html index c9de3acb6..7955456de 100644 --- a/tests/templates/ajax/ajax.html +++ b/tests/templates/ajax/ajax.html @@ -1,4 +1,5 @@ {% extends "base.html" %} + {% block content %}
    click for ajax
    @@ -18,4 +19,4 @@ } document.addEventListener("click", (event) => {send_ajax()}); -{% endblock %} +{% endblock content %} diff --git a/tests/templates/basic.html b/tests/templates/basic.html index 46f88e4da..02f87200a 100644 --- a/tests/templates/basic.html +++ b/tests/templates/basic.html @@ -1,2 +1,3 @@ {% extends "base.html" %} + {% block content %}Test for {{ title }}{% endblock %} diff --git a/tests/templates/jinja2/basic.jinja b/tests/templates/jinja2/basic.jinja index e531eee64..1ebced724 100644 --- a/tests/templates/jinja2/basic.jinja +++ b/tests/templates/jinja2/basic.jinja @@ -1,5 +1,6 @@ {% extends 'base.html' %} + {% block content %} Test for {{ title }} (Jinja) {% for i in range(10) %}{{ i }}{% endfor %} {# Jinja2 supports range(), Django templates do not #} -{% endblock %} +{% endblock content %} diff --git a/tests/templates/sql/flat.html b/tests/templates/sql/flat.html index 058dbe043..ee5386c55 100644 --- a/tests/templates/sql/flat.html +++ b/tests/templates/sql/flat.html @@ -1,4 +1,5 @@ {% extends "base.html" %} + {% block content %} {{ users }} -{% endblock %} +{% endblock content %} diff --git a/tests/templates/sql/nested.html b/tests/templates/sql/nested.html index 8558e2d45..e23a53af1 100644 --- a/tests/templates/sql/nested.html +++ b/tests/templates/sql/nested.html @@ -1,4 +1,5 @@ {% extends "base.html" %} + {% block content %} {% include "sql/included.html" %} -{% endblock %} +{% endblock content %} diff --git a/tests/templates/staticfiles/async_static.html b/tests/templates/staticfiles/async_static.html index fc0c9b885..80f636cce 100644 --- a/tests/templates/staticfiles/async_static.html +++ b/tests/templates/staticfiles/async_static.html @@ -3,4 +3,4 @@ {% block head %} -{% endblock %} +{% endblock head %} diff --git a/tests/test_integration.py b/tests/test_integration.py index 3cc0b1420..a431ba29f 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -649,6 +649,9 @@ def setUpClass(cls): options = Options() if os.environ.get("CI"): options.add_argument("-headless") + # Set the browser preference to light mode for consistent testing + options.set_preference("ui.systemUsesDarkTheme", 0) + options.set_preference("ui.prefersReducedMotion", 0) cls.selenium = webdriver.Firefox(options=options) @classmethod @@ -809,7 +812,7 @@ def test_displays_server_error(self): debug_window = self.selenium.find_element(By.ID, "djDebugWindow") self.selenium.find_element(By.CLASS_NAME, "BuggyPanel").click() self.wait.until(EC.visibility_of(debug_window)) - self.assertEqual(debug_window.text, "»\n500: Internal Server Error") + self.assertEqual(debug_window.text, "500: Internal Server Error\n»") def test_toolbar_language_will_render_to_default_language_when_not_set(self): self.get("/regular/basic/") @@ -892,26 +895,35 @@ def test_theme_toggle(self): toolbar = self.selenium.find_element(By.ID, "djDebug") # Check that the default theme is auto - self.assertEqual(toolbar.get_attribute("data-theme"), "auto") + self.assertEqual(toolbar.get_attribute("data-user-theme"), "auto") # The theme toggle button is shown on the toolbar toggle_button = self.selenium.find_element(By.ID, "djToggleThemeButton") self.assertTrue(toggle_button.is_displayed()) - # The theme changes when user clicks the button - toggle_button.click() + # The browser is set to light mode via Firefox preferences + # With light mode system preference, the order is: auto -> dark -> light -> auto + # Check that auto initially uses light theme + self.assertEqual(toolbar.get_attribute("data-user-theme"), "auto") self.assertEqual(toolbar.get_attribute("data-theme"), "light") - toggle_button.click() + + # The theme changes when user clicks the button + toggle_button.click() # auto -> dark + self.assertEqual(toolbar.get_attribute("data-user-theme"), "dark") self.assertEqual(toolbar.get_attribute("data-theme"), "dark") - toggle_button.click() - self.assertEqual(toolbar.get_attribute("data-theme"), "auto") - # Switch back to light. - toggle_button.click() + + toggle_button.click() # dark -> light + self.assertEqual(toolbar.get_attribute("data-user-theme"), "light") + self.assertEqual(toolbar.get_attribute("data-theme"), "light") + + toggle_button.click() # light -> auto + self.assertEqual(toolbar.get_attribute("data-user-theme"), "auto") self.assertEqual(toolbar.get_attribute("data-theme"), "light") # Enter the page again to check that user settings is saved self.get("/regular/basic/") toolbar = self.selenium.find_element(By.ID, "djDebug") + self.assertEqual(toolbar.get_attribute("data-user-theme"), "auto") self.assertEqual(toolbar.get_attribute("data-theme"), "light") def test_async_sql_action(self): diff --git a/tests/test_utils.py b/tests/test_utils.py index 26bfce005..646b6a5ad 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,5 +1,6 @@ import unittest +from django.http import QueryDict from django.test import override_settings import debug_toolbar.utils @@ -8,6 +9,7 @@ get_stack, get_stack_trace, render_stacktrace, + sanitize_and_sort_request_vars, tidy_stacktrace, ) @@ -109,3 +111,63 @@ def __init__(self, value): rendered_stack_2 = render_stacktrace(stack_2_wrapper.value) self.assertNotIn("test_locals_value_1", rendered_stack_2) self.assertIn("test_locals_value_2", rendered_stack_2) + + +class SanitizeAndSortRequestVarsTestCase(unittest.TestCase): + """Tests for the sanitize_and_sort_request_vars function.""" + + def test_dict_sanitization(self): + """Test sanitization of a regular dictionary.""" + test_dict = { + "username": "testuser", + "password": "secret123", + "api_key": "abc123", + } + result = sanitize_and_sort_request_vars(test_dict) + + # Convert to dict for easier testing + result_dict = dict(result["list"]) + + self.assertEqual(result_dict["username"], "testuser") + self.assertEqual(result_dict["password"], "********************") + self.assertEqual(result_dict["api_key"], "********************") + + def test_querydict_sanitization(self): + """Test sanitization of a QueryDict.""" + query_dict = QueryDict("username=testuser&password=secret123&api_key=abc123") + result = sanitize_and_sort_request_vars(query_dict) + + # Convert to dict for easier testing + result_dict = dict(result["list"]) + + self.assertEqual(result_dict["username"], "testuser") + self.assertEqual(result_dict["password"], "********************") + self.assertEqual(result_dict["api_key"], "********************") + + def test_non_sortable_dict_keys(self): + """Test dictionary with keys that can't be sorted.""" + test_dict = { + 1: "one", + "2": "two", + None: "none", + } + result = sanitize_and_sort_request_vars(test_dict) + self.assertEqual(len(result["list"]), 3) + result_dict = dict(result["list"]) + self.assertEqual(result_dict[1], "one") + self.assertEqual(result_dict["2"], "two") + self.assertEqual(result_dict[None], "none") + + def test_querydict_multiple_values(self): + """Test QueryDict with multiple values for the same key.""" + query_dict = QueryDict("name=bar1&name=bar2&title=value") + result = sanitize_and_sort_request_vars(query_dict) + result_dict = dict(result["list"]) + self.assertEqual(result_dict["name"], ["bar1", "bar2"]) + self.assertEqual(result_dict["title"], "value") + + def test_non_dict_input(self): + """Test handling of non-dict input.""" + test_input = ["not", "a", "dict"] + result = sanitize_and_sort_request_vars(test_input) + self.assertEqual(result["raw"], test_input) diff --git a/tox.ini b/tox.ini index c8f4a6815..e2dcdd6c6 100644 --- a/tox.ini +++ b/tox.ini @@ -26,6 +26,7 @@ deps = selenium>=4.8.0 sqlparse django-csp + django-template-partials passenv= CI COVERAGE_ARGS
    {% trans "Timing attribute" %}{% trans "Timeline" %}{% trans "Milliseconds since navigation start (+length)" %}{% translate "Timing attribute" %}{% translate "Timeline" %}{% translate "Milliseconds since navigation start (+length)" %}
    {% trans "Package" %}{% trans "Name" %}{% trans "Version" %}{% translate "Package" %}{% translate "Name" %}{% translate "Version" %}