├── .editorconfig
├── .github
├── PULL_REQUEST_TEMPLATE.md
├── dependabot.yml
└── workflows
│ ├── coverage.yml
│ ├── release.yml
│ └── test.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .readthedocs.yaml
├── .tx
└── config
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.rst
├── SECURITY.md
├── biome.json
├── debug_toolbar
├── __init__.py
├── _compat.py
├── _stubs.py
├── apps.py
├── decorators.py
├── forms.py
├── locale
│ ├── bg
│ │ └── LC_MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ ├── ca
│ │ └── LC_MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ ├── cs
│ │ └── LC_MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ ├── de
│ │ └── LC_MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ ├── en
│ │ └── LC_MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ ├── es
│ │ └── LC_MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ ├── fa
│ │ └── LC_MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ ├── fi
│ │ └── LC_MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ ├── fr
│ │ └── LC_MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ ├── he
│ │ └── LC_MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ ├── id
│ │ └── LC_MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ ├── it
│ │ └── LC_MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ ├── ja
│ │ └── LC_MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ ├── ko
│ │ └── LC_MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ ├── nl
│ │ └── LC_MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ ├── pl
│ │ └── LC_MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ ├── pt
│ │ └── LC_MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ ├── pt_BR
│ │ └── LC_MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ ├── ru
│ │ └── LC_MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ ├── sk
│ │ └── LC_MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ ├── sv_SE
│ │ └── LC_MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ ├── uk
│ │ └── LC_MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ └── zh_CN
│ │ └── LC_MESSAGES
│ │ ├── django.mo
│ │ └── django.po
├── management
│ ├── __init__.py
│ └── commands
│ │ ├── __init__.py
│ │ └── debugsqlshell.py
├── middleware.py
├── panels
│ ├── __init__.py
│ ├── alerts.py
│ ├── cache.py
│ ├── headers.py
│ ├── history
│ │ ├── __init__.py
│ │ ├── forms.py
│ │ ├── panel.py
│ │ └── views.py
│ ├── profiling.py
│ ├── redirects.py
│ ├── request.py
│ ├── settings.py
│ ├── signals.py
│ ├── sql
│ │ ├── __init__.py
│ │ ├── forms.py
│ │ ├── panel.py
│ │ ├── tracking.py
│ │ ├── utils.py
│ │ └── views.py
│ ├── staticfiles.py
│ ├── templates
│ │ ├── __init__.py
│ │ ├── jinja2.py
│ │ ├── panel.py
│ │ └── views.py
│ ├── timer.py
│ └── versions.py
├── settings.py
├── static
│ └── debug_toolbar
│ │ ├── css
│ │ ├── print.css
│ │ └── toolbar.css
│ │ └── js
│ │ ├── history.js
│ │ ├── redirect.js
│ │ ├── timer.js
│ │ ├── toolbar.js
│ │ └── utils.js
├── templates
│ └── debug_toolbar
│ │ ├── base.html
│ │ ├── includes
│ │ ├── panel_button.html
│ │ ├── panel_content.html
│ │ └── theme_selector.html
│ │ ├── panels
│ │ ├── alerts.html
│ │ ├── cache.html
│ │ ├── headers.html
│ │ ├── history.html
│ │ ├── history_tr.html
│ │ ├── profiling.html
│ │ ├── request.html
│ │ ├── request_variables.html
│ │ ├── settings.html
│ │ ├── signals.html
│ │ ├── sql.html
│ │ ├── sql_explain.html
│ │ ├── sql_profile.html
│ │ ├── sql_select.html
│ │ ├── staticfiles.html
│ │ ├── template_source.html
│ │ ├── templates.html
│ │ ├── timer.html
│ │ └── versions.html
│ │ └── redirect.html
├── templatetags
│ └── __init__.py
├── toolbar.py
├── urls.py
├── utils.py
└── views.py
├── docs
├── Makefile
├── architecture.rst
├── changes.rst
├── checks.rst
├── commands.rst
├── conf.py
├── configuration.rst
├── contributing.rst
├── index.rst
├── installation.rst
├── make.bat
├── panels.rst
├── resources.rst
├── spelling_wordlist.txt
└── tips.rst
├── example
├── README.rst
├── __init__.py
├── asgi.py
├── django-debug-toolbar.png
├── example.db
├── manage.py
├── screenshot.py
├── settings.py
├── static
│ └── test.css
├── templates
│ ├── async_db.html
│ ├── bad_form.html
│ ├── htmx
│ │ └── boost.html
│ ├── index.html
│ ├── jinja2
│ │ └── index.jinja
│ ├── jquery
│ │ └── index.html
│ ├── mootools
│ │ └── index.html
│ ├── prototype
│ │ └── index.html
│ └── turbo
│ │ └── index.html
├── test_views.py
├── urls.py
├── views.py
└── wsgi.py
├── pyproject.toml
├── requirements_dev.txt
├── setup.py
├── tests
├── __init__.py
├── additional_static
│ └── base.css
├── base.py
├── commands
│ ├── __init__.py
│ └── test_debugsqlshell.py
├── context_processors.py
├── forms.py
├── loaders.py
├── middleware.py
├── models.py
├── panels
│ ├── __init__.py
│ ├── test_alerts.py
│ ├── test_async_panel_compatibility.py
│ ├── test_cache.py
│ ├── test_custom.py
│ ├── test_history.py
│ ├── test_profiling.py
│ ├── test_redirects.py
│ ├── test_request.py
│ ├── test_settings.py
│ ├── test_sql.py
│ ├── test_staticfiles.py
│ ├── test_template.py
│ └── test_versions.py
├── settings.py
├── sync.py
├── templates
│ ├── ajax
│ │ └── ajax.html
│ ├── base.html
│ ├── basic.html
│ ├── jinja2
│ │ ├── base.html
│ │ └── basic.jinja
│ ├── registration
│ │ └── login.html
│ ├── sql
│ │ ├── flat.html
│ │ ├── included.html
│ │ └── nested.html
│ └── staticfiles
│ │ ├── async_static.html
│ │ └── path.html
├── test_checks.py
├── test_csp_rendering.py
├── test_decorators.py
├── test_forms.py
├── test_integration.py
├── test_integration_async.py
├── test_login_not_required.py
├── test_middleware.py
├── test_settings.py
├── test_toolbar.py
├── test_utils.py
├── urls.py
├── urls_invalid.py
├── urls_use_package_urls.py
└── views.py
└── tox.ini
/.editorconfig:
--------------------------------------------------------------------------------
1 | # https://editorconfig.org/
2 |
3 | root = true
4 |
5 | [*]
6 | indent_style = space
7 | indent_size = 4
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 | end_of_line = lf
11 | charset = utf-8
12 |
13 | [*.html]
14 | indent_size = 2
15 |
16 | [Makefile]
17 | indent_style = tab
18 |
19 | [*.bat]
20 | indent_style = tab
21 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | #### Description
2 |
3 | Please include a summary of the change and which issue is fixed. Please also
4 | include relevant motivation and context. Your commit message should include
5 | this information as well.
6 |
7 | Fixes # (issue)
8 |
9 | #### Checklist:
10 |
11 | - [ ] I have added the relevant tests for this change.
12 | - [ ] I have added an item to the Pending section of ``docs/changes.rst``.
13 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # Keep GitHub Actions up to date with GitHub's Dependabot...
2 | # https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot
3 | # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem
4 | version: 2
5 | updates:
6 | - package-ecosystem: github-actions
7 | directory: /
8 | groups:
9 | github-actions:
10 | patterns:
11 | - "*" # Group all Actions updates into a single larger pull request
12 | schedule:
13 | interval: weekly
14 |
--------------------------------------------------------------------------------
/.github/workflows/coverage.yml:
--------------------------------------------------------------------------------
1 | # .github/workflows/coverage.yml
2 | name: Post coverage comment
3 |
4 | on:
5 | workflow_run:
6 | workflows: ["Test"]
7 | types:
8 | - completed
9 |
10 | jobs:
11 | test:
12 | name: Run tests & display coverage
13 | runs-on: ubuntu-latest
14 | if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
15 | permissions:
16 | # Gives the action the necessary permissions for publishing new
17 | # comments in pull requests.
18 | pull-requests: write
19 | # Gives the action the necessary permissions for editing existing
20 | # comments (to avoid publishing multiple comments in the same PR)
21 | contents: write
22 | # Gives the action the necessary permissions for looking up the
23 | # workflow that launched this workflow, and download the related
24 | # artifact that contains the comment to be published
25 | actions: read
26 | steps:
27 | # DO NOT run actions/checkout here, for security reasons
28 | # For details, refer to https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
29 | - name: Post comment
30 | uses: py-cov-action/python-coverage-comment-action@v3
31 | with:
32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
33 | GITHUB_PR_RUN_ID: ${{ github.event.workflow_run.id }}
34 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Publish Python 🐍 distribution 📦 to PyPI and TestPyPI
2 |
3 | on: push
4 |
5 | env:
6 | PYPI_URL: https://pypi.org/p/django-debug-toolbar
7 | PYPI_TEST_URL: https://test.pypi.org/p/django-debug-toolbar
8 |
9 | jobs:
10 |
11 | build:
12 | name: Build distribution 📦
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - uses: actions/checkout@v4
17 | - name: Set up Python
18 | uses: actions/setup-python@v5
19 | with:
20 | python-version: "3.x"
21 | - name: Install pypa/build
22 | run:
23 | python3 -m pip install build --user
24 | - name: Build a binary wheel and a source tarball
25 | run: python3 -m build
26 | - name: Store the distribution packages
27 | uses: actions/upload-artifact@v4
28 | with:
29 | name: python-package-distributions
30 | path: dist/
31 |
32 | publish-to-pypi:
33 | name: >-
34 | Publish Python 🐍 distribution 📦 to PyPI
35 | if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes
36 | needs:
37 | - build
38 | runs-on: ubuntu-latest
39 | environment:
40 | name: pypi
41 | url: ${{ env.PYPI_URL }}
42 | permissions:
43 | id-token: write # IMPORTANT: mandatory for trusted publishing
44 | steps:
45 | - name: Download all the dists
46 | uses: actions/download-artifact@v4
47 | with:
48 | name: python-package-distributions
49 | path: dist/
50 | - name: Publish distribution 📦 to PyPI
51 | uses: pypa/gh-action-pypi-publish@release/v1.12
52 |
53 | github-release:
54 | name: >-
55 | Sign the Python 🐍 distribution 📦 with Sigstore
56 | and upload them to GitHub Release
57 | needs:
58 | - publish-to-pypi
59 | runs-on: ubuntu-latest
60 |
61 | permissions:
62 | contents: write # IMPORTANT: mandatory for making GitHub Releases
63 | id-token: write # IMPORTANT: mandatory for sigstore
64 |
65 | steps:
66 | - name: Download all the dists
67 | uses: actions/download-artifact@v4
68 | with:
69 | name: python-package-distributions
70 | path: dist/
71 | - name: Sign the dists with Sigstore
72 | uses: sigstore/gh-action-sigstore-python@v3.0.0
73 | with:
74 | inputs: >-
75 | ./dist/*.tar.gz
76 | ./dist/*.whl
77 | - name: Create GitHub Release
78 | env:
79 | GITHUB_TOKEN: ${{ github.token }}
80 | run: >-
81 | gh release create
82 | '${{ github.ref_name }}'
83 | --repo '${{ github.repository }}'
84 | --notes ""
85 | - name: Upload artifact signatures to GitHub Release
86 | env:
87 | GITHUB_TOKEN: ${{ github.token }}
88 | # Upload to GitHub Release using the `gh` CLI.
89 | # `dist/` contains the built packages, and the
90 | # sigstore-produced signatures and certificates.
91 | run: >-
92 | gh release upload
93 | '${{ github.ref_name }}' dist/**
94 | --repo '${{ github.repository }}'
95 |
96 | publish-to-testpypi:
97 | name: Publish Python 🐍 distribution 📦 to TestPyPI
98 | if: startsWith(github.ref, 'refs/tags/') # only publish to Test PyPI on tag pushes
99 | needs:
100 | - build
101 | runs-on: ubuntu-latest
102 |
103 | environment:
104 | name: testpypi
105 | url: ${{ env.PYPI_TEST_URL }}
106 |
107 | permissions:
108 | id-token: write # IMPORTANT: mandatory for trusted publishing
109 |
110 | steps:
111 | - name: Download all the dists
112 | uses: actions/download-artifact@v4
113 | with:
114 | name: python-package-distributions
115 | path: dist/
116 | - name: Publish distribution 📦 to TestPyPI
117 | uses: pypa/gh-action-pypi-publish@release/v1.12
118 | with:
119 | repository-url: https://test.pypi.org/legacy/
120 | skip-existing: true
121 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.DS_Store
3 | *~
4 | .idea
5 | build
6 | .coverage*
7 | dist
8 | django_debug_toolbar.egg-info
9 | docs/_build
10 | example/db.sqlite3
11 | htmlcov
12 | .tox
13 | geckodriver.log
14 | coverage.xml
15 | .direnv/
16 | .envrc
17 | venv
18 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/pre-commit/pre-commit-hooks
3 | rev: v5.0.0
4 | hooks:
5 | - id: check-toml
6 | - id: check-yaml
7 | - id: end-of-file-fixer
8 | - id: trailing-whitespace
9 | - id: mixed-line-ending
10 | - id: file-contents-sorter
11 | files: docs/spelling_wordlist.txt
12 | - repo: https://github.com/pycqa/doc8
13 | rev: v1.1.2
14 | hooks:
15 | - id: doc8
16 | - repo: https://github.com/adamchainz/django-upgrade
17 | rev: 1.25.0
18 | hooks:
19 | - id: django-upgrade
20 | args: [--target-version, "4.2"]
21 | - repo: https://github.com/adamchainz/djade-pre-commit
22 | rev: "1.4.0"
23 | hooks:
24 | - id: djade
25 | args: [--target-version, "4.2"]
26 | - repo: https://github.com/pre-commit/pygrep-hooks
27 | rev: v1.10.0
28 | hooks:
29 | - id: rst-backticks
30 | - id: rst-directive-colons
31 | - repo: https://github.com/biomejs/pre-commit
32 | rev: v2.0.0-beta.5
33 | hooks:
34 | - id: biome-check
35 | verbose: true
36 | - repo: https://github.com/astral-sh/ruff-pre-commit
37 | rev: 'v0.11.12'
38 | hooks:
39 | - id: ruff
40 | args: [--fix, --exit-non-zero-on-fix]
41 | - id: ruff-format
42 | - repo: https://github.com/tox-dev/pyproject-fmt
43 | rev: v2.6.0
44 | hooks:
45 | - id: pyproject-fmt
46 | - repo: https://github.com/abravalheri/validate-pyproject
47 | rev: v0.24.1
48 | hooks:
49 | - id: validate-pyproject
50 |
--------------------------------------------------------------------------------
/.readthedocs.yaml:
--------------------------------------------------------------------------------
1 | # .readthedocs.yaml
2 | # Read the Docs configuration file
3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
4 |
5 | version: 2
6 |
7 | build:
8 | os: ubuntu-24.04
9 | tools:
10 | python: "3.10"
11 |
12 | sphinx:
13 | configuration: docs/conf.py
14 |
15 | python:
16 | install:
17 | - requirements: requirements_dev.txt
18 | - method: pip
19 | path: .
20 |
--------------------------------------------------------------------------------
/.tx/config:
--------------------------------------------------------------------------------
1 | [main]
2 | host = https://www.transifex.com
3 | lang_map = sr@latin: sr_Latn
4 |
5 | [o:django-debug-toolbar:p:django-debug-toolbar:r:main]
6 | file_filter = debug_toolbar/locale//LC_MESSAGES/django.po
7 | source_file = debug_toolbar/locale/en/LC_MESSAGES/django.po
8 | source_lang = en
9 | replace_edited_strings = false
10 | keep_translations = false
11 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Django Debug Toolbar Code of Conduct
2 |
3 | The django-debug-toolbar project utilizes the [Django Commons Code of Conduct](https://github.com/django-commons/membership/blob/main/CODE_OF_CONDUCT.md).
4 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Django Debug Toolbar
2 |
3 | This is a [Django Commons](https://github.com/django-commons/) project. By contributing you agree to abide by the [Contributor Code of Conduct](https://github.com/django-commons/membership/blob/main/CODE_OF_CONDUCT.md).
4 |
5 | ## Documentation
6 |
7 | For detailed contributing guidelines, please see our [Documentation](https://django-debug-toolbar.readthedocs.io/en/latest/contributing.html).
8 |
9 | ## Additional Resources
10 |
11 | Please see the [README](https://github.com/django-commons/membership/blob/main/README.md) for more help.
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) Rob Hudson and individual contributors.
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification,
5 | are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice,
8 | this list of conditions and the following disclaimer.
9 |
10 | 2. Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in the
12 | documentation and/or other materials provided with the distribution.
13 |
14 | 3. Neither the name of Django nor the names of its contributors may be used
15 | to endorse or promote products derived from this software without
16 | specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: example test coverage translatable_strings update_translations help
2 | .DEFAULT_GOAL := help
3 |
4 | example: ## Run the example application
5 | python example/manage.py migrate --noinput
6 | -DJANGO_SUPERUSER_PASSWORD=p python example/manage.py createsuperuser \
7 | --noinput --username="$(USER)" --email="$(USER)@mailinator.com"
8 | python example/manage.py runserver
9 |
10 | example_test: ## Run the test suite for the example application
11 | python example/manage.py test example
12 |
13 | test: ## Run the test suite
14 | DJANGO_SETTINGS_MODULE=tests.settings \
15 | python -m django test $${TEST_ARGS:-tests}
16 |
17 | test_selenium: ## Run frontend tests written with Selenium
18 | DJANGO_SELENIUM_TESTS=true DJANGO_SETTINGS_MODULE=tests.settings \
19 | python -m django test $${TEST_ARGS:-tests}
20 |
21 | coverage: ## Run the test suite with coverage enabled
22 | python --version
23 | DJANGO_SETTINGS_MODULE=tests.settings \
24 | python -b -W always -m coverage run -m django test -v2 $${TEST_ARGS:-tests}
25 | coverage report
26 | coverage html
27 | coverage xml
28 |
29 | translatable_strings: ## Update the English '.po' file
30 | cd debug_toolbar && python -m django makemessages -l en --no-obsolete
31 | @echo "Please commit changes and run 'tx push -s' (or wait for Transifex to pick them)"
32 |
33 | update_translations: ## Download updated '.po' files from Transifex
34 | tx pull -a --minimum-perc=10
35 | cd debug_toolbar && python -m django compilemessages
36 |
37 | .PHONY: example/django-debug-toolbar.png
38 | example/django-debug-toolbar.png: example/screenshot.py ## Update the screenshot in 'README.rst'
39 | python $< --browser firefox --headless -o $@
40 | optipng $@
41 |
42 | help: ## Help message for targets
43 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
44 | | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
45 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | =====================================
2 | Django Debug Toolbar |latest-version|
3 | =====================================
4 |
5 | |build-status| |coverage| |docs| |python-support| |django-support|
6 |
7 | .. |latest-version| image:: https://img.shields.io/pypi/v/django-debug-toolbar.svg
8 | :target: https://pypi.org/project/django-debug-toolbar/
9 | :alt: Latest version on PyPI
10 |
11 | .. |build-status| image:: https://github.com/django-commons/django-debug-toolbar/workflows/Test/badge.svg
12 | :target: https://github.com/django-commons/django-debug-toolbar/actions/workflows/test.yml
13 | :alt: Build Status
14 |
15 | .. |coverage| image:: https://img.shields.io/badge/Coverage-94%25-green
16 | :target: https://github.com/django-commons/django-debug-toolbar/actions/workflows/test.yml?query=branch%3Amain
17 | :alt: Test coverage status
18 |
19 | .. |docs| image:: https://img.shields.io/readthedocs/django-debug-toolbar/latest.svg
20 | :target: https://readthedocs.org/projects/django-debug-toolbar/
21 | :alt: Documentation status
22 |
23 | .. |python-support| image:: https://img.shields.io/pypi/pyversions/django-debug-toolbar
24 | :target: https://pypi.org/project/django-debug-toolbar/
25 | :alt: Supported Python versions
26 |
27 | .. |django-support| image:: https://img.shields.io/pypi/djversions/django-debug-toolbar
28 | :target: https://pypi.org/project/django-debug-toolbar/
29 | :alt: Supported Django versions
30 |
31 | The Django Debug Toolbar is a configurable set of panels that display various
32 | debug information about the current request/response and when clicked, display
33 | more details about the panel's content.
34 |
35 | Here's a screenshot of the toolbar in action:
36 |
37 | .. image:: https://raw.github.com/django-commons/django-debug-toolbar/main/example/django-debug-toolbar.png
38 | :alt: Django Debug Toolbar screenshot
39 |
40 | In addition to the built-in panels, a number of third-party panels are
41 | contributed by the community.
42 |
43 | The current stable version of the Debug Toolbar is 5.2.0. It works on
44 | Django ≥ 4.2.0.
45 |
46 | The Debug Toolbar has experimental support for `Django's asynchronous views
47 | `_. Please note that
48 | the Debug Toolbar still lacks the capability for handling concurrent requests.
49 | If you find any issues, please report them on the `issue tracker`_.
50 |
51 | Documentation, including installation and configuration instructions, is
52 | available at https://django-debug-toolbar.readthedocs.io/.
53 |
54 | The Django Debug Toolbar is released under the BSD license, like Django
55 | itself. If you like it, please consider contributing!
56 |
57 | The Django Debug Toolbar was originally created by Rob Hudson
58 | in August 2008 and was further developed by many contributors_.
59 |
60 | .. _contributors: https://github.com/django-commons/django-debug-toolbar/graphs/contributors
61 | .. _issue tracker: https://github.com/django-commons/django-debug-toolbar/issues
62 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | Only the latest version of django-debug-toolbar [](https://pypi.python.org/pypi/django-debug-toolbar) is supported.
6 |
7 | ## Reporting a Vulnerability
8 |
9 | If you think you have found a vulnerability, and even if you are not sure, please [report it to us in private](https://github.com/django-commons/django-debug-toolbar/security/advisories/new). We will review it and get back to you. Please refrain from public discussions of the issue.
10 |
--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://biomejs.dev/schemas/2.0.0-beta.5/schema.json",
3 | "formatter": {
4 | "enabled": true,
5 | "useEditorconfig": true
6 | },
7 | "assist": {
8 | "actions": {
9 | "source": {
10 | "organizeImports": "on"
11 | }
12 | }
13 | },
14 | "linter": {
15 | "enabled": true,
16 | "rules": {
17 | "recommended": true,
18 | "style": {
19 | "useLiteralEnumMembers": "error",
20 | "noCommaOperator": "error",
21 | "useNodejsImportProtocol": "error",
22 | "useAsConstAssertion": "error",
23 | "useEnumInitializers": "error",
24 | "useSelfClosingElements": "error",
25 | "useConst": "error",
26 | "useSingleVarDeclarator": "error",
27 | "noUnusedTemplateLiteral": "error",
28 | "useNumberNamespace": "error",
29 | "noInferrableTypes": "error",
30 | "useExponentiationOperator": "error",
31 | "useTemplate": "error",
32 | "noParameterAssign": "error",
33 | "noNonNullAssertion": "error",
34 | "useDefaultParameterLast": "error",
35 | "noArguments": "error",
36 | "useImportType": "error",
37 | "useExportType": "error",
38 | "noUselessElse": "error",
39 | "useShorthandFunctionType": "error"
40 | },
41 | "suspicious": {
42 | "noDocumentCookie": "off"
43 | },
44 | "complexity": {
45 | "useNumericLiterals": "error"
46 | }
47 | }
48 | },
49 | "javascript": {
50 | "formatter": {
51 | "trailingCommas": "es5",
52 | "quoteStyle": "double"
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/debug_toolbar/__init__.py:
--------------------------------------------------------------------------------
1 | __all__ = ["APP_NAME", "VERSION"]
2 |
3 | APP_NAME = "djdt"
4 |
5 | # Do not use pkg_resources to find the version but set it here directly!
6 | # see issue #1446
7 | VERSION = "5.2.0"
8 |
9 | # Code that discovers files or modules in INSTALLED_APPS imports this module.
10 | urls = "debug_toolbar.urls", APP_NAME
11 |
--------------------------------------------------------------------------------
/debug_toolbar/_compat.py:
--------------------------------------------------------------------------------
1 | try:
2 | from django.contrib.auth.decorators import login_not_required
3 | except ImportError:
4 | # For Django < 5.1, copy the current Django implementation
5 | def login_not_required(view_func):
6 | """
7 | Decorator for views that allows access to unauthenticated requests.
8 | """
9 | view_func.login_required = False
10 | return view_func
11 |
--------------------------------------------------------------------------------
/debug_toolbar/_stubs.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from typing import Any, NamedTuple, Optional
4 |
5 | from django import template as dj_template
6 |
7 |
8 | class InspectStack(NamedTuple):
9 | frame: Any
10 | filename: str
11 | lineno: int
12 | function: str
13 | code_context: str
14 | index: int
15 |
16 |
17 | TidyStackTrace = list[tuple[str, int, str, str, Optional[Any]]]
18 |
19 |
20 | class RenderContext(dj_template.context.RenderContext):
21 | template: dj_template.Template
22 |
23 |
24 | class RequestContext(dj_template.RequestContext):
25 | template: dj_template.Template
26 | render_context: RenderContext
27 |
--------------------------------------------------------------------------------
/debug_toolbar/decorators.py:
--------------------------------------------------------------------------------
1 | import functools
2 |
3 | from asgiref.sync import iscoroutinefunction
4 | from django.http import Http404
5 | from django.utils.translation import get_language, override as language_override
6 |
7 | from debug_toolbar import settings as dt_settings
8 |
9 |
10 | def require_show_toolbar(view):
11 | """
12 | Async compatible decorator to restrict access to a view
13 | based on the Debug Toolbar's visibility settings.
14 | """
15 | from debug_toolbar.middleware import get_show_toolbar
16 |
17 | if iscoroutinefunction(view):
18 |
19 | @functools.wraps(view)
20 | async def inner(request, *args, **kwargs):
21 | show_toolbar = get_show_toolbar(async_mode=True)
22 | if not await show_toolbar(request):
23 | raise Http404
24 |
25 | return await view(request, *args, **kwargs)
26 | else:
27 |
28 | @functools.wraps(view)
29 | def inner(request, *args, **kwargs):
30 | show_toolbar = get_show_toolbar(async_mode=False)
31 | if not show_toolbar(request):
32 | raise Http404
33 |
34 | return view(request, *args, **kwargs)
35 |
36 | return inner
37 |
38 |
39 | def render_with_toolbar_language(view):
40 | """Force any rendering within the view to use the toolbar's language."""
41 |
42 | @functools.wraps(view)
43 | def inner(request, *args, **kwargs):
44 | lang = dt_settings.get_config()["TOOLBAR_LANGUAGE"] or get_language()
45 | with language_override(lang):
46 | return view(request, *args, **kwargs)
47 |
48 | return inner
49 |
--------------------------------------------------------------------------------
/debug_toolbar/forms.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | from django import forms
4 | from django.core import signing
5 | from django.core.exceptions import ValidationError
6 | from django.utils.encoding import force_str
7 |
8 |
9 | class SignedDataForm(forms.Form):
10 | """Helper form that wraps a form to validate its contents on post.
11 |
12 | class PanelForm(forms.Form):
13 | # fields
14 |
15 | On render:
16 | form = SignedDataForm(initial=PanelForm(initial=data).initial)
17 |
18 | On POST:
19 | signed_form = SignedDataForm(request.POST)
20 | if signed_form.is_valid():
21 | panel_form = PanelForm(signed_form.verified_data)
22 | if panel_form.is_valid():
23 | # Success
24 | """
25 |
26 | salt = "django_debug_toolbar"
27 | signed = forms.CharField(required=True, widget=forms.HiddenInput)
28 |
29 | def __init__(self, *args, **kwargs):
30 | initial = kwargs.pop("initial", None)
31 | if initial:
32 | initial = {"signed": self.sign(initial)}
33 | super().__init__(*args, initial=initial, **kwargs)
34 |
35 | def clean_signed(self):
36 | try:
37 | verified = json.loads(
38 | signing.Signer(salt=self.salt).unsign(self.cleaned_data["signed"])
39 | )
40 | return verified
41 | except signing.BadSignature as exc:
42 | raise ValidationError("Bad signature") from exc
43 |
44 | def verified_data(self):
45 | return self.is_valid() and self.cleaned_data["signed"]
46 |
47 | @classmethod
48 | def sign(cls, data):
49 | return signing.Signer(salt=cls.salt).sign(
50 | json.dumps({key: force_str(value) for key, value in data.items()})
51 | )
52 |
--------------------------------------------------------------------------------
/debug_toolbar/locale/bg/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-commons/django-debug-toolbar/c217334010fa54a8726640519ca29cb77fffda58/debug_toolbar/locale/bg/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/debug_toolbar/locale/ca/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-commons/django-debug-toolbar/c217334010fa54a8726640519ca29cb77fffda58/debug_toolbar/locale/ca/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/debug_toolbar/locale/cs/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-commons/django-debug-toolbar/c217334010fa54a8726640519ca29cb77fffda58/debug_toolbar/locale/cs/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/debug_toolbar/locale/de/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-commons/django-debug-toolbar/c217334010fa54a8726640519ca29cb77fffda58/debug_toolbar/locale/de/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/debug_toolbar/locale/en/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-commons/django-debug-toolbar/c217334010fa54a8726640519ca29cb77fffda58/debug_toolbar/locale/en/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/debug_toolbar/locale/es/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-commons/django-debug-toolbar/c217334010fa54a8726640519ca29cb77fffda58/debug_toolbar/locale/es/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/debug_toolbar/locale/fa/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-commons/django-debug-toolbar/c217334010fa54a8726640519ca29cb77fffda58/debug_toolbar/locale/fa/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/debug_toolbar/locale/fi/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-commons/django-debug-toolbar/c217334010fa54a8726640519ca29cb77fffda58/debug_toolbar/locale/fi/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/debug_toolbar/locale/fr/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-commons/django-debug-toolbar/c217334010fa54a8726640519ca29cb77fffda58/debug_toolbar/locale/fr/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/debug_toolbar/locale/he/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-commons/django-debug-toolbar/c217334010fa54a8726640519ca29cb77fffda58/debug_toolbar/locale/he/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/debug_toolbar/locale/id/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-commons/django-debug-toolbar/c217334010fa54a8726640519ca29cb77fffda58/debug_toolbar/locale/id/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/debug_toolbar/locale/it/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-commons/django-debug-toolbar/c217334010fa54a8726640519ca29cb77fffda58/debug_toolbar/locale/it/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/debug_toolbar/locale/ja/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-commons/django-debug-toolbar/c217334010fa54a8726640519ca29cb77fffda58/debug_toolbar/locale/ja/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/debug_toolbar/locale/ko/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-commons/django-debug-toolbar/c217334010fa54a8726640519ca29cb77fffda58/debug_toolbar/locale/ko/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/debug_toolbar/locale/nl/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-commons/django-debug-toolbar/c217334010fa54a8726640519ca29cb77fffda58/debug_toolbar/locale/nl/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/debug_toolbar/locale/pl/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-commons/django-debug-toolbar/c217334010fa54a8726640519ca29cb77fffda58/debug_toolbar/locale/pl/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/debug_toolbar/locale/pt/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-commons/django-debug-toolbar/c217334010fa54a8726640519ca29cb77fffda58/debug_toolbar/locale/pt/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/debug_toolbar/locale/pt_BR/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-commons/django-debug-toolbar/c217334010fa54a8726640519ca29cb77fffda58/debug_toolbar/locale/pt_BR/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/debug_toolbar/locale/ru/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-commons/django-debug-toolbar/c217334010fa54a8726640519ca29cb77fffda58/debug_toolbar/locale/ru/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/debug_toolbar/locale/sk/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-commons/django-debug-toolbar/c217334010fa54a8726640519ca29cb77fffda58/debug_toolbar/locale/sk/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/debug_toolbar/locale/sv_SE/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-commons/django-debug-toolbar/c217334010fa54a8726640519ca29cb77fffda58/debug_toolbar/locale/sv_SE/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/debug_toolbar/locale/uk/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-commons/django-debug-toolbar/c217334010fa54a8726640519ca29cb77fffda58/debug_toolbar/locale/uk/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/debug_toolbar/locale/zh_CN/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-commons/django-debug-toolbar/c217334010fa54a8726640519ca29cb77fffda58/debug_toolbar/locale/zh_CN/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/debug_toolbar/management/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-commons/django-debug-toolbar/c217334010fa54a8726640519ca29cb77fffda58/debug_toolbar/management/__init__.py
--------------------------------------------------------------------------------
/debug_toolbar/management/commands/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/django-commons/django-debug-toolbar/c217334010fa54a8726640519ca29cb77fffda58/debug_toolbar/management/commands/__init__.py
--------------------------------------------------------------------------------
/debug_toolbar/management/commands/debugsqlshell.py:
--------------------------------------------------------------------------------
1 | from time import perf_counter
2 |
3 | import sqlparse
4 | from django.core.management.commands.shell import Command
5 | from django.db import connection
6 |
7 | if connection.vendor == "postgresql":
8 | from django.db.backends.postgresql import base as base_module
9 | else:
10 | from django.db.backends import utils as base_module
11 |
12 | # 'debugsqlshell' is the same as the 'shell'.
13 |
14 |
15 | # Command is required to exist to be loaded via
16 | # django.core.managementload_command_class
17 | __all__ = ["Command", "PrintQueryWrapper"]
18 |
19 |
20 | class PrintQueryWrapper(base_module.CursorDebugWrapper):
21 | def execute(self, sql, params=()):
22 | start_time = perf_counter()
23 | try:
24 | return self.cursor.execute(sql, params)
25 | finally:
26 | raw_sql = self.db.ops.last_executed_query(self.cursor, sql, params)
27 | end_time = perf_counter()
28 | duration = (end_time - start_time) * 1000
29 | formatted_sql = sqlparse.format(raw_sql, reindent=True)
30 | print(f"{formatted_sql} [{duration:.2f}ms]")
31 |
32 |
33 | base_module.CursorDebugWrapper = PrintQueryWrapper
34 |
--------------------------------------------------------------------------------
/debug_toolbar/panels/headers.py:
--------------------------------------------------------------------------------
1 | from django.utils.translation import gettext_lazy as _
2 |
3 | from debug_toolbar.panels import Panel
4 |
5 |
6 | class HeadersPanel(Panel):
7 | """
8 | A panel to display HTTP headers.
9 | """
10 |
11 | # List of environment variables we want to display
12 | ENVIRON_FILTER = {
13 | "CONTENT_LENGTH",
14 | "CONTENT_TYPE",
15 | "DJANGO_SETTINGS_MODULE",
16 | "GATEWAY_INTERFACE",
17 | "QUERY_STRING",
18 | "PATH_INFO",
19 | "PYTHONPATH",
20 | "REMOTE_ADDR",
21 | "REMOTE_HOST",
22 | "REQUEST_METHOD",
23 | "SCRIPT_NAME",
24 | "SERVER_NAME",
25 | "SERVER_PORT",
26 | "SERVER_PROTOCOL",
27 | "SERVER_SOFTWARE",
28 | "TZ",
29 | }
30 |
31 | title = _("Headers")
32 |
33 | is_async = True
34 |
35 | template = "debug_toolbar/panels/headers.html"
36 |
37 | def process_request(self, request):
38 | wsgi_env = sorted(request.META.items())
39 | self.request_headers = {
40 | unmangle(k): v for (k, v) in wsgi_env if is_http_header(k)
41 | }
42 | if "Cookie" in self.request_headers:
43 | self.request_headers["Cookie"] = "=> see Request panel"
44 | self.environ = {k: v for (k, v) in wsgi_env if k in self.ENVIRON_FILTER}
45 | self.record_stats(
46 | {"request_headers": self.request_headers, "environ": self.environ}
47 | )
48 | return super().process_request(request)
49 |
50 | def generate_stats(self, request, response):
51 | self.response_headers = dict(sorted(response.items()))
52 | self.record_stats({"response_headers": self.response_headers})
53 |
54 |
55 | def is_http_header(wsgi_key):
56 | # The WSGI spec says that keys should be str objects in the environ dict,
57 | # but this isn't true in practice. See issues #449 and #482.
58 | return isinstance(wsgi_key, str) and wsgi_key.startswith("HTTP_")
59 |
60 |
61 | def unmangle(wsgi_key):
62 | return wsgi_key[5:].replace("_", "-").title()
63 |
--------------------------------------------------------------------------------
/debug_toolbar/panels/history/__init__.py:
--------------------------------------------------------------------------------
1 | from debug_toolbar.panels.history.panel import HistoryPanel
2 |
3 | __all__ = ["HistoryPanel"]
4 |
--------------------------------------------------------------------------------
/debug_toolbar/panels/history/forms.py:
--------------------------------------------------------------------------------
1 | from django import forms
2 |
3 |
4 | class HistoryStoreForm(forms.Form):
5 | """
6 | Validate params
7 |
8 | store_id: The key for the store instance to be fetched.
9 | """
10 |
11 | store_id = forms.CharField(widget=forms.HiddenInput())
12 | exclude_history = forms.BooleanField(widget=forms.HiddenInput(), required=False)
13 |
--------------------------------------------------------------------------------
/debug_toolbar/panels/history/panel.py:
--------------------------------------------------------------------------------
1 | import contextlib
2 | import json
3 |
4 | from django.http.request import RawPostDataException
5 | from django.template.loader import render_to_string
6 | from django.templatetags.static import static
7 | from django.urls import path
8 | from django.utils import timezone
9 | from django.utils.translation import gettext_lazy as _
10 |
11 | from debug_toolbar.panels import Panel
12 | from debug_toolbar.panels.history import views
13 | from debug_toolbar.panels.history.forms import HistoryStoreForm
14 |
15 |
16 | class HistoryPanel(Panel):
17 | """A panel to display History"""
18 |
19 | is_async = True
20 | title = _("History")
21 | nav_title = _("History")
22 | template = "debug_toolbar/panels/history.html"
23 |
24 | def get_headers(self, request):
25 | headers = super().get_headers(request)
26 | observe_request = self.toolbar.get_observe_request()
27 | store_id = self.toolbar.store_id
28 | if store_id and observe_request(request):
29 | headers["djdt-store-id"] = store_id
30 | return headers
31 |
32 | @property
33 | def enabled(self):
34 | # Do not show the history panel if the panels are rendered on request
35 | # rather than loaded via ajax.
36 | return super().enabled and not self.toolbar.should_render_panels()
37 |
38 | @property
39 | def is_historical(self):
40 | """The HistoryPanel should not be included in the historical panels."""
41 | return False
42 |
43 | @classmethod
44 | def get_urls(cls):
45 | return [
46 | path("history_sidebar/", views.history_sidebar, name="history_sidebar"),
47 | path("history_refresh/", views.history_refresh, name="history_refresh"),
48 | ]
49 |
50 | @property
51 | def nav_subtitle(self):
52 | return self.get_stats().get("request_url", "")
53 |
54 | def generate_stats(self, request, response):
55 | try:
56 | if request.method == "GET":
57 | data = request.GET.copy()
58 | else:
59 | data = request.POST.copy()
60 | # GraphQL tends to not be populated in POST. If the request seems
61 | # empty, check if it's a JSON request.
62 | if (
63 | not data
64 | and request.body
65 | and request.headers.get("content-type") == "application/json"
66 | ):
67 | with contextlib.suppress(ValueError):
68 | data = json.loads(request.body)
69 |
70 | except RawPostDataException:
71 | # It is not guaranteed that we may read the request data (again).
72 | data = None
73 |
74 | self.record_stats(
75 | {
76 | "request_url": request.get_full_path(),
77 | "request_method": request.method,
78 | "status_code": response.status_code,
79 | "data": data,
80 | "time": timezone.now(),
81 | }
82 | )
83 |
84 | @property
85 | def content(self):
86 | """Content of the panel when it's displayed in full screen.
87 |
88 | Fetch every store for the toolbar and include it in the template.
89 | """
90 | stores = {}
91 | for id, toolbar in reversed(self.toolbar._store.items()):
92 | stores[id] = {
93 | "toolbar": toolbar,
94 | "form": HistoryStoreForm(
95 | initial={"store_id": id, "exclude_history": True}
96 | ),
97 | }
98 |
99 | return render_to_string(
100 | self.template,
101 | {
102 | "current_store_id": self.toolbar.store_id,
103 | "stores": stores,
104 | "refresh_form": HistoryStoreForm(
105 | initial={
106 | "store_id": self.toolbar.store_id,
107 | "exclude_history": True,
108 | }
109 | ),
110 | },
111 | )
112 |
113 | @property
114 | def scripts(self):
115 | scripts = super().scripts
116 | scripts.append(static("debug_toolbar/js/history.js"))
117 | return scripts
118 |
--------------------------------------------------------------------------------
/debug_toolbar/panels/history/views.py:
--------------------------------------------------------------------------------
1 | from django.http import HttpResponseBadRequest, JsonResponse
2 | from django.template.loader import render_to_string
3 |
4 | from debug_toolbar._compat import login_not_required
5 | from debug_toolbar.decorators import render_with_toolbar_language, require_show_toolbar
6 | from debug_toolbar.panels.history.forms import HistoryStoreForm
7 | from debug_toolbar.toolbar import DebugToolbar
8 |
9 |
10 | @login_not_required
11 | @require_show_toolbar
12 | @render_with_toolbar_language
13 | def history_sidebar(request):
14 | """Returns the selected debug toolbar history snapshot."""
15 | form = HistoryStoreForm(request.GET)
16 |
17 | if form.is_valid():
18 | store_id = form.cleaned_data["store_id"]
19 | toolbar = DebugToolbar.fetch(store_id)
20 | exclude_history = form.cleaned_data["exclude_history"]
21 | context = {}
22 | if toolbar is None:
23 | # When the store_id has been popped already due to
24 | # RESULTS_CACHE_SIZE
25 | return JsonResponse(context)
26 | for panel in toolbar.panels:
27 | if exclude_history and not panel.is_historical:
28 | continue
29 | panel_context = {"panel": panel}
30 | context[panel.panel_id] = {
31 | "button": render_to_string(
32 | "debug_toolbar/includes/panel_button.html", panel_context
33 | ),
34 | "content": render_to_string(
35 | "debug_toolbar/includes/panel_content.html", panel_context
36 | ),
37 | }
38 | return JsonResponse(context)
39 | return HttpResponseBadRequest("Form errors")
40 |
41 |
42 | @login_not_required
43 | @require_show_toolbar
44 | @render_with_toolbar_language
45 | def history_refresh(request):
46 | """Returns the refreshed list of table rows for the History Panel."""
47 | form = HistoryStoreForm(request.GET)
48 |
49 | if form.is_valid():
50 | requests = []
51 | # Convert to list to handle mutations happening in parallel
52 | for id, toolbar in list(DebugToolbar._store.items()):
53 | requests.append(
54 | {
55 | "id": id,
56 | "content": render_to_string(
57 | "debug_toolbar/panels/history_tr.html",
58 | {
59 | "id": id,
60 | "store_context": {
61 | "toolbar": toolbar,
62 | "form": HistoryStoreForm(
63 | initial={
64 | "store_id": id,
65 | "exclude_history": True,
66 | }
67 | ),
68 | },
69 | },
70 | ),
71 | }
72 | )
73 |
74 | return JsonResponse({"requests": requests})
75 | return HttpResponseBadRequest("Form errors")
76 |
--------------------------------------------------------------------------------
/debug_toolbar/panels/redirects.py:
--------------------------------------------------------------------------------
1 | from inspect import iscoroutine
2 |
3 | from django.template.response import SimpleTemplateResponse
4 | from django.utils.translation import gettext_lazy as _
5 |
6 | from debug_toolbar.panels import Panel
7 |
8 |
9 | class RedirectsPanel(Panel):
10 | """
11 | Panel that intercepts redirects and displays a page with debug info.
12 | """
13 |
14 | has_content = False
15 |
16 | is_async = True
17 |
18 | nav_title = _("Intercept redirects")
19 |
20 | def _process_response(self, response):
21 | """
22 | Common response processing logic.
23 | """
24 | if 300 <= response.status_code < 400:
25 | if redirect_to := response.get("Location"):
26 | response = self.get_interception_response(response, redirect_to)
27 | response.render()
28 | return response
29 |
30 | async def aprocess_request(self, request, response_coroutine):
31 | """
32 | Async version of process_request. used for accessing the response
33 | by awaiting it when running in ASGI.
34 | """
35 |
36 | response = await response_coroutine
37 | return self._process_response(response)
38 |
39 | def process_request(self, request):
40 | response = super().process_request(request)
41 | if iscoroutine(response):
42 | return self.aprocess_request(request, response)
43 | return self._process_response(response)
44 |
45 | def get_interception_response(self, response, redirect_to):
46 | """
47 | Hook method to allow subclasses to customize the interception response.
48 | """
49 | status_line = f"{response.status_code} {response.reason_phrase}"
50 | cookies = response.cookies
51 | original_response = response
52 | context = {
53 | "redirect_to": redirect_to,
54 | "status_line": status_line,
55 | "toolbar": self.toolbar,
56 | "original_response": original_response,
57 | }
58 | # Using SimpleTemplateResponse avoids running global context processors.
59 | response = SimpleTemplateResponse("debug_toolbar/redirect.html", context)
60 | response.cookies = cookies
61 | response.original_response = original_response
62 | return response
63 |
--------------------------------------------------------------------------------
/debug_toolbar/panels/request.py:
--------------------------------------------------------------------------------
1 | from django.http import Http404
2 | from django.urls import resolve
3 | from django.utils.translation import gettext_lazy as _
4 |
5 | from debug_toolbar.panels import Panel
6 | from debug_toolbar.utils import get_name_from_obj, sanitize_and_sort_request_vars
7 |
8 |
9 | class RequestPanel(Panel):
10 | """
11 | A panel to display request variables (POST/GET, session, cookies).
12 | """
13 |
14 | template = "debug_toolbar/panels/request.html"
15 |
16 | title = _("Request")
17 |
18 | @property
19 | def nav_subtitle(self):
20 | """
21 | Show abbreviated name of view function as subtitle
22 | """
23 | view_func = self.get_stats().get("view_func", "")
24 | return view_func.rsplit(".", 1)[-1]
25 |
26 | def generate_stats(self, request, response):
27 | self.record_stats(
28 | {
29 | "get": sanitize_and_sort_request_vars(request.GET),
30 | "post": sanitize_and_sort_request_vars(request.POST),
31 | "cookies": sanitize_and_sort_request_vars(request.COOKIES),
32 | }
33 | )
34 |
35 | view_info = {
36 | "view_func": _(""),
37 | "view_args": "None",
38 | "view_kwargs": "None",
39 | "view_urlname": "None",
40 | }
41 | try:
42 | match = resolve(request.path_info)
43 | func, args, kwargs = match
44 | view_info["view_func"] = get_name_from_obj(func)
45 | view_info["view_args"] = args
46 | view_info["view_kwargs"] = kwargs
47 |
48 | if getattr(match, "url_name", False):
49 | url_name = match.url_name
50 | if match.namespaces:
51 | url_name = ":".join([*match.namespaces, url_name])
52 | else:
53 | url_name = _("")
54 |
55 | view_info["view_urlname"] = url_name
56 |
57 | except Http404:
58 | pass
59 | self.record_stats(view_info)
60 |
61 | if hasattr(request, "session"):
62 | session_data = dict(request.session)
63 | self.record_stats({"session": sanitize_and_sort_request_vars(session_data)})
64 |
--------------------------------------------------------------------------------
/debug_toolbar/panels/settings.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.utils.translation import gettext_lazy as _
3 | from django.views.debug import get_default_exception_reporter_filter
4 |
5 | from debug_toolbar.panels import Panel
6 |
7 | get_safe_settings = get_default_exception_reporter_filter().get_safe_settings
8 |
9 |
10 | class SettingsPanel(Panel):
11 | """
12 | A panel to display all variables in django.conf.settings
13 | """
14 |
15 | template = "debug_toolbar/panels/settings.html"
16 |
17 | is_async = True
18 |
19 | nav_title = _("Settings")
20 |
21 | def title(self):
22 | return _("Settings from %s") % settings.SETTINGS_MODULE
23 |
24 | def generate_stats(self, request, response):
25 | self.record_stats({"settings": dict(sorted(get_safe_settings().items()))})
26 |
--------------------------------------------------------------------------------
/debug_toolbar/panels/signals.py:
--------------------------------------------------------------------------------
1 | import weakref
2 |
3 | from django.core.signals import (
4 | got_request_exception,
5 | request_finished,
6 | request_started,
7 | setting_changed,
8 | )
9 | from django.db.backends.signals import connection_created
10 | from django.db.models.signals import (
11 | class_prepared,
12 | m2m_changed,
13 | post_delete,
14 | post_init,
15 | post_migrate,
16 | post_save,
17 | pre_delete,
18 | pre_init,
19 | pre_migrate,
20 | pre_save,
21 | )
22 | from django.utils.module_loading import import_string
23 | from django.utils.translation import gettext_lazy as _, ngettext
24 |
25 | from debug_toolbar.panels import Panel
26 |
27 |
28 | class SignalsPanel(Panel):
29 | template = "debug_toolbar/panels/signals.html"
30 |
31 | is_async = True
32 |
33 | SIGNALS = {
34 | "request_started": request_started,
35 | "request_finished": request_finished,
36 | "got_request_exception": got_request_exception,
37 | "connection_created": connection_created,
38 | "class_prepared": class_prepared,
39 | "pre_init": pre_init,
40 | "post_init": post_init,
41 | "pre_save": pre_save,
42 | "post_save": post_save,
43 | "pre_delete": pre_delete,
44 | "post_delete": post_delete,
45 | "m2m_changed": m2m_changed,
46 | "pre_migrate": pre_migrate,
47 | "post_migrate": post_migrate,
48 | "setting_changed": setting_changed,
49 | }
50 |
51 | def nav_subtitle(self):
52 | signals = self.get_stats()["signals"]
53 | num_receivers = sum(len(receivers) for name, receivers in signals)
54 | num_signals = len(signals)
55 | # here we have to handle a double count translation, hence the
56 | # hard coding of one signal
57 | if num_signals == 1:
58 | return ngettext(
59 | "%(num_receivers)d receiver of 1 signal",
60 | "%(num_receivers)d receivers of 1 signal",
61 | num_receivers,
62 | ) % {"num_receivers": num_receivers}
63 | return ngettext(
64 | "%(num_receivers)d receiver of %(num_signals)d signals",
65 | "%(num_receivers)d receivers of %(num_signals)d signals",
66 | num_receivers,
67 | ) % {"num_receivers": num_receivers, "num_signals": num_signals}
68 |
69 | title = _("Signals")
70 |
71 | @property
72 | def signals(self):
73 | signals = self.SIGNALS.copy()
74 | for signal in self.toolbar.config["EXTRA_SIGNALS"]:
75 | signal_name = signal.rsplit(".", 1)[-1]
76 | signals[signal_name] = import_string(signal)
77 | return signals
78 |
79 | def generate_stats(self, request, response):
80 | signals = []
81 | for name, signal in sorted(self.signals.items()):
82 | receivers = []
83 | for receiver in signal.receivers:
84 | receiver = receiver[1]
85 | if isinstance(receiver, weakref.ReferenceType):
86 | receiver = receiver()
87 | if receiver is None:
88 | continue
89 |
90 | receiver = getattr(receiver, "__wraps__", receiver)
91 | receiver_name = getattr(receiver, "__name__", str(receiver))
92 | if getattr(receiver, "__self__", None) is not None:
93 | receiver_class_name = getattr(
94 | receiver.__self__, "__class__", type
95 | ).__name__
96 | text = f"{receiver_class_name}.{receiver_name}"
97 | else:
98 | text = receiver_name
99 | receivers.append(text)
100 | signals.append((name, receivers))
101 |
102 | self.record_stats({"signals": signals})
103 |
--------------------------------------------------------------------------------
/debug_toolbar/panels/sql/__init__.py:
--------------------------------------------------------------------------------
1 | from debug_toolbar.panels.sql.panel import SQLPanel
2 |
3 | __all__ = ["SQLPanel"]
4 |
--------------------------------------------------------------------------------
/debug_toolbar/panels/sql/forms.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | from django import forms
4 | from django.core.exceptions import ValidationError
5 | from django.db import connections
6 | from django.utils.functional import cached_property
7 |
8 | from debug_toolbar.panels.sql.utils import is_select_query, reformat_sql
9 |
10 |
11 | class SQLSelectForm(forms.Form):
12 | """
13 | Validate params
14 |
15 | sql: The sql statement with interpolated params
16 | raw_sql: The sql statement with placeholders
17 | params: JSON encoded parameter values
18 | duration: time for SQL to execute passed in from toolbar just for redisplay
19 | """
20 |
21 | sql = forms.CharField()
22 | raw_sql = forms.CharField()
23 | params = forms.CharField()
24 | alias = forms.CharField(required=False, initial="default")
25 | duration = forms.FloatField()
26 |
27 | def clean_raw_sql(self):
28 | value = self.cleaned_data["raw_sql"]
29 |
30 | if not is_select_query(value):
31 | raise ValidationError("Only 'select' queries are allowed.")
32 |
33 | return value
34 |
35 | def clean_params(self):
36 | value = self.cleaned_data["params"]
37 |
38 | try:
39 | return json.loads(value)
40 | except ValueError as exc:
41 | raise ValidationError("Is not valid JSON") from exc
42 |
43 | def clean_alias(self):
44 | value = self.cleaned_data["alias"]
45 |
46 | if value not in connections:
47 | raise ValidationError(f"Database alias '{value}' not found")
48 |
49 | return value
50 |
51 | def reformat_sql(self):
52 | return reformat_sql(self.cleaned_data["sql"], with_toggle=False)
53 |
54 | @property
55 | def connection(self):
56 | return connections[self.cleaned_data["alias"]]
57 |
58 | @cached_property
59 | def cursor(self):
60 | return self.connection.cursor()
61 |
--------------------------------------------------------------------------------
/debug_toolbar/panels/templates/__init__.py:
--------------------------------------------------------------------------------
1 | from debug_toolbar.panels.templates.panel import TemplatesPanel
2 |
3 | __all__ = ["TemplatesPanel"]
4 |
--------------------------------------------------------------------------------
/debug_toolbar/panels/templates/jinja2.py:
--------------------------------------------------------------------------------
1 | import functools
2 |
3 | from django.template.backends.jinja2 import Template as JinjaTemplate
4 | from django.template.context import make_context
5 | from django.test.signals import template_rendered
6 |
7 |
8 | def patch_jinja_render():
9 | orig_render = JinjaTemplate.render
10 |
11 | @functools.wraps(orig_render)
12 | def wrapped_render(self, context=None, request=None):
13 | # This patching of render only instruments the rendering
14 | # of the immediate template. It won't include the parent template(s).
15 | self.name = self.template.name
16 | template_rendered.send(
17 | sender=self, template=self, context=make_context(context, request)
18 | )
19 | return orig_render(self, context, request)
20 |
21 | if JinjaTemplate.render != wrapped_render:
22 | JinjaTemplate.original_render = JinjaTemplate.render
23 | JinjaTemplate.render = wrapped_render
24 |
--------------------------------------------------------------------------------
/debug_toolbar/panels/templates/views.py:
--------------------------------------------------------------------------------
1 | from django.core import signing
2 | from django.http import HttpResponseBadRequest, JsonResponse
3 | from django.template import Origin, TemplateDoesNotExist
4 | from django.template.engine import Engine
5 | from django.template.loader import render_to_string
6 | from django.utils.html import format_html, mark_safe
7 |
8 | from debug_toolbar._compat import login_not_required
9 | from debug_toolbar.decorators import render_with_toolbar_language, require_show_toolbar
10 |
11 |
12 | @login_not_required
13 | @require_show_toolbar
14 | @render_with_toolbar_language
15 | def template_source(request):
16 | """
17 | Return the source of a template, syntax-highlighted by Pygments if
18 | it's available.
19 | """
20 | template_origin_name = request.GET.get("template_origin")
21 | if template_origin_name is None:
22 | return HttpResponseBadRequest('"template_origin" key is required')
23 | try:
24 | template_origin_name = signing.loads(template_origin_name)
25 | except Exception:
26 | return HttpResponseBadRequest('"template_origin" is invalid')
27 | template_name = request.GET.get("template", template_origin_name)
28 |
29 | final_loaders = []
30 | loaders = list(Engine.get_default().template_loaders)
31 |
32 | while loaders:
33 | loader = loaders.pop(0)
34 |
35 | if loader is not None:
36 | # Recursively unwrap loaders until we get to loaders which do not
37 | # themselves wrap other loaders. This adds support for
38 | # django.template.loaders.cached.Loader and the
39 | # django-template-partials loader (possibly among others)
40 | if hasattr(loader, "loaders"):
41 | loaders.extend(loader.loaders)
42 | else:
43 | final_loaders.append(loader)
44 |
45 | for loader in final_loaders:
46 | origin = Origin(template_origin_name)
47 | try:
48 | source = loader.get_contents(origin)
49 | break
50 | except TemplateDoesNotExist:
51 | pass
52 | else:
53 | source = f"Template Does Not Exist: {template_origin_name}"
54 |
55 | try:
56 | from pygments import highlight
57 | from pygments.formatters import HtmlFormatter
58 | from pygments.lexers import HtmlDjangoLexer
59 | except ModuleNotFoundError:
60 | source = format_html("{}
", source)
61 | else:
62 | source = highlight(source, HtmlDjangoLexer(), HtmlFormatter(wrapcode=True))
63 | source = mark_safe(source)
64 |
65 | content = render_to_string(
66 | "debug_toolbar/panels/template_source.html",
67 | {"source": source, "template_name": template_name},
68 | )
69 | return JsonResponse({"content": content})
70 |
--------------------------------------------------------------------------------
/debug_toolbar/panels/timer.py:
--------------------------------------------------------------------------------
1 | from time import perf_counter
2 |
3 | from django.template.loader import render_to_string
4 | from django.templatetags.static import static
5 | from django.utils.translation import gettext_lazy as _
6 |
7 | from debug_toolbar.panels import Panel
8 |
9 | try:
10 | import resource # Not available on Win32 systems
11 | except ImportError:
12 | resource = None
13 |
14 |
15 | class TimerPanel(Panel):
16 | """
17 | Panel that displays the time a response took in milliseconds.
18 | """
19 |
20 | def nav_subtitle(self):
21 | stats = self.get_stats()
22 | if hasattr(self, "_start_rusage"):
23 | utime = self._end_rusage.ru_utime - self._start_rusage.ru_utime
24 | stime = self._end_rusage.ru_stime - self._start_rusage.ru_stime
25 | return _("CPU: %(cum)0.2fms (%(total)0.2fms)") % {
26 | "cum": (utime + stime) * 1000.0,
27 | "total": stats["total_time"],
28 | }
29 | elif "total_time" in stats:
30 | return _("Total: %0.2fms") % stats["total_time"]
31 | else:
32 | return ""
33 |
34 | has_content = resource is not None
35 |
36 | title = _("Time")
37 |
38 | template = "debug_toolbar/panels/timer.html"
39 |
40 | @property
41 | def content(self):
42 | stats = self.get_stats()
43 | rows = (
44 | (_("User CPU time"), _("%(utime)0.3f msec") % stats),
45 | (_("System CPU time"), _("%(stime)0.3f msec") % stats),
46 | (_("Total CPU time"), _("%(total)0.3f msec") % stats),
47 | (_("Elapsed time"), _("%(total_time)0.3f msec") % stats),
48 | (
49 | _("Context switches"),
50 | _("%(vcsw)d voluntary, %(ivcsw)d involuntary") % stats,
51 | ),
52 | )
53 | return render_to_string(self.template, {"rows": rows})
54 |
55 | @property
56 | def scripts(self):
57 | scripts = super().scripts
58 | scripts.append(static("debug_toolbar/js/timer.js"))
59 | return scripts
60 |
61 | def process_request(self, request):
62 | self._start_time = perf_counter()
63 | if self.has_content:
64 | self._start_rusage = resource.getrusage(resource.RUSAGE_SELF)
65 | return super().process_request(request)
66 |
67 | def generate_stats(self, request, response):
68 | stats = {}
69 | if hasattr(self, "_start_time"):
70 | stats["total_time"] = (perf_counter() - self._start_time) * 1000
71 | if hasattr(self, "_start_rusage"):
72 | self._end_rusage = resource.getrusage(resource.RUSAGE_SELF)
73 | stats["utime"] = 1000 * self._elapsed_ru("ru_utime")
74 | stats["stime"] = 1000 * self._elapsed_ru("ru_stime")
75 | stats["total"] = stats["utime"] + stats["stime"]
76 | stats["vcsw"] = self._elapsed_ru("ru_nvcsw")
77 | stats["ivcsw"] = self._elapsed_ru("ru_nivcsw")
78 | stats["minflt"] = self._elapsed_ru("ru_minflt")
79 | stats["majflt"] = self._elapsed_ru("ru_majflt")
80 | # these are documented as not meaningful under Linux. If you're
81 | # running BSD feel free to enable them, and add any others that I
82 | # hadn't gotten to before I noticed that I was getting nothing but
83 | # zeroes and that the docs agreed. :-(
84 | #
85 | # stats['blkin'] = self._elapsed_ru('ru_inblock')
86 | # stats['blkout'] = self._elapsed_ru('ru_oublock')
87 | # stats['swap'] = self._elapsed_ru('ru_nswap')
88 | # stats['rss'] = self._end_rusage.ru_maxrss
89 | # stats['srss'] = self._end_rusage.ru_ixrss
90 | # stats['urss'] = self._end_rusage.ru_idrss
91 | # stats['usrss'] = self._end_rusage.ru_isrss
92 |
93 | self.record_stats(stats)
94 |
95 | def generate_server_timing(self, request, response):
96 | stats = self.get_stats()
97 |
98 | self.record_server_timing("utime", "User CPU time", stats.get("utime", 0))
99 | self.record_server_timing("stime", "System CPU time", stats.get("stime", 0))
100 | self.record_server_timing("total", "Total CPU time", stats.get("total", 0))
101 | self.record_server_timing(
102 | "total_time", "Elapsed time", stats.get("total_time", 0)
103 | )
104 |
105 | def _elapsed_ru(self, name):
106 | return getattr(self._end_rusage, name) - getattr(self._start_rusage, name)
107 |
--------------------------------------------------------------------------------
/debug_toolbar/panels/versions.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | import django
4 | from django.apps import apps
5 | from django.utils.translation import gettext_lazy as _
6 |
7 | from debug_toolbar.panels import Panel
8 |
9 |
10 | class VersionsPanel(Panel):
11 | """
12 | Shows versions of Python, Django, and installed apps if possible.
13 | """
14 |
15 | is_async = True
16 |
17 | @property
18 | def nav_subtitle(self):
19 | return f"Django {django.get_version()}"
20 |
21 | title = _("Versions")
22 |
23 | template = "debug_toolbar/panels/versions.html"
24 |
25 | def generate_stats(self, request, response):
26 | versions = [
27 | ("Python", "", "%d.%d.%d" % sys.version_info[:3]),
28 | ("Django", "", self.get_app_version(django)),
29 | ]
30 | versions += list(self.gen_app_versions())
31 | self.record_stats(
32 | {"versions": sorted(versions, key=lambda v: v[0]), "paths": sys.path}
33 | )
34 |
35 | def gen_app_versions(self):
36 | for app_config in apps.get_app_configs():
37 | name = app_config.verbose_name
38 | app = app_config.module
39 | version = self.get_app_version(app)
40 | if version:
41 | yield app.__name__, name, version
42 |
43 | def get_app_version(self, app):
44 | version = self.get_version_from_app(app)
45 | if isinstance(version, (list, tuple)):
46 | # We strip dots from the right because we do not want to show
47 | # trailing dots if there are empty elements in the list/tuple
48 | version = ".".join(str(o) for o in version).rstrip(".")
49 | return version
50 |
51 | def get_version_from_app(self, app):
52 | if hasattr(app, "get_version"):
53 | get_version = app.get_version
54 | if callable(get_version):
55 | try:
56 | return get_version()
57 | except TypeError:
58 | pass
59 | else:
60 | return get_version
61 | if hasattr(app, "VERSION"):
62 | return app.VERSION
63 | if hasattr(app, "__version__"):
64 | return app.__version__
65 | return
66 |
--------------------------------------------------------------------------------
/debug_toolbar/settings.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import warnings
4 | from functools import cache
5 |
6 | from django.conf import settings
7 | from django.dispatch import receiver
8 | from django.test.signals import setting_changed
9 |
10 |
11 | def _is_running_tests():
12 | """
13 | Helper function to support testing default value for
14 | IS_RUNNING_TESTS
15 | """
16 | return "test" in sys.argv or "PYTEST_VERSION" in os.environ
17 |
18 |
19 | CONFIG_DEFAULTS = {
20 | # Toolbar options
21 | "DISABLE_PANELS": {
22 | "debug_toolbar.panels.profiling.ProfilingPanel",
23 | "debug_toolbar.panels.redirects.RedirectsPanel",
24 | },
25 | "INSERT_BEFORE": "
9 | {{ status_line }}
10 |
11 |
12 | {% 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." %}
13 |
14 | ",
26 | "RENDER_PANELS": None,
27 | "RESULTS_CACHE_SIZE": 25,
28 | "ROOT_TAG_EXTRA_ATTRS": "",
29 | "SHOW_COLLAPSED": False,
30 | "SHOW_TOOLBAR_CALLBACK": "debug_toolbar.middleware.show_toolbar",
31 | # Panel options
32 | "EXTRA_SIGNALS": [],
33 | "ENABLE_STACKTRACES": True,
34 | "ENABLE_STACKTRACES_LOCALS": False,
35 | "HIDE_IN_STACKTRACES": (
36 | "socketserver",
37 | "threading",
38 | "wsgiref",
39 | "debug_toolbar",
40 | "django.db",
41 | "django.core.handlers",
42 | "django.core.servers",
43 | "django.utils.decorators",
44 | "django.utils.deprecation",
45 | "django.utils.functional",
46 | ),
47 | "PRETTIFY_SQL": True,
48 | "PROFILER_CAPTURE_PROJECT_CODE": True,
49 | "PROFILER_MAX_DEPTH": 10,
50 | "PROFILER_THRESHOLD_RATIO": 8,
51 | "SHOW_TEMPLATE_CONTEXT": True,
52 | "SKIP_TEMPLATE_PREFIXES": ("django/forms/widgets/", "admin/widgets/"),
53 | "SQL_WARNING_THRESHOLD": 500, # milliseconds
54 | "OBSERVE_REQUEST_CALLBACK": "debug_toolbar.toolbar.observe_request",
55 | "TOOLBAR_LANGUAGE": None,
56 | "IS_RUNNING_TESTS": _is_running_tests(),
57 | "UPDATE_ON_FETCH": False,
58 | }
59 |
60 |
61 | @cache
62 | def get_config():
63 | USER_CONFIG = getattr(settings, "DEBUG_TOOLBAR_CONFIG", {})
64 | CONFIG = CONFIG_DEFAULTS.copy()
65 | CONFIG.update(USER_CONFIG)
66 | return CONFIG
67 |
68 |
69 | PANELS_DEFAULTS = [
70 | "debug_toolbar.panels.history.HistoryPanel",
71 | "debug_toolbar.panels.versions.VersionsPanel",
72 | "debug_toolbar.panels.timer.TimerPanel",
73 | "debug_toolbar.panels.settings.SettingsPanel",
74 | "debug_toolbar.panels.headers.HeadersPanel",
75 | "debug_toolbar.panels.request.RequestPanel",
76 | "debug_toolbar.panels.sql.SQLPanel",
77 | "debug_toolbar.panels.staticfiles.StaticFilesPanel",
78 | "debug_toolbar.panels.templates.TemplatesPanel",
79 | "debug_toolbar.panels.alerts.AlertsPanel",
80 | "debug_toolbar.panels.cache.CachePanel",
81 | "debug_toolbar.panels.signals.SignalsPanel",
82 | "debug_toolbar.panels.redirects.RedirectsPanel",
83 | "debug_toolbar.panels.profiling.ProfilingPanel",
84 | ]
85 |
86 |
87 | @cache
88 | def get_panels():
89 | try:
90 | PANELS = list(settings.DEBUG_TOOLBAR_PANELS)
91 | except AttributeError:
92 | PANELS = PANELS_DEFAULTS
93 |
94 | logging_panel = "debug_toolbar.panels.logging.LoggingPanel"
95 | if logging_panel in PANELS:
96 | PANELS = [panel for panel in PANELS if panel != logging_panel]
97 | warnings.warn(
98 | f"Please remove {logging_panel} from your DEBUG_TOOLBAR_PANELS setting.",
99 | DeprecationWarning,
100 | stacklevel=1,
101 | )
102 | return PANELS
103 |
104 |
105 | @receiver(setting_changed)
106 | def update_toolbar_config(*, setting, **kwargs):
107 | """
108 | Refresh configuration when overriding settings.
109 | """
110 | if setting == "DEBUG_TOOLBAR_CONFIG":
111 | get_config.cache_clear()
112 | elif setting == "DEBUG_TOOLBAR_PANELS":
113 | from debug_toolbar.toolbar import DebugToolbar
114 |
115 | get_panels.cache_clear()
116 | DebugToolbar._panel_classes = None
117 | # Not implemented: invalidate debug_toolbar.urls.
118 |
--------------------------------------------------------------------------------
/debug_toolbar/static/debug_toolbar/css/print.css:
--------------------------------------------------------------------------------
1 | #djDebug {
2 | display: none !important;
3 | }
4 |
--------------------------------------------------------------------------------
/debug_toolbar/static/debug_toolbar/js/history.js:
--------------------------------------------------------------------------------
1 | import { $$, ajaxForm, replaceToolbarState } from "./utils.js";
2 |
3 | const djDebug = document.getElementById("djDebug");
4 |
5 | function difference(setA, setB) {
6 | const _difference = new Set(setA);
7 | for (const elem of setB) {
8 | _difference.delete(elem);
9 | }
10 | return _difference;
11 | }
12 |
13 | /**
14 | * Create an array of dataset properties from a NodeList.
15 | */
16 | function pluckData(nodes, key) {
17 | return [...nodes].map((obj) => obj.dataset[key]);
18 | }
19 |
20 | function refreshHistory() {
21 | const formTarget = djDebug.querySelector(".refreshHistory");
22 | const container = document.getElementById("djdtHistoryRequests");
23 | const oldIds = new Set(
24 | pluckData(container.querySelectorAll("tr[data-store-id]"), "storeId")
25 | );
26 |
27 | ajaxForm(formTarget)
28 | .then((data) => {
29 | // Remove existing rows first then re-populate with new data
30 | for (const node of container.querySelectorAll(
31 | "tr[data-store-id]"
32 | )) {
33 | node.remove();
34 | }
35 | for (const request of data.requests) {
36 | container.innerHTML = request.content + container.innerHTML;
37 | }
38 | })
39 | .then(() => {
40 | const allIds = new Set(
41 | pluckData(
42 | container.querySelectorAll("tr[data-store-id]"),
43 | "storeId"
44 | )
45 | );
46 | const newIds = difference(allIds, oldIds);
47 | const lastRequestId = newIds.values().next().value;
48 | return {
49 | allIds,
50 | newIds,
51 | lastRequestId,
52 | };
53 | })
54 | .then((refreshInfo) => {
55 | for (const newId of refreshInfo.newIds) {
56 | const row = container.querySelector(
57 | `tr[data-store-id="${newId}"]`
58 | );
59 | row.classList.add("flash-new");
60 | }
61 | setTimeout(() => {
62 | for (const row of container.querySelectorAll(
63 | "tr[data-store-id]"
64 | )) {
65 | row.classList.remove("flash-new");
66 | }
67 | }, 2000);
68 | });
69 | }
70 |
71 | function switchHistory(newStoreId) {
72 | const formTarget = djDebug.querySelector(
73 | `.switchHistory[data-store-id='${newStoreId}']`
74 | );
75 | const tbody = formTarget.closest("tbody");
76 |
77 | const highlighted = tbody.querySelector(".djdt-highlighted");
78 | if (highlighted) {
79 | highlighted.classList.remove("djdt-highlighted");
80 | }
81 | formTarget.closest("tr").classList.add("djdt-highlighted");
82 |
83 | ajaxForm(formTarget).then((data) => {
84 | if (Object.keys(data).length === 0) {
85 | const container = document.getElementById("djdtHistoryRequests");
86 | container.querySelector(
87 | `button[data-store-id="${newStoreId}"]`
88 | ).innerHTML = "Switch [EXPIRED]";
89 | }
90 | replaceToolbarState(newStoreId, data);
91 | });
92 | }
93 |
94 | $$.on(djDebug, "click", ".switchHistory", function (event) {
95 | event.preventDefault();
96 | switchHistory(this.dataset.storeId);
97 | });
98 |
99 | $$.on(djDebug, "click", ".refreshHistory", (event) => {
100 | event.preventDefault();
101 | refreshHistory();
102 | });
103 | // We don't refresh the whole toolbar each fetch or ajax request,
104 | // so we need to refresh the history when we open the panel
105 | $$.onPanelRender(djDebug, "HistoryPanel", refreshHistory);
106 |
--------------------------------------------------------------------------------
/debug_toolbar/static/debug_toolbar/js/redirect.js:
--------------------------------------------------------------------------------
1 | document.getElementById("redirect_to").focus();
2 |
--------------------------------------------------------------------------------
/debug_toolbar/static/debug_toolbar/js/timer.js:
--------------------------------------------------------------------------------
1 | import { $$ } from "./utils.js";
2 |
3 | function insertBrowserTiming() {
4 | const timingOffset = performance.timing.navigationStart;
5 | const timingEnd = performance.timing.loadEventEnd;
6 | const totalTime = timingEnd - timingOffset;
7 | function getLeft(stat) {
8 | if (totalTime !== 0) {
9 | return (
10 | ((performance.timing[stat] - timingOffset) / totalTime) * 100.0
11 | );
12 | }
13 | return 0;
14 | }
15 | function getCSSWidth(stat, endStat) {
16 | let width = 0;
17 | if (totalTime !== 0) {
18 | width =
19 | ((performance.timing[endStat] - performance.timing[stat]) /
20 | totalTime) *
21 | 100.0;
22 | }
23 | const denominator = 100.0 - getLeft(stat);
24 | if (denominator !== 0) {
25 | // Calculate relative percent (same as sql panel logic)
26 | width = (100.0 * width) / denominator;
27 | } else {
28 | width = 0;
29 | }
30 | return width < 1 ? "2px" : `${width}%`;
31 | }
32 | function addRow(tbody, stat, endStat) {
33 | const row = document.createElement("tr");
34 | const elapsed = performance.timing[stat] - timingOffset;
35 | if (endStat) {
36 | const duration =
37 | performance.timing[endStat] - performance.timing[stat];
38 | // Render a start through end bar
39 | row.innerHTML = `
40 |
${stat.replace("Start", "")}
41 |
42 |
${elapsed} (+${duration})
43 | `;
44 | row.querySelector("rect").setAttribute(
45 | "width",
46 | getCSSWidth(stat, endStat)
47 | );
48 | } else {
49 | // Render a point in time
50 | row.innerHTML = `
51 |
${stat}
52 |
53 |
${elapsed}
54 | `;
55 | row.querySelector("rect").setAttribute("width", 2);
56 | }
57 | row.querySelector("rect").setAttribute("x", getLeft(stat));
58 | tbody.appendChild(row);
59 | }
60 |
61 | const browserTiming = document.getElementById("djDebugBrowserTiming");
62 | // Determine if the browser timing section has already been rendered.
63 | if (browserTiming.classList.contains("djdt-hidden")) {
64 | const tbody = document.getElementById("djDebugBrowserTimingTableBody");
65 | // This is a reasonably complete and ordered set of timing periods (2 params) and events (1 param)
66 | addRow(tbody, "domainLookupStart", "domainLookupEnd");
67 | addRow(tbody, "connectStart", "connectEnd");
68 | addRow(tbody, "requestStart", "responseEnd"); // There is no requestEnd
69 | addRow(tbody, "responseStart", "responseEnd");
70 | addRow(tbody, "domLoading", "domComplete"); // Spans the events below
71 | addRow(tbody, "domInteractive");
72 | addRow(tbody, "domContentLoadedEventStart", "domContentLoadedEventEnd");
73 | addRow(tbody, "loadEventStart", "loadEventEnd");
74 | browserTiming.classList.remove("djdt-hidden");
75 | }
76 | }
77 |
78 | const djDebug = document.getElementById("djDebug");
79 | // Insert the browser timing now since it's possible for this
80 | // script to miss the initial panel load event.
81 | insertBrowserTiming();
82 | $$.onPanelRender(djDebug, "TimerPanel", insertBrowserTiming);
83 |
--------------------------------------------------------------------------------
/debug_toolbar/templates/debug_toolbar/base.html:
--------------------------------------------------------------------------------
1 | {% load i18n static %}
2 | {% block css %}
3 |
4 |
5 | {% endblock css %}
6 | {% block js %}
7 |
8 | {% endblock js %}
9 |
20 |
33 |
38 |
39 | {% for panel in toolbar.panels %}
40 | {% include "debug_toolbar/includes/panel_content.html" %}
41 | {% endfor %}
42 |
43 |
44 |
--------------------------------------------------------------------------------
/debug_toolbar/templates/debug_toolbar/includes/panel_button.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 |
3 |
4 |
5 | {% if panel.has_content and panel.enabled %}
6 |
7 | {% else %}
8 |
9 | {% endif %}
10 | {{ panel.nav_title }}
11 | {% if panel.enabled %}
12 | {% with subtitle=panel.nav_subtitle %}
13 | {% if subtitle %}{{ subtitle }} {% endif %}
14 | {% endwith %}
15 | {% endif %}
16 | {% if panel.has_content and panel.enabled %}
17 |
18 | {% else %}
19 |
20 | {% endif %}
21 |
22 |
--------------------------------------------------------------------------------
/debug_toolbar/templates/debug_toolbar/includes/panel_content.html:
--------------------------------------------------------------------------------
1 | {% load static %}
2 |
3 | {% if panel.has_content and panel.enabled %}
4 |
5 |
6 |
{{ panel.title }}
7 | ×
8 |
9 |
10 | {% if toolbar.should_render_panels %}
11 | {% for script in panel.scripts %}{% endfor %}
12 |
{{ panel.content }}
13 | {% else %}
14 |
15 |
16 | {% endif %}
17 |
18 |
19 | {% endif %}
20 |
--------------------------------------------------------------------------------
/debug_toolbar/templates/debug_toolbar/includes/theme_selector.html:
--------------------------------------------------------------------------------
1 |
9 |
12 |
13 |
20 |
26 |
27 |
34 |
41 |
42 |
--------------------------------------------------------------------------------
/debug_toolbar/templates/debug_toolbar/panels/alerts.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 |
3 | {% if alerts %}
4 |
{% translate "Alerts found" %}
5 | {% for alert in alerts %}
6 |
7 | {{ alert.alert }}
8 |
9 | {% endfor %}
10 | {% else %}
11 |
{% translate "No alerts found" %}
12 | {% endif %}
13 |
--------------------------------------------------------------------------------
/debug_toolbar/templates/debug_toolbar/panels/cache.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 |
{% translate "Summary" %}
3 |
4 |
5 |
6 | {% translate "Total calls" %}
7 | {% translate "Total time" %}
8 | {% translate "Cache hits" %}
9 | {% translate "Cache misses" %}
10 |
11 |
12 |
13 |
14 | {{ total_calls }}
15 | {{ total_time }} ms
16 | {{ hits }}
17 | {{ misses }}
18 |
19 |
20 |
21 |
{% translate "Commands" %}
22 |
23 |
24 |
25 | {% for name in counts.keys %}
26 | {{ name }}
27 | {% endfor %}
28 |
29 |
30 |
31 |
32 | {% for value in counts.values %}
33 | {{ value }}
34 | {% endfor %}
35 |
36 |
37 |
38 | {% if calls %}
39 |
{% translate "Calls" %}
40 |
41 |
42 |
43 | {% translate "Time (ms)" %}
44 | {% translate "Type" %}
45 | {% translate "Arguments" %}
46 | {% translate "Keyword arguments" %}
47 | {% translate "Backend" %}
48 |
49 |
50 |
51 | {% for call in calls %}
52 |
53 |
54 | +
55 |
56 | {{ call.time|floatformat:"4" }}
57 | {{ call.name|escape }}
58 | {{ call.args|escape }}
59 | {{ call.kwargs|escape }}
60 | {{ call.backend }}
61 |
62 |
63 |
64 | {{ call.trace }}
65 |
66 | {% endfor %}
67 |
68 |
69 | {% endif %}
70 |
--------------------------------------------------------------------------------
/debug_toolbar/templates/debug_toolbar/panels/headers.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 |
3 |
{% translate "Request headers" %}
4 |
5 |
6 |
7 |
8 | {% translate "Key" %}
9 | {% translate "Value" %}
10 |
11 |
12 |
13 | {% for key, value in request_headers.items %}
14 |
15 | {{ key|escape }}
16 | {{ value|escape }}
17 |
18 | {% endfor %}
19 |
20 |
21 |
22 |
{% translate "Response headers" %}
23 |
24 |
25 |
26 |
27 | {% translate "Key" %}
28 | {% translate "Value" %}
29 |
30 |
31 |
32 | {% for key, value in response_headers.items %}
33 |
34 | {{ key|escape }}
35 | {{ value|escape }}
36 |
37 | {% endfor %}
38 |
39 |
40 |
41 |
{% translate "WSGI environ" %}
42 |
43 |
{% translate "Since the WSGI environ inherits the environment of the server, only a significant subset is shown below." %}
44 |
45 |
46 |
47 |
48 | {% translate "Key" %}
49 | {% translate "Value" %}
50 |
51 |
52 |
53 | {% for key, value in environ.items %}
54 |
55 | {{ key|escape }}
56 | {{ value|escape }}
57 |
58 | {% endfor %}
59 |
60 |
61 |
--------------------------------------------------------------------------------
/debug_toolbar/templates/debug_toolbar/panels/history.html:
--------------------------------------------------------------------------------
1 | {% load i18n static %}
2 |
6 |
7 |
8 |
9 | {% translate "Time" %}
10 | {% translate "Method" %}
11 | {% translate "Path" %}
12 | {% translate "Request Variables" %}
13 | {% translate "Status" %}
14 | {% translate "Action" %}
15 |
16 |
17 |
18 | {% for id, store_context in stores.items %}
19 | {% include "debug_toolbar/panels/history_tr.html" %}
20 | {% endfor %}
21 |
22 |
23 |
--------------------------------------------------------------------------------
/debug_toolbar/templates/debug_toolbar/panels/history_tr.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 |
3 |
4 | {{ store_context.toolbar.stats.HistoryPanel.time|escape }}
5 |
6 |
7 | {{ store_context.toolbar.stats.HistoryPanel.request_method|escape }}
8 |
9 |
10 | {{ store_context.toolbar.stats.HistoryPanel.request_url|truncatechars:100|escape }}
11 |
12 |
13 | +
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | {% translate "Variable" %}
23 | {% translate "Value" %}
24 |
25 |
26 |
27 | {% for key, value in store_context.toolbar.stats.HistoryPanel.data.items %}
28 |
29 | {{ key|pprint }}
30 | {{ value|pprint }}
31 |
32 | {% empty %}
33 |
34 | No data
35 |
36 | {% endfor %}
37 |
38 |
39 |
40 |
41 |
42 | {{ store_context.toolbar.stats.HistoryPanel.status_code|escape }}
43 |
44 |
45 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/debug_toolbar/templates/debug_toolbar/panels/profiling.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 |
3 |
4 |
5 | {% translate "Call" %}
6 | {% translate "CumTime" %}
7 | {% translate "Per" %}
8 | {% translate "TotTime" %}
9 | {% translate "Per" %}
10 | {% translate "Count" %}
11 |
12 |
13 |
14 | {% for call in func_list %}
15 |
16 |
17 |
18 | {% if call.has_subfuncs %}
19 | -
20 | {% else %}
21 |
22 | {% endif %}
23 | {{ call.func_std_string }}
24 |
25 |
26 | {{ call.cumtime|floatformat:3 }}
27 | {{ call.cumtime_per_call|floatformat:3 }}
28 | {{ call.tottime|floatformat:3 }}
29 | {{ call.tottime_per_call|floatformat:3 }}
30 | {{ call.count }}
31 |
32 | {% endfor %}
33 |
34 |
35 |
--------------------------------------------------------------------------------
/debug_toolbar/templates/debug_toolbar/panels/request.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 |
3 |
{% translate "View information" %}
4 |
5 |
6 |
7 | {% translate "View function" %}
8 | {% translate "Arguments" %}
9 | {% translate "Keyword arguments" %}
10 | {% translate "URL name" %}
11 |
12 |
13 |
14 |
15 | {{ view_func }}
16 | {{ view_args|pprint }}
17 | {{ view_kwargs|pprint }}
18 | {{ view_urlname }}
19 |
20 |
21 |
22 |
23 | {% if cookies.list or cookies.raw %}
24 |
{% translate "Cookies" %}
25 | {% include 'debug_toolbar/panels/request_variables.html' with variables=cookies %}
26 | {% else %}
27 |
{% translate "No cookies" %}
28 | {% endif %}
29 |
30 | {% if session.list or session.raw %}
31 |
{% translate "Session data" %}
32 | {% include 'debug_toolbar/panels/request_variables.html' with variables=session %}
33 | {% else %}
34 |
{% translate "No session data" %}
35 | {% endif %}
36 |
37 | {% if get.list or get.raw %}
38 |
{% translate "GET data" %}
39 | {% include 'debug_toolbar/panels/request_variables.html' with variables=get %}
40 | {% else %}
41 |
{% translate "No GET data" %}
42 | {% endif %}
43 |
44 | {% if post.list or post.raw %}
45 |
{% translate "POST data" %}
46 | {% include 'debug_toolbar/panels/request_variables.html' with variables=post %}
47 | {% else %}
48 |
{% translate "No POST data" %}
49 | {% endif %}
50 |
--------------------------------------------------------------------------------
/debug_toolbar/templates/debug_toolbar/panels/request_variables.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 |
3 | {% if variables.list %}
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | {% translate "Variable" %}
12 | {% translate "Value" %}
13 |
14 |
15 |
16 | {% for key, value in variables.list %}
17 |
18 | {{ key|pprint }}
19 | {{ value|pprint }}
20 |
21 | {% endfor %}
22 |
23 |
24 | {% elif variables.raw %}
25 | {{ variables.raw|pprint }}
26 | {% endif %}
27 |
--------------------------------------------------------------------------------
/debug_toolbar/templates/debug_toolbar/panels/settings.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 |
3 |
4 |
5 | {% translate "Setting" %}
6 | {% translate "Value" %}
7 |
8 |
9 |
10 | {% for name, value in settings.items %}
11 |
12 | {{ name }}
13 | {{ value|pprint }}
14 |
15 | {% endfor %}
16 |
17 |
18 |
--------------------------------------------------------------------------------
/debug_toolbar/templates/debug_toolbar/panels/signals.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 |
3 |
4 |
5 | {% translate "Signal" %}
6 | {% translate "Receivers" %}
7 |
8 |
9 |
10 | {% for name, receivers in signals %}
11 |
12 | {{ name|escape }}
13 | {{ receivers|join:", " }}
14 |
15 | {% endfor %}
16 |
17 |
18 |
--------------------------------------------------------------------------------
/debug_toolbar/templates/debug_toolbar/panels/sql_explain.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 |
3 |
{% translate "SQL explained" %}
4 | »
5 |
6 |
36 |
--------------------------------------------------------------------------------
/debug_toolbar/templates/debug_toolbar/panels/sql_profile.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 |
3 |
{% translate "SQL profiled" %}
4 | »
5 |
6 |
43 |
--------------------------------------------------------------------------------
/debug_toolbar/templates/debug_toolbar/panels/sql_select.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 |
3 |
{% translate "SQL selected" %}
4 | »
5 |
6 |
40 |
--------------------------------------------------------------------------------
/debug_toolbar/templates/debug_toolbar/panels/staticfiles.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 |
3 |
{% blocktranslate count dirs_count=staticfiles_dirs|length %}Static file path{% plural %}Static file paths{% endblocktranslate %}
4 | {% if staticfiles_dirs %}
5 |
6 | {% for prefix, staticfiles_dir in staticfiles_dirs %}
7 | {{ staticfiles_dir }}{% if prefix %} {% blocktranslate %}(prefix {{ prefix }}){% endblocktranslate %}{% endif %}
8 | {% endfor %}
9 |
10 | {% else %}
11 |
{% translate "None" %}
12 | {% endif %}
13 |
14 |
{% blocktranslate count apps_count=staticfiles_apps|length %}Static file app{% plural %}Static file apps{% endblocktranslate %}
15 | {% if staticfiles_apps %}
16 |
17 | {% for static_app in staticfiles_apps %}
18 | {{ static_app }}
19 | {% endfor %}
20 |
21 | {% else %}
22 |
{% translate "None" %}
23 | {% endif %}
24 |
25 |
{% blocktranslate count staticfiles_count=staticfiles|length %}Static file{% plural %}Static files{% endblocktranslate %}
26 | {% if staticfiles %}
27 |
28 | {% for staticfile in staticfiles %}
29 | {{ staticfile }}
30 | {{ staticfile.real_path }}
31 | {% endfor %}
32 |
33 | {% else %}
34 |
{% translate "None" %}
35 | {% endif %}
36 |
37 |
38 | {% for finder, payload in staticfiles_finders.items %}
39 |
{{ finder }} ({% blocktranslate count payload_count=payload|length %}{{ payload_count }} file{% plural %}{{ payload_count }} files{% endblocktranslate %})
40 |
41 |
42 |
43 | {% translate 'Path' %}
44 | {% translate 'Location' %}
45 |
46 |
47 |
48 | {% for path, real_path in payload %}
49 |
50 | {{ path }}
51 | {{ real_path }}
52 |
53 | {% endfor %}
54 |
55 |
56 | {% endfor %}
57 |
--------------------------------------------------------------------------------
/debug_toolbar/templates/debug_toolbar/panels/template_source.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 |
3 |
{% translate "Template source:" %} {{ template_name }}
4 | »
5 |
6 |
7 |
8 | {{ source }}
9 |
10 |
11 |
--------------------------------------------------------------------------------
/debug_toolbar/templates/debug_toolbar/panels/templates.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 |
{% blocktranslate count template_count=template_dirs|length %}Template path{% plural %}Template paths{% endblocktranslate %}
3 | {% if template_dirs %}
4 |
5 | {% for template in template_dirs %}
6 | {{ template }}
7 | {% endfor %}
8 |
9 | {% else %}
10 |
{% translate "None" %}
11 | {% endif %}
12 |
13 |
{% blocktranslate count template_count=templates|length %}Template{% plural %}Templates{% endblocktranslate %}
14 | {% if templates %}
15 |
16 | {% for template in templates %}
17 | {{ template.template.name|addslashes }}
18 | {{ template.template.origin_name|addslashes }}
19 | {% if template.context %}
20 |
21 |
22 | {% translate "Toggle context" %}
23 | {{ template.context }}
24 |
25 |
26 | {% endif %}
27 | {% endfor %}
28 |
29 | {% else %}
30 |
{% translate "None" %}
31 | {% endif %}
32 |
33 |
{% blocktranslate count context_processors_count=context_processors|length %}Context processor{% plural %}Context processors{% endblocktranslate %}
34 | {% if context_processors %}
35 |
36 | {% for key, value in context_processors.items %}
37 | {{ key|escape }}
38 |
39 |
40 | {% translate "Toggle context" %}
41 | {{ value|escape }}
42 |
43 |
44 | {% endfor %}
45 |
46 | {% else %}
47 |
{% translate "None" %}
48 | {% endif %}
49 |
--------------------------------------------------------------------------------
/debug_toolbar/templates/debug_toolbar/panels/timer.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 |
{% translate "Resource usage" %}
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | {% translate "Resource" %}
11 | {% translate "Value" %}
12 |
13 |
14 |
15 | {% for key, value in rows %}
16 |
17 | {{ key|escape }}
18 | {{ value|escape }}
19 |
20 | {% endfor %}
21 |
22 |
23 |
24 |
25 |
26 |
{% translate "Browser timing" %}
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | {% translate "Timing attribute" %}
36 | {% translate "Timeline" %}
37 | {% translate "Milliseconds since navigation start (+length)" %}
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/debug_toolbar/templates/debug_toolbar/panels/versions.html:
--------------------------------------------------------------------------------
1 | {% load i18n %}
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | {% translate "Package" %}
11 | {% translate "Name" %}
12 | {% translate "Version" %}
13 |
14 |
15 |
16 | {% for package, name, version in versions %}
17 |
18 | {{ package }}
19 | {{ name }}
20 | {{ version }}
21 |
22 | {% endfor %}
23 |
24 |
25 |
--------------------------------------------------------------------------------
/debug_toolbar/templates/debug_toolbar/redirect.html:
--------------------------------------------------------------------------------
1 | {% load i18n static %}
2 |
3 |
4 |
5 |
Django Debug Toolbar Redirects Panel: {{ status_line }}
6 |
7 |
8 |
15 |