Codebase list python-django-debug-toolbar / 2a9178c
Update upstream source from tag 'upstream/3.2.1' Update to upstream version '3.2.1' with Debian dir c28074a3eaeb42364c52b29ab6701d328a118713 Raphaƫl Hertzog 2 years ago
132 changed file(s) with 4465 addition(s) and 3041 deletion(s). Raw diff Collapse all Expand all
0 # https://editorconfig.org/
1
2 root = true
3
4 [*]
5 indent_style = space
6 indent_size = 4
7 insert_final_newline = true
8 trim_trailing_whitespace = true
9 end_of_line = lf
10 charset = utf-8
11
12 [*.html]
13 indent_size = 2
14
15 [Makefile]
16 indent_style = tab
17
18 [*.bat]
19 indent_style = tab
0 {
1 "env": {
2 "browser": true,
3 "es6": true
4 },
5 "extends": "eslint:recommended",
6 "parserOptions": {
7 "ecmaVersion": 6,
8 "sourceType": "module"
9 },
10 "rules": {
11 "curly": ["error", "all"],
12 "dot-notation": "error",
13 "eqeqeq": "error",
14 "no-eval": "error",
15 "no-var": "error",
16 "prefer-const": "error",
17 "semi": "error"
18 }
19 }
0 name: Release
1
2 on:
3 push:
4 tags:
5 - '*'
6
7 jobs:
8 build:
9 if: github.repository == 'jazzband/django-debug-toolbar'
10 runs-on: ubuntu-latest
11
12 steps:
13 - uses: actions/checkout@v2
14 with:
15 fetch-depth: 0
16
17 - name: Set up Python
18 uses: actions/setup-python@v2
19 with:
20 python-version: 3.8
21
22 - name: Install dependencies
23 run: |
24 python -m pip install -U pip
25 python -m pip install -U setuptools twine wheel
26
27 - name: Build package
28 run: |
29 python setup.py --version
30 python setup.py sdist --format=gztar bdist_wheel
31 twine check dist/*
32
33 - name: Upload packages to Jazzband
34 if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
35 uses: pypa/gh-action-pypi-publish@master
36 with:
37 user: jazzband
38 password: ${{ secrets.JAZZBAND_RELEASE_KEY }}
39 repository_url: https://jazzband.co/projects/django-debug-toolbar/upload
0 name: Test
1
2 on: [push, pull_request]
3
4 jobs:
5 mysql:
6 runs-on: ubuntu-latest
7 strategy:
8 fail-fast: false
9 max-parallel: 5
10 matrix:
11 python-version: ['3.6', '3.7', '3.8', '3.9']
12
13 services:
14 mariadb:
15 image: mariadb:10.3
16 env:
17 MYSQL_ROOT_PASSWORD: debug_toolbar
18 options: >-
19 --health-cmd "mysqladmin ping"
20 --health-interval 10s
21 --health-timeout 5s
22 --health-retries 5
23 ports:
24 - 3306:3306
25
26 steps:
27 - uses: actions/checkout@v2
28
29 - name: Set up Python ${{ matrix.python-version }}
30 uses: actions/setup-python@v2
31 with:
32 python-version: ${{ matrix.python-version }}
33
34 - name: Get pip cache dir
35 id: pip-cache
36 run: |
37 echo "::set-output name=dir::$(pip cache dir)"
38
39 - name: Cache
40 uses: actions/cache@v2
41 with:
42 path: ${{ steps.pip-cache.outputs.dir }}
43 key:
44 ${{ matrix.python-version }}-v1-${{ hashFiles('**/setup.cfg') }}-${{ hashFiles('**/tox.ini') }}
45 restore-keys: |
46 ${{ matrix.python-version }}-v1-
47
48 - name: Install enchant (only for docs)
49 run: |
50 sudo apt-get -qq update
51 sudo apt-get -y install enchant
52
53 - name: Install dependencies
54 run: |
55 python -m pip install --upgrade pip
56 python -m pip install --upgrade tox tox-gh-actions
57
58 - name: Test with tox
59 run: tox
60 env:
61 DB_BACKEND: mysql
62 DB_USER: root
63 DB_PASSWORD: debug_toolbar
64 DB_HOST: 127.0.0.1
65 DB_PORT: 3306
66
67 - name: Upload coverage
68 uses: codecov/codecov-action@v1
69 with:
70 name: Python ${{ matrix.python-version }}
71
72 postgres:
73 runs-on: ubuntu-latest
74 strategy:
75 fail-fast: false
76 max-parallel: 5
77 matrix:
78 python-version: ['3.6', '3.7', '3.8', '3.9']
79
80 services:
81 postgres:
82 image: 'postgres:9.5'
83 env:
84 POSTGRES_DB: debug_toolbar
85 POSTGRES_USER: debug_toolbar
86 POSTGRES_PASSWORD: debug_toolbar
87 ports:
88 - 5432:5432
89 options: >-
90 --health-cmd pg_isready
91 --health-interval 10s
92 --health-timeout 5s
93 --health-retries 5
94
95 steps:
96 - uses: actions/checkout@v2
97
98 - name: Set up Python ${{ matrix.python-version }}
99 uses: actions/setup-python@v2
100 with:
101 python-version: ${{ matrix.python-version }}
102
103 - name: Get pip cache dir
104 id: pip-cache
105 run: |
106 echo "::set-output name=dir::$(pip cache dir)"
107
108 - name: Cache
109 uses: actions/cache@v2
110 with:
111 path: ${{ steps.pip-cache.outputs.dir }}
112 key:
113 ${{ matrix.python-version }}-v1-${{ hashFiles('**/setup.cfg') }}-${{ hashFiles('**/tox.ini') }}
114 restore-keys: |
115 ${{ matrix.python-version }}-v1-
116
117 - name: Install enchant (only for docs)
118 run: |
119 sudo apt-get -qq update
120 sudo apt-get -y install enchant
121
122 - name: Install dependencies
123 run: |
124 python -m pip install --upgrade pip
125 python -m pip install --upgrade tox tox-gh-actions
126
127 - name: Test with tox
128 run: tox
129 env:
130 DB_BACKEND: postgresql
131 DB_HOST: localhost
132 DB_PORT: 5432
133
134 - name: Upload coverage
135 uses: codecov/codecov-action@v1
136 with:
137 name: Python ${{ matrix.python-version }}
138
139 sqlite:
140 runs-on: ubuntu-latest
141 strategy:
142 fail-fast: false
143 max-parallel: 5
144 matrix:
145 python-version: ['3.6', '3.7', '3.8', '3.9']
146
147 steps:
148 - uses: actions/checkout@v2
149
150 - name: Set up Python ${{ matrix.python-version }}
151 uses: actions/setup-python@v2
152 with:
153 python-version: ${{ matrix.python-version }}
154
155 - name: Get pip cache dir
156 id: pip-cache
157 run: |
158 echo "::set-output name=dir::$(pip cache dir)"
159
160 - name: Cache
161 uses: actions/cache@v2
162 with:
163 path: ${{ steps.pip-cache.outputs.dir }}
164 key:
165 ${{ matrix.python-version }}-v1-${{ hashFiles('**/setup.cfg') }}-${{ hashFiles('**/tox.ini') }}
166 restore-keys: |
167 ${{ matrix.python-version }}-v1-
168
169 - name: Install dependencies
170 run: |
171 python -m pip install --upgrade pip
172 python -m pip install --upgrade tox tox-gh-actions
173
174 - name: Test with tox
175 run: tox
176 env:
177 DB_BACKEND: sqlite3
178 DB_NAME: ":memory:"
179
180 - name: Upload coverage
181 uses: codecov/codecov-action@v1
182 with:
183 name: Python ${{ matrix.python-version }}
184
185 lint:
186 runs-on: ubuntu-latest
187 strategy:
188 fail-fast: false
189
190 steps:
191 - uses: actions/checkout@v2
192
193 - name: Set up Python ${{ matrix.python-version }}
194 uses: actions/setup-python@v2
195 with:
196 python-version: 3.8
197
198 - name: Get pip cache dir
199 id: pip-cache
200 run: |
201 echo "::set-output name=dir::$(pip cache dir)"
202
203 - name: Cache
204 uses: actions/cache@v2
205 with:
206 path: ${{ steps.pip-cache.outputs.dir }}
207 key:
208 ${{ matrix.python-version }}-v1-${{ hashFiles('**/setup.cfg') }}-${{ hashFiles('**/tox.ini') }}
209 restore-keys: |
210 ${{ matrix.python-version }}-v1-
211
212 - name: Install dependencies
213 run: |
214 python -m pip install --upgrade pip
215 python -m pip install --upgrade tox
216
217 - name: Test with tox
218 run: tox -e docs,style,readme
99 htmlcov
1010 .tox
1111 node_modules
12 package-lock.json
13 geckodriver.log
14 coverage.xml
+0
-65
.travis.yml less more
0 dist: xenial
1 sudo: false
2 language: python
3 cache: pip
4 matrix:
5 fast_finish: true
6 include:
7 - python: 3.5
8 env: TOXENV=py35-dj111
9 - python: 3.6
10 env: TOXENV=py36-dj111
11 - python: 3.7
12 env: TOXENV=py37-dj111
13 - python: 3.5
14 env: TOXENV=py35-dj20
15 - python: 3.6
16 env: TOXENV=py36-dj20
17 - python: 3.7
18 env: TOXENV=py37-dj20
19 - python: 3.5
20 env: TOXENV=py35-dj21
21 - python: 3.6
22 env: TOXENV=py36-dj21
23 - python: 3.7
24 env: TOXENV=py37-dj21
25 - python: 3.5
26 env: TOXENV=py35-dj22
27 - python: 3.6
28 env: TOXENV=py36-dj22
29 - python: 3.7
30 env: TOXENV=py37-dj22
31 - python: 3.6
32 env: TOXENV=py36-djmaster
33 - python: 3.7
34 env: TOXENV=py37-djmaster
35 - python: 3.7
36 env: TOXENV=postgresql
37 addons:
38 postgresql: "9.5"
39 - python: 3.7
40 env: TOXENV=mariadb
41 addons:
42 mariadb: "10.3"
43 script:
44 # working around https://travis-ci.community/t/mariadb-build-error-with-xenial/3160
45 - mysql -u root -e "DROP USER IF EXISTS 'travis'@'%';"
46 - mysql -u root -e "CREATE USER 'travis'@'%';"
47 - mysql -u root -e "CREATE DATABASE debug_toolbar;"
48 - mysql -u root -e "GRANT ALL PRIVILEGES ON *.* TO 'travis'@'%';";
49 - tox -v
50 - env: TOXENV=flake8
51 - python: 3.7
52 env: TOXENV=style
53 - python: 3.7
54 env: TOXENV=readme
55 allow_failures:
56 - env: TOXENV=py36-djmaster
57 - env: TOXENV=py37-djmaster
58
59 install:
60 - pip install tox codecov
61 script:
62 - tox -v
63 after_success:
64 - codecov
11 host = https://www.transifex.com
22 lang_map = sr@latin:sr_Latn
33
4 [django-debug-toolbar.master]
4 [django-debug-toolbar.main]
55 file_filter = debug_toolbar/locale/<lang>/LC_MESSAGES/django.po
66 source_file = debug_toolbar/locale/en/LC_MESSAGES/django.po
77 source_lang = en
00 .PHONY: flake8 example test coverage translatable_strings update_translations
11
2 style:
3 isort -rc debug_toolbar example tests
4 black --target-version=py34 debug_toolbar example tests setup.py
5 flake8 debug_toolbar example tests
2 PRETTIER_TARGETS = '**/*.(css|js)'
63
7 style_check:
8 isort -rc -c debug_toolbar example tests
9 black --target-version=py34 --check debug_toolbar example tests setup.py
4 style: package-lock.json
5 isort .
6 black --target-version=py36 .
7 flake8
8 npx eslint --ignore-path .gitignore --fix .
9 npx prettier --ignore-path .gitignore --write $(PRETTIER_TARGETS)
10 ! grep -r '\(style=\|onclick=\|<script>\|<style\)' debug_toolbar/templates/
1011
11 flake8:
12 flake8 debug_toolbar example tests
12 style_check: package-lock.json
13 isort -c .
14 black --target-version=py36 --check .
15 flake8
16 npx eslint --ignore-path .gitignore .
17 npx prettier --ignore-path .gitignore --check $(PRETTIER_TARGETS)
18 ! grep -r '\(style=\|onclick=\|<script>\|<style\)' debug_toolbar/templates/
1319
1420 example:
21 python example/manage.py migrate --noinput
22 -DJANGO_SUPERUSER_PASSWORD=p python example/manage.py createsuperuser \
23 --noinput --username="$(USER)" --email="$(USER)@mailinator.com"
1524 python example/manage.py runserver
1625
17 jshint: node_modules/jshint/bin/jshint
18 ./node_modules/jshint/bin/jshint debug_toolbar/static/debug_toolbar/js/*.js
19
20 node_modules/jshint/bin/jshint:
21 npm install jshint --prefix .
26 package-lock.json: package.json
27 npm install
28 touch $@
2229
2330 test:
2431 DJANGO_SETTINGS_MODULE=tests.settings \
3037
3138 coverage:
3239 python --version
33 coverage erase
3440 DJANGO_SETTINGS_MODULE=tests.settings \
35 coverage run -m django test -v2 $${TEST_ARGS:-tests}
41 python -b -W always -m coverage run -m django test -v2 $${TEST_ARGS:-tests}
3642 coverage report
3743 coverage html
44 coverage xml
3845
3946 translatable_strings:
4047 cd debug_toolbar && python -m django makemessages -l en --no-obsolete
4350 update_translations:
4451 tx pull -a --minimum-perc=10
4552 cd debug_toolbar && python -m django compilemessages
53
54 .PHONY: example/django-debug-toolbar.png
55 example/django-debug-toolbar.png: example/screenshot.py
56 python $< --browser firefox --headless -o $@
57 optipng $@
55 :target: https://jazzband.co/
66 :alt: Jazzband
77
8 .. image:: https://travis-ci.org/jazzband/django-debug-toolbar.svg?branch=master
9 :target: https://travis-ci.org/jazzband/django-debug-toolbar
8 .. image:: https://github.com/jazzband/django-debug-toolbar/workflows/Test/badge.svg
9 :target: https://github.com/jazzband/django-debug-toolbar/actions
1010 :alt: Build Status
1111
12 .. image:: https://codecov.io/gh/jazzband/django-debug-toolbar/branch/master/graph/badge.svg
12 .. image:: https://codecov.io/gh/jazzband/django-debug-toolbar/branch/main/graph/badge.svg
1313 :target: https://codecov.io/gh/jazzband/django-debug-toolbar
1414 :alt: Test coverage status
1515
16 .. image:: https://requires.io/github/jazzband/django-debug-toolbar/requirements.svg?branch=master
17 :target: https://requires.io/github/jazzband/django-debug-toolbar/requirements/?branch=master
18 :alt: Requirements Status
16 .. image:: https://img.shields.io/pypi/pyversions/django-debug-toolbar
17 :target: https://pypi.python.org/pypi/django-debug-toolbar
18 :alt: Supported Python versions
19
20 .. image:: https://img.shields.io/pypi/djversions/django-debug-toolbar
21 :target: https://pypi.org/project/django-debug-toolbar
22 :alt: Supported Django versions
1923
2024 The Django Debug Toolbar is a configurable set of panels that display various
2125 debug information about the current request/response and when clicked, display
2327
2428 Here's a screenshot of the toolbar in action:
2529
26 .. image:: https://raw.github.com/jazzband/django-debug-toolbar/master/example/django-debug-toolbar.png
27 :width: 908
28 :height: 557
30 .. image:: https://raw.github.com/jazzband/django-debug-toolbar/main/example/django-debug-toolbar.png
31 :alt: Django Debug Toolbar screenshot
2932
3033 In addition to the built-in panels, a number of third-party panels are
3134 contributed by the community.
3235
33 The current stable version of the Debug Toolbar is 1.11. It works on
34 Django ā‰„ 1.11.
36 The current stable version of the Debug Toolbar is 3.2.1. It works on
37 Django ā‰„ 2.2.
3538
3639 Documentation, including installation and configuration instructions, is
3740 available at https://django-debug-toolbar.readthedocs.io/.
00 __all__ = ["VERSION"]
11
22
3 try:
4 import pkg_resources
5
6 VERSION = pkg_resources.get_distribution("django-debug-toolbar").version
7 except Exception:
8 VERSION = "unknown"
9
3 # Do not use pkg_resources to find the version but set it here directly!
4 # see issue #1446
5 VERSION = "3.2.1"
106
117 # Code that discovers files or modules in INSTALLED_APPS imports this module.
128
55 from django.middleware.gzip import GZipMiddleware
66 from django.utils.module_loading import import_string
77 from django.utils.translation import gettext_lazy as _
8
9 from debug_toolbar import settings as dt_settings
810
911
1012 class DebugToolbarConfig(AppConfig):
1921 errors = []
2022 gzip_index = None
2123 debug_toolbar_indexes = []
24
25 # If old style MIDDLEWARE_CLASSES is being used, report an error.
26 if settings.is_overridden("MIDDLEWARE_CLASSES"):
27 errors.append(
28 Warning(
29 "debug_toolbar is incompatible with MIDDLEWARE_CLASSES setting.",
30 hint="Use MIDDLEWARE instead of MIDDLEWARE_CLASSES",
31 id="debug_toolbar.W004",
32 )
33 )
34 return errors
2235
2336 # Determine the indexes which gzip and/or the toolbar are installed at
2437 for i, middleware in enumerate(settings.MIDDLEWARE):
6073 id="debug_toolbar.W003",
6174 )
6275 )
76 return errors
6377
78
79 @register
80 def check_panel_configs(app_configs, **kwargs):
81 """Allow each panel to check the toolbar's integration for their its own purposes."""
82 from debug_toolbar.toolbar import DebugToolbar
83
84 errors = []
85 for panel_class in DebugToolbar.get_panel_classes():
86 for check_message in panel_class.run_checks():
87 errors.append(check_message)
6488 return errors
6589
6690
7296 return inspect.isclass(middleware_cls) and issubclass(
7397 middleware_cls, middleware_class
7498 )
99
100
101 @register
102 def check_panels(app_configs, **kwargs):
103 errors = []
104 panels = dt_settings.get_panels()
105 if not panels:
106 errors.append(
107 Warning(
108 "Setting DEBUG_TOOLBAR_PANELS is empty.",
109 hint="Set DEBUG_TOOLBAR_PANELS to a non-empty list in your "
110 "settings.py.",
111 id="debug_toolbar.W005",
112 )
113 )
114 return errors
00 import functools
11
2 from django.http import Http404
2 from django.http import Http404, HttpResponseBadRequest
33
44
55 def require_show_toolbar(view):
1414 return view(request, *args, **kwargs)
1515
1616 return inner
17
18
19 def signed_data_view(view):
20 """Decorator that handles unpacking a signed data form"""
21
22 @functools.wraps(view)
23 def inner(request, *args, **kwargs):
24 from debug_toolbar.forms import SignedDataForm
25
26 data = request.GET if request.method == "GET" else request.POST
27 signed_form = SignedDataForm(data)
28 if signed_form.is_valid():
29 return view(
30 request, *args, verified_data=signed_form.verified_data(), **kwargs
31 )
32 return HttpResponseBadRequest("Invalid signature")
33
34 return inner
0 import json
1
2 from django import forms
3 from django.core import signing
4 from django.core.exceptions import ValidationError
5 from django.utils.encoding import force_str
6
7
8 class SignedDataForm(forms.Form):
9 """Helper form that wraps a form to validate its contents on post.
10
11 class PanelForm(forms.Form):
12 # fields
13
14 On render:
15 form = SignedDataForm(initial=PanelForm(initial=data).initial)
16
17 On POST:
18 signed_form = SignedDataForm(request.POST)
19 if signed_form.is_valid():
20 panel_form = PanelForm(signed_form.verified_data)
21 if panel_form.is_valid():
22 # Success
23 Or wrap the FBV with ``debug_toolbar.decorators.signed_data_view``
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:
42 raise ValidationError("Bad signature")
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 items = sorted(data.items(), key=lambda item: item[0])
50 return signing.Signer(salt=cls.salt).sign(
51 json.dumps({key: force_str(value) for key, value in items})
52 )
00 # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
11 # This file is distributed under the same license as the PACKAGE package.
2 #
3 #
2 #
3 #
44 # Translators:
55 # Dario Agliottone <dario.agliottone@gmail.com>, 2012
66 # Flavio Curella <flavio.curella@gmail.com>, 2013
77 # yakky <i.spalletti@nephila.it>, 2013-2014
8 # Andrea Rabbaglietti <rabbagliettiandrea@gmail.com>, 2020
89 msgid ""
910 msgstr ""
1011 "Project-Id-Version: Django Debug Toolbar\n"
4950
5051 #: panels/headers.py:35
5152 msgid "Headers"
52 msgstr "Intestazioni"
53 msgstr "Headers"
5354
5455 #: panels/logging.py:64
5556 msgid "Logging"
7273
7374 #: panels/redirects.py:17
7475 msgid "Intercept redirects"
75 msgstr "Intercetta ridirezioni"
76 msgstr "Intercetta redirezioni"
7677
7778 #: panels/request.py:18
7879 msgid "Request"
221222
222223 #: panels/sql/panel.py:38
223224 msgid "Active"
224 msgstr "Azione"
225 msgstr "Attivo"
225226
226227 #: panels/sql/panel.py:39
227228 msgid "In transaction"
241242
242243 #: panels/templates/panel.py:141
243244 msgid "Templates"
244 msgstr "Template"
245 msgstr "Templates"
245246
246247 #: panels/templates/panel.py:146
247248 #, python-format
328329 #: templates/debug_toolbar/panels/cache.html:46
329330 #: templates/debug_toolbar/panels/request.html:9
330331 msgid "Keyword arguments"
331 msgstr "Parole chiave"
332 msgstr "Keyword arguments"
332333
333334 #: templates/debug_toolbar/panels/cache.html:47
334335 msgid "Backend"
336337
337338 #: templates/debug_toolbar/panels/headers.html:3
338339 msgid "Request headers"
339 msgstr "Header della request"
340 msgstr "Request headers"
340341
341342 #: templates/debug_toolbar/panels/headers.html:8
342343 #: templates/debug_toolbar/panels/headers.html:27
343344 #: templates/debug_toolbar/panels/headers.html:48
344345 msgid "Key"
345 msgstr "Nome"
346 msgstr "Chiave"
346347
347348 #: templates/debug_toolbar/panels/headers.html:9
348349 #: templates/debug_toolbar/panels/headers.html:28
528529
529530 #: templates/debug_toolbar/panels/sql_explain.html:4
530531 msgid "SQL explained"
531 msgstr "SQL spigato"
532 msgstr "SQL spiegato"
532533
533534 #: templates/debug_toolbar/panels/sql_explain.html:9
534535 #: templates/debug_toolbar/panels/sql_profile.html:10
00 from time import time
11
2 import django
23 import sqlparse
34 from django.core.management.commands.shell import Command # noqa
4 from django.db.backends import utils as db_backends_utils
5 from django.db import connection
6
7 if connection.vendor == "postgresql" and django.VERSION >= (3, 0, 0):
8 from django.db.backends.postgresql import base as base_module
9 else:
10 from django.db.backends import utils as base_module
511
612 # 'debugsqlshell' is the same as the 'shell'.
713
814
9 class PrintQueryWrapper(db_backends_utils.CursorDebugWrapper):
15 class PrintQueryWrapper(base_module.CursorDebugWrapper):
1016 def execute(self, sql, params=()):
1117 start_time = time()
1218 try:
1925 print("{} [{:.2f}ms]".format(formatted_sql, duration))
2026
2127
22 db_backends_utils.CursorDebugWrapper = PrintQueryWrapper
28 base_module.CursorDebugWrapper = PrintQueryWrapper
1717 """
1818 Default function to determine whether to show the toolbar on a given page.
1919 """
20 if request.META.get("REMOTE_ADDR", None) not in settings.INTERNAL_IPS:
21 return False
22
23 return bool(settings.DEBUG)
20 return settings.DEBUG and request.META.get("REMOTE_ADDR") in settings.INTERNAL_IPS
2421
2522
2623 @lru_cache()
4441 self.get_response = get_response
4542
4643 def __call__(self, request):
47 # Decide whether the toolbar is active for this request. Don't render
48 # the toolbar during AJAX requests.
44 # Decide whether the toolbar is active for this request.
4945 show_toolbar = get_show_toolbar()
50 if not show_toolbar(request) or request.is_ajax():
46 if not show_toolbar(request) or DebugToolbar.is_toolbar_request(request):
5147 return self.get_response(request)
5248
5349 toolbar = DebugToolbar(request, self.get_response)
6460 for panel in reversed(toolbar.enabled_panels):
6561 panel.disable_instrumentation()
6662
63 # Generate the stats for all requests when the toolbar is being shown,
64 # but not necessarily inserted.
65 for panel in reversed(toolbar.enabled_panels):
66 panel.generate_stats(request, response)
67 panel.generate_server_timing(request, response)
68
69 response = self.generate_server_timing_header(response, toolbar.enabled_panels)
70
71 # Always render the toolbar for the history panel, even if it is not
72 # included in the response.
73 rendered = toolbar.render_toolbar()
74
6775 # Check for responses where the toolbar can't be inserted.
6876 content_encoding = response.get("Content-Encoding", "")
6977 content_type = response.get("Content-Type", "").split(";")[0]
70 if any(
71 (
72 getattr(response, "streaming", False),
73 "gzip" in content_encoding,
74 content_type not in _HTML_TYPES,
75 )
78 if (
79 getattr(response, "streaming", False)
80 or "gzip" in content_encoding
81 or content_type not in _HTML_TYPES
7682 ):
7783 return response
78
79 # Collapse the toolbar by default if SHOW_COLLAPSED is set.
80 if toolbar.config["SHOW_COLLAPSED"] and "djdt" not in request.COOKIES:
81 response.set_cookie("djdt", "hide", 864000)
8284
8385 # Insert the toolbar in the response.
8486 content = response.content.decode(response.charset)
8688 pattern = re.escape(insert_before)
8789 bits = re.split(pattern, content, flags=re.IGNORECASE)
8890 if len(bits) > 1:
89 # When the toolbar will be inserted for sure, generate the stats.
90 for panel in reversed(toolbar.enabled_panels):
91 panel.generate_stats(request, response)
92 panel.generate_server_timing(request, response)
93
94 response = self.generate_server_timing_header(
95 response, toolbar.enabled_panels
96 )
97
98 bits[-2] += toolbar.render_toolbar()
91 bits[-2] += rendered
9992 response.content = insert_before.join(bits)
100 if response.get("Content-Length", None):
93 if "Content-Length" in response:
10194 response["Content-Length"] = len(response.content)
10295 return response
10396
111104 continue
112105
113106 for key, record in stats.items():
114 # example: `SQLPanel_sql_time=0; "SQL 0 queries"`
107 # example: `SQLPanel_sql_time;dur=0;desc="SQL 0 queries"`
115108 data.append(
116 '{}_{}={}; "{}"'.format(
109 '{}_{};dur={};desc="{}"'.format(
117110 panel.panel_id, key, record.get("value"), record.get("title")
118111 )
119112 )
6363 return True
6464
6565 @property
66 def is_historical(self):
67 """
68 Panel supports rendering historical values.
69
70 Defaults to :attr:`has_content`.
71 """
72 return self.has_content
73
74 @property
6675 def title(self):
6776 """
6877 Title shown in the panel when it's displayed in full screen.
92101 """
93102 if self.has_content:
94103 return render_to_string(self.template, self.get_stats())
104
105 @property
106 def scripts(self):
107 """
108 Scripts used by the HTML content of the panel when it's displayed.
109 """
110 return []
95111
96112 # URLs for panel-specific views
97113
114130 time.
115131
116132 Unless the toolbar or this panel is disabled, this method will be
117 called early in :class:`DebugToolbarMiddleware.process_request`. It
118 should be idempotent.
133 called early in ``DebugToolbarMiddleware``. It should be idempotent.
119134 """
120135
121136 def disable_instrumentation(self):
194209
195210 Does not return a value.
196211 """
212
213 @classmethod
214 def run_checks(cls):
215 """
216 Check that the integration is configured correctly for the panel.
217
218 This will be called as a part of the Django checks system when the
219 application is being setup.
220
221 Return a list of :class: `django.core.checks.CheckMessage` instances.
222 """
223 return []
22 import time
33 from collections import OrderedDict
44
5 try:
6 from django.utils.connection import ConnectionProxy
7 except ImportError:
8 ConnectionProxy = None
9
510 from django.conf import settings
611 from django.core import cache
7 from django.core.cache import CacheHandler, caches as original_caches
12 from django.core.cache import (
13 DEFAULT_CACHE_ALIAS,
14 CacheHandler,
15 cache as original_cache,
16 caches as original_caches,
17 )
818 from django.core.cache.backends.base import BaseCache
919 from django.dispatch import Signal
1020 from django.middleware import cache as middleware_cache
1929 tidy_stacktrace,
2030 )
2131
22 cache_called = Signal(
23 providing_args=["time_taken", "name", "return_value", "args", "kwargs", "trace"]
24 )
32 cache_called = Signal()
2533
2634
2735 def send_signal(method):
8593 return self.cache.set(*args, **kwargs)
8694
8795 @send_signal
96 def touch(self, *args, **kwargs):
97 return self.cache.touch(*args, **kwargs)
98
99 @send_signal
88100 def delete(self, *args, **kwargs):
89101 return self.cache.delete(*args, **kwargs)
90102
154166 ("add", 0),
155167 ("get", 0),
156168 ("set", 0),
169 ("touch", 0),
157170 ("delete", 0),
158171 ("clear", 0),
159172 ("get_many", 0),
179192 trace=None,
180193 template_info=None,
181194 backend=None,
182 **kw
195 **kw,
183196 ):
184197 if name == "get":
185198 if return_value is None:
215228 @property
216229 def nav_subtitle(self):
217230 cache_calls = len(self.calls)
218 return __(
219 "%(cache_calls)d call in %(time).2fms",
220 "%(cache_calls)d calls in %(time).2fms",
221 cache_calls,
222 ) % {"cache_calls": cache_calls, "time": self.total_time}
231 return (
232 __(
233 "%(cache_calls)d call in %(time).2fms",
234 "%(cache_calls)d calls in %(time).2fms",
235 cache_calls,
236 )
237 % {"cache_calls": cache_calls, "time": self.total_time}
238 )
223239
224240 @property
225241 def title(self):
226242 count = len(getattr(settings, "CACHES", ["default"]))
227 return __(
228 "Cache calls from %(count)d backend",
229 "Cache calls from %(count)d backends",
230 count,
231 ) % {"count": count}
243 return (
244 __(
245 "Cache calls from %(count)d backend",
246 "Cache calls from %(count)d backends",
247 count,
248 )
249 % {"count": count}
250 )
232251
233252 def enable_instrumentation(self):
234253 if isinstance(middleware_cache.caches, CacheHandlerPatch):
236255 else:
237256 cache.caches = CacheHandlerPatch()
238257
258 # Wrap the patched cache inside Django's ConnectionProxy
259 if ConnectionProxy:
260 cache.cache = ConnectionProxy(cache.caches, DEFAULT_CACHE_ALIAS)
261
239262 def disable_instrumentation(self):
240263 cache.caches = original_caches
264 cache.cache = original_cache
241265 # While it can be restored to the original, any views that were
242266 # wrapped with the cache_page decorator will continue to use a
243267 # monkey patched cache.
0 from debug_toolbar.panels.history.panel import HistoryPanel # noqa
0 from django import forms
1
2
3 class HistoryStoreForm(forms.Form):
4 """
5 Validate params
6
7 store_id: The key for the store instance to be fetched.
8 """
9
10 store_id = forms.CharField(widget=forms.HiddenInput())
0 import json
1 from collections import OrderedDict
2
3 from django.http.request import RawPostDataException
4 from django.template.loader import render_to_string
5 from django.templatetags.static import static
6 from django.urls import path
7 from django.utils import timezone
8 from django.utils.translation import gettext_lazy as _
9
10 from debug_toolbar.forms import SignedDataForm
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 title = _("History")
20 nav_title = _("History")
21 template = "debug_toolbar/panels/history.html"
22
23 @property
24 def is_historical(self):
25 """The HistoryPanel should not be included in the historical panels."""
26 return False
27
28 @classmethod
29 def get_urls(cls):
30 return [
31 path("history_sidebar/", views.history_sidebar, name="history_sidebar"),
32 path("history_refresh/", views.history_refresh, name="history_refresh"),
33 ]
34
35 @property
36 def nav_subtitle(self):
37 return self.get_stats().get("request_url", "")
38
39 def generate_stats(self, request, response):
40 try:
41 if request.method == "GET":
42 data = request.GET.copy()
43 else:
44 data = request.POST.copy()
45 # GraphQL tends to not be populated in POST. If the request seems
46 # empty, check if it's a JSON request.
47 if (
48 not data
49 and request.body
50 and request.META.get("CONTENT_TYPE") == "application/json"
51 ):
52 try:
53 data = json.loads(request.body)
54 except ValueError:
55 pass
56 except RawPostDataException:
57 # It is not guaranteed that we may read the request data (again).
58 data = None
59
60 self.record_stats(
61 {
62 "request_url": request.get_full_path(),
63 "request_method": request.method,
64 "data": data,
65 "time": timezone.now(),
66 }
67 )
68
69 @property
70 def content(self):
71 """Content of the panel when it's displayed in full screen.
72
73 Fetch every store for the toolbar and include it in the template.
74 """
75 stores = OrderedDict()
76 for id, toolbar in reversed(self.toolbar._store.items()):
77 stores[id] = {
78 "toolbar": toolbar,
79 "form": SignedDataForm(
80 initial=HistoryStoreForm(initial={"store_id": id}).initial
81 ),
82 }
83
84 return render_to_string(
85 self.template,
86 {
87 "current_store_id": self.toolbar.store_id,
88 "stores": stores,
89 "refresh_form": SignedDataForm(
90 initial=HistoryStoreForm(
91 initial={"store_id": self.toolbar.store_id}
92 ).initial
93 ),
94 },
95 )
96
97 @property
98 def scripts(self):
99 scripts = super().scripts
100 scripts.append(static("debug_toolbar/js/history.js"))
101 return scripts
0 from django.http import HttpResponseBadRequest, JsonResponse
1 from django.template.loader import render_to_string
2
3 from debug_toolbar.decorators import require_show_toolbar, signed_data_view
4 from debug_toolbar.panels.history.forms import HistoryStoreForm
5 from debug_toolbar.toolbar import DebugToolbar
6
7
8 @require_show_toolbar
9 @signed_data_view
10 def history_sidebar(request, verified_data):
11 """Returns the selected debug toolbar history snapshot."""
12 form = HistoryStoreForm(verified_data)
13
14 if form.is_valid():
15 store_id = form.cleaned_data["store_id"]
16 toolbar = DebugToolbar.fetch(store_id)
17 context = {}
18 for panel in toolbar.panels:
19 if not panel.is_historical:
20 continue
21 panel_context = {"panel": panel}
22 context[panel.panel_id] = {
23 "button": render_to_string(
24 "debug_toolbar/includes/panel_button.html", panel_context
25 ),
26 "content": render_to_string(
27 "debug_toolbar/includes/panel_content.html", panel_context
28 ),
29 }
30 return JsonResponse(context)
31 return HttpResponseBadRequest("Form errors")
32
33
34 @require_show_toolbar
35 @signed_data_view
36 def history_refresh(request, verified_data):
37 """Returns the refreshed list of table rows for the History Panel."""
38 form = HistoryStoreForm(verified_data)
39
40 if form.is_valid():
41 requests = []
42 for id, toolbar in reversed(DebugToolbar._store.items()):
43 requests.append(
44 {
45 "id": id,
46 "content": render_to_string(
47 "debug_toolbar/panels/history_tr.html",
48 {
49 "id": id,
50 "store_context": {
51 "toolbar": toolbar,
52 "form": HistoryStoreForm(initial={"store_id": id}),
53 },
54 },
55 ),
56 }
57 )
58
59 return JsonResponse({"requests": requests})
60 return HttpResponseBadRequest("Form errors")
6363
6464 @property
6565 def nav_subtitle(self):
66 records = self._records[threading.currentThread()]
67 record_count = len(records)
66 stats = self.get_stats()
67 record_count = len(stats["records"]) if stats else None
6868 return __("%(count)s message", "%(count)s messages", record_count) % {
6969 "count": record_count
7070 }
1414
1515 def process_request(self, request):
1616 response = super().process_request(request)
17 if 300 <= int(response.status_code) < 400:
18 redirect_to = response.get("Location", None)
17 if 300 <= response.status_code < 400:
18 redirect_to = response.get("Location")
1919 if redirect_to:
2020 status_line = "{} {}".format(
2121 response.status_code, response.reason_phrase
22 from django.utils.translation import gettext_lazy as _
33
44 from debug_toolbar.panels import Panel
5 from debug_toolbar.utils import get_name_from_obj
5 from debug_toolbar.utils import get_name_from_obj, get_sorted_request_variable
66
77
88 class RequestPanel(Panel):
2525 def generate_stats(self, request, response):
2626 self.record_stats(
2727 {
28 "get": [(k, request.GET.getlist(k)) for k in sorted(request.GET)],
29 "post": [(k, request.POST.getlist(k)) for k in sorted(request.POST)],
30 "cookies": [
31 (k, request.COOKIES.get(k)) for k in sorted(request.COOKIES)
32 ],
28 "get": get_sorted_request_variable(request.GET),
29 "post": get_sorted_request_variable(request.POST),
30 "cookies": get_sorted_request_variable(request.COOKIES),
3331 }
3432 )
33
3534 view_info = {
3635 "view_func": _("<no view>"),
3736 "view_args": "None",
4443 view_info["view_func"] = get_name_from_obj(func)
4544 view_info["view_args"] = args
4645 view_info["view_kwargs"] = kwargs
47 view_info["view_urlname"] = getattr(match, "url_name", _("<unavailable>"))
46
47 if getattr(match, "url_name", False):
48 url_name = match.url_name
49 if match.namespaces:
50 url_name = ":".join([*match.namespaces, url_name])
51 else:
52 url_name = _("<unavailable>")
53
54 view_info["view_urlname"] = url_name
55
4856 except Http404:
4957 pass
5058 self.record_stats(view_info)
00 from collections import OrderedDict
11
2 import django
23 from django.conf import settings
34 from django.utils.translation import gettext_lazy as _
4 from django.views.debug import get_safe_settings
55
66 from debug_toolbar.panels import Panel
7
8 if django.VERSION >= (3, 1):
9 from django.views.debug import get_default_exception_reporter_filter
10
11 get_safe_settings = get_default_exception_reporter_filter().get_safe_settings
12 else:
13 from django.views.debug import get_safe_settings
714
815
916 class SettingsPanel(Panel):
1623 nav_title = _("Settings")
1724
1825 def title(self):
19 return _("Settings from <code>%s</code>") % settings.SETTINGS_MODULE
26 return _("Settings from %s") % settings.SETTINGS_MODULE
2027
2128 def generate_stats(self, request, response):
2229 self.record_stats(
00 import weakref
11
2 from django.core.signals import got_request_exception, request_finished, request_started
2 from django.core.signals import (
3 got_request_exception,
4 request_finished,
5 request_started,
6 setting_changed,
7 )
38 from django.db.backends.signals import connection_created
49 from django.db.models.signals import (
510 class_prepared,
11 m2m_changed,
612 post_delete,
713 post_init,
814 post_migrate,
915 post_save,
1016 pre_delete,
1117 pre_init,
18 pre_migrate,
1219 pre_save,
1320 )
1421 from django.utils.module_loading import import_string
3239 "post_save": post_save,
3340 "pre_delete": pre_delete,
3441 "post_delete": post_delete,
42 "m2m_changed": m2m_changed,
43 "pre_migrate": pre_migrate,
3544 "post_migrate": post_migrate,
45 "setting_changed": setting_changed,
3646 }
3747
3848 def nav_subtitle(self):
3949 signals = self.get_stats()["signals"]
40 num_receivers = sum(len(s[2]) for s in signals)
50 num_receivers = sum(len(receivers) for name, receivers in signals)
4151 num_signals = len(signals)
4252 # here we have to handle a double count translation, hence the
4353 # hard coding of one signal
4454 if num_signals == 1:
45 return __(
46 "%(num_receivers)d receiver of 1 signal",
47 "%(num_receivers)d receivers of 1 signal",
55 return (
56 __(
57 "%(num_receivers)d receiver of 1 signal",
58 "%(num_receivers)d receivers of 1 signal",
59 num_receivers,
60 )
61 % {"num_receivers": num_receivers}
62 )
63 return (
64 __(
65 "%(num_receivers)d receiver of %(num_signals)d signals",
66 "%(num_receivers)d receivers of %(num_signals)d signals",
4867 num_receivers,
49 ) % {"num_receivers": num_receivers}
50 return __(
51 "%(num_receivers)d receiver of %(num_signals)d signals",
52 "%(num_receivers)d receivers of %(num_signals)d signals",
53 num_receivers,
54 ) % {"num_receivers": num_receivers, "num_signals": num_signals}
68 )
69 % {"num_receivers": num_receivers, "num_signals": num_signals}
70 )
5571
5672 title = _("Signals")
5773
84100 else:
85101 text = receiver_name
86102 receivers.append(text)
87 signals.append((name, signal, receivers))
103 signals.append((name, receivers))
88104
89105 self.record_stats({"signals": signals})
0 import hashlib
1 import hmac
20 import json
31
42 from django import forms
5 from django.conf import settings
63 from django.core.exceptions import ValidationError
74 from django.db import connections
8 from django.utils.crypto import constant_time_compare
9 from django.utils.encoding import force_bytes
105 from django.utils.functional import cached_property
116
127 from debug_toolbar.panels.sql.utils import reformat_sql
2015 raw_sql: The sql statement with placeholders
2116 params: JSON encoded parameter values
2217 duration: time for SQL to execute passed in from toolbar just for redisplay
23 hash: the hash of (secret + sql + params) for tamper checking
2418 """
2519
2620 sql = forms.CharField()
2822 params = forms.CharField()
2923 alias = forms.CharField(required=False, initial="default")
3024 duration = forms.FloatField()
31 hash = forms.CharField()
32
33 def __init__(self, *args, **kwargs):
34 initial = kwargs.get("initial", None)
35
36 if initial is not None:
37 initial["hash"] = self.make_hash(initial)
38
39 super().__init__(*args, **kwargs)
40
41 for name in self.fields:
42 self.fields[name].widget = forms.HiddenInput()
4325
4426 def clean_raw_sql(self):
4527 value = self.cleaned_data["raw_sql"]
6547
6648 return value
6749
68 def clean_hash(self):
69 hash = self.cleaned_data["hash"]
70
71 if not constant_time_compare(hash, self.make_hash(self.data)):
72 raise ValidationError("Tamper alert")
73
74 return hash
75
7650 def reformat_sql(self):
7751 return reformat_sql(self.cleaned_data["sql"], with_toggle=False)
78
79 def make_hash(self, data):
80 m = hmac.new(key=force_bytes(settings.SECRET_KEY), digestmod=hashlib.sha1)
81 for item in [data["sql"], data["params"]]:
82 m.update(force_bytes(item))
83 return m.hexdigest()
8452
8553 @property
8654 def connection(self):
22 from copy import copy
33 from pprint import saferepr
44
5 from django.conf.urls import url
65 from django.db import connections
6 from django.urls import path
77 from django.utils.translation import gettext_lazy as _, ngettext_lazy as __
88
9 from debug_toolbar.forms import SignedDataForm
910 from debug_toolbar.panels import Panel
1011 from debug_toolbar.panels.sql import views
1112 from debug_toolbar.panels.sql.forms import SQLSelectForm
116117 @property
117118 def title(self):
118119 count = len(self._databases)
119 return __(
120 "SQL queries from %(count)d connection",
121 "SQL queries from %(count)d connections",
122 count,
123 ) % {"count": count}
120 return (
121 __(
122 "SQL queries from %(count)d connection",
123 "SQL queries from %(count)d connections",
124 count,
125 )
126 % {"count": count}
127 )
124128
125129 template = "debug_toolbar/panels/sql.html"
126130
127131 @classmethod
128132 def get_urls(cls):
129133 return [
130 url(r"^sql_select/$", views.sql_select, name="sql_select"),
131 url(r"^sql_explain/$", views.sql_explain, name="sql_explain"),
132 url(r"^sql_profile/$", views.sql_profile, name="sql_profile"),
134 path("sql_select/", views.sql_select, name="sql_select"),
135 path("sql_explain/", views.sql_explain, name="sql_explain"),
136 path("sql_profile/", views.sql_profile, name="sql_profile"),
133137 ]
134138
135139 def enable_instrumentation(self):
207211 query["vendor"], query["trans_status"]
208212 )
209213
210 query["form"] = SQLSelectForm(auto_id=None, initial=copy(query))
214 query["form"] = SignedDataForm(
215 auto_id=None, initial=SQLSelectForm(initial=copy(query)).initial
216 )
211217
212218 if query["sql"]:
213219 query["sql"] = reformat_sql(query["sql"], with_toggle=True)
214220 query["rgb_color"] = self._databases[alias]["rgb_color"]
215221 try:
216222 query["width_ratio"] = (query["duration"] / self._sql_time) * 100
217 query["width_ratio_relative"] = (
218 100.0 * query["width_ratio"] / (100.0 - width_ratio_tally)
219 )
220223 except ZeroDivisionError:
221224 query["width_ratio"] = 0
222 query["width_ratio_relative"] = 0
223225 query["start_offset"] = width_ratio_tally
224226 query["end_offset"] = query["width_ratio"] + query["start_offset"]
225227 width_ratio_tally += query["width_ratio"]
22 from threading import local
33 from time import time
44
5 from django.utils.encoding import force_text
5 from django.utils.encoding import force_str
66
77 from debug_toolbar import settings as dt_settings
88 from debug_toolbar.utils import get_stack, get_template_info, tidy_stacktrace
9
10 try:
11 from psycopg2._json import Json as PostgresJson
12 except ImportError:
13 PostgresJson = None
914
1015
1116 class SQLQueryTriggered(Exception):
104109 return [self._quote_expr(p) for p in params]
105110
106111 def _decode(self, param):
112 if PostgresJson and isinstance(param, PostgresJson):
113 return param.dumps(param.adapted)
107114 # If a sequence type, decode each element separately
108115 if isinstance(param, (tuple, list)):
109116 return [self._decode(element) for element in param]
112119 if isinstance(param, dict):
113120 return {key: self._decode(value) for key, value in param.items()}
114121
115 # make sure datetime, date and time are converted to string by force_text
122 # make sure datetime, date and time are converted to string by force_str
116123 CONVERT_TYPES = (datetime.datetime, datetime.date, datetime.time)
117124 try:
118 return force_text(param, strings_only=not isinstance(param, CONVERT_TYPES))
125 return force_str(param, strings_only=not isinstance(param, CONVERT_TYPES))
119126 except UnicodeDecodeError:
120127 return "(encoded string)"
121128
135142 _params = json.dumps(self._decode(params))
136143 except TypeError:
137144 pass # object not JSON serializable
138
139145 template_info = get_template_info()
140146
141147 alias = getattr(self.db, "alias", "default")
22 import sqlparse
33 from django.utils.html import escape
44 from sqlparse import tokens as T
5
6 from debug_toolbar import settings as dt_settings
57
68
79 class BoldKeywordFilter:
2325 if not with_toggle:
2426 return formatted
2527 simple = simplify(parse_sql(sql, aligned_indent=False))
26 uncollapsed = '<span class="djDebugUncollapsed" href="#">{}</span>'.format(simple)
27 collapsed = '<span class="djDebugCollapsed" href="#">{}</span>'.format(formatted)
28 uncollapsed = '<span class="djDebugUncollapsed">{}</span>'.format(simple)
29 collapsed = '<span class="djDebugCollapsed djdt-hidden">{}</span>'.format(formatted)
2830 return collapsed + uncollapsed
2931
3032
3133 def parse_sql(sql, aligned_indent=False):
3234 stack = sqlparse.engine.FilterStack()
33 stack.enable_grouping()
35 if dt_settings.get_config()["PRETTIFY_SQL"]:
36 stack.enable_grouping()
3437 if aligned_indent:
3538 stack.stmtprocess.append(
3639 sqlparse.filters.AlignedIndentFilter(char="&nbsp;", n="<br/>")
0 from django.http import HttpResponseBadRequest
1 from django.template.response import SimpleTemplateResponse
0 from django.http import HttpResponseBadRequest, JsonResponse
1 from django.template.loader import render_to_string
22 from django.views.decorators.csrf import csrf_exempt
33
4 from debug_toolbar.decorators import require_show_toolbar
4 from debug_toolbar.decorators import require_show_toolbar, signed_data_view
55 from debug_toolbar.panels.sql.forms import SQLSelectForm
66
77
88 @csrf_exempt
99 @require_show_toolbar
10 def sql_select(request):
10 @signed_data_view
11 def sql_select(request, verified_data):
1112 """Returns the output of the SQL SELECT statement"""
12 form = SQLSelectForm(request.POST or None)
13 form = SQLSelectForm(verified_data)
1314
1415 if form.is_valid():
1516 sql = form.cleaned_data["raw_sql"]
1617 params = form.cleaned_data["params"]
17 cursor = form.cursor
18 cursor.execute(sql, params)
19 headers = [d[0] for d in cursor.description]
20 result = cursor.fetchall()
21 cursor.close()
18 with form.cursor as cursor:
19 cursor.execute(sql, params)
20 headers = [d[0] for d in cursor.description]
21 result = cursor.fetchall()
22
2223 context = {
2324 "result": result,
2425 "sql": form.reformat_sql(),
2627 "headers": headers,
2728 "alias": form.cleaned_data["alias"],
2829 }
29 # Using SimpleTemplateResponse avoids running global context processors.
30 return SimpleTemplateResponse("debug_toolbar/panels/sql_select.html", context)
30 content = render_to_string("debug_toolbar/panels/sql_select.html", context)
31 return JsonResponse({"content": content})
3132 return HttpResponseBadRequest("Form errors")
3233
3334
3435 @csrf_exempt
3536 @require_show_toolbar
36 def sql_explain(request):
37 @signed_data_view
38 def sql_explain(request, verified_data):
3739 """Returns the output of the SQL EXPLAIN on the given query"""
38 form = SQLSelectForm(request.POST or None)
40 form = SQLSelectForm(verified_data)
3941
4042 if form.is_valid():
4143 sql = form.cleaned_data["raw_sql"]
4244 params = form.cleaned_data["params"]
4345 vendor = form.connection.vendor
44 cursor = form.cursor
46 with form.cursor as cursor:
47 if vendor == "sqlite":
48 # SQLite's EXPLAIN dumps the low-level opcodes generated for a query;
49 # EXPLAIN QUERY PLAN dumps a more human-readable summary
50 # See https://www.sqlite.org/lang_explain.html for details
51 cursor.execute("EXPLAIN QUERY PLAN {}".format(sql), params)
52 elif vendor == "postgresql":
53 cursor.execute("EXPLAIN ANALYZE {}".format(sql), params)
54 else:
55 cursor.execute("EXPLAIN {}".format(sql), params)
56 headers = [d[0] for d in cursor.description]
57 result = cursor.fetchall()
4558
46 if vendor == "sqlite":
47 # SQLite's EXPLAIN dumps the low-level opcodes generated for a query;
48 # EXPLAIN QUERY PLAN dumps a more human-readable summary
49 # See https://www.sqlite.org/lang_explain.html for details
50 cursor.execute("EXPLAIN QUERY PLAN {}".format(sql), params)
51 elif vendor == "postgresql":
52 cursor.execute("EXPLAIN ANALYZE {}".format(sql), params)
53 else:
54 cursor.execute("EXPLAIN {}".format(sql), params)
55
56 headers = [d[0] for d in cursor.description]
57 result = cursor.fetchall()
58 cursor.close()
5959 context = {
6060 "result": result,
6161 "sql": form.reformat_sql(),
6363 "headers": headers,
6464 "alias": form.cleaned_data["alias"],
6565 }
66 # Using SimpleTemplateResponse avoids running global context processors.
67 return SimpleTemplateResponse("debug_toolbar/panels/sql_explain.html", context)
66 content = render_to_string("debug_toolbar/panels/sql_explain.html", context)
67 return JsonResponse({"content": content})
6868 return HttpResponseBadRequest("Form errors")
6969
7070
7171 @csrf_exempt
7272 @require_show_toolbar
73 def sql_profile(request):
73 @signed_data_view
74 def sql_profile(request, verified_data):
7475 """Returns the output of running the SQL and getting the profiling statistics"""
75 form = SQLSelectForm(request.POST or None)
76 form = SQLSelectForm(verified_data)
7677
7778 if form.is_valid():
7879 sql = form.cleaned_data["raw_sql"]
7980 params = form.cleaned_data["params"]
80 cursor = form.cursor
8181 result = None
8282 headers = None
8383 result_error = None
84 try:
85 cursor.execute("SET PROFILING=1") # Enable profiling
86 cursor.execute(sql, params) # Execute SELECT
87 cursor.execute("SET PROFILING=0") # Disable profiling
88 # The Query ID should always be 1 here but I'll subselect to get
89 # the last one just in case...
90 cursor.execute(
91 """
92 SELECT *
93 FROM information_schema.profiling
94 WHERE query_id = (
95 SELECT query_id
96 FROM information_schema.profiling
97 ORDER BY query_id DESC
98 LIMIT 1
99 )
100 """
101 )
102 headers = [d[0] for d in cursor.description]
103 result = cursor.fetchall()
104 except Exception:
105 result_error = (
106 "Profiling is either not available or not supported by your database."
107 )
108 cursor.close()
84 with form.cursor as cursor:
85 try:
86 cursor.execute("SET PROFILING=1") # Enable profiling
87 cursor.execute(sql, params) # Execute SELECT
88 cursor.execute("SET PROFILING=0") # Disable profiling
89 # The Query ID should always be 1 here but I'll subselect to get
90 # the last one just in case...
91 cursor.execute(
92 """
93 SELECT *
94 FROM information_schema.profiling
95 WHERE query_id = (
96 SELECT query_id
97 FROM information_schema.profiling
98 ORDER BY query_id DESC
99 LIMIT 1
100 )
101 """
102 )
103 headers = [d[0] for d in cursor.description]
104 result = cursor.fetchall()
105 except Exception:
106 result_error = (
107 "Profiling is either not available or not supported by your "
108 "database."
109 )
110
109111 context = {
110112 "result": result,
111113 "result_error": result_error,
114116 "headers": headers,
115117 "alias": form.cleaned_data["alias"],
116118 }
117 # Using SimpleTemplateResponse avoids running global context processors.
118 return SimpleTemplateResponse("debug_toolbar/panels/sql_profile.html", context)
119 content = render_to_string("debug_toolbar/panels/sql_profile.html", context)
120 return JsonResponse({"content": content})
119121 return HttpResponseBadRequest("Form errors")
22
33 from django.conf import settings
44 from django.contrib.staticfiles import finders, storage
5 from django.core.checks import Warning
56 from django.core.files.storage import get_storage_class
67 from django.utils.functional import LazyObject
78 from django.utils.translation import gettext_lazy as _, ngettext as __
9899
99100 @property
100101 def num_used(self):
101 return len(self._paths[threading.currentThread()])
102 stats = self.get_stats()
103 return stats and stats["num_used"]
102104
103105 nav_title = _("Static files")
104106
120122 self.record_stats(
121123 {
122124 "num_found": self.num_found,
123 "num_used": self.num_used,
125 "num_used": len(used_paths),
124126 "staticfiles": used_paths,
125127 "staticfiles_apps": self.get_staticfiles_apps(),
126128 "staticfiles_dirs": self.get_staticfiles_dirs(),
136138 """
137139 finders_mapping = OrderedDict()
138140 for finder in finders.get_finders():
139 for path, finder_storage in finder.list([]):
140 if getattr(finder_storage, "prefix", None):
141 prefixed_path = join(finder_storage.prefix, path)
142 else:
143 prefixed_path = path
144 finder_cls = finder.__class__
145 finder_path = ".".join([finder_cls.__module__, finder_cls.__name__])
146 real_path = finder_storage.path(path)
147 payload = (prefixed_path, real_path)
148 finders_mapping.setdefault(finder_path, []).append(payload)
149 self.num_found += 1
141 try:
142 for path, finder_storage in finder.list([]):
143 if getattr(finder_storage, "prefix", None):
144 prefixed_path = join(finder_storage.prefix, path)
145 else:
146 prefixed_path = path
147 finder_cls = finder.__class__
148 finder_path = ".".join([finder_cls.__module__, finder_cls.__name__])
149 real_path = finder_storage.path(path)
150 payload = (prefixed_path, real_path)
151 finders_mapping.setdefault(finder_path, []).append(payload)
152 self.num_found += 1
153 except OSError:
154 # This error should be captured and presented as a part of run_checks.
155 pass
150156 return finders_mapping
151157
152158 def get_staticfiles_dirs(self):
170176 if app not in apps:
171177 apps.append(app)
172178 return apps
179
180 @classmethod
181 def run_checks(cls):
182 """
183 Check that the integration is configured correctly for the panel.
184
185 Specifically look for static files that haven't been collected yet.
186
187 Return a list of :class: `django.core.checks.CheckMessage` instances.
188 """
189 errors = []
190 for finder in finders.get_finders():
191 try:
192 for path, finder_storage in finder.list([]):
193 finder_storage.path(path)
194 except OSError:
195 errors.append(
196 Warning(
197 "debug_toolbar requires the STATICFILES_DIRS directories to exist.",
198 hint="Running manage.py collectstatic may help uncover the issue.",
199 id="debug_toolbar.staticfiles.W001",
200 )
201 )
202 return errors
33 from pprint import pformat, saferepr
44
55 from django import http
6 from django.conf.urls import url
76 from django.core import signing
87 from django.db.models.query import QuerySet, RawQuerySet
98 from django.template import RequestContext, Template
109 from django.test.signals import template_rendered
1110 from django.test.utils import instrumented_test_render
11 from django.urls import path
1212 from django.utils.translation import gettext_lazy as _
1313
1414 from debug_toolbar.panels import Panel
6666 def __init__(self, *args, **kwargs):
6767 super().__init__(*args, **kwargs)
6868 self.templates = []
69 # Refs GitHub issue #910
70 # Hold a series of seen dictionaries within Contexts. A dictionary is
71 # considered seen if it is `in` this list, requiring that the __eq__
72 # for the dictionary matches. If *anything* in the dictionary is
73 # different it is counted as a new layer.
74 self.seen_layers = []
75 # Holds all dictionaries which have been prettified for output.
76 # This should align with the seen_layers such that an index here is
77 # the same as the index there.
69 # An associated list of dictionaries and their prettified
70 # representation.
7871 self.pformat_layers = []
7972
8073 def _store_template_info(self, sender, **kwargs):
9386 context_list = []
9487 for context_layer in context.dicts:
9588 if hasattr(context_layer, "items") and context_layer:
96 # Refs GitHub issue #910
97 # If we can find this layer in our pseudo-cache then find the
98 # matching prettified version in the associated list.
99 key_values = sorted(context_layer.items())
100 if key_values in self.seen_layers:
101 index = self.seen_layers.index(key_values)
102 pformatted = self.pformat_layers[index]
103 context_list.append(pformatted)
104 else:
89 # Check if the layer is in the cache.
90 pformatted = None
91 for key_values, _pformatted in self.pformat_layers:
92 if key_values == context_layer:
93 pformatted = _pformatted
94 break
95
96 if pformatted is None:
10597 temp_layer = {}
10698 for key, value in context_layer.items():
10799 # Replace any request elements - they have a large
108 # unicode representation and the request data is
100 # Unicode representation and the request data is
109101 # already made available from the Request panel.
110102 if isinstance(value, http.HttpRequest):
111103 temp_layer[key] = "<<request>>"
120112 # QuerySet would trigger the database: user can run the
121113 # query from SQL Panel
122114 elif isinstance(value, (QuerySet, RawQuerySet)):
123 model_name = "{}.{}".format(
124 value.model._meta.app_label, value.model.__name__
125 )
126115 temp_layer[key] = "<<{} of {}>>".format(
127 value.__class__.__name__.lower(), model_name
116 value.__class__.__name__.lower(),
117 value.model._meta.label,
128118 )
129119 else:
120 recording(False)
130121 try:
131 recording(False)
132122 saferepr(value) # this MAY trigger a db query
133123 except SQLQueryTriggered:
134124 temp_layer[key] = "<<triggers database query>>"
135125 except UnicodeEncodeError:
136 temp_layer[key] = "<<unicode encode error>>"
126 temp_layer[key] = "<<Unicode encode error>>"
137127 except Exception:
138128 temp_layer[key] = "<<unhandled exception>>"
139129 else:
140130 temp_layer[key] = value
141131 finally:
142132 recording(True)
143 # Execute pformat first - if for some reason pformat/repr
144 # causes more templates to be rendered, seen/pformat layers
145 # will still be consistent
146133 pformatted = pformat(temp_layer)
147 # Refs GitHub issue #910
148 # If we've not seen the layer before then we will add it
149 # so that if we see it again we can skip formatting it.
150 self.seen_layers.append(key_values)
151 # Note: this *ought* to be len(...) - 1 but let's be safe.
152 index = self.seen_layers.index(key_values)
153 # Note: this *ought* to be len(...) - 1 but let's be safe.
154 self.pformat_layers.insert(index, pformatted)
155 context_list.append(pformatted)
134 self.pformat_layers.append((context_layer, pformatted))
135 context_list.append(pformatted)
156136
157137 kwargs["context"] = context_list
158138 kwargs["context_processors"] = getattr(context, "context_processors", None)
179159
180160 @classmethod
181161 def get_urls(cls):
182 return [
183 url(r"^template_source/$", views.template_source, name="template_source")
184 ]
162 return [path("template_source/", views.template_source, name="template_source")]
185163
186164 def enable_instrumentation(self):
187165 template_rendered.connect(self._store_template_info)
194172 for template_data in self.templates:
195173 info = {}
196174 # Clean up some info about templates
197 template = template_data.get("template", None)
175 template = template_data["template"]
198176 if hasattr(template, "origin") and template.origin and template.origin.name:
199177 template.origin_name = template.origin.name
200178 template.origin_hash = signing.dumps(template.origin.name)
00 from django.core import signing
1 from django.http import HttpResponseBadRequest
1 from django.http import HttpResponseBadRequest, JsonResponse
22 from django.template import Origin, TemplateDoesNotExist
33 from django.template.engine import Engine
4 from django.template.response import SimpleTemplateResponse
4 from django.template.loader import render_to_string
55 from django.utils.safestring import mark_safe
66
77 from debug_toolbar.decorators import require_show_toolbar
1313 Return the source of a template, syntax-highlighted by Pygments if
1414 it's available.
1515 """
16 template_origin_name = request.GET.get("template_origin", None)
16 template_origin_name = request.GET.get("template_origin")
1717 if template_origin_name is None:
1818 return HttpResponseBadRequest('"template_origin" key is required')
1919 try:
4747
4848 try:
4949 from pygments import highlight
50 from pygments.formatters import HtmlFormatter
5051 from pygments.lexers import HtmlDjangoLexer
51 from pygments.formatters import HtmlFormatter
5252
5353 source = highlight(source, HtmlDjangoLexer(), HtmlFormatter())
5454 source = mark_safe(source)
5656 except ImportError:
5757 pass
5858
59 # Using SimpleTemplateResponse avoids running global context processors.
60 return SimpleTemplateResponse(
59 content = render_to_string(
6160 "debug_toolbar/panels/template_source.html",
6261 {"source": source, "template_name": template_name},
6362 )
63 return JsonResponse({"content": content})
00 import time
11
22 from django.template.loader import render_to_string
3 from django.templatetags.static import static
34 from django.utils.translation import gettext_lazy as _
45
56 from debug_toolbar.panels import Panel
5051 )
5152 return render_to_string(self.template, {"rows": rows})
5253
54 @property
55 def scripts(self):
56 scripts = super().scripts
57 scripts.append(static("debug_toolbar/js/timer.js"))
58 return scripts
59
5360 def process_request(self, request):
5461 self._start_time = time.time()
5562 if self.has_content:
1616 },
1717 "INSERT_BEFORE": "</body>",
1818 "RENDER_PANELS": None,
19 "RESULTS_CACHE_SIZE": 10,
19 "RESULTS_CACHE_SIZE": 25,
2020 "ROOT_TAG_EXTRA_ATTRS": "",
2121 "SHOW_COLLAPSED": False,
2222 "SHOW_TOOLBAR_CALLBACK": "debug_toolbar.middleware.show_toolbar",
2323 # Panel options
2424 "EXTRA_SIGNALS": [],
2525 "ENABLE_STACKTRACES": True,
26 "ENABLE_STACKTRACES_LOCALS": False,
2627 "HIDE_IN_STACKTRACES": (
2728 "socketserver",
2829 "threading",
3536 "django.utils.deprecation",
3637 "django.utils.functional",
3738 ),
39 "PRETTIFY_SQL": True,
3840 "PROFILER_MAX_DEPTH": 10,
3941 "SHOW_TEMPLATE_CONTEXT": True,
4042 "SKIP_TEMPLATE_PREFIXES": ("django/forms/widgets/", "admin/widgets/"),
5153
5254
5355 PANELS_DEFAULTS = [
56 "debug_toolbar.panels.history.HistoryPanel",
5457 "debug_toolbar.panels.versions.VersionsPanel",
5558 "debug_toolbar.panels.timer.TimerPanel",
5659 "debug_toolbar.panels.settings.SettingsPanel",
00 #djDebug {
1 display: none !important;
1 display: none !important;
22 }
0 /* http://www.positioniseverything.net/easyclearing.html */
1 #djDebug .djdt-clearfix:after {
2 content: ".";
3 display: block;
4 height: 0;
5 clear: both;
6 visibility: hidden;
7 }
8 #djDebug .djdt-clearfix {display: inline-block;}
9 /* Hides from IE-mac \*/
10 #djDebug .djdt-clearfix {display: block;}
11 * html #djDebug .djdt-clearfix {height: 1%;}
12 /* end hide from IE-mac */
13
140 /* Debug Toolbar CSS Reset, adapted from Eric Meyer's CSS Reset */
15 #djDebug {color:#000;background:#FFF;}
16 #djDebug, #djDebug div, #djDebug span, #djDebug applet, #djDebug object, #djDebug iframe,
17 #djDebug h1, #djDebug h2, #djDebug h3, #djDebug h4, #djDebug h5, #djDebug h6, #djDebug p, #djDebug blockquote, #djDebug pre,
18 #djDebug a, #djDebug abbr, #djDebug acronym, #djDebug address, #djDebug big, #djDebug cite, #djDebug code,
19 #djDebug del, #djDebug dfn, #djDebug em, #djDebug font, #djDebug img, #djDebug ins, #djDebug kbd, #djDebug q, #djDebug s, #djDebug samp,
20 #djDebug small, #djDebug strike, #djDebug strong, #djDebug sub, #djDebug sup, #djDebug tt, #djDebug var,
21 #djDebug b, #djDebug u, #djDebug i, #djDebug center,
22 #djDebug dl, #djDebug dt, #djDebug dd, #djDebug ol, #djDebug ul, #djDebug li,
23 #djDebug fieldset, #djDebug form, #djDebug label, #djDebug legend,
24 #djDebug table, #djDebug caption, #djDebug tbody, #djDebug tfoot, #djDebug thead, #djDebug tr, #djDebug th, #djDebug td,
1 #djDebug {
2 color: #000;
3 background: #fff;
4 }
5 #djDebug,
6 #djDebug div,
7 #djDebug span,
8 #djDebug applet,
9 #djDebug object,
10 #djDebug iframe,
11 #djDebug h1,
12 #djDebug h2,
13 #djDebug h3,
14 #djDebug h4,
15 #djDebug h5,
16 #djDebug h6,
17 #djDebug p,
18 #djDebug blockquote,
19 #djDebug pre,
20 #djDebug a,
21 #djDebug abbr,
22 #djDebug acronym,
23 #djDebug address,
24 #djDebug big,
25 #djDebug cite,
26 #djDebug code,
27 #djDebug del,
28 #djDebug dfn,
29 #djDebug em,
30 #djDebug font,
31 #djDebug img,
32 #djDebug ins,
33 #djDebug kbd,
34 #djDebug q,
35 #djDebug s,
36 #djDebug samp,
37 #djDebug small,
38 #djDebug strike,
39 #djDebug strong,
40 #djDebug sub,
41 #djDebug sup,
42 #djDebug tt,
43 #djDebug var,
44 #djDebug b,
45 #djDebug u,
46 #djDebug i,
47 #djDebug center,
48 #djDebug dl,
49 #djDebug dt,
50 #djDebug dd,
51 #djDebug ol,
52 #djDebug ul,
53 #djDebug li,
54 #djDebug fieldset,
55 #djDebug form,
56 #djDebug label,
57 #djDebug legend,
58 #djDebug table,
59 #djDebug caption,
60 #djDebug tbody,
61 #djDebug tfoot,
62 #djDebug thead,
63 #djDebug tr,
64 #djDebug th,
65 #djDebug td,
2566 #djDebug button {
26 margin:0;
27 padding:0;
28 min-width:0;
29 width:auto;
30 border:0;
31 outline:0;
32 font-size:12px;
33 line-height:1.5em;
34 color:#000;
35 vertical-align:baseline;
36 background-color:transparent;
37 font-family:sans-serif;
38 text-align:left;
39 text-shadow: none;
40 white-space: normal;
41 -webkit-transition: none;
42 -moz-transition: none;
43 -o-transition: none;
67 margin: 0;
68 padding: 0;
69 min-width: 0;
70 width: auto;
71 border: 0;
72 outline: 0;
73 font-size: 12px;
74 line-height: 1.5em;
75 color: #000;
76 vertical-align: baseline;
77 background-color: transparent;
78 font-family: sans-serif;
79 text-align: left;
80 text-shadow: none;
81 white-space: normal;
4482 transition: none;
4583 }
4684
47 #djDebug button, #djDebug a.button {
48 background-color: #eee;
49 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #eee), color-stop(100%, #cccccc));
50 background-image: -webkit-linear-gradient(top, #eee, #cccccc);
51 background-image: -moz-linear-gradient(top, #eee, #cccccc);
52 background-image: -ms-linear-gradient(top, #eee, #cccccc);
53 background-image: -o-linear-gradient(top, #eee, #cccccc);
54 background-image: linear-gradient(top, #eee, #cccccc);
55 border: 1px solid #ccc;
56 border-bottom: 1px solid #bbb;
57 -webkit-border-radius: 3px;
58 -moz-border-radius: 3px;
59 border-radius: 3px;
60 color: #333;
61 line-height: 1;
62 padding: 0 8px;
63 text-align: center;
64 text-shadow: 0 1px 0 #eee;
65 }
66
67 #djDebug button:hover, #djDebug a.button:hover {
85 #djDebug button {
86 background-color: #eee;
87 background-image: linear-gradient(to bottom, #eee, #cccccc);
88 border: 1px solid #ccc;
89 border-bottom: 1px solid #bbb;
90 border-radius: 3px;
91 color: #333;
92 line-height: 1;
93 padding: 0 8px;
94 text-align: center;
95 text-shadow: 0 1px 0 #eee;
96 }
97
98 #djDebug button:hover {
6899 background-color: #ddd;
69 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ddd), color-stop(100%, #bbb));
70 background-image: -webkit-linear-gradient(top, #ddd, #bbb);
71 background-image: -moz-linear-gradient(top, #ddd, #bbb);
72 background-image: -ms-linear-gradient(top, #ddd, #bbb);
73 background-image: -o-linear-gradient(top, #ddd, #bbb);
74 background-image: linear-gradient(top, #ddd, #bbb);
100 background-image: linear-gradient(to bottom, #ddd, #bbb);
75101 border-color: #bbb;
76102 border-bottom-color: #999;
77103 cursor: pointer;
78104 text-shadow: 0 1px 0 #ddd;
79105 }
80106
81 #djDebug button:active, #djDebug a.button:active {
107 #djDebug button:active {
82108 border: 1px solid #aaa;
83109 border-bottom: 1px solid #888;
84 -webkit-box-shadow: inset 0 0 5px 2px #aaa, 0 1px 0 0 #eee;
85 -moz-box-shadow: inset 0 0 5px 2px #aaa, 0 1px 0 0 #eee;
86110 box-shadow: inset 0 0 5px 2px #aaa, 0 1px 0 0 #eee;
87111 }
88112
89113 #djDebug #djDebugToolbar {
90 background-color:#111;
91 width:200px;
92 z-index:100000000;
93 position:fixed;
94 top:0;
95 bottom:0;
96 right:0;
97 opacity:0.9;
98 overflow-y: auto;
114 background-color: #111;
115 width: 220px;
116 z-index: 100000000;
117 position: fixed;
118 top: 0;
119 bottom: 0;
120 right: 0;
121 opacity: 0.9;
122 overflow-y: auto;
99123 }
100124
101125 #djDebug #djDebugToolbar small {
102 color:#999;
126 color: #999;
103127 }
104128
105129 #djDebug #djDebugToolbar ul {
106 margin:0;
107 padding:0;
108 list-style:none;
130 margin: 0;
131 padding: 0;
132 list-style: none;
109133 }
110134
111135 #djDebug #djDebugToolbar li {
112 border-bottom:1px solid #222;
113 color:#fff;
114 display:block;
115 font-weight:bold;
116 float:none;
117 margin:0;
118 padding:0;
119 position:relative;
120 width:auto;
121 }
122
123 #djDebug #djDebugToolbar input[type=checkbox] {
124 float: right;
125 margin: 10px;
126 }
127
128 #djDebug #djDebugToolbar li>a,
129 #djDebug #djDebugToolbar li>div.djdt-contentless {
130 font-weight:normal;
131 font-style:normal;
132 text-decoration:none;
133 display:block;
134 font-size:16px;
135 padding:10px 10px 5px 25px;
136 color:#fff;
137 }
138 #djDebug #djDebugToolbar li>div.djdt-disabled {
139 font-style: italic;
140 color: #999;
136 border-bottom: 1px solid #222;
137 color: #fff;
138 display: block;
139 font-weight: bold;
140 float: none;
141 margin: 0;
142 padding: 0;
143 position: relative;
144 width: auto;
145 }
146
147 #djDebug #djDebugToolbar input[type="checkbox"] {
148 float: right;
149 margin: 10px;
150 }
151
152 #djDebug #djDebugToolbar li > a,
153 #djDebug #djDebugToolbar li > div.djdt-contentless {
154 font-weight: normal;
155 font-style: normal;
156 text-decoration: none;
157 display: block;
158 font-size: 16px;
159 padding: 10px 10px 5px 25px;
160 color: #fff;
161 }
162 #djDebug #djDebugToolbar li > div.djdt-disabled {
163 font-style: italic;
164 color: #999;
141165 }
142166
143167 #djDebug #djDebugToolbar li a:hover {
144 color:#111;
145 background-color:#ffc;
168 color: #111;
169 background-color: #ffc;
146170 }
147171
148172 #djDebug #djDebugToolbar li.djdt-active {
149 background: #333 no-repeat left center;
150 background-image: url(../img/indicator.png);
151 padding-left:10px;
173 background: #333;
174 }
175
176 #djDebug #djDebugToolbar li.djdt-active:before {
177 content: "ā–¶";
178 position: absolute;
179 left: 0;
180 top: 50%;
181 transform: translateY(-50%);
182 color: #eee;
183 font-size: 150%;
152184 }
153185
154186 #djDebug #djDebugToolbar li.djdt-active a:hover {
155 color:#b36a60;
156 background-color:transparent;
187 color: #b36a60;
188 background-color: transparent;
157189 }
158190
159191 #djDebug #djDebugToolbar li small {
160 font-size:12px;
161 color:#999;
162 font-style:normal;
163 text-decoration:none;
164 font-variant:small-caps;
192 font-size: 12px;
193 color: #999;
194 font-style: normal;
195 text-decoration: none;
165196 }
166197
167198 #djDebug #djDebugToolbarHandle {
168 position:fixed;
169 background-color:#fff;
170 border:1px solid #111;
171 top:30px;
172 right:0;
173 z-index:100000000;
174 opacity:0.75;
199 position: fixed;
200 transform: rotate(-90deg);
201 transform-origin: right bottom;
202 background-color: #fff;
203 border: 1px solid #111;
204 border-bottom: 0;
205 top: 0;
206 right: 0;
207 z-index: 100000000;
208 opacity: 0.75;
175209 }
176210
177211 #djDebug #djShowToolBarButton {
178 display:block;
179 height:75px;
180 width:30px;
181 border-right:none;
182 border-bottom:4px solid #fff;
183 border-top:4px solid #fff;
184 border-left:4px solid #fff;
185 color:#fff;
186 font-size:10px;
187 font-weight:bold;
188 text-decoration:none;
189 text-align:center;
190 text-indent:-999999px;
191 background: #000 no-repeat left center;
192 background-image: url(../img/djdt_vertical.png);
193 opacity:0.5;
212 padding: 0 5px;
213 border: 4px solid #fff;
214 border-bottom-width: 0;
215 color: #fff;
216 font-size: 22px;
217 font-weight: bold;
218 background: #000;
219 opacity: 0.5;
194220 }
195221
196222 #djDebug #djShowToolBarButton:hover {
197 background-color:#111;
198 border-top-color:#FFE761;
199 border-left-color:#FFE761;
200 border-bottom-color:#FFE761;
201 cursor:move;
202 opacity:1.0;
223 background-color: #111;
224 border-color: #ffe761;
225 cursor: move;
226 opacity: 1;
227 }
228
229 #djDebug #djShowToolBarD {
230 color: #cf9;
231 font-size: 22px;
232 }
233
234 #djDebug #djShowToolBarJ {
235 color: #cf9;
236 font-size: 16px;
203237 }
204238
205239 #djDebug code {
206 display:block;
207 font-family:Consolas, Monaco, "Bitstream Vera Sans Mono", "Lucida Console", monospace;
208 font-size: 12px;
209 white-space:pre;
210 overflow:auto;
211 }
212
213 #djDebug .djDebugOdd {
214 background-color:#f5f5f5;
240 display: block;
241 font-family: Consolas, Monaco, "Bitstream Vera Sans Mono", "Lucida Console",
242 monospace;
243 font-size: 12px;
244 white-space: pre;
245 overflow: auto;
215246 }
216247
217248 #djDebug .djdt-panelContent {
218 display:none;
219 position:fixed;
220 margin:0;
221 top:0;
222 right:200px;
223 bottom:0;
224 left:0px;
225 background-color:#eee;
226 color:#666;
227 z-index:100000000;
249 position: fixed;
250 margin: 0;
251 top: 0;
252 right: 220px;
253 bottom: 0;
254 left: 0px;
255 background-color: #eee;
256 color: #666;
257 z-index: 100000000;
228258 }
229259
230260 #djDebug .djdt-panelContent > div {
231 border-bottom:1px solid #ddd;
261 border-bottom: 1px solid #ddd;
232262 }
233263
234264 #djDebug .djDebugPanelTitle {
235 position:absolute;
236 background-color:#ffc;
237 color:#666;
238 padding-left:20px;
239 top:0;
240 right:0;
241 left:0;
242 height:50px;
265 position: absolute;
266 background-color: #ffc;
267 color: #666;
268 padding-left: 20px;
269 top: 0;
270 right: 0;
271 left: 0;
272 height: 50px;
243273 }
244274
245275 #djDebug .djDebugPanelTitle code {
246 display:inline;
247 font-size:inherit;
276 display: inline;
277 font-size: inherit;
248278 }
249279
250280 #djDebug .djDebugPanelContent {
251 position:absolute;
252 top:50px;
253 right:0;
254 bottom:0;
255 left:0;
256 height:auto;
257 padding:5px 0 0 20px;
281 position: absolute;
282 top: 50px;
283 right: 0;
284 bottom: 0;
285 left: 0;
286 height: auto;
287 padding: 5px 0 0 20px;
258288 }
259289
260290 #djDebug .djDebugPanelContent .djdt-loader {
261 display:block;
262 margin:80px auto;
291 margin: 80px auto;
292 border: 6px solid white;
293 border-radius: 50%;
294 border-top: 6px solid #ffe761;
295 width: 38px;
296 height: 38px;
297 animation: spin 2s linear infinite;
298 }
299
300 @keyframes spin {
301 0% {
302 transform: rotate(0deg);
303 }
304 100% {
305 transform: rotate(360deg);
306 }
263307 }
264308
265309 #djDebug .djDebugPanelContent .djdt-scroll {
266 height:100%;
267 overflow:auto;
268 display:block;
269 padding:0 10px 0 0;
310 height: 100%;
311 overflow: auto;
312 display: block;
313 padding: 0 10px 0 0;
270314 }
271315
272316 #djDebug h3 {
273 font-size:24px;
274 font-weight:normal;
275 line-height:50px;
317 font-size: 24px;
318 font-weight: normal;
319 line-height: 50px;
276320 }
277321
278322 #djDebug h4 {
279 font-size:20px;
280 font-weight:bold;
281 margin-top:0.8em;
323 font-size: 20px;
324 font-weight: bold;
325 margin-top: 0.8em;
282326 }
283327
284328 #djDebug .djdt-panelContent table {
285 border:1px solid #ccc;
286 border-collapse:collapse;
287 width:100%;
288 background-color:#fff;
289 display:table;
290 margin-top:0.8em;
291 overflow: auto;
329 border: 1px solid #ccc;
330 border-collapse: collapse;
331 width: 100%;
332 background-color: #fff;
333 display: table;
334 margin-top: 0.8em;
335 overflow: auto;
336 }
337 #djDebug .djdt-panelContent tbody > tr:nth-child(odd) {
338 background-color: #f5f5f5;
292339 }
293340 #djDebug .djdt-panelContent tbody td,
294341 #djDebug .djdt-panelContent tbody th {
295 vertical-align:top;
296 padding:2px 3px;
342 vertical-align: top;
343 padding: 2px 3px;
297344 }
298345 #djDebug .djdt-panelContent tbody td.djdt-time {
299346 text-align: center;
300347 }
301348
302349 #djDebug .djdt-panelContent thead th {
303 padding:1px 6px 1px 3px;
304 text-align:left;
305 font-weight:bold;
306 font-size:14px;
350 padding: 1px 6px 1px 3px;
351 text-align: left;
352 font-weight: bold;
353 font-size: 14px;
307354 white-space: nowrap;
308355 }
309356 #djDebug .djdt-panelContent tbody th {
310 width:12em;
311 text-align:right;
312 color:#666;
313 padding-right:.5em;
357 width: 12em;
358 text-align: right;
359 color: #666;
360 padding-right: 0.5em;
314361 }
315362
316363 #djDebug .djTemplateContext {
317 background-color:#fff;
318 }
319
320 /*
321 #djDebug .djdt-panelContent p a:hover, #djDebug .djdt-panelContent dd a:hover {
322 color:#111;
323 background-color:#ffc;
324 }
325
326 #djDebug .djdt-panelContent p {
327 padding:0 5px;
328 }
329
330 #djDebug .djdt-panelContent p, #djDebug .djdt-panelContent table, #djDebug .djdt-panelContent ol, #djDebug .djdt-panelContent ul, #djDebug .djdt-panelContent dl {
331 margin:5px 0 15px;
332 background-color:#fff;
333 }
334 #djDebug .djdt-panelContent table {
335 clear:both;
336 border:0;
337 padding:0;
338 margin:0;
339 border-collapse:collapse;
340 border-spacing:0;
341 }
342
343 #djDebug .djdt-panelContent table a {
344 color:#000;
345 padding:2px 4px;
346 }
347 #djDebug .djdt-panelContent table a:hover {
348 background-color:#ffc;
349 }
350
351 #djDebug .djdt-panelContent table th {
352 background-color:#333;
353 font-weight:bold;
354 color:#fff;
355 padding:3px 7px 3px;
356 text-align:left;
357 cursor:pointer;
358 }
359 #djDebug .djdt-panelContent table td {
360 padding:5px 10px;
361 font-size:14px;
362 background-color:#fff;
363 color:#000;
364 vertical-align:top;
365 border:0;
366 }
367 #djDebug .djdt-panelContent table tr.djDebugOdd td {
368 background-color:#eee;
369 }
370 */
364 background-color: #fff;
365 }
371366
372367 #djDebug .djdt-panelContent .djDebugClose {
373 display:block;
374 position:absolute;
375 top:4px;
376 right:15px;
377 height:40px;
378 width:40px;
379 background: no-repeat center center;
380 background-image: url(../img/close.png);
368 position: absolute;
369 top: 4px;
370 right: 15px;
371 height: 16px;
372 width: 16px;
373 line-height: 16px;
374 padding: 5px;
375 border: 6px solid #ddd;
376 border-radius: 50%;
377 background: #fff;
378 color: #ddd;
379 text-align: center;
380 font-weight: 900;
381 font-size: 20px;
382 box-sizing: content-box;
381383 }
382384
383385 #djDebug .djdt-panelContent .djDebugClose:hover {
384 background-image: url(../img/close_hover.png);
385 }
386
387 #djDebug .djdt-panelContent .djDebugClose.djDebugBack {
388 background-image: url(../img/back.png);
389 }
390
391 #djDebug .djdt-panelContent .djDebugClose.djDebugBack:hover {
392 background-image: url(../img/back_hover.png);
393 }
394
395 #djDebug .djdt-panelContent dt, #djDebug .djdt-panelContent dd {
396 display:block;
386 background: #c0695d;
387 }
388
389 #djDebug .djdt-panelContent dt,
390 #djDebug .djdt-panelContent dd {
391 display: block;
397392 }
398393
399394 #djDebug .djdt-panelContent dt {
400 margin-top:0.75em;
395 margin-top: 0.75em;
401396 }
402397
403398 #djDebug .djdt-panelContent dd {
404 margin-left:10px;
399 margin-left: 10px;
405400 }
406401
407402 #djDebug a.toggleTemplate {
408 padding:4px;
409 background-color:#bbb;
410 -webkit-border-radius:3px;
411 -moz-border-radius:3px;
412 border-radius:3px;
403 padding: 4px;
404 background-color: #bbb;
405 border-radius: 3px;
413406 }
414407
415408 #djDebug a.toggleTemplate:hover {
416 padding:4px;
417 background-color:#444;
418 color:#ffe761;
419 -webkit-border-radius:3px;
420 -moz-border-radius:3px;
421 border-radius:3px;
422 }
423
424
425 #djDebug .djDebugSqlWrap {
426 position:relative;
409 padding: 4px;
410 background-color: #444;
411 color: #ffe761;
412 border-radius: 3px;
427413 }
428414
429415 #djDebug .djDebugCollapsed {
430 display: none;
431 text-decoration: none;
432416 color: #333;
433417 }
434418
435419 #djDebug .djDebugUncollapsed {
436420 color: #333;
437 text-decoration: none;
438421 }
439422
440423 #djDebug .djUnselected {
441424 display: none;
442425 }
443 #djDebug tr.djHiddenByDefault {
444 display: none;
445 }
426
446427 #djDebug tr.djSelected {
447428 display: table-row;
448429 }
449430
450431 #djDebug .djDebugSql {
451 word-break:break-word;
452 z-index:100000002;
432 overflow-wrap: anywhere;
453433 }
454434
455435 #djDebug .djSQLDetailsDiv tbody th {
456 text-align: left;
457 }
458
459 #djDebug .djSqlExplain td {
460 white-space: pre;
436 text-align: left;
461437 }
462438
463439 #djDebug span.djDebugLineChart {
464 background-color:#777;
465 height:3px;
466 position:absolute;
467 bottom:0;
468 top:0;
469 left:0;
470 display:block;
471 z-index:1000000001;
440 background-color: #777;
441 height: 3px;
442 position: absolute;
443 bottom: 0;
444 top: 0;
445 left: 0;
446 display: block;
447 z-index: 1000000001;
472448 }
473449 #djDebug span.djDebugLineChartWarning {
474 background-color:#900;
475 }
476
477 #djDebug .highlight { color:#000; }
478 #djDebug .highlight .err { color:#000; } /* Error */
479 #djDebug .highlight .g { color:#000; } /* Generic */
480 #djDebug .highlight .k { color:#000; font-weight:bold } /* Keyword */
481 #djDebug .highlight .o { color:#000; } /* Operator */
482 #djDebug .highlight .n { color:#000; } /* Name */
483 #djDebug .highlight .mi { color:#000; font-weight:bold } /* Literal.Number.Integer */
484 #djDebug .highlight .l { color:#000; } /* Literal */
485 #djDebug .highlight .x { color:#000; } /* Other */
486 #djDebug .highlight .p { color:#000; } /* Punctuation */
487 #djDebug .highlight .m { color:#000; font-weight:bold } /* Literal.Number */
488 #djDebug .highlight .s { color:#333 } /* Literal.String */
489 #djDebug .highlight .w { color:#888888 } /* Text.Whitespace */
490 #djDebug .highlight .il { color:#000; font-weight:bold } /* Literal.Number.Integer.Long */
491 #djDebug .highlight .na { color:#333 } /* Name.Attribute */
492 #djDebug .highlight .nt { color:#000; font-weight:bold } /* Name.Tag */
493 #djDebug .highlight .nv { color:#333 } /* Name.Variable */
494 #djDebug .highlight .s2 { color:#333 } /* Literal.String.Double */
495 #djDebug .highlight .cp { color:#333 } /* Comment.Preproc */
496
497 #djDebug .djdt-timeline {
498 width: 30%;
499 }
500 #djDebug .djDebugTimeline {
501 position: relative;
502 height: 100%;
503 min-height: 100%;
504 }
505 #djDebug div.djDebugLineChart {
506 position: absolute;
507 left: 0;
508 right: 0;
509 top: 0;
510 bottom: 0;
511 vertical-align: middle;
512 }
513 #djDebug div.djDebugLineChart strong {
514 text-indent: -10000em;
515 display: block;
516 font-weight: normal;
517 vertical-align: middle;
518 background-color:#ccc;
519 }
520
521 #djDebug div.djDebugLineChartWarning strong {
522 background-color:#900;
523 }
524
525 #djDebug .djDebugInTransaction div.djDebugLineChart strong {
526 background-color: #d3ff82;
527 }
528 #djDebug .djDebugStartTransaction div.djDebugLineChart strong {
529 border-left: 1px solid #94b24d;
530 }
531 #djDebug .djDebugEndTransaction div.djDebugLineChart strong {
532 border-right: 1px solid #94b24d;
533 }
534
535 #djDebug .djdt-panelContent ul.djdt-stats {
536 position: relative;
537 list-style-type: none;
538 }
539 #djDebug .djdt-panelContent ul.djdt-stats li {
540 width: 30%;
541 float: left;
542 }
543 #djDebug .djdt-panelContent ul.djdt-stats li strong.djdt-label {
544 display: block;
545 }
546 #djDebug .djdt-panelContent ul.djdt-stats li span.djdt-color {
547 height: 12px;
548 width: 3px;
549 display: inline-block;
550 }
551 #djDebug .djdt-panelContent ul.djdt-stats li span.djdt-info {
552 display: block;
553 padding-left: 5px;
554 }
555
556 #djDebug .djdt-panelContent thead th {
557 white-space: nowrap;
558 }
450 background-color: #900;
451 }
452
453 #djDebug .highlight {
454 color: #000;
455 }
456 #djDebug .highlight .err {
457 color: #000;
458 } /* Error */
459 #djDebug .highlight .g {
460 color: #000;
461 } /* Generic */
462 #djDebug .highlight .k {
463 color: #000;
464 font-weight: bold;
465 } /* Keyword */
466 #djDebug .highlight .o {
467 color: #000;
468 } /* Operator */
469 #djDebug .highlight .n {
470 color: #000;
471 } /* Name */
472 #djDebug .highlight .mi {
473 color: #000;
474 font-weight: bold;
475 } /* Literal.Number.Integer */
476 #djDebug .highlight .l {
477 color: #000;
478 } /* Literal */
479 #djDebug .highlight .x {
480 color: #000;
481 } /* Other */
482 #djDebug .highlight .p {
483 color: #000;
484 } /* Punctuation */
485 #djDebug .highlight .m {
486 color: #000;
487 font-weight: bold;
488 } /* Literal.Number */
489 #djDebug .highlight .s {
490 color: #333;
491 } /* Literal.String */
492 #djDebug .highlight .w {
493 color: #888888;
494 } /* Text.Whitespace */
495 #djDebug .highlight .il {
496 color: #000;
497 font-weight: bold;
498 } /* Literal.Number.Integer.Long */
499 #djDebug .highlight .na {
500 color: #333;
501 } /* Name.Attribute */
502 #djDebug .highlight .nt {
503 color: #000;
504 font-weight: bold;
505 } /* Name.Tag */
506 #djDebug .highlight .nv {
507 color: #333;
508 } /* Name.Variable */
509 #djDebug .highlight .s2 {
510 color: #333;
511 } /* Literal.String.Double */
512 #djDebug .highlight .cp {
513 color: #333;
514 } /* Comment.Preproc */
515
516 #djDebug svg.djDebugLineChart {
517 width: 100%;
518 height: 1.5em;
519 }
520
521 #djDebug svg.djDebugLineChartWarning rect {
522 fill: #900;
523 }
524
525 #djDebug svg.djDebugLineChartInTransaction rect {
526 fill: #d3ff82;
527 }
528 #djDebug svg.djDebugLineChart line {
529 stroke: #94b24d;
530 }
531
559532 #djDebug .djDebugRowWarning .djdt-time {
560533 color: red;
561534 }
562 #djdebug .djdt-panelContent table .djdt-toggle {
535 #djDebug .djdt-panelContent table .djdt-toggle {
563536 width: 14px;
564537 padding-top: 3px;
565538 }
567540 min-width: 70px;
568541 white-space: nowrap;
569542 }
570 #djdebug .djdt-panelContent table .djdt-color {
571 width: 3px;
572 }
573 #djdebug .djdt-panelContent table .djdt-color span {
574 width: 3px;
575 height: 12px;
576 overflow: hidden;
543 #djDebug .djdt-color:after {
544 content: "\00a0";
545 }
546 #djDebug .djToggleSwitch {
547 box-sizing: content-box;
577548 padding: 0;
578 }
579 #djDebug .djToggleSwitch {
580 text-decoration: none;
581549 border: 1px solid #999;
582 height: 12px;
550 border-radius: 0;
583551 width: 12px;
584 line-height: 12px;
585 text-align: center;
586552 color: #777;
587 display: inline-block;
588 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFF', endColorstr='#DCDCDC'); /* for IE */
589 background: -webkit-gradient(linear, left top, left bottom, from(#FFF), to(#DCDCDC)); /* for webkit browsers */
590 background:-moz-linear-gradient(center top , #FFFFFF 0pt, #DCDCDC 100%) repeat scroll 0 0 transparent;
553 background: linear-gradient(to bottom, #fff, #dcdcdc);
591554 }
592555 #djDebug .djNoToggleSwitch {
593556 height: 14px;
596559 }
597560
598561 #djDebug .djSQLDetailsDiv {
599 margin-top:0.8em;
562 margin-top: 0.8em;
600563 }
601564 #djDebug pre {
602 white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
603 white-space: -pre-wrap; /* Opera 4-6 */
604 white-space: -o-pre-wrap; /* Opera 7 */
605 white-space: pre-wrap; /* CSS-3 */
606 word-wrap: break-word; /* Internet Explorer 5.5+ */
565 white-space: pre-wrap;
607566 color: #555;
608 border:1px solid #ccc;
609 border-collapse:collapse;
610 background-color:#fff;
611 display:block;
567 border: 1px solid #ccc;
568 border-collapse: collapse;
569 background-color: #fff;
570 display: block;
612571 overflow: auto;
613 padding:2px 3px;
572 padding: 2px 3px;
614573 margin-bottom: 3px;
615 font-family:Consolas, Monaco, "Bitstream Vera Sans Mono", "Lucida Console", monospace;
574 font-family: Consolas, Monaco, "Bitstream Vera Sans Mono", "Lucida Console",
575 monospace;
616576 }
617577 #djDebug .djdt-stack span {
618578 color: #000;
619579 font-weight: bold;
620580 }
621 #djDebug .djdt-stack span.djdt-path {
581 #djDebug .djdt-stack span.djdt-path,
582 #djDebug .djdt-stack pre.djdt-locals,
583 #djDebug .djdt-stack pre.djdt-locals span {
622584 color: #777;
623585 font-weight: normal;
624586 }
625587 #djDebug .djdt-stack span.djdt-code {
626588 font-weight: normal;
627589 }
590 #djDebug .djdt-stack pre.djdt-locals {
591 margin: 0 27px 27px 27px;
592 }
628593
629594 #djDebug .djdt-width-20 {
630595 width: 20%;
631596 }
597 #djDebug .djdt-width-30 {
598 width: 30%;
599 }
632600 #djDebug .djdt-width-60 {
633601 width: 60%;
634602 }
603 #djDebug .djdt-max-height-100 {
604 max-height: 100%;
605 }
635606 #djDebug .djdt-highlighted {
636607 background-color: lightgrey;
637608 }
debug_toolbar/static/debug_toolbar/img/ajax-loader.gif less more
Binary diff not shown
debug_toolbar/static/debug_toolbar/img/back.png less more
Binary diff not shown
debug_toolbar/static/debug_toolbar/img/back_hover.png less more
Binary diff not shown
debug_toolbar/static/debug_toolbar/img/close.png less more
Binary diff not shown
debug_toolbar/static/debug_toolbar/img/close_hover.png less more
Binary diff not shown
debug_toolbar/static/debug_toolbar/img/djdt_vertical.png less more
Binary diff not shown
debug_toolbar/static/debug_toolbar/img/indicator.png less more
Binary diff not shown
0 import { $$, ajaxForm } from "./utils.js";
1
2 const djDebug = document.getElementById("djDebug");
3
4 $$.on(djDebug, "click", ".switchHistory", function (event) {
5 event.preventDefault();
6 const newStoreId = this.dataset.storeId;
7 const tbody = this.closest("tbody");
8
9 tbody
10 .querySelector(".djdt-highlighted")
11 .classList.remove("djdt-highlighted");
12 this.closest("tr").classList.add("djdt-highlighted");
13
14 ajaxForm(this).then(function (data) {
15 djDebug.setAttribute("data-store-id", newStoreId);
16 Object.keys(data).forEach(function (panelId) {
17 const panel = document.getElementById(panelId);
18 if (panel) {
19 panel.outerHTML = data[panelId].content;
20 document.getElementById("djdt-" + panelId).outerHTML =
21 data[panelId].button;
22 }
23 });
24 });
25 });
26
27 $$.on(djDebug, "click", ".refreshHistory", function (event) {
28 event.preventDefault();
29 const container = document.getElementById("djdtHistoryRequests");
30 ajaxForm(this).then(function (data) {
31 data.requests.forEach(function (request) {
32 if (
33 !container.querySelector('[data-store-id="' + request.id + '"]')
34 ) {
35 container.innerHTML = request.content + container.innerHTML;
36 }
37 });
38 });
39 });
0 document.getElementById('redirect_to').focus();
0 document.getElementById("redirect_to").focus();
0 const timingOffset = performance.timing.navigationStart,
1 timingEnd = performance.timing.loadEventEnd,
2 totalTime = timingEnd - timingOffset;
3 function getLeft(stat) {
4 return ((performance.timing[stat] - timingOffset) / totalTime) * 100.0;
5 }
6 function getCSSWidth(stat, endStat) {
7 let width =
8 ((performance.timing[endStat] - performance.timing[stat]) / totalTime) *
9 100.0;
10 // Calculate relative percent (same as sql panel logic)
11 width = (100.0 * width) / (100.0 - getLeft(stat));
12 return width < 1 ? "2px" : width + "%";
13 }
14 function addRow(tbody, stat, endStat) {
15 const row = document.createElement("tr");
16 if (endStat) {
17 // Render a start through end bar
18 row.innerHTML =
19 "<td>" +
20 stat.replace("Start", "") +
21 "</td>" +
22 '<td><svg class="djDebugLineChart" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 5" preserveAspectRatio="none"><rect y="0" height="5" fill="#ccc" /></svg></td>' +
23 "<td>" +
24 (performance.timing[stat] - timingOffset) +
25 " (+" +
26 (performance.timing[endStat] - performance.timing[stat]) +
27 ")</td>";
28 row.querySelector("rect").setAttribute(
29 "width",
30 getCSSWidth(stat, endStat)
31 );
32 } else {
33 // Render a point in time
34 row.innerHTML =
35 "<td>" +
36 stat +
37 "</td>" +
38 '<td><svg class="djDebugLineChart" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 5" preserveAspectRatio="none"><rect y="0" height="5" fill="#ccc" /></svg></td>' +
39 "<td>" +
40 (performance.timing[stat] - timingOffset) +
41 "</td>";
42 row.querySelector("rect").setAttribute("width", 2);
43 }
44 row.querySelector("rect").setAttribute("x", getLeft(stat));
45 tbody.appendChild(row);
46 }
47
48 const tbody = document.getElementById("djDebugBrowserTimingTableBody");
49 // This is a reasonably complete and ordered set of timing periods (2 params) and events (1 param)
50 addRow(tbody, "domainLookupStart", "domainLookupEnd");
51 addRow(tbody, "connectStart", "connectEnd");
52 addRow(tbody, "requestStart", "responseEnd"); // There is no requestEnd
53 addRow(tbody, "responseStart", "responseEnd");
54 addRow(tbody, "domLoading", "domComplete"); // Spans the events below
55 addRow(tbody, "domInteractive");
56 addRow(tbody, "domContentLoadedEventStart", "domContentLoadedEventEnd");
57 addRow(tbody, "loadEventStart", "loadEventEnd");
58 document.getElementById("djDebugBrowserTiming").classList.remove("djdt-hidden");
0 (function () {
1 var $$ = {
2 on: function(root, eventName, selector, fn) {
3 root.addEventListener(eventName, function(event) {
4 var target = event.target.closest(selector);
5 if (root.contains(target)) {
6 fn.call(target, event);
7 }
8 });
9 },
10 show: function(element) {
11 element.style.display = 'block';
12 },
13 hide: function(element) {
14 element.style.display = 'none';
15 },
16 toggle: function(element, value) {
17 if (value) {
18 $$.show(element);
19 } else {
20 $$.hide(element);
21 }
22 },
23 visible: function(element) {
24 style = getComputedStyle(element);
25 return style.display !== 'none';
26 },
27 executeScripts: function(root) {
28 root.querySelectorAll('script').forEach(function(e) {
29 var clone = document.createElement('script');
30 clone.src = e.src;
31 root.appendChild(clone);
32 });
33 },
34 };
35
36 var onKeyDown = function(event) {
37 if (event.keyCode == 27) {
38 djdt.hide_one_level();
39 }
40 };
41
42 var ajax = function(url, init) {
43 init = Object.assign({credentials: 'same-origin'}, init);
44 return fetch(url, init).then(function(response) {
45 if (response.ok) {
46 return response.text();
47 } else {
48 var win = document.querySelector('#djDebugWindow');
49 win.innerHTML = '<div class="djDebugPanelTitle"><a class="djDebugClose djDebugBack" href=""></a><h3>'+response.status+': '+response.statusText+'</h3></div>';
50 $$.show(win);
51 return Promise.reject();
52 }
53 });
54 };
55
56 var djdt = {
57 handleDragged: false,
58 events: {
59 ready: []
60 },
61 isReady: false,
62 init: function() {
63 var djDebug = document.querySelector('#djDebug');
64 $$.show(djDebug);
65 $$.on(djDebug.querySelector('#djDebugPanelList'), 'click', 'li a', function(event) {
0 import { $$, ajax } from "./utils.js";
1
2 function onKeyDown(event) {
3 if (event.keyCode === 27) {
4 djdt.hide_one_level();
5 }
6 }
7
8 const djdt = {
9 handleDragged: false,
10 init() {
11 const djDebug = document.getElementById("djDebug");
12 $$.show(djDebug);
13 $$.on(
14 document.getElementById("djDebugPanelList"),
15 "click",
16 "li a",
17 function (event) {
6618 event.preventDefault();
6719 if (!this.className) {
6820 return;
6921 }
70 var current = djDebug.querySelector('#' + this.className);
22 const current = document.getElementById(this.className);
7123 if ($$.visible(current)) {
7224 djdt.hide_panels();
7325 } else {
7426 djdt.hide_panels();
7527
7628 $$.show(current);
77 this.parentElement.classList.add('djdt-active');
78
79 var inner = current.querySelector('.djDebugPanelContent .djdt-scroll'),
80 store_id = djDebug.getAttribute('data-store-id');
29 this.parentElement.classList.add("djdt-active");
30
31 const inner = current.querySelector(
32 ".djDebugPanelContent .djdt-scroll"
33 ),
34 store_id = djDebug.dataset.storeId;
8135 if (store_id && inner.children.length === 0) {
82 var url = djDebug.getAttribute('data-render-panel-url');
83 var url_params = new URLSearchParams();
84 url_params.append('store_id', store_id);
85 url_params.append('panel_id', this.className);
86 url += '?' + url_params.toString();
87 ajax(url).then(function(body) {
88 inner.previousElementSibling.remove(); // Remove AJAX loader
89 inner.innerHTML = body;
90 $$.executeScripts(inner);
36 const url = new URL(
37 djDebug.dataset.renderPanelUrl,
38 window.location
39 );
40 url.searchParams.append("store_id", store_id);
41 url.searchParams.append("panel_id", this.className);
42 ajax(url).then(function (data) {
43 inner.previousElementSibling.remove(); // Remove AJAX loader
44 inner.innerHTML = data.content;
45 $$.executeScripts(data.scripts);
46 $$.applyStyles(inner);
9147 });
9248 }
9349 }
94 });
95 $$.on(djDebug, 'click', 'a.djDebugClose', function(event) {
96 event.preventDefault();
97 djdt.hide_one_level();
98 });
99 $$.on(djDebug, 'click', '.djDebugPanelButton input[type=checkbox]', function() {
100 djdt.cookie.set(this.getAttribute('data-cookie'), this.checked ? 'on' : 'off', {
101 path: '/',
102 expires: 10
103 });
104 });
105
106 // Used by the SQL and template panels
107 $$.on(djDebug, 'click', '.remoteCall', function(event) {
108 event.preventDefault();
109
110 var name = this.tagName.toLowerCase();
111 var ajax_data = {};
112
113 if (name == 'button') {
114 var form = this.closest('form');
115 ajax_data.url = this.getAttribute('formaction');
116
117 if (form) {
118 ajax_data.body = new FormData(form);
119 ajax_data.method = form.getAttribute('method') || 'POST';
120 }
121 }
122
123 if (name == 'a') {
124 ajax_data.url = this.getAttribute('href');
125 }
126
127 ajax(ajax_data.url, ajax_data).then(function(body) {
128 var win = djDebug.querySelector('#djDebugWindow');
129 win.innerHTML = body;
130 $$.executeScripts(win);
131 $$.show(win);
132 });
133 });
134
135 // Used by the cache, profiling and SQL panels
136 $$.on(djDebug, 'click', 'a.djToggleSwitch', function(event) {
137 event.preventDefault();
138 var self = this;
139 var id = this.getAttribute('data-toggle-id');
140 var open_me = this.textContent == this.getAttribute('data-toggle-open');
141 if (id === '' || !id) {
142 return;
143 }
144 var name = this.getAttribute('data-toggle-name');
145 var container = this.closest('.djDebugPanelContent').querySelector('#' + name + '_' + id);
146 container.querySelectorAll('.djDebugCollapsed').forEach(function(e) {
50 }
51 );
52 $$.on(djDebug, "click", ".djDebugClose", function () {
53 djdt.hide_one_level();
54 });
55 $$.on(
56 djDebug,
57 "click",
58 ".djDebugPanelButton input[type=checkbox]",
59 function () {
60 djdt.cookie.set(
61 this.dataset.cookie,
62 this.checked ? "on" : "off",
63 {
64 path: "/",
65 expires: 10,
66 }
67 );
68 }
69 );
70
71 // Used by the SQL and template panels
72 $$.on(djDebug, "click", ".remoteCall", function (event) {
73 event.preventDefault();
74
75 let url;
76 const ajax_data = {};
77
78 if (this.tagName === "BUTTON") {
79 const form = this.closest("form");
80 url = this.formAction;
81 ajax_data.method = form.method.toUpperCase();
82 ajax_data.body = new FormData(form);
83 } else if (this.tagName === "A") {
84 url = this.href;
85 }
86
87 ajax(url, ajax_data).then(function (data) {
88 const win = document.getElementById("djDebugWindow");
89 win.innerHTML = data.content;
90 $$.show(win);
91 });
92 });
93
94 // Used by the cache, profiling and SQL panels
95 $$.on(djDebug, "click", ".djToggleSwitch", function () {
96 const id = this.dataset.toggleId;
97 const toggleOpen = "+";
98 const toggleClose = "-";
99 const open_me = this.textContent === toggleOpen;
100 const name = this.dataset.toggleName;
101 const container = document.getElementById(name + "_" + id);
102 container
103 .querySelectorAll(".djDebugCollapsed")
104 .forEach(function (e) {
147105 $$.toggle(e, open_me);
148106 });
149 container.querySelectorAll('.djDebugUncollapsed').forEach(function(e) {
107 container
108 .querySelectorAll(".djDebugUncollapsed")
109 .forEach(function (e) {
150110 $$.toggle(e, !open_me);
151111 });
152 this.closest('.djDebugPanelContent').querySelectorAll('.djToggleDetails_' + id).forEach(function(e) {
112 const self = this;
113 this.closest(".djDebugPanelContent")
114 .querySelectorAll(".djToggleDetails_" + id)
115 .forEach(function (e) {
153116 if (open_me) {
154 e.classList.add('djSelected');
155 e.classList.remove('djUnselected');
156 self.textContent = self.getAttribute('data-toggle-close');
117 e.classList.add("djSelected");
118 e.classList.remove("djUnselected");
119 self.textContent = toggleClose;
157120 } else {
158 e.classList.remove('djSelected');
159 e.classList.add('djUnselected');
160 self.textContent = self.getAttribute('data-toggle-open');
161 }
162 var switch_ = e.querySelector('.djToggleSwitch')
163 if (switch_) switch_.textContent = self.textContent;
164 });
165 });
166
167 djDebug.querySelector('#djHideToolBarButton').addEventListener('click', function(event) {
168 event.preventDefault();
169 djdt.hide_toolbar(true);
170 });
171 djDebug.querySelector('#djShowToolBarButton').addEventListener('click', function(event) {
172 event.preventDefault();
121 e.classList.remove("djSelected");
122 e.classList.add("djUnselected");
123 self.textContent = toggleOpen;
124 }
125 const switch_ = e.querySelector(".djToggleSwitch");
126 if (switch_) {
127 switch_.textContent = self.textContent;
128 }
129 });
130 });
131
132 document
133 .getElementById("djHideToolBarButton")
134 .addEventListener("click", function (event) {
135 event.preventDefault();
136 djdt.hide_toolbar();
137 });
138 document
139 .getElementById("djShowToolBarButton")
140 .addEventListener("click", function () {
173141 if (!djdt.handleDragged) {
174142 djdt.show_toolbar();
175143 }
176144 });
177 var startPageY, baseY;
178 var handle = document.querySelector('#djDebugToolbarHandle');
179 var onHandleMove = function(event) {
180 // Chrome can send spurious mousemove events, so don't do anything unless the
181 // cursor really moved. Otherwise, it will be impossible to expand the toolbar
182 // due to djdt.handleDragged being set to true.
183 if (djdt.handleDragged || event.pageY != startPageY) {
184 var top = baseY + event.pageY;
185
186 if (top < 0) {
187 top = 0;
188 } else if (top + handle.offsetHeight > window.innerHeight) {
189 top = window.innerHeight - handle.offsetHeight;
190 }
191
192 handle.style.top = top + 'px';
193 djdt.handleDragged = true;
194 }
195 };
196 djDebug.querySelector('#djShowToolBarButton').addEventListener('mousedown', function(event) {
145 let startPageY, baseY;
146 const handle = document.getElementById("djDebugToolbarHandle");
147 function onHandleMove(event) {
148 // Chrome can send spurious mousemove events, so don't do anything unless the
149 // cursor really moved. Otherwise, it will be impossible to expand the toolbar
150 // due to djdt.handleDragged being set to true.
151 if (djdt.handleDragged || event.pageY !== startPageY) {
152 let top = baseY + event.pageY;
153
154 if (top < 0) {
155 top = 0;
156 } else if (top + handle.offsetHeight > window.innerHeight) {
157 top = window.innerHeight - handle.offsetHeight;
158 }
159
160 handle.style.top = top + "px";
161 djdt.handleDragged = true;
162 }
163 }
164 document
165 .getElementById("djShowToolBarButton")
166 .addEventListener("mousedown", function (event) {
197167 event.preventDefault();
198168 startPageY = event.pageY;
199169 baseY = handle.offsetTop - startPageY;
200 document.addEventListener('mousemove', onHandleMove);
201 });
202 document.addEventListener('mouseup', function (event) {
203 document.removeEventListener('mousemove', onHandleMove);
204 if (djdt.handleDragged) {
205 event.preventDefault();
206 djdt.cookie.set('djdttop', handle.offsetTop, {
207 path: '/',
208 expires: 10
209 });
210 setTimeout(function () {
211 djdt.handleDragged = false;
212 }, 10);
213 }
214 });
215 if (djdt.cookie.get('djdt') == 'hide') {
216 djdt.hide_toolbar(false);
217 } else {
218 djdt.show_toolbar();
219 }
220 djdt.isReady = true;
221 djdt.events.ready.forEach(function(callback) {
222 callback(djdt);
223 });
224 },
225 hide_panels: function() {
226 $$.hide(djDebug.querySelector('#djDebugWindow'));
227 djDebug.querySelectorAll('.djdt-panelContent').forEach(function(e) {
228 $$.hide(e);
229 });
230 djDebug.querySelectorAll('#djDebugToolbar li').forEach(function(e) {
231 e.classList.remove('djdt-active');
232 });
233 },
234 hide_toolbar: function(setCookie) {
235 djdt.hide_panels();
236 $$.hide(djDebug.querySelector('#djDebugToolbar'));
237
238 var handle = document.querySelector('#djDebugToolbarHandle');
239 $$.show(handle);
240 // set handle position
241 var handleTop = djdt.cookie.get('djdttop');
242 if (handleTop) {
243 handleTop = Math.min(handleTop, window.innerHeight - handle.offsetHeight);
244 handle.style.top = handleTop + 'px';
245 }
246
247 document.removeEventListener('keydown', onKeyDown);
248
249 if (setCookie) {
250 djdt.cookie.set('djdt', 'hide', {
251 path: '/',
252 expires: 10
253 });
254 }
255 },
256 hide_one_level: function(skipDebugWindow) {
257 if ($$.visible(djDebug.querySelector('#djDebugWindow'))) {
258 $$.hide(djDebug.querySelector('#djDebugWindow'));
259 } else if (djDebug.querySelector('#djDebugToolbar li.djdt-active')) {
170 document.addEventListener("mousemove", onHandleMove);
171 });
172 document.addEventListener("mouseup", function (event) {
173 document.removeEventListener("mousemove", onHandleMove);
174 if (djdt.handleDragged) {
175 event.preventDefault();
176 localStorage.setItem("djdt.top", handle.offsetTop);
177 requestAnimationFrame(function () {
178 djdt.handleDragged = false;
179 });
180 }
181 });
182 const show =
183 localStorage.getItem("djdt.show") || djDebug.dataset.defaultShow;
184 if (show === "true") {
185 djdt.show_toolbar();
186 } else {
187 djdt.hide_toolbar();
188 }
189 },
190 hide_panels() {
191 const djDebug = document.getElementById("djDebug");
192 $$.hide(document.getElementById("djDebugWindow"));
193 djDebug.querySelectorAll(".djdt-panelContent").forEach(function (e) {
194 $$.hide(e);
195 });
196 document.querySelectorAll("#djDebugToolbar li").forEach(function (e) {
197 e.classList.remove("djdt-active");
198 });
199 },
200 hide_toolbar() {
201 djdt.hide_panels();
202
203 $$.hide(document.getElementById("djDebugToolbar"));
204
205 const handle = document.getElementById("djDebugToolbarHandle");
206 $$.show(handle);
207 // set handle position
208 let handleTop = localStorage.getItem("djdt.top");
209 if (handleTop) {
210 handleTop = Math.min(
211 handleTop,
212 window.innerHeight - handle.offsetHeight
213 );
214 handle.style.top = handleTop + "px";
215 }
216
217 document.removeEventListener("keydown", onKeyDown);
218
219 localStorage.setItem("djdt.show", "false");
220 },
221 hide_one_level() {
222 const win = document.getElementById("djDebugWindow");
223 if ($$.visible(win)) {
224 $$.hide(win);
225 } else {
226 const toolbar = document.getElementById("djDebugToolbar");
227 if (toolbar.querySelector("li.djdt-active")) {
260228 djdt.hide_panels();
261229 } else {
262 djdt.hide_toolbar(true);
263 }
230 djdt.hide_toolbar();
231 }
232 }
233 },
234 show_toolbar() {
235 document.addEventListener("keydown", onKeyDown);
236 $$.hide(document.getElementById("djDebugToolbarHandle"));
237 $$.show(document.getElementById("djDebugToolbar"));
238 localStorage.setItem("djdt.show", "true");
239 },
240 cookie: {
241 get(key) {
242 if (!document.cookie.includes(key)) {
243 return null;
244 }
245
246 const cookieArray = document.cookie.split("; "),
247 cookies = {};
248
249 cookieArray.forEach(function (e) {
250 const parts = e.split("=");
251 cookies[parts[0]] = parts[1];
252 });
253
254 return cookies[key];
264255 },
265 show_toolbar: function() {
266 document.addEventListener('keydown', onKeyDown);
267 $$.hide(djDebug.querySelector('#djDebugToolbarHandle'));
268 $$.show(djDebug.querySelector('#djDebugToolbar'));
269 djdt.cookie.set('djdt', 'show', {
270 path: '/',
271 expires: 10
272 });
256 set(key, value, options) {
257 options = options || {};
258
259 if (typeof options.expires === "number") {
260 const days = options.expires,
261 t = (options.expires = new Date());
262 t.setDate(t.getDate() + days);
263 }
264
265 document.cookie = [
266 encodeURIComponent(key) + "=" + String(value),
267 options.expires
268 ? "; expires=" + options.expires.toUTCString()
269 : "",
270 options.path ? "; path=" + options.path : "",
271 options.domain ? "; domain=" + options.domain : "",
272 options.secure ? "; secure" : "",
273 "sameSite" in options
274 ? "; sameSite=" + options.samesite
275 : "; sameSite=Lax",
276 ].join("");
277
278 return value;
273279 },
274 ready: function(callback){
275 if (djdt.isReady) {
276 callback(djdt);
277 } else {
278 djdt.events.ready.push(callback);
279 }
280 },
281 cookie: {
282 get: function(key){
283 if (document.cookie.indexOf(key) === -1) return null;
284
285 var cookieArray = document.cookie.split('; '),
286 cookies = {};
287
288 cookieArray.forEach(function(e){
289 var parts = e.split('=');
290 cookies[ parts[0] ] = parts[1];
291 });
292
293 return cookies[ key ];
294 },
295 set: function(key, value, options){
296 options = options || {};
297
298 if (typeof options.expires === 'number') {
299 var days = options.expires, t = options.expires = new Date();
300 t.setDate(t.getDate() + days);
301 }
302
303 document.cookie = [
304 encodeURIComponent(key) + '=' + String(value),
305 options.expires ? '; expires=' + options.expires.toUTCString() : '',
306 options.path ? '; path=' + options.path : '',
307 options.domain ? '; domain=' + options.domain : '',
308 options.secure ? '; secure' : ''
309 ].join('');
310
311 return value;
312 }
313 },
314 applyStyle: function(name) {
315 var selector = '#djDebug [data-' + name + ']';
316 document.querySelectorAll(selector).forEach(function(element) {
317 element.style[name] = element.getAttribute('data-' + name);
318 });
319 }
320 };
321 window.djdt = {
322 show_toolbar: djdt.show_toolbar,
323 hide_toolbar: djdt.hide_toolbar,
324 close: djdt.hide_one_level,
325 cookie: djdt.cookie,
326 applyStyle: djdt.applyStyle
327 };
328 document.addEventListener('DOMContentLoaded', djdt.init);
329 })();
280 },
281 };
282 window.djdt = {
283 show_toolbar: djdt.show_toolbar,
284 hide_toolbar: djdt.hide_toolbar,
285 init: djdt.init,
286 close: djdt.hide_one_level,
287 cookie: djdt.cookie,
288 };
289
290 if (document.readyState !== "loading") {
291 djdt.init();
292 } else {
293 document.addEventListener("DOMContentLoaded", djdt.init);
294 }
+0
-3
debug_toolbar/static/debug_toolbar/js/toolbar.profiling.js less more
0 (function () {
1 djdt.applyStyle('padding-left');
2 })();
+0
-5
debug_toolbar/static/debug_toolbar/js/toolbar.sql.js less more
0 (function () {
1 djdt.applyStyle('background-color');
2 djdt.applyStyle('left');
3 djdt.applyStyle('width');
4 })();
+0
-52
debug_toolbar/static/debug_toolbar/js/toolbar.timer.js less more
0 (function () {
1 // Browser timing remains hidden unless we can successfully access the performance object
2 var perf = window.performance || window.msPerformance ||
3 window.webkitPerformance || window.mozPerformance;
4 if (!perf)
5 return;
6
7 var rowCount = 0,
8 timingOffset = perf.timing.navigationStart,
9 timingEnd = perf.timing.loadEventEnd,
10 totalTime = timingEnd - timingOffset;
11 function getLeft(stat) {
12 return ((perf.timing[stat] - timingOffset) / (totalTime)) * 100.0;
13 }
14 function getCSSWidth(stat, endStat) {
15 var width = ((perf.timing[endStat] - perf.timing[stat]) / (totalTime)) * 100.0;
16 // Calculate relative percent (same as sql panel logic)
17 width = 100.0 * width / (100.0 - getLeft(stat));
18 return (width < 1) ? "2px" : width + "%";
19 }
20 function addRow(stat, endStat) {
21 rowCount++;
22 var row = document.createElement('tr');
23 row.className = (rowCount % 2) ? 'djDebugOdd' : 'djDebugEven';
24 if (endStat) {
25 // Render a start through end bar
26 row.innerHTML = '<td>' + stat.replace('Start', '') + '</td>' +
27 '<td class="djdt-timeline"><div class="djDebugTimeline"><div class="djDebugLineChart"><strong>&#160;</strong></div></div></td>' +
28 '<td>' + (perf.timing[stat] - timingOffset) + ' (+' + (perf.timing[endStat] - perf.timing[stat]) + ')</td>';
29 row.querySelector('strong').style.width = getCSSWidth(stat, endStat);
30 } else {
31 // Render a point in time
32 row.innerHTML = '<td>' + stat + '</td>' +
33 '<td class="djdt-timeline"><div class="djDebugTimeline"><div class="djDebugLineChart"><strong>&#160;</strong></div></div></td>' +
34 '<td>' + (perf.timing[stat] - timingOffset) + '</td>';
35 row.querySelector('strong').style.width = '2px';
36 }
37 row.querySelector('.djDebugLineChart').style.left = getLeft(stat) + '%';
38 document.querySelector('#djDebugBrowserTimingTableBody').appendChild(row);
39 }
40
41 // This is a reasonably complete and ordered set of timing periods (2 params) and events (1 param)
42 addRow('domainLookupStart', 'domainLookupEnd');
43 addRow('connectStart', 'connectEnd');
44 addRow('requestStart', 'responseEnd'); // There is no requestEnd
45 addRow('responseStart', 'responseEnd');
46 addRow('domLoading', 'domComplete'); // Spans the events below
47 addRow('domInteractive');
48 addRow('domContentLoadedEventStart', 'domContentLoadedEventEnd');
49 addRow('loadEventStart', 'loadEventEnd');
50 document.querySelector('#djDebugBrowserTiming').classList.remove('djdt-hidden');
51 })();
0 const $$ = {
1 on(root, eventName, selector, fn) {
2 root.addEventListener(eventName, function (event) {
3 const target = event.target.closest(selector);
4 if (root.contains(target)) {
5 fn.call(target, event);
6 }
7 });
8 },
9 show(element) {
10 element.classList.remove("djdt-hidden");
11 },
12 hide(element) {
13 element.classList.add("djdt-hidden");
14 },
15 toggle(element, value) {
16 if (value) {
17 $$.show(element);
18 } else {
19 $$.hide(element);
20 }
21 },
22 visible(element) {
23 return !element.classList.contains("djdt-hidden");
24 },
25 executeScripts(scripts) {
26 scripts.forEach(function (script) {
27 const el = document.createElement("script");
28 el.type = "module";
29 el.src = script;
30 el.async = true;
31 document.head.appendChild(el);
32 });
33 },
34 applyStyles(container) {
35 /*
36 * Given a container element, apply styles set via data-djdt-styles attribute.
37 * The format is data-djdt-styles="styleName1:value;styleName2:value2"
38 * The style names should use the CSSStyleDeclaration camel cased names.
39 */
40 container
41 .querySelectorAll("[data-djdt-styles]")
42 .forEach(function (element) {
43 const styles = element.dataset.djdtStyles || "";
44 styles.split(";").forEach(function (styleText) {
45 const styleKeyPair = styleText.split(":");
46 if (styleKeyPair.length === 2) {
47 const name = styleKeyPair[0].trim();
48 const value = styleKeyPair[1].trim();
49 element.style[name] = value;
50 }
51 });
52 });
53 },
54 };
55
56 function ajax(url, init) {
57 init = Object.assign({ credentials: "same-origin" }, init);
58 return fetch(url, init)
59 .then(function (response) {
60 if (response.ok) {
61 return response.json();
62 }
63 return Promise.reject(
64 new Error(response.status + ": " + response.statusText)
65 );
66 })
67 .catch(function (error) {
68 const win = document.getElementById("djDebugWindow");
69 win.innerHTML =
70 '<div class="djDebugPanelTitle"><button type="button" class="djDebugClose">Ā»</button><h3>' +
71 error.message +
72 "</h3></div>";
73 $$.show(win);
74 throw error;
75 });
76 }
77
78 function ajaxForm(element) {
79 const form = element.closest("form");
80 const url = new URL(form.action);
81 const formData = new FormData(form);
82 for (const [name, value] of formData.entries()) {
83 url.searchParams.append(name, value);
84 }
85 const ajaxData = {
86 method: form.method.toUpperCase(),
87 };
88 return ajax(url, ajaxData);
89 }
90
91 export { $$, ajax, ajaxForm };
0 {% load i18n %}{% load static %}
1 <link rel="stylesheet" href="{% static 'debug_toolbar/css/print.css' %}" type="text/css" media="print">
2 <link rel="stylesheet" href="{% static 'debug_toolbar/css/toolbar.css' %}" type="text/css">
3 <script src="{% static 'debug_toolbar/js/toolbar.js' %}" defer></script>
0 {% load i18n static %}
1 {% block css %}
2 <link rel="stylesheet" href="{% static 'debug_toolbar/css/print.css' %}" media="print">
3 <link rel="stylesheet" href="{% static 'debug_toolbar/css/toolbar.css' %}">
4 {% endblock %}
5 {% block js %}
6 <script type="module" src="{% static 'debug_toolbar/js/toolbar.js' %}" async></script>
7 {% endblock %}
48 <div id="djDebug" class="djdt-hidden" dir="ltr"
5 {% if toolbar.store_id %}
6 data-store-id="{{ toolbar.store_id }}"
7 data-render-panel-url="{% url 'djdt:render_panel' %}"
8 {% endif %}
9 {% if toolbar.store_id %}data-store-id="{{ toolbar.store_id }}" data-render-panel-url="{% url 'djdt:render_panel' %}"{% endif %}
10 data-default-show="{% if toolbar.config.SHOW_COLLAPSED %}false{% else %}true{% endif %}"
911 {{ toolbar.config.ROOT_TAG_EXTRA_ATTRS|safe }}>
10 <div class="djdt-hidden" id="djDebugToolbar">
11 <ul id="djDebugPanelList">
12 {% if toolbar.panels %}
13 <li><a id="djHideToolBarButton" href="#" title="{% trans "Hide toolbar" %}">{% trans "Hide" %} &#187;</a></li>
14 {% else %}
15 <li id="djDebugButton">DEBUG</li>
16 {% endif %}
17 {% for panel in toolbar.panels %}
18 <li class="djDebugPanelButton">
19 <input type="checkbox" data-cookie="djdt{{ panel.panel_id }}" {% if panel.enabled %}checked title="{% trans "Disable for next and successive requests" %}"{% else %}title="{% trans "Enable for next and successive requests" %}"{% endif %}>
20 {% if panel.has_content and panel.enabled %}
21 <a href="#" title="{{ panel.title }}" class="{{ panel.panel_id }}">
22 {% else %}
23 <div class="djdt-contentless{% if not panel.enabled %} djdt-disabled{% endif %}">
24 {% endif %}
25 {{ panel.nav_title }}
26 {% if panel.enabled %}
27 {% with panel.nav_subtitle as subtitle %}
28 {% if subtitle %}<br><small>{{ subtitle }}</small>{% endif %}
29 {% endwith %}
30 {% endif %}
31 {% if panel.has_content and panel.enabled %}
32 </a>
33 {% else %}
34 </div>
35 {% endif %}
36 </li>
37 {% endfor %}
38 </ul>
39 </div>
40 <div class="djdt-hidden" id="djDebugToolbarHandle">
41 <span title="{% trans "Show toolbar" %}" id="djShowToolBarButton">&#171;</span>
42 </div>
43 {% for panel in toolbar.panels %}
44 {% if panel.has_content and panel.enabled %}
45 <div id="{{ panel.panel_id }}" class="djdt-panelContent">
46 <div class="djDebugPanelTitle">
47 <a href="" class="djDebugClose"></a>
48 <h3>{{ panel.title|safe }}</h3>
49 </div>
50 <div class="djDebugPanelContent">
51 {% if toolbar.store_id %}
52 <img src="{% static 'debug_toolbar/img/ajax-loader.gif' %}" alt="loading" class="djdt-loader">
53 <div class="djdt-scroll"></div>
54 {% else %}
55 <div class="djdt-scroll">{{ panel.content }}</div>
56 {% endif %}
57 </div>
58 </div>
59 {% endif %}
60 {% endfor %}
61 <div id="djDebugWindow" class="djdt-panelContent"></div>
12 <div class="djdt-hidden" id="djDebugToolbar">
13 <ul id="djDebugPanelList">
14 <li><a id="djHideToolBarButton" href="#" title="{% trans 'Hide toolbar' %}">{% trans "Hide" %} Ā»</a></li>
15 {% for panel in toolbar.panels %}
16 {% include "debug_toolbar/includes/panel_button.html" %}
17 {% endfor %}
18 </ul>
19 </div>
20 <div class="djdt-hidden" id="djDebugToolbarHandle">
21 <div title="{% trans 'Show toolbar' %}" id="djShowToolBarButton">
22 <span id="djShowToolBarD">D</span><span id="djShowToolBarJ">J</span>DT
23 </div>
24 </div>
25
26 {% for panel in toolbar.panels %}
27 {% include "debug_toolbar/includes/panel_content.html" %}
28 {% endfor %}
29 <div id="djDebugWindow" class="djdt-panelContent djdt-hidden"></div>
6230 </div>
0 {% load i18n %}
1
2 <li id="djdt-{{ panel.panel_id }}" class="djDebugPanelButton">
3 <input type="checkbox" data-cookie="djdt{{ panel.panel_id }}" {% if panel.enabled %}checked title="{% trans "Disable for next and successive requests" %}"{% else %}title="{% trans "Enable for next and successive requests" %}"{% endif %}>
4 {% if panel.has_content and panel.enabled %}
5 <a href="#" title="{{ panel.title }}" class="{{ panel.panel_id }}">
6 {% else %}
7 <div class="djdt-contentless{% if not panel.enabled %} djdt-disabled{% endif %}">
8 {% endif %}
9 {{ panel.nav_title }}
10 {% if panel.enabled %}
11 {% with panel.nav_subtitle as subtitle %}
12 {% if subtitle %}<br><small>{{ subtitle }}</small>{% endif %}
13 {% endwith %}
14 {% endif %}
15 {% if panel.has_content and panel.enabled %}
16 </a>
17 {% else %}
18 </div>
19 {% endif %}
20 </li>
0 {% load static %}
1
2 {% if panel.has_content and panel.enabled %}
3 <div id="{{ panel.panel_id }}" class="djdt-panelContent djdt-hidden">
4 <div class="djDebugPanelTitle">
5 <button type="button" class="djDebugClose">Ɨ</button>
6 <h3>{{ panel.title }}</h3>
7 </div>
8 <div class="djDebugPanelContent">
9 {% if toolbar.store_id %}
10 <div class="djdt-loader"></div>
11 <div class="djdt-scroll"></div>
12 {% else %}
13 <div class="djdt-scroll">{{ panel.content }}</div>
14 {% endif %}
15 </div>
16 </div>
17 {% endif %}
00 {% load i18n %}
11 <h4>{% trans "Summary" %}</h4>
22 <table>
3 <thead>
4 <tr>
5 <th>{% trans "Total calls" %}</th>
6 <th>{% trans "Total time" %}</th>
7 <th>{% trans "Cache hits" %}</th>
8 <th>{% trans "Cache misses" %}</th>
9 </tr>
10 </thead>
11 <tbody>
12 <tr>
13 <td>{{ total_calls }}</td>
14 <td>{{ total_time }} ms</td>
15 <td>{{ hits }}</td>
16 <td>{{ misses }}</td>
17 </tr>
18 </tbody>
3 <thead>
4 <tr>
5 <th>{% trans "Total calls" %}</th>
6 <th>{% trans "Total time" %}</th>
7 <th>{% trans "Cache hits" %}</th>
8 <th>{% trans "Cache misses" %}</th>
9 </tr>
10 </thead>
11 <tbody>
12 <tr>
13 <td>{{ total_calls }}</td>
14 <td>{{ total_time }} ms</td>
15 <td>{{ hits }}</td>
16 <td>{{ misses }}</td>
17 </tr>
18 </tbody>
1919 </table>
2020 <h4>{% trans "Commands" %}</h4>
2121 <table>
22 <thead>
23 <tr>
24 {% for name in counts.keys %}
25 <th>{{ name }}</th>
26 {% endfor %}
27 </tr>
28 </thead>
29 <tbody>
30 <tr>
31 {% for value in counts.values %}
32 <td>{{ value }}</td>
33 {% endfor %}
34 </tr>
35 </tbody>
22 <thead>
23 <tr>
24 {% for name in counts.keys %}
25 <th>{{ name }}</th>
26 {% endfor %}
27 </tr>
28 </thead>
29 <tbody>
30 <tr>
31 {% for value in counts.values %}
32 <td>{{ value }}</td>
33 {% endfor %}
34 </tr>
35 </tbody>
3636 </table>
3737 {% if calls %}
38 <h4>{% trans "Calls" %}</h4>
39 <table>
40 <thead>
41 <tr>
42 <th colspan="2">{% trans "Time (ms)" %}</th>
43 <th>{% trans "Type" %}</th>
44 <th>{% trans "Arguments" %}</th>
45 <th>{% trans "Keyword arguments" %}</th>
46 <th>{% trans "Backend" %}</th>
47 </tr>
48 </thead>
49 <tbody>
50 {% for call in calls %}
51 <tr class="{% cycle 'djDebugOdd' 'djDebugEven' %}" id="cacheMain_{{ forloop.counter }}">
52 <td class="djdt-toggle">
53 <a class="djToggleSwitch" data-toggle-name="cacheMain" data-toggle-id="{{ forloop.counter }}" data-toggle-open="+" data-toggle-close="-" href>+</a>
54 </td>
55 <td>{{ call.time|floatformat:"4" }}</td>
56 <td>{{ call.name|escape }}</td>
57 <td>{{ call.args|escape }}</td>
58 <td>{{ call.kwargs|escape }}</td>
59 <td>{{ call.backend }}</td>
60 </tr>
61 <tr class="djUnselected {% cycle 'djDebugOdd' 'djDebugEven' %} djToggleDetails_{{ forloop.counter }}" id="cacheDetails_{{ forloop.counter }}">
62 <td colspan="1"></td>
63 <td colspan="5"><pre class="djdt-stack">{{ call.trace }}</pre></td>
64 </tr>
65 {% endfor %}
66 </tbody>
67 </table>
38 <h4>{% trans "Calls" %}</h4>
39 <table>
40 <thead>
41 <tr>
42 <th colspan="2">{% trans "Time (ms)" %}</th>
43 <th>{% trans "Type" %}</th>
44 <th>{% trans "Arguments" %}</th>
45 <th>{% trans "Keyword arguments" %}</th>
46 <th>{% trans "Backend" %}</th>
47 </tr>
48 </thead>
49 <tbody>
50 {% for call in calls %}
51 <tr id="cacheMain_{{ forloop.counter }}">
52 <td class="djdt-toggle">
53 <button class="djToggleSwitch" data-toggle-name="cacheMain" data-toggle-id="{{ forloop.counter }}">+</button>
54 </td>
55 <td>{{ call.time|floatformat:"4" }}</td>
56 <td>{{ call.name|escape }}</td>
57 <td>{{ call.args|escape }}</td>
58 <td>{{ call.kwargs|escape }}</td>
59 <td>{{ call.backend }}</td>
60 </tr>
61 <tr class="djUnselected djToggleDetails_{{ forloop.counter }}" id="cacheDetails_{{ forloop.counter }}">
62 <td colspan="1"></td>
63 <td colspan="5"><pre class="djdt-stack">{{ call.trace }}</pre></td>
64 </tr>
65 {% endfor %}
66 </tbody>
67 </table>
6868 {% endif %}
22 <h4>{% trans "Request headers" %}</h4>
33
44 <table>
5 <thead>
6 <tr>
7 <th>{% trans "Key" %}</th>
8 <th>{% trans "Value" %}</th>
9 </tr>
10 </thead>
11 <tbody>
12 {% for key, value in request_headers.items %}
13 <tr class="{% cycle 'djDebugOdd' 'djDebugEven' %}">
14 <td>{{ key|escape }}</td>
15 <td>{{ value|escape }}</td>
16 </tr>
17 {% endfor %}
18 </tbody>
5 <thead>
6 <tr>
7 <th>{% trans "Key" %}</th>
8 <th>{% trans "Value" %}</th>
9 </tr>
10 </thead>
11 <tbody>
12 {% for key, value in request_headers.items %}
13 <tr>
14 <td>{{ key|escape }}</td>
15 <td>{{ value|escape }}</td>
16 </tr>
17 {% endfor %}
18 </tbody>
1919 </table>
2020
2121 <h4>{% trans "Response headers" %}</h4>
2222
2323 <table>
24 <thead>
25 <tr>
26 <th>{% trans "Key" %}</th>
27 <th>{% trans "Value" %}</th>
28 </tr>
29 </thead>
30 <tbody>
31 {% for key, value in response_headers.items %}
32 <tr class="{% cycle 'djDebugOdd' 'djDebugEven' %}">
33 <td>{{ key|escape }}</td>
34 <td>{{ value|escape }}</td>
35 </tr>
36 {% endfor %}
37 </tbody>
24 <thead>
25 <tr>
26 <th>{% trans "Key" %}</th>
27 <th>{% trans "Value" %}</th>
28 </tr>
29 </thead>
30 <tbody>
31 {% for key, value in response_headers.items %}
32 <tr>
33 <td>{{ key|escape }}</td>
34 <td>{{ value|escape }}</td>
35 </tr>
36 {% endfor %}
37 </tbody>
3838 </table>
3939
4040 <h4>{% trans "WSGI environ" %}</h4>
4242 <p>{% trans "Since the WSGI environ inherits the environment of the server, only a significant subset is shown below." %}</p>
4343
4444 <table>
45 <thead>
46 <tr>
47 <th>{% trans "Key" %}</th>
48 <th>{% trans "Value" %}</th>
49 </tr>
50 </thead>
51 <tbody>
52 {% for key, value in environ.items %}
53 <tr class="{% cycle 'djDebugOdd' 'djDebugEven' %}">
54 <td>{{ key|escape }}</td>
55 <td>{{ value|escape }}</td>
56 </tr>
57 {% endfor %}
58 </tbody>
45 <thead>
46 <tr>
47 <th>{% trans "Key" %}</th>
48 <th>{% trans "Value" %}</th>
49 </tr>
50 </thead>
51 <tbody>
52 {% for key, value in environ.items %}
53 <tr>
54 <td>{{ key|escape }}</td>
55 <td>{{ value|escape }}</td>
56 </tr>
57 {% endfor %}
58 </tbody>
5959 </table>
0 {% load i18n %}{% load static %}
1 <form method="get" action="{% url 'djdt:history_refresh' %}">
2 {{ refresh_form }}
3 <button class="refreshHistory">Refresh</button>
4 </form>
5 <table class="djdt-max-height-100">
6 <thead>
7 <tr>
8 <th>{% trans "Time" %}</th>
9 <th>{% trans "Method" %}</th>
10 <th>{% trans "Path" %}</th>
11 <th>{% trans "Request Variables" %}</th>
12 <th>{% trans "Action" %}</th>
13 </tr>
14 </thead>
15 <tbody id="djdtHistoryRequests">
16 {% for id, store_context in stores.items %}
17 {% include "debug_toolbar/panels/history_tr.html" %}
18 {% endfor %}
19 </tbody>
20 </table>
0 {% load i18n %}
1 <tr class="{% if id == current_store_id %}djdt-highlighted{% endif %}" id="historyMain_{{ id }}" data-store-id="{{ id }}">
2 <td>
3 {{ store_context.toolbar.stats.HistoryPanel.time|escape }}
4 </td>
5 <td>
6 <p>{{ store_context.toolbar.stats.HistoryPanel.request_method|escape }}</p>
7 </td>
8 <td>
9 <p>{{ store_context.toolbar.stats.HistoryPanel.request_url|truncatechars:100|escape }}</p>
10 </td>
11 <td>
12 <button type="button" class="djToggleSwitch" data-toggle-name="historyMain" data-toggle-id="{{ id }}">+</button>
13 <div class="djUnselected djToggleDetails_{{ id }}">
14 <table>
15 <colgroup>
16 <col class="djdt-width-20">
17 <col>
18 </colgroup>
19 <thead>
20 <tr>
21 <th>{% trans "Variable" %}</th>
22 <th>{% trans "Value" %}</th>
23 </tr>
24 </thead>
25 <tbody>
26 {% for key, value in store_context.toolbar.stats.HistoryPanel.data.items %}
27 <tr>
28 <td><code>{{ key|pprint }}</code></td>
29 <td><code>{{ value|pprint }}</code></td>
30 </tr>
31 {% empty %}
32 <tr>
33 <td colspan="2">No data</td>
34 </tr>
35 {% endfor %}
36 </tbody>
37 </table>
38 </div>
39 </td>
40 <td class="djdt-actions">
41 <form method="get" action="{% url 'djdt:history_sidebar' %}">
42 {{ store_context.form }}
43 <button data-store-id="{{ id }}" class="switchHistory">Switch</button>
44 </form>
45 </td>
46 </tr>
00 {% load i18n %}
11 {% if records %}
2 <table>
3 <thead>
4 <tr>
5 <th>{% trans "Level" %}</th>
6 <th>{% trans "Time" %}</th>
7 <th>{% trans "Channel" %}</th>
8 <th>{% trans "Message" %}</th>
9 <th>{% trans "Location" %}</th>
10 </tr>
11 </thead>
12 <tbody>
13 {% for record in records %}
14 <tr class="{% cycle 'djDebugOdd' 'djDebugEven' %}">
15 <td>{{ record.level }}</td>
16 <td>{{ record.time|date:"h:i:s m/d/Y" }}</td>
17 <td>{{ record.channel|default:"-" }}</td>
18 <td>{{ record.message|linebreaksbr }}</td>
19 <td>{{ record.file }}:{{ record.line }}</td>
20 </tr>
21 {% endfor %}
22 </tbody>
23 </table>
2 <table>
3 <thead>
4 <tr>
5 <th>{% trans "Level" %}</th>
6 <th>{% trans "Time" %}</th>
7 <th>{% trans "Channel" %}</th>
8 <th>{% trans "Message" %}</th>
9 <th>{% trans "Location" %}</th>
10 </tr>
11 </thead>
12 <tbody>
13 {% for record in records %}
14 <tr>
15 <td>{{ record.level }}</td>
16 <td>{{ record.time|date:"h:i:s m/d/Y" }}</td>
17 <td>{{ record.channel|default:"-" }}</td>
18 <td>{{ record.message|linebreaksbr }}</td>
19 <td>{{ record.file }}:{{ record.line }}</td>
20 </tr>
21 {% endfor %}
22 </tbody>
23 </table>
2424 {% else %}
25 <p>{% trans "No messages logged" %}.</p>
25 <p>{% trans "No messages logged" %}.</p>
2626 {% endif %}
27
0 {% load i18n %}{% load static %}
1 <table width="100%">
2 <thead>
3 <tr>
4 <th>{% trans "Call" %}</th>
5 <th>{% trans "CumTime" %}</th>
6 <th>{% trans "Per" %}</th>
7 <th>{% trans "TotTime" %}</th>
8 <th>{% trans "Per" %}</th>
9 <th>{% trans "Count" %}</th>
10 </tr>
11 </thead>
12 <tbody>
13 {% for call in func_list %}
14 <tr class="{% cycle 'djDebugOdd' 'djDebugEven' %} djDebugProfileRow{% for parent_id in call.parent_ids %} djToggleDetails_{{ parent_id }}{% endfor %}" depth="{{ call.depth }}" id="profilingMain_{{ call.id }}">
15 <td>
16 <div data-padding-left="{{ call.indent }}px">
17 {% if call.has_subfuncs %}
18 <a class="djProfileToggleDetails djToggleSwitch" data-toggle-name="profilingMain" data-toggle-id="{{ call.id }}" data-toggle-open="+" data-toggle-close="-" href>-</a>
19 {% else %}
20 <span class="djNoToggleSwitch"></span>
21 {% endif %}
22 <span class="djdt-stack">{{ call.func_std_string }}</span>
23 </div>
24 </td>
25 <td>{{ call.cumtime|floatformat:3 }}</td>
26 <td>{{ call.cumtime_per_call|floatformat:3 }}</td>
27 <td>{{ call.tottime|floatformat:3 }}</td>
28 <td>{{ call.tottime_per_call|floatformat:3 }}</td>
29 <td>{{ call.count }}</td>
30 </tr>
31 {% endfor %}
32 </tbody>
0 {% load i18n %}
1 <table>
2 <thead>
3 <tr>
4 <th>{% trans "Call" %}</th>
5 <th>{% trans "CumTime" %}</th>
6 <th>{% trans "Per" %}</th>
7 <th>{% trans "TotTime" %}</th>
8 <th>{% trans "Per" %}</th>
9 <th>{% trans "Count" %}</th>
10 </tr>
11 </thead>
12 <tbody>
13 {% for call in func_list %}
14 <tr class="{% for parent_id in call.parent_ids %} djToggleDetails_{{ parent_id }}{% endfor %}" id="profilingMain_{{ call.id }}">
15 <td>
16 <div data-djdt-styles="paddingLeft:{{ call.indent }}px">
17 {% if call.has_subfuncs %}
18 <button type="button" class="djProfileToggleDetails djToggleSwitch" data-toggle-name="profilingMain" data-toggle-id="{{ call.id }}">-</button>
19 {% else %}
20 <span class="djNoToggleSwitch"></span>
21 {% endif %}
22 <span class="djdt-stack">{{ call.func_std_string }}</span>
23 </div>
24 </td>
25 <td>{{ call.cumtime|floatformat:3 }}</td>
26 <td>{{ call.cumtime_per_call|floatformat:3 }}</td>
27 <td>{{ call.tottime|floatformat:3 }}</td>
28 <td>{{ call.tottime_per_call|floatformat:3 }}</td>
29 <td>{{ call.count }}</td>
30 </tr>
31 {% endfor %}
32 </tbody>
3333 </table>
34
35 <script src="{% static 'debug_toolbar/js/toolbar.profiling.js' %}" defer></script>
11
22 <h4>{% trans "View information" %}</h4>
33 <table>
4 <thead>
5 <tr>
6 <th>{% trans "View function" %}</th>
7 <th>{% trans "Arguments" %}</th>
8 <th>{% trans "Keyword arguments" %}</th>
9 <th>{% trans "URL name" %}</th>
10 </tr>
11 </thead>
12 <tbody>
13 <tr class="djDebugOdd">
14 <td><code>{{ view_func }}</code></td>
15 <td><code>{{ view_args|pprint }}</code></td>
16 <td><code>{{ view_kwargs|pprint }}</code></td>
17 <td><code>{{ view_urlname }}</code></td>
18 </tr>
19 </tbody>
4 <thead>
5 <tr>
6 <th>{% trans "View function" %}</th>
7 <th>{% trans "Arguments" %}</th>
8 <th>{% trans "Keyword arguments" %}</th>
9 <th>{% trans "URL name" %}</th>
10 </tr>
11 </thead>
12 <tbody>
13 <tr>
14 <td><code>{{ view_func }}</code></td>
15 <td><code>{{ view_args|pprint }}</code></td>
16 <td><code>{{ view_kwargs|pprint }}</code></td>
17 <td><code>{{ view_urlname }}</code></td>
18 </tr>
19 </tbody>
2020 </table>
2121
2222 {% if cookies %}
23 <h4>{% trans "Cookies" %}</h4>
24 <table>
25 <colgroup>
26 <col class="djdt-width-20">
27 <col>
28 </colgroup>
29 <thead>
30 <tr>
31 <th>{% trans "Variable" %}</th>
32 <th>{% trans "Value" %}</th>
33 </tr>
34 </thead>
35 <tbody>
36 {% for key, value in cookies %}
37 <tr class="{% cycle 'djDebugOdd' 'djDebugEven' %}">
38 <td><code>{{ key|pprint }}</code></td>
39 <td><code>{{ value|pprint }}</code></td>
40 </tr>
41 {% endfor %}
42 </tbody>
43 </table>
23 <h4>{% trans "Cookies" %}</h4>
24 {% include 'debug_toolbar/panels/request_variables.html' with variables=cookies %}
4425 {% else %}
45 <h4>{% trans "No cookies" %}</h4>
26 <h4>{% trans "No cookies" %}</h4>
4627 {% endif %}
4728
4829 {% if session %}
49 <h4>{% trans "Session data" %}</h4>
50 <table>
51 <colgroup>
52 <col class="djdt-width-20">
53 <col>
54 </colgroup>
55 <thead>
56 <tr>
57 <th>{% trans "Variable" %}</th>
58 <th>{% trans "Value" %}</th>
59 </tr>
60 </thead>
61 <tbody>
62 {% for key, value in session %}
63 <tr class="{% cycle 'djDebugOdd' 'djDebugEven' %}">
64 <td><code>{{ key|pprint }}</code></td>
65 <td><code>{{ value|pprint }}</code></td>
66 </tr>
67 {% endfor %}
68 </tbody>
69 </table>
30 <h4>{% trans "Session data" %}</h4>
31 {% include 'debug_toolbar/panels/request_variables.html' with variables=session %}
7032 {% else %}
71 <h4>{% trans "No session data" %}</h4>
33 <h4>{% trans "No session data" %}</h4>
7234 {% endif %}
7335
7436 {% if get %}
75 <h4>{% trans "GET data" %}</h4>
76 <table>
77 <colgroup>
78 <col class="djdt-width-20">
79 <col>
80 </colgroup>
81 <thead>
82 <tr>
83 <th>{% trans "Variable" %}</th>
84 <th>{% trans "Value" %}</th>
85 </tr>
86 </thead>
87 <tbody>
88 {% for key, value in get %}
89 <tr class="{% cycle 'djDebugOdd' 'djDebugEven' %}">
90 <td><code>{{ key|pprint }}</code></td>
91 <td><code>{{ value|pprint }}</code></td>
92 </tr>
93 {% endfor %}
94 </tbody>
95 </table>
37 <h4>{% trans "GET data" %}</h4>
38 {% include 'debug_toolbar/panels/request_variables.html' with variables=get %}
9639 {% else %}
97 <h4>{% trans "No GET data" %}</h4>
40 <h4>{% trans "No GET data" %}</h4>
9841 {% endif %}
9942
10043 {% if post %}
101 <h4>{% trans "POST data" %}</h4>
102 <table>
103 <colgroup>
104 <col class="djdt-width-20">
105 <col>
106 </colgroup>
107 <tr>
108 <th>{% trans "Variable" %}</th>
109 <th>{% trans "Value" %}</th>
110 </tr>
111 </thead>
112 <tbody>
113 {% for key, value in post %}
114 <tr class="{% cycle 'row1' 'row2' %}">
115 <td><code>{{ key|pprint }}</code></td>
116 <td><code>{{ value|pprint }}</code></td>
117 </tr>
118 {% endfor %}
119 </tbody>
120 </table>
44 <h4>{% trans "POST data" %}</h4>
45 {% include 'debug_toolbar/panels/request_variables.html' with variables=post %}
12146 {% else %}
122 <h4>{% trans "No POST data" %}</h4>
47 <h4>{% trans "No POST data" %}</h4>
12348 {% endif %}
0 {% load i18n %}
1
2 <table>
3 <colgroup>
4 <col class="djdt-width-20">
5 <col>
6 </colgroup>
7 <thead>
8 <tr>
9 <th>{% trans "Variable" %}</th>
10 <th>{% trans "Value" %}</th>
11 </tr>
12 </thead>
13 <tbody>
14 {% for key, value in variables %}
15 <tr>
16 <td><code>{{ key|pprint }}</code></td>
17 <td><code>{{ value|pprint }}</code></td>
18 </tr>
19 {% endfor %}
20 </tbody>
21 </table>
00 {% load i18n %}
11 <table>
2 <thead>
3 <tr>
4 <th>{% trans "Setting" %}</th>
5 <th>{% trans "Value" %}</th>
6 </tr>
7 </thead>
8 <tbody>
9 {% for name, value in settings.items %}
10 <tr class="{% cycle 'djDebugOdd' 'djDebugEven' %}">
11 <td>{{ name }}</td>
12 <td><code>{{ value|pprint }}</code></td>
13 </tr>
14 {% endfor %}
15 </tbody>
2 <thead>
3 <tr>
4 <th>{% trans "Setting" %}</th>
5 <th>{% trans "Value" %}</th>
6 </tr>
7 </thead>
8 <tbody>
9 {% for name, value in settings.items %}
10 <tr>
11 <td>{{ name }}</td>
12 <td><code>{{ value|pprint }}</code></td>
13 </tr>
14 {% endfor %}
15 </tbody>
1616 </table>
00 {% load i18n %}
11 <table>
2 <thead>
3 <tr>
4 <th>{% trans "Signal" %}</th>
5 <th>{% trans "Providing" %}</th>
6 <th>{% trans "Receivers" %}</th>
7 </tr>
8 </thead>
9 <tbody>
10 {% for name, signal, receivers in signals %}
11 <tr class="{% cycle 'djDebugOdd' 'djDebugEven' %}">
12 <td>{{ name|escape }}</td>
13 <td>{{ signal.providing_args|join:", " }}</td>
14 <td>{{ receivers|join:", " }}</td>
15 </tr>
16 {% endfor %}
17 </tbody>
2 <thead>
3 <tr>
4 <th>{% trans "Signal" %}</th>
5 <th>{% trans "Receivers" %}</th>
6 </tr>
7 </thead>
8 <tbody>
9 {% for name, receivers in signals %}
10 <tr>
11 <td>{{ name|escape }}</td>
12 <td>{{ receivers|join:", " }}</td>
13 </tr>
14 {% endfor %}
15 </tbody>
1816 </table>
0 {% load i18n l10n %}{% load static %}
1 <div class="djdt-clearfix">
2 <ul class="djdt-stats">
3 {% for alias, info in databases %}
4 <li>
5 <strong class="djdt-label"><span data-background-color="rgb({{ info.rgb_color|join:", " }})" class="djdt-color">&#160;</span> {{ alias }}</strong>
6 <span class="djdt-info">{{ info.time_spent|floatformat:"2" }} ms ({% blocktrans count info.num_queries as num %}{{ num }} query{% plural %}{{ num }} queries{% endblocktrans %}
7 {% if info.similar_count %}
8 {% blocktrans with count=info.similar_count trimmed %}
9 including <abbr title="Similar queries are queries with the same SQL, but potentially different parameters.">{{ count }} similar</abbr>
10 {% endblocktrans %}
11 {% if info.duplicate_count %}
12 {% blocktrans with dupes=info.duplicate_count trimmed %}
13 and <abbr title="Duplicate queries are identical to each other: they execute exactly the same SQL and parameters.">{{ dupes }} duplicates</abbr>
14 {% endblocktrans %}
15 {% endif %}
16 {% endif %})</span>
17 </li>
18 {% endfor %}
19 </ul>
20 </div>
0 {% load i18n l10n %}
1 <ul>
2 {% for alias, info in databases %}
3 <li>
4 <strong><span class="djdt-color" data-djdt-styles="backgroundColor:rgb({{ info.rgb_color|join:', ' }})"></span> {{ alias }}</strong>
5 {{ info.time_spent|floatformat:"2" }} ms ({% blocktrans count info.num_queries as num %}{{ num }} query{% plural %}{{ num }} queries{% endblocktrans %}
6 {% if info.similar_count %}
7 {% blocktrans with count=info.similar_count trimmed %}
8 including <abbr title="Similar queries are queries with the same SQL, but potentially different parameters.">{{ count }} similar</abbr>
9 {% endblocktrans %}
10 {% if info.duplicate_count %}
11 {% blocktrans with dupes=info.duplicate_count trimmed %}
12 and <abbr title="Duplicate queries are identical to each other: they execute exactly the same SQL and parameters.">{{ dupes }} duplicates</abbr>
13 {% endblocktrans %}
14 {% endif %}
15 {% endif %})
16 </li>
17 {% endfor %}
18 </ul>
2119
2220 {% if queries %}
23 <table>
24 <thead>
25 <tr>
26 <th class="djdt-color">&#160;</th>
27 <th class="djdt-query" colspan="2">{% trans "Query" %}</th>
28 <th class="djdt-timeline">{% trans "Timeline" %}</th>
29 <th class="djdt-time">{% trans "Time (ms)" %}</th>
30 <th class="djdt-actions">{% trans "Action" %}</th>
31 </tr>
32 </thead>
33 <tbody>
34 {% for query in queries %}
35 <tr class="{% cycle 'djDebugOdd' 'djDebugEven' %}{% if query.is_slow %} djDebugRowWarning{% endif %}{% if query.starts_trans %} djDebugStartTransaction{% endif %}{% if query.ends_trans %} djDebugEndTransaction{% endif %}{% if query.in_trans %} djDebugInTransaction{% endif %}" id="sqlMain_{{ forloop.counter }}">
36 <td class="djdt-color"><span data-background-color="rgb({{ query.rgb_color|join:", " }})">&#160;</span></td>
37 <td class="djdt-toggle">
38 <a class="djToggleSwitch" data-toggle-name="sqlMain" data-toggle-id="{{ forloop.counter }}" data-toggle-open="+" data-toggle-close="-" href="">+</a>
39 </td>
40 <td class="djdt-query">
41 <div class="djDebugSqlWrap">
42 <div class="djDebugSql">{{ query.sql|safe }}</div>
43 </div>
44 {% if query.similar_count %}
45 <strong>
46 <span data-background-color="{{ query.similar_color }}">&#160;</span>
47 {% blocktrans with count=query.similar_count %}{{ count }} similar queries.{% endblocktrans %}
48 </strong>
49 {% endif %}
50 {% if query.duplicate_count %}
51 <strong>
52 <span data-background-color="{{ query.duplicate_color }}">&#160;</span>
53 {% blocktrans with dupes=query.duplicate_count %}Duplicated {{ dupes }} times.{% endblocktrans %}
54 </strong>
55 {% endif %}
56 </td>
57 <td class="djdt-timeline">
58 <div class="djDebugTimeline"><div class="djDebugLineChart{% if query.is_slow %} djDebugLineChartWarning{% endif %}" data-left="{{ query.start_offset|unlocalize }}%"><strong data-width="{{ query.width_ratio_relative|unlocalize }}%" data-background-color="{{ query.trace_color }}">{{ query.width_ratio }}%</strong></div></div>
59 </td>
60 <td class="djdt-time">
61 {{ query.duration|floatformat:"2" }}
62 </td>
63 <td class="djdt-actions">
64
65 {% if query.params %}
66 {% if query.is_select %}
67 <form method="post">
68 {{ query.form }}
69
70 <button formaction="{% url 'djdt:sql_select' %}" class="remoteCall">Sel</button>
71 <button formaction="{% url 'djdt:sql_explain' %}" class="remoteCall">Expl</button>
72
73 {% if query.vendor == 'mysql' %}
74 <button formaction="{% url 'djdt:sql_profile' %}" class="remoteCall">Prof</button>
75 {% endif %}
76 </form>
77 {% endif %}
78 {% endif %}
79 </td>
80 </tr>
81 <tr class="djUnselected {% cycle 'djDebugOdd' 'djDebugEven' %}{% if query.is_slow %} djDebugRowWarning{% endif %} djToggleDetails_{{ forloop.counter }}" id="sqlDetails_{{ forloop.counter }}">
82 <td colspan="2"></td>
83 <td colspan="4">
84 <div class="djSQLDetailsDiv">
85 <p><strong>{% trans "Connection:" %}</strong> {{ query.alias }}</p>
86 {% if query.iso_level %}
87 <p><strong>{% trans "Isolation level:" %}</strong> {{ query.iso_level }}</p>
88 {% endif %}
89 {% if query.trans_status %}
90 <p><strong>{% trans "Transaction status:" %}</strong> {{ query.trans_status }}</p>
91 {% endif %}
92 {% if query.stacktrace %}
93 <pre class="djdt-stack">{{ query.stacktrace }}</pre>
94 {% endif %}
95 {% if query.template_info %}
96 <table>
97 {% for line in query.template_info.context %}
98 <tr>
99 <td>{{ line.num }}</td>
100 <td><code {% if line.highlight %}class="djdt-highlighted"{% endif %}>{{ line.content }}</code></td>
101 </tr>
102 {% endfor %}
103 </table>
104 <p><strong>{{ query.template_info.name|default:_("(unknown)") }}</strong></p>
105 {% endif %}
106 </div>
107 </td>
108 </tr>
109 {% endfor %}
110 </tbody>
111 </table>
21 <table>
22 <colgroup>
23 <col>
24 <col>
25 <col>
26 <col class="djdt-width-30">
27 <col>
28 <col>
29 </colgroup>
30 <thead>
31 <tr>
32 <th></th>
33 <th colspan="2">{% trans "Query" %}</th>
34 <th>{% trans "Timeline" %}</th>
35 <th>{% trans "Time (ms)" %}</th>
36 <th>{% trans "Action" %}</th>
37 </tr>
38 </thead>
39 <tbody>
40 {% for query in queries %}
41 <tr class="{% if query.is_slow %} djDebugRowWarning{% endif %}" id="sqlMain_{{ forloop.counter }}">
42 <td><span class="djdt-color" data-djdt-styles="backgroundColor:rgb({{ query.rgb_color|join:', '}})"></span></td>
43 <td class="djdt-toggle">
44 <button type="button" class="djToggleSwitch" data-toggle-name="sqlMain" data-toggle-id="{{ forloop.counter }}">+</button>
45 </td>
46 <td>
47 <div class="djDebugSql">{{ query.sql|safe }}</div>
48 {% if query.similar_count %}
49 <strong>
50 <span class="djdt-color" data-djdt-styles="backgroundColor:{{ query.similar_color }}"></span>
51 {% blocktrans with count=query.similar_count %}{{ count }} similar queries.{% endblocktrans %}
52 </strong>
53 {% endif %}
54 {% if query.duplicate_count %}
55 <strong>
56 <span class="djdt-color" data-djdt-styles="backgroundColor:{{ query.duplicate_color }}"></span>
57 {% blocktrans with dupes=query.duplicate_count %}Duplicated {{ dupes }} times.{% endblocktrans %}
58 </strong>
59 {% endif %}
60 </td>
61 <td>
62 <svg class="djDebugLineChart{% if query.is_slow %} djDebugLineChartWarning{% endif %}{% if query.in_trans %} djDebugLineChartInTransaction{% endif %}" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 5" preserveAspectRatio="none" aria-label="{{ query.width_ratio }}%">
63 <rect x="{{ query.start_offset|unlocalize }}" y="0" height="5" width="{{ query.width_ratio|unlocalize }}" fill="{{ query.trace_color }}" />
64 {% if query.starts_trans %}
65 <line x1="{{ query.start_offset|unlocalize }}" y1="0" x2="{{ query.start_offset|unlocalize }}" y2="5" />
66 {% endif %}
67 {% if query.ends_trans %}
68 <line x1="{{ query.end_offset|unlocalize }}" y1="0" x2="{{ query.end_offset|unlocalize }}" y2="5" />
69 {% endif %}
70 </svg>
71 </td>
72 <td class="djdt-time">
73 {{ query.duration|floatformat:"2" }}
74 </td>
75 <td class="djdt-actions">
76 {% if query.params %}
77 {% if query.is_select %}
78 <form method="post">
79 {{ query.form }}
80 <button formaction="{% url 'djdt:sql_select' %}" class="remoteCall">Sel</button>
81 <button formaction="{% url 'djdt:sql_explain' %}" class="remoteCall">Expl</button>
82 {% if query.vendor == 'mysql' %}
83 <button formaction="{% url 'djdt:sql_profile' %}" class="remoteCall">Prof</button>
84 {% endif %}
85 </form>
86 {% endif %}
87 {% endif %}
88 </td>
89 </tr>
90 <tr class="djUnselected {% if query.is_slow %} djDebugRowWarning{% endif %} djToggleDetails_{{ forloop.counter }}" id="sqlDetails_{{ forloop.counter }}">
91 <td colspan="2"></td>
92 <td colspan="4">
93 <div class="djSQLDetailsDiv">
94 <p><strong>{% trans "Connection:" %}</strong> {{ query.alias }}</p>
95 {% if query.iso_level %}
96 <p><strong>{% trans "Isolation level:" %}</strong> {{ query.iso_level }}</p>
97 {% endif %}
98 {% if query.trans_status %}
99 <p><strong>{% trans "Transaction status:" %}</strong> {{ query.trans_status }}</p>
100 {% endif %}
101 {% if query.stacktrace %}
102 <pre class="djdt-stack">{{ query.stacktrace }}</pre>
103 {% endif %}
104 {% if query.template_info %}
105 <table>
106 {% for line in query.template_info.context %}
107 <tr>
108 <td>{{ line.num }}</td>
109 <td><code {% if line.highlight %}class="djdt-highlighted"{% endif %}>{{ line.content }}</code></td>
110 </tr>
111 {% endfor %}
112 </table>
113 <p><strong>{{ query.template_info.name|default:_("(unknown)") }}</strong></p>
114 {% endif %}
115 </div>
116 </td>
117 </tr>
118 {% endfor %}
119 </tbody>
120 </table>
112121 {% else %}
113 <p>{% trans "No SQL queries were recorded during this request." %}</p>
122 <p>{% trans "No SQL queries were recorded during this request." %}</p>
114123 {% endif %}
115
116 <script src="{% static 'debug_toolbar/js/toolbar.sql.js' %}" defer></script>
0 {% load i18n %}{% load static %}
0 {% load i18n %}
11 <div class="djDebugPanelTitle">
2 <a class="djDebugClose djDebugBack" href=""></a>
3 <h3>{% trans "SQL explained" %}</h3>
2 <button type="button" class="djDebugClose">Ā»</button>
3 <h3>{% trans "SQL explained" %}</h3>
44 </div>
55 <div class="djDebugPanelContent">
6 <div class="djdt-scroll">
7 <dl>
8 <dt>{% trans "Executed SQL" %}</dt>
9 <dd>{{ sql|safe }}</dd>
10 <dt>{% trans "Time" %}</dt>
11 <dd>{{ duration }} ms</dd>
12 <dt>{% trans "Database" %}</dt>
13 <dd>{{ alias }}</dd>
14 </dl>
15 <table class="djSqlExplain">
16 <thead>
17 <tr>
18 {% for h in headers %}
19 <th>{{ h|upper }}</th>
20 {% endfor %}
21 </tr>
22 </thead>
23 <tbody>
24 {% for row in result %}
25 <tr class="{% cycle 'djDebugOdd' 'djDebugEven' %}">
26 {% for column in row %}
27 <td>{{ column|escape }}</td>
28 {% endfor %}
29 </tr>
30 {% endfor %}
31 </tbody>
32 </table>
33 </div>
6 <div class="djdt-scroll">
7 <dl>
8 <dt>{% trans "Executed SQL" %}</dt>
9 <dd>{{ sql|safe }}</dd>
10 <dt>{% trans "Time" %}</dt>
11 <dd>{{ duration }} ms</dd>
12 <dt>{% trans "Database" %}</dt>
13 <dd>{{ alias }}</dd>
14 </dl>
15 <table>
16 <thead>
17 <tr>
18 {% for h in headers %}
19 <th>{{ h|upper }}</th>
20 {% endfor %}
21 </tr>
22 </thead>
23 <tbody>
24 {% for row in result %}
25 <tr>
26 {% for column in row %}
27 <td>{% if forloop.last %}<code>{% endif %}{{ column|escape }}{% if forloop.last %}</code>{% endif %}</td>
28 {% endfor %}
29 </tr>
30 {% endfor %}
31 </tbody>
32 </table>
33 </div>
3434 </div>
35
36 <script src="{% static 'debug_toolbar/js/toolbar.sql.js' %}" defer></script>
0 {% load i18n %}{% load static %}
0 {% load i18n %}
11 <div class="djDebugPanelTitle">
2 <a class="djDebugClose djDebugBack" href=""></a>
3 <h3>{% trans "SQL profiled" %}</h3>
2 <button type="button" class="djDebugClose">Ā»</button>
3 <h3>{% trans "SQL profiled" %}</h3>
44 </div>
55 <div class="djDebugPanelContent">
6 <div class="djdt-scroll">
7 {% if result %}
8 <dl>
9 <dt>{% trans "Executed SQL" %}</dt>
10 <dd>{{ sql|safe }}</dd>
11 <dt>{% trans "Time" %}</dt>
12 <dd>{{ duration }} ms</dd>
13 <dt>{% trans "Database" %}</dt>
14 <dd>{{ alias }}</dd>
15 </dl>
16 <table class="djSqlProfile">
17 <thead>
18 <tr>
19 {% for h in headers %}
20 <th>{{ h|upper }}</th>
21 {% endfor %}
22 </tr>
23 </thead>
24 <tbody>
25 {% for row in result %}
26 <tr class="{% cycle 'djDebugOdd' 'djDebugEven' %}">
27 {% for column in row %}
28 <td>{{ column|escape }}</td>
29 {% endfor %}
30 </tr>
31 {% endfor %}
32 </tbody>
33 </table>
34 {% else %}
35 <dl>
36 <dt>{% trans "Error" %}</dt>
37 <dd>{{ result_error }}</dd>
38 </dl>
39 {% endif %}
40 </div>
6 <div class="djdt-scroll">
7 {% if result %}
8 <dl>
9 <dt>{% trans "Executed SQL" %}</dt>
10 <dd>{{ sql|safe }}</dd>
11 <dt>{% trans "Time" %}</dt>
12 <dd>{{ duration }} ms</dd>
13 <dt>{% trans "Database" %}</dt>
14 <dd>{{ alias }}</dd>
15 </dl>
16 <table>
17 <thead>
18 <tr>
19 {% for h in headers %}
20 <th>{{ h|upper }}</th>
21 {% endfor %}
22 </tr>
23 </thead>
24 <tbody>
25 {% for row in result %}
26 <tr>
27 {% for column in row %}
28 <td>{{ column|escape }}</td>
29 {% endfor %}
30 </tr>
31 {% endfor %}
32 </tbody>
33 </table>
34 {% else %}
35 <dl>
36 <dt>{% trans "Error" %}</dt>
37 <dd>{{ result_error }}</dd>
38 </dl>
39 {% endif %}
40 </div>
4141 </div>
42
43 <script src="{% static 'debug_toolbar/js/toolbar.sql.js' %}" defer></script>
0 {% load i18n %}{% load static %}
0 {% load i18n %}
11 <div class="djDebugPanelTitle">
2 <a class="djDebugClose djDebugBack" href=""></a>
3 <h3>{% trans "SQL selected" %}</h3>
2 <button type="button" class="djDebugClose">Ā»</button>
3 <h3>{% trans "SQL selected" %}</h3>
44 </div>
55 <div class="djDebugPanelContent">
6 <div class="djdt-scroll">
7 <dl>
8 <dt>{% trans "Executed SQL" %}</dt>
9 <dd>{{ sql|safe }}</dd>
10 <dt>{% trans "Time" %}</dt>
11 <dd>{{ duration }} ms</dd>
12 <dt>{% trans "Database" %}</dt>
13 <dd>{{ alias }}</dd>
14 </dl>
15 {% if result %}
16 <table class="djSqlSelect">
17 <thead>
18 <tr>
19 {% for h in headers %}
20 <th>{{ h|upper }}</th>
21 {% endfor %}
22 </tr>
23 </thead>
24 <tbody>
25 {% for row in result %}
26 <tr class="{% cycle 'djDebugOdd' 'djDebugEven' %}">
27 {% for column in row %}
28 <td>{{ column|escape }}</td>
29 {% endfor %}
30 </tr>
31 {% endfor %}
32 </tbody>
33 </table>
34 {% else %}
35 <p>{% trans "Empty set" %}</p>
36 {% endif %}
37 </div>
6 <div class="djdt-scroll">
7 <dl>
8 <dt>{% trans "Executed SQL" %}</dt>
9 <dd>{{ sql|safe }}</dd>
10 <dt>{% trans "Time" %}</dt>
11 <dd>{{ duration }} ms</dd>
12 <dt>{% trans "Database" %}</dt>
13 <dd>{{ alias }}</dd>
14 </dl>
15 {% if result %}
16 <table>
17 <thead>
18 <tr>
19 {% for h in headers %}
20 <th>{{ h|upper }}</th>
21 {% endfor %}
22 </tr>
23 </thead>
24 <tbody>
25 {% for row in result %}
26 <tr>
27 {% for column in row %}
28 <td>{{ column|escape }}</td>
29 {% endfor %}
30 </tr>
31 {% endfor %}
32 </tbody>
33 </table>
34 {% else %}
35 <p>{% trans "Empty set" %}</p>
36 {% endif %}
37 </div>
3838 </div>
39
40 <script src="{% static 'debug_toolbar/js/toolbar.sql.js' %}" defer></script>
0 {% for s in stacktrace %}<span class="djdt-path">{{s.0}}/</span><span class="djdt-file">{{s.1}}</span> in <span class="djdt-func">{{s.3}}</span>(<span class="djdt-lineno">{{s.2}}</span>)
1 <span class="djdt-code">{{s.4}}</span>
2 {% if show_locals %}<pre class="djdt-locals">{{s.5|pprint}}</pre>{% endif %}
3 {% endfor %}
11
22 <h4>{% blocktrans count staticfiles_dirs|length as dirs_count %}Static file path{% plural %}Static file paths{% endblocktrans %}</h4>
33 {% if staticfiles_dirs %}
4 <ol>
4 <ol>
55 {% for prefix, staticfiles_dir in staticfiles_dirs %}
6 <li>{{ staticfiles_dir }}{% if prefix %} {% blocktrans %}(prefix {{ prefix }}){% endblocktrans %}{% endif %}</li>
6 <li>{{ staticfiles_dir }}{% if prefix %} {% blocktrans %}(prefix {{ prefix }}){% endblocktrans %}{% endif %}</li>
77 {% endfor %}
8 </ol>
8 </ol>
99 {% else %}
10 <p>{% trans "None" %}</p>
10 <p>{% trans "None" %}</p>
1111 {% endif %}
1212
1313 <h4>{% blocktrans count staticfiles_apps|length as apps_count %}Static file app{% plural %}Static file apps{% endblocktrans %}</h4>
1414 {% if staticfiles_apps %}
15 <ol>
15 <ol>
1616 {% for static_app in staticfiles_apps %}
17 <li>{{ static_app }}</li>
17 <li>{{ static_app }}</li>
1818 {% endfor %}
19 </ol>
19 </ol>
2020 {% else %}
21 <p>{% trans "None" %}</p>
21 <p>{% trans "None" %}</p>
2222 {% endif %}
2323
2424 <h4>{% blocktrans count staticfiles|length as staticfiles_count %}Static file{% plural %}Static files{% endblocktrans %}</h4>
2525 {% if staticfiles %}
26 <dl>
27 {% for staticfile in staticfiles %}
28 <dt><strong><a class="toggleTemplate" href="{{ staticfile.url }}">{{ staticfile }}</a></strong></dt>
29 <dd><samp>{{ staticfile.real_path }}</samp></dd>
30 {% endfor %}
31 </dl>
26 <dl>
27 {% for staticfile in staticfiles %}
28 <dt><strong><a class="toggleTemplate" href="{{ staticfile.url }}">{{ staticfile }}</a></strong></dt>
29 <dd><samp>{{ staticfile.real_path }}</samp></dd>
30 {% endfor %}
31 </dl>
3232 {% else %}
33 <p>{% trans "None" %}</p>
33 <p>{% trans "None" %}</p>
3434 {% endif %}
3535
3636
3737 {% for finder, payload in staticfiles_finders.items %}
38 <h4>{{ finder }} ({% blocktrans count payload|length as payload_count %}{{ payload_count }} file{% plural %}{{ payload_count }} files{% endblocktrans %})</h4>
39 <table>
38 <h4>{{ finder }} ({% blocktrans count payload|length as payload_count %}{{ payload_count }} file{% plural %}{{ payload_count }} files{% endblocktrans %})</h4>
39 <table>
4040 <thead>
41 <tr>
42 <th>{% trans 'Path' %}</th>
43 <th>{% trans 'Location' %}</th>
44 </tr>
41 <tr>
42 <th>{% trans 'Path' %}</th>
43 <th>{% trans 'Location' %}</th>
44 </tr>
4545 </thead>
4646 <tbody>
47 {% for path, real_path in payload %}
48 <tr class="{% cycle 'djDebugOdd' 'djDebugEven' %}">
49 <td>{{ path }}</td>
50 <td>{{ real_path }}</td>
51 </tr>
52 {% endfor %}
47 {% for path, real_path in payload %}
48 <tr>
49 <td>{{ path }}</td>
50 <td>{{ real_path }}</td>
51 </tr>
52 {% endfor %}
5353 </tbody>
54 </table>
54 </table>
5555 {% endfor %}
00 {% load i18n %}
11 <div class="djDebugPanelTitle">
2 <a class="djDebugClose djDebugBack" href=""></a>
3 <h3>{% trans "Template source:" %} <code>{{ template_name }}</code></h3>
2 <button type="button" class="djDebugClose">Ā»</button>
3 <h3>{% trans "Template source:" %} <code>{{ template_name }}</code></h3>
44 </div>
55 <div class="djDebugPanelContent">
6 <div class="djdt-scroll">
7 {% if not source.pygmentized %}
8 <code>{{ source }}</code>
9 {% else %}
10 {{ source }}
11 {% endif %}
12 </div>
6 <div class="djdt-scroll">
7 {% if not source.pygmentized %}
8 <code>{{ source }}</code>
9 {% else %}
10 {{ source }}
11 {% endif %}
12 </div>
1313 </div>
0 {% load i18n %}{% load static %}
0 {% load i18n %}
11 <h4>{% blocktrans count template_dirs|length as template_count %}Template path{% plural %}Template paths{% endblocktrans %}</h4>
22 {% if template_dirs %}
3 <ol>
4 {% for template in template_dirs %}
5 <li>{{ template }}</li>
6 {% endfor %}
7 </ol>
3 <ol>
4 {% for template in template_dirs %}
5 <li>{{ template }}</li>
6 {% endfor %}
7 </ol>
88 {% else %}
9 <p>{% trans "None" %}</p>
9 <p>{% trans "None" %}</p>
1010 {% endif %}
1111
1212 <h4>{% blocktrans count templates|length as template_count %}Template{% plural %}Templates{% endblocktrans %}</h4>
1313 {% if templates %}
14 <dl>
15 {% for template in templates %}
16 <dt><strong><a class="remoteCall toggleTemplate" href="{% url 'djdt:template_source' %}?template={{ template.template.name }}&amp;template_origin={{ template.template.origin_hash }}">{{ template.template.name|addslashes }}</a></strong></dt>
17 <dd><samp>{{ template.template.origin_name|addslashes }}</samp></dd>
18 {% if template.context %}
19 <dd>
20 <details>
21 <summary>{% trans "Toggle context" %}</summary>
22 <code class="djTemplateContext">{{ template.context }}</code>
23 </details>
24 </dd>
25 {% endif %}
26 {% endfor %}
27 </dl>
14 <dl>
15 {% for template in templates %}
16 <dt><strong><a class="remoteCall toggleTemplate" href="{% url 'djdt:template_source' %}?template={{ template.template.name }}&amp;template_origin={{ template.template.origin_hash }}">{{ template.template.name|addslashes }}</a></strong></dt>
17 <dd><samp>{{ template.template.origin_name|addslashes }}</samp></dd>
18 {% if template.context %}
19 <dd>
20 <details>
21 <summary>{% trans "Toggle context" %}</summary>
22 <code class="djTemplateContext">{{ template.context }}</code>
23 </details>
24 </dd>
25 {% endif %}
26 {% endfor %}
27 </dl>
2828 {% else %}
29 <p>{% trans "None" %}</p>
29 <p>{% trans "None" %}</p>
3030 {% endif %}
3131
3232 <h4>{% blocktrans count context_processors|length as context_processors_count %}Context processor{% plural %}Context processors{% endblocktrans %}</h4>
3333 {% if context_processors %}
34 <dl>
35 {% for key, value in context_processors.items %}
36 <dt><strong>{{ key|escape }}</strong></dt>
37 <dd>
38 <details>
39 <summary>{% trans "Toggle context" %}</summary>
40 <code class="djTemplateContext">{{ value|escape }}</code>
41 </details>
42 </dd>
43 {% endfor %}
44 </dl>
34 <dl>
35 {% for key, value in context_processors.items %}
36 <dt><strong>{{ key|escape }}</strong></dt>
37 <dd>
38 <details>
39 <summary>{% trans "Toggle context" %}</summary>
40 <code class="djTemplateContext">{{ value|escape }}</code>
41 </details>
42 </dd>
43 {% endfor %}
44 </dl>
4545 {% else %}
46 <p>{% trans "None" %}</p>
46 <p>{% trans "None" %}</p>
4747 {% endif %}
0 {% load i18n %}{% load static %}
0 {% load i18n %}
11 <h4>{% trans "Resource usage" %}</h4>
22 <table>
3 <colgroup>
4 <col class="djdt-width-20">
5 <col>
6 </colgroup>
7 <thead>
8 <tr>
9 <th>{% trans "Resource" %}</th>
10 <th>{% trans "Value" %}</th>
11 </tr>
12 </thead>
13 <tbody>
14 {% for key, value in rows %}
15 <tr class="{% cycle 'djDebugOdd' 'djDebugEven' %}">
16 <td>{{ key|escape }}</td>
17 <td>{{ value|escape }}</td>
18 </tr>
19 {% endfor %}
20 </tbody>
3 <colgroup>
4 <col class="djdt-width-20">
5 <col>
6 </colgroup>
7 <thead>
8 <tr>
9 <th>{% trans "Resource" %}</th>
10 <th>{% trans "Value" %}</th>
11 </tr>
12 </thead>
13 <tbody>
14 {% for key, value in rows %}
15 <tr>
16 <td>{{ key|escape }}</td>
17 <td>{{ value|escape }}</td>
18 </tr>
19 {% endfor %}
20 </tbody>
2121 </table>
2222
2323 <!-- This hidden div is populated and displayed by code in toolbar.timer.js -->
2424 <div id="djDebugBrowserTiming" class="djdt-hidden">
25 <h4>{% trans "Browser timing" %}</h4>
26 <table>
27 <colgroup>
28 <col class="djdt-width-20">
29 <col class="djdt-width-60">
30 <col class="djdt-width-20">
31 </colgroup>
32 <thead>
33 <tr>
34 <th>{% trans "Timing attribute" %}</th>
35 <th class="djdt-timeline">{% trans "Timeline" %}</th>
36 <th class="djdt-time">{% trans "Milliseconds since navigation start (+length)" %}</th>
37 </tr>
38 </thead>
39 <tbody id="djDebugBrowserTimingTableBody">
40 </tbody>
41 </table>
25 <h4>{% trans "Browser timing" %}</h4>
26 <table>
27 <colgroup>
28 <col class="djdt-width-20">
29 <col class="djdt-width-60">
30 <col class="djdt-width-20">
31 </colgroup>
32 <thead>
33 <tr>
34 <th>{% trans "Timing attribute" %}</th>
35 <th>{% trans "Timeline" %}</th>
36 <th>{% trans "Milliseconds since navigation start (+length)" %}</th>
37 </tr>
38 </thead>
39 <tbody id="djDebugBrowserTimingTableBody">
40 </tbody>
41 </table>
4242 </div>
43 <script src="{% static 'debug_toolbar/js/toolbar.timer.js' %}" defer></script>
00 {% load i18n %}
11 <table>
2 <colgroup>
3 <col style="width:15%">
4 <col style="width:15%">
5 <col>
6 </colgroup>
7 <thead>
8 <tr>
9 <th>{% trans "Package" %}</th>
10 <th>{% trans "Name" %}</th>
11 <th>{% trans "Version" %}</th>
12 </tr>
13 </thead>
14 <tbody>
15 {% for package, name, version in versions %}
16 <tr class="{% cycle 'djDebugOdd' 'djDebugEven' %}">
17 <td>{{ package }}</td>
18 <td>{{ name }}</td>
19 <td>{{ version }}</td>
20 </tr>
21 {% endfor %}
22 </tbody>
2 <colgroup>
3 <col class="djdt-width-20">
4 <col class="djdt-width-20">
5 <col>
6 </colgroup>
7 <thead>
8 <tr>
9 <th>{% trans "Package" %}</th>
10 <th>{% trans "Name" %}</th>
11 <th>{% trans "Version" %}</th>
12 </tr>
13 </thead>
14 <tbody>
15 {% for package, name, version in versions %}
16 <tr>
17 <td>{{ package }}</td>
18 <td>{{ name }}</td>
19 <td>{{ version }}</td>
20 </tr>
21 {% endfor %}
22 </tbody>
2323 </table>
00 {% load i18n static %}
11 <!DOCTYPE html>
2 <html>
3 <head>
4 </head>
5 <body>
6 <h1>{{ status_line }}</h1>
7 <h2>{% trans "Location:" %} <a id="redirect_to" href="{{ redirect_to }}">{{ redirect_to }}</a></h2>
8 <p class="notice">
9 {% trans "The Django Debug Toolbar has intercepted a redirect to the above URL for debug viewing purposes. You can click the above link to continue with the redirect as normal." %}
10 </p>
11 <script src="{% static 'debug_toolbar/js/redirect.js' %}" defer></script>
12 </body>
2 <html lang="en">
3 <head>
4 <title>Django Debug Toolbar Redirects Panel: {{ status_line }}</title>
5 <script type="module" src="{% static 'debug_toolbar/js/redirect.js' %}" async></script>
6 </head>
7 <body>
8 <h1>{{ status_line }}</h1>
9 <h2>{% trans "Location:" %} <a id="redirect_to" href="{{ redirect_to }}">{{ redirect_to }}</a></h2>
10 <p class="notice">
11 {% trans "The Django Debug Toolbar has intercepted a redirect to the above URL for debug viewing purposes. You can click the above link to continue with the redirect as normal." %}
12 </p>
13 </body>
1314 </html>
55 from collections import OrderedDict
66
77 from django.apps import apps
8 from django.conf.urls import url
98 from django.core.exceptions import ImproperlyConfigured
109 from django.template import TemplateSyntaxError
1110 from django.template.loader import render_to_string
11 from django.urls import path, resolve
12 from django.urls.exceptions import Resolver404
1213 from django.utils.module_loading import import_string
1314
1415 from debug_toolbar import settings as dt_settings
7677 else:
7778 raise
7879
79 # Handle storing toolbars in memory and fetching them later on
80
81 _store = OrderedDict()
82
8380 def should_render_panels(self):
8481 render_panels = self.config["RENDER_PANELS"]
8582 if render_panels is None:
8683 render_panels = self.request.META["wsgi.multiprocess"]
8784 return render_panels
8885
86 # Handle storing toolbars in memory and fetching them later on
87
88 _store = OrderedDict()
89
8990 def store(self):
91 # Store already exists.
92 if self.store_id:
93 return
9094 self.store_id = uuid.uuid4().hex
91 cls = type(self)
92 cls._store[self.store_id] = self
93 for _ in range(len(cls._store) - self.config["RESULTS_CACHE_SIZE"]):
94 try:
95 # collections.OrderedDict
96 cls._store.popitem(last=False)
97 except TypeError:
98 # django.utils.datastructures.SortedDict
99 del cls._store[cls._store.keyOrder[0]]
95 self._store[self.store_id] = self
96 for _ in range(self.config["RESULTS_CACHE_SIZE"], len(self._store)):
97 self._store.popitem(last=False)
10098
10199 @classmethod
102100 def fetch(cls, store_id):
127125 # Load URLs in a temporary variable for thread safety.
128126 # Global URLs
129127 urlpatterns = [
130 url(r"^render_panel/$", views.render_panel, name="render_panel")
128 path("render_panel/", views.render_panel, name="render_panel")
131129 ]
132130 # Per-panel URLs
133131 for panel_class in cls.get_panel_classes():
135133 cls._urlpatterns = urlpatterns
136134 return cls._urlpatterns
137135
136 @classmethod
137 def is_toolbar_request(cls, request):
138 """
139 Determine if the request is for a DebugToolbar view.
140 """
141 # The primary caller of this function is in the middleware which may
142 # not have resolver_match set.
143 try:
144 resolver_match = request.resolver_match or resolve(request.path)
145 except Resolver404:
146 return False
147 return resolver_match.namespaces and resolver_match.namespaces[-1] == app_name
148
138149
139150 app_name = "djdt"
140151 urlpatterns = DebugToolbar.get_urls()
77 import django
88 from django.core.exceptions import ImproperlyConfigured
99 from django.template import Node
10 from django.utils.html import escape
10 from django.template.loader import render_to_string
1111 from django.utils.safestring import mark_safe
1212
1313 from debug_toolbar import settings as dt_settings
5858 if omit_path(os.path.realpath(path)):
5959 continue
6060 text = "".join(text).strip() if text else ""
61 trace.append((path, line_no, func_name, text))
61 frame_locals = (
62 frame.f_locals
63 if dt_settings.get_config()["ENABLE_STACKTRACES_LOCALS"]
64 else None
65 )
66 trace.append((path, line_no, func_name, text, frame_locals))
6267 return trace
6368
6469
6570 def render_stacktrace(trace):
6671 stacktrace = []
6772 for frame in trace:
68 params = (escape(v) for v in chain(frame[0].rsplit(os.path.sep, 1), frame[1:]))
73 params = (v for v in chain(frame[0].rsplit(os.path.sep, 1), frame[1:]))
6974 params_dict = {str(idx): v for idx, v in enumerate(params)}
7075 try:
71 stacktrace.append(
72 '<span class="djdt-path">%(0)s/</span>'
73 '<span class="djdt-file">%(1)s</span>'
74 ' in <span class="djdt-func">%(3)s</span>'
75 '(<span class="djdt-lineno">%(2)s</span>)\n'
76 ' <span class="djdt-code">%(4)s</span>' % params_dict
77 )
76 stacktrace.append(params_dict)
7877 except KeyError:
7978 # This frame doesn't have the expected format, so skip it and move
8079 # on to the next one
8180 continue
82 return mark_safe("\n".join(stacktrace))
81
82 return mark_safe(
83 render_to_string(
84 "debug_toolbar/panels/sql_stacktrace.html",
85 {
86 "stacktrace": stacktrace,
87 "show_locals": dt_settings.get_config()["ENABLE_STACKTRACES_LOCALS"],
88 },
89 )
90 )
8391
8492
8593 def get_template_info():
136144 def get_name_from_obj(obj):
137145 if hasattr(obj, "__name__"):
138146 name = obj.__name__
139 elif hasattr(obj, "__class__") and hasattr(obj.__class__, "__name__"):
147 else:
140148 name = obj.__class__.__name__
141 else:
142 name = "<unknown>"
143149
144150 if hasattr(obj, "__module__"):
145151 module = obj.__module__
203209 return (filename, lineno, frame.f_code.co_name, lines, index)
204210
205211
212 def get_sorted_request_variable(variable):
213 """
214 Get a sorted list of variables from the request data.
215 """
216 if isinstance(variable, dict):
217 return [(k, variable.get(k)) for k in sorted(variable)]
218 else:
219 return [(k, variable.getlist(k)) for k in sorted(variable)]
220
221
206222 def get_stack(context=1):
207223 """
208224 Get a list of records for a frame and all higher (calling) frames.
0 from django.http import HttpResponse
0 from django.http import JsonResponse
11 from django.utils.html import escape
22 from django.utils.translation import gettext as _
33
1515 "Please reload the page and retry."
1616 )
1717 content = "<p>%s</p>" % escape(content)
18 scripts = []
1819 else:
1920 panel = toolbar.get_panel_by_id(request.GET["panel_id"])
2021 content = panel.content
21 return HttpResponse(content)
22 scripts = panel.scripts
23 return JsonResponse({"content": content, "scripts": scripts})
0 # Makefile for Sphinx documentation
0 # Minimal makefile for Sphinx documentation
11 #
22
3 # You can set these variables from the command line.
4 SPHINXOPTS =
5 SPHINXBUILD = sphinx-build
6 PAPER =
3 # You can set these variables from the command line, and also
4 # from the environment for the first two.
5 SPHINXOPTS ?= -n -W
6 SPHINXBUILD ?= sphinx-build
7 SOURCEDIR = .
78 BUILDDIR = _build
89
9 # User-friendly check for sphinx-build
10 ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
11 $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
12 endif
10 # Put it first so that "make" without argument is like "make help".
11 help:
12 @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
1313
14 # Internal variables.
15 PAPEROPT_a4 = -D latex_paper_size=a4
16 PAPEROPT_letter = -D latex_paper_size=letter
17 ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
18 # the i18n builder cannot share the environment and doctrees with the others
19 I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
14 .PHONY: help Makefile
2015
21 .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
22
23 help:
24 @echo "Please use \`make <target>' where <target> is one of"
25 @echo " html to make standalone HTML files"
26 @echo " dirhtml to make HTML files named index.html in directories"
27 @echo " singlehtml to make a single large HTML file"
28 @echo " pickle to make pickle files"
29 @echo " json to make JSON files"
30 @echo " htmlhelp to make HTML files and a HTML help project"
31 @echo " qthelp to make HTML files and a qthelp project"
32 @echo " devhelp to make HTML files and a Devhelp project"
33 @echo " epub to make an epub"
34 @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
35 @echo " latexpdf to make LaTeX files and run them through pdflatex"
36 @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
37 @echo " text to make text files"
38 @echo " man to make manual pages"
39 @echo " texinfo to make Texinfo files"
40 @echo " info to make Texinfo files and run them through makeinfo"
41 @echo " gettext to make PO message catalogs"
42 @echo " changes to make an overview of all changed/added/deprecated items"
43 @echo " xml to make Docutils-native XML files"
44 @echo " pseudoxml to make pseudoxml-XML files for display purposes"
45 @echo " linkcheck to check all external links for integrity"
46 @echo " doctest to run all doctests embedded in the documentation (if enabled)"
47
48 clean:
49 rm -rf $(BUILDDIR)/*
50
51 html:
52 $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
53 @echo
54 @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
55
56 dirhtml:
57 $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
58 @echo
59 @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
60
61 singlehtml:
62 $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
63 @echo
64 @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
65
66 pickle:
67 $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
68 @echo
69 @echo "Build finished; now you can process the pickle files."
70
71 json:
72 $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
73 @echo
74 @echo "Build finished; now you can process the JSON files."
75
76 htmlhelp:
77 $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
78 @echo
79 @echo "Build finished; now you can run HTML Help Workshop with the" \
80 ".hhp project file in $(BUILDDIR)/htmlhelp."
81
82 qthelp:
83 $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
84 @echo
85 @echo "Build finished; now you can run "qcollectiongenerator" with the" \
86 ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
87 @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/DjangoDebugToolbar.qhcp"
88 @echo "To view the help file:"
89 @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/DjangoDebugToolbar.qhc"
90
91 devhelp:
92 $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
93 @echo
94 @echo "Build finished."
95 @echo "To view the help file:"
96 @echo "# mkdir -p $$HOME/.local/share/devhelp/DjangoDebugToolbar"
97 @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/DjangoDebugToolbar"
98 @echo "# devhelp"
99
100 epub:
101 $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
102 @echo
103 @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
104
105 latex:
106 $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
107 @echo
108 @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
109 @echo "Run \`make' in that directory to run these through (pdf)latex" \
110 "(use \`make latexpdf' here to do that automatically)."
111
112 latexpdf:
113 $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
114 @echo "Running LaTeX files through pdflatex..."
115 $(MAKE) -C $(BUILDDIR)/latex all-pdf
116 @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
117
118 latexpdfja:
119 $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
120 @echo "Running LaTeX files through platex and dvipdfmx..."
121 $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
122 @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
123
124 text:
125 $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
126 @echo
127 @echo "Build finished. The text files are in $(BUILDDIR)/text."
128
129 man:
130 $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
131 @echo
132 @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
133
134 texinfo:
135 $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
136 @echo
137 @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
138 @echo "Run \`make' in that directory to run these through makeinfo" \
139 "(use \`make info' here to do that automatically)."
140
141 info:
142 $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
143 @echo "Running Texinfo files through makeinfo..."
144 make -C $(BUILDDIR)/texinfo info
145 @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
146
147 gettext:
148 $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
149 @echo
150 @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
151
152 changes:
153 $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
154 @echo
155 @echo "The overview file is in $(BUILDDIR)/changes."
156
157 linkcheck:
158 $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
159 @echo
160 @echo "Link check complete; look for any errors in the above output " \
161 "or in $(BUILDDIR)/linkcheck/output.txt."
162
163 doctest:
164 $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
165 @echo "Testing of doctests in the sources finished, look at the " \
166 "results in $(BUILDDIR)/doctest/output.txt."
167
168 xml:
169 $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
170 @echo
171 @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
172
173 pseudoxml:
174 $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
175 @echo
176 @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
16 # Catch-all target: route all unknown targets to Sphinx using the new
17 # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
18 %: Makefile
19 @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
00 Change log
11 ==========
22
3 Next version
4 ------------
5
6
7 3.2.1 (2021-04-14)
8 ------------------
9
10 * Fixed SQL Injection vulnerability, CVE-2021-30459. The toolbar now
11 calculates a signature on all fields for the SQL select, explain,
12 and analyze forms.
13 * Changed ``djdt.cookie.set()`` to set ``sameSite=Lax`` by default if
14 callers do not provide a value.
15 * Added ``PRETTIFY_SQL`` configuration option to support controlling
16 SQL token grouping. By default it's set to True. When set to False,
17 a performance improvement can be seen by the SQL panel.
18 * Fixed issue with toolbar expecting URL paths to start with `/__debug__/`
19 while the documentation indicates it's not required.
20
21 3.2 (2020-12-03)
22 ----------------
23
24 * Moved CI to GitHub Actions: https://github.com/jazzband/django-debug-toolbar/actions
25 * Stopped crashing when ``request.GET`` and ``request.POST`` are
26 dictionaries instead of ``QueryDict`` instances. This isn't a valid
27 use of Django but django-debug-toolbar shouldn't crash anyway.
28 * Fixed a crash in the history panel when sending a JSON POST request
29 with invalid JSON.
30 * Added missing signals to the signals panel by default.
31 * Documented how to avoid CORS errors now that we're using JavaScript
32 modules.
33 * Verified support for Python 3.9.
34 * Added a ``css`` and a ``js`` template block to
35 ``debug_toolbar/base.html`` to allow overriding CSS and JS.
36
37
38 3.2a1 (2020-10-19)
39 ------------------
40
41 * Fixed a regression where the JavaScript code crashed with an invalid
42 CSS selector when searching for an element to replace.
43 * Replaced remaining images with CSS.
44 * Continued refactoring the HTML and CSS code for simplicity, continued
45 improving the use of semantic HTML.
46 * Stopped caring about prehistoric browsers for good. Started splitting
47 up the JavaScript code to take advantage of JavaScript modules.
48 * Continued removing unused CSS.
49 * Started running Selenium tests on Travis CI.
50 * Added a system check which prevents using django-debug-toolbar without
51 any enabled panels.
52 * Added :meth:`Panel.run_checks() <debug_toolbar.panels.Panel.run_checks>` for
53 panels to verify the configuration before the application starts.
54 * Validate the static file paths specified in ``STATICFILES_DIRS``
55 exist via :class:`~debug_toolbar.panels.staticfiles.StaticFilesPanel`
56 * Introduced `prettier <https://prettier.io/>`__ to format the frontend
57 code.
58 * Started accessing history views using GET requests since they do not
59 change state on the server.
60 * Fixed a bug where unsuccessful requests (e.g. network errors) were
61 silently ignored.
62 * Started spellchecking the documentation.
63 * Removed calls to the deprecated ``request.is_ajax()`` method. These calls
64 were unnecessary now that most endpoints return JSON anyway.
65 * Removed support for Python 3.5.
66
67
68 3.1 (2020-09-21)
69 ----------------
70
71 * Fixed a crash in the history panel when sending an empty JSON POST
72 request.
73 * Made ``make example`` also set up the database and a superuser
74 account.
75 * Added a Makefile target for regenerating the django-debug-toolbar
76 screenshot.
77 * Added automatic escaping of panel titles resp. disallowed HTML tags.
78 * Removed some CSS
79 * Restructured the SQL stats template.
80 * Changed command line examples to prefer ``python -m pip`` to ``pip``.
81
82
83 3.0 (2020-09-20)
84 ----------------
85
86 * Added an ``.editorconfig`` file specifying indentation rules etc.
87 * Updated the Italian translation.
88 * Added support for Django 3.1a1. ``fetch()`` and ``jQuery.ajax`` requests are
89 now detected by the absence of a ``Accept: text/html`` header instead of the
90 jQuery-specific ``X-Requested-With`` header on Django 3.1 or better.
91 * Pruned unused CSS and removed hacks for ancient browsers.
92 * Added the new :attr:`Panel.scripts <debug_toolbar.panels.Panel.scripts>`
93 property. This property should return a list of JavaScript resources to be
94 loaded in the browser when displaying the panel. Right now, this is used by a
95 single panel, the Timer panel. Third party panels can use this property to
96 add scripts rather then embedding them in the content HTML.
97 * Switched from JSHint to ESLint. Added an ESLint job to the Travis CI matrix.
98 * Debug toolbar state which is only needed in the JavaScript code now uses
99 ``localStorage``.
100 * Updated the code to avoid a few deprecation warnings and resource warnings.
101 * Started loading JavaScript as ES6 modules.
102 * Added support for :meth:`cache.touch() <django.core.caches.cache.touch>` when
103 using django-debug-toolbar.
104 * Eliminated more inline CSS.
105 * Updated ``tox.ini`` and ``Makefile`` to use isort>=5.
106 * Increased RESULTS_CACHE_SIZE to 25 to better support AJAX requests.
107 * Fixed the close button CSS by explicitly specifying the
108 ``box-sizing`` property.
109 * Simplified the ``isort`` configuration by taking advantage of isort's
110 ``black`` profile.
111 * Added :class:`~debug_toolbar.panels.history.HistoryPanel` including support
112 for AJAX requests.
113
114 **Backwards incompatible changes**
115 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
116
117 * Loading panel content no longer executes the scripts elements embedded in the
118 HTML. Third party panels that require JavaScript resources should now use the
119 :attr:`Panel.scripts <debug_toolbar.panels.Panel.scripts>` property.
120 * Removed support for end of life Django 1.11. The minimum supported Django is
121 now 2.2.
122 * The Debug Toolbar now loads a `JavaScript module`_. Typical local development
123 using Django ``runserver`` is not impacted. However, if your application
124 server and static files server are at different origins, you may see CORS
125 errors in your browser's development console. See the "Cross-Origin Request
126 Blocked" section of the :doc:`installation docs <installation>` for details
127 on how to resolve this issue.
128
129 .. _JavaScript module: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules
130
131 2.2 (2020-01-31)
132 ----------------
133
134 * Removed support for end of life Django 2.0 and 2.1.
135 * Added support for Python 3.8.
136 * Add locals() option for SQL panel.
137 * Added support for Django 3.0.
138
139
140 2.1 (2019-11-12)
141 ----------------
142
143 * Changed the Travis CI matrix to run style checks first.
144 * Exposed the ``djdt.init`` function too.
145 * Small improvements to the code to take advantage of newer Django APIs
146 and avoid warnings because of deprecated code.
147 * Verified compatibility with the upcoming Django 3.0 (at the time of
148 writing).
149
150
3151 2.0 (2019-06-20)
4152 ----------------
5153
6 * Updated ``StaticFilesPanel`` to be compatible with Django 3.0.
7 * The ``ProfilingPanel`` is now enabled but inactive by default.
154 * Updated :class:`~debug_toolbar.panels.staticfiles.StaticFilesPanel` to be
155 compatible with Django 3.0.
156 * The :class:`~debug_toolbar.panels.profiling.ProfilingPanel` is now enabled
157 but inactive by default.
8158 * Fixed toggling of table rows in the profiling panel UI.
9 * The ``ProfilingPanel`` no longer skips remaining panels or middlewares.
159 * The :class:`~debug_toolbar.panels.profiling.ProfilingPanel` no longer skips
160 remaining panels or middlewares.
10161 * Improved the installation documentation.
11162 * Fixed a possible crash in the template panel.
12163 * Added support for psycopg2 ``Composed`` objects.
18169 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
19170 * Removed support for Python 2.
20171 * Removed support for Django's deprecated ``MIDDLEWARE_CLASSES`` setting.
21 * Restructured ``Panel`` to execute more like the new-style Django MIDDLEWARE.
22 The ``Panel.__init__()`` method is now passed ``get_response`` as the first
23 positional argument. The ``Panel.process_request()`` method must now always
172 * Restructured :class:`debug_toolbar.panels.Panel` to execute more like the
173 new-style Django MIDDLEWARE. The ``Panel.__init__()`` method is now passed
174 ``get_response`` as the first positional argument. The
175 :meth:`debug_toolbar.panels.Panel.process_request` method must now always
24176 return a response. Usually this is the response returned by
25177 ``get_response()`` but the panel may also return a different response as is
26 the case in the ``RedirectsPanel``. Third party panels must adjust to this
27 new architecture. ``Panel.process_response()`` and ``Panel.process_view()``
28 have been removed as a result of this change.
178 the case in the :class:`~debug_toolbar.panels.redirects.RedirectsPanel`.
179 Third party panels must adjust to this new architecture.
180 ``Panel.process_response()`` and ``Panel.process_view()`` have been removed
181 as a result of this change.
29182
30183 The deprecated API, ``debug_toolbar.panels.DebugPanel``, has been removed.
31 Third party panels should use ``debug_toolbar.panels.Panel`` instead.
184 Third party panels should use :class:`debug_toolbar.panels.Panel` instead.
32185
33186 The following deprecated settings have been removed:
34187
48201 altogether.
49202 * Reformatted the code using `black <https://github.com/ambv/black>`__.
50203 * Added the Django mail panel to the list of third-party panels.
51 * Convert system check errors to warnings to accomodate exotic
204 * Convert system check errors to warnings to accommodate exotic
52205 configurations.
53206 * Fixed a crash when explaining raw querysets.
54 * Fixed an obscure unicode error with binary data fields.
207 * Fixed an obscure Unicode error with binary data fields.
55208 * Added MariaDB and Python 3.7 builds to the CI.
56209
57210 1.10.1 (2018-09-11)
58211 -------------------
59212
60213 * Fixed a problem where the duplicate query detection breaks for
61 non-hashable query parameters.
214 unhashable query parameters.
62215 * Added support for structured types when recording SQL.
63216 * Made Travis CI also run one test no PostgreSQL.
64217 * Added fallbacks for inline images in CSS.
75228 changes were required.
76229 * Removed the jQuery dependency. This means that django-debug-toolbar
77230 now requires modern browsers with support for ``fetch``, ``classList``
78 etc.
231 etc. The ``JQUERY_URL`` setting is also removed because it isn't
232 necessary anymore. If you depend on jQuery, integrate it yourself.
79233 * Added support for the server timing header.
80234 * Added a differentiation between similar and duplicate queries. Similar
81235 queries are what duplicate queries used to be (same SQL, different
82236 parameters).
83237 * Stopped hiding frames from Django's contrib apps in stacktraces by
84238 default.
85 * Lots of small cleanups and bugfixes.
239 * Lots of small cleanups and bug fixes.
86240
87241 1.9.1 (2017-11-15)
88242 ------------------
95249 This version is compatible with Django 2.0 and requires Django 1.8 or
96250 later.
97251
98 Bugfixes
99 ~~~~~~~~
252 Bug fixes
253 ~~~~~~~~~
100254
101255 * The profiling panel now escapes reported data resulting in valid HTML.
102 * Many minor cleanups and bugfixes.
256 * Many minor cleanups and bug fixes.
103257
104258 1.8 (2017-05-05)
105259 ----------------
128282 skipped by default to avoid panel sizes going into hundreds of
129283 megabytes of HTML.
130284
131 Bugfixes
132 ~~~~~~~~
285 Bug fixes
286 ~~~~~~~~~
133287
134288 * All views are now decorated with
135289 ``debug_toolbar.decorators.require_show_toolbar`` preventing unauthorized
142296 1.7 (2017-03-05)
143297 ----------------
144298
145 Bugfixes
146 ~~~~~~~~
299 Bug fixes
300 ~~~~~~~~~
147301
148302 * Recursive template extension is now understood.
149303 * Deprecation warnings were fixed.
158312 1.6 (2016-10-05)
159313 ----------------
160314
161 The debug toolbar was adopted by jazzband.
315 The debug toolbar was adopted by Jazzband.
162316
163317 Removed features
164318 ~~~~~~~~~~~~~~~~
168322 ``DEBUG_TOOLBAR_PATCH_SETTINGS`` setting has also been removed as it is now
169323 unused. See the :doc:`installation documentation <installation>` for details.
170324
171 Bugfixes
172 ~~~~~~~~
325 Bug fixes
326 ~~~~~~~~~
173327
174328 * The ``DebugToolbarMiddleware`` now also supports Django 1.10's ``MIDDLEWARE``
175329 setting.
181335
182336 Support for Python 3.2 is dropped.
183337
184 Bugfixes
185 ~~~~~~~~
338 Bug fixes
339 ~~~~~~~~~
186340
187341 * Restore compatibility with sqlparse ā‰„ 0.2.0.
188342 * Add compatibility with Bootstrap 4, Pure CSS, MDL, etc.
202356 to only record stats when the toolbar is going to be inserted into the
203357 response.
204358
205 Bugfixes
206 ~~~~~~~~
359 Bug fixes
360 ~~~~~~~~~
207361
208362 * Response time for requests of projects with numerous media files has
209363 been improved.
220374 * The ``SHOW_TOOLBAR_CALLBACK`` accepts a callable.
221375 * The toolbar now provides a :ref:`javascript-api`.
222376
223 Bugfixes
224 ~~~~~~~~
377 Bug fixes
378 ~~~~~~~~~
225379
226380 * The toolbar handle cannot leave the visible area anymore when the toolbar is
227381 collapsed.
239393
240394 * The ``JQUERY_URL`` setting defines where the toolbar loads jQuery from.
241395
242 Bugfixes
243 ~~~~~~~~
396 Bug fixes
397 ~~~~~~~~~
244398
245399 * The toolbar now always loads a private copy of jQuery in order to avoid
246 using an incompatible version. It no longer attemps to integrate with AMD.
400 using an incompatible version. It no longer attempts to integrate with AMD.
247401
248402 This private copy is available in ``djdt.jQuery``. Third-party panels are
249403 encouraged to use it because it should be as stable as the toolbar itself.
259413 * The SQL panel colors queries depending on the stack level.
260414 * The Profiler panel allows configuring the maximum depth.
261415
262 Bugfixes
263 ~~~~~~~~
416 Bug fixes
417 ~~~~~~~~~
264418
265419 * Support languages where lowercase and uppercase strings may have different
266420 lengths.
0 =============
1 System checks
2 =============
3
4 The following :doc:`system checks <topics/checks>` help verify the Django
5 Debug Toolbar setup and configuration:
6
7 * **debug_toolbar.W001**: ``debug_toolbar.middleware.DebugToolbarMiddleware``
8 is missing from ``MIDDLEWARE``.
9 * **debug_toolbar.W002**: ``debug_toolbar.middleware.DebugToolbarMiddleware``
10 occurs multiple times in ``MIDDLEWARE``.
11 * **debug_toolbar.W003**: ``debug_toolbar.middleware.DebugToolbarMiddleware``
12 occurs before ``django.middleware.gzip.GZipMiddleware`` in ``MIDDLEWARE``.
13 * **debug_toolbar.W004**: ``debug_toolbar`` is incompatible with
14 ``MIDDLEWARE_CLASSES`` setting.
15 * **debug_toolbar.W005**: Setting ``DEBUG_TOOLBAR_PANELS`` is empty.
2121 FROM "page_page"
2222 WHERE "page_page"."id" = 1
2323
24 >>> print p.template.name
24 >>> print(p.template.name)
2525 SELECT "page_template"."id",
2626 "page_template"."name",
2727 "page_template"."description"
4242 INNER JOIN "page_template" ON ("page_page"."template_id" = "page_template"."id")
4343 WHERE "page_page"."id" = 1
4444
45 >>> print p.template.name
45 >>> print(p.template.name)
4646 Home
0 # -*- coding: utf-8 -*-
0 # Configuration file for the Sphinx documentation builder.
11 #
2 # Django Debug Toolbar documentation build configuration file, created by
3 # sphinx-quickstart on Sun Oct 27 13:18:25 2013.
4 #
5 # This file is execfile()d with the current directory set to its
6 # containing dir.
7 #
8 # Note that not all possible configuration values are present in this
9 # autogenerated file.
10 #
11 # All configuration values have a default; values that are commented out
12 # serve to show the default.
2 # This file only contains a selection of the most common options. For a full
3 # list see the documentation:
4 # https://www.sphinx-doc.org/en/master/usage/configuration.html
135
14 import datetime
15 import sys
16 import os
17
18 os.environ['DJANGO_SETTINGS_MODULE'] = 'example.settings'
19 sys.path.append(os.path.dirname(os.path.dirname(__file__)))
6 # -- Path setup --------------------------------------------------------------
207
218 # If extensions (or modules to document with autodoc) are in another directory,
229 # add these directories to sys.path here. If the directory is relative to the
2310 # documentation root, use os.path.abspath to make it absolute, like shown here.
24 #sys.path.insert(0, os.path.abspath('.'))
2511
26 # -- General configuration ------------------------------------------------
12 import datetime
13 import os
14 import sys
2715
28 # If your documentation needs a minimal Sphinx version, state it here.
29 #needs_sphinx = '1.0'
16 os.environ["DJANGO_SETTINGS_MODULE"] = "example.settings"
17 sys.path.append(os.path.dirname(os.path.dirname(__file__)))
18
19
20 # -- Project information -----------------------------------------------------
21
22 project = "Django Debug Toolbar"
23 copyright = "{}, Django Debug Toolbar developers and contributors"
24 copyright = copyright.format(datetime.date.today().year)
25
26 # The full version, including alpha/beta/rc tags
27 release = "3.2.1"
28
29
30 # -- General configuration ---------------------------------------------------
3031
3132 # Add any Sphinx extension module names here, as strings. They can be
3233 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
3334 # ones.
3435 extensions = [
35 'sphinx.ext.autodoc',
36 'sphinx.ext.intersphinx',
36 "sphinx.ext.autodoc",
37 "sphinx.ext.intersphinx",
3738 ]
3839
3940 # Add any paths that contain templates here, relative to this directory.
40 templates_path = ['_templates']
41
42 # The suffix of source filenames.
43 source_suffix = '.rst'
44
45 # The encoding of source files.
46 #source_encoding = 'utf-8-sig'
47
48 # The master toctree document.
49 master_doc = 'index'
50
51 # General information about the project.
52 project = u'Django Debug Toolbar'
53 copyright = u'{}, Django Debug Toolbar developers and contributors'
54 copyright = copyright.format(datetime.date.today().year)
55
56 # The version info for the project you're documenting, acts as replacement for
57 # |version| and |release|, also used in various other places throughout the
58 # built documents.
59 #
60 # The short X.Y version.
61 version = '2.0'
62 # The full version, including alpha/beta/rc tags.
63 release = '2.0'
64
65 # The language for content autogenerated by Sphinx. Refer to documentation
66 # for a list of supported languages.
67 #language = None
68
69 # There are two options for replacing |today|: either, you set today to some
70 # non-false value, then it is used:
71 #today = ''
72 # Else, today_fmt is used as the format for a strftime call.
73 #today_fmt = '%B %d, %Y'
41 templates_path = ["_templates"]
7442
7543 # List of patterns, relative to source directory, that match files and
7644 # directories to ignore when looking for source files.
77 exclude_patterns = ['_build']
78
79 # The reST default role (used for this markup: `text`) to use for all
80 # documents.
81 #default_role = None
82
83 # If true, '()' will be appended to :func: etc. cross-reference text.
84 #add_function_parentheses = True
85
86 # If true, the current module name will be prepended to all description
87 # unit titles (such as .. function::).
88 #add_module_names = True
89
90 # If true, sectionauthor and moduleauthor directives will be shown in the
91 # output. They are ignored by default.
92 #show_authors = False
93
94 # The name of the Pygments (syntax highlighting) style to use.
95 pygments_style = 'sphinx'
96
97 # A list of ignored prefixes for module index sorting.
98 #modindex_common_prefix = []
99
100 # If true, keep warnings as "system message" paragraphs in the built documents.
101 #keep_warnings = False
45 # This pattern also affects html_static_path and html_extra_path.
46 exclude_patterns = ["_build"]
10247
10348
104 # -- Options for HTML output ----------------------------------------------
49 # -- Options for HTML output -------------------------------------------------
10550
10651 # The theme to use for HTML and HTML Help pages. See the documentation for
10752 # a list of builtin themes.
108 html_theme = 'default'
109
110 # Theme options are theme-specific and customize the look and feel of a theme
111 # further. For a list of options available for each theme, see the
112 # documentation.
113 #html_theme_options = {}
114
115 # Add any paths that contain custom themes here, relative to this directory.
116 #html_theme_path = []
117
118 # The name for this set of Sphinx documents. If None, it defaults to
119 # "<project> v<release> documentation".
120 #html_title = None
121
122 # A shorter title for the navigation bar. Default is the same as html_title.
123 #html_short_title = None
124
125 # The name of an image file (relative to this directory) to place at the top
126 # of the sidebar.
127 #html_logo = None
128
129 # The name of an image file (within the static path) to use as favicon of the
130 # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
131 # pixels large.
132 #html_favicon = None
53 html_theme = "default"
13354
13455 # Add any paths that contain custom static files (such as style sheets) here,
13556 # relative to this directory. They are copied after the builtin static files,
13657 # so a file named "default.css" will overwrite the builtin "default.css".
137 html_static_path = ['_static']
58 # html_static_path = ['_static']
13859
139 # Add any extra paths that contain custom files (such as robots.txt or
140 # .htaccess) here, relative to this directory. These files are copied
141 # directly to the root of the documentation.
142 #html_extra_path = []
143
144 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
145 # using the given strftime format.
146 #html_last_updated_fmt = '%b %d, %Y'
147
148 # If true, SmartyPants will be used to convert quotes and dashes to
149 # typographically correct entities.
150 #html_use_smartypants = True
151
152 # Custom sidebar templates, maps document names to template names.
153 #html_sidebars = {}
154
155 # Additional templates that should be rendered to pages, maps page names to
156 # template names.
157 #html_additional_pages = {}
158
159 # If false, no module index is generated.
160 #html_domain_indices = True
161
162 # If false, no index is generated.
163 #html_use_index = True
164
165 # If true, the index is split into individual pages for each letter.
166 #html_split_index = False
167
168 # If true, links to the reST sources are added to the pages.
169 #html_show_sourcelink = True
170
171 # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
172 #html_show_sphinx = True
173
174 # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
175 #html_show_copyright = True
176
177 # If true, an OpenSearch description file will be output, and all pages will
178 # contain a <link> tag referring to it. The value of this option must be the
179 # base URL from which the finished HTML is served.
180 #html_use_opensearch = ''
181
182 # This is the file name suffix for HTML files (e.g. ".xhtml").
183 #html_file_suffix = None
184
185 # Output file base name for HTML help builder.
186 htmlhelp_basename = 'DjangoDebugToolbardoc'
187
188
189 # -- Options for LaTeX output ---------------------------------------------
190
191 latex_elements = {
192 # The paper size ('letterpaper' or 'a4paper').
193 #'papersize': 'letterpaper',
194
195 # The font size ('10pt', '11pt' or '12pt').
196 #'pointsize': '10pt',
197
198 # Additional stuff for the LaTeX preamble.
199 #'preamble': '',
60 intersphinx_mapping = {
61 "https://docs.python.org/": None,
62 "https://docs.djangoproject.com/en/dev/": "https://docs.djangoproject.com/en/dev/_objects/",
20063 }
20164
202 # Grouping the document tree into LaTeX files. List of tuples
203 # (source start file, target name, title,
204 # author, documentclass [howto, manual, or own class]).
205 latex_documents = [
206 ('index', 'DjangoDebugToolbar.tex', u'Django Debug Toolbar Documentation',
207 u'Django Debug Toolbar developers and contributors', 'manual'),
208 ]
209
210 # The name of an image file (relative to this directory) to place at the top of
211 # the title page.
212 #latex_logo = None
213
214 # For "manual" documents, if this is true, then toplevel headings are parts,
215 # not chapters.
216 #latex_use_parts = False
217
218 # If true, show page references after internal links.
219 #latex_show_pagerefs = False
220
221 # If true, show URL addresses after external links.
222 #latex_show_urls = False
223
224 # Documents to append as an appendix to all manuals.
225 #latex_appendices = []
226
227 # If false, no module index is generated.
228 #latex_domain_indices = True
229
230
231 # -- Options for manual page output ---------------------------------------
232
233 # One entry per manual page. List of tuples
234 # (source start file, name, description, authors, manual section).
235 man_pages = [
236 ('index', 'djangodebugtoolbar', u'Django Debug Toolbar Documentation',
237 [u'Django Debug Toolbar developers and contributors'], 1)
238 ]
239
240 # If true, show URL addresses after external links.
241 #man_show_urls = False
242
243
244 # -- Options for Texinfo output -------------------------------------------
245
246 # Grouping the document tree into Texinfo files. List of tuples
247 # (source start file, target name, title, author,
248 # dir menu entry, description, category)
249 texinfo_documents = [
250 ('index', 'DjangoDebugToolbar', u'Django Debug Toolbar Documentation',
251 u'Django Debug Toolbar developers and contributors', 'DjangoDebugToolbar', 'One line description of project.',
252 'Miscellaneous'),
253 ]
254
255 # Documents to append as an appendix to all manuals.
256 #texinfo_appendices = []
257
258 # If false, no module index is generated.
259 #texinfo_domain_indices = True
260
261 # How to display URL addresses: 'footnote', 'no', or 'inline'.
262 #texinfo_show_urls = 'footnote'
263
264 # If true, do not generate a @detailmenu in the "Top" node's menu.
265 #texinfo_no_detailmenu = False
266
267
268 # Example configuration for intersphinx: refer to the Python standard library.
269 intersphinx_mapping = {
270 'https://docs.python.org/': None,
271 'https://docs.djangoproject.com/en/dev/': 'https://docs.djangoproject.com/en/dev/_objects/',
272 }
273
274 # -- Options for Read the Docs --------------------------------------------
275
276 RTD_NEW_THEME = True
65 # -- Options for Read the Docs -----------------------------------------------
27766
27867
27968 def setup(app):
280 """ Configure documentation via Sphinx extension
281 """
69 """Configure documentation via Sphinx extension"""
28270 # Add the :setting: role for intersphinx linking to Django's docs
28371 app.add_crossref_type(
28472 directivename="setting",
77
88 The debug toolbar ships with a default configuration that is considered
99 sane for the vast majority of Django projects. Don't copy-paste blindly
10 the default values shown below into you settings module! It's useless and
10 the default values shown below into your settings module! It's useless and
1111 it'll prevent you from taking advantage of better defaults that may be
1212 introduced in future releases.
1313
1919 default value is::
2020
2121 DEBUG_TOOLBAR_PANELS = [
22 'debug_toolbar.panels.history.HistoryPanel',
2223 'debug_toolbar.panels.versions.VersionsPanel',
2324 'debug_toolbar.panels.timer.TimerPanel',
2425 'debug_toolbar.panels.settings.SettingsPanel',
3132 'debug_toolbar.panels.signals.SignalsPanel',
3233 'debug_toolbar.panels.logging.LoggingPanel',
3334 'debug_toolbar.panels.redirects.RedirectsPanel',
35 'debug_toolbar.panels.profiling.ProfilingPanel',
3436 ]
3537
3638 This setting allows you to:
7981
8082 * ``RESULTS_CACHE_SIZE``
8183
82 Default: ``10``
84 Default: ``25``
8385
8486 The toolbar keeps up to this many results in memory.
8587
137139 calls. Enabling stacktraces can increase the CPU time used when executing
138140 queries.
139141
142 * ``ENABLE_STACKTRACES_LOCALS``
143
144 Default: ``False``
145
146 Panels: cache, SQL
147
148 If set to ``True``, this will show locals() for each stacktrace piece of
149 code for SQL queries and cache calls.
150 Enabling stacktraces locals will increase the CPU time used when executing
151 queries and will give too verbose information in most cases, but is useful
152 for debugging complex cases.
153
154 .. caution::
155 This will expose all members from each frame of the stacktrace. This can
156 potentially expose sensitive or private information. It's advised to only
157 use this configuration locally.
158
140159 * ``HIDE_IN_STACKTRACES``
141160
142 Default: ``('socketserver', 'threading', 'wsgiref', 'debug_toolbar',
143 'django')``.
161 Default::
162
163 (
164 "socketserver",
165 "threading",
166 "wsgiref",
167 "debug_toolbar",
168 "django.db",
169 "django.core.handlers",
170 "django.core.servers",
171 "django.utils.decorators",
172 "django.utils.deprecation",
173 "django.utils.functional",
174 )
175
144176
145177 Panels: cache, SQL
146178
147179 Useful for eliminating server-related entries which can result
148180 in enormous DOM structures and toolbar rendering delays.
149181
182 * ``PRETTIFY_SQL``
183
184 Default: ``True``
185
186 Panel: SQL
187
188 Controls SQL token grouping.
189
190 Token grouping allows pretty print of similar tokens,
191 like aligned indentation for every selected field.
192
193 When set to ``True``, it might cause render slowdowns
194 when a view make long SQL textual queries.
195
196 **Without grouping**::
197
198 SELECT "auth_user"."id", "auth_user"."password", "auth_user"."last_login", "auth_user"."is_superuser", "auth_user"."username", "auth_user"."first_name", "auth_user"."last_name"
199 FROM "auth_user"
200 WHERE "auth_user"."username" = '''test_username'''
201 LIMIT 21
202
203 **With grouping**::
204
205 SELECT "auth_user"."id",
206 "auth_user"."password",
207 "auth_user"."last_login",
208 "auth_user"."is_superuser",
209 "auth_user"."username",
210 "auth_user"."first_name",
211 "auth_user"."last_name",
212 FROM "auth_user"
213 WHERE "auth_user"."username" = '''test_username'''
214 LIMIT 21
215
150216 * ``PROFILER_MAX_DEPTH``
151217
152218 Default: ``10``
164230
165231 If set to ``True`` then a template's context will be included with it in the
166232 template debug panel. Turning this off is useful when you have large
167 template contexts, or you have template contexts with lazy datastructures
233 template contexts, or you have template contexts with lazy data structures
168234 that you don't want to be evaluated.
169235
170236 * ``SKIP_TEMPLATE_PREFIXES``
3131 Once you've obtained a checkout, you should create a virtualenv_ and install
3232 the libraries required for working on the Debug Toolbar::
3333
34 $ pip install -r requirements_dev.txt
34 $ python -m pip install -r requirements_dev.txt
3535
3636 .. _virtualenv: https://virtualenv.pypa.io/
3737
5252
5353 Once you've set up a development environment as explained above, you can run
5454 the test suite for the versions of Django and Python installed in that
55 environment::
55 environment using the SQLite database::
5656
5757 $ make test
5858
7878 $ DJANGO_SELENIUM_TESTS=true make coverage
7979 $ DJANGO_SELENIUM_TESTS=true tox
8080
81 At this time, there isn't an easy way to test against databases other than
82 SQLite.
81 To test via `tox` against other databases, you'll need to create the user,
82 database and assign the proper permissions. For PostgreSQL in a `psql`
83 shell (note this allows the debug_toolbar user the permission to create
84 databases)::
85
86 psql> CREATE USER debug_toolbar WITH PASSWORD 'debug_toolbar';
87 psql> ALTER USER debug_toolbar CREATEDB;
88 psql> CREATE DATABASE debug_toolbar;
89 psql> GRANT ALL PRIVILEGES ON DATABASE debug_toolbar to debug_toolbar;
90
91 For MySQL/MariaDB in a `mysql` shell::
92
93 mysql> CREATE DATABASE debug_toolbar;
94 mysql> CREATE USER 'debug_toolbar'@'localhost' IDENTIFIED BY 'debug_toolbar';
95 mysql> GRANT ALL PRIVILEGES ON debug_toolbar.* TO 'debug_toolbar'@'localhost';
96 mysql> GRANT ALL PRIVILEGES ON test_debug_toolbar.* TO 'debug_toolbar'@'localhost';
97
8398
8499 Style
85100 -----
86101
87 The Django Debug Toolbar uses `black <https://github.com/python/black>`__
88 to format code and additionally uses flake8 and isort. You can reformat
89 the code using::
102 The Django Debug Toolbar uses `black <https://github.com/psf/black>`__ to
103 format code and additionally uses flake8 and isort. You can reformat the code
104 using::
90105
91106 $ make style
92107
131146
132147 Commit.
133148
149 #. Update the screenshot in ``README.rst``.
150
151 .. code-block:: console
152
153 $ make example/django-debug-toolbar.png
154
155 Commit.
156
134157 #. Bump version numbers in ``docs/changes.rst``, ``docs/conf.py``,
135 ``README.rst`` and ``setup.py``. Add the release date to
136 ``docs/changes.rst``. Commit.
158 ``README.rst``, ``debug_toolbar/__init__.py`` and ``setup.py``.
159 Add the release date to ``docs/changes.rst``. Commit.
137160
138161 #. Tag the new version.
139162
55
66 installation
77 configuration
8 checks
89 tips
910 panels
1011 commands
88
99 The recommended way to install the Debug Toolbar is via pip_::
1010
11 $ pip install django-debug-toolbar
11 $ python -m pip install django-debug-toolbar
1212
1313 If you aren't familiar with pip, you may also obtain a copy of the
1414 ``debug_toolbar`` directory and add it to your Python path.
1818 To test an upcoming release, you can install the in-development version
1919 instead with the following command::
2020
21 $ pip install -e git+https://github.com/jazzband/django-debug-toolbar.git#egg=django-debug-toolbar
21 $ python -m pip install -e git+https://github.com/jazzband/django-debug-toolbar.git#egg=django-debug-toolbar
2222
2323 Prerequisites
2424 -------------
4242 Setting up URLconf
4343 ------------------
4444
45 Add the Debug Toolbar's URLs to your project's URLconf as follows::
45 Add the Debug Toolbar's URLs to your project's URLconf::
4646
47 import debug_toolbar
4748 from django.conf import settings
48 from django.conf.urls import include, url # For django versions before 2.0
49 from django.urls import include, path # For django versions from 2.0 and up
49 from django.urls import include, path
5050
51 if settings.DEBUG:
52 import debug_toolbar
53 urlpatterns = [
54 path('__debug__/', include(debug_toolbar.urls)),
55
56 # For django versions before 2.0:
57 # url(r'^__debug__/', include(debug_toolbar.urls)),
58
59 ] + urlpatterns
51 urlpatterns = [
52 ...
53 path('__debug__/', include(debug_toolbar.urls)),
54 ]
6055
6156 This example uses the ``__debug__`` prefix, but you can use any prefix that
6257 doesn't clash with your application's URLs. Note the lack of quotes around
8782 ------------------------
8883
8984 The Debug Toolbar is shown only if your IP address is listed in the
90 :django:setting:`INTERNAL_IPS` setting. This means that for local
91 development, you *must* add ``'127.0.0.1'`` to :django:setting:`INTERNAL_IPS`;
85 :setting:`INTERNAL_IPS` setting. This means that for local
86 development, you *must* add ``'127.0.0.1'`` to :setting:`INTERNAL_IPS`;
9287 you'll need to create this setting if it doesn't already exist in your
9388 settings module::
9489
10196 You can change the logic of determining whether or not the Debug Toolbar
10297 should be shown with the :ref:`SHOW_TOOLBAR_CALLBACK <SHOW_TOOLBAR_CALLBACK>`
10398 option. This option allows you to specify a custom function for this purpose.
99
100 Troubleshooting
101 ---------------
102
103 On some platforms, the Django ``runserver`` command may use incorrect content
104 types for static assets. To guess content types, Django relies on the
105 :mod:`mimetypes` module from the Python standard library, which itself relies
106 on the underlying platform's map files. If you find improper content types for
107 certain files, it is most likely that the platform's map files are incorrect or
108 need to be updated. This can be achieved, for example, by installing or
109 updating the ``mailcap`` package on a Red Hat distribution, ``mime-support`` on
110 a Debian distribution, or by editing the keys under ``HKEY_CLASSES_ROOT`` in
111 the Windows registry.
112
113 Cross-Origin Request Blocked
114 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
115
116 The Debug Toolbar loads a `JavaScript module`_. Typical local development using
117 Django ``runserver`` is not impacted. However, if your application server and
118 static files server are at different origins, you may see `CORS errors`_ in
119 your browser's development console:
120
121 .. code-block:: text
122
123 Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost/static/debug_toolbar/js/toolbar.js. (Reason: CORS header ā€˜Access-Control-Allow-Originā€™ missing).
124
125 Or
126
127 .. code-block:: text
128
129 Access to script at 'http://localhost/static/debug_toolbar/js/toolbar.js' from origin 'http://localhost:8000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
130
131 To resolve, configure your static files server to add the
132 `Access-Control-Allow-Origin header`_ with the origin of the application
133 server. For example, if your application server is at ``http://example.com``,
134 and your static files are served by NGINX, add:
135
136 .. code-block:: nginx
137
138 add_header Access-Control-Allow-Origin http://example.com;
139
140 And for Apache:
141
142 .. code-block:: apache
143
144 Header add Access-Control-Allow-Origin http://example.com
145
146 .. _JavaScript module: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules
147 .. _CORS errors: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSMissingAllowOrigin
148 .. _Access-Control-Allow-Origin header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
00 @ECHO OFF
1
2 pushd %~dp0
13
24 REM Command file for Sphinx documentation
35
46 if "%SPHINXBUILD%" == "" (
57 set SPHINXBUILD=sphinx-build
68 )
9 set SOURCEDIR=.
710 set BUILDDIR=_build
8 set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
9 set I18NSPHINXOPTS=%SPHINXOPTS% .
10 if NOT "%PAPER%" == "" (
11 set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
12 set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
13 )
1411
1512 if "%1" == "" goto help
1613
17 if "%1" == "help" (
18 :help
19 echo.Please use `make ^<target^>` where ^<target^> is one of
20 echo. html to make standalone HTML files
21 echo. dirhtml to make HTML files named index.html in directories
22 echo. singlehtml to make a single large HTML file
23 echo. pickle to make pickle files
24 echo. json to make JSON files
25 echo. htmlhelp to make HTML files and a HTML help project
26 echo. qthelp to make HTML files and a qthelp project
27 echo. devhelp to make HTML files and a Devhelp project
28 echo. epub to make an epub
29 echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
30 echo. text to make text files
31 echo. man to make manual pages
32 echo. texinfo to make Texinfo files
33 echo. gettext to make PO message catalogs
34 echo. changes to make an overview over all changed/added/deprecated items
35 echo. xml to make Docutils-native XML files
36 echo. pseudoxml to make pseudoxml-XML files for display purposes
37 echo. linkcheck to check all external links for integrity
38 echo. doctest to run all doctests embedded in the documentation if enabled
39 goto end
40 )
41
42 if "%1" == "clean" (
43 for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
44 del /q /s %BUILDDIR%\*
45 goto end
46 )
47
48
49 %SPHINXBUILD% 2> nul
14 %SPHINXBUILD% >NUL 2>NUL
5015 if errorlevel 9009 (
5116 echo.
5217 echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
5924 exit /b 1
6025 )
6126
62 if "%1" == "html" (
63 %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
64 if errorlevel 1 exit /b 1
65 echo.
66 echo.Build finished. The HTML pages are in %BUILDDIR%/html.
67 goto end
68 )
27 %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
28 goto end
6929
70 if "%1" == "dirhtml" (
71 %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
72 if errorlevel 1 exit /b 1
73 echo.
74 echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
75 goto end
76 )
77
78 if "%1" == "singlehtml" (
79 %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
80 if errorlevel 1 exit /b 1
81 echo.
82 echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
83 goto end
84 )
85
86 if "%1" == "pickle" (
87 %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
88 if errorlevel 1 exit /b 1
89 echo.
90 echo.Build finished; now you can process the pickle files.
91 goto end
92 )
93
94 if "%1" == "json" (
95 %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
96 if errorlevel 1 exit /b 1
97 echo.
98 echo.Build finished; now you can process the JSON files.
99 goto end
100 )
101
102 if "%1" == "htmlhelp" (
103 %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
104 if errorlevel 1 exit /b 1
105 echo.
106 echo.Build finished; now you can run HTML Help Workshop with the ^
107 .hhp project file in %BUILDDIR%/htmlhelp.
108 goto end
109 )
110
111 if "%1" == "qthelp" (
112 %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
113 if errorlevel 1 exit /b 1
114 echo.
115 echo.Build finished; now you can run "qcollectiongenerator" with the ^
116 .qhcp project file in %BUILDDIR%/qthelp, like this:
117 echo.^> qcollectiongenerator %BUILDDIR%\qthelp\DjangoDebugToolbar.qhcp
118 echo.To view the help file:
119 echo.^> assistant -collectionFile %BUILDDIR%\qthelp\DjangoDebugToolbar.ghc
120 goto end
121 )
122
123 if "%1" == "devhelp" (
124 %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
125 if errorlevel 1 exit /b 1
126 echo.
127 echo.Build finished.
128 goto end
129 )
130
131 if "%1" == "epub" (
132 %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
133 if errorlevel 1 exit /b 1
134 echo.
135 echo.Build finished. The epub file is in %BUILDDIR%/epub.
136 goto end
137 )
138
139 if "%1" == "latex" (
140 %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
141 if errorlevel 1 exit /b 1
142 echo.
143 echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
144 goto end
145 )
146
147 if "%1" == "latexpdf" (
148 %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
149 cd %BUILDDIR%/latex
150 make all-pdf
151 cd %BUILDDIR%/..
152 echo.
153 echo.Build finished; the PDF files are in %BUILDDIR%/latex.
154 goto end
155 )
156
157 if "%1" == "latexpdfja" (
158 %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
159 cd %BUILDDIR%/latex
160 make all-pdf-ja
161 cd %BUILDDIR%/..
162 echo.
163 echo.Build finished; the PDF files are in %BUILDDIR%/latex.
164 goto end
165 )
166
167 if "%1" == "text" (
168 %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
169 if errorlevel 1 exit /b 1
170 echo.
171 echo.Build finished. The text files are in %BUILDDIR%/text.
172 goto end
173 )
174
175 if "%1" == "man" (
176 %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
177 if errorlevel 1 exit /b 1
178 echo.
179 echo.Build finished. The manual pages are in %BUILDDIR%/man.
180 goto end
181 )
182
183 if "%1" == "texinfo" (
184 %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
185 if errorlevel 1 exit /b 1
186 echo.
187 echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
188 goto end
189 )
190
191 if "%1" == "gettext" (
192 %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
193 if errorlevel 1 exit /b 1
194 echo.
195 echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
196 goto end
197 )
198
199 if "%1" == "changes" (
200 %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
201 if errorlevel 1 exit /b 1
202 echo.
203 echo.The overview file is in %BUILDDIR%/changes.
204 goto end
205 )
206
207 if "%1" == "linkcheck" (
208 %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
209 if errorlevel 1 exit /b 1
210 echo.
211 echo.Link check complete; look for any errors in the above output ^
212 or in %BUILDDIR%/linkcheck/output.txt.
213 goto end
214 )
215
216 if "%1" == "doctest" (
217 %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
218 if errorlevel 1 exit /b 1
219 echo.
220 echo.Testing of doctests in the sources finished, look at the ^
221 results in %BUILDDIR%/doctest/output.txt.
222 goto end
223 )
224
225 if "%1" == "xml" (
226 %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
227 if errorlevel 1 exit /b 1
228 echo.
229 echo.Build finished. The XML files are in %BUILDDIR%/xml.
230 goto end
231 )
232
233 if "%1" == "pseudoxml" (
234 %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
235 if errorlevel 1 exit /b 1
236 echo.
237 echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
238 goto end
239 )
30 :help
31 %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
24032
24133 :end
34 popd
88
99 The following panels are enabled by default.
1010
11 History
12 ~~~~~~~
13
14 .. class:: debug_toolbar.panels.history.HistoryPanel
15
16 This panel shows the history of requests made and allows switching to a past
17 snapshot of the toolbar to view that request's stats.
18
1119 Version
1220 ~~~~~~~
1321
14 Path: ``debug_toolbar.panels.versions.VersionsPanel``
22 .. class:: debug_toolbar.panels.versions.VersionsPanel
1523
1624 Shows versions of Python, Django, and installed apps if possible.
1725
1826 Timer
1927 ~~~~~
2028
21 Path: ``debug_toolbar.panels.timer.TimerPanel``
29 .. class:: debug_toolbar.panels.timer.TimerPanel
2230
2331 Request timer.
2432
2533 Settings
2634 ~~~~~~~~
2735
28 Path: ``debug_toolbar.panels.settings.SettingsPanel``
36 .. class:: debug_toolbar.panels.settings.SettingsPanel
2937
3038 A list of settings in settings.py.
3139
3240 Headers
3341 ~~~~~~~
3442
35 Path: ``debug_toolbar.panels.headers.HeadersPanel``
43 .. class:: debug_toolbar.panels.headers.HeadersPanel
3644
3745 This panels shows the HTTP request and response headers, as well as a
3846 selection of values from the WSGI environment.
4452 Request
4553 ~~~~~~~
4654
47 Path: ``debug_toolbar.panels.request.RequestPanel``
55 .. class:: debug_toolbar.panels.request.RequestPanel
4856
4957 GET/POST/cookie/session variable display.
5058
5159 SQL
5260 ~~~
5361
54 Path: ``debug_toolbar.panels.sql.SQLPanel``
62 .. class:: debug_toolbar.panels.sql.SQLPanel
5563
5664 SQL queries including time to execute and links to EXPLAIN each query.
5765
5866 Template
5967 ~~~~~~~~
6068
61 Path: ``debug_toolbar.panels.templates.TemplatesPanel``
69 .. class:: debug_toolbar.panels.templates.TemplatesPanel
6270
6371 Templates and context used, and their template paths.
6472
6573 Static files
6674 ~~~~~~~~~~~~
6775
68 Path: ``debug_toolbar.panels.staticfiles.StaticFilesPanel``
69
70 Used static files and their locations (via the staticfiles finders).
76 .. class:: debug_toolbar.panels.staticfiles.StaticFilesPanel
77
78 Used static files and their locations (via the ``staticfiles`` finders).
7179
7280 Cache
7381 ~~~~~
7482
75 Path: ``debug_toolbar.panels.cache.CachePanel``
83 .. class:: debug_toolbar.panels.cache.CachePanel
7684
7785 Cache queries. Is incompatible with Django's per-site caching.
7886
7987 Signal
8088 ~~~~~~
8189
82 Path: ``debug_toolbar.panels.signals.SignalsPanel``
83
84 List of signals, their args and receivers.
90 .. class:: debug_toolbar.panels.signals.SignalsPanel
91
92 List of signals and receivers.
8593
8694 Logging
8795 ~~~~~~~
8896
89 Path: ``debug_toolbar.panels.logging.LoggingPanel``
97 .. class:: debug_toolbar.panels.logging.LoggingPanel
9098
9199 Logging output via Python's built-in :mod:`logging` module.
92100
93101 Redirects
94102 ~~~~~~~~~
95103
96 Path: ``debug_toolbar.panels.redirects.RedirectsPanel``
104 .. class:: debug_toolbar.panels.redirects.RedirectsPanel
97105
98106 When this panel is enabled, the debug toolbar will show an intermediate page
99107 upon redirect so you can view any debug information prior to redirecting. This
109117 Profiling
110118 ~~~~~~~~~
111119
112 Path: ``debug_toolbar.panels.profiling.ProfilingPanel``
120 .. class:: debug_toolbar.panels.profiling.ProfilingPanel
113121
114122 Profiling information for the processing of the request.
115123
168176 Inspector panel also logs to the console by default, but may be instructed not
169177 to.
170178
179 LDAP Tracing
180 ~~~~~~~~~~~~
181
182 URL: https://github.com/danyi1212/django-windowsauth
183
184 Path: ``windows_auth.panels.LDAPPanel``
185
186 LDAP Operations performed during the request, including timing, request and response messages,
187 the entries received, write changes list, stack-tracing and error debugging.
188 This panel also shows connection usage metrics when it is collected.
189 `Check out the docs <https://django-windowsauth.readthedocs.io/en/latest/howto/debug_toolbar.html>`_.
190
171191 Line Profiler
172192 ~~~~~~~~~~~~~
173193
174 URL: https://github.com/dmclain/django-debug-toolbar-line-profiler
194 URL: https://github.com/mikekeda/django-debug-toolbar-line-profiler
175195
176196 Path: ``debug_toolbar_line_profiler.panel.ProfilingPanel``
177197
215235
216236 Path: ``neo4j_panel.Neo4jPanel``
217237
218 Trace neo4j rest API calls in your django application, this also works for neo4django and neo4jrestclient, support for py2neo is on its way.
238 Trace neo4j rest API calls in your Django application, this also works for neo4django and neo4jrestclient, support for py2neo is on its way.
219239
220240 Pympler
221241 ~~~~~~~
233253
234254 Path: ``ddt_request_history.panels.request_history.RequestHistoryPanel``
235255
236 Switch between requests to view their stats. Also adds support for viewing stats for ajax requests.
256 Switch between requests to view their stats. Also adds support for viewing stats for AJAX requests.
257
258 Requests
259 ~~~~~~~~
260
261 URL: https://github.com/marceltschoppch/django-requests-debug-toolbar
262
263 Path: ``requests_panel.panel.RequestsDebugPanel``
264
265 Lists HTTP requests made with the popular `requests <https://requests.readthedocs.io/>`_ library.
237266
238267 Sites
239268 ~~~~~
304333 be decorated with ``debug_toolbar.decorators.require_show_toolbar`` to prevent
305334 unauthorized access. There is no public CSS API at this time.
306335
307 .. autoclass:: debug_toolbar.panels.Panel(*args, **kwargs)
336 .. autoclass:: debug_toolbar.panels.Panel
308337
309338 .. autoattribute:: debug_toolbar.panels.Panel.nav_title
310339
318347
319348 .. autoattribute:: debug_toolbar.panels.Panel.content
320349
350 .. autoattribute:: debug_toolbar.panels.Panel.scripts
351
321352 .. automethod:: debug_toolbar.panels.Panel.get_urls
322353
323354 .. automethod:: debug_toolbar.panels.Panel.enable_instrumentation
331362 .. automethod:: debug_toolbar.panels.Panel.process_request
332363
333364 .. automethod:: debug_toolbar.panels.Panel.generate_stats
365
366 .. automethod:: debug_toolbar.panels.Panel.run_checks
334367
335368 .. _javascript-api:
336369
344377
345378 Closes the topmost level (window/panel/toolbar)
346379
347 .. js:function:: djdt.cookie.get
380 .. js:function:: djdt.cookie.get(key)
348381
349382 This is a helper function to fetch values stored in the cookies.
350383
351 :param string key: The key for the value to be fetched.
352
353 .. js:function:: djdt.cookie.set
384 :param key: The key for the value to be fetched.
385
386 .. js:function:: djdt.cookie.set(key, value, options)
354387
355388 This is a helper function to set a value stored in the cookies.
356389
357 :param string key: The key to be used.
358
359 :param string value: The value to be set.
360
361 :param Object options: The options for the value to be set. It should contain
362 the properties ``expires`` and ``path``.
390 :param key: The key to be used.
391
392 :param value: The value to be set.
393
394 :param options: The options for the value to be set. It should contain the
395 properties ``expires`` and ``path``.
363396
364397 .. js:function:: djdt.hide_toolbar
365398
0 backend
1 backends
2 checkbox
3 contrib
4 django
5 fallbacks
6 flamegraph
7 flatpages
8 frontend
9 inlining
10 isort
11 Jazzband
12 jinja
13 jQuery
14 jrestclient
15 js
16 Makefile
17 memcache
18 memcached
19 middleware
20 middlewares
21 multi
22 neo
23 profiler
24 psycopg
25 py
26 pylibmc
27 Pympler
28 querysets
29 refactoring
30 spooler
31 stacktrace
32 stacktraces
33 timeline
34 Transifex
35 unhashable
36 uWSGI
37 validator
38 Werkzeug
66 The Debug Toolbar will only display when ``DEBUG = True`` in your project's
77 settings (see :ref:`Show Toolbar Callback <SHOW_TOOLBAR_CALLBACK>`) and your
88 IP address must also match an entry in your project's ``INTERNAL_IPS`` setting
9 (see :ref:`internal-ips`). It will also only display if the mimetype of the
9 (see :ref:`internal-ips`). It will also only display if the MIME type of the
1010 response is either ``text/html`` or ``application/xhtml+xml`` and contains a
1111 closing ``</body>`` tag.
1212
1313 Be aware of middleware ordering and other middleware that may intercept
14 requests and return responses. Putting the debug toolbar middleware *after*
15 the Flatpage middleware, for example, means the toolbar will not show up on
16 flatpages.
14 requests and return responses. Putting the debug toolbar middleware *after* the
15 ``FlatpageFallbackMiddleware`` middleware, for example, means the toolbar will
16 not show up on flatpages.
1717
1818 Browsers have become more aggressive with caching static assets, such as
1919 JavaScript and CSS files. Check your browser's development console, and if
6262 you can remove them permanently by customizing the ``DEBUG_TOOLBAR_PANELS``
6363 setting.
6464
65 By default, data gathered during the last 10 requests is kept in memory. This
65 By default, data gathered during the last 25 requests is kept in memory. This
6666 allows you to use the toolbar on a page even if you have browsed to a few
6767 other pages since you first loaded that page. You can reduce memory
6868 consumption by setting the ``RESULTS_CACHE_SIZE`` configuration option to a
1212 How to
1313 ------
1414
15 The test project requires a working installation of Django::
15 The example project requires a working installation of Django::
1616
17 $ pip install Django
17 $ python -m pip install Django
1818
19 The following commands must be run from the root directory of a checkout of
20 the debug toolbar, ie. the directory that contains ``example/``.
19 The following command must run from the root directory of Django Debug Toolbar,
20 i.e. the directory that contains ``example/``::
2121
22 Before running the example for the first time, you must create a database::
22 $ make example
2323
24 $ python -m django migrate --settings=example.settings
24 This will create a database, superuser, and run the Django development server.
25 The superuser's username will be the same as the current OS user and the
26 password is "p".
2527
26 Then you can use the following command to run the example::
28 If you'd like to run these steps individually, use the following commands.
29 Again, run from the root directory of Django Debug Toolbar.
2730
28 $ python -m django runserver --settings=example.settings
31 Create a database::
32
33 $ python example/manage.py migrate
34
35 Create a superuser::
36
37 $ python example/manage.py createsuperuser
38
39 Run the Django development server::
40
41 $ python example/manage.py runserver
42
43 You can change the database used by specifying the ``DB_BACKEND``
44 environment variable::
45
46 $ DB_BACKEND=postgresql python example/manage.py migrate
47 $ DB_BACKEND=postgresql python example/manage.py runserver
0 import argparse
1 import importlib
2 import os
3 import signal
4 import subprocess
5
6 from selenium.webdriver.common.keys import Keys
7 from selenium.webdriver.support import expected_conditions as EC
8 from selenium.webdriver.support.wait import WebDriverWait
9
10
11 def parse_args():
12 parser = argparse.ArgumentParser()
13 parser.add_argument("--browser", required=True)
14 parser.add_argument("--headless", action="store_true")
15 parser.add_argument("--outfile", "-o", required=True)
16 parser.add_argument("--width", type=int, default=900)
17 parser.add_argument("--height", type=int, default=700)
18 return parser.parse_args()
19
20
21 def create_webdriver_options(browser, headless):
22 mod = importlib.import_module(f"selenium.webdriver.{browser}.options")
23 options = mod.Options()
24 if headless:
25 options.headless = True
26 return options
27
28
29 def create_webdriver(browser, headless):
30 mod = importlib.import_module(f"selenium.webdriver.{browser}.webdriver")
31 return mod.WebDriver(options=create_webdriver_options(browser, headless))
32
33
34 def example_server():
35 return subprocess.Popen(["make", "example"])
36
37
38 def set_viewport_size(selenium, width, height):
39 script = """
40 return [
41 window.outerWidth - window.innerWidth + arguments[0],
42 window.outerHeight - window.innerHeight + arguments[1],
43 ];
44 """
45 window_width, window_height = selenium.execute_script(script, width, height)
46 selenium.set_window_size(window_width, window_height)
47
48
49 def submit_form(selenium, data):
50 url = selenium.current_url
51 for name, value in data.items():
52 el = selenium.find_element_by_name(name)
53 el.send_keys(value)
54 el.send_keys(Keys.RETURN)
55 WebDriverWait(selenium, timeout=5).until(EC.url_changes(url))
56
57
58 def main():
59 args = parse_args()
60 with example_server() as p:
61 try:
62 with create_webdriver(args.browser, args.headless) as selenium:
63 set_viewport_size(selenium, args.width, args.height)
64
65 selenium.get("http://localhost:8000/admin/login/")
66 submit_form(selenium, {"username": os.environ["USER"], "password": "p"})
67
68 selenium.get("http://localhost:8000/admin/auth/user/")
69 # Close the admin sidebar.
70 el = selenium.find_element_by_id("toggle-nav-sidebar")
71 el.click()
72 # Open the SQL panel.
73 el = selenium.find_element_by_id("djdt-SQLPanel")
74 el.click()
75
76 selenium.save_screenshot(args.outfile)
77 finally:
78 p.send_signal(signal.SIGTERM)
79
80
81 if __name__ == "__main__":
82 main()
7070 }
7171 }
7272
73 # To use another database, set the DJANGO_DATABASE_ENGINE environment variable.
74 if os.environ.get("DJANGO_DATABASE_ENGINE", "").lower() == "postgresql":
75 # % su postgres
76 # % createuser debug_toolbar
77 # % createdb debug_toolbar -O debug_toolbar
73 # To use another database, set the DB_BACKEND environment variable.
74 if os.environ.get("DB_BACKEND", "").lower() == "postgresql":
75 # See docs/contributing for instructions on configuring PostgreSQL.
7876 DATABASES = {
7977 "default": {
80 "ENGINE": "django.db.backends.postgresql_psycopg2",
78 "ENGINE": "django.db.backends.postgresql",
8179 "NAME": "debug_toolbar",
8280 "USER": "debug_toolbar",
81 "PASSWORD": "debug_toolbar",
8382 }
8483 }
85 if os.environ.get("DJANGO_DATABASE_ENGINE", "").lower() == "mysql":
86 # % mysql
87 # mysql> CREATE DATABASE debug_toolbar;
88 # mysql> GRANT ALL PRIVILEGES ON debug_toolbar.* TO 'debug_toolbar'@'localhost';
84 if os.environ.get("DB_BACKEND", "").lower() == "mysql":
85 # See docs/contributing for instructions on configuring MySQL/MariaDB.
8986 DATABASES = {
9087 "default": {
9188 "ENGINE": "django.db.backends.mysql",
9289 "NAME": "debug_toolbar",
9390 "USER": "debug_toolbar",
91 "PASSWORD": "debug_toolbar",
9492 }
9593 }
9694
00 body {
11 color: green;
2 }āŽ
2 }
00 {% load cache %}
11 <!DOCTYPE html>
22 <html>
3 <head>
3 <head>
44 <meta http-equiv="content-type" content="text/html; charset=utf-8">
55 <title>Index of Tests</title>
6 </head>
7 <body>
6 </head>
7 <body>
88 <h1>Index of Tests</h1>
99 {% cache 10 index_cache %}
10 <ul>
10 <ul>
1111 <li><a href="/jquery/">jQuery 3.3.1</a></li>
1212 <li><a href="/mootools/">MooTools 1.6.0</a></li>
1313 <li><a href="/prototype/">Prototype 1.7.3.0</a></li>
14 </ul>
15 <p><a href="/admin/">Django Admin</a></p>
14 </ul>
15 <p><a href="/admin/">Django Admin</a></p>
1616 {% endcache %}
17 </body>
17 </body>
1818 </html>
00 <!DOCTYPE html>
11 <html>
2 <head>
3 <meta http-equiv="content-type" content="text/html; charset=utf-8">
4 <title>jQuery Test</title>
5 <style>
6 .hide {display:none;}
7 </style>
8 <script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
9 <script type="text/javascript">
10 $(document).ready(function() {
11 $('p.hide').show();
12 $('#v').text($.fn.jquery);
13 });
14 </script>
15 </head>
16 <body>
17 <h1>jQuery Test</h1>
18 <p class="hide">If you see this, jQuery <strong id="v"></strong> is working.</p>
19 </body>
2 <head>
3 <meta http-equiv="content-type" content="text/html; charset=utf-8">
4 <title>jQuery Test</title>
5 <script src="//ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
6 <script>
7 $(document).ready(function() {
8 $('p.djdt-hidden').show();
9 $('#v').text($.fn.jquery);
10 });
11 </script>
12 </head>
13 <body>
14 <h1>jQuery Test</h1>
15 <p class="djdt-hidden">If you see this, jQuery <strong id="v"></strong> is working.</p>
16 </body>
2017 </html>
00 <!DOCTYPE html>
11 <html>
2 <head>
3 <meta http-equiv="content-type" content="text/html; charset=utf-8">
4 <title>MooTools Test</title>
5 <style>
6 .hide {display:none;}
7 </style>
8 <script src="//ajax.googleapis.com/ajax/libs/mootools/1.6.0/mootools.min.js"></script>
9 <script type="text/javascript">
10 window.addEvent('domready', function() {
11 $$('p.hide').setStyle('display', 'block');
12 $('v').set('text', MooTools.version);
13 });
14 </script>
15 </head>
16 <body>
17 <h1>MooTools Test</h1>
18 <p class="hide">If you see this, MooTools <strong id="v"></strong> is working.</p>
19 </body>
2 <head>
3 <meta http-equiv="content-type" content="text/html; charset=utf-8">
4 <title>MooTools Test</title>
5 <script src="//ajax.googleapis.com/ajax/libs/mootools/1.6.0/mootools.min.js"></script>
6 <script>
7 window.addEvent('domready', function() {
8 $$('p.djdt-hidden').setStyle('display', 'block');
9 $('v').set('text', MooTools.version);
10 });
11 </script>
12 </head>
13 <body>
14 <h1>MooTools Test</h1>
15 <p class="djdt-hidden">If you see this, MooTools <strong id="v"></strong> is working.</p>
16 </body>
2017 </html>
00 <!DOCTYPE html>
11 <html>
2 <head>
3 <meta http-equiv="content-type" content="text/html; charset=utf-8">
4 <title>Prototype Test</title>
5 <style>
6 .hide {display:none;}
7 </style>
8 <script src="//ajax.googleapis.com/ajax/libs/prototype/1.7.3.0/prototype.js"></script>
9 <script type="text/javascript">
10 document.observe('dom:loaded', function() {
11 $('showme').removeClassName('hide');
12 $('v').textContent = Prototype.Version;
13 });
14 </script>
15 </head>
16 <body>
17 <h1>Prototype Test</h1>
18 <p class="hide" id="showme">If you see this, Prototype <strong id="v"></strong> is working.</p>
19 </body>
2 <head>
3 <meta http-equiv="content-type" content="text/html; charset=utf-8">
4 <title>Prototype Test</title>
5 <script src="//ajax.googleapis.com/ajax/libs/prototype/1.7.3.0/prototype.js"></script>
6 <script>
7 document.observe('dom:loaded', function() {
8 $('showme').removeClassName('djdt-hidden');
9 $('v').textContent = Prototype.Version;
10 });
11 </script>
12 </head>
13 <body>
14 <h1>Prototype Test</h1>
15 <p class="djdt-hidden" id="showme">If you see this, Prototype <strong id="v"></strong> is working.</p>
16 </body>
2017 </html>
0 from django.conf import settings
1 from django.conf.urls import include, url
20 from django.contrib import admin
1 from django.urls import include, path
32 from django.views.generic import TemplateView
43
4 import debug_toolbar
5
56 urlpatterns = [
6 url(r"^$", TemplateView.as_view(template_name="index.html")),
7 url(r"^jquery/$", TemplateView.as_view(template_name="jquery/index.html")),
8 url(r"^mootools/$", TemplateView.as_view(template_name="mootools/index.html")),
9 url(r"^prototype/$", TemplateView.as_view(template_name="prototype/index.html")),
10 url(r"^admin/", admin.site.urls),
7 path("", TemplateView.as_view(template_name="index.html")),
8 path("jquery/", TemplateView.as_view(template_name="jquery/index.html")),
9 path("mootools/", TemplateView.as_view(template_name="mootools/index.html")),
10 path("prototype/", TemplateView.as_view(template_name="prototype/index.html")),
11 path("admin/", admin.site.urls),
12 path("__debug__/", include(debug_toolbar.urls)),
1113 ]
12
13 if settings.DEBUG:
14 import debug_toolbar
15
16 urlpatterns += [url(r"^__debug__/", include(debug_toolbar.urls))]
0 {
1 "devDependencies": {
2 "eslint": "^7.10.0",
3 "prettier": "^2.1.2"
4 }
5 }
1111 isort
1212 selenium
1313 tox
14 black
1415
1516 # Documentation
1617
1718 Sphinx
19 sphinxcontrib-spelling
1820
1921 # Other tools
2022
0 [egg_info]
1 tag_svn_revision = false
0 [metadata]
1 name = django-debug-toolbar
2 version = 3.2.1
3 description = A configurable set of panels that display various debug information about the current request/response.
4 long_description = file: README.rst
5 author = Rob Hudson
6 author_email = rob@cogit8.org
7 url = https://github.com/jazzband/django-debug-toolbar
8 download_url = https://pypi.org/project/django-debug-toolbar/
9 license = BSD
10 license_files = LICENSE
11 classifiers =
12 Development Status :: 5 - Production/Stable
13 Environment :: Web Environment
14 Framework :: Django
15 Framework :: Django :: 2.2
16 Framework :: Django :: 3.0
17 Framework :: Django :: 3.1
18 Intended Audience :: Developers
19 License :: OSI Approved :: BSD License
20 Operating System :: OS Independent
21 Programming Language :: Python
22 Programming Language :: Python :: 3
23 Programming Language :: Python :: 3 :: Only
24 Programming Language :: Python :: 3.6
25 Programming Language :: Python :: 3.7
26 Programming Language :: Python :: 3.8
27 Programming Language :: Python :: 3.9
28 Topic :: Software Development :: Libraries :: Python Modules
29
30 [options]
31 python_requires = >=3.6
32 install_requires =
33 Django >= 2.2
34 sqlparse >= 0.2.0
35 packages = find:
36 include_package_data = true
37 zip_safe = false
38
39 [options.packages.find]
40 exclude =
41 example
42 tests
43 tests.*
244
345 [flake8]
4 exclude = .tox,venv,conf.py
5 ignore = E203,W503,W601
6 max-line-length = 88
46 extend-ignore = E203, E501
747
848 [isort]
949 combine_as_imports = true
10 default_section = THIRDPARTY
11 include_trailing_comma = true
12 known_first_party = debug_toolbar
13 multi_line_output = 3
14 not_skip = __init__.py
15 force_grid_wrap = 0
16 line_length = 88
50 profile = black
00 #!/usr/bin/env python3
11
2 from io import open
2 from setuptools import setup
33
4 from setuptools import find_packages, setup
5
6
7 def readall(path):
8 with open(path, encoding="utf-8") as fp:
9 return fp.read()
10
11
12 setup(
13 name="django-debug-toolbar",
14 version="2.0",
15 description="A configurable set of panels that display various debug "
16 "information about the current request/response.",
17 long_description=readall("README.rst"),
18 author="Rob Hudson",
19 author_email="rob@cogit8.org",
20 url="https://github.com/jazzband/django-debug-toolbar",
21 download_url="https://pypi.org/project/django-debug-toolbar/",
22 license="BSD",
23 packages=find_packages(exclude=("tests.*", "tests", "example")),
24 python_requires=">=3.5",
25 install_requires=["Django>=1.11", "sqlparse>=0.2.0"],
26 include_package_data=True,
27 zip_safe=False, # because we're including static files
28 classifiers=[
29 "Development Status :: 5 - Production/Stable",
30 "Environment :: Web Environment",
31 "Framework :: Django",
32 "Framework :: Django :: 1.11",
33 "Framework :: Django :: 2.0",
34 "Framework :: Django :: 2.1",
35 "Framework :: Django :: 2.2",
36 "Intended Audience :: Developers",
37 "License :: OSI Approved :: BSD License",
38 "Operating System :: OS Independent",
39 "Programming Language :: Python",
40 "Programming Language :: Python :: 3",
41 "Programming Language :: Python :: 3 :: Only",
42 "Programming Language :: Python :: 3.5",
43 "Programming Language :: Python :: 3.6",
44 "Programming Language :: Python :: 3.7",
45 "Topic :: Software Development :: Libraries :: Python Modules",
46 ],
47 )
4 setup()
00 body {
11 color: green;
2 }āŽ
2 }
4242
4343 msg = self._formatMessage(msg, "\n".join(default_msg))
4444 raise self.failureException(msg)
45
46
47 class IntegrationTestCase(TestCase):
48 """Base TestCase for tests involving clients making requests."""
49
50 def setUp(self):
51 # The HistoryPanel keeps track of previous stores in memory.
52 # This bleeds into other tests and violates their idempotency.
53 # Clear the store before each test.
54 for key in list(DebugToolbar._store.keys()):
55 del DebugToolbar._store[key]
56 super().setUp()
00 import io
11 import sys
22
3 import django
34 from django.contrib.auth.models import User
45 from django.core import management
5 from django.db.backends import utils as db_backends_utils
6 from django.db import connection
67 from django.test import TestCase
78 from django.test.utils import override_settings
9
10 if connection.vendor == "postgresql" and django.VERSION >= (3, 0, 0):
11 from django.db.backends.postgresql import base as base_module
12 else:
13 from django.db.backends import utils as base_module
814
915
1016 @override_settings(DEBUG=True)
1117 class DebugSQLShellTestCase(TestCase):
1218 def setUp(self):
13 self.original_cursor_wrapper = db_backends_utils.CursorDebugWrapper
19 self.original_wrapper = base_module.CursorDebugWrapper
1420 # Since debugsqlshell monkey-patches django.db.backends.utils, we can
1521 # test it simply by loading it, without executing it. But we have to
1622 # undo the monkey-patch on exit.
1925 management.load_command_class(app_name, command_name)
2026
2127 def tearDown(self):
22 db_backends_utils.CursorDebugWrapper = self.original_cursor_wrapper
28 base_module.CursorDebugWrapper = self.original_wrapper
2329
2430 def test_command(self):
2531 original_stdout, sys.stdout = sys.stdout, io.StringIO()
77
88 class Binary(models.Model):
99 field = models.BinaryField()
10
11
12 try:
13 from django.db.models import JSONField
14 except ImportError: # Django<3.1
15 try:
16 from django.contrib.postgres.fields import JSONField
17 except ImportError: # psycopg2 not installed
18 JSONField = None
19
20
21 if JSONField:
22
23 class PostgresJSON(models.Model):
24 field = JSONField()
1010 cache.cache.set("foo", "bar")
1111 cache.cache.get("foo")
1212 cache.cache.delete("foo")
13 self.assertFalse(cache.cache.touch("foo"))
14 cache.cache.set("foo", "bar")
15 self.assertTrue(cache.cache.touch("foo"))
1316 # Verify that the cache has a valid clear method.
1417 cache.cache.clear()
15 self.assertEqual(len(self.panel.calls), 4)
18 self.assertEqual(len(self.panel.calls), 7)
1619
1720 def test_recording_caches(self):
1821 self.assertEqual(len(self.panel.calls), 0)
3336 self.assertNotIn("cafƩ", self.panel.content)
3437 self.panel.generate_stats(self.request, response)
3538 # ensure the panel renders correctly.
36 self.assertIn("cafƩ", self.panel.content)
37 self.assertValidHTML(self.panel.content)
39 content = self.panel.content
40 self.assertIn("cafƩ", content)
41 self.assertValidHTML(content)
3842
3943 def test_generate_server_timing(self):
4044 self.assertEqual(len(self.panel.calls), 0)
0 from django.test import override_settings
1
2 from debug_toolbar.panels import Panel
3
4 from ..base import IntegrationTestCase
5
6
7 class CustomPanel(Panel):
8 def title(self):
9 return "Title with special chars &\"'<>"
10
11
12 @override_settings(
13 DEBUG=True, DEBUG_TOOLBAR_PANELS=["tests.panels.test_custom.CustomPanel"]
14 )
15 class CustomPanelTestCase(IntegrationTestCase):
16 def test_escapes_panel_title(self):
17 response = self.client.get("/regular/basic/")
18 self.assertContains(
19 response,
20 """
21 <li id="djdt-CustomPanel" class="djDebugPanelButton">
22 <input type="checkbox" checked title="Disable for next and successive requests" data-cookie="djdtCustomPanel">
23 <a class="CustomPanel" href="#" title="Title with special chars &amp;&quot;&#39;&lt;&gt;">
24 Title with special chars &amp;&quot;&#39;&lt;&gt;
25 </a>
26 </li>
27 """,
28 html=True,
29 )
30 self.assertContains(
31 response,
32 """
33 <div id="CustomPanel" class="djdt-panelContent djdt-hidden">
34 <div class="djDebugPanelTitle">
35 <button type="button" class="djDebugClose">Ɨ</button>
36 <h3>Title with special chars &amp;&quot;&#39;&lt;&gt;</h3>
37 </div>
38 <div class="djDebugPanelContent">
39 <div class="djdt-loader"></div>
40 <div class="djdt-scroll"></div>
41 </div>
42 </div>
43 """,
44 html=True,
45 )
0 from django.test import RequestFactory, override_settings
1 from django.urls import resolve, reverse
2
3 from debug_toolbar.forms import SignedDataForm
4 from debug_toolbar.toolbar import DebugToolbar
5
6 from ..base import BaseTestCase, IntegrationTestCase
7
8 rf = RequestFactory()
9
10
11 class HistoryPanelTestCase(BaseTestCase):
12 panel_id = "HistoryPanel"
13
14 def test_disabled(self):
15 config = {"DISABLE_PANELS": {"debug_toolbar.panels.history.HistoryPanel"}}
16 self.assertTrue(self.panel.enabled)
17 with self.settings(DEBUG_TOOLBAR_CONFIG=config):
18 self.assertFalse(self.panel.enabled)
19
20 def test_post(self):
21 self.request = rf.post("/", data={"foo": "bar"})
22 response = self.panel.process_request(self.request)
23 self.panel.generate_stats(self.request, response)
24 data = self.panel.get_stats()["data"]
25 self.assertEqual(data["foo"], "bar")
26
27 def test_post_json(self):
28 for data, expected_stats_data in (
29 ({"foo": "bar"}, {"foo": "bar"}),
30 ("", {}), # Empty JSON
31 ("'", {}), # Invalid JSON
32 ):
33 with self.subTest(data=data):
34 self.request = rf.post(
35 "/",
36 data=data,
37 content_type="application/json",
38 CONTENT_TYPE="application/json", # Force django test client to add the content-type even if no data
39 )
40 response = self.panel.process_request(self.request)
41 self.panel.generate_stats(self.request, response)
42 data = self.panel.get_stats()["data"]
43 self.assertDictEqual(data, expected_stats_data)
44
45 def test_urls(self):
46 self.assertEqual(
47 reverse("djdt:history_sidebar"),
48 "/__debug__/history_sidebar/",
49 )
50 self.assertEqual(
51 resolve("/__debug__/history_sidebar/").url_name,
52 "history_sidebar",
53 )
54 self.assertEqual(
55 reverse("djdt:history_refresh"),
56 "/__debug__/history_refresh/",
57 )
58 self.assertEqual(
59 resolve("/__debug__/history_refresh/").url_name,
60 "history_refresh",
61 )
62
63
64 @override_settings(DEBUG=True)
65 class HistoryViewsTestCase(IntegrationTestCase):
66 def test_history_panel_integration_content(self):
67 """Verify the history panel's content renders properly.."""
68 self.assertEqual(len(DebugToolbar._store), 0)
69
70 data = {"foo": "bar"}
71 self.client.get("/json_view/", data, content_type="application/json")
72
73 # Check the history panel's stats to verify the toolbar rendered properly.
74 self.assertEqual(len(DebugToolbar._store), 1)
75 toolbar = list(DebugToolbar._store.values())[0]
76 content = toolbar.get_panel_by_id("HistoryPanel").content
77 self.assertIn("bar", content)
78
79 def test_history_sidebar_invalid(self):
80 response = self.client.get(reverse("djdt:history_sidebar"))
81 self.assertEqual(response.status_code, 400)
82
83 data = {"signed": SignedDataForm.sign({"store_id": "foo"}) + "invalid"}
84 response = self.client.get(reverse("djdt:history_sidebar"), data=data)
85 self.assertEqual(response.status_code, 400)
86
87 def test_history_sidebar(self):
88 """Validate the history sidebar view."""
89 self.client.get("/json_view/")
90 store_id = list(DebugToolbar._store.keys())[0]
91 data = {"signed": SignedDataForm.sign({"store_id": store_id})}
92 response = self.client.get(reverse("djdt:history_sidebar"), data=data)
93 self.assertEqual(response.status_code, 200)
94 self.assertEqual(
95 set(response.json().keys()),
96 {
97 "VersionsPanel",
98 "TimerPanel",
99 "SettingsPanel",
100 "HeadersPanel",
101 "RequestPanel",
102 "SQLPanel",
103 "StaticFilesPanel",
104 "TemplatesPanel",
105 "CachePanel",
106 "SignalsPanel",
107 "LoggingPanel",
108 "ProfilingPanel",
109 },
110 )
111
112 def test_history_refresh_invalid_signature(self):
113 response = self.client.get(reverse("djdt:history_refresh"))
114 self.assertEqual(response.status_code, 400)
115
116 data = {"signed": "eyJzdG9yZV9pZCI6ImZvbyIsImhhc2giOiI4YWFiMzIzZGZhODIyMW"}
117 response = self.client.get(reverse("djdt:history_refresh"), data=data)
118 self.assertEqual(response.status_code, 400)
119 self.assertEqual(b"Invalid signature", response.content)
120
121 def test_history_refresh(self):
122 """Verify refresh history response has request variables."""
123 data = {"foo": "bar"}
124 self.client.get("/json_view/", data, content_type="application/json")
125 data = {"signed": SignedDataForm.sign({"store_id": "foo"})}
126 response = self.client.get(reverse("djdt:history_refresh"), data=data)
127 self.assertEqual(response.status_code, 200)
128 data = response.json()
129 self.assertEqual(len(data["requests"]), 1)
130 for val in ["foo", "bar"]:
131 self.assertIn(val, data["requests"][0]["content"])
6262 self.assertNotIn("cafƩ", self.panel.content)
6363 self.panel.generate_stats(self.request, response)
6464 # ensure the panel renders correctly.
65 self.assertIn("cafƩ", self.panel.content)
66 self.assertValidHTML(self.panel.content)
65 content = self.panel.content
66 self.assertIn("cafƩ", content)
67 self.assertValidHTML(content)
6768
6869 def test_failing_formatting(self):
6970 class BadClass:
00 from django.contrib.auth.models import User
11 from django.db import IntegrityError, transaction
22 from django.http import HttpResponse
3 from django.test import TestCase
43 from django.test.utils import override_settings
54
6 from ..base import BaseTestCase
5 from ..base import BaseTestCase, IntegrationTestCase
76 from ..views import listcomp_view, regular_view
87
98
3130 self.assertNotIn("regular_view", self.panel.content)
3231 self.panel.generate_stats(self.request, response)
3332 # ensure the panel renders correctly.
34 self.assertIn("regular_view", self.panel.content)
35 self.assertValidHTML(self.panel.content)
33 content = self.panel.content
34 self.assertIn("regular_view", content)
35 self.assertValidHTML(content)
3636
3737 def test_listcomp_escaped(self):
3838 self._get_response = lambda request: listcomp_view(request)
3939 response = self.panel.process_request(self.request)
4040 self.panel.generate_stats(self.request, response)
41 self.assertNotIn(
42 '<span class="djdt-func"><listcomp></span>', self.panel.content
43 )
44 self.assertIn(
45 '<span class="djdt-func">&lt;listcomp&gt;</span>', self.panel.content
46 )
41 content = self.panel.content
42 self.assertNotIn('<span class="djdt-func"><listcomp></span>', content)
43 self.assertIn('<span class="djdt-func">&lt;listcomp&gt;</span>', content)
4744
4845 def test_generate_stats_no_profiler(self):
4946 """
6764 @override_settings(
6865 DEBUG=True, DEBUG_TOOLBAR_PANELS=["debug_toolbar.panels.profiling.ProfilingPanel"]
6966 )
70 class ProfilingPanelIntegrationTestCase(TestCase):
67 class ProfilingPanelIntegrationTestCase(IntegrationTestCase):
7168 def test_view_executed_once(self):
7269 self.assertEqual(User.objects.count(), 0)
7370
0 from django.http import QueryDict
1
02 from ..base import BaseTestCase
13
24
79 self.request.session = {"oĆ¹": "oĆ¹"}
810 response = self.panel.process_request(self.request)
911 self.panel.generate_stats(self.request, response)
10 content = self.panel.content
11 self.assertIn("oĆ¹", content)
12 self.assertIn("oĆ¹", self.panel.content)
1213
1314 def test_object_with_non_ascii_repr_in_request_params(self):
1415 self.request.path = "/non_ascii_request/"
2728 self.assertNotIn("nƓt ƄscƭƬ", self.panel.content)
2829 self.panel.generate_stats(self.request, response)
2930 # ensure the panel renders correctly.
30 self.assertIn("nƓt ƄscƭƬ", self.panel.content)
31 self.assertValidHTML(self.panel.content)
31 content = self.panel.content
32 self.assertIn("nƓt ƄscƭƬ", content)
33 self.assertValidHTML(content)
34
35 def test_query_dict_for_request_in_method_get(self):
36 """
37 Test verifies the correctness of the statistics generation method
38 in the case when the GET request is class QueryDict
39 """
40 self.request.GET = QueryDict("foo=bar")
41 response = self.panel.process_request(self.request)
42 self.panel.generate_stats(self.request, response)
43 # ensure the panel GET request data is processed correctly.
44 content = self.panel.content
45 self.assertIn("foo", content)
46 self.assertIn("bar", content)
47
48 def test_dict_for_request_in_method_get(self):
49 """
50 Test verifies the correctness of the statistics generation method
51 in the case when the GET request is class Dict
52 """
53 self.request.GET = {"foo": "bar"}
54 response = self.panel.process_request(self.request)
55 self.panel.generate_stats(self.request, response)
56 # ensure the panel GET request data is processed correctly.
57 content = self.panel.content
58 self.assertIn("foo", content)
59 self.assertIn("bar", content)
60
61 def test_query_dict_for_request_in_method_post(self):
62 """
63 Test verifies the correctness of the statistics generation method
64 in the case when the POST request is class QueryDict
65 """
66 self.request.POST = QueryDict("foo=bar")
67 response = self.panel.process_request(self.request)
68 self.panel.generate_stats(self.request, response)
69 # ensure the panel POST request data is processed correctly.
70 content = self.panel.content
71 self.assertIn("foo", content)
72 self.assertIn("bar", content)
73
74 def test_dict_for_request_in_method_post(self):
75 """
76 Test verifies the correctness of the statistics generation method
77 in the case when the POST request is class Dict
78 """
79 self.request.POST = {"foo": "bar"}
80 response = self.panel.process_request(self.request)
81 self.panel.generate_stats(self.request, response)
82 # ensure the panel POST request data is processed correctly.
83 content = self.panel.content
84 self.assertIn("foo", content)
85 self.assertIn("bar", content)
86
87 def test_namespaced_url(self):
88 self.request.path = "/admin/login/"
89 response = self.panel.process_request(self.request)
90 self.panel.generate_stats(self.request, response)
91 panel_stats = self.panel.get_stats()
92 self.assertEqual(panel_stats["view_urlname"], "admin:login")
0 from django.test import override_settings
1
2 from ..base import IntegrationTestCase
3
4
5 @override_settings(DEBUG=True)
6 class SettingsIntegrationTestCase(IntegrationTestCase):
7 def test_panel_title(self):
8 response = self.client.get("/regular/basic/")
9 # The settings module is None due to using Django's UserSettingsHolder
10 # in tests.
11 self.assertContains(
12 response,
13 """
14 <li id="djdt-SettingsPanel" class="djDebugPanelButton">
15 <input type="checkbox" checked title="Disable for next and successive requests" data-cookie="djdtSettingsPanel">
16 <a class="SettingsPanel" href="#" title="Settings from None">Settings</a>
17 </li>
18 """,
19 html=True,
20 )
21 self.assertContains(
22 response,
23 """
24 <div id="SettingsPanel" class="djdt-panelContent djdt-hidden">
25 <div class="djDebugPanelTitle">
26 <button type="button" class="djDebugClose">Ɨ</button>
27 <h3>Settings from None</h3>
28 </div>
29 <div class="djDebugPanelContent">
30 <div class="djdt-loader"></div>
31 <div class="djdt-scroll"></div>
32 </div>
33 </div>
34 """,
35 html=True,
36 )
00 import datetime
11 import unittest
22
3 import django
34 from django.contrib.auth.models import User
45 from django.db import connection
56 from django.db.models import Count
78 from django.shortcuts import render
89 from django.test.utils import override_settings
910
11 from debug_toolbar import settings as dt_settings
12
1013 from ..base import BaseTestCase
14
15 try:
16 from psycopg2._json import Json as PostgresJson
17 except ImportError:
18 PostgresJson = None
19
20 if connection.vendor == "postgresql":
21 from ..models import PostgresJSON as PostgresJSONModel
22 else:
23 PostgresJSONModel = None
1124
1225
1326 class SQLPanelTestCase(BaseTestCase):
107120 # ensure query was logged
108121 self.assertEqual(len(self.panel._queries), 3)
109122
123 if django.VERSION >= (3, 1):
124 self.assertEqual(
125 tuple([q[1]["params"] for q in self.panel._queries]),
126 ('["Foo"]', "[10, 1]", '["2017-12-22 16:07:01"]'),
127 )
128 else:
129 self.assertEqual(
130 tuple([q[1]["params"] for q in self.panel._queries]),
131 ('["Foo", true, false]', "[10, 1]", '["2017-12-22 16:07:01"]'),
132 )
133
134 @unittest.skipUnless(
135 connection.vendor == "postgresql", "Test valid only on PostgreSQL"
136 )
137 def test_json_param_conversion(self):
138 self.assertEqual(len(self.panel._queries), 0)
139
140 list(PostgresJSONModel.objects.filter(field__contains={"foo": "bar"}))
141
142 response = self.panel.process_request(self.request)
143 self.panel.generate_stats(self.request, response)
144
145 # ensure query was logged
146 self.assertEqual(len(self.panel._queries), 1)
110147 self.assertEqual(
111 tuple([q[1]["params"] for q in self.panel._queries]),
112 ('["Foo", true, false]', "[10, 1]", '["2017-12-22 16:07:01"]'),
113 )
148 self.panel._queries[0][1]["params"],
149 '["{\\"foo\\": \\"bar\\"}"]',
150 )
151 if django.VERSION < (3, 1):
152 self.assertIsInstance(
153 self.panel._queries[0][1]["raw_params"][0],
154 PostgresJson,
155 )
114156
115157 def test_binary_param_force_text(self):
116158 self.assertEqual(len(self.panel._queries), 0)
204246 self.assertNotIn("cafƩ", self.panel.content)
205247 self.panel.generate_stats(self.request, response)
206248 # ensure the panel renders correctly.
207 self.assertIn("cafƩ", self.panel.content)
208 self.assertValidHTML(self.panel.content)
249 content = self.panel.content
250 self.assertIn("cafƩ", content)
251 self.assertValidHTML(content)
252
253 @override_settings(DEBUG_TOOLBAR_CONFIG={"ENABLE_STACKTRACES_LOCALS": True})
254 def test_insert_locals(self):
255 """
256 Test that the panel inserts locals() content.
257 """
258 local_var = "<script>alert('test');</script>" # noqa
259 list(User.objects.filter(username="cafƩ".encode("utf-8")))
260 response = self.panel.process_request(self.request)
261 self.panel.generate_stats(self.request, response)
262 self.assertIn("local_var", self.panel.content)
263 # Verify the escape logic works
264 content = self.panel.content
265 self.assertNotIn("<script>alert", content)
266 self.assertIn("&lt;script&gt;alert", content)
267 self.assertIn("djdt-locals", content)
268
269 def test_not_insert_locals(self):
270 """
271 Test that the panel does not insert locals() content.
272 """
273 list(User.objects.filter(username="cafƩ".encode("utf-8")))
274 response = self.panel.process_request(self.request)
275 self.panel.generate_stats(self.request, response)
276 self.assertNotIn("djdt-locals", self.panel.content)
209277
210278 @unittest.skipUnless(
211279 connection.vendor == "postgresql", "Test valid only on PostgreSQL"
290358
291359 # ensure the stacktrace is populated
292360 self.assertTrue(len(query[1]["stacktrace"]) > 0)
361
362 @override_settings(
363 DEBUG_TOOLBAR_CONFIG={"PRETTIFY_SQL": True},
364 )
365 def test_prettify_sql(self):
366 """
367 Test case to validate that the PRETTIFY_SQL setting changes the output
368 of the sql when it's toggled. It does not validate what it does
369 though.
370 """
371 list(User.objects.filter(username__istartswith="spam"))
372
373 response = self.panel.process_request(self.request)
374 self.panel.generate_stats(self.request, response)
375 pretty_sql = self.panel._queries[-1][1]["sql"]
376 self.assertEqual(len(self.panel._queries), 1)
377
378 # Reset the queries
379 self.panel._queries = []
380 # Run it again, but with prettyify off. Verify that it's different.
381 dt_settings.get_config()["PRETTIFY_SQL"] = False
382 list(User.objects.filter(username__istartswith="spam"))
383 response = self.panel.process_request(self.request)
384 self.panel.generate_stats(self.request, response)
385 self.assertEqual(len(self.panel._queries), 1)
386 self.assertNotEqual(pretty_sql, self.panel._queries[-1][1]["sql"])
387
388 self.panel._queries = []
389 # Run it again, but with prettyify back on.
390 # This is so we don't have to check what PRETTIFY_SQL does exactly,
391 # but we know it's doing something.
392 dt_settings.get_config()["PRETTIFY_SQL"] = True
393 list(User.objects.filter(username__istartswith="spam"))
394 response = self.panel.process_request(self.request)
395 self.panel.generate_stats(self.request, response)
396 self.assertEqual(len(self.panel._queries), 1)
397 self.assertEqual(pretty_sql, self.panel._queries[-1][1]["sql"])
0 import os
1 import unittest
2
3 import django
4 from django.conf import settings
05 from django.contrib.staticfiles import finders
6 from django.test.utils import override_settings
17
28 from ..base import BaseTestCase
9
10 PATH_DOES_NOT_EXIST = os.path.join(settings.BASE_DIR, "tests", "invalid_static")
311
412
513 class StaticFilesPanelTestCase(BaseTestCase):
816 def test_default_case(self):
917 response = self.panel.process_request(self.request)
1018 self.panel.generate_stats(self.request, response)
19 content = self.panel.content
1120 self.assertIn(
12 "django.contrib.staticfiles.finders." "AppDirectoriesFinder",
13 self.panel.content,
21 "django.contrib.staticfiles.finders.AppDirectoriesFinder", content
1422 )
1523 self.assertIn(
16 "django.contrib.staticfiles.finders." "FileSystemFinder (2 files)",
17 self.panel.content,
24 "django.contrib.staticfiles.finders.FileSystemFinder (2 files)", content
1825 )
1926 self.assertEqual(self.panel.num_used, 0)
2027 self.assertNotEqual(self.panel.num_found, 0)
3340 response = self.panel.process_request(self.request)
3441 # ensure the panel does not have content yet.
3542 self.assertNotIn(
36 "django.contrib.staticfiles.finders." "AppDirectoriesFinder",
43 "django.contrib.staticfiles.finders.AppDirectoriesFinder",
3744 self.panel.content,
3845 )
3946 self.panel.generate_stats(self.request, response)
4047 # ensure the panel renders correctly.
48 content = self.panel.content
4149 self.assertIn(
42 "django.contrib.staticfiles.finders." "AppDirectoriesFinder",
43 self.panel.content,
50 "django.contrib.staticfiles.finders.AppDirectoriesFinder", content
4451 )
45 self.assertValidHTML(self.panel.content)
52 self.assertValidHTML(content)
53
54 @unittest.skipIf(django.VERSION >= (4,), "Django>=4 handles missing dirs itself.")
55 @override_settings(
56 STATICFILES_DIRS=[PATH_DOES_NOT_EXIST] + settings.STATICFILES_DIRS,
57 STATIC_ROOT=PATH_DOES_NOT_EXIST,
58 )
59 def test_finder_directory_does_not_exist(self):
60 """Misconfigure the static files settings and verify the toolbar runs.
61
62 The test case is that the STATIC_ROOT is in STATICFILES_DIRS and that
63 the directory of STATIC_ROOT does not exist.
64 """
65 response = self.panel.process_request(self.request)
66 self.panel.generate_stats(self.request, response)
67 content = self.panel.content
68 self.assertIn(
69 "django.contrib.staticfiles.finders.AppDirectoriesFinder", content
70 )
71 self.assertNotIn(
72 "django.contrib.staticfiles.finders.FileSystemFinder (2 files)", content
73 )
74 self.assertEqual(self.panel.num_used, 0)
75 self.assertNotEqual(self.panel.num_found, 0)
76 self.assertEqual(
77 self.panel.get_staticfiles_apps(), ["django.contrib.admin", "debug_toolbar"]
78 )
79 self.assertEqual(
80 self.panel.get_staticfiles_dirs(), finders.FileSystemFinder().locations
81 )
00 from django.contrib.auth.models import User
11 from django.template import Context, RequestContext, Template
2 from django.test import TestCase, override_settings
2 from django.test import override_settings
33
4 from ..base import BaseTestCase
4 from ..base import BaseTestCase, IntegrationTestCase
55 from ..forms import TemplateReprForm
66 from ..models import NonAsciiRepr
77
3131 # ensure the query was NOT logged
3232 self.assertEqual(len(self.sql_panel._queries), 0)
3333
34 ctx = self.panel.templates[0]["context"][1]
35 self.assertIn("<<queryset of auth.User>>", ctx)
36 self.assertIn("<<triggers database query>>", ctx)
34 self.assertEqual(
35 self.panel.templates[0]["context"],
36 [
37 "{'False': False, 'None': None, 'True': True}",
38 "{'deep_queryset': '<<triggers database query>>',\n"
39 " 'queryset': '<<queryset of auth.User>>'}",
40 ],
41 )
3742
3843 def test_template_repr(self):
3944 # Force widget templates to be included
6873 self.assertNotIn("nƓt ƄscƭƬ", self.panel.content)
6974 self.panel.generate_stats(self.request, response)
7075 # ensure the panel renders correctly.
71 self.assertIn("nƓt ƄscƭƬ", self.panel.content)
72 self.assertValidHTML(self.panel.content)
76 content = self.panel.content
77 self.assertIn("nƓt ƄscƭƬ", content)
78 self.assertValidHTML(content)
7379
7480 def test_custom_context_processor(self):
7581 response = self.panel.process_request(self.request)
8793 with self.settings(DEBUG_TOOLBAR_CONFIG=config):
8894 self.assertFalse(self.panel.enabled)
8995
96 def test_empty_context(self):
97 t = Template("")
98 c = Context({})
99 t.render(c)
100
101 # Includes the builtin context but not the empty one.
102 self.assertEqual(
103 self.panel.templates[0]["context"],
104 ["{'False': False, 'None': None, 'True': True}"],
105 )
106
90107
91108 @override_settings(
92109 DEBUG=True, DEBUG_TOOLBAR_PANELS=["debug_toolbar.panels.templates.TemplatesPanel"]
93110 )
94 class JinjaTemplateTestCase(TestCase):
111 class JinjaTemplateTestCase(IntegrationTestCase):
95112 def test_django_jinja2(self):
96113 r = self.client.get("/regular_jinja/foobar/")
97114 self.assertContains(r, "Test for foobar (Jinja)")
7878 "second": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"},
7979 }
8080
81 if os.environ.get("DJANGO_DATABASE_ENGINE") == "postgresql":
82 DATABASES = {
83 "default": {"ENGINE": "django.db.backends.postgresql", "NAME": "debug-toolbar"}
84 }
85 elif os.environ.get("DJANGO_DATABASE_ENGINE") == "mysql":
86 DATABASES = {
87 "default": {"ENGINE": "django.db.backends.mysql", "NAME": "debug_toolbar"}
88 }
89 else:
90 DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3"}}
81 DATABASES = {
82 "default": {
83 "ENGINE": "django.db.backends.%s" % os.getenv("DB_BACKEND", "sqlite3"),
84 "NAME": os.getenv("DB_NAME", ":memory:"),
85 "USER": os.getenv("DB_USER"),
86 "PASSWORD": os.getenv("DB_PASSWORD"),
87 "HOST": os.getenv("DB_HOST", ""),
88 "PORT": os.getenv("DB_PORT", ""),
89 "TEST": {
90 "USER": "default_test",
91 },
92 },
93 }
9194
95 DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
9296
9397 # Debug Toolbar configuration
9498
0 {% extends 'base.html' %}
0 import os
1 import unittest
2
3 import django
4 from django.conf import settings
5 from django.core.checks import Warning, run_checks
6 from django.test import SimpleTestCase, override_settings
7
8 PATH_DOES_NOT_EXIST = os.path.join(settings.BASE_DIR, "tests", "invalid_static")
9
10
11 class ChecksTestCase(SimpleTestCase):
12 @override_settings(
13 MIDDLEWARE=[
14 "django.contrib.messages.middleware.MessageMiddleware",
15 "django.contrib.sessions.middleware.SessionMiddleware",
16 "django.contrib.auth.middleware.AuthenticationMiddleware",
17 "django.middleware.gzip.GZipMiddleware",
18 "debug_toolbar.middleware.DebugToolbarMiddleware",
19 ]
20 )
21 def test_check_good_configuration(self):
22 messages = run_checks()
23 self.assertEqual(messages, [])
24
25 @override_settings(
26 MIDDLEWARE=[
27 "django.contrib.messages.middleware.MessageMiddleware",
28 "django.contrib.sessions.middleware.SessionMiddleware",
29 "django.contrib.auth.middleware.AuthenticationMiddleware",
30 ]
31 )
32 def test_check_missing_middleware_error(self):
33 messages = run_checks()
34 self.assertEqual(
35 messages,
36 [
37 Warning(
38 "debug_toolbar.middleware.DebugToolbarMiddleware is "
39 "missing from MIDDLEWARE.",
40 hint="Add debug_toolbar.middleware.DebugToolbarMiddleware "
41 "to MIDDLEWARE.",
42 id="debug_toolbar.W001",
43 )
44 ],
45 )
46
47 @override_settings(
48 MIDDLEWARE=[
49 "django.contrib.messages.middleware.MessageMiddleware",
50 "django.contrib.sessions.middleware.SessionMiddleware",
51 "django.contrib.auth.middleware.AuthenticationMiddleware",
52 "debug_toolbar.middleware.DebugToolbarMiddleware",
53 "django.middleware.gzip.GZipMiddleware",
54 ]
55 )
56 def test_check_gzip_middleware_error(self):
57 messages = run_checks()
58 self.assertEqual(
59 messages,
60 [
61 Warning(
62 "debug_toolbar.middleware.DebugToolbarMiddleware occurs "
63 "before django.middleware.gzip.GZipMiddleware in "
64 "MIDDLEWARE.",
65 hint="Move debug_toolbar.middleware.DebugToolbarMiddleware "
66 "to after django.middleware.gzip.GZipMiddleware in "
67 "MIDDLEWARE.",
68 id="debug_toolbar.W003",
69 )
70 ],
71 )
72
73 @override_settings(
74 MIDDLEWARE_CLASSES=[
75 "django.contrib.messages.middleware.MessageMiddleware",
76 "django.contrib.sessions.middleware.SessionMiddleware",
77 "django.contrib.auth.middleware.AuthenticationMiddleware",
78 "django.middleware.gzip.GZipMiddleware",
79 "debug_toolbar.middleware.DebugToolbarMiddleware",
80 ]
81 )
82 def test_check_middleware_classes_error(self):
83 messages = run_checks()
84 self.assertIn(
85 Warning(
86 "debug_toolbar is incompatible with MIDDLEWARE_CLASSES setting.",
87 hint="Use MIDDLEWARE instead of MIDDLEWARE_CLASSES",
88 id="debug_toolbar.W004",
89 ),
90 messages,
91 )
92
93 @unittest.skipIf(django.VERSION >= (4,), "Django>=4 handles missing dirs itself.")
94 @override_settings(
95 STATICFILES_DIRS=[PATH_DOES_NOT_EXIST],
96 )
97 def test_panel_check_errors(self):
98 messages = run_checks()
99 self.assertEqual(
100 messages,
101 [
102 Warning(
103 "debug_toolbar requires the STATICFILES_DIRS directories to exist.",
104 hint="Running manage.py collectstatic may help uncover the issue.",
105 id="debug_toolbar.staticfiles.W001",
106 )
107 ],
108 )
109
110 @override_settings(DEBUG_TOOLBAR_PANELS=[])
111 def test_panels_is_empty(self):
112 errors = run_checks()
113 self.assertEqual(
114 errors,
115 [
116 Warning(
117 "Setting DEBUG_TOOLBAR_PANELS is empty.",
118 hint="Set DEBUG_TOOLBAR_PANELS to a non-empty list in your "
119 "settings.py.",
120 id="debug_toolbar.W005",
121 )
122 ],
123 )
0 from datetime import datetime
1
2 import django
3 from django import forms
4 from django.test import TestCase
5
6 from debug_toolbar.forms import SignedDataForm
7
8 # Django 3.1 uses sha256 by default.
9 SIGNATURE = (
10 "v02QBcJplEET6QXHNWejnRcmSENWlw6_RjxLTR7QG9g"
11 if django.VERSION >= (3, 1)
12 else "ukcAFUqYhUUnqT-LupnYoo-KvFg"
13 )
14
15 DATA = {"value": "foo", "date": datetime(2020, 1, 1)}
16 SIGNED_DATA = f'{{"date": "2020-01-01 00:00:00", "value": "foo"}}:{SIGNATURE}'
17
18
19 class FooForm(forms.Form):
20 value = forms.CharField()
21 # Include a datetime in the tests because it's not serializable back
22 # to a datetime by SignedDataForm
23 date = forms.DateTimeField()
24
25
26 class TestSignedDataForm(TestCase):
27 def test_signed_data(self):
28 data = {"signed": SignedDataForm.sign(DATA)}
29 form = SignedDataForm(data=data)
30 self.assertTrue(form.is_valid())
31 # Check the signature value
32 self.assertEqual(data["signed"], SIGNED_DATA)
33
34 def test_verified_data(self):
35 form = SignedDataForm(data={"signed": SignedDataForm.sign(DATA)})
36 self.assertEqual(
37 form.verified_data(),
38 {
39 "value": "foo",
40 "date": "2020-01-01 00:00:00",
41 },
42 )
43 # Take it back to the foo form to validate the datetime is serialized
44 foo_form = FooForm(data=form.verified_data())
45 self.assertTrue(foo_form.is_valid())
46 self.assertDictEqual(foo_form.cleaned_data, DATA)
47
48 def test_initial_set_signed(self):
49 form = SignedDataForm(initial=DATA)
50 self.assertEqual(form.initial["signed"], SIGNED_DATA)
51
52 def test_prevents_tampering(self):
53 data = {"signed": SIGNED_DATA.replace('"value": "foo"', '"value": "bar"')}
54 form = SignedDataForm(data=data)
55 self.assertFalse(form.is_valid())
00 import os
1 import re
12 import unittest
23
4 import django
35 import html5lib
46 from django.contrib.staticfiles.testing import StaticLiveServerTestCase
57 from django.core import signing
6 from django.core.checks import Warning, run_checks
8 from django.db import connection
79 from django.http import HttpResponse
810 from django.template.loader import get_template
9 from django.test import RequestFactory, SimpleTestCase, TestCase
11 from django.test import RequestFactory
1012 from django.test.utils import override_settings
1113
14 from debug_toolbar.forms import SignedDataForm
1215 from debug_toolbar.middleware import DebugToolbarMiddleware, show_toolbar
16 from debug_toolbar.panels import Panel
1317 from debug_toolbar.toolbar import DebugToolbar
1418
15 from .base import BaseTestCase
19 from .base import BaseTestCase, IntegrationTestCase
1620 from .views import regular_view
1721
1822 try:
1923 from selenium import webdriver
2024 from selenium.common.exceptions import NoSuchElementException
25 from selenium.webdriver.common.by import By
26 from selenium.webdriver.firefox.options import Options
27 from selenium.webdriver.support import expected_conditions as EC
2128 from selenium.webdriver.support.wait import WebDriverWait
2229 except ImportError:
2330 webdriver = None
2431
2532
2633 rf = RequestFactory()
34
35
36 class BuggyPanel(Panel):
37 def title(self):
38 return "BuggyPanel"
39
40 @property
41 def content(self):
42 raise Exception
2743
2844
2945 @override_settings(DEBUG=True)
85101 self.client.get("/cached_view/")
86102 self.assertEqual(len(self.toolbar.get_panel_by_id("CachePanel").calls), 5)
87103
104 def test_is_toolbar_request(self):
105 self.request.path = "/__debug__/render_panel/"
106 self.assertTrue(self.toolbar.is_toolbar_request(self.request))
107
108 self.request.path = "/invalid/__debug__/render_panel/"
109 self.assertFalse(self.toolbar.is_toolbar_request(self.request))
110
111 self.request.path = "/render_panel/"
112 self.assertFalse(self.toolbar.is_toolbar_request(self.request))
113
114 @override_settings(ROOT_URLCONF="tests.urls_invalid")
115 def test_is_toolbar_request_without_djdt_urls(self):
116 """Test cases when the toolbar urls aren't configured."""
117 self.request.path = "/__debug__/render_panel/"
118 self.assertFalse(self.toolbar.is_toolbar_request(self.request))
119
120 self.request.path = "/render_panel/"
121 self.assertFalse(self.toolbar.is_toolbar_request(self.request))
122
88123
89124 @override_settings(DEBUG=True)
90 class DebugToolbarIntegrationTestCase(TestCase):
125 class DebugToolbarIntegrationTestCase(IntegrationTestCase):
91126 def test_middleware(self):
92127 response = self.client.get("/execute_sql/")
93128 self.assertEqual(response.status_code, 200)
129 self.assertContains(response, "djDebug")
94130
95131 @override_settings(DEFAULT_CHARSET="iso-8859-1")
96132 def test_non_utf8_charset(self):
137173 )
138174 self.assertEqual(response.status_code, 404)
139175
176 def test_middleware_render_toolbar_json(self):
177 """Verify the toolbar is rendered and data is stored for a json request."""
178 self.assertEqual(len(DebugToolbar._store), 0)
179
180 data = {"foo": "bar"}
181 response = self.client.get("/json_view/", data, content_type="application/json")
182 self.assertEqual(response.status_code, 200)
183 self.assertEqual(response.content.decode("utf-8"), '{"foo": "bar"}')
184 # Check the history panel's stats to verify the toolbar rendered properly.
185 self.assertEqual(len(DebugToolbar._store), 1)
186 toolbar = list(DebugToolbar._store.values())[0]
187 self.assertEqual(
188 toolbar.get_panel_by_id("HistoryPanel").get_stats()["data"],
189 {"foo": ["bar"]},
190 )
191
140192 def test_template_source_checks_show_toolbar(self):
141193 template = get_template("basic.html")
142194 url = "/__debug__/template_source/"
160212 def test_sql_select_checks_show_toolbar(self):
161213 url = "/__debug__/sql_select/"
162214 data = {
163 "sql": "SELECT * FROM auth_user",
164 "raw_sql": "SELECT * FROM auth_user",
165 "params": "{}",
166 "alias": "default",
167 "duration": "0",
168 "hash": "6e12daa636b8c9a8be993307135458f90a877606",
215 "signed": SignedDataForm.sign(
216 {
217 "sql": "SELECT * FROM auth_user",
218 "raw_sql": "SELECT * FROM auth_user",
219 "params": "{}",
220 "alias": "default",
221 "duration": "0",
222 }
223 )
169224 }
170225
171226 response = self.client.post(url, data)
183238 def test_sql_explain_checks_show_toolbar(self):
184239 url = "/__debug__/sql_explain/"
185240 data = {
186 "sql": "SELECT * FROM auth_user",
187 "raw_sql": "SELECT * FROM auth_user",
188 "params": "{}",
189 "alias": "default",
190 "duration": "0",
191 "hash": "6e12daa636b8c9a8be993307135458f90a877606",
192 }
193
241 "signed": SignedDataForm.sign(
242 {
243 "sql": "SELECT * FROM auth_user",
244 "raw_sql": "SELECT * FROM auth_user",
245 "params": "{}",
246 "alias": "default",
247 "duration": "0",
248 }
249 )
250 }
251
252 response = self.client.post(url, data)
253 self.assertEqual(response.status_code, 200)
254 response = self.client.post(url, data, HTTP_X_REQUESTED_WITH="XMLHttpRequest")
255 self.assertEqual(response.status_code, 200)
256 with self.settings(INTERNAL_IPS=[]):
257 response = self.client.post(url, data)
258 self.assertEqual(response.status_code, 404)
259 response = self.client.post(
260 url, data, HTTP_X_REQUESTED_WITH="XMLHttpRequest"
261 )
262 self.assertEqual(response.status_code, 404)
263
264 @unittest.skipUnless(
265 connection.vendor == "postgresql", "Test valid only on PostgreSQL"
266 )
267 def test_sql_explain_postgres_json_field(self):
268 url = "/__debug__/sql_explain/"
269 base_query = (
270 'SELECT * FROM "tests_postgresjson" WHERE "tests_postgresjson"."field" @>'
271 )
272 query = base_query + """ '{"foo": "bar"}'"""
273 data = {
274 "signed": SignedDataForm.sign(
275 {
276 "sql": query,
277 "raw_sql": base_query + " %s",
278 "params": '["{\\"foo\\": \\"bar\\"}"]',
279 "alias": "default",
280 "duration": "0",
281 }
282 )
283 }
194284 response = self.client.post(url, data)
195285 self.assertEqual(response.status_code, 200)
196286 response = self.client.post(url, data, HTTP_X_REQUESTED_WITH="XMLHttpRequest")
206296 def test_sql_profile_checks_show_toolbar(self):
207297 url = "/__debug__/sql_profile/"
208298 data = {
209 "sql": "SELECT * FROM auth_user",
210 "raw_sql": "SELECT * FROM auth_user",
211 "params": "{}",
212 "alias": "default",
213 "duration": "0",
214 "hash": "6e12daa636b8c9a8be993307135458f90a877606",
299 "signed": SignedDataForm.sign(
300 {
301 "sql": "SELECT * FROM auth_user",
302 "raw_sql": "SELECT * FROM auth_user",
303 "params": "{}",
304 "alias": "default",
305 "duration": "0",
306 }
307 )
215308 }
216309
217310 response = self.client.post(url, data)
243336 self.assertEqual(response.status_code, 200)
244337 # Link to LOCATION header.
245338 self.assertIn(b'href="/regular/redirect/"', response.content)
339
340 def test_server_timing_headers(self):
341 response = self.client.get("/execute_sql/")
342 server_timing = response["Server-Timing"]
343 expected_partials = [
344 r'TimerPanel_utime;dur=(\d)*(\.(\d)*)?;desc="User CPU time", ',
345 r'TimerPanel_stime;dur=(\d)*(\.(\d)*)?;desc="System CPU time", ',
346 r'TimerPanel_total;dur=(\d)*(\.(\d)*)?;desc="Total CPU time", ',
347 r'TimerPanel_total_time;dur=(\d)*(\.(\d)*)?;desc="Elapsed time", ',
348 r'SQLPanel_sql_time;dur=(\d)*(\.(\d)*)?;desc="SQL 1 queries", ',
349 r'CachePanel_total_time;dur=0;desc="Cache 0 Calls"',
350 ]
351 for expected in expected_partials:
352 self.assertTrue(re.compile(expected).search(server_timing))
353
354 def test_auth_login_view_without_redirect(self):
355 response = self.client.get("/login_without_redirect/")
356 self.assertEqual(response.status_code, 200)
357 parser = html5lib.HTMLParser()
358 doc = parser.parse(response.content)
359 el = doc.find(".//*[@id='djDebug']")
360 store_id = el.attrib["data-store-id"]
361 response = self.client.get(
362 "/__debug__/render_panel/",
363 {"store_id": store_id, "panel_id": "TemplatesPanel"},
364 )
365 self.assertEqual(response.status_code, 200)
366 # The key None (without quotes) exists in the list of template
367 # variables.
368 if django.VERSION < (3, 0):
369 self.assertIn("None: &#39;&#39;", response.json()["content"])
370 else:
371 self.assertIn("None: &#x27;&#x27;", response.json()["content"])
246372
247373
248374 @unittest.skipIf(webdriver is None, "selenium isn't installed")
254380 @classmethod
255381 def setUpClass(cls):
256382 super().setUpClass()
257 cls.selenium = webdriver.Firefox()
383 options = Options()
384 options.headless = bool(os.environ.get("CI"))
385 cls.selenium = webdriver.Firefox(options=options)
258386
259387 @classmethod
260388 def tearDownClass(cls):
261389 cls.selenium.quit()
262390 super().tearDownClass()
263391
392 def get(self, url):
393 self.selenium.get(self.live_server_url + url)
394
395 @property
396 def wait(self):
397 return WebDriverWait(self.selenium, timeout=3)
398
264399 def test_basic(self):
265 self.selenium.get(self.live_server_url + "/regular/basic/")
400 self.get("/regular/basic/")
266401 version_panel = self.selenium.find_element_by_id("VersionsPanel")
267402
268403 # Versions panel isn't loaded
273408 self.selenium.find_element_by_class_name("VersionsPanel").click()
274409
275410 # Version panel loads
276 table = WebDriverWait(self.selenium, timeout=10).until(
411 table = self.wait.until(
277412 lambda selenium: version_panel.find_element_by_tag_name("table")
278413 )
279414 self.assertIn("Name", table.text)
285420 }
286421 )
287422 def test_basic_jinja(self):
288 self.selenium.get(self.live_server_url + "/regular_jinja/basic")
423 self.get("/regular_jinja/basic")
289424 template_panel = self.selenium.find_element_by_id("TemplatesPanel")
290425
291426 # Click to show the template panel
292427 self.selenium.find_element_by_class_name("TemplatesPanel").click()
293428
294 self.assertIn("Templates (1 rendered)", template_panel.text)
429 self.assertIn("Templates (2 rendered)", template_panel.text)
430 self.assertIn("base.html", template_panel.text)
295431 self.assertIn("jinja2/basic.jinja", template_panel.text)
432
433 @override_settings(
434 DEBUG_TOOLBAR_CONFIG={
435 "DISABLE_PANELS": {"debug_toolbar.panels.redirects.RedirectsPanel"}
436 }
437 )
438 def test_rerender_on_history_switch(self):
439 self.get("/regular_jinja/basic")
440 # Make a new request so the history panel has more than one option.
441 self.get("/execute_sql/")
442 template_panel = self.selenium.find_element_by_id("HistoryPanel")
443 # Record the current side panel of buttons for later comparison.
444 previous_button_panel = self.selenium.find_element_by_id(
445 "djDebugPanelList"
446 ).text
447
448 # Click to show the history panel
449 self.selenium.find_element_by_class_name("HistoryPanel").click()
450 # Click to switch back to the jinja page view snapshot
451 list(template_panel.find_elements_by_css_selector("button"))[-1].click()
452
453 current_button_panel = self.selenium.find_element_by_id("djDebugPanelList").text
454 # Verify the button side panels have updated.
455 self.assertNotEqual(previous_button_panel, current_button_panel)
456 self.assertNotIn("1 query", current_button_panel)
457 self.assertIn("1 query", previous_button_panel)
296458
297459 @override_settings(DEBUG_TOOLBAR_CONFIG={"RESULTS_CACHE_SIZE": 0})
298460 def test_expired_store(self):
299 self.selenium.get(self.live_server_url + "/regular/basic/")
461 self.get("/regular/basic/")
300462 version_panel = self.selenium.find_element_by_id("VersionsPanel")
301463
302464 # Click to show the version panel
303465 self.selenium.find_element_by_class_name("VersionsPanel").click()
304466
305467 # Version panel doesn't loads
306 error = WebDriverWait(self.selenium, timeout=10).until(
468 error = self.wait.until(
307469 lambda selenium: version_panel.find_element_by_tag_name("p")
308470 )
309471 self.assertIn("Data for this panel isn't available anymore.", error.text)
310472
311473 @override_settings(
312 DEBUG=True,
313474 TEMPLATES=[
314475 {
315476 "BACKEND": "django.template.backends.django.DjangoTemplates",
328489 ],
329490 )
330491 def test_django_cached_template_loader(self):
331 self.selenium.get(self.live_server_url + "/regular/basic/")
492 self.get("/regular/basic/")
332493 version_panel = self.selenium.find_element_by_id("TemplatesPanel")
333494
334 # Click to show the versions panel
495 # Click to show the templates panel
335496 self.selenium.find_element_by_class_name("TemplatesPanel").click()
336497
337 # Version panel loads
338 trigger = WebDriverWait(self.selenium, timeout=10).until(
498 # Templates panel loads
499 trigger = self.wait.until(
339500 lambda selenium: version_panel.find_element_by_css_selector(".remoteCall")
340501 )
341502 trigger.click()
342503
343504 # Verify the code is displayed
344 WebDriverWait(self.selenium, timeout=10).until(
505 self.wait.until(
345506 lambda selenium: self.selenium.find_element_by_css_selector(
346507 "#djDebugWindow code"
347508 )
348509 )
349510
350
351 @override_settings(DEBUG=True)
352 class DebugToolbarSystemChecksTestCase(SimpleTestCase):
353 @override_settings(
354 MIDDLEWARE=[
355 "django.contrib.messages.middleware.MessageMiddleware",
356 "django.contrib.sessions.middleware.SessionMiddleware",
357 "django.contrib.auth.middleware.AuthenticationMiddleware",
358 "django.middleware.gzip.GZipMiddleware",
359 "debug_toolbar.middleware.DebugToolbarMiddleware",
360 ]
361 )
362 def test_check_good_configuration(self):
363 messages = run_checks()
364 self.assertEqual(messages, [])
365
366 @override_settings(
367 MIDDLEWARE=[
368 "django.contrib.messages.middleware.MessageMiddleware",
369 "django.contrib.sessions.middleware.SessionMiddleware",
370 "django.contrib.auth.middleware.AuthenticationMiddleware",
371 ]
372 )
373 def test_check_missing_middleware_error(self):
374 messages = run_checks()
375 self.assertEqual(
376 messages,
377 [
378 Warning(
379 "debug_toolbar.middleware.DebugToolbarMiddleware is "
380 "missing from MIDDLEWARE.",
381 hint="Add debug_toolbar.middleware.DebugToolbarMiddleware "
382 "to MIDDLEWARE.",
383 id="debug_toolbar.W001",
384 )
385 ],
386 )
387
388 @override_settings(
389 MIDDLEWARE=[
390 "django.contrib.messages.middleware.MessageMiddleware",
391 "django.contrib.sessions.middleware.SessionMiddleware",
392 "django.contrib.auth.middleware.AuthenticationMiddleware",
393 "debug_toolbar.middleware.DebugToolbarMiddleware",
394 "django.middleware.gzip.GZipMiddleware",
395 ]
396 )
397 def test_check_gzip_middleware_error(self):
398 messages = run_checks()
399 self.assertEqual(
400 messages,
401 [
402 Warning(
403 "debug_toolbar.middleware.DebugToolbarMiddleware occurs "
404 "before django.middleware.gzip.GZipMiddleware in "
405 "MIDDLEWARE.",
406 hint="Move debug_toolbar.middleware.DebugToolbarMiddleware "
407 "to after django.middleware.gzip.GZipMiddleware in "
408 "MIDDLEWARE.",
409 id="debug_toolbar.W003",
410 )
411 ],
412 )
511 def test_sql_action_and_go_back(self):
512 self.get("/execute_sql/")
513 sql_panel = self.selenium.find_element_by_id("SQLPanel")
514 debug_window = self.selenium.find_element_by_id("djDebugWindow")
515
516 # Click to show the SQL panel
517 self.selenium.find_element_by_class_name("SQLPanel").click()
518
519 # SQL panel loads
520 button = self.wait.until(
521 EC.visibility_of_element_located((By.CSS_SELECTOR, ".remoteCall"))
522 )
523 button.click()
524
525 # SQL selected window loads
526 self.wait.until(EC.visibility_of(debug_window))
527 self.assertIn("SQL selected", debug_window.text)
528
529 # Close the SQL selected window
530 debug_window.find_element_by_class_name("djDebugClose").click()
531 self.wait.until(EC.invisibility_of_element(debug_window))
532
533 # SQL panel is still visible
534 self.assertTrue(sql_panel.is_displayed())
535
536 @override_settings(DEBUG_TOOLBAR_PANELS=["tests.test_integration.BuggyPanel"])
537 def test_displays_server_error(self):
538 self.get("/regular/basic/")
539 debug_window = self.selenium.find_element_by_id("djDebugWindow")
540 self.selenium.find_element_by_class_name("BuggyPanel").click()
541 self.wait.until(EC.visibility_of(debug_window))
542 self.assertEqual(debug_window.text, "Ā»\n500: Internal Server Error")
0 from django.conf.urls import include, url
0 from django.contrib import admin
1 from django.contrib.auth.views import LoginView
2 from django.urls import include, path, re_path
13
24 import debug_toolbar
35
57 from .models import NonAsciiRepr
68
79 urlpatterns = [
8 url(r"^resolving1/(.+)/(.+)/$", views.resolving_view, name="positional-resolving"),
9 url(r"^resolving2/(?P<arg1>.+)/(?P<arg2>.+)/$", views.resolving_view),
10 url(r"^resolving3/(.+)/$", views.resolving_view, {"arg2": "default"}),
11 url(r"^regular/(?P<title>.*)/$", views.regular_view),
12 url(r"^template_response/(?P<title>.*)/$", views.template_response_view),
13 url(r"^regular_jinja/(?P<title>.*)/$", views.regular_jinjia_view),
14 url(r"^non_ascii_request/$", views.regular_view, {"title": NonAsciiRepr()}),
15 url(r"^new_user/$", views.new_user),
16 url(r"^execute_sql/$", views.execute_sql),
17 url(r"^cached_view/$", views.cached_view),
18 url(r"^redirect/$", views.redirect_view),
19 url(r"^__debug__/", include(debug_toolbar.urls)),
10 re_path(
11 r"^resolving1/(.+)/(.+)/$", views.resolving_view, name="positional-resolving"
12 ),
13 re_path(r"^resolving2/(?P<arg1>.+)/(?P<arg2>.+)/$", views.resolving_view),
14 re_path(r"^resolving3/(.+)/$", views.resolving_view, {"arg2": "default"}),
15 re_path(r"^regular/(?P<title>.*)/$", views.regular_view),
16 re_path(r"^template_response/(?P<title>.*)/$", views.template_response_view),
17 re_path(r"^regular_jinja/(?P<title>.*)/$", views.regular_jinjia_view),
18 path("non_ascii_request/", views.regular_view, {"title": NonAsciiRepr()}),
19 path("new_user/", views.new_user),
20 path("execute_sql/", views.execute_sql),
21 path("cached_view/", views.cached_view),
22 path("json_view/", views.json_view),
23 path("redirect/", views.redirect_view),
24 path("login_without_redirect/", LoginView.as_view(redirect_field_name=None)),
25 path("admin/", admin.site.urls),
26 path("__debug__/", include(debug_toolbar.urls)),
2027 ]
0 """Invalid urls.py file for testing"""
1 urlpatterns = []
00 from django.contrib.auth.models import User
1 from django.http import HttpResponse, HttpResponseRedirect
1 from django.http import HttpResponseRedirect, JsonResponse
22 from django.shortcuts import render
33 from django.template.response import TemplateResponse
44 from django.views.decorators.cache import cache_page
66
77 def execute_sql(request):
88 list(User.objects.all())
9 return HttpResponse()
9 return render(request, "base.html")
1010
1111
1212 def regular_view(request, title):
2424
2525 def resolving_view(request, arg1, arg2):
2626 # see test_url_resolving in tests.py
27 return HttpResponse()
27 return render(request, "base.html")
2828
2929
3030 @cache_page(60)
3131 def cached_view(request):
32 return HttpResponse()
32 return render(request, "base.html")
33
34
35 def json_view(request):
36 return JsonResponse({"foo": "bar"})
3337
3438
3539 def regular_jinjia_view(request, title):
00 [tox]
11 envlist =
2 py{35,36,37}-dj111
3 py{35,36,37}-dj20
4 py{35,36,37}-dj21
5 py{35,36,37}-dj22
6 py{36,37}-djmaster
7 postgresql,
8 mariadb,
9 flake8,
10 style,
2 docs
3 style
114 readme
5 py{36,37}-dj{22,30,31,32}-sqlite
6 py{38,39}-dj{22,30,31,32,main}-sqlite
7 py{36,37,38,39}-dj{22,30,31,32}-{postgresql,mysql}
128
139 [testenv]
1410 deps =
15 dj111: Django>=1.11,<2.0
16 dj20: Django>=2.0,<2.1
17 dj21: Django>=2.1,<2.2
18 dj22: Django>=2.2,<3.0
19 djmaster: https://github.com/django/django/archive/master.tar.gz
11 dj22: Django==2.2.*
12 dj30: Django==3.0.*
13 dj31: Django==3.1.*
14 dj32: Django>=3.2a1,<4.0
15 sqlite: mock
16 postgresql: psycopg2-binary
17 mysql: mysqlclient
18 djmain: https://github.com/django/django/archive/main.tar.gz
2019 coverage
2120 Jinja2
2221 html5lib
23 selenium<4.0
22 selenium
2423 sqlparse
24 passenv=
25 CI
26 DB_BACKEND
27 DB_NAME
28 DB_USER
29 DB_PASSWORD
30 DB_HOST
31 DB_PORT
32 GITHUB_*
2533 setenv =
2634 PYTHONPATH = {toxinidir}
35 PYTHONWARNINGS = d
36 py38-dj31-postgresql: DJANGO_SELENIUM_TESTS = true
37 DB_NAME = {env:DB_NAME:debug_toolbar}
38 DB_USER = {env:DB_USER:debug_toolbar}
39 DB_HOST = {env:DB_HOST:localhost}
40 DB_PASSWORD = {env:DB_PASSWORD:debug_toolbar}
2741 whitelist_externals = make
2842 pip_pre = True
2943 commands = make coverage TEST_ARGS='{posargs:tests}'
3044
31 [testenv:postgresql]
45 [testenv:py{36,37,38,39}-dj{22,30,31,32}-postgresql]
46 setenv =
47 {[testenv]setenv}
48 DB_BACKEND = postgresql
49 DB_PORT = {env:DB_PORT:5432}
50
51 [testenv:py{36,37,38,39}-dj{22,30,31,32}-mysql]
52 setenv =
53 {[testenv]setenv}
54 DB_BACKEND = mysql
55 DB_PORT = {env:DB_PORT:3306}
56
57 [testenv:py{36,37,38,39}-dj{22,30,31,32,main}-sqlite]
58 setenv =
59 {[testenv]setenv}
60 DB_BACKEND = sqlite3
61 DB_NAME = ":memory:"
62
63 [testenv:docs]
64 commands = make -C {toxinidir}/docs spelling
3265 deps =
33 Django>=2.1,<2.2
34 coverage
35 Jinja2
36 html5lib
37 psycopg2-binary
38 selenium<4.0
39 sqlparse
40 setenv =
41 PYTHONPATH = {toxinidir}
42 DJANGO_DATABASE_ENGINE = postgresql
43 whitelist_externals = make
44 pip_pre = True
45 commands = make coverage TEST_ARGS='{posargs:tests}'
46
47 [testenv:mariadb]
48 deps =
49 Django>=2.1,<2.2
50 coverage
51 Jinja2
52 html5lib
53 mysqlclient<1.4
54 selenium<4.0
55 sqlparse
56 setenv =
57 PYTHONPATH = {toxinidir}
58 DJANGO_DATABASE_ENGINE = mysql
59 whitelist_externals = make
60 pip_pre = True
61 commands = make coverage TEST_ARGS='{posargs:tests}'
62
63 [testenv:flake8]
64 basepython = python3
65 commands = make flake8
66 deps = flake8
67 skip_install = true
66 Sphinx
67 sphinxcontrib-spelling
6868
6969 [testenv:style]
70 basepython = python3
7170 commands = make style_check
7271 deps =
73 black
72 black>=19.10b0
7473 flake8
75 isort
76 skip_install = true
77
78 [testenv:jshint]
79 basepython = python3
80 commands = make jshint
74 isort>=5.0.2
8175 skip_install = true
8276
8377 [testenv:readme]
84 basepython = python3
8578 commands = python setup.py check -r -s
8679 deps = readme_renderer
8780 skip_install = true
81
82 [gh-actions]
83 python =
84 3.6: py36
85 3.7: py37
86 3.8: py38
87 3.9: py39
88
89 [gh-actions:env]
90 DB_BACKEND =
91 mysql: mysql
92 postgresql: postgresql
93 sqlite3: sqlite