diff --git a/.eslintrc.js b/.eslintrc.js
deleted file mode 100644
index b0c799d88..000000000
--- a/.eslintrc.js
+++ /dev/null
@@ -1,22 +0,0 @@
-module.exports = {
- root: true,
- "env": {
- "browser": true,
- "es6": true,
- node: true,
- },
- "extends": "eslint:recommended",
- "parserOptions": {
- "ecmaVersion": 6,
- "sourceType": "module"
- },
- "rules": {
- "curly": ["error", "all"],
- "dot-notation": "error",
- "eqeqeq": "error",
- "no-eval": "error",
- "no-var": "error",
- "prefer-const": "error",
- "semi": "error"
- }
-};
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 000000000..be006de9a
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,13 @@
+# Keep GitHub Actions up to date with GitHub's Dependabot...
+# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot
+# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem
+version: 2
+updates:
+ - package-ecosystem: github-actions
+ directory: /
+ groups:
+ github-actions:
+ patterns:
+ - "*" # Group all Actions updates into a single larger pull request
+ schedule:
+ interval: weekly
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
new file mode 100644
index 000000000..a0722f0ac
--- /dev/null
+++ b/.github/workflows/coverage.yml
@@ -0,0 +1,33 @@
+# .github/workflows/coverage.yml
+name: Post coverage comment
+
+on:
+ workflow_run:
+ workflows: ["Test"]
+ types:
+ - completed
+
+jobs:
+ test:
+ name: Run tests & display coverage
+ runs-on: ubuntu-latest
+ if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
+ permissions:
+ # Gives the action the necessary permissions for publishing new
+ # comments in pull requests.
+ pull-requests: write
+ # Gives the action the necessary permissions for editing existing
+ # comments (to avoid publishing multiple comments in the same PR)
+ contents: write
+ # Gives the action the necessary permissions for looking up the
+ # workflow that launched this workflow, and download the related
+ # artifact that contains the comment to be published
+ actions: read
+ steps:
+ # DO NOT run actions/checkout here, for security reasons
+ # For details, refer to https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
+ - name: Post comment
+ uses: py-cov-action/python-coverage-comment-action@v3
+ with:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GITHUB_PR_RUN_ID: ${{ github.event.workflow_run.id }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 2059d37f8..b57181444 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -16,7 +16,7 @@ jobs:
fetch-depth: 0
- name: Set up Python
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: 3.8
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index cb28e217e..cd5d8dd8b 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -33,7 +33,7 @@ jobs:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
@@ -44,7 +44,7 @@ jobs:
echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT
- name: Cache
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ${{ steps.pip-cache.outputs.dir }}
key:
@@ -66,11 +66,6 @@ jobs:
DB_HOST: 127.0.0.1
DB_PORT: 3306
- - name: Upload coverage data
- uses: actions/upload-artifact@v3
- with:
- name: coverage-data
- path: ".coverage.*"
postgres:
runs-on: ubuntu-latest
@@ -108,7 +103,7 @@ jobs:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
@@ -119,7 +114,7 @@ jobs:
echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT
- name: Cache
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ${{ steps.pip-cache.outputs.dir }}
key:
@@ -144,12 +139,6 @@ jobs:
DB_HOST: localhost
DB_PORT: 5432
- - name: Upload coverage data
- uses: actions/upload-artifact@v3
- with:
- name: coverage-data
- path: ".coverage.*"
-
sqlite:
runs-on: ubuntu-latest
strategy:
@@ -162,7 +151,7 @@ jobs:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
@@ -173,7 +162,7 @@ jobs:
echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT
- name: Cache
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ${{ steps.pip-cache.outputs.dir }}
key:
@@ -192,43 +181,6 @@ jobs:
DB_BACKEND: sqlite3
DB_NAME: ":memory:"
- - name: Upload coverage data
- uses: actions/upload-artifact@v3
- with:
- name: coverage-data
- path: ".coverage.*"
-
- coverage:
- name: Check coverage.
- runs-on: "ubuntu-latest"
- needs: [sqlite, mysql, postgres]
- steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-python@v4
- with:
- # Use latest, so it understands all syntax.
- python-version: "3.11"
-
- - run: python -m pip install --upgrade coverage[toml]
-
- - name: Download coverage data.
- uses: actions/download-artifact@v3
- with:
- name: coverage-data
-
- - name: Combine coverage & check percentage
- run: |
- python -m coverage combine
- python -m coverage html
- python -m coverage report
-
- - name: Upload HTML report if check failed.
- uses: actions/upload-artifact@v3
- with:
- name: html-report
- path: htmlcov
- if: ${{ failure() }}
-
lint:
runs-on: ubuntu-latest
strategy:
@@ -238,7 +190,7 @@ jobs:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: 3.8
@@ -248,7 +200,7 @@ jobs:
echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT
- name: Cache
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ${{ steps.pip-cache.outputs.dir }}
key:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index c2f93ac73..1eb0a7df1 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.5.0
+ rev: v4.6.0
hooks:
- id: check-toml
- id: check-yaml
@@ -14,10 +14,10 @@ repos:
hooks:
- id: doc8
- repo: https://github.com/adamchainz/django-upgrade
- rev: 1.15.0
+ rev: 1.16.0
hooks:
- id: django-upgrade
- args: [--target-version, "3.2"]
+ args: [--target-version, "4.2"]
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.10.0
hooks:
@@ -32,24 +32,28 @@ repos:
args:
- --trailing-comma=es5
- repo: https://github.com/pre-commit/mirrors-eslint
- rev: v8.56.0
+ rev: v9.0.0
hooks:
- id: eslint
+ additional_dependencies:
+ - "eslint@v9.0.0-beta.1"
+ - "@eslint/js@v9.0.0-beta.1"
+ - "globals"
files: \.js?$
types: [file]
args:
- --fix
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: 'v0.1.11'
+ rev: 'v0.3.7'
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format
- repo: https://github.com/tox-dev/pyproject-fmt
- rev: 1.5.3
+ rev: 1.7.0
hooks:
- id: pyproject-fmt
- repo: https://github.com/abravalheri/validate-pyproject
- rev: v0.15
+ rev: v0.16
hooks:
- id: validate-pyproject
diff --git a/Makefile b/Makefile
index 1600496e5..24b59ab95 100644
--- a/Makefile
+++ b/Makefile
@@ -6,6 +6,9 @@ example:
--noinput --username="$(USER)" --email="$(USER)@mailinator.com"
python example/manage.py runserver
+example_test:
+ python example/manage.py test example
+
test:
DJANGO_SETTINGS_MODULE=tests.settings \
python -m django test $${TEST_ARGS:-tests}
diff --git a/README.rst b/README.rst
index 0eaaa6bd3..24dba95a3 100644
--- a/README.rst
+++ b/README.rst
@@ -44,8 +44,8 @@ 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 4.1.0. It works on
-Django ≥ 3.2.4.
+The current stable version of the Debug Toolbar is 4.4.0. It works on
+Django ≥ 4.2.0.
The Debug Toolbar does not currently support `Django's asynchronous views
`_.
diff --git a/debug_toolbar/__init__.py b/debug_toolbar/__init__.py
index 9a8c2b24f..ecf3e559a 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 = "4.3.0"
+VERSION = "4.4.0"
# Code that discovers files or modules in INSTALLED_APPS imports this module.
urls = "debug_toolbar.urls", APP_NAME
diff --git a/debug_toolbar/apps.py b/debug_toolbar/apps.py
index 0a10a4b08..a2e977d84 100644
--- a/debug_toolbar/apps.py
+++ b/debug_toolbar/apps.py
@@ -3,7 +3,7 @@
from django.apps import AppConfig
from django.conf import settings
-from django.core.checks import Warning, register
+from django.core.checks import Error, Warning, register
from django.middleware.gzip import GZipMiddleware
from django.utils.module_loading import import_string
from django.utils.translation import gettext_lazy as _
@@ -177,7 +177,7 @@ def check_panels(app_configs, **kwargs):
return errors
-@register()
+@register
def js_mimetype_check(app_configs, **kwargs):
"""
Check that JavaScript files are resolving to the correct content type.
@@ -206,3 +206,40 @@ def js_mimetype_check(app_configs, **kwargs):
)
]
return []
+
+
+@register
+def debug_toolbar_installed_when_running_tests_check(app_configs, **kwargs):
+ """
+ Check that the toolbar is not being used when tests are running
+ """
+ if not settings.DEBUG and dt_settings.get_config()["IS_RUNNING_TESTS"]:
+ return [
+ Error(
+ "The Django Debug Toolbar can't be used with tests",
+ hint="Django changes the DEBUG setting to False when running "
+ "tests. By default the Django Debug Toolbar is installed because "
+ "DEBUG is set to True. For most cases, you need to avoid installing "
+ "the toolbar when running tests. If you feel this check is in error, "
+ "you can set `DEBUG_TOOLBAR_CONFIG['IS_RUNNING_TESTS'] = False` to "
+ "bypass this check.",
+ id="debug_toolbar.E001",
+ )
+ ]
+ else:
+ return []
+
+
+@register
+def check_settings(app_configs, **kwargs):
+ errors = []
+ USER_CONFIG = getattr(settings, "DEBUG_TOOLBAR_CONFIG", {})
+ if "OBSERVE_REQUEST_CALLBACK" in USER_CONFIG:
+ errors.append(
+ Warning(
+ "The deprecated OBSERVE_REQUEST_CALLBACK setting is present in DEBUG_TOOLBAR_CONFIG.",
+ hint="Use the UPDATE_ON_FETCH and/or SHOW_TOOLBAR_CALLBACK settings instead.",
+ id="debug_toolbar.W008",
+ )
+ )
+ return errors
diff --git a/debug_toolbar/middleware.py b/debug_toolbar/middleware.py
index b5e5d0827..38cf92884 100644
--- a/debug_toolbar/middleware.py
+++ b/debug_toolbar/middleware.py
@@ -3,6 +3,7 @@
"""
import re
+import socket
from functools import lru_cache
from django.conf import settings
@@ -19,7 +20,22 @@ def show_toolbar(request):
"""
Default function to determine whether to show the toolbar on a given page.
"""
- return settings.DEBUG and request.META.get("REMOTE_ADDR") in settings.INTERNAL_IPS
+ internal_ips = settings.INTERNAL_IPS.copy()
+
+ try:
+ # This is a hack for docker installations. It attempts to look
+ # up the IP address of the docker host.
+ # This is not guaranteed to work.
+ docker_ip = (
+ # Convert the last segment of the IP address to be .1
+ ".".join(socket.gethostbyname("host.docker.internal").rsplit(".")[:-1])
+ + ".1"
+ )
+ internal_ips.append(docker_ip)
+ except socket.gaierror:
+ # It's fine if the lookup errored since they may not be using docker
+ pass
+ return settings.DEBUG and request.META.get("REMOTE_ADDR") in internal_ips
@lru_cache(maxsize=None)
diff --git a/debug_toolbar/panels/profiling.py b/debug_toolbar/panels/profiling.py
index 2b7742400..64224a2db 100644
--- a/debug_toolbar/panels/profiling.py
+++ b/debug_toolbar/panels/profiling.py
@@ -86,12 +86,10 @@ def func_std_string(self): # match what old profile produced
)
def subfuncs(self):
- i = 0
h, s, v = self.hsv
count = len(self.statobj.all_callees[self.func])
- for func, stats in self.statobj.all_callees[self.func].items():
- i += 1
- h1 = h + (i / count) / (self.depth + 1)
+ for i, (func, stats) in enumerate(self.statobj.all_callees[self.func].items()):
+ h1 = h + ((i + 1) / count) / (self.depth + 1)
s1 = 0 if stats[3] == 0 else s * (stats[3] / self.stats[3])
yield FunctionCall(
self.statobj,
diff --git a/debug_toolbar/panels/staticfiles.py b/debug_toolbar/panels/staticfiles.py
index 5f9efb5c3..2eed2efa0 100644
--- a/debug_toolbar/panels/staticfiles.py
+++ b/debug_toolbar/panels/staticfiles.py
@@ -4,7 +4,6 @@
from django.conf import settings
from django.contrib.staticfiles import finders, storage
-from django.core.checks import Warning
from django.utils.functional import LazyObject
from django.utils.translation import gettext_lazy as _, ngettext
@@ -178,27 +177,3 @@ def get_staticfiles_apps(self):
if app not in apps:
apps.append(app)
return apps
-
- @classmethod
- def run_checks(cls):
- """
- Check that the integration is configured correctly for the panel.
-
- Specifically look for static files that haven't been collected yet.
-
- Return a list of :class: `django.core.checks.CheckMessage` instances.
- """
- errors = []
- for finder in finders.get_finders():
- try:
- for path, finder_storage in finder.list([]):
- finder_storage.path(path)
- except OSError:
- errors.append(
- Warning(
- "debug_toolbar requires the STATICFILES_DIRS directories to exist.",
- hint="Running manage.py collectstatic may help uncover the issue.",
- id="debug_toolbar.staticfiles.W001",
- )
- )
- return errors
diff --git a/debug_toolbar/panels/templates/panel.py b/debug_toolbar/panels/templates/panel.py
index f8c9242ca..c0c6246b2 100644
--- a/debug_toolbar/panels/templates/panel.py
+++ b/debug_toolbar/panels/templates/panel.py
@@ -154,9 +154,9 @@ def process_context_list(self, context_layers):
# QuerySet would trigger the database: user can run the
# query from SQL Panel
elif isinstance(value, (QuerySet, RawQuerySet)):
- temp_layer[
- key
- ] = f"<<{value.__class__.__name__.lower()} of {value.model._meta.label}>>"
+ temp_layer[key] = (
+ f"<<{value.__class__.__name__.lower()} of {value.model._meta.label}>>"
+ )
else:
token = allow_sql.set(False) # noqa: FBT003
try:
diff --git a/debug_toolbar/settings.py b/debug_toolbar/settings.py
index 1df24527d..ca7036c34 100644
--- a/debug_toolbar/settings.py
+++ b/debug_toolbar/settings.py
@@ -1,3 +1,4 @@
+import sys
import warnings
from functools import lru_cache
@@ -42,7 +43,9 @@
"SQL_WARNING_THRESHOLD": 500, # milliseconds
"OBSERVE_REQUEST_CALLBACK": "debug_toolbar.toolbar.observe_request",
"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 a35286a1f..170cc3d5f 100644
--- a/debug_toolbar/static/debug_toolbar/css/toolbar.css
+++ b/debug_toolbar/static/debug_toolbar/css/toolbar.css
@@ -1,5 +1,6 @@
/* Variable definitions */
-:root {
+:root,
+#djDebug[data-theme="light"] {
/* Font families are the same as in Django admin/css/base.css */
--djdt-font-family-primary: "Segoe UI", system-ui, Roboto, "Helvetica Neue",
Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
@@ -9,12 +10,79 @@
"Source Code Pro", "Fira Mono", "Droid Sans Mono", "Courier New",
monospace, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
"Noto Color Emoji";
+
+ color-scheme: light;
+ --djdt-font-color: black;
+ --djdt-background-color: white;
+ --djdt-panel-content-background-color: #eee;
+ --djdt-panel-content-table-background-color: var(--djdt-background-color);
+ --djdt-panel-title-background-color: #ffc;
+ --djdt-djdt-panel-content-table-strip-background-color: #f5f5f5;
+ --djdt--highlighted-background-color: lightgrey;
+ --djdt-toggle-template-background-color: #bbb;
+
+ --djdt-sql-font-color: #333;
+ --djdt-pre-text-color: #555;
+ --djdt-path-and-locals: #777;
+ --djdt-stack-span-color: black;
+ --djdt-template-highlight-color: #333;
+
+ --djdt-table-border-color: #ccc;
+ --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);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ color-scheme: dark;
+ --djdt-font-color: #8393a7;
+ --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"] {
+ color-scheme: dark;
+ --djdt-font-color: #8393a7;
+ --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);
}
/* Debug Toolbar CSS Reset, adapted from Eric Meyer's CSS Reset */
#djDebug {
- color: #000;
- background: #fff;
+ color: var(--djdt-font-color);
+ background: var(--djdt-background-color);
}
#djDebug,
#djDebug div,
@@ -87,7 +155,7 @@
outline: 0;
font-size: 12px;
line-height: 1.5em;
- color: #000;
+ color: var(--djdt-font-color);
vertical-align: baseline;
background-color: transparent;
font-family: var(--djdt-font-family-primary);
@@ -100,7 +168,7 @@
#djDebug button {
background-color: #eee;
background-image: linear-gradient(to bottom, #eee, #cccccc);
- border: 1px solid #ccc;
+ border: 1px solid var(--djdt-button-border-color);
border-bottom: 1px solid #bbb;
border-radius: 3px;
color: #333;
@@ -268,10 +336,10 @@
#djDebug pre {
white-space: pre-wrap;
- color: #555;
- border: 1px solid #ccc;
+ color: var(--djdt-pre-text-color);
+ border: 1px solid var(--djdt-pre-border-color);
border-collapse: collapse;
- background-color: #fff;
+ background-color: var(--djdt-background-color);
padding: 2px 3px;
margin-bottom: 3px;
}
@@ -283,7 +351,7 @@
right: 220px;
bottom: 0;
left: 0px;
- background-color: #eee;
+ background-color: var(--djdt-panel-content-background-color);
color: #666;
z-index: 100000000;
}
@@ -294,7 +362,7 @@
#djDebug .djDebugPanelTitle {
position: absolute;
- background-color: #ffc;
+ background-color: var(--djdt-panel-title-background-color);
color: #666;
padding-left: 20px;
top: 0;
@@ -357,16 +425,16 @@
}
#djDebug .djdt-panelContent table {
- border: 1px solid #ccc;
+ border: 1px solid var(--djdt-table-border-color);
border-collapse: collapse;
width: 100%;
- background-color: #fff;
+ background-color: var(--djdt-panel-content-table-background-color);
display: table;
margin-top: 0.8em;
overflow: auto;
}
#djDebug .djdt-panelContent tbody > tr:nth-child(odd):not(.djdt-highlighted) {
- background-color: #f5f5f5;
+ background-color: var(--djdt-panel-content-table-strip-background-color);
}
#djDebug .djdt-panelContent tbody td,
#djDebug .djdt-panelContent tbody th {
@@ -392,7 +460,7 @@
}
#djDebug .djTemplateContext {
- background-color: #fff;
+ background-color: var(--djdt-background-color);
}
#djDebug .djdt-panelContent .djDebugClose {
@@ -433,7 +501,7 @@
#djDebug a.toggleTemplate {
padding: 4px;
- background-color: #bbb;
+ background-color: var(--djdt-toggle-template-background-color);
border-radius: 3px;
}
@@ -445,11 +513,11 @@
}
#djDebug .djDebugCollapsed {
- color: #333;
+ color: var(--djdt-sql-font-color);
}
#djDebug .djDebugUncollapsed {
- color: #333;
+ color: var(--djdt-sql-font-color);
}
#djDebug .djUnselected {
@@ -483,66 +551,66 @@
}
#djDebug .highlight {
- color: #000;
+ color: var(--djdt-font-color);
}
#djDebug .highlight .err {
- color: #000;
+ color: var(--djdt-font-color);
} /* Error */
#djDebug .highlight .g {
- color: #000;
+ color: var(--djdt-font-color);
} /* Generic */
#djDebug .highlight .k {
- color: #000;
+ color: var(--djdt-font-color);
font-weight: bold;
} /* Keyword */
#djDebug .highlight .o {
- color: #000;
+ color: var(--djdt-font-color);
} /* Operator */
#djDebug .highlight .n {
- color: #000;
+ color: var(--djdt-font-color);
} /* Name */
#djDebug .highlight .mi {
- color: #000;
+ color: var(--djdt-font-color);
font-weight: bold;
} /* Literal.Number.Integer */
#djDebug .highlight .l {
- color: #000;
+ color: var(--djdt-font-color);
} /* Literal */
#djDebug .highlight .x {
- color: #000;
+ color: var(--djdt-font-color);
} /* Other */
#djDebug .highlight .p {
- color: #000;
+ color: var(--djdt-font-color);
} /* Punctuation */
#djDebug .highlight .m {
- color: #000;
+ color: var(--djdt-font-color);
font-weight: bold;
} /* Literal.Number */
#djDebug .highlight .s {
- color: #333;
+ color: var(--djdt-template-highlight-color);
} /* Literal.String */
#djDebug .highlight .w {
color: #888888;
} /* Text.Whitespace */
#djDebug .highlight .il {
- color: #000;
+ color: var(--djdt-font-color);
font-weight: bold;
} /* Literal.Number.Integer.Long */
#djDebug .highlight .na {
- color: #333;
+ color: var(--djdt-template-highlight-color);
} /* Name.Attribute */
#djDebug .highlight .nt {
- color: #000;
+ color: var(--djdt-font-color);
font-weight: bold;
} /* Name.Tag */
#djDebug .highlight .nv {
- color: #333;
+ color: var(--djdt-template-highlight-color);
} /* Name.Variable */
#djDebug .highlight .s2 {
- color: #333;
+ color: var(--djdt-template-highlight-color);
} /* Literal.String.Double */
#djDebug .highlight .cp {
- color: #333;
+ color: var(--djdt-template-highlight-color);
} /* Comment.Preproc */
#djDebug svg.djDebugLineChart {
@@ -595,13 +663,13 @@
}
#djDebug .djdt-stack span {
- color: #000;
+ color: var(--djdt-stack-span-color);
font-weight: bold;
}
#djDebug .djdt-stack span.djdt-path,
#djDebug .djdt-stack pre.djdt-locals,
#djDebug .djdt-stack pre.djdt-locals span {
- color: #777;
+ color: var(--djdt-path-and-locals);
font-weight: normal;
}
#djDebug .djdt-stack span.djdt-code {
@@ -612,7 +680,7 @@
}
#djDebug .djdt-raw {
background-color: #fff;
- border: 1px solid #ccc;
+ border: 1px solid var(--djdt-raw-border-color);
margin-top: 0.8em;
padding: 5px;
white-space: pre-wrap;
@@ -631,7 +699,7 @@
max-height: 100%;
}
#djDebug .djdt-highlighted {
- background-color: lightgrey;
+ background-color: var(--djdt--highlighted-background-color);
}
#djDebug tr.djdt-highlighted.djdt-profile-row {
background-color: #ffc;
@@ -654,3 +722,17 @@
.djdt-hidden {
display: none;
}
+
+#djDebug #djDebugToolbar a#djToggleThemeButton {
+ display: flex;
+ align-items: center;
+ cursor: pointer;
+}
+#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 {
+ display: block;
+}
diff --git a/debug_toolbar/static/debug_toolbar/js/toolbar.js b/debug_toolbar/static/debug_toolbar/js/toolbar.js
index 199616336..067b5a312 100644
--- a/debug_toolbar/static/debug_toolbar/js/toolbar.js
+++ b/debug_toolbar/static/debug_toolbar/js/toolbar.js
@@ -1,4 +1,4 @@
-import { $$, ajax, replaceToolbarState, debounce } from "./utils.js";
+import { $$, ajax, debounce, replaceToolbarState } from "./utils.js";
function onKeyDown(event) {
if (event.keyCode === 27) {
@@ -213,6 +213,29 @@ const djdt = {
if (djDebug.dataset.sidebarUrl !== undefined) {
djdt.updateOnAjax();
}
+
+ // Updates the theme using user settings
+ const userTheme = localStorage.getItem("djdt.user-theme");
+ if (userTheme !== null) {
+ djDebug.setAttribute("data-theme", userTheme);
+ }
+ // Adds the listener to the Theme Toggle Button
+ $$.on(djDebug, "click", "#djToggleThemeButton", function () {
+ 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;
+ }
+ });
},
hidePanels() {
const djDebug = getDebugElement();
@@ -276,7 +299,7 @@ const djdt = {
storeId = encodeURIComponent(storeId);
const dest = `${sidebarUrl}?store_id=${storeId}`;
slowjax(dest).then(function (data) {
- if (djdt.needUpdateOnFetch){
+ if (djdt.needUpdateOnFetch) {
replaceToolbarState(storeId, data);
}
});
diff --git a/debug_toolbar/static/debug_toolbar/js/utils.js b/debug_toolbar/static/debug_toolbar/js/utils.js
index b4c7a4cb8..c37525f13 100644
--- a/debug_toolbar/static/debug_toolbar/js/utils.js
+++ b/debug_toolbar/static/debug_toolbar/js/utils.js
@@ -75,7 +75,11 @@ function ajax(url, init) {
return fetch(url, init)
.then(function (response) {
if (response.ok) {
- return response.json();
+ return response.json().catch(function(error){
+ return Promise.reject(
+ new Error("The response is a invalid Json object : " + error)
+ );
+ });
}
return Promise.reject(
new Error(response.status + ": " + response.statusText)
diff --git a/debug_toolbar/templates/debug_toolbar/base.html b/debug_toolbar/templates/debug_toolbar/base.html
index 6f4967f21..4867a834e 100644
--- a/debug_toolbar/templates/debug_toolbar/base.html
+++ b/debug_toolbar/templates/debug_toolbar/base.html
@@ -16,10 +16,16 @@
data-sidebar-url="{{ history_url }}"
{% endif %}
data-default-show="{% if toolbar.config.SHOW_COLLAPSED %}false{% else %}true{% endif %}"
- {{ toolbar.config.ROOT_TAG_EXTRA_ATTRS|safe }} data-update-on-fetch="{{ toolbar.config.UPDATE_ON_FETCH }}">
+ {{ toolbar.config.ROOT_TAG_EXTRA_ATTRS|safe }} data-update-on-fetch="{{ toolbar.config.UPDATE_ON_FETCH }}"
+ data-theme="{{ toolbar.config.DEFAULT_THEME }}">