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
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 |
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 |
1 | 1 | host = https://www.transifex.com |
2 | 2 | lang_map = sr@latin:sr_Latn |
3 | 3 | |
4 | [django-debug-toolbar.master] | |
4 | [django-debug-toolbar.main] | |
5 | 5 | file_filter = debug_toolbar/locale/<lang>/LC_MESSAGES/django.po |
6 | 6 | source_file = debug_toolbar/locale/en/LC_MESSAGES/django.po |
7 | 7 | source_lang = en |
0 | 0 | .PHONY: flake8 example test coverage translatable_strings update_translations |
1 | 1 | |
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)' | |
6 | 3 | |
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/ | |
10 | 11 | |
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/ | |
13 | 19 | |
14 | 20 | 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" | |
15 | 24 | python example/manage.py runserver |
16 | 25 | |
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 $@ | |
22 | 29 | |
23 | 30 | test: |
24 | 31 | DJANGO_SETTINGS_MODULE=tests.settings \ |
30 | 37 | |
31 | 38 | coverage: |
32 | 39 | python --version |
33 | coverage erase | |
34 | 40 | 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} | |
36 | 42 | coverage report |
37 | 43 | coverage html |
44 | coverage xml | |
38 | 45 | |
39 | 46 | translatable_strings: |
40 | 47 | cd debug_toolbar && python -m django makemessages -l en --no-obsolete |
43 | 50 | update_translations: |
44 | 51 | tx pull -a --minimum-perc=10 |
45 | 52 | 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 $@ |
5 | 5 | :target: https://jazzband.co/ |
6 | 6 | :alt: Jazzband |
7 | 7 | |
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 | |
10 | 10 | :alt: Build Status |
11 | 11 | |
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 | |
13 | 13 | :target: https://codecov.io/gh/jazzband/django-debug-toolbar |
14 | 14 | :alt: Test coverage status |
15 | 15 | |
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 | |
19 | 23 | |
20 | 24 | The Django Debug Toolbar is a configurable set of panels that display various |
21 | 25 | debug information about the current request/response and when clicked, display |
23 | 27 | |
24 | 28 | Here's a screenshot of the toolbar in action: |
25 | 29 | |
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 | |
29 | 32 | |
30 | 33 | In addition to the built-in panels, a number of third-party panels are |
31 | 34 | contributed by the community. |
32 | 35 | |
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. | |
35 | 38 | |
36 | 39 | Documentation, including installation and configuration instructions, is |
37 | 40 | available at https://django-debug-toolbar.readthedocs.io/. |
0 | 0 | __all__ = ["VERSION"] |
1 | 1 | |
2 | 2 | |
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" | |
10 | 6 | |
11 | 7 | # Code that discovers files or modules in INSTALLED_APPS imports this module. |
12 | 8 |
5 | 5 | from django.middleware.gzip import GZipMiddleware |
6 | 6 | from django.utils.module_loading import import_string |
7 | 7 | from django.utils.translation import gettext_lazy as _ |
8 | ||
9 | from debug_toolbar import settings as dt_settings | |
8 | 10 | |
9 | 11 | |
10 | 12 | class DebugToolbarConfig(AppConfig): |
19 | 21 | errors = [] |
20 | 22 | gzip_index = None |
21 | 23 | 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 | |
22 | 35 | |
23 | 36 | # Determine the indexes which gzip and/or the toolbar are installed at |
24 | 37 | for i, middleware in enumerate(settings.MIDDLEWARE): |
60 | 73 | id="debug_toolbar.W003", |
61 | 74 | ) |
62 | 75 | ) |
76 | return errors | |
63 | 77 | |
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) | |
64 | 88 | return errors |
65 | 89 | |
66 | 90 | |
72 | 96 | return inspect.isclass(middleware_cls) and issubclass( |
73 | 97 | middleware_cls, middleware_class |
74 | 98 | ) |
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 |
0 | 0 | import functools |
1 | 1 | |
2 | from django.http import Http404 | |
2 | from django.http import Http404, HttpResponseBadRequest | |
3 | 3 | |
4 | 4 | |
5 | 5 | def require_show_toolbar(view): |
14 | 14 | return view(request, *args, **kwargs) |
15 | 15 | |
16 | 16 | 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 | ) |
Binary diff not shown
0 | 0 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER |
1 | 1 | # This file is distributed under the same license as the PACKAGE package. |
2 | # | |
3 | # | |
2 | # | |
3 | # | |
4 | 4 | # Translators: |
5 | 5 | # Dario Agliottone <dario.agliottone@gmail.com>, 2012 |
6 | 6 | # Flavio Curella <flavio.curella@gmail.com>, 2013 |
7 | 7 | # yakky <i.spalletti@nephila.it>, 2013-2014 |
8 | # Andrea Rabbaglietti <rabbagliettiandrea@gmail.com>, 2020 | |
8 | 9 | msgid "" |
9 | 10 | msgstr "" |
10 | 11 | "Project-Id-Version: Django Debug Toolbar\n" |
49 | 50 | |
50 | 51 | #: panels/headers.py:35 |
51 | 52 | msgid "Headers" |
52 | msgstr "Intestazioni" | |
53 | msgstr "Headers" | |
53 | 54 | |
54 | 55 | #: panels/logging.py:64 |
55 | 56 | msgid "Logging" |
72 | 73 | |
73 | 74 | #: panels/redirects.py:17 |
74 | 75 | msgid "Intercept redirects" |
75 | msgstr "Intercetta ridirezioni" | |
76 | msgstr "Intercetta redirezioni" | |
76 | 77 | |
77 | 78 | #: panels/request.py:18 |
78 | 79 | msgid "Request" |
221 | 222 | |
222 | 223 | #: panels/sql/panel.py:38 |
223 | 224 | msgid "Active" |
224 | msgstr "Azione" | |
225 | msgstr "Attivo" | |
225 | 226 | |
226 | 227 | #: panels/sql/panel.py:39 |
227 | 228 | msgid "In transaction" |
241 | 242 | |
242 | 243 | #: panels/templates/panel.py:141 |
243 | 244 | msgid "Templates" |
244 | msgstr "Template" | |
245 | msgstr "Templates" | |
245 | 246 | |
246 | 247 | #: panels/templates/panel.py:146 |
247 | 248 | #, python-format |
328 | 329 | #: templates/debug_toolbar/panels/cache.html:46 |
329 | 330 | #: templates/debug_toolbar/panels/request.html:9 |
330 | 331 | msgid "Keyword arguments" |
331 | msgstr "Parole chiave" | |
332 | msgstr "Keyword arguments" | |
332 | 333 | |
333 | 334 | #: templates/debug_toolbar/panels/cache.html:47 |
334 | 335 | msgid "Backend" |
336 | 337 | |
337 | 338 | #: templates/debug_toolbar/panels/headers.html:3 |
338 | 339 | msgid "Request headers" |
339 | msgstr "Header della request" | |
340 | msgstr "Request headers" | |
340 | 341 | |
341 | 342 | #: templates/debug_toolbar/panels/headers.html:8 |
342 | 343 | #: templates/debug_toolbar/panels/headers.html:27 |
343 | 344 | #: templates/debug_toolbar/panels/headers.html:48 |
344 | 345 | msgid "Key" |
345 | msgstr "Nome" | |
346 | msgstr "Chiave" | |
346 | 347 | |
347 | 348 | #: templates/debug_toolbar/panels/headers.html:9 |
348 | 349 | #: templates/debug_toolbar/panels/headers.html:28 |
528 | 529 | |
529 | 530 | #: templates/debug_toolbar/panels/sql_explain.html:4 |
530 | 531 | msgid "SQL explained" |
531 | msgstr "SQL spigato" | |
532 | msgstr "SQL spiegato" | |
532 | 533 | |
533 | 534 | #: templates/debug_toolbar/panels/sql_explain.html:9 |
534 | 535 | #: templates/debug_toolbar/panels/sql_profile.html:10 |
0 | 0 | from time import time |
1 | 1 | |
2 | import django | |
2 | 3 | import sqlparse |
3 | 4 | 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 | |
5 | 11 | |
6 | 12 | # 'debugsqlshell' is the same as the 'shell'. |
7 | 13 | |
8 | 14 | |
9 | class PrintQueryWrapper(db_backends_utils.CursorDebugWrapper): | |
15 | class PrintQueryWrapper(base_module.CursorDebugWrapper): | |
10 | 16 | def execute(self, sql, params=()): |
11 | 17 | start_time = time() |
12 | 18 | try: |
19 | 25 | print("{} [{:.2f}ms]".format(formatted_sql, duration)) |
20 | 26 | |
21 | 27 | |
22 | db_backends_utils.CursorDebugWrapper = PrintQueryWrapper | |
28 | base_module.CursorDebugWrapper = PrintQueryWrapper |
17 | 17 | """ |
18 | 18 | Default function to determine whether to show the toolbar on a given page. |
19 | 19 | """ |
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 | |
24 | 21 | |
25 | 22 | |
26 | 23 | @lru_cache() |
44 | 41 | self.get_response = get_response |
45 | 42 | |
46 | 43 | 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. | |
49 | 45 | 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): | |
51 | 47 | return self.get_response(request) |
52 | 48 | |
53 | 49 | toolbar = DebugToolbar(request, self.get_response) |
64 | 60 | for panel in reversed(toolbar.enabled_panels): |
65 | 61 | panel.disable_instrumentation() |
66 | 62 | |
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 | ||
67 | 75 | # Check for responses where the toolbar can't be inserted. |
68 | 76 | content_encoding = response.get("Content-Encoding", "") |
69 | 77 | 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 | |
76 | 82 | ): |
77 | 83 | 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) | |
82 | 84 | |
83 | 85 | # Insert the toolbar in the response. |
84 | 86 | content = response.content.decode(response.charset) |
86 | 88 | pattern = re.escape(insert_before) |
87 | 89 | bits = re.split(pattern, content, flags=re.IGNORECASE) |
88 | 90 | 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 | |
99 | 92 | response.content = insert_before.join(bits) |
100 | if response.get("Content-Length", None): | |
93 | if "Content-Length" in response: | |
101 | 94 | response["Content-Length"] = len(response.content) |
102 | 95 | return response |
103 | 96 | |
111 | 104 | continue |
112 | 105 | |
113 | 106 | 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"` | |
115 | 108 | data.append( |
116 | '{}_{}={}; "{}"'.format( | |
109 | '{}_{};dur={};desc="{}"'.format( | |
117 | 110 | panel.panel_id, key, record.get("value"), record.get("title") |
118 | 111 | ) |
119 | 112 | ) |
63 | 63 | return True |
64 | 64 | |
65 | 65 | @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 | |
66 | 75 | def title(self): |
67 | 76 | """ |
68 | 77 | Title shown in the panel when it's displayed in full screen. |
92 | 101 | """ |
93 | 102 | if self.has_content: |
94 | 103 | 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 [] | |
95 | 111 | |
96 | 112 | # URLs for panel-specific views |
97 | 113 | |
114 | 130 | time. |
115 | 131 | |
116 | 132 | 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. | |
119 | 134 | """ |
120 | 135 | |
121 | 136 | def disable_instrumentation(self): |
194 | 209 | |
195 | 210 | Does not return a value. |
196 | 211 | """ |
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 [] |
2 | 2 | import time |
3 | 3 | from collections import OrderedDict |
4 | 4 | |
5 | try: | |
6 | from django.utils.connection import ConnectionProxy | |
7 | except ImportError: | |
8 | ConnectionProxy = None | |
9 | ||
5 | 10 | from django.conf import settings |
6 | 11 | 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 | ) | |
8 | 18 | from django.core.cache.backends.base import BaseCache |
9 | 19 | from django.dispatch import Signal |
10 | 20 | from django.middleware import cache as middleware_cache |
19 | 29 | tidy_stacktrace, |
20 | 30 | ) |
21 | 31 | |
22 | cache_called = Signal( | |
23 | providing_args=["time_taken", "name", "return_value", "args", "kwargs", "trace"] | |
24 | ) | |
32 | cache_called = Signal() | |
25 | 33 | |
26 | 34 | |
27 | 35 | def send_signal(method): |
85 | 93 | return self.cache.set(*args, **kwargs) |
86 | 94 | |
87 | 95 | @send_signal |
96 | def touch(self, *args, **kwargs): | |
97 | return self.cache.touch(*args, **kwargs) | |
98 | ||
99 | @send_signal | |
88 | 100 | def delete(self, *args, **kwargs): |
89 | 101 | return self.cache.delete(*args, **kwargs) |
90 | 102 | |
154 | 166 | ("add", 0), |
155 | 167 | ("get", 0), |
156 | 168 | ("set", 0), |
169 | ("touch", 0), | |
157 | 170 | ("delete", 0), |
158 | 171 | ("clear", 0), |
159 | 172 | ("get_many", 0), |
179 | 192 | trace=None, |
180 | 193 | template_info=None, |
181 | 194 | backend=None, |
182 | **kw | |
195 | **kw, | |
183 | 196 | ): |
184 | 197 | if name == "get": |
185 | 198 | if return_value is None: |
215 | 228 | @property |
216 | 229 | def nav_subtitle(self): |
217 | 230 | 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 | ) | |
223 | 239 | |
224 | 240 | @property |
225 | 241 | def title(self): |
226 | 242 | 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 | ) | |
232 | 251 | |
233 | 252 | def enable_instrumentation(self): |
234 | 253 | if isinstance(middleware_cache.caches, CacheHandlerPatch): |
236 | 255 | else: |
237 | 256 | cache.caches = CacheHandlerPatch() |
238 | 257 | |
258 | # Wrap the patched cache inside Django's ConnectionProxy | |
259 | if ConnectionProxy: | |
260 | cache.cache = ConnectionProxy(cache.caches, DEFAULT_CACHE_ALIAS) | |
261 | ||
239 | 262 | def disable_instrumentation(self): |
240 | 263 | cache.caches = original_caches |
264 | cache.cache = original_cache | |
241 | 265 | # While it can be restored to the original, any views that were |
242 | 266 | # wrapped with the cache_page decorator will continue to use a |
243 | 267 | # 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") |
63 | 63 | |
64 | 64 | @property |
65 | 65 | 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 | |
68 | 68 | return __("%(count)s message", "%(count)s messages", record_count) % { |
69 | 69 | "count": record_count |
70 | 70 | } |
14 | 14 | |
15 | 15 | def process_request(self, request): |
16 | 16 | 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") | |
19 | 19 | if redirect_to: |
20 | 20 | status_line = "{} {}".format( |
21 | 21 | response.status_code, response.reason_phrase |
2 | 2 | from django.utils.translation import gettext_lazy as _ |
3 | 3 | |
4 | 4 | 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 | |
6 | 6 | |
7 | 7 | |
8 | 8 | class RequestPanel(Panel): |
25 | 25 | def generate_stats(self, request, response): |
26 | 26 | self.record_stats( |
27 | 27 | { |
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), | |
33 | 31 | } |
34 | 32 | ) |
33 | ||
35 | 34 | view_info = { |
36 | 35 | "view_func": _("<no view>"), |
37 | 36 | "view_args": "None", |
44 | 43 | view_info["view_func"] = get_name_from_obj(func) |
45 | 44 | view_info["view_args"] = args |
46 | 45 | 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 | ||
48 | 56 | except Http404: |
49 | 57 | pass |
50 | 58 | self.record_stats(view_info) |
0 | 0 | from collections import OrderedDict |
1 | 1 | |
2 | import django | |
2 | 3 | from django.conf import settings |
3 | 4 | from django.utils.translation import gettext_lazy as _ |
4 | from django.views.debug import get_safe_settings | |
5 | 5 | |
6 | 6 | 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 | |
7 | 14 | |
8 | 15 | |
9 | 16 | class SettingsPanel(Panel): |
16 | 23 | nav_title = _("Settings") |
17 | 24 | |
18 | 25 | def title(self): |
19 | return _("Settings from <code>%s</code>") % settings.SETTINGS_MODULE | |
26 | return _("Settings from %s") % settings.SETTINGS_MODULE | |
20 | 27 | |
21 | 28 | def generate_stats(self, request, response): |
22 | 29 | self.record_stats( |
0 | 0 | import weakref |
1 | 1 | |
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 | ) | |
3 | 8 | from django.db.backends.signals import connection_created |
4 | 9 | from django.db.models.signals import ( |
5 | 10 | class_prepared, |
11 | m2m_changed, | |
6 | 12 | post_delete, |
7 | 13 | post_init, |
8 | 14 | post_migrate, |
9 | 15 | post_save, |
10 | 16 | pre_delete, |
11 | 17 | pre_init, |
18 | pre_migrate, | |
12 | 19 | pre_save, |
13 | 20 | ) |
14 | 21 | from django.utils.module_loading import import_string |
32 | 39 | "post_save": post_save, |
33 | 40 | "pre_delete": pre_delete, |
34 | 41 | "post_delete": post_delete, |
42 | "m2m_changed": m2m_changed, | |
43 | "pre_migrate": pre_migrate, | |
35 | 44 | "post_migrate": post_migrate, |
45 | "setting_changed": setting_changed, | |
36 | 46 | } |
37 | 47 | |
38 | 48 | def nav_subtitle(self): |
39 | 49 | 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) | |
41 | 51 | num_signals = len(signals) |
42 | 52 | # here we have to handle a double count translation, hence the |
43 | 53 | # hard coding of one signal |
44 | 54 | 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", | |
48 | 67 | 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 | ) | |
55 | 71 | |
56 | 72 | title = _("Signals") |
57 | 73 | |
84 | 100 | else: |
85 | 101 | text = receiver_name |
86 | 102 | receivers.append(text) |
87 | signals.append((name, signal, receivers)) | |
103 | signals.append((name, receivers)) | |
88 | 104 | |
89 | 105 | self.record_stats({"signals": signals}) |
0 | import hashlib | |
1 | import hmac | |
2 | 0 | import json |
3 | 1 | |
4 | 2 | from django import forms |
5 | from django.conf import settings | |
6 | 3 | from django.core.exceptions import ValidationError |
7 | 4 | from django.db import connections |
8 | from django.utils.crypto import constant_time_compare | |
9 | from django.utils.encoding import force_bytes | |
10 | 5 | from django.utils.functional import cached_property |
11 | 6 | |
12 | 7 | from debug_toolbar.panels.sql.utils import reformat_sql |
20 | 15 | raw_sql: The sql statement with placeholders |
21 | 16 | params: JSON encoded parameter values |
22 | 17 | duration: time for SQL to execute passed in from toolbar just for redisplay |
23 | hash: the hash of (secret + sql + params) for tamper checking | |
24 | 18 | """ |
25 | 19 | |
26 | 20 | sql = forms.CharField() |
28 | 22 | params = forms.CharField() |
29 | 23 | alias = forms.CharField(required=False, initial="default") |
30 | 24 | 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() | |
43 | 25 | |
44 | 26 | def clean_raw_sql(self): |
45 | 27 | value = self.cleaned_data["raw_sql"] |
65 | 47 | |
66 | 48 | return value |
67 | 49 | |
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 | ||
76 | 50 | def reformat_sql(self): |
77 | 51 | 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() | |
84 | 52 | |
85 | 53 | @property |
86 | 54 | def connection(self): |
2 | 2 | from copy import copy |
3 | 3 | from pprint import saferepr |
4 | 4 | |
5 | from django.conf.urls import url | |
6 | 5 | from django.db import connections |
6 | from django.urls import path | |
7 | 7 | from django.utils.translation import gettext_lazy as _, ngettext_lazy as __ |
8 | 8 | |
9 | from debug_toolbar.forms import SignedDataForm | |
9 | 10 | from debug_toolbar.panels import Panel |
10 | 11 | from debug_toolbar.panels.sql import views |
11 | 12 | from debug_toolbar.panels.sql.forms import SQLSelectForm |
116 | 117 | @property |
117 | 118 | def title(self): |
118 | 119 | 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 | ) | |
124 | 128 | |
125 | 129 | template = "debug_toolbar/panels/sql.html" |
126 | 130 | |
127 | 131 | @classmethod |
128 | 132 | def get_urls(cls): |
129 | 133 | 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"), | |
133 | 137 | ] |
134 | 138 | |
135 | 139 | def enable_instrumentation(self): |
207 | 211 | query["vendor"], query["trans_status"] |
208 | 212 | ) |
209 | 213 | |
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 | ) | |
211 | 217 | |
212 | 218 | if query["sql"]: |
213 | 219 | query["sql"] = reformat_sql(query["sql"], with_toggle=True) |
214 | 220 | query["rgb_color"] = self._databases[alias]["rgb_color"] |
215 | 221 | try: |
216 | 222 | 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 | ) | |
220 | 223 | except ZeroDivisionError: |
221 | 224 | query["width_ratio"] = 0 |
222 | query["width_ratio_relative"] = 0 | |
223 | 225 | query["start_offset"] = width_ratio_tally |
224 | 226 | query["end_offset"] = query["width_ratio"] + query["start_offset"] |
225 | 227 | width_ratio_tally += query["width_ratio"] |
2 | 2 | from threading import local |
3 | 3 | from time import time |
4 | 4 | |
5 | from django.utils.encoding import force_text | |
5 | from django.utils.encoding import force_str | |
6 | 6 | |
7 | 7 | from debug_toolbar import settings as dt_settings |
8 | 8 | 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 | |
9 | 14 | |
10 | 15 | |
11 | 16 | class SQLQueryTriggered(Exception): |
104 | 109 | return [self._quote_expr(p) for p in params] |
105 | 110 | |
106 | 111 | def _decode(self, param): |
112 | if PostgresJson and isinstance(param, PostgresJson): | |
113 | return param.dumps(param.adapted) | |
107 | 114 | # If a sequence type, decode each element separately |
108 | 115 | if isinstance(param, (tuple, list)): |
109 | 116 | return [self._decode(element) for element in param] |
112 | 119 | if isinstance(param, dict): |
113 | 120 | return {key: self._decode(value) for key, value in param.items()} |
114 | 121 | |
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 | |
116 | 123 | CONVERT_TYPES = (datetime.datetime, datetime.date, datetime.time) |
117 | 124 | 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)) | |
119 | 126 | except UnicodeDecodeError: |
120 | 127 | return "(encoded string)" |
121 | 128 | |
135 | 142 | _params = json.dumps(self._decode(params)) |
136 | 143 | except TypeError: |
137 | 144 | pass # object not JSON serializable |
138 | ||
139 | 145 | template_info = get_template_info() |
140 | 146 | |
141 | 147 | alias = getattr(self.db, "alias", "default") |
2 | 2 | import sqlparse |
3 | 3 | from django.utils.html import escape |
4 | 4 | from sqlparse import tokens as T |
5 | ||
6 | from debug_toolbar import settings as dt_settings | |
5 | 7 | |
6 | 8 | |
7 | 9 | class BoldKeywordFilter: |
23 | 25 | if not with_toggle: |
24 | 26 | return formatted |
25 | 27 | 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) | |
28 | 30 | return collapsed + uncollapsed |
29 | 31 | |
30 | 32 | |
31 | 33 | def parse_sql(sql, aligned_indent=False): |
32 | 34 | stack = sqlparse.engine.FilterStack() |
33 | stack.enable_grouping() | |
35 | if dt_settings.get_config()["PRETTIFY_SQL"]: | |
36 | stack.enable_grouping() | |
34 | 37 | if aligned_indent: |
35 | 38 | stack.stmtprocess.append( |
36 | 39 | sqlparse.filters.AlignedIndentFilter(char=" ", 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 | |
2 | 2 | from django.views.decorators.csrf import csrf_exempt |
3 | 3 | |
4 | from debug_toolbar.decorators import require_show_toolbar | |
4 | from debug_toolbar.decorators import require_show_toolbar, signed_data_view | |
5 | 5 | from debug_toolbar.panels.sql.forms import SQLSelectForm |
6 | 6 | |
7 | 7 | |
8 | 8 | @csrf_exempt |
9 | 9 | @require_show_toolbar |
10 | def sql_select(request): | |
10 | @signed_data_view | |
11 | def sql_select(request, verified_data): | |
11 | 12 | """Returns the output of the SQL SELECT statement""" |
12 | form = SQLSelectForm(request.POST or None) | |
13 | form = SQLSelectForm(verified_data) | |
13 | 14 | |
14 | 15 | if form.is_valid(): |
15 | 16 | sql = form.cleaned_data["raw_sql"] |
16 | 17 | 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 | ||
22 | 23 | context = { |
23 | 24 | "result": result, |
24 | 25 | "sql": form.reformat_sql(), |
26 | 27 | "headers": headers, |
27 | 28 | "alias": form.cleaned_data["alias"], |
28 | 29 | } |
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}) | |
31 | 32 | return HttpResponseBadRequest("Form errors") |
32 | 33 | |
33 | 34 | |
34 | 35 | @csrf_exempt |
35 | 36 | @require_show_toolbar |
36 | def sql_explain(request): | |
37 | @signed_data_view | |
38 | def sql_explain(request, verified_data): | |
37 | 39 | """Returns the output of the SQL EXPLAIN on the given query""" |
38 | form = SQLSelectForm(request.POST or None) | |
40 | form = SQLSelectForm(verified_data) | |
39 | 41 | |
40 | 42 | if form.is_valid(): |
41 | 43 | sql = form.cleaned_data["raw_sql"] |
42 | 44 | params = form.cleaned_data["params"] |
43 | 45 | 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() | |
45 | 58 | |
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() | |
59 | 59 | context = { |
60 | 60 | "result": result, |
61 | 61 | "sql": form.reformat_sql(), |
63 | 63 | "headers": headers, |
64 | 64 | "alias": form.cleaned_data["alias"], |
65 | 65 | } |
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}) | |
68 | 68 | return HttpResponseBadRequest("Form errors") |
69 | 69 | |
70 | 70 | |
71 | 71 | @csrf_exempt |
72 | 72 | @require_show_toolbar |
73 | def sql_profile(request): | |
73 | @signed_data_view | |
74 | def sql_profile(request, verified_data): | |
74 | 75 | """Returns the output of running the SQL and getting the profiling statistics""" |
75 | form = SQLSelectForm(request.POST or None) | |
76 | form = SQLSelectForm(verified_data) | |
76 | 77 | |
77 | 78 | if form.is_valid(): |
78 | 79 | sql = form.cleaned_data["raw_sql"] |
79 | 80 | params = form.cleaned_data["params"] |
80 | cursor = form.cursor | |
81 | 81 | result = None |
82 | 82 | headers = None |
83 | 83 | 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 | ||
109 | 111 | context = { |
110 | 112 | "result": result, |
111 | 113 | "result_error": result_error, |
114 | 116 | "headers": headers, |
115 | 117 | "alias": form.cleaned_data["alias"], |
116 | 118 | } |
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}) | |
119 | 121 | return HttpResponseBadRequest("Form errors") |
2 | 2 | |
3 | 3 | from django.conf import settings |
4 | 4 | from django.contrib.staticfiles import finders, storage |
5 | from django.core.checks import Warning | |
5 | 6 | from django.core.files.storage import get_storage_class |
6 | 7 | from django.utils.functional import LazyObject |
7 | 8 | from django.utils.translation import gettext_lazy as _, ngettext as __ |
98 | 99 | |
99 | 100 | @property |
100 | 101 | def num_used(self): |
101 | return len(self._paths[threading.currentThread()]) | |
102 | stats = self.get_stats() | |
103 | return stats and stats["num_used"] | |
102 | 104 | |
103 | 105 | nav_title = _("Static files") |
104 | 106 | |
120 | 122 | self.record_stats( |
121 | 123 | { |
122 | 124 | "num_found": self.num_found, |
123 | "num_used": self.num_used, | |
125 | "num_used": len(used_paths), | |
124 | 126 | "staticfiles": used_paths, |
125 | 127 | "staticfiles_apps": self.get_staticfiles_apps(), |
126 | 128 | "staticfiles_dirs": self.get_staticfiles_dirs(), |
136 | 138 | """ |
137 | 139 | finders_mapping = OrderedDict() |
138 | 140 | 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 | |
150 | 156 | return finders_mapping |
151 | 157 | |
152 | 158 | def get_staticfiles_dirs(self): |
170 | 176 | if app not in apps: |
171 | 177 | apps.append(app) |
172 | 178 | 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 |
3 | 3 | from pprint import pformat, saferepr |
4 | 4 | |
5 | 5 | from django import http |
6 | from django.conf.urls import url | |
7 | 6 | from django.core import signing |
8 | 7 | from django.db.models.query import QuerySet, RawQuerySet |
9 | 8 | from django.template import RequestContext, Template |
10 | 9 | from django.test.signals import template_rendered |
11 | 10 | from django.test.utils import instrumented_test_render |
11 | from django.urls import path | |
12 | 12 | from django.utils.translation import gettext_lazy as _ |
13 | 13 | |
14 | 14 | from debug_toolbar.panels import Panel |
66 | 66 | def __init__(self, *args, **kwargs): |
67 | 67 | super().__init__(*args, **kwargs) |
68 | 68 | 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. | |
78 | 71 | self.pformat_layers = [] |
79 | 72 | |
80 | 73 | def _store_template_info(self, sender, **kwargs): |
93 | 86 | context_list = [] |
94 | 87 | for context_layer in context.dicts: |
95 | 88 | 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: | |
105 | 97 | temp_layer = {} |
106 | 98 | for key, value in context_layer.items(): |
107 | 99 | # Replace any request elements - they have a large |
108 | # unicode representation and the request data is | |
100 | # Unicode representation and the request data is | |
109 | 101 | # already made available from the Request panel. |
110 | 102 | if isinstance(value, http.HttpRequest): |
111 | 103 | temp_layer[key] = "<<request>>" |
120 | 112 | # QuerySet would trigger the database: user can run the |
121 | 113 | # query from SQL Panel |
122 | 114 | elif isinstance(value, (QuerySet, RawQuerySet)): |
123 | model_name = "{}.{}".format( | |
124 | value.model._meta.app_label, value.model.__name__ | |
125 | ) | |
126 | 115 | temp_layer[key] = "<<{} of {}>>".format( |
127 | value.__class__.__name__.lower(), model_name | |
116 | value.__class__.__name__.lower(), | |
117 | value.model._meta.label, | |
128 | 118 | ) |
129 | 119 | else: |
120 | recording(False) | |
130 | 121 | try: |
131 | recording(False) | |
132 | 122 | saferepr(value) # this MAY trigger a db query |
133 | 123 | except SQLQueryTriggered: |
134 | 124 | temp_layer[key] = "<<triggers database query>>" |
135 | 125 | except UnicodeEncodeError: |
136 | temp_layer[key] = "<<unicode encode error>>" | |
126 | temp_layer[key] = "<<Unicode encode error>>" | |
137 | 127 | except Exception: |
138 | 128 | temp_layer[key] = "<<unhandled exception>>" |
139 | 129 | else: |
140 | 130 | temp_layer[key] = value |
141 | 131 | finally: |
142 | 132 | 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 | |
146 | 133 | 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) | |
156 | 136 | |
157 | 137 | kwargs["context"] = context_list |
158 | 138 | kwargs["context_processors"] = getattr(context, "context_processors", None) |
179 | 159 | |
180 | 160 | @classmethod |
181 | 161 | 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")] | |
185 | 163 | |
186 | 164 | def enable_instrumentation(self): |
187 | 165 | template_rendered.connect(self._store_template_info) |
194 | 172 | for template_data in self.templates: |
195 | 173 | info = {} |
196 | 174 | # Clean up some info about templates |
197 | template = template_data.get("template", None) | |
175 | template = template_data["template"] | |
198 | 176 | if hasattr(template, "origin") and template.origin and template.origin.name: |
199 | 177 | template.origin_name = template.origin.name |
200 | 178 | template.origin_hash = signing.dumps(template.origin.name) |
0 | 0 | from django.core import signing |
1 | from django.http import HttpResponseBadRequest | |
1 | from django.http import HttpResponseBadRequest, JsonResponse | |
2 | 2 | from django.template import Origin, TemplateDoesNotExist |
3 | 3 | from django.template.engine import Engine |
4 | from django.template.response import SimpleTemplateResponse | |
4 | from django.template.loader import render_to_string | |
5 | 5 | from django.utils.safestring import mark_safe |
6 | 6 | |
7 | 7 | from debug_toolbar.decorators import require_show_toolbar |
13 | 13 | Return the source of a template, syntax-highlighted by Pygments if |
14 | 14 | it's available. |
15 | 15 | """ |
16 | template_origin_name = request.GET.get("template_origin", None) | |
16 | template_origin_name = request.GET.get("template_origin") | |
17 | 17 | if template_origin_name is None: |
18 | 18 | return HttpResponseBadRequest('"template_origin" key is required') |
19 | 19 | try: |
47 | 47 | |
48 | 48 | try: |
49 | 49 | from pygments import highlight |
50 | from pygments.formatters import HtmlFormatter | |
50 | 51 | from pygments.lexers import HtmlDjangoLexer |
51 | from pygments.formatters import HtmlFormatter | |
52 | 52 | |
53 | 53 | source = highlight(source, HtmlDjangoLexer(), HtmlFormatter()) |
54 | 54 | source = mark_safe(source) |
56 | 56 | except ImportError: |
57 | 57 | pass |
58 | 58 | |
59 | # Using SimpleTemplateResponse avoids running global context processors. | |
60 | return SimpleTemplateResponse( | |
59 | content = render_to_string( | |
61 | 60 | "debug_toolbar/panels/template_source.html", |
62 | 61 | {"source": source, "template_name": template_name}, |
63 | 62 | ) |
63 | return JsonResponse({"content": content}) |
0 | 0 | import time |
1 | 1 | |
2 | 2 | from django.template.loader import render_to_string |
3 | from django.templatetags.static import static | |
3 | 4 | from django.utils.translation import gettext_lazy as _ |
4 | 5 | |
5 | 6 | from debug_toolbar.panels import Panel |
50 | 51 | ) |
51 | 52 | return render_to_string(self.template, {"rows": rows}) |
52 | 53 | |
54 | @property | |
55 | def scripts(self): | |
56 | scripts = super().scripts | |
57 | scripts.append(static("debug_toolbar/js/timer.js")) | |
58 | return scripts | |
59 | ||
53 | 60 | def process_request(self, request): |
54 | 61 | self._start_time = time.time() |
55 | 62 | if self.has_content: |
16 | 16 | }, |
17 | 17 | "INSERT_BEFORE": "</body>", |
18 | 18 | "RENDER_PANELS": None, |
19 | "RESULTS_CACHE_SIZE": 10, | |
19 | "RESULTS_CACHE_SIZE": 25, | |
20 | 20 | "ROOT_TAG_EXTRA_ATTRS": "", |
21 | 21 | "SHOW_COLLAPSED": False, |
22 | 22 | "SHOW_TOOLBAR_CALLBACK": "debug_toolbar.middleware.show_toolbar", |
23 | 23 | # Panel options |
24 | 24 | "EXTRA_SIGNALS": [], |
25 | 25 | "ENABLE_STACKTRACES": True, |
26 | "ENABLE_STACKTRACES_LOCALS": False, | |
26 | 27 | "HIDE_IN_STACKTRACES": ( |
27 | 28 | "socketserver", |
28 | 29 | "threading", |
35 | 36 | "django.utils.deprecation", |
36 | 37 | "django.utils.functional", |
37 | 38 | ), |
39 | "PRETTIFY_SQL": True, | |
38 | 40 | "PROFILER_MAX_DEPTH": 10, |
39 | 41 | "SHOW_TEMPLATE_CONTEXT": True, |
40 | 42 | "SKIP_TEMPLATE_PREFIXES": ("django/forms/widgets/", "admin/widgets/"), |
51 | 53 | |
52 | 54 | |
53 | 55 | PANELS_DEFAULTS = [ |
56 | "debug_toolbar.panels.history.HistoryPanel", | |
54 | 57 | "debug_toolbar.panels.versions.VersionsPanel", |
55 | 58 | "debug_toolbar.panels.timer.TimerPanel", |
56 | 59 | "debug_toolbar.panels.settings.SettingsPanel", |
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 | ||
14 | 0 | /* 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, | |
25 | 66 | #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; | |
44 | 82 | transition: none; |
45 | 83 | } |
46 | 84 | |
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 { | |
68 | 99 | 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); | |
75 | 101 | border-color: #bbb; |
76 | 102 | border-bottom-color: #999; |
77 | 103 | cursor: pointer; |
78 | 104 | text-shadow: 0 1px 0 #ddd; |
79 | 105 | } |
80 | 106 | |
81 | #djDebug button:active, #djDebug a.button:active { | |
107 | #djDebug button:active { | |
82 | 108 | border: 1px solid #aaa; |
83 | 109 | 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; | |
86 | 110 | box-shadow: inset 0 0 5px 2px #aaa, 0 1px 0 0 #eee; |
87 | 111 | } |
88 | 112 | |
89 | 113 | #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; | |
99 | 123 | } |
100 | 124 | |
101 | 125 | #djDebug #djDebugToolbar small { |
102 | color:#999; | |
126 | color: #999; | |
103 | 127 | } |
104 | 128 | |
105 | 129 | #djDebug #djDebugToolbar ul { |
106 | margin:0; | |
107 | padding:0; | |
108 | list-style:none; | |
130 | margin: 0; | |
131 | padding: 0; | |
132 | list-style: none; | |
109 | 133 | } |
110 | 134 | |
111 | 135 | #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; | |
141 | 165 | } |
142 | 166 | |
143 | 167 | #djDebug #djDebugToolbar li a:hover { |
144 | color:#111; | |
145 | background-color:#ffc; | |
168 | color: #111; | |
169 | background-color: #ffc; | |
146 | 170 | } |
147 | 171 | |
148 | 172 | #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%; | |
152 | 184 | } |
153 | 185 | |
154 | 186 | #djDebug #djDebugToolbar li.djdt-active a:hover { |
155 | color:#b36a60; | |
156 | background-color:transparent; | |
187 | color: #b36a60; | |
188 | background-color: transparent; | |
157 | 189 | } |
158 | 190 | |
159 | 191 | #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; | |
165 | 196 | } |
166 | 197 | |
167 | 198 | #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; | |
175 | 209 | } |
176 | 210 | |
177 | 211 | #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; | |
194 | 220 | } |
195 | 221 | |
196 | 222 | #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; | |
203 | 237 | } |
204 | 238 | |
205 | 239 | #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; | |
215 | 246 | } |
216 | 247 | |
217 | 248 | #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; | |
228 | 258 | } |
229 | 259 | |
230 | 260 | #djDebug .djdt-panelContent > div { |
231 | border-bottom:1px solid #ddd; | |
261 | border-bottom: 1px solid #ddd; | |
232 | 262 | } |
233 | 263 | |
234 | 264 | #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; | |
243 | 273 | } |
244 | 274 | |
245 | 275 | #djDebug .djDebugPanelTitle code { |
246 | display:inline; | |
247 | font-size:inherit; | |
276 | display: inline; | |
277 | font-size: inherit; | |
248 | 278 | } |
249 | 279 | |
250 | 280 | #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; | |
258 | 288 | } |
259 | 289 | |
260 | 290 | #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 | } | |
263 | 307 | } |
264 | 308 | |
265 | 309 | #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; | |
270 | 314 | } |
271 | 315 | |
272 | 316 | #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; | |
276 | 320 | } |
277 | 321 | |
278 | 322 | #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; | |
282 | 326 | } |
283 | 327 | |
284 | 328 | #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; | |
292 | 339 | } |
293 | 340 | #djDebug .djdt-panelContent tbody td, |
294 | 341 | #djDebug .djdt-panelContent tbody th { |
295 | vertical-align:top; | |
296 | padding:2px 3px; | |
342 | vertical-align: top; | |
343 | padding: 2px 3px; | |
297 | 344 | } |
298 | 345 | #djDebug .djdt-panelContent tbody td.djdt-time { |
299 | 346 | text-align: center; |
300 | 347 | } |
301 | 348 | |
302 | 349 | #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; | |
307 | 354 | white-space: nowrap; |
308 | 355 | } |
309 | 356 | #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; | |
314 | 361 | } |
315 | 362 | |
316 | 363 | #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 | } | |
371 | 366 | |
372 | 367 | #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; | |
381 | 383 | } |
382 | 384 | |
383 | 385 | #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; | |
397 | 392 | } |
398 | 393 | |
399 | 394 | #djDebug .djdt-panelContent dt { |
400 | margin-top:0.75em; | |
395 | margin-top: 0.75em; | |
401 | 396 | } |
402 | 397 | |
403 | 398 | #djDebug .djdt-panelContent dd { |
404 | margin-left:10px; | |
399 | margin-left: 10px; | |
405 | 400 | } |
406 | 401 | |
407 | 402 | #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; | |
413 | 406 | } |
414 | 407 | |
415 | 408 | #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; | |
427 | 413 | } |
428 | 414 | |
429 | 415 | #djDebug .djDebugCollapsed { |
430 | display: none; | |
431 | text-decoration: none; | |
432 | 416 | color: #333; |
433 | 417 | } |
434 | 418 | |
435 | 419 | #djDebug .djDebugUncollapsed { |
436 | 420 | color: #333; |
437 | text-decoration: none; | |
438 | 421 | } |
439 | 422 | |
440 | 423 | #djDebug .djUnselected { |
441 | 424 | display: none; |
442 | 425 | } |
443 | #djDebug tr.djHiddenByDefault { | |
444 | display: none; | |
445 | } | |
426 | ||
446 | 427 | #djDebug tr.djSelected { |
447 | 428 | display: table-row; |
448 | 429 | } |
449 | 430 | |
450 | 431 | #djDebug .djDebugSql { |
451 | word-break:break-word; | |
452 | z-index:100000002; | |
432 | overflow-wrap: anywhere; | |
453 | 433 | } |
454 | 434 | |
455 | 435 | #djDebug .djSQLDetailsDiv tbody th { |
456 | text-align: left; | |
457 | } | |
458 | ||
459 | #djDebug .djSqlExplain td { | |
460 | white-space: pre; | |
436 | text-align: left; | |
461 | 437 | } |
462 | 438 | |
463 | 439 | #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; | |
472 | 448 | } |
473 | 449 | #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 | ||
559 | 532 | #djDebug .djDebugRowWarning .djdt-time { |
560 | 533 | color: red; |
561 | 534 | } |
562 | #djdebug .djdt-panelContent table .djdt-toggle { | |
535 | #djDebug .djdt-panelContent table .djdt-toggle { | |
563 | 536 | width: 14px; |
564 | 537 | padding-top: 3px; |
565 | 538 | } |
567 | 540 | min-width: 70px; |
568 | 541 | white-space: nowrap; |
569 | 542 | } |
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; | |
577 | 548 | padding: 0; |
578 | } | |
579 | #djDebug .djToggleSwitch { | |
580 | text-decoration: none; | |
581 | 549 | border: 1px solid #999; |
582 | height: 12px; | |
550 | border-radius: 0; | |
583 | 551 | width: 12px; |
584 | line-height: 12px; | |
585 | text-align: center; | |
586 | 552 | 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); | |
591 | 554 | } |
592 | 555 | #djDebug .djNoToggleSwitch { |
593 | 556 | height: 14px; |
596 | 559 | } |
597 | 560 | |
598 | 561 | #djDebug .djSQLDetailsDiv { |
599 | margin-top:0.8em; | |
562 | margin-top: 0.8em; | |
600 | 563 | } |
601 | 564 | #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; | |
607 | 566 | 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; | |
612 | 571 | overflow: auto; |
613 | padding:2px 3px; | |
572 | padding: 2px 3px; | |
614 | 573 | 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; | |
616 | 576 | } |
617 | 577 | #djDebug .djdt-stack span { |
618 | 578 | color: #000; |
619 | 579 | font-weight: bold; |
620 | 580 | } |
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 { | |
622 | 584 | color: #777; |
623 | 585 | font-weight: normal; |
624 | 586 | } |
625 | 587 | #djDebug .djdt-stack span.djdt-code { |
626 | 588 | font-weight: normal; |
627 | 589 | } |
590 | #djDebug .djdt-stack pre.djdt-locals { | |
591 | margin: 0 27px 27px 27px; | |
592 | } | |
628 | 593 | |
629 | 594 | #djDebug .djdt-width-20 { |
630 | 595 | width: 20%; |
631 | 596 | } |
597 | #djDebug .djdt-width-30 { | |
598 | width: 30%; | |
599 | } | |
632 | 600 | #djDebug .djdt-width-60 { |
633 | 601 | width: 60%; |
634 | 602 | } |
603 | #djDebug .djdt-max-height-100 { | |
604 | max-height: 100%; | |
605 | } | |
635 | 606 | #djDebug .djdt-highlighted { |
636 | 607 | background-color: lightgrey; |
637 | 608 | } |
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 | 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) { | |
66 | 18 | event.preventDefault(); |
67 | 19 | if (!this.className) { |
68 | 20 | return; |
69 | 21 | } |
70 | var current = djDebug.querySelector('#' + this.className); | |
22 | const current = document.getElementById(this.className); | |
71 | 23 | if ($$.visible(current)) { |
72 | 24 | djdt.hide_panels(); |
73 | 25 | } else { |
74 | 26 | djdt.hide_panels(); |
75 | 27 | |
76 | 28 | $$.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; | |
81 | 35 | 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); | |
91 | 47 | }); |
92 | 48 | } |
93 | 49 | } |
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) { | |
147 | 105 | $$.toggle(e, open_me); |
148 | 106 | }); |
149 | container.querySelectorAll('.djDebugUncollapsed').forEach(function(e) { | |
107 | container | |
108 | .querySelectorAll(".djDebugUncollapsed") | |
109 | .forEach(function (e) { | |
150 | 110 | $$.toggle(e, !open_me); |
151 | 111 | }); |
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) { | |
153 | 116 | 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; | |
157 | 120 | } 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 () { | |
173 | 141 | if (!djdt.handleDragged) { |
174 | 142 | djdt.show_toolbar(); |
175 | 143 | } |
176 | 144 | }); |
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) { | |
197 | 167 | event.preventDefault(); |
198 | 168 | startPageY = event.pageY; |
199 | 169 | 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")) { | |
260 | 228 | djdt.hide_panels(); |
261 | 229 | } 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]; | |
264 | 255 | }, |
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; | |
273 | 279 | }, |
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 | (function () { | |
1 | djdt.applyStyle('background-color'); | |
2 | djdt.applyStyle('left'); | |
3 | djdt.applyStyle('width'); | |
4 | })(); |
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> </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> </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 %} | |
4 | 8 | <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 %}" | |
9 | 11 | {{ 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" %} »</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">«</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> | |
62 | 30 | </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 %} |
0 | 0 | {% load i18n %} |
1 | 1 | <h4>{% trans "Summary" %}</h4> |
2 | 2 | <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> | |
19 | 19 | </table> |
20 | 20 | <h4>{% trans "Commands" %}</h4> |
21 | 21 | <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> | |
36 | 36 | </table> |
37 | 37 | {% 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> | |
68 | 68 | {% endif %} |
2 | 2 | <h4>{% trans "Request headers" %}</h4> |
3 | 3 | |
4 | 4 | <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> | |
19 | 19 | </table> |
20 | 20 | |
21 | 21 | <h4>{% trans "Response headers" %}</h4> |
22 | 22 | |
23 | 23 | <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> | |
38 | 38 | </table> |
39 | 39 | |
40 | 40 | <h4>{% trans "WSGI environ" %}</h4> |
42 | 42 | <p>{% trans "Since the WSGI environ inherits the environment of the server, only a significant subset is shown below." %}</p> |
43 | 43 | |
44 | 44 | <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> | |
59 | 59 | </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> |
0 | 0 | {% load i18n %} |
1 | 1 | {% 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> | |
24 | 24 | {% else %} |
25 | <p>{% trans "No messages logged" %}.</p> | |
25 | <p>{% trans "No messages logged" %}.</p> | |
26 | 26 | {% 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> | |
33 | 33 | </table> |
34 | ||
35 | <script src="{% static 'debug_toolbar/js/toolbar.profiling.js' %}" defer></script> |
1 | 1 | |
2 | 2 | <h4>{% trans "View information" %}</h4> |
3 | 3 | <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> | |
20 | 20 | </table> |
21 | 21 | |
22 | 22 | {% 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 %} | |
44 | 25 | {% else %} |
45 | <h4>{% trans "No cookies" %}</h4> | |
26 | <h4>{% trans "No cookies" %}</h4> | |
46 | 27 | {% endif %} |
47 | 28 | |
48 | 29 | {% 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 %} | |
70 | 32 | {% else %} |
71 | <h4>{% trans "No session data" %}</h4> | |
33 | <h4>{% trans "No session data" %}</h4> | |
72 | 34 | {% endif %} |
73 | 35 | |
74 | 36 | {% 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 %} | |
96 | 39 | {% else %} |
97 | <h4>{% trans "No GET data" %}</h4> | |
40 | <h4>{% trans "No GET data" %}</h4> | |
98 | 41 | {% endif %} |
99 | 42 | |
100 | 43 | {% 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 %} | |
121 | 46 | {% else %} |
122 | <h4>{% trans "No POST data" %}</h4> | |
47 | <h4>{% trans "No POST data" %}</h4> | |
123 | 48 | {% 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> |
0 | 0 | {% load i18n %} |
1 | 1 | <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> | |
16 | 16 | </table> |
0 | 0 | {% load i18n %} |
1 | 1 | <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> | |
18 | 16 | </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"> </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> | |
21 | 19 | |
22 | 20 | {% if queries %} |
23 | <table> | |
24 | <thead> | |
25 | <tr> | |
26 | <th class="djdt-color"> </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:", " }})"> </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 }}"> </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 }}"> </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> | |
112 | 121 | {% 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> | |
114 | 123 | {% endif %} |
115 | ||
116 | <script src="{% static 'debug_toolbar/js/toolbar.sql.js' %}" defer></script> |
0 | {% load i18n %}{% load static %} | |
0 | {% load i18n %} | |
1 | 1 | <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> | |
4 | 4 | </div> |
5 | 5 | <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> | |
34 | 34 | </div> |
35 | ||
36 | <script src="{% static 'debug_toolbar/js/toolbar.sql.js' %}" defer></script> |
0 | {% load i18n %}{% load static %} | |
0 | {% load i18n %} | |
1 | 1 | <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> | |
4 | 4 | </div> |
5 | 5 | <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> | |
41 | 41 | </div> |
42 | ||
43 | <script src="{% static 'debug_toolbar/js/toolbar.sql.js' %}" defer></script> |
0 | {% load i18n %}{% load static %} | |
0 | {% load i18n %} | |
1 | 1 | <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> | |
4 | 4 | </div> |
5 | 5 | <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> | |
38 | 38 | </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 %} |
1 | 1 | |
2 | 2 | <h4>{% blocktrans count staticfiles_dirs|length as dirs_count %}Static file path{% plural %}Static file paths{% endblocktrans %}</h4> |
3 | 3 | {% if staticfiles_dirs %} |
4 | <ol> | |
4 | <ol> | |
5 | 5 | {% 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> | |
7 | 7 | {% endfor %} |
8 | </ol> | |
8 | </ol> | |
9 | 9 | {% else %} |
10 | <p>{% trans "None" %}</p> | |
10 | <p>{% trans "None" %}</p> | |
11 | 11 | {% endif %} |
12 | 12 | |
13 | 13 | <h4>{% blocktrans count staticfiles_apps|length as apps_count %}Static file app{% plural %}Static file apps{% endblocktrans %}</h4> |
14 | 14 | {% if staticfiles_apps %} |
15 | <ol> | |
15 | <ol> | |
16 | 16 | {% for static_app in staticfiles_apps %} |
17 | <li>{{ static_app }}</li> | |
17 | <li>{{ static_app }}</li> | |
18 | 18 | {% endfor %} |
19 | </ol> | |
19 | </ol> | |
20 | 20 | {% else %} |
21 | <p>{% trans "None" %}</p> | |
21 | <p>{% trans "None" %}</p> | |
22 | 22 | {% endif %} |
23 | 23 | |
24 | 24 | <h4>{% blocktrans count staticfiles|length as staticfiles_count %}Static file{% plural %}Static files{% endblocktrans %}</h4> |
25 | 25 | {% 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> | |
32 | 32 | {% else %} |
33 | <p>{% trans "None" %}</p> | |
33 | <p>{% trans "None" %}</p> | |
34 | 34 | {% endif %} |
35 | 35 | |
36 | 36 | |
37 | 37 | {% 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> | |
40 | 40 | <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> | |
45 | 45 | </thead> |
46 | 46 | <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 %} | |
53 | 53 | </tbody> |
54 | </table> | |
54 | </table> | |
55 | 55 | {% endfor %} |
0 | 0 | {% load i18n %} |
1 | 1 | <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> | |
4 | 4 | </div> |
5 | 5 | <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> | |
13 | 13 | </div> |
0 | {% load i18n %}{% load static %} | |
0 | {% load i18n %} | |
1 | 1 | <h4>{% blocktrans count template_dirs|length as template_count %}Template path{% plural %}Template paths{% endblocktrans %}</h4> |
2 | 2 | {% 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> | |
8 | 8 | {% else %} |
9 | <p>{% trans "None" %}</p> | |
9 | <p>{% trans "None" %}</p> | |
10 | 10 | {% endif %} |
11 | 11 | |
12 | 12 | <h4>{% blocktrans count templates|length as template_count %}Template{% plural %}Templates{% endblocktrans %}</h4> |
13 | 13 | {% 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 }}&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 }}&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> | |
28 | 28 | {% else %} |
29 | <p>{% trans "None" %}</p> | |
29 | <p>{% trans "None" %}</p> | |
30 | 30 | {% endif %} |
31 | 31 | |
32 | 32 | <h4>{% blocktrans count context_processors|length as context_processors_count %}Context processor{% plural %}Context processors{% endblocktrans %}</h4> |
33 | 33 | {% 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> | |
45 | 45 | {% else %} |
46 | <p>{% trans "None" %}</p> | |
46 | <p>{% trans "None" %}</p> | |
47 | 47 | {% endif %} |
0 | {% load i18n %}{% load static %} | |
0 | {% load i18n %} | |
1 | 1 | <h4>{% trans "Resource usage" %}</h4> |
2 | 2 | <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> | |
21 | 21 | </table> |
22 | 22 | |
23 | 23 | <!-- This hidden div is populated and displayed by code in toolbar.timer.js --> |
24 | 24 | <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> | |
42 | 42 | </div> |
43 | <script src="{% static 'debug_toolbar/js/toolbar.timer.js' %}" defer></script> |
0 | 0 | {% load i18n %} |
1 | 1 | <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> | |
23 | 23 | </table> |
0 | 0 | {% load i18n static %} |
1 | 1 | <!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> | |
13 | 14 | </html> |
5 | 5 | from collections import OrderedDict |
6 | 6 | |
7 | 7 | from django.apps import apps |
8 | from django.conf.urls import url | |
9 | 8 | from django.core.exceptions import ImproperlyConfigured |
10 | 9 | from django.template import TemplateSyntaxError |
11 | 10 | from django.template.loader import render_to_string |
11 | from django.urls import path, resolve | |
12 | from django.urls.exceptions import Resolver404 | |
12 | 13 | from django.utils.module_loading import import_string |
13 | 14 | |
14 | 15 | from debug_toolbar import settings as dt_settings |
76 | 77 | else: |
77 | 78 | raise |
78 | 79 | |
79 | # Handle storing toolbars in memory and fetching them later on | |
80 | ||
81 | _store = OrderedDict() | |
82 | ||
83 | 80 | def should_render_panels(self): |
84 | 81 | render_panels = self.config["RENDER_PANELS"] |
85 | 82 | if render_panels is None: |
86 | 83 | render_panels = self.request.META["wsgi.multiprocess"] |
87 | 84 | return render_panels |
88 | 85 | |
86 | # Handle storing toolbars in memory and fetching them later on | |
87 | ||
88 | _store = OrderedDict() | |
89 | ||
89 | 90 | def store(self): |
91 | # Store already exists. | |
92 | if self.store_id: | |
93 | return | |
90 | 94 | 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) | |
100 | 98 | |
101 | 99 | @classmethod |
102 | 100 | def fetch(cls, store_id): |
127 | 125 | # Load URLs in a temporary variable for thread safety. |
128 | 126 | # Global URLs |
129 | 127 | urlpatterns = [ |
130 | url(r"^render_panel/$", views.render_panel, name="render_panel") | |
128 | path("render_panel/", views.render_panel, name="render_panel") | |
131 | 129 | ] |
132 | 130 | # Per-panel URLs |
133 | 131 | for panel_class in cls.get_panel_classes(): |
135 | 133 | cls._urlpatterns = urlpatterns |
136 | 134 | return cls._urlpatterns |
137 | 135 | |
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 | ||
138 | 149 | |
139 | 150 | app_name = "djdt" |
140 | 151 | urlpatterns = DebugToolbar.get_urls() |
7 | 7 | import django |
8 | 8 | from django.core.exceptions import ImproperlyConfigured |
9 | 9 | from django.template import Node |
10 | from django.utils.html import escape | |
10 | from django.template.loader import render_to_string | |
11 | 11 | from django.utils.safestring import mark_safe |
12 | 12 | |
13 | 13 | from debug_toolbar import settings as dt_settings |
58 | 58 | if omit_path(os.path.realpath(path)): |
59 | 59 | continue |
60 | 60 | 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)) | |
62 | 67 | return trace |
63 | 68 | |
64 | 69 | |
65 | 70 | def render_stacktrace(trace): |
66 | 71 | stacktrace = [] |
67 | 72 | 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:])) | |
69 | 74 | params_dict = {str(idx): v for idx, v in enumerate(params)} |
70 | 75 | 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) | |
78 | 77 | except KeyError: |
79 | 78 | # This frame doesn't have the expected format, so skip it and move |
80 | 79 | # on to the next one |
81 | 80 | 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 | ) | |
83 | 91 | |
84 | 92 | |
85 | 93 | def get_template_info(): |
136 | 144 | def get_name_from_obj(obj): |
137 | 145 | if hasattr(obj, "__name__"): |
138 | 146 | name = obj.__name__ |
139 | elif hasattr(obj, "__class__") and hasattr(obj.__class__, "__name__"): | |
147 | else: | |
140 | 148 | name = obj.__class__.__name__ |
141 | else: | |
142 | name = "<unknown>" | |
143 | 149 | |
144 | 150 | if hasattr(obj, "__module__"): |
145 | 151 | module = obj.__module__ |
203 | 209 | return (filename, lineno, frame.f_code.co_name, lines, index) |
204 | 210 | |
205 | 211 | |
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 | ||
206 | 222 | def get_stack(context=1): |
207 | 223 | """ |
208 | 224 | 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 | |
1 | 1 | from django.utils.html import escape |
2 | 2 | from django.utils.translation import gettext as _ |
3 | 3 | |
15 | 15 | "Please reload the page and retry." |
16 | 16 | ) |
17 | 17 | content = "<p>%s</p>" % escape(content) |
18 | scripts = [] | |
18 | 19 | else: |
19 | 20 | panel = toolbar.get_panel_by_id(request.GET["panel_id"]) |
20 | 21 | 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 | |
1 | 1 | # |
2 | 2 | |
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 = . | |
7 | 8 | BUILDDIR = _build |
8 | 9 | |
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) | |
13 | 13 | |
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 | |
20 | 15 | |
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) |
0 | 0 | Change log |
1 | 1 | ========== |
2 | 2 | |
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 | ||
3 | 151 | 2.0 (2019-06-20) |
4 | 152 | ---------------- |
5 | 153 | |
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. | |
8 | 158 | * 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. | |
10 | 161 | * Improved the installation documentation. |
11 | 162 | * Fixed a possible crash in the template panel. |
12 | 163 | * Added support for psycopg2 ``Composed`` objects. |
18 | 169 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
19 | 170 | * Removed support for Python 2. |
20 | 171 | * 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 | |
24 | 176 | return a response. Usually this is the response returned by |
25 | 177 | ``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. | |
29 | 182 | |
30 | 183 | 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. | |
32 | 185 | |
33 | 186 | The following deprecated settings have been removed: |
34 | 187 | |
48 | 201 | altogether. |
49 | 202 | * Reformatted the code using `black <https://github.com/ambv/black>`__. |
50 | 203 | * 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 | |
52 | 205 | configurations. |
53 | 206 | * 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. | |
55 | 208 | * Added MariaDB and Python 3.7 builds to the CI. |
56 | 209 | |
57 | 210 | 1.10.1 (2018-09-11) |
58 | 211 | ------------------- |
59 | 212 | |
60 | 213 | * Fixed a problem where the duplicate query detection breaks for |
61 | non-hashable query parameters. | |
214 | unhashable query parameters. | |
62 | 215 | * Added support for structured types when recording SQL. |
63 | 216 | * Made Travis CI also run one test no PostgreSQL. |
64 | 217 | * Added fallbacks for inline images in CSS. |
75 | 228 | changes were required. |
76 | 229 | * Removed the jQuery dependency. This means that django-debug-toolbar |
77 | 230 | 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. | |
79 | 233 | * Added support for the server timing header. |
80 | 234 | * Added a differentiation between similar and duplicate queries. Similar |
81 | 235 | queries are what duplicate queries used to be (same SQL, different |
82 | 236 | parameters). |
83 | 237 | * Stopped hiding frames from Django's contrib apps in stacktraces by |
84 | 238 | default. |
85 | * Lots of small cleanups and bugfixes. | |
239 | * Lots of small cleanups and bug fixes. | |
86 | 240 | |
87 | 241 | 1.9.1 (2017-11-15) |
88 | 242 | ------------------ |
95 | 249 | This version is compatible with Django 2.0 and requires Django 1.8 or |
96 | 250 | later. |
97 | 251 | |
98 | Bugfixes | |
99 | ~~~~~~~~ | |
252 | Bug fixes | |
253 | ~~~~~~~~~ | |
100 | 254 | |
101 | 255 | * The profiling panel now escapes reported data resulting in valid HTML. |
102 | * Many minor cleanups and bugfixes. | |
256 | * Many minor cleanups and bug fixes. | |
103 | 257 | |
104 | 258 | 1.8 (2017-05-05) |
105 | 259 | ---------------- |
128 | 282 | skipped by default to avoid panel sizes going into hundreds of |
129 | 283 | megabytes of HTML. |
130 | 284 | |
131 | Bugfixes | |
132 | ~~~~~~~~ | |
285 | Bug fixes | |
286 | ~~~~~~~~~ | |
133 | 287 | |
134 | 288 | * All views are now decorated with |
135 | 289 | ``debug_toolbar.decorators.require_show_toolbar`` preventing unauthorized |
142 | 296 | 1.7 (2017-03-05) |
143 | 297 | ---------------- |
144 | 298 | |
145 | Bugfixes | |
146 | ~~~~~~~~ | |
299 | Bug fixes | |
300 | ~~~~~~~~~ | |
147 | 301 | |
148 | 302 | * Recursive template extension is now understood. |
149 | 303 | * Deprecation warnings were fixed. |
158 | 312 | 1.6 (2016-10-05) |
159 | 313 | ---------------- |
160 | 314 | |
161 | The debug toolbar was adopted by jazzband. | |
315 | The debug toolbar was adopted by Jazzband. | |
162 | 316 | |
163 | 317 | Removed features |
164 | 318 | ~~~~~~~~~~~~~~~~ |
168 | 322 | ``DEBUG_TOOLBAR_PATCH_SETTINGS`` setting has also been removed as it is now |
169 | 323 | unused. See the :doc:`installation documentation <installation>` for details. |
170 | 324 | |
171 | Bugfixes | |
172 | ~~~~~~~~ | |
325 | Bug fixes | |
326 | ~~~~~~~~~ | |
173 | 327 | |
174 | 328 | * The ``DebugToolbarMiddleware`` now also supports Django 1.10's ``MIDDLEWARE`` |
175 | 329 | setting. |
181 | 335 | |
182 | 336 | Support for Python 3.2 is dropped. |
183 | 337 | |
184 | Bugfixes | |
185 | ~~~~~~~~ | |
338 | Bug fixes | |
339 | ~~~~~~~~~ | |
186 | 340 | |
187 | 341 | * Restore compatibility with sqlparse ā„ 0.2.0. |
188 | 342 | * Add compatibility with Bootstrap 4, Pure CSS, MDL, etc. |
202 | 356 | to only record stats when the toolbar is going to be inserted into the |
203 | 357 | response. |
204 | 358 | |
205 | Bugfixes | |
206 | ~~~~~~~~ | |
359 | Bug fixes | |
360 | ~~~~~~~~~ | |
207 | 361 | |
208 | 362 | * Response time for requests of projects with numerous media files has |
209 | 363 | been improved. |
220 | 374 | * The ``SHOW_TOOLBAR_CALLBACK`` accepts a callable. |
221 | 375 | * The toolbar now provides a :ref:`javascript-api`. |
222 | 376 | |
223 | Bugfixes | |
224 | ~~~~~~~~ | |
377 | Bug fixes | |
378 | ~~~~~~~~~ | |
225 | 379 | |
226 | 380 | * The toolbar handle cannot leave the visible area anymore when the toolbar is |
227 | 381 | collapsed. |
239 | 393 | |
240 | 394 | * The ``JQUERY_URL`` setting defines where the toolbar loads jQuery from. |
241 | 395 | |
242 | Bugfixes | |
243 | ~~~~~~~~ | |
396 | Bug fixes | |
397 | ~~~~~~~~~ | |
244 | 398 | |
245 | 399 | * 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. | |
247 | 401 | |
248 | 402 | This private copy is available in ``djdt.jQuery``. Third-party panels are |
249 | 403 | encouraged to use it because it should be as stable as the toolbar itself. |
259 | 413 | * The SQL panel colors queries depending on the stack level. |
260 | 414 | * The Profiler panel allows configuring the maximum depth. |
261 | 415 | |
262 | Bugfixes | |
263 | ~~~~~~~~ | |
416 | Bug fixes | |
417 | ~~~~~~~~~ | |
264 | 418 | |
265 | 419 | * Support languages where lowercase and uppercase strings may have different |
266 | 420 | 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. |
21 | 21 | FROM "page_page" |
22 | 22 | WHERE "page_page"."id" = 1 |
23 | 23 | |
24 | >>> print p.template.name | |
24 | >>> print(p.template.name) | |
25 | 25 | SELECT "page_template"."id", |
26 | 26 | "page_template"."name", |
27 | 27 | "page_template"."description" |
42 | 42 | INNER JOIN "page_template" ON ("page_page"."template_id" = "page_template"."id") |
43 | 43 | WHERE "page_page"."id" = 1 |
44 | 44 | |
45 | >>> print p.template.name | |
45 | >>> print(p.template.name) | |
46 | 46 | Home |
0 | # -*- coding: utf-8 -*- | |
0 | # Configuration file for the Sphinx documentation builder. | |
1 | 1 | # |
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 | |
13 | 5 | |
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 -------------------------------------------------------------- | |
20 | 7 | |
21 | 8 | # If extensions (or modules to document with autodoc) are in another directory, |
22 | 9 | # add these directories to sys.path here. If the directory is relative to the |
23 | 10 | # documentation root, use os.path.abspath to make it absolute, like shown here. |
24 | #sys.path.insert(0, os.path.abspath('.')) | |
25 | 11 | |
26 | # -- General configuration ------------------------------------------------ | |
12 | import datetime | |
13 | import os | |
14 | import sys | |
27 | 15 | |
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 --------------------------------------------------- | |
30 | 31 | |
31 | 32 | # Add any Sphinx extension module names here, as strings. They can be |
32 | 33 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom |
33 | 34 | # ones. |
34 | 35 | extensions = [ |
35 | 'sphinx.ext.autodoc', | |
36 | 'sphinx.ext.intersphinx', | |
36 | "sphinx.ext.autodoc", | |
37 | "sphinx.ext.intersphinx", | |
37 | 38 | ] |
38 | 39 | |
39 | 40 | # 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"] | |
74 | 42 | |
75 | 43 | # List of patterns, relative to source directory, that match files and |
76 | 44 | # 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"] | |
102 | 47 | |
103 | 48 | |
104 | # -- Options for HTML output ---------------------------------------------- | |
49 | # -- Options for HTML output ------------------------------------------------- | |
105 | 50 | |
106 | 51 | # The theme to use for HTML and HTML Help pages. See the documentation for |
107 | 52 | # 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" | |
133 | 54 | |
134 | 55 | # Add any paths that contain custom static files (such as style sheets) here, |
135 | 56 | # relative to this directory. They are copied after the builtin static files, |
136 | 57 | # so a file named "default.css" will overwrite the builtin "default.css". |
137 | html_static_path = ['_static'] | |
58 | # html_static_path = ['_static'] | |
138 | 59 | |
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/", | |
200 | 63 | } |
201 | 64 | |
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 ----------------------------------------------- | |
277 | 66 | |
278 | 67 | |
279 | 68 | def setup(app): |
280 | """ Configure documentation via Sphinx extension | |
281 | """ | |
69 | """Configure documentation via Sphinx extension""" | |
282 | 70 | # Add the :setting: role for intersphinx linking to Django's docs |
283 | 71 | app.add_crossref_type( |
284 | 72 | directivename="setting", |
7 | 7 | |
8 | 8 | The debug toolbar ships with a default configuration that is considered |
9 | 9 | 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 | |
11 | 11 | it'll prevent you from taking advantage of better defaults that may be |
12 | 12 | introduced in future releases. |
13 | 13 | |
19 | 19 | default value is:: |
20 | 20 | |
21 | 21 | DEBUG_TOOLBAR_PANELS = [ |
22 | 'debug_toolbar.panels.history.HistoryPanel', | |
22 | 23 | 'debug_toolbar.panels.versions.VersionsPanel', |
23 | 24 | 'debug_toolbar.panels.timer.TimerPanel', |
24 | 25 | 'debug_toolbar.panels.settings.SettingsPanel', |
31 | 32 | 'debug_toolbar.panels.signals.SignalsPanel', |
32 | 33 | 'debug_toolbar.panels.logging.LoggingPanel', |
33 | 34 | 'debug_toolbar.panels.redirects.RedirectsPanel', |
35 | 'debug_toolbar.panels.profiling.ProfilingPanel', | |
34 | 36 | ] |
35 | 37 | |
36 | 38 | This setting allows you to: |
79 | 81 | |
80 | 82 | * ``RESULTS_CACHE_SIZE`` |
81 | 83 | |
82 | Default: ``10`` | |
84 | Default: ``25`` | |
83 | 85 | |
84 | 86 | The toolbar keeps up to this many results in memory. |
85 | 87 | |
137 | 139 | calls. Enabling stacktraces can increase the CPU time used when executing |
138 | 140 | queries. |
139 | 141 | |
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 | ||
140 | 159 | * ``HIDE_IN_STACKTRACES`` |
141 | 160 | |
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 | ||
144 | 176 | |
145 | 177 | Panels: cache, SQL |
146 | 178 | |
147 | 179 | Useful for eliminating server-related entries which can result |
148 | 180 | in enormous DOM structures and toolbar rendering delays. |
149 | 181 | |
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 | ||
150 | 216 | * ``PROFILER_MAX_DEPTH`` |
151 | 217 | |
152 | 218 | Default: ``10`` |
164 | 230 | |
165 | 231 | If set to ``True`` then a template's context will be included with it in the |
166 | 232 | 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 | |
168 | 234 | that you don't want to be evaluated. |
169 | 235 | |
170 | 236 | * ``SKIP_TEMPLATE_PREFIXES`` |
31 | 31 | Once you've obtained a checkout, you should create a virtualenv_ and install |
32 | 32 | the libraries required for working on the Debug Toolbar:: |
33 | 33 | |
34 | $ pip install -r requirements_dev.txt | |
34 | $ python -m pip install -r requirements_dev.txt | |
35 | 35 | |
36 | 36 | .. _virtualenv: https://virtualenv.pypa.io/ |
37 | 37 | |
52 | 52 | |
53 | 53 | Once you've set up a development environment as explained above, you can run |
54 | 54 | the test suite for the versions of Django and Python installed in that |
55 | environment:: | |
55 | environment using the SQLite database:: | |
56 | 56 | |
57 | 57 | $ make test |
58 | 58 | |
78 | 78 | $ DJANGO_SELENIUM_TESTS=true make coverage |
79 | 79 | $ DJANGO_SELENIUM_TESTS=true tox |
80 | 80 | |
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 | ||
83 | 98 | |
84 | 99 | Style |
85 | 100 | ----- |
86 | 101 | |
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:: | |
90 | 105 | |
91 | 106 | $ make style |
92 | 107 | |
131 | 146 | |
132 | 147 | Commit. |
133 | 148 | |
149 | #. Update the screenshot in ``README.rst``. | |
150 | ||
151 | .. code-block:: console | |
152 | ||
153 | $ make example/django-debug-toolbar.png | |
154 | ||
155 | Commit. | |
156 | ||
134 | 157 | #. 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. | |
137 | 160 | |
138 | 161 | #. Tag the new version. |
139 | 162 |
8 | 8 | |
9 | 9 | The recommended way to install the Debug Toolbar is via pip_:: |
10 | 10 | |
11 | $ pip install django-debug-toolbar | |
11 | $ python -m pip install django-debug-toolbar | |
12 | 12 | |
13 | 13 | If you aren't familiar with pip, you may also obtain a copy of the |
14 | 14 | ``debug_toolbar`` directory and add it to your Python path. |
18 | 18 | To test an upcoming release, you can install the in-development version |
19 | 19 | instead with the following command:: |
20 | 20 | |
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 | |
22 | 22 | |
23 | 23 | Prerequisites |
24 | 24 | ------------- |
42 | 42 | Setting up URLconf |
43 | 43 | ------------------ |
44 | 44 | |
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:: | |
46 | 46 | |
47 | import debug_toolbar | |
47 | 48 | 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 | |
50 | 50 | |
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 | ] | |
60 | 55 | |
61 | 56 | This example uses the ``__debug__`` prefix, but you can use any prefix that |
62 | 57 | doesn't clash with your application's URLs. Note the lack of quotes around |
87 | 82 | ------------------------ |
88 | 83 | |
89 | 84 | 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`; | |
92 | 87 | you'll need to create this setting if it doesn't already exist in your |
93 | 88 | settings module:: |
94 | 89 | |
101 | 96 | You can change the logic of determining whether or not the Debug Toolbar |
102 | 97 | should be shown with the :ref:`SHOW_TOOLBAR_CALLBACK <SHOW_TOOLBAR_CALLBACK>` |
103 | 98 | 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 |
0 | 0 | @ECHO OFF |
1 | ||
2 | pushd %~dp0 | |
1 | 3 | |
2 | 4 | REM Command file for Sphinx documentation |
3 | 5 | |
4 | 6 | if "%SPHINXBUILD%" == "" ( |
5 | 7 | set SPHINXBUILD=sphinx-build |
6 | 8 | ) |
9 | set SOURCEDIR=. | |
7 | 10 | 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 | ) | |
14 | 11 | |
15 | 12 | if "%1" == "" goto help |
16 | 13 | |
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 | |
50 | 15 | if errorlevel 9009 ( |
51 | 16 | echo. |
52 | 17 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx |
59 | 24 | exit /b 1 |
60 | 25 | ) |
61 | 26 | |
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 | |
69 | 29 | |
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% | |
240 | 32 | |
241 | 33 | :end |
34 | popd |
8 | 8 | |
9 | 9 | The following panels are enabled by default. |
10 | 10 | |
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 | ||
11 | 19 | Version |
12 | 20 | ~~~~~~~ |
13 | 21 | |
14 | Path: ``debug_toolbar.panels.versions.VersionsPanel`` | |
22 | .. class:: debug_toolbar.panels.versions.VersionsPanel | |
15 | 23 | |
16 | 24 | Shows versions of Python, Django, and installed apps if possible. |
17 | 25 | |
18 | 26 | Timer |
19 | 27 | ~~~~~ |
20 | 28 | |
21 | Path: ``debug_toolbar.panels.timer.TimerPanel`` | |
29 | .. class:: debug_toolbar.panels.timer.TimerPanel | |
22 | 30 | |
23 | 31 | Request timer. |
24 | 32 | |
25 | 33 | Settings |
26 | 34 | ~~~~~~~~ |
27 | 35 | |
28 | Path: ``debug_toolbar.panels.settings.SettingsPanel`` | |
36 | .. class:: debug_toolbar.panels.settings.SettingsPanel | |
29 | 37 | |
30 | 38 | A list of settings in settings.py. |
31 | 39 | |
32 | 40 | Headers |
33 | 41 | ~~~~~~~ |
34 | 42 | |
35 | Path: ``debug_toolbar.panels.headers.HeadersPanel`` | |
43 | .. class:: debug_toolbar.panels.headers.HeadersPanel | |
36 | 44 | |
37 | 45 | This panels shows the HTTP request and response headers, as well as a |
38 | 46 | selection of values from the WSGI environment. |
44 | 52 | Request |
45 | 53 | ~~~~~~~ |
46 | 54 | |
47 | Path: ``debug_toolbar.panels.request.RequestPanel`` | |
55 | .. class:: debug_toolbar.panels.request.RequestPanel | |
48 | 56 | |
49 | 57 | GET/POST/cookie/session variable display. |
50 | 58 | |
51 | 59 | SQL |
52 | 60 | ~~~ |
53 | 61 | |
54 | Path: ``debug_toolbar.panels.sql.SQLPanel`` | |
62 | .. class:: debug_toolbar.panels.sql.SQLPanel | |
55 | 63 | |
56 | 64 | SQL queries including time to execute and links to EXPLAIN each query. |
57 | 65 | |
58 | 66 | Template |
59 | 67 | ~~~~~~~~ |
60 | 68 | |
61 | Path: ``debug_toolbar.panels.templates.TemplatesPanel`` | |
69 | .. class:: debug_toolbar.panels.templates.TemplatesPanel | |
62 | 70 | |
63 | 71 | Templates and context used, and their template paths. |
64 | 72 | |
65 | 73 | Static files |
66 | 74 | ~~~~~~~~~~~~ |
67 | 75 | |
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). | |
71 | 79 | |
72 | 80 | Cache |
73 | 81 | ~~~~~ |
74 | 82 | |
75 | Path: ``debug_toolbar.panels.cache.CachePanel`` | |
83 | .. class:: debug_toolbar.panels.cache.CachePanel | |
76 | 84 | |
77 | 85 | Cache queries. Is incompatible with Django's per-site caching. |
78 | 86 | |
79 | 87 | Signal |
80 | 88 | ~~~~~~ |
81 | 89 | |
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. | |
85 | 93 | |
86 | 94 | Logging |
87 | 95 | ~~~~~~~ |
88 | 96 | |
89 | Path: ``debug_toolbar.panels.logging.LoggingPanel`` | |
97 | .. class:: debug_toolbar.panels.logging.LoggingPanel | |
90 | 98 | |
91 | 99 | Logging output via Python's built-in :mod:`logging` module. |
92 | 100 | |
93 | 101 | Redirects |
94 | 102 | ~~~~~~~~~ |
95 | 103 | |
96 | Path: ``debug_toolbar.panels.redirects.RedirectsPanel`` | |
104 | .. class:: debug_toolbar.panels.redirects.RedirectsPanel | |
97 | 105 | |
98 | 106 | When this panel is enabled, the debug toolbar will show an intermediate page |
99 | 107 | upon redirect so you can view any debug information prior to redirecting. This |
109 | 117 | Profiling |
110 | 118 | ~~~~~~~~~ |
111 | 119 | |
112 | Path: ``debug_toolbar.panels.profiling.ProfilingPanel`` | |
120 | .. class:: debug_toolbar.panels.profiling.ProfilingPanel | |
113 | 121 | |
114 | 122 | Profiling information for the processing of the request. |
115 | 123 | |
168 | 176 | Inspector panel also logs to the console by default, but may be instructed not |
169 | 177 | to. |
170 | 178 | |
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 | ||
171 | 191 | Line Profiler |
172 | 192 | ~~~~~~~~~~~~~ |
173 | 193 | |
174 | URL: https://github.com/dmclain/django-debug-toolbar-line-profiler | |
194 | URL: https://github.com/mikekeda/django-debug-toolbar-line-profiler | |
175 | 195 | |
176 | 196 | Path: ``debug_toolbar_line_profiler.panel.ProfilingPanel`` |
177 | 197 | |
215 | 235 | |
216 | 236 | Path: ``neo4j_panel.Neo4jPanel`` |
217 | 237 | |
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. | |
219 | 239 | |
220 | 240 | Pympler |
221 | 241 | ~~~~~~~ |
233 | 253 | |
234 | 254 | Path: ``ddt_request_history.panels.request_history.RequestHistoryPanel`` |
235 | 255 | |
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. | |
237 | 266 | |
238 | 267 | Sites |
239 | 268 | ~~~~~ |
304 | 333 | be decorated with ``debug_toolbar.decorators.require_show_toolbar`` to prevent |
305 | 334 | unauthorized access. There is no public CSS API at this time. |
306 | 335 | |
307 | .. autoclass:: debug_toolbar.panels.Panel(*args, **kwargs) | |
336 | .. autoclass:: debug_toolbar.panels.Panel | |
308 | 337 | |
309 | 338 | .. autoattribute:: debug_toolbar.panels.Panel.nav_title |
310 | 339 | |
318 | 347 | |
319 | 348 | .. autoattribute:: debug_toolbar.panels.Panel.content |
320 | 349 | |
350 | .. autoattribute:: debug_toolbar.panels.Panel.scripts | |
351 | ||
321 | 352 | .. automethod:: debug_toolbar.panels.Panel.get_urls |
322 | 353 | |
323 | 354 | .. automethod:: debug_toolbar.panels.Panel.enable_instrumentation |
331 | 362 | .. automethod:: debug_toolbar.panels.Panel.process_request |
332 | 363 | |
333 | 364 | .. automethod:: debug_toolbar.panels.Panel.generate_stats |
365 | ||
366 | .. automethod:: debug_toolbar.panels.Panel.run_checks | |
334 | 367 | |
335 | 368 | .. _javascript-api: |
336 | 369 | |
344 | 377 | |
345 | 378 | Closes the topmost level (window/panel/toolbar) |
346 | 379 | |
347 | .. js:function:: djdt.cookie.get | |
380 | .. js:function:: djdt.cookie.get(key) | |
348 | 381 | |
349 | 382 | This is a helper function to fetch values stored in the cookies. |
350 | 383 | |
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) | |
354 | 387 | |
355 | 388 | This is a helper function to set a value stored in the cookies. |
356 | 389 | |
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``. | |
363 | 396 | |
364 | 397 | .. js:function:: djdt.hide_toolbar |
365 | 398 |
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 |
6 | 6 | The Debug Toolbar will only display when ``DEBUG = True`` in your project's |
7 | 7 | settings (see :ref:`Show Toolbar Callback <SHOW_TOOLBAR_CALLBACK>`) and your |
8 | 8 | 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 | |
10 | 10 | response is either ``text/html`` or ``application/xhtml+xml`` and contains a |
11 | 11 | closing ``</body>`` tag. |
12 | 12 | |
13 | 13 | 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. | |
17 | 17 | |
18 | 18 | Browsers have become more aggressive with caching static assets, such as |
19 | 19 | JavaScript and CSS files. Check your browser's development console, and if |
62 | 62 | you can remove them permanently by customizing the ``DEBUG_TOOLBAR_PANELS`` |
63 | 63 | setting. |
64 | 64 | |
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 | |
66 | 66 | allows you to use the toolbar on a page even if you have browsed to a few |
67 | 67 | other pages since you first loaded that page. You can reduce memory |
68 | 68 | consumption by setting the ``RESULTS_CACHE_SIZE`` configuration option to a |
12 | 12 | How to |
13 | 13 | ------ |
14 | 14 | |
15 | The test project requires a working installation of Django:: | |
15 | The example project requires a working installation of Django:: | |
16 | 16 | |
17 | $ pip install Django | |
17 | $ python -m pip install Django | |
18 | 18 | |
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/``:: | |
21 | 21 | |
22 | Before running the example for the first time, you must create a database:: | |
22 | $ make example | |
23 | 23 | |
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". | |
25 | 27 | |
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. | |
27 | 30 | |
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 |
Binary diff not shown
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() |
70 | 70 | } |
71 | 71 | } |
72 | 72 | |
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. | |
78 | 76 | DATABASES = { |
79 | 77 | "default": { |
80 | "ENGINE": "django.db.backends.postgresql_psycopg2", | |
78 | "ENGINE": "django.db.backends.postgresql", | |
81 | 79 | "NAME": "debug_toolbar", |
82 | 80 | "USER": "debug_toolbar", |
81 | "PASSWORD": "debug_toolbar", | |
83 | 82 | } |
84 | 83 | } |
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. | |
89 | 86 | DATABASES = { |
90 | 87 | "default": { |
91 | 88 | "ENGINE": "django.db.backends.mysql", |
92 | 89 | "NAME": "debug_toolbar", |
93 | 90 | "USER": "debug_toolbar", |
91 | "PASSWORD": "debug_toolbar", | |
94 | 92 | } |
95 | 93 | } |
96 | 94 |
0 | 0 | {% load cache %} |
1 | 1 | <!DOCTYPE html> |
2 | 2 | <html> |
3 | <head> | |
3 | <head> | |
4 | 4 | <meta http-equiv="content-type" content="text/html; charset=utf-8"> |
5 | 5 | <title>Index of Tests</title> |
6 | </head> | |
7 | <body> | |
6 | </head> | |
7 | <body> | |
8 | 8 | <h1>Index of Tests</h1> |
9 | 9 | {% cache 10 index_cache %} |
10 | <ul> | |
10 | <ul> | |
11 | 11 | <li><a href="/jquery/">jQuery 3.3.1</a></li> |
12 | 12 | <li><a href="/mootools/">MooTools 1.6.0</a></li> |
13 | 13 | <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> | |
16 | 16 | {% endcache %} |
17 | </body> | |
17 | </body> | |
18 | 18 | </html> |
0 | 0 | <!DOCTYPE html> |
1 | 1 | <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> | |
20 | 17 | </html> |
0 | 0 | <!DOCTYPE html> |
1 | 1 | <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> | |
20 | 17 | </html> |
0 | 0 | <!DOCTYPE html> |
1 | 1 | <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> | |
20 | 17 | </html> |
0 | from django.conf import settings | |
1 | from django.conf.urls import include, url | |
2 | 0 | from django.contrib import admin |
1 | from django.urls import include, path | |
3 | 2 | from django.views.generic import TemplateView |
4 | 3 | |
4 | import debug_toolbar | |
5 | ||
5 | 6 | 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)), | |
11 | 13 | ] |
12 | ||
13 | if settings.DEBUG: | |
14 | import debug_toolbar | |
15 | ||
16 | urlpatterns += [url(r"^__debug__/", include(debug_toolbar.urls))] |
11 | 11 | isort |
12 | 12 | selenium |
13 | 13 | tox |
14 | black | |
14 | 15 | |
15 | 16 | # Documentation |
16 | 17 | |
17 | 18 | Sphinx |
19 | sphinxcontrib-spelling | |
18 | 20 | |
19 | 21 | # Other tools |
20 | 22 |
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.* | |
2 | 44 | |
3 | 45 | [flake8] |
4 | exclude = .tox,venv,conf.py | |
5 | ignore = E203,W503,W601 | |
6 | max-line-length = 88 | |
46 | extend-ignore = E203, E501 | |
7 | 47 | |
8 | 48 | [isort] |
9 | 49 | 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 |
0 | 0 | #!/usr/bin/env python3 |
1 | 1 | |
2 | from io import open | |
2 | from setuptools import setup | |
3 | 3 | |
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() |
42 | 42 | |
43 | 43 | msg = self._formatMessage(msg, "\n".join(default_msg)) |
44 | 44 | 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() |
0 | 0 | import io |
1 | 1 | import sys |
2 | 2 | |
3 | import django | |
3 | 4 | from django.contrib.auth.models import User |
4 | 5 | from django.core import management |
5 | from django.db.backends import utils as db_backends_utils | |
6 | from django.db import connection | |
6 | 7 | from django.test import TestCase |
7 | 8 | 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 | |
8 | 14 | |
9 | 15 | |
10 | 16 | @override_settings(DEBUG=True) |
11 | 17 | class DebugSQLShellTestCase(TestCase): |
12 | 18 | def setUp(self): |
13 | self.original_cursor_wrapper = db_backends_utils.CursorDebugWrapper | |
19 | self.original_wrapper = base_module.CursorDebugWrapper | |
14 | 20 | # Since debugsqlshell monkey-patches django.db.backends.utils, we can |
15 | 21 | # test it simply by loading it, without executing it. But we have to |
16 | 22 | # undo the monkey-patch on exit. |
19 | 25 | management.load_command_class(app_name, command_name) |
20 | 26 | |
21 | 27 | def tearDown(self): |
22 | db_backends_utils.CursorDebugWrapper = self.original_cursor_wrapper | |
28 | base_module.CursorDebugWrapper = self.original_wrapper | |
23 | 29 | |
24 | 30 | def test_command(self): |
25 | 31 | original_stdout, sys.stdout = sys.stdout, io.StringIO() |
7 | 7 | |
8 | 8 | class Binary(models.Model): |
9 | 9 | 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() |
10 | 10 | cache.cache.set("foo", "bar") |
11 | 11 | cache.cache.get("foo") |
12 | 12 | cache.cache.delete("foo") |
13 | self.assertFalse(cache.cache.touch("foo")) | |
14 | cache.cache.set("foo", "bar") | |
15 | self.assertTrue(cache.cache.touch("foo")) | |
13 | 16 | # Verify that the cache has a valid clear method. |
14 | 17 | cache.cache.clear() |
15 | self.assertEqual(len(self.panel.calls), 4) | |
18 | self.assertEqual(len(self.panel.calls), 7) | |
16 | 19 | |
17 | 20 | def test_recording_caches(self): |
18 | 21 | self.assertEqual(len(self.panel.calls), 0) |
33 | 36 | self.assertNotIn("cafƩ", self.panel.content) |
34 | 37 | self.panel.generate_stats(self.request, response) |
35 | 38 | # 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) | |
38 | 42 | |
39 | 43 | def test_generate_server_timing(self): |
40 | 44 | 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 &"'<>"> | |
24 | Title with special chars &"'<> | |
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 &"'<></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"]) |
62 | 62 | self.assertNotIn("cafƩ", self.panel.content) |
63 | 63 | self.panel.generate_stats(self.request, response) |
64 | 64 | # 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) | |
67 | 68 | |
68 | 69 | def test_failing_formatting(self): |
69 | 70 | class BadClass: |
0 | 0 | from django.contrib.auth.models import User |
1 | 1 | from django.db import IntegrityError, transaction |
2 | 2 | from django.http import HttpResponse |
3 | from django.test import TestCase | |
4 | 3 | from django.test.utils import override_settings |
5 | 4 | |
6 | from ..base import BaseTestCase | |
5 | from ..base import BaseTestCase, IntegrationTestCase | |
7 | 6 | from ..views import listcomp_view, regular_view |
8 | 7 | |
9 | 8 | |
31 | 30 | self.assertNotIn("regular_view", self.panel.content) |
32 | 31 | self.panel.generate_stats(self.request, response) |
33 | 32 | # 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) | |
36 | 36 | |
37 | 37 | def test_listcomp_escaped(self): |
38 | 38 | self._get_response = lambda request: listcomp_view(request) |
39 | 39 | response = self.panel.process_request(self.request) |
40 | 40 | 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"><listcomp></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"><listcomp></span>', content) | |
47 | 44 | |
48 | 45 | def test_generate_stats_no_profiler(self): |
49 | 46 | """ |
67 | 64 | @override_settings( |
68 | 65 | DEBUG=True, DEBUG_TOOLBAR_PANELS=["debug_toolbar.panels.profiling.ProfilingPanel"] |
69 | 66 | ) |
70 | class ProfilingPanelIntegrationTestCase(TestCase): | |
67 | class ProfilingPanelIntegrationTestCase(IntegrationTestCase): | |
71 | 68 | def test_view_executed_once(self): |
72 | 69 | self.assertEqual(User.objects.count(), 0) |
73 | 70 |
0 | from django.http import QueryDict | |
1 | ||
0 | 2 | from ..base import BaseTestCase |
1 | 3 | |
2 | 4 | |
7 | 9 | self.request.session = {"oĆ¹": "oĆ¹"} |
8 | 10 | response = self.panel.process_request(self.request) |
9 | 11 | self.panel.generate_stats(self.request, response) |
10 | content = self.panel.content | |
11 | self.assertIn("oĆ¹", content) | |
12 | self.assertIn("oĆ¹", self.panel.content) | |
12 | 13 | |
13 | 14 | def test_object_with_non_ascii_repr_in_request_params(self): |
14 | 15 | self.request.path = "/non_ascii_request/" |
27 | 28 | self.assertNotIn("nĆ“t Ć„scĆƬ", self.panel.content) |
28 | 29 | self.panel.generate_stats(self.request, response) |
29 | 30 | # 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 | ) |
0 | 0 | import datetime |
1 | 1 | import unittest |
2 | 2 | |
3 | import django | |
3 | 4 | from django.contrib.auth.models import User |
4 | 5 | from django.db import connection |
5 | 6 | from django.db.models import Count |
7 | 8 | from django.shortcuts import render |
8 | 9 | from django.test.utils import override_settings |
9 | 10 | |
11 | from debug_toolbar import settings as dt_settings | |
12 | ||
10 | 13 | 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 | |
11 | 24 | |
12 | 25 | |
13 | 26 | class SQLPanelTestCase(BaseTestCase): |
107 | 120 | # ensure query was logged |
108 | 121 | self.assertEqual(len(self.panel._queries), 3) |
109 | 122 | |
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) | |
110 | 147 | 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 | ) | |
114 | 156 | |
115 | 157 | def test_binary_param_force_text(self): |
116 | 158 | self.assertEqual(len(self.panel._queries), 0) |
204 | 246 | self.assertNotIn("cafƩ", self.panel.content) |
205 | 247 | self.panel.generate_stats(self.request, response) |
206 | 248 | # 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("<script>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) | |
209 | 277 | |
210 | 278 | @unittest.skipUnless( |
211 | 279 | connection.vendor == "postgresql", "Test valid only on PostgreSQL" |
290 | 358 | |
291 | 359 | # ensure the stacktrace is populated |
292 | 360 | 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 | |
0 | 5 | from django.contrib.staticfiles import finders |
6 | from django.test.utils import override_settings | |
1 | 7 | |
2 | 8 | from ..base import BaseTestCase |
9 | ||
10 | PATH_DOES_NOT_EXIST = os.path.join(settings.BASE_DIR, "tests", "invalid_static") | |
3 | 11 | |
4 | 12 | |
5 | 13 | class StaticFilesPanelTestCase(BaseTestCase): |
8 | 16 | def test_default_case(self): |
9 | 17 | response = self.panel.process_request(self.request) |
10 | 18 | self.panel.generate_stats(self.request, response) |
19 | content = self.panel.content | |
11 | 20 | self.assertIn( |
12 | "django.contrib.staticfiles.finders." "AppDirectoriesFinder", | |
13 | self.panel.content, | |
21 | "django.contrib.staticfiles.finders.AppDirectoriesFinder", content | |
14 | 22 | ) |
15 | 23 | self.assertIn( |
16 | "django.contrib.staticfiles.finders." "FileSystemFinder (2 files)", | |
17 | self.panel.content, | |
24 | "django.contrib.staticfiles.finders.FileSystemFinder (2 files)", content | |
18 | 25 | ) |
19 | 26 | self.assertEqual(self.panel.num_used, 0) |
20 | 27 | self.assertNotEqual(self.panel.num_found, 0) |
33 | 40 | response = self.panel.process_request(self.request) |
34 | 41 | # ensure the panel does not have content yet. |
35 | 42 | self.assertNotIn( |
36 | "django.contrib.staticfiles.finders." "AppDirectoriesFinder", | |
43 | "django.contrib.staticfiles.finders.AppDirectoriesFinder", | |
37 | 44 | self.panel.content, |
38 | 45 | ) |
39 | 46 | self.panel.generate_stats(self.request, response) |
40 | 47 | # ensure the panel renders correctly. |
48 | content = self.panel.content | |
41 | 49 | self.assertIn( |
42 | "django.contrib.staticfiles.finders." "AppDirectoriesFinder", | |
43 | self.panel.content, | |
50 | "django.contrib.staticfiles.finders.AppDirectoriesFinder", content | |
44 | 51 | ) |
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 | ) |
0 | 0 | from django.contrib.auth.models import User |
1 | 1 | from django.template import Context, RequestContext, Template |
2 | from django.test import TestCase, override_settings | |
2 | from django.test import override_settings | |
3 | 3 | |
4 | from ..base import BaseTestCase | |
4 | from ..base import BaseTestCase, IntegrationTestCase | |
5 | 5 | from ..forms import TemplateReprForm |
6 | 6 | from ..models import NonAsciiRepr |
7 | 7 | |
31 | 31 | # ensure the query was NOT logged |
32 | 32 | self.assertEqual(len(self.sql_panel._queries), 0) |
33 | 33 | |
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 | ) | |
37 | 42 | |
38 | 43 | def test_template_repr(self): |
39 | 44 | # Force widget templates to be included |
68 | 73 | self.assertNotIn("nĆ“t Ć„scĆƬ", self.panel.content) |
69 | 74 | self.panel.generate_stats(self.request, response) |
70 | 75 | # 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) | |
73 | 79 | |
74 | 80 | def test_custom_context_processor(self): |
75 | 81 | response = self.panel.process_request(self.request) |
87 | 93 | with self.settings(DEBUG_TOOLBAR_CONFIG=config): |
88 | 94 | self.assertFalse(self.panel.enabled) |
89 | 95 | |
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 | ||
90 | 107 | |
91 | 108 | @override_settings( |
92 | 109 | DEBUG=True, DEBUG_TOOLBAR_PANELS=["debug_toolbar.panels.templates.TemplatesPanel"] |
93 | 110 | ) |
94 | class JinjaTemplateTestCase(TestCase): | |
111 | class JinjaTemplateTestCase(IntegrationTestCase): | |
95 | 112 | def test_django_jinja2(self): |
96 | 113 | r = self.client.get("/regular_jinja/foobar/") |
97 | 114 | self.assertContains(r, "Test for foobar (Jinja)") |
78 | 78 | "second": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"}, |
79 | 79 | } |
80 | 80 | |
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 | } | |
91 | 94 | |
95 | DEFAULT_AUTO_FIELD = "django.db.models.AutoField" | |
92 | 96 | |
93 | 97 | # Debug Toolbar configuration |
94 | 98 |
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()) |
0 | 0 | import os |
1 | import re | |
1 | 2 | import unittest |
2 | 3 | |
4 | import django | |
3 | 5 | import html5lib |
4 | 6 | from django.contrib.staticfiles.testing import StaticLiveServerTestCase |
5 | 7 | from django.core import signing |
6 | from django.core.checks import Warning, run_checks | |
8 | from django.db import connection | |
7 | 9 | from django.http import HttpResponse |
8 | 10 | from django.template.loader import get_template |
9 | from django.test import RequestFactory, SimpleTestCase, TestCase | |
11 | from django.test import RequestFactory | |
10 | 12 | from django.test.utils import override_settings |
11 | 13 | |
14 | from debug_toolbar.forms import SignedDataForm | |
12 | 15 | from debug_toolbar.middleware import DebugToolbarMiddleware, show_toolbar |
16 | from debug_toolbar.panels import Panel | |
13 | 17 | from debug_toolbar.toolbar import DebugToolbar |
14 | 18 | |
15 | from .base import BaseTestCase | |
19 | from .base import BaseTestCase, IntegrationTestCase | |
16 | 20 | from .views import regular_view |
17 | 21 | |
18 | 22 | try: |
19 | 23 | from selenium import webdriver |
20 | 24 | 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 | |
21 | 28 | from selenium.webdriver.support.wait import WebDriverWait |
22 | 29 | except ImportError: |
23 | 30 | webdriver = None |
24 | 31 | |
25 | 32 | |
26 | 33 | 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 | |
27 | 43 | |
28 | 44 | |
29 | 45 | @override_settings(DEBUG=True) |
85 | 101 | self.client.get("/cached_view/") |
86 | 102 | self.assertEqual(len(self.toolbar.get_panel_by_id("CachePanel").calls), 5) |
87 | 103 | |
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 | ||
88 | 123 | |
89 | 124 | @override_settings(DEBUG=True) |
90 | class DebugToolbarIntegrationTestCase(TestCase): | |
125 | class DebugToolbarIntegrationTestCase(IntegrationTestCase): | |
91 | 126 | def test_middleware(self): |
92 | 127 | response = self.client.get("/execute_sql/") |
93 | 128 | self.assertEqual(response.status_code, 200) |
129 | self.assertContains(response, "djDebug") | |
94 | 130 | |
95 | 131 | @override_settings(DEFAULT_CHARSET="iso-8859-1") |
96 | 132 | def test_non_utf8_charset(self): |
137 | 173 | ) |
138 | 174 | self.assertEqual(response.status_code, 404) |
139 | 175 | |
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 | ||
140 | 192 | def test_template_source_checks_show_toolbar(self): |
141 | 193 | template = get_template("basic.html") |
142 | 194 | url = "/__debug__/template_source/" |
160 | 212 | def test_sql_select_checks_show_toolbar(self): |
161 | 213 | url = "/__debug__/sql_select/" |
162 | 214 | 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 | ) | |
169 | 224 | } |
170 | 225 | |
171 | 226 | response = self.client.post(url, data) |
183 | 238 | def test_sql_explain_checks_show_toolbar(self): |
184 | 239 | url = "/__debug__/sql_explain/" |
185 | 240 | 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 | } | |
194 | 284 | response = self.client.post(url, data) |
195 | 285 | self.assertEqual(response.status_code, 200) |
196 | 286 | response = self.client.post(url, data, HTTP_X_REQUESTED_WITH="XMLHttpRequest") |
206 | 296 | def test_sql_profile_checks_show_toolbar(self): |
207 | 297 | url = "/__debug__/sql_profile/" |
208 | 298 | 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 | ) | |
215 | 308 | } |
216 | 309 | |
217 | 310 | response = self.client.post(url, data) |
243 | 336 | self.assertEqual(response.status_code, 200) |
244 | 337 | # Link to LOCATION header. |
245 | 338 | 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: ''", response.json()["content"]) | |
370 | else: | |
371 | self.assertIn("None: ''", response.json()["content"]) | |
246 | 372 | |
247 | 373 | |
248 | 374 | @unittest.skipIf(webdriver is None, "selenium isn't installed") |
254 | 380 | @classmethod |
255 | 381 | def setUpClass(cls): |
256 | 382 | 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) | |
258 | 386 | |
259 | 387 | @classmethod |
260 | 388 | def tearDownClass(cls): |
261 | 389 | cls.selenium.quit() |
262 | 390 | super().tearDownClass() |
263 | 391 | |
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 | ||
264 | 399 | def test_basic(self): |
265 | self.selenium.get(self.live_server_url + "/regular/basic/") | |
400 | self.get("/regular/basic/") | |
266 | 401 | version_panel = self.selenium.find_element_by_id("VersionsPanel") |
267 | 402 | |
268 | 403 | # Versions panel isn't loaded |
273 | 408 | self.selenium.find_element_by_class_name("VersionsPanel").click() |
274 | 409 | |
275 | 410 | # Version panel loads |
276 | table = WebDriverWait(self.selenium, timeout=10).until( | |
411 | table = self.wait.until( | |
277 | 412 | lambda selenium: version_panel.find_element_by_tag_name("table") |
278 | 413 | ) |
279 | 414 | self.assertIn("Name", table.text) |
285 | 420 | } |
286 | 421 | ) |
287 | 422 | def test_basic_jinja(self): |
288 | self.selenium.get(self.live_server_url + "/regular_jinja/basic") | |
423 | self.get("/regular_jinja/basic") | |
289 | 424 | template_panel = self.selenium.find_element_by_id("TemplatesPanel") |
290 | 425 | |
291 | 426 | # Click to show the template panel |
292 | 427 | self.selenium.find_element_by_class_name("TemplatesPanel").click() |
293 | 428 | |
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) | |
295 | 431 | 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) | |
296 | 458 | |
297 | 459 | @override_settings(DEBUG_TOOLBAR_CONFIG={"RESULTS_CACHE_SIZE": 0}) |
298 | 460 | def test_expired_store(self): |
299 | self.selenium.get(self.live_server_url + "/regular/basic/") | |
461 | self.get("/regular/basic/") | |
300 | 462 | version_panel = self.selenium.find_element_by_id("VersionsPanel") |
301 | 463 | |
302 | 464 | # Click to show the version panel |
303 | 465 | self.selenium.find_element_by_class_name("VersionsPanel").click() |
304 | 466 | |
305 | 467 | # Version panel doesn't loads |
306 | error = WebDriverWait(self.selenium, timeout=10).until( | |
468 | error = self.wait.until( | |
307 | 469 | lambda selenium: version_panel.find_element_by_tag_name("p") |
308 | 470 | ) |
309 | 471 | self.assertIn("Data for this panel isn't available anymore.", error.text) |
310 | 472 | |
311 | 473 | @override_settings( |
312 | DEBUG=True, | |
313 | 474 | TEMPLATES=[ |
314 | 475 | { |
315 | 476 | "BACKEND": "django.template.backends.django.DjangoTemplates", |
328 | 489 | ], |
329 | 490 | ) |
330 | 491 | def test_django_cached_template_loader(self): |
331 | self.selenium.get(self.live_server_url + "/regular/basic/") | |
492 | self.get("/regular/basic/") | |
332 | 493 | version_panel = self.selenium.find_element_by_id("TemplatesPanel") |
333 | 494 | |
334 | # Click to show the versions panel | |
495 | # Click to show the templates panel | |
335 | 496 | self.selenium.find_element_by_class_name("TemplatesPanel").click() |
336 | 497 | |
337 | # Version panel loads | |
338 | trigger = WebDriverWait(self.selenium, timeout=10).until( | |
498 | # Templates panel loads | |
499 | trigger = self.wait.until( | |
339 | 500 | lambda selenium: version_panel.find_element_by_css_selector(".remoteCall") |
340 | 501 | ) |
341 | 502 | trigger.click() |
342 | 503 | |
343 | 504 | # Verify the code is displayed |
344 | WebDriverWait(self.selenium, timeout=10).until( | |
505 | self.wait.until( | |
345 | 506 | lambda selenium: self.selenium.find_element_by_css_selector( |
346 | 507 | "#djDebugWindow code" |
347 | 508 | ) |
348 | 509 | ) |
349 | 510 | |
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 | |
1 | 3 | |
2 | 4 | import debug_toolbar |
3 | 5 | |
5 | 7 | from .models import NonAsciiRepr |
6 | 8 | |
7 | 9 | 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)), | |
20 | 27 | ] |
0 | 0 | from django.contrib.auth.models import User |
1 | from django.http import HttpResponse, HttpResponseRedirect | |
1 | from django.http import HttpResponseRedirect, JsonResponse | |
2 | 2 | from django.shortcuts import render |
3 | 3 | from django.template.response import TemplateResponse |
4 | 4 | from django.views.decorators.cache import cache_page |
6 | 6 | |
7 | 7 | def execute_sql(request): |
8 | 8 | list(User.objects.all()) |
9 | return HttpResponse() | |
9 | return render(request, "base.html") | |
10 | 10 | |
11 | 11 | |
12 | 12 | def regular_view(request, title): |
24 | 24 | |
25 | 25 | def resolving_view(request, arg1, arg2): |
26 | 26 | # see test_url_resolving in tests.py |
27 | return HttpResponse() | |
27 | return render(request, "base.html") | |
28 | 28 | |
29 | 29 | |
30 | 30 | @cache_page(60) |
31 | 31 | 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"}) | |
33 | 37 | |
34 | 38 | |
35 | 39 | def regular_jinjia_view(request, title): |
0 | 0 | [tox] |
1 | 1 | 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 | |
11 | 4 | 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} | |
12 | 8 | |
13 | 9 | [testenv] |
14 | 10 | 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 | |
20 | 19 | coverage |
21 | 20 | Jinja2 |
22 | 21 | html5lib |
23 | selenium<4.0 | |
22 | selenium | |
24 | 23 | 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_* | |
25 | 33 | setenv = |
26 | 34 | 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} | |
27 | 41 | whitelist_externals = make |
28 | 42 | pip_pre = True |
29 | 43 | commands = make coverage TEST_ARGS='{posargs:tests}' |
30 | 44 | |
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 | |
32 | 65 | 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 | |
68 | 68 | |
69 | 69 | [testenv:style] |
70 | basepython = python3 | |
71 | 70 | commands = make style_check |
72 | 71 | deps = |
73 | black | |
72 | black>=19.10b0 | |
74 | 73 | flake8 |
75 | isort | |
76 | skip_install = true | |
77 | ||
78 | [testenv:jshint] | |
79 | basepython = python3 | |
80 | commands = make jshint | |
74 | isort>=5.0.2 | |
81 | 75 | skip_install = true |
82 | 76 | |
83 | 77 | [testenv:readme] |
84 | basepython = python3 | |
85 | 78 | commands = python setup.py check -r -s |
86 | 79 | deps = readme_renderer |
87 | 80 | 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 |