diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 0e2d03b56..000000000 --- a/.coveragerc +++ /dev/null @@ -1,6 +0,0 @@ -[run] -source = debug_toolbar -branch = 1 - -[report] -omit = *tests*,*migrations* diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 8a2452b7a..000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "env": { - "browser": true, - "es6": true - }, - "extends": "eslint:recommended", - "parserOptions": { - "ecmaVersion": 6, - "sourceType": "module" - }, - "rules": { - "curly": ["error", "all"], - "dot-notation": "error", - "eqeqeq": "error", - "no-eval": "error", - "no-var": "error", - "prefer-const": "error", - "semi": "error" - } -} diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..fd2dc52cb --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,12 @@ +#### Description + +Please include a summary of the change and which issue is fixed. Please also +include relevant motivation and context. Your commit message should include +this information as well. + +Fixes # (issue) + +#### Checklist: + +- [ ] I have added the relevant tests for this change. +- [ ] I have added an item to the Pending section of ``docs/changes.rst``. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..be006de9a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +# Keep GitHub Actions up to date with GitHub's Dependabot... +# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot +# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + groups: + github-actions: + patterns: + - "*" # Group all Actions updates into a single larger pull request + schedule: + interval: weekly diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 000000000..a0722f0ac --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,33 @@ +# .github/workflows/coverage.yml +name: Post coverage comment + +on: + workflow_run: + workflows: ["Test"] + types: + - completed + +jobs: + test: + name: Run tests & display coverage + runs-on: ubuntu-latest + if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' + permissions: + # Gives the action the necessary permissions for publishing new + # comments in pull requests. + pull-requests: write + # Gives the action the necessary permissions for editing existing + # comments (to avoid publishing multiple comments in the same PR) + contents: write + # Gives the action the necessary permissions for looking up the + # workflow that launched this workflow, and download the related + # artifact that contains the comment to be published + actions: read + steps: + # DO NOT run actions/checkout here, for security reasons + # For details, refer to https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ + - name: Post comment + uses: py-cov-action/python-coverage-comment-action@v3 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_PR_RUN_ID: ${{ github.event.workflow_run.id }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..5e61d05bc --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,120 @@ +name: Publish Python 🐍 distribution 📦 to PyPI and TestPyPI + +on: push + +env: + PYPI_URL: https://pypi.org/p/django-debug-toolbar + PYPI_TEST_URL: https://test.pypi.org/p/django-debug-toolbar + +jobs: + + build: + name: Build distribution 📦 + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: Install pypa/build + run: + python3 -m pip install build --user + - name: Build a binary wheel and a source tarball + run: python3 -m build + - name: Store the distribution packages + uses: actions/upload-artifact@v4 + with: + name: python-package-distributions + path: dist/ + + publish-to-pypi: + name: >- + Publish Python 🐍 distribution 📦 to PyPI + if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes + needs: + - build + runs-on: ubuntu-latest + environment: + name: pypi + url: ${{ env.PYPI_URL }} + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution 📦 to PyPI + uses: pypa/gh-action-pypi-publish@release/v1.12 + + github-release: + name: >- + Sign the Python 🐍 distribution 📦 with Sigstore + and upload them to GitHub Release + needs: + - publish-to-pypi + runs-on: ubuntu-latest + + permissions: + contents: write # IMPORTANT: mandatory for making GitHub Releases + id-token: write # IMPORTANT: mandatory for sigstore + + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Sign the dists with Sigstore + uses: sigstore/gh-action-sigstore-python@v3.0.0 + with: + inputs: >- + ./dist/*.tar.gz + ./dist/*.whl + - name: Create GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + run: >- + gh release create + '${{ github.ref_name }}' + --repo '${{ github.repository }}' + --notes "" + - name: Upload artifact signatures to GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + # Upload to GitHub Release using the `gh` CLI. + # `dist/` contains the built packages, and the + # sigstore-produced signatures and certificates. + run: >- + gh release upload + '${{ github.ref_name }}' dist/** + --repo '${{ github.repository }}' + + publish-to-testpypi: + name: Publish Python 🐍 distribution 📦 to TestPyPI + if: startsWith(github.ref, 'refs/tags/') # only publish to Test PyPI on tag pushes + needs: + - build + runs-on: ubuntu-latest + + environment: + name: testpypi + url: ${{ env.PYPI_TEST_URL }} + + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution 📦 to TestPyPI + uses: pypa/gh-action-pypi-publish@release/v1.12 + with: + repository-url: https://test.pypi.org/legacy/ + skip-existing: true diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..a2ded4678 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,222 @@ +name: Test + +on: + push: + pull_request: + schedule: + # Run weekly on Saturday + - cron: '37 3 * * SAT' + +jobs: + mysql: + runs-on: ubuntu-latest + strategy: + fail-fast: false + max-parallel: 5 + matrix: + python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] + + services: + mariadb: + image: mariadb + env: + MARIADB_ROOT_PASSWORD: debug_toolbar + options: >- + --health-cmd "mariadb-admin ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 3306:3306 + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + allow-prereleases: true + + - name: Get pip cache dir + id: pip-cache + run: | + echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT + + - name: Cache + uses: actions/cache@v4 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: + ${{ matrix.python-version }}-v1-${{ hashFiles('**/pyproject.toml') }}-${{ hashFiles('**/tox.ini') }} + restore-keys: | + ${{ matrix.python-version }}-v1- + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install --upgrade tox tox-gh-actions + + - name: Test with tox + run: tox + env: + DB_BACKEND: mysql + DB_USER: root + DB_PASSWORD: debug_toolbar + DB_HOST: 127.0.0.1 + DB_PORT: 3306 + + + postgres: + runs-on: ubuntu-latest + strategy: + fail-fast: false + max-parallel: 5 + matrix: + # Skip 3.13 here, it needs the psycopg3 / postgis3 database + python-version: ['3.9', '3.10', '3.11', '3.12'] + database: [postgresql, postgis] + # Add psycopg3 to our matrix for modern python versions + include: + - python-version: '3.10' + database: psycopg3 + - python-version: '3.11' + database: psycopg3 + - python-version: '3.12' + database: psycopg3 + - python-version: '3.13' + database: psycopg3 + - python-version: '3.13' + database: postgis3 + + services: + postgres: + image: postgis/postgis:14-3.1 + env: + POSTGRES_DB: debug_toolbar + POSTGRES_USER: debug_toolbar + POSTGRES_PASSWORD: debug_toolbar + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + allow-prereleases: true + + - name: Get pip cache dir + id: pip-cache + run: | + echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT + + - name: Cache + uses: actions/cache@v4 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: + ${{ matrix.python-version }}-v1-${{ hashFiles('**/pyproject.toml') }}-${{ hashFiles('**/tox.ini') }} + restore-keys: | + ${{ matrix.python-version }}-v1- + + - name: Install gdal-bin (for postgis) + run: | + sudo apt-get -qq update + sudo apt-get -y install gdal-bin + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install --upgrade tox tox-gh-actions + + - name: Test with tox + run: tox + env: + DB_BACKEND: ${{ matrix.database }} + DB_HOST: localhost + DB_PORT: 5432 + + sqlite: + runs-on: ubuntu-latest + strategy: + fail-fast: false + max-parallel: 5 + matrix: + python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + allow-prereleases: true + + - name: Get pip cache dir + id: pip-cache + run: | + echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT + + - name: Cache + uses: actions/cache@v4 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: + ${{ matrix.python-version }}-v1-${{ hashFiles('**/pyproject.toml') }}-${{ hashFiles('**/tox.ini') }} + restore-keys: | + ${{ matrix.python-version }}-v1- + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install --upgrade tox tox-gh-actions + + - name: Test with tox + run: tox + env: + DB_BACKEND: sqlite3 + DB_NAME: ":memory:" + + lint: + runs-on: ubuntu-latest + strategy: + fail-fast: false + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: 3.9 + + - name: Get pip cache dir + id: pip-cache + run: | + echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT + + - name: Cache + uses: actions/cache@v4 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: + ${{ matrix.python-version }}-v1-${{ hashFiles('**/pyproject.toml') }}-${{ hashFiles('**/tox.ini') }} + restore-keys: | + ${{ matrix.python-version }}-v1- + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install --upgrade tox + + - name: Test with tox + run: tox -e docs,packaging diff --git a/.gitignore b/.gitignore index 836dfe93d..c89013a11 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,17 @@ *.pyc *.DS_Store *~ +.idea build -.coverage +.coverage* dist django_debug_toolbar.egg-info docs/_build example/db.sqlite3 htmlcov .tox -node_modules -package-lock.json geckodriver.log +coverage.xml +.direnv/ +.envrc +venv diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..8c6115813 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,49 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-toml + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + - id: mixed-line-ending + - id: file-contents-sorter + files: docs/spelling_wordlist.txt +- repo: https://github.com/pycqa/doc8 + rev: v1.1.2 + hooks: + - id: doc8 +- repo: https://github.com/adamchainz/django-upgrade + rev: 1.25.0 + hooks: + - id: django-upgrade + args: [--target-version, "4.2"] +- repo: https://github.com/adamchainz/djade-pre-commit + rev: "1.4.0" + hooks: + - id: djade + args: [--target-version, "4.2"] +- repo: https://github.com/pre-commit/pygrep-hooks + rev: v1.10.0 + hooks: + - id: rst-backticks + - id: rst-directive-colons +- repo: https://github.com/biomejs/pre-commit + rev: v2.0.0-beta.5 + hooks: + - id: biome-check + verbose: true +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: 'v0.11.12' + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix] + - id: ruff-format +- repo: https://github.com/tox-dev/pyproject-fmt + rev: v2.6.0 + hooks: + - id: pyproject-fmt +- repo: https://github.com/abravalheri/validate-pyproject + rev: v0.24.1 + hooks: + - id: validate-pyproject diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 000000000..794f8b3ed --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,19 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +version: 2 + +build: + os: ubuntu-24.04 + tools: + python: "3.10" + +sphinx: + configuration: docs/conf.py + +python: + install: + - requirements: requirements_dev.txt + - method: pip + path: . diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 76998f37a..000000000 --- a/.travis.yml +++ /dev/null @@ -1,100 +0,0 @@ -dist: bionic -language: python -cache: pip -matrix: - fast_finish: true - include: - - env: TOXENV=docs - before_install: - - sudo apt-get -y install libenchant1c2a - - env: TOXENV=style - - env: TOXENV=readme - - python: 3.5 - env: TOXENV=py35-dj22-sqlite - - python: 3.6 - env: TOXENV=py36-dj22-sqlite - - python: 3.7 - env: TOXENV=py37-dj22-sqlite - - python: 3.8 - env: TOXENV=py38-dj22-sqlite - - python: 3.6 - env: TOXENV=py36-dj30-sqlite - - python: 3.7 - env: TOXENV=py37-dj30-sqlite - - python: 3.8 - env: TOXENV=py38-dj30-sqlite - - python: 3.6 - env: TOXENV=py36-dj31-sqlite - - python: 3.7 - env: TOXENV=py37-dj31-sqlite - - python: 3.8 - env: TOXENV=py38-dj31-sqlite - - python: 3.6 - env: TOXENV=py36-djmaster-sqlite - - python: 3.7 - env: TOXENV=py37-djmaster-sqlite - - python: 3.8 - env: TOXENV=py38-djmaster-sqlite - - python: 3.8 - env: TOXENV=py38-dj22-postgresql - addons: - postgresql: "9.5" - - python: 3.8 - env: TOXENV=py38-dj30-postgresql - addons: - postgresql: "9.5" - - python: 3.8 - env: TOXENV=py38-dj31-postgresql DJANGO_SELENIUM_TESTS=True - before_install: - - wget https://github.com/mozilla/geckodriver/releases/download/v0.27.0/geckodriver-v0.27.0-linux64.tar.gz - - mkdir geckodriver && tar zxvf geckodriver-v0.27.0-linux64.tar.gz -C geckodriver - - export PATH=$PATH:$PWD/geckodriver - addons: - firefox: latest - postgresql: "9.5" - - python: 3.8 - env: TOXENV=py38-dj22-mariadb - addons: - mariadb: "10.3" - script: - # working around https://travis-ci.community/t/mariadb-build-error-with-xenial/3160 - - mysql -u root -e "DROP USER IF EXISTS 'travis'@'%';" - - mysql -u root -e "CREATE USER 'travis'@'%';" - - mysql -u root -e "CREATE DATABASE debug_toolbar;" - - mysql -u root -e "GRANT ALL PRIVILEGES ON *.* TO 'travis'@'%';"; - - tox -v - - python: 3.8 - env: TOXENV=py38-dj30-mariadb - addons: - mariadb: "10.3" - script: - # working around https://travis-ci.community/t/mariadb-build-error-with-xenial/3160 - - mysql -u root -e "DROP USER IF EXISTS 'travis'@'%';" - - mysql -u root -e "CREATE USER 'travis'@'%';" - - mysql -u root -e "CREATE DATABASE debug_toolbar;" - - mysql -u root -e "GRANT ALL PRIVILEGES ON *.* TO 'travis'@'%';"; - - tox -v - - python: 3.8 - env: TOXENV=py38-dj31-mariadb - addons: - mariadb: "10.3" - script: - # working around https://travis-ci.community/t/mariadb-build-error-with-xenial/3160 - - mysql -u root -e "DROP USER IF EXISTS 'travis'@'%';" - - mysql -u root -e "CREATE USER 'travis'@'%';" - - mysql -u root -e "CREATE DATABASE debug_toolbar;" - - mysql -u root -e "GRANT ALL PRIVILEGES ON *.* TO 'travis'@'%';"; - - tox -v - allow_failures: - - env: TOXENV=py36-djmaster-sqlite - - env: TOXENV=py37-djmaster-sqlite - - env: TOXENV=py38-djmaster-sqlite - - env: TOXENV=py38-djmaster-postgresql - - env: TOXENV=py38-djmaster-mariadb - -install: - - pip install tox codecov -script: - - tox -v -after_success: - - codecov diff --git a/.tx/config b/.tx/config index 238622d0f..15e624db3 100644 --- a/.tx/config +++ b/.tx/config @@ -1,9 +1,10 @@ [main] -host = https://www.transifex.com -lang_map = sr@latin:sr_Latn - -[django-debug-toolbar.master] -file_filter = debug_toolbar/locale//LC_MESSAGES/django.po -source_file = debug_toolbar/locale/en/LC_MESSAGES/django.po -source_lang = en +host = https://www.transifex.com +lang_map = sr@latin: sr_Latn +[o:django-debug-toolbar:p:django-debug-toolbar:r:main] +file_filter = debug_toolbar/locale//LC_MESSAGES/django.po +source_file = debug_toolbar/locale/en/LC_MESSAGES/django.po +source_lang = en +replace_edited_strings = false +keep_translations = false diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..5fedea529 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,3 @@ +# Django Debug Toolbar Code of Conduct + +The django-debug-toolbar project utilizes the [Django Commons Code of Conduct](https://github.com/django-commons/membership/blob/main/CODE_OF_CONDUCT.md). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 829a22ace..efc91ec2a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,11 @@ -[![Jazzband](https://jazzband.co/static/img/jazzband.svg)](https://jazzband.co/) +# Contributing to Django Debug Toolbar -This is a [Jazzband](https://jazzband.co/) project. By contributing you agree to abide by the [Contributor Code of Conduct](https://jazzband.co/about/conduct) and follow the [guidelines](https://jazzband.co/about/guidelines). +This is a [Django Commons](https://github.com/django-commons/) project. By contributing you agree to abide by the [Contributor Code of Conduct](https://github.com/django-commons/membership/blob/main/CODE_OF_CONDUCT.md). -Please see the -[full contributing documentation](https://django-debug-toolbar.readthedocs.io/en/stable/contributing.html) -for more help. +## Documentation + +For detailed contributing guidelines, please see our [Documentation](https://django-debug-toolbar.readthedocs.io/en/latest/contributing.html). + +## Additional Resources + +Please see the [README](https://github.com/django-commons/membership/blob/main/README.md) for more help. diff --git a/LICENSE b/LICENSE index 15d830926..221d73313 100644 --- a/LICENSE +++ b/LICENSE @@ -4,10 +4,10 @@ All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - 1. Redistributions of source code must retain the above copyright notice, + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright + + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index e3d4782fc..000000000 --- a/MANIFEST.in +++ /dev/null @@ -1,6 +0,0 @@ -include LICENSE -include README.rst -include CONTRIBUTING.md -recursive-include debug_toolbar/locale * -recursive-include debug_toolbar/static * -recursive-include debug_toolbar/templates * diff --git a/Makefile b/Makefile index 05ff29203..4d2db27af 100644 --- a/Makefile +++ b/Makefile @@ -1,56 +1,44 @@ -.PHONY: flake8 example test coverage translatable_strings update_translations +.PHONY: example test coverage translatable_strings update_translations help +.DEFAULT_GOAL := help -PRETTIER_TARGETS = '**/*.(css|js)' - -style: package-lock.json - isort . - black --target-version=py35 . - flake8 - npx eslint --ignore-path .gitignore --fix . - npx prettier --ignore-path .gitignore --write $(PRETTIER_TARGETS) - -style_check: package-lock.json - isort -c . - black --target-version=py35 --check . - flake8 - npx eslint --ignore-path .gitignore . - npx prettier --ignore-path .gitignore --check $(PRETTIER_TARGETS) - -example: +example: ## Run the example application python example/manage.py migrate --noinput -DJANGO_SUPERUSER_PASSWORD=p python example/manage.py createsuperuser \ --noinput --username="$(USER)" --email="$(USER)@mailinator.com" python example/manage.py runserver -package-lock.json: package.json - npm install - touch $@ +example_test: ## Run the test suite for the example application + python example/manage.py test example -test: +test: ## Run the test suite DJANGO_SETTINGS_MODULE=tests.settings \ python -m django test $${TEST_ARGS:-tests} -test_selenium: +test_selenium: ## Run frontend tests written with Selenium DJANGO_SELENIUM_TESTS=true DJANGO_SETTINGS_MODULE=tests.settings \ python -m django test $${TEST_ARGS:-tests} -coverage: +coverage: ## Run the test suite with coverage enabled python --version - coverage erase DJANGO_SETTINGS_MODULE=tests.settings \ python -b -W always -m coverage run -m django test -v2 $${TEST_ARGS:-tests} coverage report coverage html + coverage xml -translatable_strings: +translatable_strings: ## Update the English '.po' file cd debug_toolbar && python -m django makemessages -l en --no-obsolete @echo "Please commit changes and run 'tx push -s' (or wait for Transifex to pick them)" -update_translations: +update_translations: ## Download updated '.po' files from Transifex tx pull -a --minimum-perc=10 cd debug_toolbar && python -m django compilemessages .PHONY: example/django-debug-toolbar.png -example/django-debug-toolbar.png: example/screenshot.py +example/django-debug-toolbar.png: example/screenshot.py ## Update the screenshot in 'README.rst' python $< --browser firefox --headless -o $@ optipng $@ + +help: ## Help message for targets + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \ + | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' diff --git a/README.rst b/README.rst index 5e02e88b3..6c7da3615 100644 --- a/README.rst +++ b/README.rst @@ -1,33 +1,52 @@ -==================== -Django Debug Toolbar -==================== +===================================== +Django Debug Toolbar |latest-version| +===================================== -.. image:: https://jazzband.co/static/img/badge.svg - :target: https://jazzband.co/ - :alt: Jazzband +|build-status| |coverage| |docs| |python-support| |django-support| -.. image:: https://travis-ci.org/jazzband/django-debug-toolbar.svg?branch=master - :target: https://travis-ci.org/jazzband/django-debug-toolbar +.. |latest-version| image:: https://img.shields.io/pypi/v/django-debug-toolbar.svg + :target: https://pypi.org/project/django-debug-toolbar/ + :alt: Latest version on PyPI + +.. |build-status| image:: https://github.com/django-commons/django-debug-toolbar/workflows/Test/badge.svg + :target: https://github.com/django-commons/django-debug-toolbar/actions/workflows/test.yml :alt: Build Status -.. image:: https://codecov.io/gh/jazzband/django-debug-toolbar/branch/master/graph/badge.svg - :target: https://codecov.io/gh/jazzband/django-debug-toolbar +.. |coverage| image:: https://img.shields.io/badge/Coverage-94%25-green + :target: https://github.com/django-commons/django-debug-toolbar/actions/workflows/test.yml?query=branch%3Amain :alt: Test coverage status +.. |docs| image:: https://img.shields.io/readthedocs/django-debug-toolbar/latest.svg + :target: https://readthedocs.org/projects/django-debug-toolbar/ + :alt: Documentation status + +.. |python-support| image:: https://img.shields.io/pypi/pyversions/django-debug-toolbar + :target: https://pypi.org/project/django-debug-toolbar/ + :alt: Supported Python versions + +.. |django-support| image:: https://img.shields.io/pypi/djversions/django-debug-toolbar + :target: https://pypi.org/project/django-debug-toolbar/ + :alt: Supported Django versions + The Django Debug Toolbar is a configurable set of panels that display various debug information about the current request/response and when clicked, display more details about the panel's content. Here's a screenshot of the toolbar in action: -.. image:: https://raw.github.com/jazzband/django-debug-toolbar/master/example/django-debug-toolbar.png +.. image:: https://raw.github.com/django-commons/django-debug-toolbar/main/example/django-debug-toolbar.png :alt: Django Debug Toolbar screenshot In addition to the built-in panels, a number of third-party panels are contributed by the community. -The current stable version of the Debug Toolbar is 3.1. It works on -Django ≥ 2.2. +The current stable version of the Debug Toolbar is 5.2.0. It works on +Django ≥ 4.2.0. + +The Debug Toolbar has experimental support for `Django's asynchronous views +`_. Please note that +the Debug Toolbar still lacks the capability for handling concurrent requests. +If you find any issues, please report them on the `issue tracker`_. Documentation, including installation and configuration instructions, is available at https://django-debug-toolbar.readthedocs.io/. @@ -38,4 +57,5 @@ itself. If you like it, please consider contributing! The Django Debug Toolbar was originally created by Rob Hudson in August 2008 and was further developed by many contributors_. -.. _contributors: https://github.com/jazzband/django-debug-toolbar/graphs/contributors +.. _contributors: https://github.com/django-commons/django-debug-toolbar/graphs/contributors +.. _issue tracker: https://github.com/django-commons/django-debug-toolbar/issues diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..31750a749 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,9 @@ +# Security Policy + +## Supported Versions + +Only the latest version of django-debug-toolbar [![PyPI version](https://badge.fury.io/py/django-debug-toolbar.svg)](https://pypi.python.org/pypi/django-debug-toolbar) is supported. + +## Reporting a Vulnerability + +If you think you have found a vulnerability, and even if you are not sure, please [report it to us in private](https://github.com/django-commons/django-debug-toolbar/security/advisories/new). We will review it and get back to you. Please refrain from public discussions of the issue. diff --git a/biome.json b/biome.json new file mode 100644 index 000000000..dc2776d79 --- /dev/null +++ b/biome.json @@ -0,0 +1,55 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.0.0-beta.5/schema.json", + "formatter": { + "enabled": true, + "useEditorconfig": true + }, + "assist": { + "actions": { + "source": { + "organizeImports": "on" + } + } + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "style": { + "useLiteralEnumMembers": "error", + "noCommaOperator": "error", + "useNodejsImportProtocol": "error", + "useAsConstAssertion": "error", + "useEnumInitializers": "error", + "useSelfClosingElements": "error", + "useConst": "error", + "useSingleVarDeclarator": "error", + "noUnusedTemplateLiteral": "error", + "useNumberNamespace": "error", + "noInferrableTypes": "error", + "useExponentiationOperator": "error", + "useTemplate": "error", + "noParameterAssign": "error", + "noNonNullAssertion": "error", + "useDefaultParameterLast": "error", + "noArguments": "error", + "useImportType": "error", + "useExportType": "error", + "noUselessElse": "error", + "useShorthandFunctionType": "error" + }, + "suspicious": { + "noDocumentCookie": "off" + }, + "complexity": { + "useNumericLiterals": "error" + } + } + }, + "javascript": { + "formatter": { + "trailingCommas": "es5", + "quoteStyle": "double" + } + } +} diff --git a/debug_toolbar/__init__.py b/debug_toolbar/__init__.py index 844da1a5b..770c5eeed 100644 --- a/debug_toolbar/__init__.py +++ b/debug_toolbar/__init__.py @@ -1,16 +1,10 @@ -__all__ = ["VERSION"] +__all__ = ["APP_NAME", "VERSION"] +APP_NAME = "djdt" -try: - import pkg_resources - - VERSION = pkg_resources.get_distribution("django-debug-toolbar").version -except Exception: - VERSION = "unknown" - +# Do not use pkg_resources to find the version but set it here directly! +# see issue #1446 +VERSION = "5.2.0" # Code that discovers files or modules in INSTALLED_APPS imports this module. - -urls = "debug_toolbar.toolbar", "djdt" - -default_app_config = "debug_toolbar.apps.DebugToolbarConfig" +urls = "debug_toolbar.urls", APP_NAME diff --git a/debug_toolbar/_compat.py b/debug_toolbar/_compat.py new file mode 100644 index 000000000..0e0ab8c1b --- /dev/null +++ b/debug_toolbar/_compat.py @@ -0,0 +1,10 @@ +try: + from django.contrib.auth.decorators import login_not_required +except ImportError: + # For Django < 5.1, copy the current Django implementation + def login_not_required(view_func): + """ + Decorator for views that allows access to unauthenticated requests. + """ + view_func.login_required = False + return view_func diff --git a/debug_toolbar/_stubs.py b/debug_toolbar/_stubs.py new file mode 100644 index 000000000..c536a0fe7 --- /dev/null +++ b/debug_toolbar/_stubs.py @@ -0,0 +1,26 @@ +from __future__ import annotations + +from typing import Any, NamedTuple, Optional + +from django import template as dj_template + + +class InspectStack(NamedTuple): + frame: Any + filename: str + lineno: int + function: str + code_context: str + index: int + + +TidyStackTrace = list[tuple[str, int, str, str, Optional[Any]]] + + +class RenderContext(dj_template.context.RenderContext): + template: dj_template.Template + + +class RequestContext(dj_template.RequestContext): + template: dj_template.Template + render_context: RenderContext diff --git a/debug_toolbar/apps.py b/debug_toolbar/apps.py index eec750a3b..a49875bac 100644 --- a/debug_toolbar/apps.py +++ b/debug_toolbar/apps.py @@ -1,19 +1,66 @@ import inspect +import mimetypes from django.apps import AppConfig from django.conf import settings -from django.core.checks import Warning, register +from django.core.checks import Error, Warning, register from django.middleware.gzip import GZipMiddleware +from django.urls import NoReverseMatch, reverse from django.utils.module_loading import import_string from django.utils.translation import gettext_lazy as _ -from debug_toolbar import settings as dt_settings +from debug_toolbar import APP_NAME, settings as dt_settings +from debug_toolbar.settings import CONFIG_DEFAULTS class DebugToolbarConfig(AppConfig): name = "debug_toolbar" verbose_name = _("Debug Toolbar") + def ready(self): + from debug_toolbar.toolbar import DebugToolbar + + # Import the panels when the app is ready and call their ready() methods. This + # allows panels like CachePanel to enable their instrumentation immediately. + for cls in DebugToolbar.get_panel_classes(): + cls.ready() + + +def check_template_config(config): + """ + Checks if a template configuration is valid. + + The toolbar requires either the toolbars to be unspecified or + ``django.template.loaders.app_directories.Loader`` to be + included in the loaders. + If custom loaders are specified, then APP_DIRS must be True. + """ + + def flat_loaders(loaders): + """ + Recursively flatten the settings list of template loaders. + + Check for (loader, [child_loaders]) tuples. + Django's default cached loader uses this pattern. + """ + for loader in loaders: + if isinstance(loader, tuple): + yield loader[0] + yield from flat_loaders(loader[1]) + else: + yield loader + + app_dirs = config.get("APP_DIRS", False) + loaders = config.get("OPTIONS", {}).get("loaders", None) + if loaders: + loaders = list(flat_loaders(loaders)) + + # By default the app loader is included. + has_app_loaders = ( + loaders is None or "django.template.loaders.app_directories.Loader" in loaders + ) + return has_app_loaders or app_dirs + @register def check_middleware(app_configs, **kwargs): @@ -23,6 +70,23 @@ def check_middleware(app_configs, **kwargs): gzip_index = None debug_toolbar_indexes = [] + if all(not check_template_config(config) for config in settings.TEMPLATES): + errors.append( + Warning( + "At least one DjangoTemplates TEMPLATES configuration needs " + "to use django.template.loaders.app_directories.Loader or " + "have APP_DIRS set to True.", + hint=( + "Include django.template.loaders.app_directories.Loader " + 'in ["OPTIONS"]["loaders"]. Alternatively use ' + "APP_DIRS=True for at least one " + "django.template.backends.django.DjangoTemplates " + "backend configuration." + ), + id="debug_toolbar.W006", + ) + ) + # If old style MIDDLEWARE_CLASSES is being used, report an error. if settings.is_overridden("MIDDLEWARE_CLASSES"): errors.append( @@ -113,3 +177,97 @@ def check_panels(app_configs, **kwargs): ) ) return errors + + +@register +def js_mimetype_check(app_configs, **kwargs): + """ + Check that JavaScript files are resolving to the correct content type. + """ + # Ideally application/javascript is returned, but text/javascript is + # acceptable. + javascript_types = {"application/javascript", "text/javascript"} + check_failed = not set(mimetypes.guess_type("toolbar.js")).intersection( + javascript_types + ) + if check_failed: + return [ + Warning( + "JavaScript files are resolving to the wrong content type.", + hint="The Django Debug Toolbar may not load properly while mimetypes are misconfigured. " + "See the Django documentation for an explanation of why this occurs.\n" + "https://docs.djangoproject.com/en/stable/ref/contrib/staticfiles/#static-file-development-view\n" + "\n" + "This typically occurs on Windows machines. The suggested solution is to modify " + "HKEY_CLASSES_ROOT in the registry to specify the content type for JavaScript " + "files.\n" + "\n" + "[HKEY_CLASSES_ROOT\\.js]\n" + '"Content Type"="application/javascript"', + id="debug_toolbar.W007", + ) + ] + return [] + + +@register +def debug_toolbar_installed_when_running_tests_check(app_configs, **kwargs): + """ + Check that the toolbar is not being used when tests are running + """ + # Check if show toolbar callback has changed + show_toolbar_changed = ( + dt_settings.get_config()["SHOW_TOOLBAR_CALLBACK"] + != CONFIG_DEFAULTS["SHOW_TOOLBAR_CALLBACK"] + ) + try: + # Check if the toolbar's urls are installed + reverse(f"{APP_NAME}:render_panel") + toolbar_urls_installed = True + except NoReverseMatch: + toolbar_urls_installed = False + + # If the user is using the default SHOW_TOOLBAR_CALLBACK, + # then the middleware will respect the change to settings.DEBUG. + # However, if the user has changed the callback to: + # DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": lambda request: DEBUG} + # where DEBUG is not settings.DEBUG, then it won't pick up that Django' + # test runner has changed the value for settings.DEBUG, and the middleware + # will inject the toolbar, while the URLs aren't configured leading to a + # NoReverseMatch error. + likely_error_setup = show_toolbar_changed and not toolbar_urls_installed + + if ( + not settings.DEBUG + and dt_settings.get_config()["IS_RUNNING_TESTS"] + and likely_error_setup + ): + return [ + Error( + "The Django Debug Toolbar can't be used with tests", + hint="Django changes the DEBUG setting to False when running " + "tests. By default the Django Debug Toolbar is installed because " + "DEBUG is set to True. For most cases, you need to avoid installing " + "the toolbar when running tests. If you feel this check is in error, " + "you can set `DEBUG_TOOLBAR_CONFIG['IS_RUNNING_TESTS'] = False` to " + "bypass this check.", + id="debug_toolbar.E001", + ) + ] + else: + return [] + + +@register +def check_settings(app_configs, **kwargs): + errors = [] + USER_CONFIG = getattr(settings, "DEBUG_TOOLBAR_CONFIG", {}) + if "OBSERVE_REQUEST_CALLBACK" in USER_CONFIG: + errors.append( + Warning( + "The deprecated OBSERVE_REQUEST_CALLBACK setting is present in DEBUG_TOOLBAR_CONFIG.", + hint="Use the UPDATE_ON_FETCH and/or SHOW_TOOLBAR_CALLBACK settings instead.", + id="debug_toolbar.W008", + ) + ) + return errors diff --git a/debug_toolbar/decorators.py b/debug_toolbar/decorators.py index 8114b05d7..61e46490d 100644 --- a/debug_toolbar/decorators.py +++ b/debug_toolbar/decorators.py @@ -1,17 +1,48 @@ import functools +from asgiref.sync import iscoroutinefunction from django.http import Http404 +from django.utils.translation import get_language, override as language_override + +from debug_toolbar import settings as dt_settings def require_show_toolbar(view): - @functools.wraps(view) - def inner(request, *args, **kwargs): - from debug_toolbar.middleware import get_show_toolbar + """ + Async compatible decorator to restrict access to a view + based on the Debug Toolbar's visibility settings. + """ + from debug_toolbar.middleware import get_show_toolbar + + if iscoroutinefunction(view): + + @functools.wraps(view) + async def inner(request, *args, **kwargs): + show_toolbar = get_show_toolbar(async_mode=True) + if not await show_toolbar(request): + raise Http404 + + return await view(request, *args, **kwargs) + else: + + @functools.wraps(view) + def inner(request, *args, **kwargs): + show_toolbar = get_show_toolbar(async_mode=False) + if not show_toolbar(request): + raise Http404 - show_toolbar = get_show_toolbar() - if not show_toolbar(request): - raise Http404 + return view(request, *args, **kwargs) - return view(request, *args, **kwargs) + return inner + + +def render_with_toolbar_language(view): + """Force any rendering within the view to use the toolbar's language.""" + + @functools.wraps(view) + def inner(request, *args, **kwargs): + lang = dt_settings.get_config()["TOOLBAR_LANGUAGE"] or get_language() + with language_override(lang): + return view(request, *args, **kwargs) return inner diff --git a/debug_toolbar/forms.py b/debug_toolbar/forms.py new file mode 100644 index 000000000..61444b43c --- /dev/null +++ b/debug_toolbar/forms.py @@ -0,0 +1,51 @@ +import json + +from django import forms +from django.core import signing +from django.core.exceptions import ValidationError +from django.utils.encoding import force_str + + +class SignedDataForm(forms.Form): + """Helper form that wraps a form to validate its contents on post. + + class PanelForm(forms.Form): + # fields + + On render: + form = SignedDataForm(initial=PanelForm(initial=data).initial) + + On POST: + signed_form = SignedDataForm(request.POST) + if signed_form.is_valid(): + panel_form = PanelForm(signed_form.verified_data) + if panel_form.is_valid(): + # Success + """ + + salt = "django_debug_toolbar" + signed = forms.CharField(required=True, widget=forms.HiddenInput) + + def __init__(self, *args, **kwargs): + initial = kwargs.pop("initial", None) + if initial: + initial = {"signed": self.sign(initial)} + super().__init__(*args, initial=initial, **kwargs) + + def clean_signed(self): + try: + verified = json.loads( + signing.Signer(salt=self.salt).unsign(self.cleaned_data["signed"]) + ) + return verified + except signing.BadSignature as exc: + raise ValidationError("Bad signature") from exc + + def verified_data(self): + return self.is_valid() and self.cleaned_data["signed"] + + @classmethod + def sign(cls, data): + return signing.Signer(salt=cls.salt).sign( + json.dumps({key: force_str(value) for key, value in data.items()}) + ) diff --git a/debug_toolbar/locale/bg/LC_MESSAGES/django.mo b/debug_toolbar/locale/bg/LC_MESSAGES/django.mo new file mode 100644 index 000000000..ae59298d5 Binary files /dev/null and b/debug_toolbar/locale/bg/LC_MESSAGES/django.mo differ diff --git a/debug_toolbar/locale/bg/LC_MESSAGES/django.po b/debug_toolbar/locale/bg/LC_MESSAGES/django.po new file mode 100644 index 000000000..d9fd766fe --- /dev/null +++ b/debug_toolbar/locale/bg/LC_MESSAGES/django.po @@ -0,0 +1,705 @@ +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# +# Translators: +# arneatec , 2022 +msgid "" +msgstr "" +"Project-Id-Version: Django Debug Toolbar\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-08-06 07:12-0500\n" +"PO-Revision-Date: 2010-11-30 00:00+0000\n" +"Last-Translator: arneatec , 2022\n" +"Language-Team: Bulgarian (http://app.transifex.com/django-debug-toolbar/django-debug-toolbar/language/bg/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: bg\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: apps.py:18 +msgid "Debug Toolbar" +msgstr "Debug Toolbar" + +#: panels/alerts.py:67 +#, python-brace-format +msgid "" +"Form with id \"{form_id}\" contains file input, but does not have the " +"attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:70 +msgid "" +"Form contains file input, but does not have the attribute " +"enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:73 +#, python-brace-format +msgid "" +"Input element references form with id \"{form_id}\", but the form does not " +"have the attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:77 +msgid "Alerts" +msgstr "" + +#: panels/cache.py:168 +msgid "Cache" +msgstr "Кеш" + +#: panels/cache.py:174 +#, python-format +msgid "%(cache_calls)d call in %(time).2fms" +msgid_plural "%(cache_calls)d calls in %(time).2fms" +msgstr[0] "%(cache_calls)d извикване за %(time).2fms" +msgstr[1] "%(cache_calls)d извиквания за %(time).2fms" + +#: panels/cache.py:183 +#, python-format +msgid "Cache calls from %(count)d backend" +msgid_plural "Cache calls from %(count)d backends" +msgstr[0] "Извиквания на кеша от %(count)d бекенд" +msgstr[1] "Извиквания на кеша от %(count)d бекенда" + +#: panels/headers.py:31 +msgid "Headers" +msgstr "Хедъри" + +#: panels/history/panel.py:19 panels/history/panel.py:20 +msgid "History" +msgstr "История" + +#: panels/profiling.py:140 +msgid "Profiling" +msgstr "Профилиране" + +#: panels/redirects.py:17 +msgid "Intercept redirects" +msgstr "Прехвани пренасочвания" + +#: panels/request.py:16 +msgid "Request" +msgstr "Заявка" + +#: panels/request.py:38 +msgid "" +msgstr "" + +#: panels/request.py:55 +msgid "" +msgstr "" + +#: panels/settings.py:17 +msgid "Settings" +msgstr "Настройки" + +#: panels/settings.py:20 +#, python-format +msgid "Settings from %s" +msgstr "Настройки от %s" + +#: panels/signals.py:57 +#, python-format +msgid "%(num_receivers)d receiver of 1 signal" +msgid_plural "%(num_receivers)d receivers of 1 signal" +msgstr[0] "%(num_receivers)d получател на 1 сигнал" +msgstr[1] "%(num_receivers)d получатели на 1 сигнал" + +#: panels/signals.py:62 +#, python-format +msgid "%(num_receivers)d receiver of %(num_signals)d signals" +msgid_plural "%(num_receivers)d receivers of %(num_signals)d signals" +msgstr[0] "%(num_receivers)d приемник на %(num_signals)d сигнала" +msgstr[1] "%(num_receivers)d приемника на %(num_signals)d сигнала" + +#: panels/signals.py:67 +msgid "Signals" +msgstr "Сигнали" + +#: panels/sql/panel.py:30 panels/sql/panel.py:41 +msgid "Read uncommitted" +msgstr "Read uncommitted" + +#: panels/sql/panel.py:31 panels/sql/panel.py:43 +msgid "Read committed" +msgstr "Read committed" + +#: panels/sql/panel.py:32 panels/sql/panel.py:45 +msgid "Repeatable read" +msgstr "Repeatable read" + +#: panels/sql/panel.py:33 panels/sql/panel.py:47 +msgid "Serializable" +msgstr "Serializable" + +#: panels/sql/panel.py:39 +msgid "Autocommit" +msgstr "Autocommit" + +#: panels/sql/panel.py:61 panels/sql/panel.py:71 +msgid "Idle" +msgstr "Незает" + +#: panels/sql/panel.py:62 panels/sql/panel.py:72 +msgid "Active" +msgstr "Активен" + +#: panels/sql/panel.py:63 panels/sql/panel.py:73 +msgid "In transaction" +msgstr "В транзакция" + +#: panels/sql/panel.py:64 panels/sql/panel.py:74 +msgid "In error" +msgstr "В грешка" + +#: panels/sql/panel.py:65 panels/sql/panel.py:75 +msgid "Unknown" +msgstr "Непознато" + +#: panels/sql/panel.py:162 +msgid "SQL" +msgstr "SQL" + +#: panels/sql/panel.py:168 +#, python-format +msgid "%(query_count)d query in %(sql_time).2fms" +msgid_plural "%(query_count)d queries in %(sql_time).2fms" +msgstr[0] "%(query_count)d заявка за %(sql_time).2fms" +msgstr[1] "%(query_count)d заявки за %(sql_time).2fms" + +#: panels/sql/panel.py:180 +#, python-format +msgid "SQL queries from %(count)d connection" +msgid_plural "SQL queries from %(count)d connections" +msgstr[0] "SQL заявки от %(count)d връзка" +msgstr[1] "SQL заявки от %(count)d връзки" + +#: panels/staticfiles.py:82 +#, python-format +msgid "Static files (%(num_found)s found, %(num_used)s used)" +msgstr "Статични файлове (%(num_found)s открити, %(num_used)s използвани)" + +#: panels/staticfiles.py:103 +msgid "Static files" +msgstr "Статични файлове" + +#: panels/staticfiles.py:109 +#, python-format +msgid "%(num_used)s file used" +msgid_plural "%(num_used)s files used" +msgstr[0] "%(num_used)s файл използван" +msgstr[1] "%(num_used)s файла са използвани" + +#: panels/templates/panel.py:101 +msgid "Templates" +msgstr "Шаблони" + +#: panels/templates/panel.py:106 +#, python-format +msgid "Templates (%(num_templates)s rendered)" +msgstr "Шаблони (%(num_templates)s рендерирани)" + +#: panels/templates/panel.py:195 +msgid "No origin" +msgstr "Няма произход" + +#: panels/timer.py:27 +#, python-format +msgid "CPU: %(cum)0.2fms (%(total)0.2fms)" +msgstr "Процесор: %(cum)0.2fms (%(total)0.2fms)" + +#: panels/timer.py:32 +#, python-format +msgid "Total: %0.2fms" +msgstr "Общо: %0.2fms" + +#: panels/timer.py:38 templates/debug_toolbar/panels/history.html:9 +#: templates/debug_toolbar/panels/sql_explain.html:11 +#: templates/debug_toolbar/panels/sql_profile.html:12 +#: templates/debug_toolbar/panels/sql_select.html:11 +msgid "Time" +msgstr "Време" + +#: panels/timer.py:46 +msgid "User CPU time" +msgstr "Потребителско процесорно време " + +#: panels/timer.py:46 +#, python-format +msgid "%(utime)0.3f msec" +msgstr "%(utime)0.3f msec" + +#: panels/timer.py:47 +msgid "System CPU time" +msgstr "Системно процесорно време" + +#: panels/timer.py:47 +#, python-format +msgid "%(stime)0.3f msec" +msgstr "%(stime)0.3f msec" + +#: panels/timer.py:48 +msgid "Total CPU time" +msgstr "Общо процесорно време" + +#: panels/timer.py:48 +#, python-format +msgid "%(total)0.3f msec" +msgstr "%(total)0.3f msec" + +#: panels/timer.py:49 +msgid "Elapsed time" +msgstr "Изминало време" + +#: panels/timer.py:49 +#, python-format +msgid "%(total_time)0.3f msec" +msgstr "%(total_time)0.3f msec" + +#: panels/timer.py:51 +msgid "Context switches" +msgstr "Контекстни превключвания" + +#: panels/timer.py:52 +#, python-format +msgid "%(vcsw)d voluntary, %(ivcsw)d involuntary" +msgstr "%(vcsw)d волеви, %(ivcsw)d неволеви" + +#: panels/versions.py:19 +msgid "Versions" +msgstr "Версии" + +#: templates/debug_toolbar/base.html:23 +msgid "Hide toolbar" +msgstr "Скрий лента с инструменти " + +#: templates/debug_toolbar/base.html:23 +msgid "Hide" +msgstr "Скрий" + +#: templates/debug_toolbar/base.html:25 templates/debug_toolbar/base.html:26 +msgid "Toggle Theme" +msgstr "" + +#: templates/debug_toolbar/base.html:35 +msgid "Show toolbar" +msgstr "Покажи лента с инструменти" + +#: templates/debug_toolbar/includes/panel_button.html:4 +msgid "Disable for next and successive requests" +msgstr "Деактивирай за следващо и всички последващи заявки" + +#: templates/debug_toolbar/includes/panel_button.html:4 +msgid "Enable for next and successive requests" +msgstr "Активирай за следващо и всички последващи заявки" + +#: templates/debug_toolbar/panels/alerts.html:4 +msgid "Alerts found" +msgstr "" + +#: templates/debug_toolbar/panels/alerts.html:11 +msgid "No alerts found" +msgstr "" + +#: templates/debug_toolbar/panels/cache.html:2 +msgid "Summary" +msgstr "Обобщение" + +#: templates/debug_toolbar/panels/cache.html:6 +msgid "Total calls" +msgstr "Общо извиквания" + +#: templates/debug_toolbar/panels/cache.html:7 +msgid "Total time" +msgstr "Общо време" + +#: templates/debug_toolbar/panels/cache.html:8 +msgid "Cache hits" +msgstr "Кеш успехи" + +#: templates/debug_toolbar/panels/cache.html:9 +msgid "Cache misses" +msgstr "Кеш неуспехи" + +#: templates/debug_toolbar/panels/cache.html:21 +msgid "Commands" +msgstr "Команди" + +#: templates/debug_toolbar/panels/cache.html:39 +msgid "Calls" +msgstr "Извиквания" + +#: templates/debug_toolbar/panels/cache.html:43 +#: templates/debug_toolbar/panels/sql.html:36 +msgid "Time (ms)" +msgstr "Време (ms)" + +#: templates/debug_toolbar/panels/cache.html:44 +msgid "Type" +msgstr "Вид" + +#: templates/debug_toolbar/panels/cache.html:45 +#: templates/debug_toolbar/panels/request.html:8 +msgid "Arguments" +msgstr "Аргументи" + +#: templates/debug_toolbar/panels/cache.html:46 +#: templates/debug_toolbar/panels/request.html:9 +msgid "Keyword arguments" +msgstr "Аргументи с ключови думи" + +#: templates/debug_toolbar/panels/cache.html:47 +msgid "Backend" +msgstr "Бекенд" + +#: templates/debug_toolbar/panels/headers.html:3 +msgid "Request headers" +msgstr "Хедъри на заявката" + +#: templates/debug_toolbar/panels/headers.html:8 +#: templates/debug_toolbar/panels/headers.html:27 +#: templates/debug_toolbar/panels/headers.html:48 +msgid "Key" +msgstr "Ключ" + +#: templates/debug_toolbar/panels/headers.html:9 +#: templates/debug_toolbar/panels/headers.html:28 +#: templates/debug_toolbar/panels/headers.html:49 +#: templates/debug_toolbar/panels/history_tr.html:23 +#: templates/debug_toolbar/panels/request_variables.html:12 +#: templates/debug_toolbar/panels/settings.html:6 +#: templates/debug_toolbar/panels/timer.html:11 +msgid "Value" +msgstr "Стойност" + +#: templates/debug_toolbar/panels/headers.html:22 +msgid "Response headers" +msgstr "Хедъри на отговора" + +#: templates/debug_toolbar/panels/headers.html:41 +msgid "WSGI environ" +msgstr "WSGI environ" + +#: templates/debug_toolbar/panels/headers.html:43 +msgid "" +"Since the WSGI environ inherits the environment of the server, only a " +"significant subset is shown below." +msgstr "Понеже WSGI environ наследява средата на сървъра, е показана само важната част от него по-долу." + +#: templates/debug_toolbar/panels/history.html:10 +msgid "Method" +msgstr "Метод" + +#: templates/debug_toolbar/panels/history.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:43 +msgid "Path" +msgstr "Път" + +#: templates/debug_toolbar/panels/history.html:12 +msgid "Request Variables" +msgstr "Променливи на зявката" + +#: templates/debug_toolbar/panels/history.html:13 +msgid "Status" +msgstr "Състояние" + +#: templates/debug_toolbar/panels/history.html:14 +#: templates/debug_toolbar/panels/sql.html:37 +msgid "Action" +msgstr "Действие" + +#: templates/debug_toolbar/panels/history_tr.html:22 +#: templates/debug_toolbar/panels/request_variables.html:11 +msgid "Variable" +msgstr "Променлива" + +#: templates/debug_toolbar/panels/profiling.html:5 +msgid "Call" +msgstr "Извикване" + +#: templates/debug_toolbar/panels/profiling.html:6 +msgid "CumTime" +msgstr "КумулативноВреме" + +#: templates/debug_toolbar/panels/profiling.html:7 +#: templates/debug_toolbar/panels/profiling.html:9 +msgid "Per" +msgstr "За" + +#: templates/debug_toolbar/panels/profiling.html:8 +msgid "TotTime" +msgstr "ОбщоВреме" + +#: templates/debug_toolbar/panels/profiling.html:10 +msgid "Count" +msgstr "Брой" + +#: templates/debug_toolbar/panels/request.html:3 +msgid "View information" +msgstr "Информация за изгледа" + +#: templates/debug_toolbar/panels/request.html:7 +msgid "View function" +msgstr "Функция на изгледа" + +#: templates/debug_toolbar/panels/request.html:10 +msgid "URL name" +msgstr "Име на URL" + +#: templates/debug_toolbar/panels/request.html:24 +msgid "Cookies" +msgstr "Бисквитки" + +#: templates/debug_toolbar/panels/request.html:27 +msgid "No cookies" +msgstr "Няма бисквитки" + +#: templates/debug_toolbar/panels/request.html:31 +msgid "Session data" +msgstr "Данни на сесията" + +#: templates/debug_toolbar/panels/request.html:34 +msgid "No session data" +msgstr "Няма данни от сесията" + +#: templates/debug_toolbar/panels/request.html:38 +msgid "GET data" +msgstr "GET данни" + +#: templates/debug_toolbar/panels/request.html:41 +msgid "No GET data" +msgstr "Няма GET данни" + +#: templates/debug_toolbar/panels/request.html:45 +msgid "POST data" +msgstr "POST данни" + +#: templates/debug_toolbar/panels/request.html:48 +msgid "No POST data" +msgstr "Няма POST данни" + +#: templates/debug_toolbar/panels/settings.html:5 +msgid "Setting" +msgstr "Настройка" + +#: templates/debug_toolbar/panels/signals.html:5 +msgid "Signal" +msgstr "Сигнал" + +#: templates/debug_toolbar/panels/signals.html:6 +msgid "Receivers" +msgstr "Получатели" + +#: templates/debug_toolbar/panels/sql.html:6 +#, python-format +msgid "%(num)s query" +msgid_plural "%(num)s queries" +msgstr[0] "%(num)s заявка" +msgstr[1] "%(num)s заявки" + +#: templates/debug_toolbar/panels/sql.html:8 +#, python-format +msgid "" +"including %(count)s similar" +msgstr "Включва %(count)s подобни" + +#: templates/debug_toolbar/panels/sql.html:12 +#, python-format +msgid "" +"and %(dupes)s duplicates" +msgstr "и %(dupes)s повторени" + +#: templates/debug_toolbar/panels/sql.html:34 +msgid "Query" +msgstr "Заявка" + +#: templates/debug_toolbar/panels/sql.html:35 +#: templates/debug_toolbar/panels/timer.html:36 +msgid "Timeline" +msgstr "Във времето" + +#: templates/debug_toolbar/panels/sql.html:52 +#, python-format +msgid "%(count)s similar queries." +msgstr "%(count)s подобни заявки." + +#: templates/debug_toolbar/panels/sql.html:58 +#, python-format +msgid "Duplicated %(dupes)s times." +msgstr "Повторени %(dupes)s пъти." + +#: templates/debug_toolbar/panels/sql.html:95 +msgid "Connection:" +msgstr "Връзка:" + +#: templates/debug_toolbar/panels/sql.html:97 +msgid "Isolation level:" +msgstr "Изолационно ниво:" + +#: templates/debug_toolbar/panels/sql.html:100 +msgid "Transaction status:" +msgstr "Статус на транзакцията:" + +#: templates/debug_toolbar/panels/sql.html:114 +msgid "(unknown)" +msgstr "(неясен)" + +#: templates/debug_toolbar/panels/sql.html:123 +msgid "No SQL queries were recorded during this request." +msgstr "Не са записани никакви SQL заявки по време на тази заявка." + +#: templates/debug_toolbar/panels/sql_explain.html:4 +msgid "SQL explained" +msgstr "SQL разяснен" + +#: templates/debug_toolbar/panels/sql_explain.html:9 +#: templates/debug_toolbar/panels/sql_profile.html:10 +#: templates/debug_toolbar/panels/sql_select.html:9 +msgid "Executed SQL" +msgstr "Изпълнен SQL" + +#: templates/debug_toolbar/panels/sql_explain.html:13 +#: templates/debug_toolbar/panels/sql_profile.html:14 +#: templates/debug_toolbar/panels/sql_select.html:13 +msgid "Database" +msgstr "База данни" + +#: templates/debug_toolbar/panels/sql_profile.html:4 +msgid "SQL profiled" +msgstr "SQL профилиран" + +#: templates/debug_toolbar/panels/sql_profile.html:37 +msgid "Error" +msgstr "Грешка" + +#: templates/debug_toolbar/panels/sql_select.html:4 +msgid "SQL selected" +msgstr "Избран SQL" + +#: templates/debug_toolbar/panels/sql_select.html:36 +msgid "Empty set" +msgstr "Празно множество" + +#: templates/debug_toolbar/panels/staticfiles.html:3 +msgid "Static file path" +msgid_plural "Static file paths" +msgstr[0] "Път към статичен файл" +msgstr[1] "Пътища към статични файлове" + +#: templates/debug_toolbar/panels/staticfiles.html:7 +#, python-format +msgid "(prefix %(prefix)s)" +msgstr "(префикс %(prefix)s)" + +#: templates/debug_toolbar/panels/staticfiles.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:22 +#: templates/debug_toolbar/panels/staticfiles.html:34 +#: templates/debug_toolbar/panels/templates.html:10 +#: templates/debug_toolbar/panels/templates.html:30 +#: templates/debug_toolbar/panels/templates.html:47 +msgid "None" +msgstr "None" + +#: templates/debug_toolbar/panels/staticfiles.html:14 +msgid "Static file app" +msgid_plural "Static file apps" +msgstr[0] "Приложение статичен файл" +msgstr[1] "Приложения статично файлове" + +#: templates/debug_toolbar/panels/staticfiles.html:25 +msgid "Static file" +msgid_plural "Static files" +msgstr[0] "Статичен файл" +msgstr[1] "Статични файлове" + +#: templates/debug_toolbar/panels/staticfiles.html:39 +#, python-format +msgid "%(payload_count)s file" +msgid_plural "%(payload_count)s files" +msgstr[0] "%(payload_count)s файл" +msgstr[1] "%(payload_count)s файла" + +#: templates/debug_toolbar/panels/staticfiles.html:44 +msgid "Location" +msgstr "Местоположение" + +#: templates/debug_toolbar/panels/template_source.html:4 +msgid "Template source:" +msgstr "Произход на шаблона:" + +#: templates/debug_toolbar/panels/templates.html:2 +msgid "Template path" +msgid_plural "Template paths" +msgstr[0] "Път към шаблон" +msgstr[1] "Пътища към шаблони" + +#: templates/debug_toolbar/panels/templates.html:13 +msgid "Template" +msgid_plural "Templates" +msgstr[0] "Шаблон" +msgstr[1] "Шаблони" + +#: templates/debug_toolbar/panels/templates.html:22 +#: templates/debug_toolbar/panels/templates.html:40 +msgid "Toggle context" +msgstr "Превключи контекста" + +#: templates/debug_toolbar/panels/templates.html:33 +msgid "Context processor" +msgid_plural "Context processors" +msgstr[0] "Контекстен процесор" +msgstr[1] "Контекстни процесори" + +#: templates/debug_toolbar/panels/timer.html:2 +msgid "Resource usage" +msgstr "Използване на ресурси" + +#: templates/debug_toolbar/panels/timer.html:10 +msgid "Resource" +msgstr "Ресурс" + +#: templates/debug_toolbar/panels/timer.html:26 +msgid "Browser timing" +msgstr "Време в браузъра" + +#: templates/debug_toolbar/panels/timer.html:35 +msgid "Timing attribute" +msgstr "Атрибут на измерването" + +#: templates/debug_toolbar/panels/timer.html:37 +msgid "Milliseconds since navigation start (+length)" +msgstr "Милисекунди от началото на навигацията (+дължината)" + +#: templates/debug_toolbar/panels/versions.html:10 +msgid "Package" +msgstr "Пакет" + +#: templates/debug_toolbar/panels/versions.html:11 +msgid "Name" +msgstr "Име" + +#: templates/debug_toolbar/panels/versions.html:12 +msgid "Version" +msgstr "Версия" + +#: templates/debug_toolbar/redirect.html:10 +msgid "Location:" +msgstr "Местоположение:" + +#: templates/debug_toolbar/redirect.html:12 +msgid "" +"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." +msgstr "Django Debug Toolbar прехвана пренасочване към горния URL с цел преглед за отстраняване на грешки /дебъг/. Можете да кликнете върху връзката по-горе, за да продължите с пренасочването по нормалния начин." + +#: views.py:16 +msgid "" +"Data for this panel isn't available anymore. Please reload the page and " +"retry." +msgstr "Данните за този панел вече не са налични. Моля, презаредете страницата и опитайте отново. " diff --git a/debug_toolbar/locale/ca/LC_MESSAGES/django.mo b/debug_toolbar/locale/ca/LC_MESSAGES/django.mo index 260ace99e..c7e63dbe4 100644 Binary files a/debug_toolbar/locale/ca/LC_MESSAGES/django.mo and b/debug_toolbar/locale/ca/LC_MESSAGES/django.mo differ diff --git a/debug_toolbar/locale/ca/LC_MESSAGES/django.po b/debug_toolbar/locale/ca/LC_MESSAGES/django.po index 5de03a115..393b74bc1 100644 --- a/debug_toolbar/locale/ca/LC_MESSAGES/django.po +++ b/debug_toolbar/locale/ca/LC_MESSAGES/django.po @@ -1,71 +1,78 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# -# +# +# # Translators: -# Libre El Chaval , 2013 +# el_libre como el chaval , 2013 msgid "" msgstr "" "Project-Id-Version: Django Debug Toolbar\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-04-25 21:52+0200\n" -"PO-Revision-Date: 2014-04-25 19:53+0000\n" -"Last-Translator: Aymeric Augustin \n" -"Language-Team: Catalan (http://www.transifex.com/projects/p/django-debug-toolbar/language/ca/)\n" +"POT-Creation-Date: 2024-08-06 07:12-0500\n" +"PO-Revision-Date: 2010-11-30 00:00+0000\n" +"Last-Translator: el_libre como el chaval , 2013\n" +"Language-Team: Catalan (http://app.transifex.com/django-debug-toolbar/django-debug-toolbar/language/ca/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ca\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: apps.py:11 +#: apps.py:18 msgid "Debug Toolbar" msgstr "" -#: views.py:14 +#: panels/alerts.py:67 +#, python-brace-format msgid "" -"Data for this panel isn't available anymore. Please reload the page and " -"retry." +"Form with id \"{form_id}\" contains file input, but does not have the " +"attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:70 +msgid "" +"Form contains file input, but does not have the attribute " +"enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:73 +#, python-brace-format +msgid "" +"Input element references form with id \"{form_id}\", but the form does not " +"have the attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:77 +msgid "Alerts" msgstr "" -#: panels/cache.py:191 +#: panels/cache.py:168 msgid "Cache" msgstr "Caxè" -#: panels/cache.py:196 +#: panels/cache.py:174 #, python-format msgid "%(cache_calls)d call in %(time).2fms" msgid_plural "%(cache_calls)d calls in %(time).2fms" msgstr[0] "" msgstr[1] "" -#: panels/cache.py:204 +#: panels/cache.py:183 #, python-format msgid "Cache calls from %(count)d backend" msgid_plural "Cache calls from %(count)d backends" msgstr[0] "" msgstr[1] "" -#: panels/headers.py:35 +#: panels/headers.py:31 msgid "Headers" msgstr "Encapçalaments" -#: panels/logging.py:64 -msgid "Logging" -msgstr "Entrant" - -#: panels/logging.py:70 -#, python-format -msgid "%(count)s message" -msgid_plural "%(count)s messages" -msgstr[0] "" -msgstr[1] "" - -#: panels/logging.py:73 -msgid "Log messages" -msgstr "Registre de missatges" +#: panels/history/panel.py:19 panels/history/panel.py:20 +msgid "History" +msgstr "" -#: panels/profiling.py:127 +#: panels/profiling.py:140 msgid "Profiling" msgstr "" @@ -73,213 +80,228 @@ msgstr "" msgid "Intercept redirects" msgstr "" -#: panels/request.py:18 +#: panels/request.py:16 msgid "Request" msgstr "Demanar" -#: panels/request.py:35 +#: panels/request.py:38 msgid "" msgstr "" -#: panels/request.py:47 +#: panels/request.py:55 msgid "" msgstr "" -#: panels/settings.py:20 +#: panels/settings.py:17 msgid "Settings" msgstr "Configuració" -#: panels/settings.py:23 +#: panels/settings.py:20 #, python-format -msgid "Settings from %s" +msgid "Settings from %s" msgstr "" -#: panels/signals.py:45 +#: panels/signals.py:57 #, python-format msgid "%(num_receivers)d receiver of 1 signal" msgid_plural "%(num_receivers)d receivers of 1 signal" msgstr[0] "" msgstr[1] "" -#: panels/signals.py:48 +#: panels/signals.py:62 #, python-format msgid "%(num_receivers)d receiver of %(num_signals)d signals" msgid_plural "%(num_receivers)d receivers of %(num_signals)d signals" msgstr[0] "" msgstr[1] "" -#: panels/signals.py:53 +#: panels/signals.py:67 msgid "Signals" msgstr "Senyals" -#: panels/staticfiles.py:89 +#: panels/sql/panel.py:30 panels/sql/panel.py:41 +msgid "Read uncommitted" +msgstr "" + +#: panels/sql/panel.py:31 panels/sql/panel.py:43 +msgid "Read committed" +msgstr "" + +#: panels/sql/panel.py:32 panels/sql/panel.py:45 +msgid "Repeatable read" +msgstr "" + +#: panels/sql/panel.py:33 panels/sql/panel.py:47 +msgid "Serializable" +msgstr "Seriable" + +#: panels/sql/panel.py:39 +msgid "Autocommit" +msgstr "" + +#: panels/sql/panel.py:61 panels/sql/panel.py:71 +msgid "Idle" +msgstr "" + +#: panels/sql/panel.py:62 panels/sql/panel.py:72 +msgid "Active" +msgstr "Actiu" + +#: panels/sql/panel.py:63 panels/sql/panel.py:73 +msgid "In transaction" +msgstr "En transacció" + +#: panels/sql/panel.py:64 panels/sql/panel.py:74 +msgid "In error" +msgstr "" + +#: panels/sql/panel.py:65 panels/sql/panel.py:75 +msgid "Unknown" +msgstr "Desconegut" + +#: panels/sql/panel.py:162 +msgid "SQL" +msgstr "SQL" + +#: panels/sql/panel.py:168 +#, python-format +msgid "%(query_count)d query in %(sql_time).2fms" +msgid_plural "%(query_count)d queries in %(sql_time).2fms" +msgstr[0] "" +msgstr[1] "" + +#: panels/sql/panel.py:180 +#, python-format +msgid "SQL queries from %(count)d connection" +msgid_plural "SQL queries from %(count)d connections" +msgstr[0] "" +msgstr[1] "" + +#: panels/staticfiles.py:82 #, python-format msgid "Static files (%(num_found)s found, %(num_used)s used)" msgstr "" -#: panels/staticfiles.py:107 +#: panels/staticfiles.py:103 msgid "Static files" msgstr "" -#: panels/staticfiles.py:112 +#: panels/staticfiles.py:109 #, python-format msgid "%(num_used)s file used" msgid_plural "%(num_used)s files used" msgstr[0] "" msgstr[1] "" -#: panels/timer.py:23 +#: panels/templates/panel.py:101 +msgid "Templates" +msgstr "Plantilles" + +#: panels/templates/panel.py:106 +#, python-format +msgid "Templates (%(num_templates)s rendered)" +msgstr "" + +#: panels/templates/panel.py:195 +msgid "No origin" +msgstr "" + +#: panels/timer.py:27 #, python-format msgid "CPU: %(cum)0.2fms (%(total)0.2fms)" msgstr "" -#: panels/timer.py:28 +#: panels/timer.py:32 #, python-format msgid "Total: %0.2fms" msgstr "Total: %0.2fms" -#: panels/timer.py:34 templates/debug_toolbar/panels/logging.html:7 +#: panels/timer.py:38 templates/debug_toolbar/panels/history.html:9 #: templates/debug_toolbar/panels/sql_explain.html:11 #: templates/debug_toolbar/panels/sql_profile.html:12 #: templates/debug_toolbar/panels/sql_select.html:11 msgid "Time" msgstr "Hora" -#: panels/timer.py:42 +#: panels/timer.py:46 msgid "User CPU time" msgstr "" -#: panels/timer.py:42 +#: panels/timer.py:46 #, python-format msgid "%(utime)0.3f msec" msgstr "" -#: panels/timer.py:43 +#: panels/timer.py:47 msgid "System CPU time" msgstr "" -#: panels/timer.py:43 +#: panels/timer.py:47 #, python-format msgid "%(stime)0.3f msec" msgstr "" -#: panels/timer.py:44 +#: panels/timer.py:48 msgid "Total CPU time" msgstr "" -#: panels/timer.py:44 +#: panels/timer.py:48 #, python-format msgid "%(total)0.3f msec" msgstr "" -#: panels/timer.py:45 +#: panels/timer.py:49 msgid "Elapsed time" msgstr "Temps emprat" -#: panels/timer.py:45 +#: panels/timer.py:49 #, python-format msgid "%(total_time)0.3f msec" msgstr "" -#: panels/timer.py:46 +#: panels/timer.py:51 msgid "Context switches" msgstr "" -#: panels/timer.py:46 +#: panels/timer.py:52 #, python-format msgid "%(vcsw)d voluntary, %(ivcsw)d involuntary" msgstr "" -#: panels/versions.py:25 +#: panels/versions.py:19 msgid "Versions" msgstr "Versions" -#: panels/sql/panel.py:22 -msgid "Autocommit" -msgstr "" - -#: panels/sql/panel.py:23 -msgid "Read uncommitted" -msgstr "" - -#: panels/sql/panel.py:24 -msgid "Read committed" -msgstr "" - -#: panels/sql/panel.py:25 -msgid "Repeatable read" -msgstr "" - -#: panels/sql/panel.py:26 -msgid "Serializable" -msgstr "Seriable" - -#: panels/sql/panel.py:37 -msgid "Idle" -msgstr "" - -#: panels/sql/panel.py:38 -msgid "Active" -msgstr "Actiu" - -#: panels/sql/panel.py:39 -msgid "In transaction" -msgstr "En transacció" - -#: panels/sql/panel.py:40 -msgid "In error" -msgstr "" - -#: panels/sql/panel.py:41 -msgid "Unknown" -msgstr "Desconegut" - -#: panels/sql/panel.py:105 -msgid "SQL" -msgstr "SQL" - -#: panels/templates/panel.py:141 -msgid "Templates" -msgstr "Plantilles" - -#: panels/templates/panel.py:146 -#, python-format -msgid "Templates (%(num_templates)s rendered)" -msgstr "" - -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide toolbar" msgstr "Amagar barra d'eina" -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide" msgstr "Amagar" -#: templates/debug_toolbar/base.html:25 -msgid "Disable for next and successive requests" +#: templates/debug_toolbar/base.html:25 templates/debug_toolbar/base.html:26 +msgid "Toggle Theme" msgstr "" -#: templates/debug_toolbar/base.html:25 -msgid "Enable for next and successive requests" -msgstr "" - -#: templates/debug_toolbar/base.html:47 +#: templates/debug_toolbar/base.html:35 msgid "Show toolbar" msgstr "Mostrar barra d'eines" -#: templates/debug_toolbar/base.html:53 -msgid "Close" -msgstr "Tancar" +#: templates/debug_toolbar/includes/panel_button.html:4 +msgid "Disable for next and successive requests" +msgstr "" -#: templates/debug_toolbar/redirect.html:8 -msgid "Location:" -msgstr "Ubicació:" +#: templates/debug_toolbar/includes/panel_button.html:4 +msgid "Enable for next and successive requests" +msgstr "" -#: templates/debug_toolbar/redirect.html:10 -msgid "" -"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." +#: templates/debug_toolbar/panels/alerts.html:4 +msgid "Alerts found" +msgstr "" + +#: templates/debug_toolbar/panels/alerts.html:11 +msgid "No alerts found" msgstr "" #: templates/debug_toolbar/panels/cache.html:2 @@ -311,7 +333,7 @@ msgid "Calls" msgstr "Crides" #: templates/debug_toolbar/panels/cache.html:43 -#: templates/debug_toolbar/panels/sql.html:20 +#: templates/debug_toolbar/panels/sql.html:36 msgid "Time (ms)" msgstr "Temps (ms)" @@ -346,10 +368,8 @@ msgstr "Clau" #: templates/debug_toolbar/panels/headers.html:9 #: templates/debug_toolbar/panels/headers.html:28 #: templates/debug_toolbar/panels/headers.html:49 -#: templates/debug_toolbar/panels/request.html:33 -#: templates/debug_toolbar/panels/request.html:59 -#: templates/debug_toolbar/panels/request.html:85 -#: templates/debug_toolbar/panels/request.html:110 +#: templates/debug_toolbar/panels/history_tr.html:23 +#: templates/debug_toolbar/panels/request_variables.html:12 #: templates/debug_toolbar/panels/settings.html:6 #: templates/debug_toolbar/panels/timer.html:11 msgid "Value" @@ -369,27 +389,33 @@ msgid "" "significant subset is shown below." msgstr "" -#: templates/debug_toolbar/panels/logging.html:6 -msgid "Level" -msgstr "Nivell" - -#: templates/debug_toolbar/panels/logging.html:8 -msgid "Channel" -msgstr "Canal" +#: templates/debug_toolbar/panels/history.html:10 +msgid "Method" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:9 -msgid "Message" -msgstr "Missatge" +#: templates/debug_toolbar/panels/history.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:43 +msgid "Path" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:10 -#: templates/debug_toolbar/panels/staticfiles.html:45 -msgid "Location" -msgstr "Ubicació" +#: templates/debug_toolbar/panels/history.html:12 +msgid "Request Variables" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:26 -msgid "No messages logged" +#: templates/debug_toolbar/panels/history.html:13 +msgid "Status" msgstr "" +#: templates/debug_toolbar/panels/history.html:14 +#: templates/debug_toolbar/panels/sql.html:37 +msgid "Action" +msgstr "Acció" + +#: templates/debug_toolbar/panels/history_tr.html:22 +#: templates/debug_toolbar/panels/request_variables.html:11 +msgid "Variable" +msgstr "Variable" + #: templates/debug_toolbar/panels/profiling.html:5 msgid "Call" msgstr "Cridar" @@ -427,38 +453,31 @@ msgstr "" msgid "Cookies" msgstr "" -#: templates/debug_toolbar/panels/request.html:32 -#: templates/debug_toolbar/panels/request.html:58 -#: templates/debug_toolbar/panels/request.html:84 -#: templates/debug_toolbar/panels/request.html:109 -msgid "Variable" -msgstr "Variable" - -#: templates/debug_toolbar/panels/request.html:46 +#: templates/debug_toolbar/panels/request.html:27 msgid "No cookies" msgstr "" -#: templates/debug_toolbar/panels/request.html:50 +#: templates/debug_toolbar/panels/request.html:31 msgid "Session data" msgstr "" -#: templates/debug_toolbar/panels/request.html:72 +#: templates/debug_toolbar/panels/request.html:34 msgid "No session data" msgstr "" -#: templates/debug_toolbar/panels/request.html:76 +#: templates/debug_toolbar/panels/request.html:38 msgid "GET data" msgstr "" -#: templates/debug_toolbar/panels/request.html:98 +#: templates/debug_toolbar/panels/request.html:41 msgid "No GET data" msgstr "Sense dades GET" -#: templates/debug_toolbar/panels/request.html:102 +#: templates/debug_toolbar/panels/request.html:45 msgid "POST data" msgstr "" -#: templates/debug_toolbar/panels/request.html:123 +#: templates/debug_toolbar/panels/request.html:48 msgid "No POST data" msgstr "Sense dades POST" @@ -471,60 +490,69 @@ msgid "Signal" msgstr "Senyal" #: templates/debug_toolbar/panels/signals.html:6 -msgid "Providing" -msgstr "" - -#: templates/debug_toolbar/panels/signals.html:7 msgid "Receivers" msgstr "Destinataris" -#: templates/debug_toolbar/panels/sql.html:7 +#: templates/debug_toolbar/panels/sql.html:6 #, python-format msgid "%(num)s query" msgid_plural "%(num)s queries" msgstr[0] "" msgstr[1] "" -#: templates/debug_toolbar/panels/sql.html:18 +#: templates/debug_toolbar/panels/sql.html:8 +#, python-format +msgid "" +"including %(count)s similar" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:12 +#, python-format +msgid "" +"and %(dupes)s duplicates" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:34 msgid "Query" msgstr "Petició" -#: templates/debug_toolbar/panels/sql.html:19 +#: templates/debug_toolbar/panels/sql.html:35 #: templates/debug_toolbar/panels/timer.html:36 msgid "Timeline" msgstr "Línia temporal" -#: templates/debug_toolbar/panels/sql.html:21 -msgid "Action" -msgstr "Acció" +#: templates/debug_toolbar/panels/sql.html:52 +#, python-format +msgid "%(count)s similar queries." +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:58 +#, python-format +msgid "Duplicated %(dupes)s times." +msgstr "" -#: templates/debug_toolbar/panels/sql.html:64 +#: templates/debug_toolbar/panels/sql.html:95 msgid "Connection:" msgstr "Connexió:" -#: templates/debug_toolbar/panels/sql.html:66 +#: templates/debug_toolbar/panels/sql.html:97 msgid "Isolation level:" msgstr "" -#: templates/debug_toolbar/panels/sql.html:69 +#: templates/debug_toolbar/panels/sql.html:100 msgid "Transaction status:" msgstr "" -#: templates/debug_toolbar/panels/sql.html:83 +#: templates/debug_toolbar/panels/sql.html:114 msgid "(unknown)" msgstr "(desconegut)" -#: templates/debug_toolbar/panels/sql.html:92 +#: templates/debug_toolbar/panels/sql.html:123 msgid "No SQL queries were recorded during this request." msgstr "" -#: templates/debug_toolbar/panels/sql_explain.html:3 -#: templates/debug_toolbar/panels/sql_profile.html:3 -#: templates/debug_toolbar/panels/sql_select.html:3 -#: templates/debug_toolbar/panels/template_source.html:3 -msgid "Back" -msgstr "Tornar" - #: templates/debug_toolbar/panels/sql_explain.html:4 msgid "SQL explained" msgstr "" @@ -557,39 +585,39 @@ msgstr "" msgid "Empty set" msgstr "" -#: templates/debug_toolbar/panels/staticfiles.html:4 +#: templates/debug_toolbar/panels/staticfiles.html:3 msgid "Static file path" msgid_plural "Static file paths" msgstr[0] "" msgstr[1] "" -#: templates/debug_toolbar/panels/staticfiles.html:8 +#: templates/debug_toolbar/panels/staticfiles.html:7 #, python-format msgid "(prefix %(prefix)s)" msgstr "" -#: templates/debug_toolbar/panels/staticfiles.html:12 -#: templates/debug_toolbar/panels/staticfiles.html:23 -#: templates/debug_toolbar/panels/staticfiles.html:35 +#: templates/debug_toolbar/panels/staticfiles.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:22 +#: templates/debug_toolbar/panels/staticfiles.html:34 #: templates/debug_toolbar/panels/templates.html:10 -#: templates/debug_toolbar/panels/templates.html:28 -#: templates/debug_toolbar/panels/templates.html:43 +#: templates/debug_toolbar/panels/templates.html:30 +#: templates/debug_toolbar/panels/templates.html:47 msgid "None" msgstr "Cap" -#: templates/debug_toolbar/panels/staticfiles.html:15 +#: templates/debug_toolbar/panels/staticfiles.html:14 msgid "Static file app" msgid_plural "Static file apps" msgstr[0] "" msgstr[1] "" -#: templates/debug_toolbar/panels/staticfiles.html:26 +#: templates/debug_toolbar/panels/staticfiles.html:25 msgid "Static file" msgid_plural "Static files" msgstr[0] "" msgstr[1] "" -#: templates/debug_toolbar/panels/staticfiles.html:40 +#: templates/debug_toolbar/panels/staticfiles.html:39 #, python-format msgid "%(payload_count)s file" msgid_plural "%(payload_count)s files" @@ -597,8 +625,8 @@ msgstr[0] "" msgstr[1] "" #: templates/debug_toolbar/panels/staticfiles.html:44 -msgid "Path" -msgstr "" +msgid "Location" +msgstr "Ubicació" #: templates/debug_toolbar/panels/template_source.html:4 msgid "Template source:" @@ -616,12 +644,12 @@ msgid_plural "Templates" msgstr[0] "" msgstr[1] "Plantilles" -#: templates/debug_toolbar/panels/templates.html:21 -#: templates/debug_toolbar/panels/templates.html:37 +#: templates/debug_toolbar/panels/templates.html:22 +#: templates/debug_toolbar/panels/templates.html:40 msgid "Toggle context" msgstr "" -#: templates/debug_toolbar/panels/templates.html:31 +#: templates/debug_toolbar/panels/templates.html:33 msgid "Context processor" msgid_plural "Context processors" msgstr[0] "" @@ -647,10 +675,31 @@ msgstr "" msgid "Milliseconds since navigation start (+length)" msgstr "" -#: templates/debug_toolbar/panels/versions.html:5 +#: templates/debug_toolbar/panels/versions.html:10 +msgid "Package" +msgstr "" + +#: templates/debug_toolbar/panels/versions.html:11 msgid "Name" msgstr "Nom" -#: templates/debug_toolbar/panels/versions.html:6 +#: templates/debug_toolbar/panels/versions.html:12 msgid "Version" msgstr "Versió" + +#: templates/debug_toolbar/redirect.html:10 +msgid "Location:" +msgstr "Ubicació:" + +#: templates/debug_toolbar/redirect.html:12 +msgid "" +"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." +msgstr "" + +#: views.py:16 +msgid "" +"Data for this panel isn't available anymore. Please reload the page and " +"retry." +msgstr "" diff --git a/debug_toolbar/locale/cs/LC_MESSAGES/django.mo b/debug_toolbar/locale/cs/LC_MESSAGES/django.mo index 3a2db0fb4..a0daa392e 100644 Binary files a/debug_toolbar/locale/cs/LC_MESSAGES/django.mo and b/debug_toolbar/locale/cs/LC_MESSAGES/django.mo differ diff --git a/debug_toolbar/locale/cs/LC_MESSAGES/django.po b/debug_toolbar/locale/cs/LC_MESSAGES/django.po index 26b6cdbdc..395b6feca 100644 --- a/debug_toolbar/locale/cs/LC_MESSAGES/django.po +++ b/debug_toolbar/locale/cs/LC_MESSAGES/django.po @@ -1,74 +1,85 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# -# +# +# # Translators: -# Vlada Macek , 2013 +# Josef Kolář , 2020 +# kuboja, 2024 +# Vláďa Macek , 2013-2014 +# Vláďa Macek , 2015,2021 msgid "" msgstr "" "Project-Id-Version: Django Debug Toolbar\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-04-25 21:52+0200\n" -"PO-Revision-Date: 2014-04-25 19:53+0000\n" -"Last-Translator: Aymeric Augustin \n" -"Language-Team: Czech (http://www.transifex.com/projects/p/django-debug-toolbar/language/cs/)\n" +"POT-Creation-Date: 2024-08-06 07:12-0500\n" +"PO-Revision-Date: 2010-11-30 00:00+0000\n" +"Last-Translator: kuboja, 2024\n" +"Language-Team: Czech (http://app.transifex.com/django-debug-toolbar/django-debug-toolbar/language/cs/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: cs\n" -"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" +"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n" -#: apps.py:11 +#: apps.py:18 msgid "Debug Toolbar" +msgstr "Debug Toolbar" + +#: panels/alerts.py:67 +#, python-brace-format +msgid "" +"Form with id \"{form_id}\" contains file input, but does not have the " +"attribute enctype=\"multipart/form-data\"." msgstr "" -#: views.py:14 +#: panels/alerts.py:70 msgid "" -"Data for this panel isn't available anymore. Please reload the page and " -"retry." -msgstr "Data pro tento panel již nejsou k dispozici. Obnovte stránku a zkuste to znova." +"Form contains file input, but does not have the attribute " +"enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:73 +#, python-brace-format +msgid "" +"Input element references form with id \"{form_id}\", but the form does not " +"have the attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:77 +msgid "Alerts" +msgstr "" -#: panels/cache.py:191 +#: panels/cache.py:168 msgid "Cache" msgstr "Mezipaměť" -#: panels/cache.py:196 +#: panels/cache.py:174 #, python-format msgid "%(cache_calls)d call in %(time).2fms" msgid_plural "%(cache_calls)d calls in %(time).2fms" msgstr[0] "%(cache_calls)d volání během %(time).2fms" msgstr[1] "%(cache_calls)d volání během %(time).2fms" msgstr[2] "%(cache_calls)d volání během %(time).2fms" +msgstr[3] "%(cache_calls)d volání během %(time).2fms" -#: panels/cache.py:204 +#: panels/cache.py:183 #, python-format msgid "Cache calls from %(count)d backend" msgid_plural "Cache calls from %(count)d backends" msgstr[0] "Volání mezipaměti z %(count)d backendu" msgstr[1] "Volání mezipaměti z %(count)d backendů" msgstr[2] "Volání mezipaměti z %(count)d backendů" +msgstr[3] "Volání mezipaměti z %(count)d backendů" -#: panels/headers.py:35 +#: panels/headers.py:31 msgid "Headers" -msgstr "Záhlaví" +msgstr "Hlavičky" -#: panels/logging.py:64 -msgid "Logging" -msgstr "Protokol" +#: panels/history/panel.py:19 panels/history/panel.py:20 +msgid "History" +msgstr "Historie" -#: panels/logging.py:70 -#, python-format -msgid "%(count)s message" -msgid_plural "%(count)s messages" -msgstr[0] "%(count)s zpráva" -msgstr[1] "%(count)s zprávy" -msgstr[2] "%(count)s zpráv" - -#: panels/logging.py:73 -msgid "Log messages" -msgstr "Zprávy protokolu" - -#: panels/profiling.py:127 +#: panels/profiling.py:140 msgid "Profiling" msgstr "Profilování" @@ -76,217 +87,239 @@ msgstr "Profilování" msgid "Intercept redirects" msgstr "Zachycení přesměrování" -#: panels/request.py:18 +#: panels/request.py:16 msgid "Request" msgstr "Požadavek" -#: panels/request.py:35 +#: panels/request.py:38 msgid "" msgstr "<žádný pohled>" -#: panels/request.py:47 +#: panels/request.py:55 msgid "" msgstr "" -#: panels/settings.py:20 +#: panels/settings.py:17 msgid "Settings" -msgstr "Settings" +msgstr "Nastavení" -#: panels/settings.py:23 +#: panels/settings.py:20 #, python-format -msgid "Settings from %s" -msgstr "Nastavení z modulu %s" +msgid "Settings from %s" +msgstr "" -#: panels/signals.py:45 +#: panels/signals.py:57 #, python-format msgid "%(num_receivers)d receiver of 1 signal" msgid_plural "%(num_receivers)d receivers of 1 signal" msgstr[0] "%(num_receivers)d příjemce 1 signálu" msgstr[1] "%(num_receivers)d příjemci 1 signálu" msgstr[2] "%(num_receivers)d příjemců 1 signálu" +msgstr[3] "%(num_receivers)d příjemců 1 signálu" -#: panels/signals.py:48 +#: panels/signals.py:62 #, python-format msgid "%(num_receivers)d receiver of %(num_signals)d signals" msgid_plural "%(num_receivers)d receivers of %(num_signals)d signals" msgstr[0] "%(num_receivers)d příjemce %(num_signals)d signálů" msgstr[1] "%(num_receivers)d příjemci %(num_signals)d signálů" msgstr[2] "%(num_receivers)d příjemců %(num_signals)d signálů" +msgstr[3] "%(num_receivers)d příjemců %(num_signals)d signálů" -#: panels/signals.py:53 +#: panels/signals.py:67 msgid "Signals" msgstr "Signály" -#: panels/staticfiles.py:89 +#: panels/sql/panel.py:30 panels/sql/panel.py:41 +msgid "Read uncommitted" +msgstr "Read uncommitted" + +#: panels/sql/panel.py:31 panels/sql/panel.py:43 +msgid "Read committed" +msgstr "Read committed" + +#: panels/sql/panel.py:32 panels/sql/panel.py:45 +msgid "Repeatable read" +msgstr "Repeatable read" + +#: panels/sql/panel.py:33 panels/sql/panel.py:47 +msgid "Serializable" +msgstr "Serializable" + +#: panels/sql/panel.py:39 +msgid "Autocommit" +msgstr "Autocommit" + +#: panels/sql/panel.py:61 panels/sql/panel.py:71 +msgid "Idle" +msgstr "V klidu (idle)" + +#: panels/sql/panel.py:62 panels/sql/panel.py:72 +msgid "Active" +msgstr "Aktivní" + +#: panels/sql/panel.py:63 panels/sql/panel.py:73 +msgid "In transaction" +msgstr "Uvnitř transakce" + +#: panels/sql/panel.py:64 panels/sql/panel.py:74 +msgid "In error" +msgstr "V chybovém stavu" + +#: panels/sql/panel.py:65 panels/sql/panel.py:75 +msgid "Unknown" +msgstr "Neznámé" + +#: panels/sql/panel.py:162 +msgid "SQL" +msgstr "SQL" + +#: panels/sql/panel.py:168 +#, python-format +msgid "%(query_count)d query in %(sql_time).2fms" +msgid_plural "%(query_count)d queries in %(sql_time).2fms" +msgstr[0] "%(query_count)ddotaz během %(sql_time).2f ms" +msgstr[1] "%(query_count)d dotazy během %(sql_time).2f ms" +msgstr[2] "%(query_count)d dotazů během %(sql_time).2f ms" +msgstr[3] "%(query_count)d dotazů během %(sql_time).2f ms" + +#: panels/sql/panel.py:180 +#, python-format +msgid "SQL queries from %(count)d connection" +msgid_plural "SQL queries from %(count)d connections" +msgstr[0] "SQL dotazy z %(count)d spojení" +msgstr[1] "SQL dotazy ze %(count)d spojení" +msgstr[2] "SQL dotazy z %(count)d spojení" +msgstr[3] "SQL dotazy z %(count)d spojení" + +#: panels/staticfiles.py:82 #, python-format msgid "Static files (%(num_found)s found, %(num_used)s used)" msgstr "Statické soubory (nalezeno: %(num_found)s, použito: %(num_used)s)" -#: panels/staticfiles.py:107 +#: panels/staticfiles.py:103 msgid "Static files" msgstr "Statické soubory" -#: panels/staticfiles.py:112 +#: panels/staticfiles.py:109 #, python-format msgid "%(num_used)s file used" msgid_plural "%(num_used)s files used" msgstr[0] "%(num_used)s soubor použit" msgstr[1] "%(num_used)s soubory použity" msgstr[2] "%(num_used)s souborů použito" +msgstr[3] "%(num_used)s souborů použito" -#: panels/timer.py:23 +#: panels/templates/panel.py:101 +msgid "Templates" +msgstr "Šablony" + +#: panels/templates/panel.py:106 +#, python-format +msgid "Templates (%(num_templates)s rendered)" +msgstr "Šablony (renderovaných: %(num_templates)s)" + +#: panels/templates/panel.py:195 +msgid "No origin" +msgstr "Zdroj chybí" + +#: panels/timer.py:27 #, python-format msgid "CPU: %(cum)0.2fms (%(total)0.2fms)" msgstr "CPU: %(cum)0.2fms (%(total)0.2fms)" -#: panels/timer.py:28 +#: panels/timer.py:32 #, python-format msgid "Total: %0.2fms" msgstr "Celkem: %0.2fms" -#: panels/timer.py:34 templates/debug_toolbar/panels/logging.html:7 +#: panels/timer.py:38 templates/debug_toolbar/panels/history.html:9 #: templates/debug_toolbar/panels/sql_explain.html:11 #: templates/debug_toolbar/panels/sql_profile.html:12 #: templates/debug_toolbar/panels/sql_select.html:11 msgid "Time" msgstr "Čas" -#: panels/timer.py:42 +#: panels/timer.py:46 msgid "User CPU time" msgstr "Uživatelský čas CPU" -#: panels/timer.py:42 +#: panels/timer.py:46 #, python-format msgid "%(utime)0.3f msec" msgstr "%(utime)0.3f msec" -#: panels/timer.py:43 +#: panels/timer.py:47 msgid "System CPU time" msgstr "Systémový čas CPU" -#: panels/timer.py:43 +#: panels/timer.py:47 #, python-format msgid "%(stime)0.3f msec" msgstr "%(stime)0.3f msec" -#: panels/timer.py:44 +#: panels/timer.py:48 msgid "Total CPU time" msgstr "Celkový čas CPU" -#: panels/timer.py:44 +#: panels/timer.py:48 #, python-format msgid "%(total)0.3f msec" msgstr "%(total)0.3f msec" -#: panels/timer.py:45 +#: panels/timer.py:49 msgid "Elapsed time" msgstr "Uplynulý čas" -#: panels/timer.py:45 +#: panels/timer.py:49 #, python-format msgid "%(total_time)0.3f msec" msgstr "%(total_time)0.3f msec" -#: panels/timer.py:46 +#: panels/timer.py:51 msgid "Context switches" msgstr "Přepnutí kontextu" -#: panels/timer.py:46 +#: panels/timer.py:52 #, python-format msgid "%(vcsw)d voluntary, %(ivcsw)d involuntary" msgstr "%(vcsw)d dobrovolně, %(ivcsw)d nedobrovolně" -#: panels/versions.py:25 +#: panels/versions.py:19 msgid "Versions" msgstr "Verze" -#: panels/sql/panel.py:22 -msgid "Autocommit" -msgstr "Autocommit" - -#: panels/sql/panel.py:23 -msgid "Read uncommitted" -msgstr "Read uncommitted" - -#: panels/sql/panel.py:24 -msgid "Read committed" -msgstr "Read committed" - -#: panels/sql/panel.py:25 -msgid "Repeatable read" -msgstr "Repeatable read" - -#: panels/sql/panel.py:26 -msgid "Serializable" -msgstr "Serializable" - -#: panels/sql/panel.py:37 -msgid "Idle" -msgstr "V klidu (idle)" - -#: panels/sql/panel.py:38 -msgid "Active" -msgstr "Aktivní" - -#: panels/sql/panel.py:39 -msgid "In transaction" -msgstr "Uvnitř transakce" - -#: panels/sql/panel.py:40 -msgid "In error" -msgstr "V chybovém stavu" - -#: panels/sql/panel.py:41 -msgid "Unknown" -msgstr "Neznámé" - -#: panels/sql/panel.py:105 -msgid "SQL" -msgstr "SQL" - -#: panels/templates/panel.py:141 -msgid "Templates" -msgstr "Šablony" - -#: panels/templates/panel.py:146 -#, python-format -msgid "Templates (%(num_templates)s rendered)" -msgstr "Šablony (renderovaných: %(num_templates)s)" - -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide toolbar" msgstr "Skrýt lištu" -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide" msgstr "Skrýt" -#: templates/debug_toolbar/base.html:25 +#: templates/debug_toolbar/base.html:25 templates/debug_toolbar/base.html:26 +msgid "Toggle Theme" +msgstr "" + +#: templates/debug_toolbar/base.html:35 +msgid "Show toolbar" +msgstr "Zobrazit lištu" + +#: templates/debug_toolbar/includes/panel_button.html:4 msgid "Disable for next and successive requests" msgstr "Vypnout pro následné požadavky" -#: templates/debug_toolbar/base.html:25 +#: templates/debug_toolbar/includes/panel_button.html:4 msgid "Enable for next and successive requests" msgstr "Zapnout pro následné požadavky" -#: templates/debug_toolbar/base.html:47 -msgid "Show toolbar" -msgstr "Zobrazit lištu" - -#: templates/debug_toolbar/base.html:53 -msgid "Close" -msgstr "Zavřít" - -#: templates/debug_toolbar/redirect.html:8 -msgid "Location:" -msgstr "Adresa:" +#: templates/debug_toolbar/panels/alerts.html:4 +msgid "Alerts found" +msgstr "" -#: templates/debug_toolbar/redirect.html:10 -msgid "" -"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." -msgstr "Aplikace Django Debug Toolbar zachytila přesměrování na výše uvedenou adresu URL za účelem ladicího zobrazení. Chcete-li přesměrování dokončit, klepněte na odkaz výše." +#: templates/debug_toolbar/panels/alerts.html:11 +msgid "No alerts found" +msgstr "" #: templates/debug_toolbar/panels/cache.html:2 msgid "Summary" @@ -317,7 +350,7 @@ msgid "Calls" msgstr "Volání" #: templates/debug_toolbar/panels/cache.html:43 -#: templates/debug_toolbar/panels/sql.html:20 +#: templates/debug_toolbar/panels/sql.html:36 msgid "Time (ms)" msgstr "Čas (ms)" @@ -352,10 +385,8 @@ msgstr "Klíč" #: templates/debug_toolbar/panels/headers.html:9 #: templates/debug_toolbar/panels/headers.html:28 #: templates/debug_toolbar/panels/headers.html:49 -#: templates/debug_toolbar/panels/request.html:33 -#: templates/debug_toolbar/panels/request.html:59 -#: templates/debug_toolbar/panels/request.html:85 -#: templates/debug_toolbar/panels/request.html:110 +#: templates/debug_toolbar/panels/history_tr.html:23 +#: templates/debug_toolbar/panels/request_variables.html:12 #: templates/debug_toolbar/panels/settings.html:6 #: templates/debug_toolbar/panels/timer.html:11 msgid "Value" @@ -375,26 +406,32 @@ msgid "" "significant subset is shown below." msgstr "Níže je zobrazena pouze podstatná část proměnných prostředí, protože WSGI je dědí od serveru." -#: templates/debug_toolbar/panels/logging.html:6 -msgid "Level" -msgstr "Úroveň" +#: templates/debug_toolbar/panels/history.html:10 +msgid "Method" +msgstr "Metoda" -#: templates/debug_toolbar/panels/logging.html:8 -msgid "Channel" -msgstr "Kanál" +#: templates/debug_toolbar/panels/history.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:43 +msgid "Path" +msgstr "Cesta" -#: templates/debug_toolbar/panels/logging.html:9 -msgid "Message" -msgstr "Zpráva" +#: templates/debug_toolbar/panels/history.html:12 +msgid "Request Variables" +msgstr "Proměnné požadavku" -#: templates/debug_toolbar/panels/logging.html:10 -#: templates/debug_toolbar/panels/staticfiles.html:45 -msgid "Location" -msgstr "Adresa" +#: templates/debug_toolbar/panels/history.html:13 +msgid "Status" +msgstr "Stav" + +#: templates/debug_toolbar/panels/history.html:14 +#: templates/debug_toolbar/panels/sql.html:37 +msgid "Action" +msgstr "Akce" -#: templates/debug_toolbar/panels/logging.html:26 -msgid "No messages logged" -msgstr "Nebyly protokolovány žádné zprávy." +#: templates/debug_toolbar/panels/history_tr.html:22 +#: templates/debug_toolbar/panels/request_variables.html:11 +msgid "Variable" +msgstr "Proměnná" #: templates/debug_toolbar/panels/profiling.html:5 msgid "Call" @@ -433,38 +470,31 @@ msgstr "Název URL" msgid "Cookies" msgstr "Soubory cookie" -#: templates/debug_toolbar/panels/request.html:32 -#: templates/debug_toolbar/panels/request.html:58 -#: templates/debug_toolbar/panels/request.html:84 -#: templates/debug_toolbar/panels/request.html:109 -msgid "Variable" -msgstr "Proměnná" - -#: templates/debug_toolbar/panels/request.html:46 +#: templates/debug_toolbar/panels/request.html:27 msgid "No cookies" msgstr "Žádné soubory cookie" -#: templates/debug_toolbar/panels/request.html:50 +#: templates/debug_toolbar/panels/request.html:31 msgid "Session data" msgstr "Data sezení" -#: templates/debug_toolbar/panels/request.html:72 +#: templates/debug_toolbar/panels/request.html:34 msgid "No session data" msgstr "Žádná data sezení" -#: templates/debug_toolbar/panels/request.html:76 +#: templates/debug_toolbar/panels/request.html:38 msgid "GET data" msgstr "Data typu GET" -#: templates/debug_toolbar/panels/request.html:98 +#: templates/debug_toolbar/panels/request.html:41 msgid "No GET data" msgstr "Žádná data typu GET" -#: templates/debug_toolbar/panels/request.html:102 +#: templates/debug_toolbar/panels/request.html:45 msgid "POST data" msgstr "Data typu POST" -#: templates/debug_toolbar/panels/request.html:123 +#: templates/debug_toolbar/panels/request.html:48 msgid "No POST data" msgstr "Žádná data typu POST" @@ -477,61 +507,71 @@ msgid "Signal" msgstr "Signál" #: templates/debug_toolbar/panels/signals.html:6 -msgid "Providing" -msgstr "Poskytuje" - -#: templates/debug_toolbar/panels/signals.html:7 msgid "Receivers" msgstr "Příjemci" -#: templates/debug_toolbar/panels/sql.html:7 +#: templates/debug_toolbar/panels/sql.html:6 #, python-format msgid "%(num)s query" msgid_plural "%(num)s queries" msgstr[0] "%(num)s dotaz" msgstr[1] "%(num)s dotazy" msgstr[2] "%(num)s dotazů" +msgstr[3] "%(num)s dotazů" + +#: templates/debug_toolbar/panels/sql.html:8 +#, python-format +msgid "" +"including %(count)s similar" +msgstr "včetně %(count)s podobných" + +#: templates/debug_toolbar/panels/sql.html:12 +#, python-format +msgid "" +"and %(dupes)s duplicates" +msgstr "a %(dupes)s duplicitních" -#: templates/debug_toolbar/panels/sql.html:18 +#: templates/debug_toolbar/panels/sql.html:34 msgid "Query" msgstr "Dotaz" -#: templates/debug_toolbar/panels/sql.html:19 +#: templates/debug_toolbar/panels/sql.html:35 #: templates/debug_toolbar/panels/timer.html:36 msgid "Timeline" msgstr "Časová osa" -#: templates/debug_toolbar/panels/sql.html:21 -msgid "Action" -msgstr "Akce" +#: templates/debug_toolbar/panels/sql.html:52 +#, python-format +msgid "%(count)s similar queries." +msgstr "%(count)s podobných dotazů." + +#: templates/debug_toolbar/panels/sql.html:58 +#, python-format +msgid "Duplicated %(dupes)s times." +msgstr "" -#: templates/debug_toolbar/panels/sql.html:64 +#: templates/debug_toolbar/panels/sql.html:95 msgid "Connection:" msgstr "Spojení:" -#: templates/debug_toolbar/panels/sql.html:66 +#: templates/debug_toolbar/panels/sql.html:97 msgid "Isolation level:" msgstr "Úroveň izolace:" -#: templates/debug_toolbar/panels/sql.html:69 +#: templates/debug_toolbar/panels/sql.html:100 msgid "Transaction status:" msgstr "Stav transakce:" -#: templates/debug_toolbar/panels/sql.html:83 +#: templates/debug_toolbar/panels/sql.html:114 msgid "(unknown)" msgstr "(neznámé)" -#: templates/debug_toolbar/panels/sql.html:92 +#: templates/debug_toolbar/panels/sql.html:123 msgid "No SQL queries were recorded during this request." msgstr "Pro tento požadavek nebyl zaznamenán žádný dotaz SQL." -#: templates/debug_toolbar/panels/sql_explain.html:3 -#: templates/debug_toolbar/panels/sql_profile.html:3 -#: templates/debug_toolbar/panels/sql_select.html:3 -#: templates/debug_toolbar/panels/template_source.html:3 -msgid "Back" -msgstr "Zpět" - #: templates/debug_toolbar/panels/sql_explain.html:4 msgid "SQL explained" msgstr "Vysvětlené SQL" @@ -564,52 +604,56 @@ msgstr "Vybrané SQL" msgid "Empty set" msgstr "Prázdná sada" -#: templates/debug_toolbar/panels/staticfiles.html:4 +#: templates/debug_toolbar/panels/staticfiles.html:3 msgid "Static file path" msgid_plural "Static file paths" msgstr[0] "Cesta ke statickým souborům" msgstr[1] "Cesty ke statickým souborům" msgstr[2] "Cesty ke statickým souborům" +msgstr[3] "Cesty ke statickým souborům" -#: templates/debug_toolbar/panels/staticfiles.html:8 +#: templates/debug_toolbar/panels/staticfiles.html:7 #, python-format msgid "(prefix %(prefix)s)" msgstr "(prefix %(prefix)s)" -#: templates/debug_toolbar/panels/staticfiles.html:12 -#: templates/debug_toolbar/panels/staticfiles.html:23 -#: templates/debug_toolbar/panels/staticfiles.html:35 +#: templates/debug_toolbar/panels/staticfiles.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:22 +#: templates/debug_toolbar/panels/staticfiles.html:34 #: templates/debug_toolbar/panels/templates.html:10 -#: templates/debug_toolbar/panels/templates.html:28 -#: templates/debug_toolbar/panels/templates.html:43 +#: templates/debug_toolbar/panels/templates.html:30 +#: templates/debug_toolbar/panels/templates.html:47 msgid "None" msgstr "Žádné" -#: templates/debug_toolbar/panels/staticfiles.html:15 +#: templates/debug_toolbar/panels/staticfiles.html:14 msgid "Static file app" msgid_plural "Static file apps" msgstr[0] "Aplikace se statickými soubory" msgstr[1] "Aplikace se statickými soubory" msgstr[2] "Aplikace se statickými soubory" +msgstr[3] "Aplikace se statickými soubory" -#: templates/debug_toolbar/panels/staticfiles.html:26 +#: templates/debug_toolbar/panels/staticfiles.html:25 msgid "Static file" msgid_plural "Static files" msgstr[0] "Statický soubor" msgstr[1] "Statické soubory" msgstr[2] "Statické soubory" +msgstr[3] "Statické soubory" -#: templates/debug_toolbar/panels/staticfiles.html:40 +#: templates/debug_toolbar/panels/staticfiles.html:39 #, python-format msgid "%(payload_count)s file" msgid_plural "%(payload_count)s files" msgstr[0] "%(payload_count)s soubor" msgstr[1] "%(payload_count)s soubory" msgstr[2] "%(payload_count)s souborů" +msgstr[3] "%(payload_count)s souborů" #: templates/debug_toolbar/panels/staticfiles.html:44 -msgid "Path" -msgstr "Cesta" +msgid "Location" +msgstr "Adresa" #: templates/debug_toolbar/panels/template_source.html:4 msgid "Template source:" @@ -621,6 +665,7 @@ msgid_plural "Template paths" msgstr[0] "Cesta k šabloně" msgstr[1] "Cesty k šablonám" msgstr[2] "Cesty k šablonám" +msgstr[3] "Cesty k šablonám" #: templates/debug_toolbar/panels/templates.html:13 msgid "Template" @@ -628,18 +673,20 @@ msgid_plural "Templates" msgstr[0] "Šablona" msgstr[1] "Šablony" msgstr[2] "Šablony" +msgstr[3] "Šablony" -#: templates/debug_toolbar/panels/templates.html:21 -#: templates/debug_toolbar/panels/templates.html:37 +#: templates/debug_toolbar/panels/templates.html:22 +#: templates/debug_toolbar/panels/templates.html:40 msgid "Toggle context" msgstr "Zap./vyp. kontext" -#: templates/debug_toolbar/panels/templates.html:31 +#: templates/debug_toolbar/panels/templates.html:33 msgid "Context processor" msgid_plural "Context processors" msgstr[0] "Procesor kontextu" msgstr[1] "Procesory kontextu" msgstr[2] "Procesory kontextu" +msgstr[3] "Procesory kontextu" #: templates/debug_toolbar/panels/timer.html:2 msgid "Resource usage" @@ -661,10 +708,31 @@ msgstr "Atribut" msgid "Milliseconds since navigation start (+length)" msgstr "Milisekund od začátku navigace (+délka)" -#: templates/debug_toolbar/panels/versions.html:5 +#: templates/debug_toolbar/panels/versions.html:10 +msgid "Package" +msgstr "Balíček" + +#: templates/debug_toolbar/panels/versions.html:11 msgid "Name" msgstr "Název" -#: templates/debug_toolbar/panels/versions.html:6 +#: templates/debug_toolbar/panels/versions.html:12 msgid "Version" msgstr "Verze" + +#: templates/debug_toolbar/redirect.html:10 +msgid "Location:" +msgstr "Adresa:" + +#: templates/debug_toolbar/redirect.html:12 +msgid "" +"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." +msgstr "Aplikace Django Debug Toolbar zachytila přesměrování na výše uvedenou adresu URL za účelem ladicího zobrazení. Chcete-li přesměrování dokončit, klepněte na odkaz výše." + +#: views.py:16 +msgid "" +"Data for this panel isn't available anymore. Please reload the page and " +"retry." +msgstr "Data pro tento panel již nejsou k dispozici. Obnovte stránku a zkuste to znova." diff --git a/debug_toolbar/locale/de/LC_MESSAGES/django.mo b/debug_toolbar/locale/de/LC_MESSAGES/django.mo index b3d52c204..f62a4baf6 100644 Binary files a/debug_toolbar/locale/de/LC_MESSAGES/django.mo and b/debug_toolbar/locale/de/LC_MESSAGES/django.mo differ diff --git a/debug_toolbar/locale/de/LC_MESSAGES/django.po b/debug_toolbar/locale/de/LC_MESSAGES/django.po index 6ba6974f3..18a6be6a8 100644 --- a/debug_toolbar/locale/de/LC_MESSAGES/django.po +++ b/debug_toolbar/locale/de/LC_MESSAGES/django.po @@ -1,71 +1,80 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# -# +# +# # Translators: -# Jannis Leidel , 2012-2013 +# Jannis Leidel , 2012-2014,2021 +# Matthias Kestenholz , 2021 +# Tim Schilling, 2021 msgid "" msgstr "" "Project-Id-Version: Django Debug Toolbar\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-04-25 21:52+0200\n" -"PO-Revision-Date: 2014-04-25 19:53+0000\n" -"Last-Translator: Aymeric Augustin \n" -"Language-Team: German (http://www.transifex.com/projects/p/django-debug-toolbar/language/de/)\n" +"POT-Creation-Date: 2024-08-06 07:12-0500\n" +"PO-Revision-Date: 2010-11-30 00:00+0000\n" +"Last-Translator: Tim Schilling, 2021\n" +"Language-Team: German (http://app.transifex.com/django-debug-toolbar/django-debug-toolbar/language/de/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: de\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: apps.py:11 +#: apps.py:18 msgid "Debug Toolbar" +msgstr "Debug Toolbar" + +#: panels/alerts.py:67 +#, python-brace-format +msgid "" +"Form with id \"{form_id}\" contains file input, but does not have the " +"attribute enctype=\"multipart/form-data\"." msgstr "" -#: views.py:14 +#: panels/alerts.py:70 msgid "" -"Data for this panel isn't available anymore. Please reload the page and " -"retry." -msgstr "Die Daten für dieses Panel sind nicht mehr verfügbar. Bitte laden Sie die Seite neu." +"Form contains file input, but does not have the attribute " +"enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:73 +#, python-brace-format +msgid "" +"Input element references form with id \"{form_id}\", but the form does not " +"have the attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:77 +msgid "Alerts" +msgstr "" -#: panels/cache.py:191 +#: panels/cache.py:168 msgid "Cache" msgstr "Cache" -#: panels/cache.py:196 +#: panels/cache.py:174 #, python-format msgid "%(cache_calls)d call in %(time).2fms" msgid_plural "%(cache_calls)d calls in %(time).2fms" msgstr[0] "%(cache_calls)d Abfrage in %(time).2fms" msgstr[1] "%(cache_calls)d Abfragen in %(time).2fms" -#: panels/cache.py:204 +#: panels/cache.py:183 #, python-format msgid "Cache calls from %(count)d backend" msgid_plural "Cache calls from %(count)d backends" msgstr[0] "Cache-Aufrufe von %(count)d Backend" msgstr[1] "Cache-Aufrufe von %(count)d Backends" -#: panels/headers.py:35 +#: panels/headers.py:31 msgid "Headers" msgstr "Header" -#: panels/logging.py:64 -msgid "Logging" -msgstr "Logging" +#: panels/history/panel.py:19 panels/history/panel.py:20 +msgid "History" +msgstr "Geschichte" -#: panels/logging.py:70 -#, python-format -msgid "%(count)s message" -msgid_plural "%(count)s messages" -msgstr[0] "%(count)s Eintrag" -msgstr[1] "%(count)s Einträge" - -#: panels/logging.py:73 -msgid "Log messages" -msgstr "Logeinträge" - -#: panels/profiling.py:127 +#: panels/profiling.py:140 msgid "Profiling" msgstr "Profiling" @@ -73,214 +82,229 @@ msgstr "Profiling" msgid "Intercept redirects" msgstr "Umleitungen abfangen" -#: panels/request.py:18 +#: panels/request.py:16 msgid "Request" msgstr "Anfrage" -#: panels/request.py:35 +#: panels/request.py:38 msgid "" msgstr "" -#: panels/request.py:47 +#: panels/request.py:55 msgid "" msgstr "" -#: panels/settings.py:20 +#: panels/settings.py:17 msgid "Settings" msgstr "Einstellungen" -#: panels/settings.py:23 +#: panels/settings.py:20 #, python-format -msgid "Settings from %s" -msgstr "Einstellungen von %s" +msgid "Settings from %s" +msgstr "Einstellungen von %s" -#: panels/signals.py:45 +#: panels/signals.py:57 #, python-format msgid "%(num_receivers)d receiver of 1 signal" msgid_plural "%(num_receivers)d receivers of 1 signal" msgstr[0] "%(num_receivers)d Empfänger von einem Signal" msgstr[1] "%(num_receivers)d Empfänger von einem Signal" -#: panels/signals.py:48 +#: panels/signals.py:62 #, python-format msgid "%(num_receivers)d receiver of %(num_signals)d signals" msgid_plural "%(num_receivers)d receivers of %(num_signals)d signals" msgstr[0] "%(num_receivers)d Empfänger von %(num_signals)d Signalen" msgstr[1] "%(num_receivers)d Empfänger von %(num_signals)d Signalen" -#: panels/signals.py:53 +#: panels/signals.py:67 msgid "Signals" msgstr "Signale" -#: panels/staticfiles.py:89 +#: panels/sql/panel.py:30 panels/sql/panel.py:41 +msgid "Read uncommitted" +msgstr "Read uncommitted" + +#: panels/sql/panel.py:31 panels/sql/panel.py:43 +msgid "Read committed" +msgstr "Read committed" + +#: panels/sql/panel.py:32 panels/sql/panel.py:45 +msgid "Repeatable read" +msgstr "Repeatable read" + +#: panels/sql/panel.py:33 panels/sql/panel.py:47 +msgid "Serializable" +msgstr "Serializable" + +#: panels/sql/panel.py:39 +msgid "Autocommit" +msgstr "Autocommit" + +#: panels/sql/panel.py:61 panels/sql/panel.py:71 +msgid "Idle" +msgstr "Wartet" + +#: panels/sql/panel.py:62 panels/sql/panel.py:72 +msgid "Active" +msgstr "Aktiv" + +#: panels/sql/panel.py:63 panels/sql/panel.py:73 +msgid "In transaction" +msgstr "In einer Transaktion" + +#: panels/sql/panel.py:64 panels/sql/panel.py:74 +msgid "In error" +msgstr "Fehler" + +#: panels/sql/panel.py:65 panels/sql/panel.py:75 +msgid "Unknown" +msgstr "Unbekannt" + +#: panels/sql/panel.py:162 +msgid "SQL" +msgstr "SQL" + +#: panels/sql/panel.py:168 +#, python-format +msgid "%(query_count)d query in %(sql_time).2fms" +msgid_plural "%(query_count)d queries in %(sql_time).2fms" +msgstr[0] "%(query_count)d Abfrage in %(sql_time).2f ms" +msgstr[1] "%(query_count)d Abfragen in %(sql_time).2f ms" + +#: panels/sql/panel.py:180 +#, python-format +msgid "SQL queries from %(count)d connection" +msgid_plural "SQL queries from %(count)d connections" +msgstr[0] "SQL-Abfragen von %(count)d Verbindung" +msgstr[1] "SQL-Abfragen von %(count)d Verbindungen" + +#: panels/staticfiles.py:82 #, python-format msgid "Static files (%(num_found)s found, %(num_used)s used)" msgstr "Statische Dateien (%(num_found)s gefunden, %(num_used)s benutzt)" -#: panels/staticfiles.py:107 +#: panels/staticfiles.py:103 msgid "Static files" msgstr "Statische Dateien" -#: panels/staticfiles.py:112 +#: panels/staticfiles.py:109 #, python-format msgid "%(num_used)s file used" msgid_plural "%(num_used)s files used" msgstr[0] "%(num_used)s Datei benutzt" msgstr[1] "%(num_used)s Dateien benutzt" -#: panels/timer.py:23 +#: panels/templates/panel.py:101 +msgid "Templates" +msgstr "Templates" + +#: panels/templates/panel.py:106 +#, python-format +msgid "Templates (%(num_templates)s rendered)" +msgstr "Templates (%(num_templates)s gerendert)" + +#: panels/templates/panel.py:195 +msgid "No origin" +msgstr "Kein Ursprung" + +#: panels/timer.py:27 #, python-format msgid "CPU: %(cum)0.2fms (%(total)0.2fms)" msgstr "CPU: %(cum)0.2fms (%(total)0.2fms)" -#: panels/timer.py:28 +#: panels/timer.py:32 #, python-format msgid "Total: %0.2fms" msgstr "Gesamt: %0.2fms" -#: panels/timer.py:34 templates/debug_toolbar/panels/logging.html:7 +#: panels/timer.py:38 templates/debug_toolbar/panels/history.html:9 #: templates/debug_toolbar/panels/sql_explain.html:11 #: templates/debug_toolbar/panels/sql_profile.html:12 #: templates/debug_toolbar/panels/sql_select.html:11 msgid "Time" msgstr "Zeit" -#: panels/timer.py:42 +#: panels/timer.py:46 msgid "User CPU time" msgstr "CPU-Zeit Benutzer" -#: panels/timer.py:42 +#: panels/timer.py:46 #, python-format msgid "%(utime)0.3f msec" msgstr "%(utime)0.3f ms" -#: panels/timer.py:43 +#: panels/timer.py:47 msgid "System CPU time" msgstr "CPU-Zeit System" -#: panels/timer.py:43 +#: panels/timer.py:47 #, python-format msgid "%(stime)0.3f msec" msgstr "%(stime)0.3f ms" -#: panels/timer.py:44 +#: panels/timer.py:48 msgid "Total CPU time" msgstr "CPU-Zeit gesamt" -#: panels/timer.py:44 +#: panels/timer.py:48 #, python-format msgid "%(total)0.3f msec" msgstr "%(total)0.3f ms" -#: panels/timer.py:45 +#: panels/timer.py:49 msgid "Elapsed time" msgstr "Verstrichene Zeit" -#: panels/timer.py:45 +#: panels/timer.py:49 #, python-format msgid "%(total_time)0.3f msec" msgstr "%(total_time)0.3f ms" -#: panels/timer.py:46 +#: panels/timer.py:51 msgid "Context switches" msgstr "Kontextwechsel" -#: panels/timer.py:46 +#: panels/timer.py:52 #, python-format msgid "%(vcsw)d voluntary, %(ivcsw)d involuntary" msgstr "%(vcsw)d freiwillig, %(ivcsw)d unfreiwillig" -#: panels/versions.py:25 +#: panels/versions.py:19 msgid "Versions" msgstr "Versionen" -#: panels/sql/panel.py:22 -msgid "Autocommit" -msgstr "Autocommit" - -#: panels/sql/panel.py:23 -msgid "Read uncommitted" -msgstr "Lesend, nicht ausgeführt" - -#: panels/sql/panel.py:24 -msgid "Read committed" -msgstr "Lesend, ausgeführt" - -#: panels/sql/panel.py:25 -msgid "Repeatable read" -msgstr "Wiederholtes Lesen" - -#: panels/sql/panel.py:26 -msgid "Serializable" -msgstr "Variable" - -#: panels/sql/panel.py:37 -msgid "Idle" -msgstr "Wartet" - -#: panels/sql/panel.py:38 -msgid "Active" -msgstr "Aktiv" - -#: panels/sql/panel.py:39 -msgid "In transaction" -msgstr "In einer Transaktion" - -#: panels/sql/panel.py:40 -msgid "In error" -msgstr "Fehler" - -#: panels/sql/panel.py:41 -msgid "Unknown" -msgstr "Unbekannt" - -#: panels/sql/panel.py:105 -msgid "SQL" -msgstr "SQL" - -#: panels/templates/panel.py:141 -msgid "Templates" -msgstr "Templates" - -#: panels/templates/panel.py:146 -#, python-format -msgid "Templates (%(num_templates)s rendered)" -msgstr "Templates (%(num_templates)s gerendert)" - -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide toolbar" msgstr "Toolbar ausblenden" -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide" msgstr "Ausblenden" -#: templates/debug_toolbar/base.html:25 +#: templates/debug_toolbar/base.html:25 templates/debug_toolbar/base.html:26 +msgid "Toggle Theme" +msgstr "" + +#: templates/debug_toolbar/base.html:35 +msgid "Show toolbar" +msgstr "Toolbar einblenden" + +#: templates/debug_toolbar/includes/panel_button.html:4 msgid "Disable for next and successive requests" msgstr "Für nächste und die darauffolgenden Anfragen deaktivieren" -#: templates/debug_toolbar/base.html:25 +#: templates/debug_toolbar/includes/panel_button.html:4 msgid "Enable for next and successive requests" msgstr "Für nächste und die darauffolgenden Anfragen aktivieren" -#: templates/debug_toolbar/base.html:47 -msgid "Show toolbar" -msgstr "Toolbar einblenden" - -#: templates/debug_toolbar/base.html:53 -msgid "Close" -msgstr "Schließen" - -#: templates/debug_toolbar/redirect.html:8 -msgid "Location:" -msgstr "Ziel:" +#: templates/debug_toolbar/panels/alerts.html:4 +msgid "Alerts found" +msgstr "" -#: templates/debug_toolbar/redirect.html:10 -msgid "" -"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." -msgstr "Die Django Debug Toolbar hat eine Weiterleitung an die obenstehende URL zur weiteren Überprüfung abgefangen. Klicken Sie den Link, um wie gewohnt weitergeleitet zu werden." +#: templates/debug_toolbar/panels/alerts.html:11 +msgid "No alerts found" +msgstr "" #: templates/debug_toolbar/panels/cache.html:2 msgid "Summary" @@ -311,7 +335,7 @@ msgid "Calls" msgstr "Aufrufe" #: templates/debug_toolbar/panels/cache.html:43 -#: templates/debug_toolbar/panels/sql.html:20 +#: templates/debug_toolbar/panels/sql.html:36 msgid "Time (ms)" msgstr "Zeit (ms)" @@ -341,15 +365,13 @@ msgstr "Anfrage-Header" #: templates/debug_toolbar/panels/headers.html:27 #: templates/debug_toolbar/panels/headers.html:48 msgid "Key" -msgstr "Name" +msgstr "Schlüssel" #: templates/debug_toolbar/panels/headers.html:9 #: templates/debug_toolbar/panels/headers.html:28 #: templates/debug_toolbar/panels/headers.html:49 -#: templates/debug_toolbar/panels/request.html:33 -#: templates/debug_toolbar/panels/request.html:59 -#: templates/debug_toolbar/panels/request.html:85 -#: templates/debug_toolbar/panels/request.html:110 +#: templates/debug_toolbar/panels/history_tr.html:23 +#: templates/debug_toolbar/panels/request_variables.html:12 #: templates/debug_toolbar/panels/settings.html:6 #: templates/debug_toolbar/panels/timer.html:11 msgid "Value" @@ -369,26 +391,32 @@ msgid "" "significant subset is shown below." msgstr "Da sich die WSGI-Umgebung von der Umgebung des Servers ableitet, wird nur eine notwendige Teilmenge dargestellt." -#: templates/debug_toolbar/panels/logging.html:6 -msgid "Level" -msgstr "Level" +#: templates/debug_toolbar/panels/history.html:10 +msgid "Method" +msgstr "Methode" -#: templates/debug_toolbar/panels/logging.html:8 -msgid "Channel" -msgstr "Kanal" +#: templates/debug_toolbar/panels/history.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:43 +msgid "Path" +msgstr "Pfad" -#: templates/debug_toolbar/panels/logging.html:9 -msgid "Message" -msgstr "Eintrag" +#: templates/debug_toolbar/panels/history.html:12 +msgid "Request Variables" +msgstr "Anfrage-Variablen" -#: templates/debug_toolbar/panels/logging.html:10 -#: templates/debug_toolbar/panels/staticfiles.html:45 -msgid "Location" -msgstr "Ort" +#: templates/debug_toolbar/panels/history.html:13 +msgid "Status" +msgstr "Status" -#: templates/debug_toolbar/panels/logging.html:26 -msgid "No messages logged" -msgstr "Keine Logbucheinträge vorhanden" +#: templates/debug_toolbar/panels/history.html:14 +#: templates/debug_toolbar/panels/sql.html:37 +msgid "Action" +msgstr "Aktion" + +#: templates/debug_toolbar/panels/history_tr.html:22 +#: templates/debug_toolbar/panels/request_variables.html:11 +msgid "Variable" +msgstr "Variable" #: templates/debug_toolbar/panels/profiling.html:5 msgid "Call" @@ -427,38 +455,31 @@ msgstr "URL-Name" msgid "Cookies" msgstr "Cookies" -#: templates/debug_toolbar/panels/request.html:32 -#: templates/debug_toolbar/panels/request.html:58 -#: templates/debug_toolbar/panels/request.html:84 -#: templates/debug_toolbar/panels/request.html:109 -msgid "Variable" -msgstr "Variable" - -#: templates/debug_toolbar/panels/request.html:46 +#: templates/debug_toolbar/panels/request.html:27 msgid "No cookies" msgstr "Keine Cookies" -#: templates/debug_toolbar/panels/request.html:50 +#: templates/debug_toolbar/panels/request.html:31 msgid "Session data" msgstr "Sitzungsdaten" -#: templates/debug_toolbar/panels/request.html:72 +#: templates/debug_toolbar/panels/request.html:34 msgid "No session data" msgstr "Keine Sitzungsdaten" -#: templates/debug_toolbar/panels/request.html:76 +#: templates/debug_toolbar/panels/request.html:38 msgid "GET data" msgstr "GET-Daten" -#: templates/debug_toolbar/panels/request.html:98 +#: templates/debug_toolbar/panels/request.html:41 msgid "No GET data" msgstr "Keine GET-Daten" -#: templates/debug_toolbar/panels/request.html:102 +#: templates/debug_toolbar/panels/request.html:45 msgid "POST data" msgstr "POST-Daten" -#: templates/debug_toolbar/panels/request.html:123 +#: templates/debug_toolbar/panels/request.html:48 msgid "No POST data" msgstr "Keine POST-Daten" @@ -471,60 +492,69 @@ msgid "Signal" msgstr "Signal" #: templates/debug_toolbar/panels/signals.html:6 -msgid "Providing" -msgstr "Stellt bereit" - -#: templates/debug_toolbar/panels/signals.html:7 msgid "Receivers" msgstr "Empfänger" -#: templates/debug_toolbar/panels/sql.html:7 +#: templates/debug_toolbar/panels/sql.html:6 #, python-format msgid "%(num)s query" msgid_plural "%(num)s queries" msgstr[0] "%(num)s Abfrage" msgstr[1] "%(num)s Abfragen" -#: templates/debug_toolbar/panels/sql.html:18 +#: templates/debug_toolbar/panels/sql.html:8 +#, python-format +msgid "" +"including %(count)s similar" +msgstr "inklusive %(count)s ähnlich" + +#: templates/debug_toolbar/panels/sql.html:12 +#, python-format +msgid "" +"and %(dupes)s duplicates" +msgstr "und %(dupes)s dupliziert" + +#: templates/debug_toolbar/panels/sql.html:34 msgid "Query" msgstr "Abfrage" -#: templates/debug_toolbar/panels/sql.html:19 +#: templates/debug_toolbar/panels/sql.html:35 #: templates/debug_toolbar/panels/timer.html:36 msgid "Timeline" msgstr "Verlauf" -#: templates/debug_toolbar/panels/sql.html:21 -msgid "Action" -msgstr "Aktion" +#: templates/debug_toolbar/panels/sql.html:52 +#, python-format +msgid "%(count)s similar queries." +msgstr "%(count)s ähnliche Abfragen." + +#: templates/debug_toolbar/panels/sql.html:58 +#, python-format +msgid "Duplicated %(dupes)s times." +msgstr "%(dupes)s-mal dupliziert." -#: templates/debug_toolbar/panels/sql.html:64 +#: templates/debug_toolbar/panels/sql.html:95 msgid "Connection:" msgstr "Verbindung:" -#: templates/debug_toolbar/panels/sql.html:66 +#: templates/debug_toolbar/panels/sql.html:97 msgid "Isolation level:" msgstr "Isolationsebene:" -#: templates/debug_toolbar/panels/sql.html:69 +#: templates/debug_toolbar/panels/sql.html:100 msgid "Transaction status:" msgstr "Transaktionsstatus:" -#: templates/debug_toolbar/panels/sql.html:83 +#: templates/debug_toolbar/panels/sql.html:114 msgid "(unknown)" msgstr "(unbekannt)" -#: templates/debug_toolbar/panels/sql.html:92 +#: templates/debug_toolbar/panels/sql.html:123 msgid "No SQL queries were recorded during this request." msgstr "Es wurde keine SQL-Abfrage während dieses Vorgangs aufgezeichnet." -#: templates/debug_toolbar/panels/sql_explain.html:3 -#: templates/debug_toolbar/panels/sql_profile.html:3 -#: templates/debug_toolbar/panels/sql_select.html:3 -#: templates/debug_toolbar/panels/template_source.html:3 -msgid "Back" -msgstr "Zurück" - #: templates/debug_toolbar/panels/sql_explain.html:4 msgid "SQL explained" msgstr "SQL erklärt" @@ -557,39 +587,39 @@ msgstr "SQL ausgewählt" msgid "Empty set" msgstr "Leeres Set" -#: templates/debug_toolbar/panels/staticfiles.html:4 +#: templates/debug_toolbar/panels/staticfiles.html:3 msgid "Static file path" msgid_plural "Static file paths" msgstr[0] "Pfad mit statischen Dateien" msgstr[1] "Pfade mit statischen Dateien" -#: templates/debug_toolbar/panels/staticfiles.html:8 +#: templates/debug_toolbar/panels/staticfiles.html:7 #, python-format msgid "(prefix %(prefix)s)" -msgstr "" +msgstr "(Präfix %(prefix)s)" -#: templates/debug_toolbar/panels/staticfiles.html:12 -#: templates/debug_toolbar/panels/staticfiles.html:23 -#: templates/debug_toolbar/panels/staticfiles.html:35 +#: templates/debug_toolbar/panels/staticfiles.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:22 +#: templates/debug_toolbar/panels/staticfiles.html:34 #: templates/debug_toolbar/panels/templates.html:10 -#: templates/debug_toolbar/panels/templates.html:28 -#: templates/debug_toolbar/panels/templates.html:43 +#: templates/debug_toolbar/panels/templates.html:30 +#: templates/debug_toolbar/panels/templates.html:47 msgid "None" msgstr "-" -#: templates/debug_toolbar/panels/staticfiles.html:15 +#: templates/debug_toolbar/panels/staticfiles.html:14 msgid "Static file app" msgid_plural "Static file apps" msgstr[0] "App mit statischen Dateien" msgstr[1] "Apps mit statischen Dateien" -#: templates/debug_toolbar/panels/staticfiles.html:26 +#: templates/debug_toolbar/panels/staticfiles.html:25 msgid "Static file" msgid_plural "Static files" msgstr[0] "Statische Datei" msgstr[1] "Statische Dateien" -#: templates/debug_toolbar/panels/staticfiles.html:40 +#: templates/debug_toolbar/panels/staticfiles.html:39 #, python-format msgid "%(payload_count)s file" msgid_plural "%(payload_count)s files" @@ -597,8 +627,8 @@ msgstr[0] "%(payload_count)s Datei" msgstr[1] "%(payload_count)s Dateien" #: templates/debug_toolbar/panels/staticfiles.html:44 -msgid "Path" -msgstr "Pfad" +msgid "Location" +msgstr "Ort" #: templates/debug_toolbar/panels/template_source.html:4 msgid "Template source:" @@ -616,12 +646,12 @@ msgid_plural "Templates" msgstr[0] "Template" msgstr[1] "Templates" -#: templates/debug_toolbar/panels/templates.html:21 -#: templates/debug_toolbar/panels/templates.html:37 +#: templates/debug_toolbar/panels/templates.html:22 +#: templates/debug_toolbar/panels/templates.html:40 msgid "Toggle context" msgstr "Context zeigen" -#: templates/debug_toolbar/panels/templates.html:31 +#: templates/debug_toolbar/panels/templates.html:33 msgid "Context processor" msgid_plural "Context processors" msgstr[0] "Context-Prozessor" @@ -647,10 +677,31 @@ msgstr "Timing-Attribut" msgid "Milliseconds since navigation start (+length)" msgstr "Millisekunden seit Seitenaufruf (plus Dauer)" -#: templates/debug_toolbar/panels/versions.html:5 +#: templates/debug_toolbar/panels/versions.html:10 +msgid "Package" +msgstr "Paket" + +#: templates/debug_toolbar/panels/versions.html:11 msgid "Name" msgstr "Name" -#: templates/debug_toolbar/panels/versions.html:6 +#: templates/debug_toolbar/panels/versions.html:12 msgid "Version" msgstr "Version" + +#: templates/debug_toolbar/redirect.html:10 +msgid "Location:" +msgstr "Ziel:" + +#: templates/debug_toolbar/redirect.html:12 +msgid "" +"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." +msgstr "Die Django Debug Toolbar hat eine Weiterleitung an die obenstehende URL zur weiteren Überprüfung abgefangen. Klicken Sie den Link, um wie gewohnt weitergeleitet zu werden." + +#: views.py:16 +msgid "" +"Data for this panel isn't available anymore. Please reload the page and " +"retry." +msgstr "Die Daten für dieses Panel sind nicht mehr verfügbar. Bitte laden Sie die Seite neu." diff --git a/debug_toolbar/locale/en/LC_MESSAGES/django.mo b/debug_toolbar/locale/en/LC_MESSAGES/django.mo index 18bc8c95f..a34e2efe8 100644 Binary files a/debug_toolbar/locale/en/LC_MESSAGES/django.mo and b/debug_toolbar/locale/en/LC_MESSAGES/django.mo differ diff --git a/debug_toolbar/locale/en/LC_MESSAGES/django.po b/debug_toolbar/locale/en/LC_MESSAGES/django.po index 9e58f09fd..9dc155bef 100644 --- a/debug_toolbar/locale/en/LC_MESSAGES/django.po +++ b/debug_toolbar/locale/en/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: Django Debug Toolbar\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-09-06 09:19+0200\n" +"POT-Creation-Date: 2024-08-06 07:12-0500\n" "PO-Revision-Date: 2012-03-31 20:10+0000\n" "Last-Translator: \n" "Language-Team: \n" @@ -16,251 +16,290 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -#: apps.py:15 +#: apps.py:18 msgid "Debug Toolbar" msgstr "" -#: panels/cache.py:188 +#: panels/alerts.py:67 +#, python-brace-format +msgid "" +"Form with id \"{form_id}\" contains file input, but does not have the " +"attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:70 +msgid "" +"Form contains file input, but does not have the attribute " +"enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:73 +#, python-brace-format +msgid "" +"Input element references form with id \"{form_id}\", but the form does not " +"have the attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:77 +msgid "Alerts" +msgstr "" + +#: panels/cache.py:168 msgid "Cache" msgstr "" -#: panels/cache.py:193 +#: panels/cache.py:174 #, python-format msgid "%(cache_calls)d call in %(time).2fms" msgid_plural "%(cache_calls)d calls in %(time).2fms" msgstr[0] "" msgstr[1] "" -#: panels/cache.py:201 +#: panels/cache.py:183 #, python-format msgid "Cache calls from %(count)d backend" msgid_plural "Cache calls from %(count)d backends" msgstr[0] "" msgstr[1] "" -#: panels/headers.py:34 +#: panels/headers.py:31 msgid "Headers" msgstr "" -#: panels/logging.py:66 -msgid "Logging" -msgstr "" - -#: panels/logging.py:72 -#, python-format -msgid "%(count)s message" -msgid_plural "%(count)s messages" -msgstr[0] "" -msgstr[1] "" - -#: panels/logging.py:75 -msgid "Log messages" +#: panels/history/panel.py:19 panels/history/panel.py:20 +msgid "History" msgstr "" -#: panels/profiling.py:148 +#: panels/profiling.py:140 msgid "Profiling" msgstr "" -#: panels/redirects.py:16 +#: panels/redirects.py:17 msgid "Intercept redirects" msgstr "" -#: panels/request.py:18 +#: panels/request.py:16 msgid "Request" msgstr "" -#: panels/request.py:35 +#: panels/request.py:38 msgid "" msgstr "" -#: panels/request.py:47 +#: panels/request.py:55 msgid "" msgstr "" -#: panels/settings.py:18 +#: panels/settings.py:17 msgid "Settings" msgstr "" -#: panels/settings.py:21 +#: panels/settings.py:20 #, python-format -msgid "Settings from %s" +msgid "Settings from %s" msgstr "" -#: panels/signals.py:44 +#: panels/signals.py:57 #, python-format msgid "%(num_receivers)d receiver of 1 signal" msgid_plural "%(num_receivers)d receivers of 1 signal" msgstr[0] "" msgstr[1] "" -#: panels/signals.py:47 +#: panels/signals.py:62 #, python-format msgid "%(num_receivers)d receiver of %(num_signals)d signals" msgid_plural "%(num_receivers)d receivers of %(num_signals)d signals" msgstr[0] "" msgstr[1] "" -#: panels/signals.py:52 +#: panels/signals.py:67 msgid "Signals" msgstr "" -#: panels/sql/panel.py:25 -msgid "Autocommit" -msgstr "" - -#: panels/sql/panel.py:26 +#: panels/sql/panel.py:30 panels/sql/panel.py:41 msgid "Read uncommitted" msgstr "" -#: panels/sql/panel.py:27 +#: panels/sql/panel.py:31 panels/sql/panel.py:43 msgid "Read committed" msgstr "" -#: panels/sql/panel.py:28 +#: panels/sql/panel.py:32 panels/sql/panel.py:45 msgid "Repeatable read" msgstr "" -#: panels/sql/panel.py:29 +#: panels/sql/panel.py:33 panels/sql/panel.py:47 msgid "Serializable" msgstr "" -#: panels/sql/panel.py:40 +#: panels/sql/panel.py:39 +msgid "Autocommit" +msgstr "" + +#: panels/sql/panel.py:61 panels/sql/panel.py:71 msgid "Idle" msgstr "" -#: panels/sql/panel.py:41 +#: panels/sql/panel.py:62 panels/sql/panel.py:72 msgid "Active" msgstr "" -#: panels/sql/panel.py:42 +#: panels/sql/panel.py:63 panels/sql/panel.py:73 msgid "In transaction" msgstr "" -#: panels/sql/panel.py:43 +#: panels/sql/panel.py:64 panels/sql/panel.py:74 msgid "In error" msgstr "" -#: panels/sql/panel.py:44 +#: panels/sql/panel.py:65 panels/sql/panel.py:75 msgid "Unknown" msgstr "" -#: panels/sql/panel.py:108 +#: panels/sql/panel.py:162 msgid "SQL" msgstr "" -#: panels/staticfiles.py:88 +#: panels/sql/panel.py:168 +#, python-format +msgid "%(query_count)d query in %(sql_time).2fms" +msgid_plural "%(query_count)d queries in %(sql_time).2fms" +msgstr[0] "" +msgstr[1] "" + +#: panels/sql/panel.py:180 +#, python-format +msgid "SQL queries from %(count)d connection" +msgid_plural "SQL queries from %(count)d connections" +msgstr[0] "" +msgstr[1] "" + +#: panels/staticfiles.py:82 #, python-format msgid "Static files (%(num_found)s found, %(num_used)s used)" msgstr "" -#: panels/staticfiles.py:106 +#: panels/staticfiles.py:103 msgid "Static files" msgstr "" -#: panels/staticfiles.py:111 +#: panels/staticfiles.py:109 #, python-format msgid "%(num_used)s file used" msgid_plural "%(num_used)s files used" msgstr[0] "" msgstr[1] "" -#: panels/templates/panel.py:161 +#: panels/templates/panel.py:101 msgid "Templates" msgstr "" -#: panels/templates/panel.py:166 +#: panels/templates/panel.py:106 #, python-format msgid "Templates (%(num_templates)s rendered)" msgstr "" -#: panels/templates/panel.py:198 +#: panels/templates/panel.py:195 msgid "No origin" msgstr "" -#: panels/timer.py:26 +#: panels/timer.py:27 #, python-format msgid "CPU: %(cum)0.2fms (%(total)0.2fms)" msgstr "" -#: panels/timer.py:31 +#: panels/timer.py:32 #, python-format msgid "Total: %0.2fms" msgstr "" -#: panels/timer.py:37 templates/debug_toolbar/panels/logging.html:7 +#: panels/timer.py:38 templates/debug_toolbar/panels/history.html:9 #: templates/debug_toolbar/panels/sql_explain.html:11 #: templates/debug_toolbar/panels/sql_profile.html:12 #: templates/debug_toolbar/panels/sql_select.html:11 msgid "Time" msgstr "" -#: panels/timer.py:45 +#: panels/timer.py:46 msgid "User CPU time" msgstr "" -#: panels/timer.py:45 +#: panels/timer.py:46 #, python-format msgid "%(utime)0.3f msec" msgstr "" -#: panels/timer.py:46 +#: panels/timer.py:47 msgid "System CPU time" msgstr "" -#: panels/timer.py:46 +#: panels/timer.py:47 #, python-format msgid "%(stime)0.3f msec" msgstr "" -#: panels/timer.py:47 +#: panels/timer.py:48 msgid "Total CPU time" msgstr "" -#: panels/timer.py:47 +#: panels/timer.py:48 #, python-format msgid "%(total)0.3f msec" msgstr "" -#: panels/timer.py:48 +#: panels/timer.py:49 msgid "Elapsed time" msgstr "" -#: panels/timer.py:48 +#: panels/timer.py:49 #, python-format msgid "%(total_time)0.3f msec" msgstr "" -#: panels/timer.py:49 +#: panels/timer.py:51 msgid "Context switches" msgstr "" -#: panels/timer.py:49 +#: panels/timer.py:52 #, python-format msgid "%(vcsw)d voluntary, %(ivcsw)d involuntary" msgstr "" -#: panels/versions.py:20 +#: panels/versions.py:19 msgid "Versions" msgstr "" -#: templates/debug_toolbar/base.html:14 +#: templates/debug_toolbar/base.html:23 msgid "Hide toolbar" msgstr "" -#: templates/debug_toolbar/base.html:14 +#: templates/debug_toolbar/base.html:23 msgid "Hide" msgstr "" -#: templates/debug_toolbar/base.html:20 +#: templates/debug_toolbar/base.html:25 templates/debug_toolbar/base.html:26 +msgid "Toggle Theme" +msgstr "" + +#: templates/debug_toolbar/base.html:35 +msgid "Show toolbar" +msgstr "" + +#: templates/debug_toolbar/includes/panel_button.html:4 msgid "Disable for next and successive requests" msgstr "" -#: templates/debug_toolbar/base.html:20 +#: templates/debug_toolbar/includes/panel_button.html:4 msgid "Enable for next and successive requests" msgstr "" -#: templates/debug_toolbar/base.html:42 -msgid "Show toolbar" +#: templates/debug_toolbar/panels/alerts.html:4 +msgid "Alerts found" +msgstr "" + +#: templates/debug_toolbar/panels/alerts.html:11 +msgid "No alerts found" msgstr "" #: templates/debug_toolbar/panels/cache.html:2 @@ -292,7 +331,7 @@ msgid "Calls" msgstr "" #: templates/debug_toolbar/panels/cache.html:43 -#: templates/debug_toolbar/panels/sql.html:30 +#: templates/debug_toolbar/panels/sql.html:36 msgid "Time (ms)" msgstr "" @@ -327,10 +366,8 @@ msgstr "" #: templates/debug_toolbar/panels/headers.html:9 #: templates/debug_toolbar/panels/headers.html:28 #: templates/debug_toolbar/panels/headers.html:49 -#: templates/debug_toolbar/panels/request.html:33 -#: templates/debug_toolbar/panels/request.html:59 -#: templates/debug_toolbar/panels/request.html:85 -#: templates/debug_toolbar/panels/request.html:110 +#: templates/debug_toolbar/panels/history_tr.html:23 +#: templates/debug_toolbar/panels/request_variables.html:12 #: templates/debug_toolbar/panels/settings.html:6 #: templates/debug_toolbar/panels/timer.html:11 msgid "Value" @@ -350,25 +387,31 @@ msgid "" "significant subset is shown below." msgstr "" -#: templates/debug_toolbar/panels/logging.html:6 -msgid "Level" +#: templates/debug_toolbar/panels/history.html:10 +msgid "Method" msgstr "" -#: templates/debug_toolbar/panels/logging.html:8 -msgid "Channel" +#: templates/debug_toolbar/panels/history.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:43 +msgid "Path" msgstr "" -#: templates/debug_toolbar/panels/logging.html:9 -msgid "Message" +#: templates/debug_toolbar/panels/history.html:12 +msgid "Request Variables" msgstr "" -#: templates/debug_toolbar/panels/logging.html:10 -#: templates/debug_toolbar/panels/staticfiles.html:44 -msgid "Location" +#: templates/debug_toolbar/panels/history.html:13 +msgid "Status" msgstr "" -#: templates/debug_toolbar/panels/logging.html:26 -msgid "No messages logged" +#: templates/debug_toolbar/panels/history.html:14 +#: templates/debug_toolbar/panels/sql.html:37 +msgid "Action" +msgstr "" + +#: templates/debug_toolbar/panels/history_tr.html:22 +#: templates/debug_toolbar/panels/request_variables.html:11 +msgid "Variable" msgstr "" #: templates/debug_toolbar/panels/profiling.html:5 @@ -408,38 +451,31 @@ msgstr "" msgid "Cookies" msgstr "" -#: templates/debug_toolbar/panels/request.html:32 -#: templates/debug_toolbar/panels/request.html:58 -#: templates/debug_toolbar/panels/request.html:84 -#: templates/debug_toolbar/panels/request.html:109 -msgid "Variable" -msgstr "" - -#: templates/debug_toolbar/panels/request.html:46 +#: templates/debug_toolbar/panels/request.html:27 msgid "No cookies" msgstr "" -#: templates/debug_toolbar/panels/request.html:50 +#: templates/debug_toolbar/panels/request.html:31 msgid "Session data" msgstr "" -#: templates/debug_toolbar/panels/request.html:72 +#: templates/debug_toolbar/panels/request.html:34 msgid "No session data" msgstr "" -#: templates/debug_toolbar/panels/request.html:76 +#: templates/debug_toolbar/panels/request.html:38 msgid "GET data" msgstr "" -#: templates/debug_toolbar/panels/request.html:98 +#: templates/debug_toolbar/panels/request.html:41 msgid "No GET data" msgstr "" -#: templates/debug_toolbar/panels/request.html:102 +#: templates/debug_toolbar/panels/request.html:45 msgid "POST data" msgstr "" -#: templates/debug_toolbar/panels/request.html:123 +#: templates/debug_toolbar/panels/request.html:48 msgid "No POST data" msgstr "" @@ -452,74 +488,66 @@ msgid "Signal" msgstr "" #: templates/debug_toolbar/panels/signals.html:6 -msgid "Providing" -msgstr "" - -#: templates/debug_toolbar/panels/signals.html:7 msgid "Receivers" msgstr "" -#: templates/debug_toolbar/panels/sql.html:7 +#: templates/debug_toolbar/panels/sql.html:6 #, python-format msgid "%(num)s query" msgid_plural "%(num)s queries" msgstr[0] "" msgstr[1] "" -#: templates/debug_toolbar/panels/sql.html:9 +#: templates/debug_toolbar/panels/sql.html:8 #, python-format msgid "" "including %(count)s similar" msgstr "" -#: templates/debug_toolbar/panels/sql.html:13 +#: templates/debug_toolbar/panels/sql.html:12 #, python-format msgid "" "and %(dupes)s duplicates" msgstr "" -#: templates/debug_toolbar/panels/sql.html:28 +#: templates/debug_toolbar/panels/sql.html:34 msgid "Query" msgstr "" -#: templates/debug_toolbar/panels/sql.html:29 +#: templates/debug_toolbar/panels/sql.html:35 #: templates/debug_toolbar/panels/timer.html:36 msgid "Timeline" msgstr "" -#: templates/debug_toolbar/panels/sql.html:31 -msgid "Action" -msgstr "" - -#: templates/debug_toolbar/panels/sql.html:48 +#: templates/debug_toolbar/panels/sql.html:52 #, python-format msgid "%(count)s similar queries." msgstr "" -#: templates/debug_toolbar/panels/sql.html:54 +#: templates/debug_toolbar/panels/sql.html:58 #, python-format msgid "Duplicated %(dupes)s times." msgstr "" -#: templates/debug_toolbar/panels/sql.html:86 +#: templates/debug_toolbar/panels/sql.html:95 msgid "Connection:" msgstr "" -#: templates/debug_toolbar/panels/sql.html:88 +#: templates/debug_toolbar/panels/sql.html:97 msgid "Isolation level:" msgstr "" -#: templates/debug_toolbar/panels/sql.html:91 +#: templates/debug_toolbar/panels/sql.html:100 msgid "Transaction status:" msgstr "" -#: templates/debug_toolbar/panels/sql.html:105 +#: templates/debug_toolbar/panels/sql.html:114 msgid "(unknown)" msgstr "" -#: templates/debug_toolbar/panels/sql.html:114 +#: templates/debug_toolbar/panels/sql.html:123 msgid "No SQL queries were recorded during this request." msgstr "" @@ -594,8 +622,8 @@ msgid_plural "%(payload_count)s files" msgstr[0] "" msgstr[1] "" -#: templates/debug_toolbar/panels/staticfiles.html:43 -msgid "Path" +#: templates/debug_toolbar/panels/staticfiles.html:44 +msgid "Location" msgstr "" #: templates/debug_toolbar/panels/template_source.html:4 @@ -657,11 +685,11 @@ msgstr "" msgid "Version" msgstr "" -#: templates/debug_toolbar/redirect.html:8 +#: templates/debug_toolbar/redirect.html:10 msgid "Location:" msgstr "" -#: templates/debug_toolbar/redirect.html:10 +#: templates/debug_toolbar/redirect.html:12 msgid "" "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 " diff --git a/debug_toolbar/locale/es/LC_MESSAGES/django.mo b/debug_toolbar/locale/es/LC_MESSAGES/django.mo index 0706dbe52..583f88ef9 100644 Binary files a/debug_toolbar/locale/es/LC_MESSAGES/django.mo and b/debug_toolbar/locale/es/LC_MESSAGES/django.mo differ diff --git a/debug_toolbar/locale/es/LC_MESSAGES/django.po b/debug_toolbar/locale/es/LC_MESSAGES/django.po index f7d763e1e..d757cce1f 100644 --- a/debug_toolbar/locale/es/LC_MESSAGES/django.po +++ b/debug_toolbar/locale/es/LC_MESSAGES/django.po @@ -1,73 +1,84 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# -# +# +# # Translators: # jcatalan , 2014 -# Leonardo J. Caballero G. , 2013-2014 +# Daniel Iglesias , 2021 +# Leonardo J. Caballero G. , 2013-2014,2020 # marcelor , 2013 +# Sergio Infante , 2015 msgid "" msgstr "" "Project-Id-Version: Django Debug Toolbar\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-04-25 21:52+0200\n" -"PO-Revision-Date: 2014-04-25 19:53+0000\n" -"Last-Translator: Aymeric Augustin \n" -"Language-Team: Spanish (http://www.transifex.com/projects/p/django-debug-toolbar/language/es/)\n" +"POT-Creation-Date: 2024-08-06 07:12-0500\n" +"PO-Revision-Date: 2010-11-30 00:00+0000\n" +"Last-Translator: Daniel Iglesias , 2021\n" +"Language-Team: Spanish (http://app.transifex.com/django-debug-toolbar/django-debug-toolbar/language/es/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: es\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" -#: apps.py:11 +#: apps.py:18 msgid "Debug Toolbar" +msgstr "Barra de herramientas de Depuración" + +#: panels/alerts.py:67 +#, python-brace-format +msgid "" +"Form with id \"{form_id}\" contains file input, but does not have the " +"attribute enctype=\"multipart/form-data\"." msgstr "" -#: views.py:14 +#: panels/alerts.py:70 msgid "" -"Data for this panel isn't available anymore. Please reload the page and " -"retry." -msgstr "La información de este panel ya no se encuentra disponible. Por favor recargue la página y pruebe nuevamente." +"Form contains file input, but does not have the attribute " +"enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:73 +#, python-brace-format +msgid "" +"Input element references form with id \"{form_id}\", but the form does not " +"have the attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:77 +msgid "Alerts" +msgstr "" -#: panels/cache.py:191 +#: panels/cache.py:168 msgid "Cache" msgstr "Cache" -#: panels/cache.py:196 +#: panels/cache.py:174 #, python-format msgid "%(cache_calls)d call in %(time).2fms" msgid_plural "%(cache_calls)d calls in %(time).2fms" msgstr[0] "%(cache_calls)d llamada en %(time).2fms" msgstr[1] "%(cache_calls)d llamadas en %(time).2fms" +msgstr[2] "%(cache_calls)d llamadas en %(time).2fms" -#: panels/cache.py:204 +#: panels/cache.py:183 #, python-format msgid "Cache calls from %(count)d backend" msgid_plural "Cache calls from %(count)d backends" msgstr[0] "%(count)d llamadas al Cache desde el backend" msgstr[1] "%(count)d llamadas al Caché desde backends" +msgstr[2] "%(count)d llamadas al Caché desde backends" -#: panels/headers.py:35 +#: panels/headers.py:31 msgid "Headers" msgstr "Encabezados" -#: panels/logging.py:64 -msgid "Logging" -msgstr "Registros" +#: panels/history/panel.py:19 panels/history/panel.py:20 +msgid "History" +msgstr "Historial" -#: panels/logging.py:70 -#, python-format -msgid "%(count)s message" -msgid_plural "%(count)s messages" -msgstr[0] "%(count)s mensaje" -msgstr[1] "%(count)s mensajes" - -#: panels/logging.py:73 -msgid "Log messages" -msgstr "Mensajes del registro" - -#: panels/profiling.py:127 +#: panels/profiling.py:140 msgid "Profiling" msgstr "Análisis de rendimiento" @@ -75,214 +86,234 @@ msgstr "Análisis de rendimiento" msgid "Intercept redirects" msgstr "Interceptar re-direcionamiento" -#: panels/request.py:18 +#: panels/request.py:16 msgid "Request" msgstr "Petición" -#: panels/request.py:35 +#: panels/request.py:38 msgid "" msgstr "" -#: panels/request.py:47 +#: panels/request.py:55 msgid "" msgstr "" -#: panels/settings.py:20 +#: panels/settings.py:17 msgid "Settings" msgstr "Configuraciones" -#: panels/settings.py:23 +#: panels/settings.py:20 #, python-format -msgid "Settings from %s" -msgstr "Configuraciones en %s" +msgid "Settings from %s" +msgstr "Valores procedentes de %s" -#: panels/signals.py:45 +#: panels/signals.py:57 #, python-format msgid "%(num_receivers)d receiver of 1 signal" msgid_plural "%(num_receivers)d receivers of 1 signal" msgstr[0] "%(num_receivers)d receptor de 1 señal" msgstr[1] "%(num_receivers)d receptores de 1 señal" +msgstr[2] "%(num_receivers)d receptores de 1 señal" -#: panels/signals.py:48 +#: panels/signals.py:62 #, python-format msgid "%(num_receivers)d receiver of %(num_signals)d signals" msgid_plural "%(num_receivers)d receivers of %(num_signals)d signals" msgstr[0] "%(num_receivers)d receptor de %(num_signals)d señales" msgstr[1] "%(num_receivers)d receptores de %(num_signals)d señales" +msgstr[2] "%(num_receivers)d receptores de %(num_signals)d señales" -#: panels/signals.py:53 +#: panels/signals.py:67 msgid "Signals" msgstr "Señales" -#: panels/staticfiles.py:89 +#: panels/sql/panel.py:30 panels/sql/panel.py:41 +msgid "Read uncommitted" +msgstr "Leer cambios tentativos" + +#: panels/sql/panel.py:31 panels/sql/panel.py:43 +msgid "Read committed" +msgstr "Leer cambios permanentes" + +#: panels/sql/panel.py:32 panels/sql/panel.py:45 +msgid "Repeatable read" +msgstr "Lectura repetible" + +#: panels/sql/panel.py:33 panels/sql/panel.py:47 +msgid "Serializable" +msgstr "Serializable" + +#: panels/sql/panel.py:39 +msgid "Autocommit" +msgstr "Autocommit" + +#: panels/sql/panel.py:61 panels/sql/panel.py:71 +msgid "Idle" +msgstr "Inactivo" + +#: panels/sql/panel.py:62 panels/sql/panel.py:72 +msgid "Active" +msgstr "Activo" + +#: panels/sql/panel.py:63 panels/sql/panel.py:73 +msgid "In transaction" +msgstr "En transacción" + +#: panels/sql/panel.py:64 panels/sql/panel.py:74 +msgid "In error" +msgstr "En error" + +#: panels/sql/panel.py:65 panels/sql/panel.py:75 +msgid "Unknown" +msgstr "Desconocido" + +#: panels/sql/panel.py:162 +msgid "SQL" +msgstr "SQL" + +#: panels/sql/panel.py:168 +#, python-format +msgid "%(query_count)d query in %(sql_time).2fms" +msgid_plural "%(query_count)d queries in %(sql_time).2fms" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +#: panels/sql/panel.py:180 +#, python-format +msgid "SQL queries from %(count)d connection" +msgid_plural "SQL queries from %(count)d connections" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +#: panels/staticfiles.py:82 #, python-format msgid "Static files (%(num_found)s found, %(num_used)s used)" msgstr "Archivos estáticos (%(num_found)s encontrados, %(num_used)s en uso)" -#: panels/staticfiles.py:107 +#: panels/staticfiles.py:103 msgid "Static files" msgstr "Archivos estáticos" -#: panels/staticfiles.py:112 +#: panels/staticfiles.py:109 #, python-format msgid "%(num_used)s file used" msgid_plural "%(num_used)s files used" msgstr[0] "%(num_used)s archivo usado" msgstr[1] "%(num_used)s archivos usados" +msgstr[2] "%(num_used)s archivos usados" + +#: panels/templates/panel.py:101 +msgid "Templates" +msgstr "Plantillas" -#: panels/timer.py:23 +#: panels/templates/panel.py:106 +#, python-format +msgid "Templates (%(num_templates)s rendered)" +msgstr "Plantillas (%(num_templates)s renderizadas)" + +#: panels/templates/panel.py:195 +msgid "No origin" +msgstr "Sin origen" + +#: panels/timer.py:27 #, python-format msgid "CPU: %(cum)0.2fms (%(total)0.2fms)" msgstr "CPU: %(cum)0.2fms (%(total)0.2fms)" -#: panels/timer.py:28 +#: panels/timer.py:32 #, python-format msgid "Total: %0.2fms" msgstr "Total: %0.2fms" -#: panels/timer.py:34 templates/debug_toolbar/panels/logging.html:7 +#: panels/timer.py:38 templates/debug_toolbar/panels/history.html:9 #: templates/debug_toolbar/panels/sql_explain.html:11 #: templates/debug_toolbar/panels/sql_profile.html:12 #: templates/debug_toolbar/panels/sql_select.html:11 msgid "Time" msgstr "Tiempo" -#: panels/timer.py:42 +#: panels/timer.py:46 msgid "User CPU time" msgstr "Tiempo en CPU de usuario" -#: panels/timer.py:42 +#: panels/timer.py:46 #, python-format msgid "%(utime)0.3f msec" msgstr "%(utime)0.3f mseg" -#: panels/timer.py:43 +#: panels/timer.py:47 msgid "System CPU time" msgstr "Tiempo en CPU del sistema" -#: panels/timer.py:43 +#: panels/timer.py:47 #, python-format msgid "%(stime)0.3f msec" msgstr "%(stime)0.3f mseg" -#: panels/timer.py:44 +#: panels/timer.py:48 msgid "Total CPU time" msgstr "Tiempo total de CPU" -#: panels/timer.py:44 +#: panels/timer.py:48 #, python-format msgid "%(total)0.3f msec" msgstr "%(total)0.3f mseg" -#: panels/timer.py:45 +#: panels/timer.py:49 msgid "Elapsed time" msgstr "Tiempo transcurrido" -#: panels/timer.py:45 +#: panels/timer.py:49 #, python-format msgid "%(total_time)0.3f msec" msgstr "%(total_time)0.3f mseg" -#: panels/timer.py:46 +#: panels/timer.py:51 msgid "Context switches" msgstr "Cambios de contexto" -#: panels/timer.py:46 +#: panels/timer.py:52 #, python-format msgid "%(vcsw)d voluntary, %(ivcsw)d involuntary" msgstr "%(vcsw)d voluntario, %(ivcsw)d involuntario" -#: panels/versions.py:25 +#: panels/versions.py:19 msgid "Versions" msgstr "Versiones" -#: panels/sql/panel.py:22 -msgid "Autocommit" -msgstr "Autocommit" - -#: panels/sql/panel.py:23 -msgid "Read uncommitted" -msgstr "Leer cambios tentativos" - -#: panels/sql/panel.py:24 -msgid "Read committed" -msgstr "Leer cambios permanentes" - -#: panels/sql/panel.py:25 -msgid "Repeatable read" -msgstr "Lectura repetible" - -#: panels/sql/panel.py:26 -msgid "Serializable" -msgstr "Serializable" - -#: panels/sql/panel.py:37 -msgid "Idle" -msgstr "Inactivo" - -#: panels/sql/panel.py:38 -msgid "Active" -msgstr "Activo" - -#: panels/sql/panel.py:39 -msgid "In transaction" -msgstr "En transacción" - -#: panels/sql/panel.py:40 -msgid "In error" -msgstr "En error" - -#: panels/sql/panel.py:41 -msgid "Unknown" -msgstr "Desconocido" - -#: panels/sql/panel.py:105 -msgid "SQL" -msgstr "SQL" - -#: panels/templates/panel.py:141 -msgid "Templates" -msgstr "Plantillas" - -#: panels/templates/panel.py:146 -#, python-format -msgid "Templates (%(num_templates)s rendered)" -msgstr "Plantillas (%(num_templates)s renderizadas)" - -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide toolbar" msgstr "Ocutar barra de herramientas" -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide" msgstr "Ocultar" -#: templates/debug_toolbar/base.html:25 +#: templates/debug_toolbar/base.html:25 templates/debug_toolbar/base.html:26 +msgid "Toggle Theme" +msgstr "" + +#: templates/debug_toolbar/base.html:35 +msgid "Show toolbar" +msgstr "Mostrar barra de herramientas" + +#: templates/debug_toolbar/includes/panel_button.html:4 msgid "Disable for next and successive requests" msgstr "Deshabilitar para el próximo y sucesivos peticiones" -#: templates/debug_toolbar/base.html:25 +#: templates/debug_toolbar/includes/panel_button.html:4 msgid "Enable for next and successive requests" msgstr "Habilitar para el próximo y sucesivos peticiones" -#: templates/debug_toolbar/base.html:47 -msgid "Show toolbar" -msgstr "Mostrar barra de herramientas" - -#: templates/debug_toolbar/base.html:53 -msgid "Close" -msgstr "Cerrar" - -#: templates/debug_toolbar/redirect.html:8 -msgid "Location:" -msgstr "Ubicación:" +#: templates/debug_toolbar/panels/alerts.html:4 +msgid "Alerts found" +msgstr "" -#: templates/debug_toolbar/redirect.html:10 -msgid "" -"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." -msgstr "El Django Debug Toolbar ha interceptado un re-direccionamiento a la dirección de Internet mostrada arriba, con el propósito de inspeccionarla. Usted puede hacer clic en el vínculo de arriba para continuar con el re-direccionamiento normalmente." +#: templates/debug_toolbar/panels/alerts.html:11 +msgid "No alerts found" +msgstr "" #: templates/debug_toolbar/panels/cache.html:2 msgid "Summary" @@ -313,7 +344,7 @@ msgid "Calls" msgstr "Llamadas" #: templates/debug_toolbar/panels/cache.html:43 -#: templates/debug_toolbar/panels/sql.html:20 +#: templates/debug_toolbar/panels/sql.html:36 msgid "Time (ms)" msgstr "Tiempo (ms)" @@ -348,10 +379,8 @@ msgstr "Clave" #: templates/debug_toolbar/panels/headers.html:9 #: templates/debug_toolbar/panels/headers.html:28 #: templates/debug_toolbar/panels/headers.html:49 -#: templates/debug_toolbar/panels/request.html:33 -#: templates/debug_toolbar/panels/request.html:59 -#: templates/debug_toolbar/panels/request.html:85 -#: templates/debug_toolbar/panels/request.html:110 +#: templates/debug_toolbar/panels/history_tr.html:23 +#: templates/debug_toolbar/panels/request_variables.html:12 #: templates/debug_toolbar/panels/settings.html:6 #: templates/debug_toolbar/panels/timer.html:11 msgid "Value" @@ -371,26 +400,32 @@ msgid "" "significant subset is shown below." msgstr "Ya que el entorno WSGI hereda el entorno del servidor, solo un subconjunto significativo es mostrado más abajo." -#: templates/debug_toolbar/panels/logging.html:6 -msgid "Level" -msgstr "Nivel" +#: templates/debug_toolbar/panels/history.html:10 +msgid "Method" +msgstr "Método" -#: templates/debug_toolbar/panels/logging.html:8 -msgid "Channel" -msgstr "Canal" +#: templates/debug_toolbar/panels/history.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:43 +msgid "Path" +msgstr "Ruta" -#: templates/debug_toolbar/panels/logging.html:9 -msgid "Message" -msgstr "Mensaje" +#: templates/debug_toolbar/panels/history.html:12 +msgid "Request Variables" +msgstr "Variables de la petición" -#: templates/debug_toolbar/panels/logging.html:10 -#: templates/debug_toolbar/panels/staticfiles.html:45 -msgid "Location" -msgstr "Ubicación" +#: templates/debug_toolbar/panels/history.html:13 +msgid "Status" +msgstr "Estado" -#: templates/debug_toolbar/panels/logging.html:26 -msgid "No messages logged" -msgstr "No hay mensajes registrados" +#: templates/debug_toolbar/panels/history.html:14 +#: templates/debug_toolbar/panels/sql.html:37 +msgid "Action" +msgstr "Acción" + +#: templates/debug_toolbar/panels/history_tr.html:22 +#: templates/debug_toolbar/panels/request_variables.html:11 +msgid "Variable" +msgstr "Variable" #: templates/debug_toolbar/panels/profiling.html:5 msgid "Call" @@ -429,38 +464,31 @@ msgstr "Nombre de dirección URL" msgid "Cookies" msgstr "Cookies" -#: templates/debug_toolbar/panels/request.html:32 -#: templates/debug_toolbar/panels/request.html:58 -#: templates/debug_toolbar/panels/request.html:84 -#: templates/debug_toolbar/panels/request.html:109 -msgid "Variable" -msgstr "Variable" - -#: templates/debug_toolbar/panels/request.html:46 +#: templates/debug_toolbar/panels/request.html:27 msgid "No cookies" msgstr "Sin cookies" -#: templates/debug_toolbar/panels/request.html:50 +#: templates/debug_toolbar/panels/request.html:31 msgid "Session data" msgstr "Datos de sesión" -#: templates/debug_toolbar/panels/request.html:72 +#: templates/debug_toolbar/panels/request.html:34 msgid "No session data" msgstr "Sin datos de sesión" -#: templates/debug_toolbar/panels/request.html:76 +#: templates/debug_toolbar/panels/request.html:38 msgid "GET data" msgstr "Datos del GET" -#: templates/debug_toolbar/panels/request.html:98 +#: templates/debug_toolbar/panels/request.html:41 msgid "No GET data" msgstr "Sin datos GET" -#: templates/debug_toolbar/panels/request.html:102 +#: templates/debug_toolbar/panels/request.html:45 msgid "POST data" msgstr "Datos del POST" -#: templates/debug_toolbar/panels/request.html:123 +#: templates/debug_toolbar/panels/request.html:48 msgid "No POST data" msgstr "Sin datos POST" @@ -473,60 +501,70 @@ msgid "Signal" msgstr "Señal" #: templates/debug_toolbar/panels/signals.html:6 -msgid "Providing" -msgstr "Proporcionando" - -#: templates/debug_toolbar/panels/signals.html:7 msgid "Receivers" msgstr "Receptores" -#: templates/debug_toolbar/panels/sql.html:7 +#: templates/debug_toolbar/panels/sql.html:6 #, python-format msgid "%(num)s query" msgid_plural "%(num)s queries" msgstr[0] "%(num)s consulta" msgstr[1] "%(num)s consultas" +msgstr[2] "%(num)s consultas" + +#: templates/debug_toolbar/panels/sql.html:8 +#, python-format +msgid "" +"including %(count)s similar" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:12 +#, python-format +msgid "" +"and %(dupes)s duplicates" +msgstr "y %(dupes)s repetidos" -#: templates/debug_toolbar/panels/sql.html:18 +#: templates/debug_toolbar/panels/sql.html:34 msgid "Query" msgstr "Query" -#: templates/debug_toolbar/panels/sql.html:19 +#: templates/debug_toolbar/panels/sql.html:35 #: templates/debug_toolbar/panels/timer.html:36 msgid "Timeline" msgstr "Línea de tiempo" -#: templates/debug_toolbar/panels/sql.html:21 -msgid "Action" -msgstr "Acción" +#: templates/debug_toolbar/panels/sql.html:52 +#, python-format +msgid "%(count)s similar queries." +msgstr "%(count)s consultas similares. " -#: templates/debug_toolbar/panels/sql.html:64 +#: templates/debug_toolbar/panels/sql.html:58 +#, python-format +msgid "Duplicated %(dupes)s times." +msgstr "Repetidas %(dupes)s veces." + +#: templates/debug_toolbar/panels/sql.html:95 msgid "Connection:" msgstr "Conexión:" -#: templates/debug_toolbar/panels/sql.html:66 +#: templates/debug_toolbar/panels/sql.html:97 msgid "Isolation level:" msgstr "Nivel de aislamiento:" -#: templates/debug_toolbar/panels/sql.html:69 +#: templates/debug_toolbar/panels/sql.html:100 msgid "Transaction status:" msgstr "Estado de la transacción:" -#: templates/debug_toolbar/panels/sql.html:83 +#: templates/debug_toolbar/panels/sql.html:114 msgid "(unknown)" msgstr "(desconocido)" -#: templates/debug_toolbar/panels/sql.html:92 +#: templates/debug_toolbar/panels/sql.html:123 msgid "No SQL queries were recorded during this request." msgstr "No se registraron consultas SQL durante ésta petición." -#: templates/debug_toolbar/panels/sql_explain.html:3 -#: templates/debug_toolbar/panels/sql_profile.html:3 -#: templates/debug_toolbar/panels/sql_select.html:3 -#: templates/debug_toolbar/panels/template_source.html:3 -msgid "Back" -msgstr "Regresar" - #: templates/debug_toolbar/panels/sql_explain.html:4 msgid "SQL explained" msgstr "SQL explicado" @@ -559,48 +597,52 @@ msgstr "SQL seleccionado" msgid "Empty set" msgstr "Establecer Vacío" -#: templates/debug_toolbar/panels/staticfiles.html:4 +#: templates/debug_toolbar/panels/staticfiles.html:3 msgid "Static file path" msgid_plural "Static file paths" msgstr[0] "Ruta a archivos estático" msgstr[1] "Rutas a archivos estáticos" +msgstr[2] "Rutas a archivos estáticos" -#: templates/debug_toolbar/panels/staticfiles.html:8 +#: templates/debug_toolbar/panels/staticfiles.html:7 #, python-format msgid "(prefix %(prefix)s)" msgstr "(prefijo %(prefix)s)" -#: templates/debug_toolbar/panels/staticfiles.html:12 -#: templates/debug_toolbar/panels/staticfiles.html:23 -#: templates/debug_toolbar/panels/staticfiles.html:35 +#: templates/debug_toolbar/panels/staticfiles.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:22 +#: templates/debug_toolbar/panels/staticfiles.html:34 #: templates/debug_toolbar/panels/templates.html:10 -#: templates/debug_toolbar/panels/templates.html:28 -#: templates/debug_toolbar/panels/templates.html:43 +#: templates/debug_toolbar/panels/templates.html:30 +#: templates/debug_toolbar/panels/templates.html:47 msgid "None" msgstr "Ninguno" -#: templates/debug_toolbar/panels/staticfiles.html:15 +#: templates/debug_toolbar/panels/staticfiles.html:14 msgid "Static file app" msgid_plural "Static file apps" msgstr[0] "Aplicación a archivos estáticos" msgstr[1] "Aplicaciones de archivos estáticos" +msgstr[2] "Aplicaciones de archivos estáticos" -#: templates/debug_toolbar/panels/staticfiles.html:26 +#: templates/debug_toolbar/panels/staticfiles.html:25 msgid "Static file" msgid_plural "Static files" msgstr[0] "Archivo estático" msgstr[1] "Archivos estáticos" +msgstr[2] "Archivos estáticos" -#: templates/debug_toolbar/panels/staticfiles.html:40 +#: templates/debug_toolbar/panels/staticfiles.html:39 #, python-format msgid "%(payload_count)s file" msgid_plural "%(payload_count)s files" msgstr[0] "%(payload_count)s archivo" msgstr[1] "%(payload_count)s archivos" +msgstr[2] "%(payload_count)s archivos" #: templates/debug_toolbar/panels/staticfiles.html:44 -msgid "Path" -msgstr "Ruta" +msgid "Location" +msgstr "Ubicación" #: templates/debug_toolbar/panels/template_source.html:4 msgid "Template source:" @@ -611,23 +653,26 @@ msgid "Template path" msgid_plural "Template paths" msgstr[0] "Ruta de plantilla" msgstr[1] "Rutas de plantillas" +msgstr[2] "Rutas de plantillas" #: templates/debug_toolbar/panels/templates.html:13 msgid "Template" msgid_plural "Templates" msgstr[0] "Plantilla" msgstr[1] "Plantillas" +msgstr[2] "Plantillas" -#: templates/debug_toolbar/panels/templates.html:21 -#: templates/debug_toolbar/panels/templates.html:37 +#: templates/debug_toolbar/panels/templates.html:22 +#: templates/debug_toolbar/panels/templates.html:40 msgid "Toggle context" msgstr "Mostrar/Ocultar contexto" -#: templates/debug_toolbar/panels/templates.html:31 +#: templates/debug_toolbar/panels/templates.html:33 msgid "Context processor" msgid_plural "Context processors" msgstr[0] "Procesador de contexto" msgstr[1] "Procesadores de contexto" +msgstr[2] "Procesadores de contexto" #: templates/debug_toolbar/panels/timer.html:2 msgid "Resource usage" @@ -643,16 +688,37 @@ msgstr "Distribución de tiempos de navegador" #: templates/debug_toolbar/panels/timer.html:35 msgid "Timing attribute" -msgstr "" +msgstr "Atributo de tiempo" #: templates/debug_toolbar/panels/timer.html:37 msgid "Milliseconds since navigation start (+length)" msgstr "Milisegundos desde inicio de la navegación (+longitud)" -#: templates/debug_toolbar/panels/versions.html:5 +#: templates/debug_toolbar/panels/versions.html:10 +msgid "Package" +msgstr "Paquete" + +#: templates/debug_toolbar/panels/versions.html:11 msgid "Name" msgstr "Nombre" -#: templates/debug_toolbar/panels/versions.html:6 +#: templates/debug_toolbar/panels/versions.html:12 msgid "Version" msgstr "Versión" + +#: templates/debug_toolbar/redirect.html:10 +msgid "Location:" +msgstr "Ubicación:" + +#: templates/debug_toolbar/redirect.html:12 +msgid "" +"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." +msgstr "El Django Debug Toolbar ha interceptado un re-direccionamiento a la dirección de Internet mostrada arriba, con el propósito de inspeccionarla. Usted puede hacer clic en el vínculo de arriba para continuar con el re-direccionamiento normalmente." + +#: views.py:16 +msgid "" +"Data for this panel isn't available anymore. Please reload the page and " +"retry." +msgstr "La información de este panel ya no se encuentra disponible. Por favor recargue la página y pruebe nuevamente." diff --git a/debug_toolbar/locale/fa/LC_MESSAGES/django.mo b/debug_toolbar/locale/fa/LC_MESSAGES/django.mo new file mode 100644 index 000000000..fa30fd402 Binary files /dev/null and b/debug_toolbar/locale/fa/LC_MESSAGES/django.mo differ diff --git a/debug_toolbar/locale/fa/LC_MESSAGES/django.po b/debug_toolbar/locale/fa/LC_MESSAGES/django.po new file mode 100644 index 000000000..edb202e62 --- /dev/null +++ b/debug_toolbar/locale/fa/LC_MESSAGES/django.po @@ -0,0 +1,706 @@ +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# +# Translators: +# Ali Soltani , 2021 +# Elyas Ebrahimpour , 2024 +msgid "" +msgstr "" +"Project-Id-Version: Django Debug Toolbar\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-08-06 07:12-0500\n" +"PO-Revision-Date: 2010-11-30 00:00+0000\n" +"Last-Translator: Elyas Ebrahimpour , 2024\n" +"Language-Team: Persian (http://app.transifex.com/django-debug-toolbar/django-debug-toolbar/language/fa/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: fa\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: apps.py:18 +msgid "Debug Toolbar" +msgstr "نوار ابزار دیباگ" + +#: panels/alerts.py:67 +#, python-brace-format +msgid "" +"Form with id \"{form_id}\" contains file input, but does not have the " +"attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:70 +msgid "" +"Form contains file input, but does not have the attribute " +"enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:73 +#, python-brace-format +msgid "" +"Input element references form with id \"{form_id}\", but the form does not " +"have the attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:77 +msgid "Alerts" +msgstr "" + +#: panels/cache.py:168 +msgid "Cache" +msgstr "Cache" + +#: panels/cache.py:174 +#, python-format +msgid "%(cache_calls)d call in %(time).2fms" +msgid_plural "%(cache_calls)d calls in %(time).2fms" +msgstr[0] "%(cache_calls)d فراخوان در %(time).2f میلی‌ثانیه" +msgstr[1] "%(cache_calls)d فراخوان در %(time).2f میلی‌ثانیه" + +#: panels/cache.py:183 +#, python-format +msgid "Cache calls from %(count)d backend" +msgid_plural "Cache calls from %(count)d backends" +msgstr[0] "فراخوان‌های کش از %(count)d بک‌اند" +msgstr[1] "فراخوان‌های کش از %(count)d بک‌اندها" + +#: panels/headers.py:31 +msgid "Headers" +msgstr "هدر ها" + +#: panels/history/panel.py:19 panels/history/panel.py:20 +msgid "History" +msgstr "تاریخچه" + +#: panels/profiling.py:140 +msgid "Profiling" +msgstr "نمایه سازی" + +#: panels/redirects.py:17 +msgid "Intercept redirects" +msgstr "رهگیری تغییر مسیرها" + +#: panels/request.py:16 +msgid "Request" +msgstr "ریکوئست" + +#: panels/request.py:38 +msgid "" +msgstr "<بدون نمایش>" + +#: panels/request.py:55 +msgid "" +msgstr "<در دسترس نیست>" + +#: panels/settings.py:17 +msgid "Settings" +msgstr "تنظیمات" + +#: panels/settings.py:20 +#, python-format +msgid "Settings from %s" +msgstr "تنظیمات از %s" + +#: panels/signals.py:57 +#, python-format +msgid "%(num_receivers)d receiver of 1 signal" +msgid_plural "%(num_receivers)d receivers of 1 signal" +msgstr[0] "%(num_receivers)d گیرنده از 1 سیگنال" +msgstr[1] "%(num_receivers)d گیرنده از 1 سیگنال" + +#: panels/signals.py:62 +#, python-format +msgid "%(num_receivers)d receiver of %(num_signals)d signals" +msgid_plural "%(num_receivers)d receivers of %(num_signals)d signals" +msgstr[0] "%(num_receivers)d گیرنده از %(num_signals)d سیگنال" +msgstr[1] "%(num_receivers)d گیرنده از %(num_signals)d سیگنال" + +#: panels/signals.py:67 +msgid "Signals" +msgstr "سیگنال‌ها" + +#: panels/sql/panel.py:30 panels/sql/panel.py:41 +msgid "Read uncommitted" +msgstr "خواندن بدون تاثیر" + +#: panels/sql/panel.py:31 panels/sql/panel.py:43 +msgid "Read committed" +msgstr "خواندن با تاثیر" + +#: panels/sql/panel.py:32 panels/sql/panel.py:45 +msgid "Repeatable read" +msgstr "خواندن تکرارپذیر" + +#: panels/sql/panel.py:33 panels/sql/panel.py:47 +msgid "Serializable" +msgstr "قابل سریالایز شدن" + +#: panels/sql/panel.py:39 +msgid "Autocommit" +msgstr "کامیت خودکار" + +#: panels/sql/panel.py:61 panels/sql/panel.py:71 +msgid "Idle" +msgstr "IDLE" + +#: panels/sql/panel.py:62 panels/sql/panel.py:72 +msgid "Active" +msgstr "فعال" + +#: panels/sql/panel.py:63 panels/sql/panel.py:73 +msgid "In transaction" +msgstr "در تراکنش" + +#: panels/sql/panel.py:64 panels/sql/panel.py:74 +msgid "In error" +msgstr "در خطا" + +#: panels/sql/panel.py:65 panels/sql/panel.py:75 +msgid "Unknown" +msgstr "ناشناخته" + +#: panels/sql/panel.py:162 +msgid "SQL" +msgstr "اس کیو ال" + +#: panels/sql/panel.py:168 +#, python-format +msgid "%(query_count)d query in %(sql_time).2fms" +msgid_plural "%(query_count)d queries in %(sql_time).2fms" +msgstr[0] "%(query_count)d کوئری در %(sql_time).2f میلی‌ثانیه" +msgstr[1] "%(query_count)d کوئری در %(sql_time).2f میلی‌ثانیه" + +#: panels/sql/panel.py:180 +#, python-format +msgid "SQL queries from %(count)d connection" +msgid_plural "SQL queries from %(count)d connections" +msgstr[0] "کوئری‌های SQL از %(count)d اتصال" +msgstr[1] "کوئری‌های SQL از %(count)d اتصال" + +#: panels/staticfiles.py:82 +#, python-format +msgid "Static files (%(num_found)s found, %(num_used)s used)" +msgstr "فایل‌های استاتیک (%(num_found)s یافته شده، %(num_used)s استفاده شده)" + +#: panels/staticfiles.py:103 +msgid "Static files" +msgstr "فایل های استاتیک" + +#: panels/staticfiles.py:109 +#, python-format +msgid "%(num_used)s file used" +msgid_plural "%(num_used)s files used" +msgstr[0] "%(num_used)s فایل استفاده شده" +msgstr[1] "%(num_used)s فایل استفاده شده" + +#: panels/templates/panel.py:101 +msgid "Templates" +msgstr "تمپلیت ها" + +#: panels/templates/panel.py:106 +#, python-format +msgid "Templates (%(num_templates)s rendered)" +msgstr "تمپلیت ها (%(num_templates)s rendered)" + +#: panels/templates/panel.py:195 +msgid "No origin" +msgstr "بدون origin" + +#: panels/timer.py:27 +#, python-format +msgid "CPU: %(cum)0.2fms (%(total)0.2fms)" +msgstr "پردازنده: %(cum)0.2f میلی‌ثانیه (%(total)0.2f میلی‌ثانیه)" + +#: panels/timer.py:32 +#, python-format +msgid "Total: %0.2fms" +msgstr "مجموع: %0.2f میلی‌ثانیه" + +#: panels/timer.py:38 templates/debug_toolbar/panels/history.html:9 +#: templates/debug_toolbar/panels/sql_explain.html:11 +#: templates/debug_toolbar/panels/sql_profile.html:12 +#: templates/debug_toolbar/panels/sql_select.html:11 +msgid "Time" +msgstr "زمان" + +#: panels/timer.py:46 +msgid "User CPU time" +msgstr "زمان سی پی یو کاربر" + +#: panels/timer.py:46 +#, python-format +msgid "%(utime)0.3f msec" +msgstr "%(utime)0.3f میلی‌ثانیه" + +#: panels/timer.py:47 +msgid "System CPU time" +msgstr "زمان CPU سیستم" + +#: panels/timer.py:47 +#, python-format +msgid "%(stime)0.3f msec" +msgstr "%(stime)0.3f میلی‌ثانیه" + +#: panels/timer.py:48 +msgid "Total CPU time" +msgstr "زمان کل سی پی یو" + +#: panels/timer.py:48 +#, python-format +msgid "%(total)0.3f msec" +msgstr "%(total)0.3f میلی ثانیه" + +#: panels/timer.py:49 +msgid "Elapsed time" +msgstr "زمان سپری شده" + +#: panels/timer.py:49 +#, python-format +msgid "%(total_time)0.3f msec" +msgstr "%(total_time)0.3f میلی‌ثانیه" + +#: panels/timer.py:51 +msgid "Context switches" +msgstr "تغییرات زمینه" + +#: panels/timer.py:52 +#, python-format +msgid "%(vcsw)d voluntary, %(ivcsw)d involuntary" +msgstr "%(vcsw)d اختیاری، %(ivcsw)d غیراختیاری" + +#: panels/versions.py:19 +msgid "Versions" +msgstr "ورژن ها" + +#: templates/debug_toolbar/base.html:23 +msgid "Hide toolbar" +msgstr "پنهان کردن toolbar" + +#: templates/debug_toolbar/base.html:23 +msgid "Hide" +msgstr "پنهان کردن" + +#: templates/debug_toolbar/base.html:25 templates/debug_toolbar/base.html:26 +msgid "Toggle Theme" +msgstr "" + +#: templates/debug_toolbar/base.html:35 +msgid "Show toolbar" +msgstr "نمایش toolbar" + +#: templates/debug_toolbar/includes/panel_button.html:4 +msgid "Disable for next and successive requests" +msgstr "غیر فعال کردن برای ریکوئست های پی در پی" + +#: templates/debug_toolbar/includes/panel_button.html:4 +msgid "Enable for next and successive requests" +msgstr "فعال کردن برای ریکوئست های بعدی و پی در پی" + +#: templates/debug_toolbar/panels/alerts.html:4 +msgid "Alerts found" +msgstr "" + +#: templates/debug_toolbar/panels/alerts.html:11 +msgid "No alerts found" +msgstr "" + +#: templates/debug_toolbar/panels/cache.html:2 +msgid "Summary" +msgstr "خلاصه" + +#: templates/debug_toolbar/panels/cache.html:6 +msgid "Total calls" +msgstr "تعداد نهایی کال کردن ها" + +#: templates/debug_toolbar/panels/cache.html:7 +msgid "Total time" +msgstr "زمان نهایی" + +#: templates/debug_toolbar/panels/cache.html:8 +msgid "Cache hits" +msgstr "Cache hits" + +#: templates/debug_toolbar/panels/cache.html:9 +msgid "Cache misses" +msgstr "عدم دسترسی به کش" + +#: templates/debug_toolbar/panels/cache.html:21 +msgid "Commands" +msgstr "دستورات" + +#: templates/debug_toolbar/panels/cache.html:39 +msgid "Calls" +msgstr "فراخوانی ها" + +#: templates/debug_toolbar/panels/cache.html:43 +#: templates/debug_toolbar/panels/sql.html:36 +msgid "Time (ms)" +msgstr "زمان (میلی ثانیه)" + +#: templates/debug_toolbar/panels/cache.html:44 +msgid "Type" +msgstr "نوع" + +#: templates/debug_toolbar/panels/cache.html:45 +#: templates/debug_toolbar/panels/request.html:8 +msgid "Arguments" +msgstr "آرگومان ها" + +#: templates/debug_toolbar/panels/cache.html:46 +#: templates/debug_toolbar/panels/request.html:9 +msgid "Keyword arguments" +msgstr "آرگومان های کیورد" + +#: templates/debug_toolbar/panels/cache.html:47 +msgid "Backend" +msgstr "بک اند" + +#: templates/debug_toolbar/panels/headers.html:3 +msgid "Request headers" +msgstr "هدر های ریکوئست" + +#: templates/debug_toolbar/panels/headers.html:8 +#: templates/debug_toolbar/panels/headers.html:27 +#: templates/debug_toolbar/panels/headers.html:48 +msgid "Key" +msgstr "کلید" + +#: templates/debug_toolbar/panels/headers.html:9 +#: templates/debug_toolbar/panels/headers.html:28 +#: templates/debug_toolbar/panels/headers.html:49 +#: templates/debug_toolbar/panels/history_tr.html:23 +#: templates/debug_toolbar/panels/request_variables.html:12 +#: templates/debug_toolbar/panels/settings.html:6 +#: templates/debug_toolbar/panels/timer.html:11 +msgid "Value" +msgstr "مقدار" + +#: templates/debug_toolbar/panels/headers.html:22 +msgid "Response headers" +msgstr "هدر های ریسپانس" + +#: templates/debug_toolbar/panels/headers.html:41 +msgid "WSGI environ" +msgstr "محیط WSGI" + +#: templates/debug_toolbar/panels/headers.html:43 +msgid "" +"Since the WSGI environ inherits the environment of the server, only a " +"significant subset is shown below." +msgstr "از آنجا که محیط WSGI محیط سرور را به ارث می برد ، فقط یک زیر مجموعه مهم در زیر نشان داده شده است." + +#: templates/debug_toolbar/panels/history.html:10 +msgid "Method" +msgstr "متد" + +#: templates/debug_toolbar/panels/history.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:43 +msgid "Path" +msgstr "Path" + +#: templates/debug_toolbar/panels/history.html:12 +msgid "Request Variables" +msgstr "متغیر های ریکوئست" + +#: templates/debug_toolbar/panels/history.html:13 +msgid "Status" +msgstr "وضعیت" + +#: templates/debug_toolbar/panels/history.html:14 +#: templates/debug_toolbar/panels/sql.html:37 +msgid "Action" +msgstr "اکشن" + +#: templates/debug_toolbar/panels/history_tr.html:22 +#: templates/debug_toolbar/panels/request_variables.html:11 +msgid "Variable" +msgstr "متغیر" + +#: templates/debug_toolbar/panels/profiling.html:5 +msgid "Call" +msgstr "Call" + +#: templates/debug_toolbar/panels/profiling.html:6 +msgid "CumTime" +msgstr "" + +#: templates/debug_toolbar/panels/profiling.html:7 +#: templates/debug_toolbar/panels/profiling.html:9 +msgid "Per" +msgstr "بر" + +#: templates/debug_toolbar/panels/profiling.html:8 +msgid "TotTime" +msgstr "زمان نهایی" + +#: templates/debug_toolbar/panels/profiling.html:10 +msgid "Count" +msgstr "تعداد" + +#: templates/debug_toolbar/panels/request.html:3 +msgid "View information" +msgstr "اطلاعات View" + +#: templates/debug_toolbar/panels/request.html:7 +msgid "View function" +msgstr "تابع Viw" + +#: templates/debug_toolbar/panels/request.html:10 +msgid "URL name" +msgstr "نام URL" + +#: templates/debug_toolbar/panels/request.html:24 +msgid "Cookies" +msgstr "کوکی ها" + +#: templates/debug_toolbar/panels/request.html:27 +msgid "No cookies" +msgstr "کوکی ای وجود ندارد" + +#: templates/debug_toolbar/panels/request.html:31 +msgid "Session data" +msgstr "دیتای سشن" + +#: templates/debug_toolbar/panels/request.html:34 +msgid "No session data" +msgstr "دیتای سشن وجود ندارد" + +#: templates/debug_toolbar/panels/request.html:38 +msgid "GET data" +msgstr "دیتای GET" + +#: templates/debug_toolbar/panels/request.html:41 +msgid "No GET data" +msgstr "دیتای GET وجود ندارد" + +#: templates/debug_toolbar/panels/request.html:45 +msgid "POST data" +msgstr "دیتای POST" + +#: templates/debug_toolbar/panels/request.html:48 +msgid "No POST data" +msgstr "دیتای POST وجود ندارد" + +#: templates/debug_toolbar/panels/settings.html:5 +msgid "Setting" +msgstr "تنظیمات" + +#: templates/debug_toolbar/panels/signals.html:5 +msgid "Signal" +msgstr "Signal" + +#: templates/debug_toolbar/panels/signals.html:6 +msgid "Receivers" +msgstr "Receiver ها" + +#: templates/debug_toolbar/panels/sql.html:6 +#, python-format +msgid "%(num)s query" +msgid_plural "%(num)s queries" +msgstr[0] "" +msgstr[1] "" + +#: templates/debug_toolbar/panels/sql.html:8 +#, python-format +msgid "" +"including %(count)s similar" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:12 +#, python-format +msgid "" +"and %(dupes)s duplicates" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:34 +msgid "Query" +msgstr "کوئری" + +#: templates/debug_toolbar/panels/sql.html:35 +#: templates/debug_toolbar/panels/timer.html:36 +msgid "Timeline" +msgstr "تایملاین" + +#: templates/debug_toolbar/panels/sql.html:52 +#, python-format +msgid "%(count)s similar queries." +msgstr "%(count)s کوئری مشابه" + +#: templates/debug_toolbar/panels/sql.html:58 +#, python-format +msgid "Duplicated %(dupes)s times." +msgstr "%(dupes)s بار تکرار شده" + +#: templates/debug_toolbar/panels/sql.html:95 +msgid "Connection:" +msgstr "کانکشن:" + +#: templates/debug_toolbar/panels/sql.html:97 +msgid "Isolation level:" +msgstr "سطح isolation:" + +#: templates/debug_toolbar/panels/sql.html:100 +msgid "Transaction status:" +msgstr "وضعیت تراکنش" + +#: templates/debug_toolbar/panels/sql.html:114 +msgid "(unknown)" +msgstr "(ناشناخته)" + +#: templates/debug_toolbar/panels/sql.html:123 +msgid "No SQL queries were recorded during this request." +msgstr "کوئری SQL ای در این ریکوئست ثبت نشده است" + +#: templates/debug_toolbar/panels/sql_explain.html:4 +msgid "SQL explained" +msgstr "توضیح SQL" + +#: templates/debug_toolbar/panels/sql_explain.html:9 +#: templates/debug_toolbar/panels/sql_profile.html:10 +#: templates/debug_toolbar/panels/sql_select.html:9 +msgid "Executed SQL" +msgstr "کوئری اجرا شده" + +#: templates/debug_toolbar/panels/sql_explain.html:13 +#: templates/debug_toolbar/panels/sql_profile.html:14 +#: templates/debug_toolbar/panels/sql_select.html:13 +msgid "Database" +msgstr "دیتابیس" + +#: templates/debug_toolbar/panels/sql_profile.html:4 +msgid "SQL profiled" +msgstr "SQL profiled" + +#: templates/debug_toolbar/panels/sql_profile.html:37 +msgid "Error" +msgstr "خطا" + +#: templates/debug_toolbar/panels/sql_select.html:4 +msgid "SQL selected" +msgstr "انتخاب شده SQL" + +#: templates/debug_toolbar/panels/sql_select.html:36 +msgid "Empty set" +msgstr "set خالی" + +#: templates/debug_toolbar/panels/staticfiles.html:3 +msgid "Static file path" +msgid_plural "Static file paths" +msgstr[0] "" +msgstr[1] "" + +#: templates/debug_toolbar/panels/staticfiles.html:7 +#, python-format +msgid "(prefix %(prefix)s)" +msgstr "(prefix %(prefix)s)" + +#: templates/debug_toolbar/panels/staticfiles.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:22 +#: templates/debug_toolbar/panels/staticfiles.html:34 +#: templates/debug_toolbar/panels/templates.html:10 +#: templates/debug_toolbar/panels/templates.html:30 +#: templates/debug_toolbar/panels/templates.html:47 +msgid "None" +msgstr "خالی" + +#: templates/debug_toolbar/panels/staticfiles.html:14 +msgid "Static file app" +msgid_plural "Static file apps" +msgstr[0] "" +msgstr[1] "" + +#: templates/debug_toolbar/panels/staticfiles.html:25 +msgid "Static file" +msgid_plural "Static files" +msgstr[0] "" +msgstr[1] "" + +#: templates/debug_toolbar/panels/staticfiles.html:39 +#, python-format +msgid "%(payload_count)s file" +msgid_plural "%(payload_count)s files" +msgstr[0] "" +msgstr[1] "" + +#: templates/debug_toolbar/panels/staticfiles.html:44 +msgid "Location" +msgstr "مکان" + +#: templates/debug_toolbar/panels/template_source.html:4 +msgid "Template source:" +msgstr "منبع تمپلیت:" + +#: templates/debug_toolbar/panels/templates.html:2 +msgid "Template path" +msgid_plural "Template paths" +msgstr[0] "" +msgstr[1] "" + +#: templates/debug_toolbar/panels/templates.html:13 +msgid "Template" +msgid_plural "Templates" +msgstr[0] "" +msgstr[1] "" + +#: templates/debug_toolbar/panels/templates.html:22 +#: templates/debug_toolbar/panels/templates.html:40 +msgid "Toggle context" +msgstr "تغییر متن" + +#: templates/debug_toolbar/panels/templates.html:33 +msgid "Context processor" +msgid_plural "Context processors" +msgstr[0] "پردازشگر محیط" +msgstr[1] "پردازشگرهای محیط" + +#: templates/debug_toolbar/panels/timer.html:2 +msgid "Resource usage" +msgstr "استفاده منابع" + +#: templates/debug_toolbar/panels/timer.html:10 +msgid "Resource" +msgstr "منابع" + +#: templates/debug_toolbar/panels/timer.html:26 +msgid "Browser timing" +msgstr "زمان بندی مرورگر" + +#: templates/debug_toolbar/panels/timer.html:35 +msgid "Timing attribute" +msgstr "ویژگی زمان بندی" + +#: templates/debug_toolbar/panels/timer.html:37 +msgid "Milliseconds since navigation start (+length)" +msgstr "میلی‌ثانیه از آغاز ناوبری (+length)" + +#: templates/debug_toolbar/panels/versions.html:10 +msgid "Package" +msgstr "پکیج" + +#: templates/debug_toolbar/panels/versions.html:11 +msgid "Name" +msgstr "نام" + +#: templates/debug_toolbar/panels/versions.html:12 +msgid "Version" +msgstr "ورژن" + +#: templates/debug_toolbar/redirect.html:10 +msgid "Location:" +msgstr "مکان:" + +#: templates/debug_toolbar/redirect.html:12 +msgid "" +"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." +msgstr "نوار ابزار اشکال‌زدای Django یک هدایت به URL بالا را به منظور مشاهده اشکال توسط ابزار اشکال‌زدای افزونه کرده است. می‌توانید بر روی پیوند بالا کلیک کنید تا با هدایت به صورت عادی ادامه دهید." + +#: views.py:16 +msgid "" +"Data for this panel isn't available anymore. Please reload the page and " +"retry." +msgstr "دیتا برای نمایش در دسترس نیست. لطفا صفحه را ریفرش کنید." diff --git a/debug_toolbar/locale/fi/LC_MESSAGES/django.mo b/debug_toolbar/locale/fi/LC_MESSAGES/django.mo index 4b9ff457b..3c0054dc7 100644 Binary files a/debug_toolbar/locale/fi/LC_MESSAGES/django.mo and b/debug_toolbar/locale/fi/LC_MESSAGES/django.mo differ diff --git a/debug_toolbar/locale/fi/LC_MESSAGES/django.po b/debug_toolbar/locale/fi/LC_MESSAGES/django.po index 7e27b07d4..fed2c9759 100644 --- a/debug_toolbar/locale/fi/LC_MESSAGES/django.po +++ b/debug_toolbar/locale/fi/LC_MESSAGES/django.po @@ -1,71 +1,78 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# -# +# +# # Translators: -# nanook , 2012 +# Klaus Dahlén, 2012 msgid "" msgstr "" "Project-Id-Version: Django Debug Toolbar\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-04-25 21:52+0200\n" -"PO-Revision-Date: 2014-04-25 19:53+0000\n" -"Last-Translator: Aymeric Augustin \n" -"Language-Team: Finnish (http://www.transifex.com/projects/p/django-debug-toolbar/language/fi/)\n" +"POT-Creation-Date: 2024-08-06 07:12-0500\n" +"PO-Revision-Date: 2010-11-30 00:00+0000\n" +"Last-Translator: Klaus Dahlén, 2012\n" +"Language-Team: Finnish (http://app.transifex.com/django-debug-toolbar/django-debug-toolbar/language/fi/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: fi\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: apps.py:11 +#: apps.py:18 msgid "Debug Toolbar" msgstr "" -#: views.py:14 +#: panels/alerts.py:67 +#, python-brace-format msgid "" -"Data for this panel isn't available anymore. Please reload the page and " -"retry." +"Form with id \"{form_id}\" contains file input, but does not have the " +"attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:70 +msgid "" +"Form contains file input, but does not have the attribute " +"enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:73 +#, python-brace-format +msgid "" +"Input element references form with id \"{form_id}\", but the form does not " +"have the attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:77 +msgid "Alerts" msgstr "" -#: panels/cache.py:191 +#: panels/cache.py:168 msgid "Cache" msgstr "Välimuisti" -#: panels/cache.py:196 +#: panels/cache.py:174 #, python-format msgid "%(cache_calls)d call in %(time).2fms" msgid_plural "%(cache_calls)d calls in %(time).2fms" msgstr[0] "%(cache_calls)d kutsu %(time).2fms" msgstr[1] "%(cache_calls)d kutsua %(time).2fms" -#: panels/cache.py:204 +#: panels/cache.py:183 #, python-format msgid "Cache calls from %(count)d backend" msgid_plural "Cache calls from %(count)d backends" msgstr[0] "" msgstr[1] "" -#: panels/headers.py:35 +#: panels/headers.py:31 msgid "Headers" msgstr "" -#: panels/logging.py:64 -msgid "Logging" -msgstr "Loki" - -#: panels/logging.py:70 -#, python-format -msgid "%(count)s message" -msgid_plural "%(count)s messages" -msgstr[0] "%(count)s viesti" -msgstr[1] "%(count)s viestiä" - -#: panels/logging.py:73 -msgid "Log messages" +#: panels/history/panel.py:19 panels/history/panel.py:20 +msgid "History" msgstr "" -#: panels/profiling.py:127 +#: panels/profiling.py:140 msgid "Profiling" msgstr "Profilointi" @@ -73,213 +80,228 @@ msgstr "Profilointi" msgid "Intercept redirects" msgstr "" -#: panels/request.py:18 +#: panels/request.py:16 msgid "Request" msgstr "" -#: panels/request.py:35 +#: panels/request.py:38 msgid "" msgstr "" -#: panels/request.py:47 +#: panels/request.py:55 msgid "" msgstr "" -#: panels/settings.py:20 +#: panels/settings.py:17 msgid "Settings" msgstr "Asetukset" -#: panels/settings.py:23 +#: panels/settings.py:20 #, python-format -msgid "Settings from %s" -msgstr "Asetukset tiedostosta %s" +msgid "Settings from %s" +msgstr "" -#: panels/signals.py:45 +#: panels/signals.py:57 #, python-format msgid "%(num_receivers)d receiver of 1 signal" msgid_plural "%(num_receivers)d receivers of 1 signal" msgstr[0] "%(num_receivers)d vastaanotin 1 signaalille" msgstr[1] "%(num_receivers)d vastaanotinta 1 signaalille" -#: panels/signals.py:48 +#: panels/signals.py:62 #, python-format msgid "%(num_receivers)d receiver of %(num_signals)d signals" msgid_plural "%(num_receivers)d receivers of %(num_signals)d signals" msgstr[0] "%(num_receivers)d vastaanotin %(num_signals)d signaalille" msgstr[1] "%(num_receivers)d vastaanotinta %(num_signals)d signaalille" -#: panels/signals.py:53 +#: panels/signals.py:67 msgid "Signals" msgstr "Signaalit" -#: panels/staticfiles.py:89 +#: panels/sql/panel.py:30 panels/sql/panel.py:41 +msgid "Read uncommitted" +msgstr "" + +#: panels/sql/panel.py:31 panels/sql/panel.py:43 +msgid "Read committed" +msgstr "" + +#: panels/sql/panel.py:32 panels/sql/panel.py:45 +msgid "Repeatable read" +msgstr "" + +#: panels/sql/panel.py:33 panels/sql/panel.py:47 +msgid "Serializable" +msgstr "Muuttuja" + +#: panels/sql/panel.py:39 +msgid "Autocommit" +msgstr "Autocommit" + +#: panels/sql/panel.py:61 panels/sql/panel.py:71 +msgid "Idle" +msgstr "" + +#: panels/sql/panel.py:62 panels/sql/panel.py:72 +msgid "Active" +msgstr "Tapahtuma" + +#: panels/sql/panel.py:63 panels/sql/panel.py:73 +msgid "In transaction" +msgstr "Tapahtuman tila:" + +#: panels/sql/panel.py:64 panels/sql/panel.py:74 +msgid "In error" +msgstr "Virhe" + +#: panels/sql/panel.py:65 panels/sql/panel.py:75 +msgid "Unknown" +msgstr "(tuntematon)" + +#: panels/sql/panel.py:162 +msgid "SQL" +msgstr "SQL" + +#: panels/sql/panel.py:168 +#, python-format +msgid "%(query_count)d query in %(sql_time).2fms" +msgid_plural "%(query_count)d queries in %(sql_time).2fms" +msgstr[0] "" +msgstr[1] "" + +#: panels/sql/panel.py:180 +#, python-format +msgid "SQL queries from %(count)d connection" +msgid_plural "SQL queries from %(count)d connections" +msgstr[0] "" +msgstr[1] "" + +#: panels/staticfiles.py:82 #, python-format msgid "Static files (%(num_found)s found, %(num_used)s used)" msgstr "" -#: panels/staticfiles.py:107 +#: panels/staticfiles.py:103 msgid "Static files" msgstr "Staattiset tiedostot" -#: panels/staticfiles.py:112 +#: panels/staticfiles.py:109 #, python-format msgid "%(num_used)s file used" msgid_plural "%(num_used)s files used" msgstr[0] "" msgstr[1] "" -#: panels/timer.py:23 +#: panels/templates/panel.py:101 +msgid "Templates" +msgstr "Asettelupohjat" + +#: panels/templates/panel.py:106 +#, python-format +msgid "Templates (%(num_templates)s rendered)" +msgstr "Asetttelupohjat (%(num_templates)s renderöity)" + +#: panels/templates/panel.py:195 +msgid "No origin" +msgstr "" + +#: panels/timer.py:27 #, python-format msgid "CPU: %(cum)0.2fms (%(total)0.2fms)" msgstr "CPU: %(cum)0.2fms (%(total)0.2fms)" -#: panels/timer.py:28 +#: panels/timer.py:32 #, python-format msgid "Total: %0.2fms" msgstr "" -#: panels/timer.py:34 templates/debug_toolbar/panels/logging.html:7 +#: panels/timer.py:38 templates/debug_toolbar/panels/history.html:9 #: templates/debug_toolbar/panels/sql_explain.html:11 #: templates/debug_toolbar/panels/sql_profile.html:12 #: templates/debug_toolbar/panels/sql_select.html:11 msgid "Time" msgstr "Aika" -#: panels/timer.py:42 +#: panels/timer.py:46 msgid "User CPU time" msgstr "Käyttäjän CPU-aika" -#: panels/timer.py:42 +#: panels/timer.py:46 #, python-format msgid "%(utime)0.3f msec" msgstr "%(utime)0.3f msek" -#: panels/timer.py:43 +#: panels/timer.py:47 msgid "System CPU time" msgstr "Järjestelmän CPU-aika" -#: panels/timer.py:43 +#: panels/timer.py:47 #, python-format msgid "%(stime)0.3f msec" msgstr "%(stime)0.3f msek" -#: panels/timer.py:44 +#: panels/timer.py:48 msgid "Total CPU time" msgstr "CPU-aika yhteensä" -#: panels/timer.py:44 +#: panels/timer.py:48 #, python-format msgid "%(total)0.3f msec" msgstr "%(total)0.3f msek" -#: panels/timer.py:45 +#: panels/timer.py:49 msgid "Elapsed time" msgstr "Kulunut aika" -#: panels/timer.py:45 +#: panels/timer.py:49 #, python-format msgid "%(total_time)0.3f msec" msgstr "%(total_time)0.3f msek" -#: panels/timer.py:46 +#: panels/timer.py:51 msgid "Context switches" msgstr "Kontekstin vivut" -#: panels/timer.py:46 +#: panels/timer.py:52 #, python-format msgid "%(vcsw)d voluntary, %(ivcsw)d involuntary" msgstr "" -#: panels/versions.py:25 +#: panels/versions.py:19 msgid "Versions" msgstr "Versiot" -#: panels/sql/panel.py:22 -msgid "Autocommit" -msgstr "Autocommit" - -#: panels/sql/panel.py:23 -msgid "Read uncommitted" -msgstr "" - -#: panels/sql/panel.py:24 -msgid "Read committed" -msgstr "" - -#: panels/sql/panel.py:25 -msgid "Repeatable read" -msgstr "" - -#: panels/sql/panel.py:26 -msgid "Serializable" -msgstr "Muuttuja" - -#: panels/sql/panel.py:37 -msgid "Idle" -msgstr "" - -#: panels/sql/panel.py:38 -msgid "Active" -msgstr "Tapahtuma" - -#: panels/sql/panel.py:39 -msgid "In transaction" -msgstr "Tapahtuman tila:" - -#: panels/sql/panel.py:40 -msgid "In error" -msgstr "Virhe" - -#: panels/sql/panel.py:41 -msgid "Unknown" -msgstr "(tuntematon)" - -#: panels/sql/panel.py:105 -msgid "SQL" -msgstr "SQL" - -#: panels/templates/panel.py:141 -msgid "Templates" -msgstr "Asettelupohjat" - -#: panels/templates/panel.py:146 -#, python-format -msgid "Templates (%(num_templates)s rendered)" -msgstr "Asetttelupohjat (%(num_templates)s renderöity)" - -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide toolbar" msgstr "" -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide" msgstr "Piilota" -#: templates/debug_toolbar/base.html:25 -msgid "Disable for next and successive requests" +#: templates/debug_toolbar/base.html:25 templates/debug_toolbar/base.html:26 +msgid "Toggle Theme" msgstr "" -#: templates/debug_toolbar/base.html:25 -msgid "Enable for next and successive requests" +#: templates/debug_toolbar/base.html:35 +msgid "Show toolbar" msgstr "" -#: templates/debug_toolbar/base.html:47 -msgid "Show toolbar" +#: templates/debug_toolbar/includes/panel_button.html:4 +msgid "Disable for next and successive requests" msgstr "" -#: templates/debug_toolbar/base.html:53 -msgid "Close" -msgstr "Sulje" +#: templates/debug_toolbar/includes/panel_button.html:4 +msgid "Enable for next and successive requests" +msgstr "" -#: templates/debug_toolbar/redirect.html:8 -msgid "Location:" +#: templates/debug_toolbar/panels/alerts.html:4 +msgid "Alerts found" msgstr "" -#: templates/debug_toolbar/redirect.html:10 -msgid "" -"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." +#: templates/debug_toolbar/panels/alerts.html:11 +msgid "No alerts found" msgstr "" #: templates/debug_toolbar/panels/cache.html:2 @@ -311,7 +333,7 @@ msgid "Calls" msgstr "" #: templates/debug_toolbar/panels/cache.html:43 -#: templates/debug_toolbar/panels/sql.html:20 +#: templates/debug_toolbar/panels/sql.html:36 msgid "Time (ms)" msgstr "Aika (ms)" @@ -346,10 +368,8 @@ msgstr "Avain" #: templates/debug_toolbar/panels/headers.html:9 #: templates/debug_toolbar/panels/headers.html:28 #: templates/debug_toolbar/panels/headers.html:49 -#: templates/debug_toolbar/panels/request.html:33 -#: templates/debug_toolbar/panels/request.html:59 -#: templates/debug_toolbar/panels/request.html:85 -#: templates/debug_toolbar/panels/request.html:110 +#: templates/debug_toolbar/panels/history_tr.html:23 +#: templates/debug_toolbar/panels/request_variables.html:12 #: templates/debug_toolbar/panels/settings.html:6 #: templates/debug_toolbar/panels/timer.html:11 msgid "Value" @@ -369,26 +389,32 @@ msgid "" "significant subset is shown below." msgstr "" -#: templates/debug_toolbar/panels/logging.html:6 -msgid "Level" -msgstr "Taso" +#: templates/debug_toolbar/panels/history.html:10 +msgid "Method" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:8 -msgid "Channel" -msgstr "Kanava" +#: templates/debug_toolbar/panels/history.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:43 +msgid "Path" +msgstr "Polku" -#: templates/debug_toolbar/panels/logging.html:9 -msgid "Message" -msgstr "Viesti" +#: templates/debug_toolbar/panels/history.html:12 +msgid "Request Variables" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:10 -#: templates/debug_toolbar/panels/staticfiles.html:45 -msgid "Location" -msgstr "Sijainti" +#: templates/debug_toolbar/panels/history.html:13 +msgid "Status" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:26 -msgid "No messages logged" -msgstr "Ei viestejä lokissa" +#: templates/debug_toolbar/panels/history.html:14 +#: templates/debug_toolbar/panels/sql.html:37 +msgid "Action" +msgstr "Tapahtuma" + +#: templates/debug_toolbar/panels/history_tr.html:22 +#: templates/debug_toolbar/panels/request_variables.html:11 +msgid "Variable" +msgstr "Muuttuja" #: templates/debug_toolbar/panels/profiling.html:5 msgid "Call" @@ -427,38 +453,31 @@ msgstr "" msgid "Cookies" msgstr "" -#: templates/debug_toolbar/panels/request.html:32 -#: templates/debug_toolbar/panels/request.html:58 -#: templates/debug_toolbar/panels/request.html:84 -#: templates/debug_toolbar/panels/request.html:109 -msgid "Variable" -msgstr "Muuttuja" - -#: templates/debug_toolbar/panels/request.html:46 +#: templates/debug_toolbar/panels/request.html:27 msgid "No cookies" msgstr "" -#: templates/debug_toolbar/panels/request.html:50 +#: templates/debug_toolbar/panels/request.html:31 msgid "Session data" msgstr "" -#: templates/debug_toolbar/panels/request.html:72 +#: templates/debug_toolbar/panels/request.html:34 msgid "No session data" msgstr "" -#: templates/debug_toolbar/panels/request.html:76 +#: templates/debug_toolbar/panels/request.html:38 msgid "GET data" msgstr "" -#: templates/debug_toolbar/panels/request.html:98 +#: templates/debug_toolbar/panels/request.html:41 msgid "No GET data" msgstr "Ei GET-dataa" -#: templates/debug_toolbar/panels/request.html:102 +#: templates/debug_toolbar/panels/request.html:45 msgid "POST data" msgstr "" -#: templates/debug_toolbar/panels/request.html:123 +#: templates/debug_toolbar/panels/request.html:48 msgid "No POST data" msgstr "Ei POST-dataa" @@ -471,60 +490,69 @@ msgid "Signal" msgstr "Signaali" #: templates/debug_toolbar/panels/signals.html:6 -msgid "Providing" -msgstr "" - -#: templates/debug_toolbar/panels/signals.html:7 msgid "Receivers" msgstr "Vastaanottimet" -#: templates/debug_toolbar/panels/sql.html:7 +#: templates/debug_toolbar/panels/sql.html:6 #, python-format msgid "%(num)s query" msgid_plural "%(num)s queries" msgstr[0] "%(num)s kysely" msgstr[1] "%(num)s kyselyä" -#: templates/debug_toolbar/panels/sql.html:18 +#: templates/debug_toolbar/panels/sql.html:8 +#, python-format +msgid "" +"including %(count)s similar" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:12 +#, python-format +msgid "" +"and %(dupes)s duplicates" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:34 msgid "Query" msgstr "Kysely" -#: templates/debug_toolbar/panels/sql.html:19 +#: templates/debug_toolbar/panels/sql.html:35 #: templates/debug_toolbar/panels/timer.html:36 msgid "Timeline" msgstr "Aikajana" -#: templates/debug_toolbar/panels/sql.html:21 -msgid "Action" -msgstr "Tapahtuma" +#: templates/debug_toolbar/panels/sql.html:52 +#, python-format +msgid "%(count)s similar queries." +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:58 +#, python-format +msgid "Duplicated %(dupes)s times." +msgstr "" -#: templates/debug_toolbar/panels/sql.html:64 +#: templates/debug_toolbar/panels/sql.html:95 msgid "Connection:" msgstr "Yhteys:" -#: templates/debug_toolbar/panels/sql.html:66 +#: templates/debug_toolbar/panels/sql.html:97 msgid "Isolation level:" msgstr "Eristystaso:" -#: templates/debug_toolbar/panels/sql.html:69 +#: templates/debug_toolbar/panels/sql.html:100 msgid "Transaction status:" msgstr "Tapahtuman status:" -#: templates/debug_toolbar/panels/sql.html:83 +#: templates/debug_toolbar/panels/sql.html:114 msgid "(unknown)" msgstr "(tuntematon)" -#: templates/debug_toolbar/panels/sql.html:92 +#: templates/debug_toolbar/panels/sql.html:123 msgid "No SQL queries were recorded during this request." msgstr "Tämän pyynnön aikana ei tehty yhtään SQL-kyselyä." -#: templates/debug_toolbar/panels/sql_explain.html:3 -#: templates/debug_toolbar/panels/sql_profile.html:3 -#: templates/debug_toolbar/panels/sql_select.html:3 -#: templates/debug_toolbar/panels/template_source.html:3 -msgid "Back" -msgstr "Takaisin" - #: templates/debug_toolbar/panels/sql_explain.html:4 msgid "SQL explained" msgstr "" @@ -557,39 +585,39 @@ msgstr "" msgid "Empty set" msgstr "Tyhjä joukko" -#: templates/debug_toolbar/panels/staticfiles.html:4 +#: templates/debug_toolbar/panels/staticfiles.html:3 msgid "Static file path" msgid_plural "Static file paths" msgstr[0] "" msgstr[1] "" -#: templates/debug_toolbar/panels/staticfiles.html:8 +#: templates/debug_toolbar/panels/staticfiles.html:7 #, python-format msgid "(prefix %(prefix)s)" msgstr "" -#: templates/debug_toolbar/panels/staticfiles.html:12 -#: templates/debug_toolbar/panels/staticfiles.html:23 -#: templates/debug_toolbar/panels/staticfiles.html:35 +#: templates/debug_toolbar/panels/staticfiles.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:22 +#: templates/debug_toolbar/panels/staticfiles.html:34 #: templates/debug_toolbar/panels/templates.html:10 -#: templates/debug_toolbar/panels/templates.html:28 -#: templates/debug_toolbar/panels/templates.html:43 +#: templates/debug_toolbar/panels/templates.html:30 +#: templates/debug_toolbar/panels/templates.html:47 msgid "None" msgstr "None" -#: templates/debug_toolbar/panels/staticfiles.html:15 +#: templates/debug_toolbar/panels/staticfiles.html:14 msgid "Static file app" msgid_plural "Static file apps" msgstr[0] "" msgstr[1] "" -#: templates/debug_toolbar/panels/staticfiles.html:26 +#: templates/debug_toolbar/panels/staticfiles.html:25 msgid "Static file" msgid_plural "Static files" msgstr[0] "" msgstr[1] "Staattiset tiedostot" -#: templates/debug_toolbar/panels/staticfiles.html:40 +#: templates/debug_toolbar/panels/staticfiles.html:39 #, python-format msgid "%(payload_count)s file" msgid_plural "%(payload_count)s files" @@ -597,8 +625,8 @@ msgstr[0] "" msgstr[1] "" #: templates/debug_toolbar/panels/staticfiles.html:44 -msgid "Path" -msgstr "Polku" +msgid "Location" +msgstr "Sijainti" #: templates/debug_toolbar/panels/template_source.html:4 msgid "Template source:" @@ -616,12 +644,12 @@ msgid_plural "Templates" msgstr[0] "Sivupohja" msgstr[1] "Sivupohja" -#: templates/debug_toolbar/panels/templates.html:21 -#: templates/debug_toolbar/panels/templates.html:37 +#: templates/debug_toolbar/panels/templates.html:22 +#: templates/debug_toolbar/panels/templates.html:40 msgid "Toggle context" msgstr "" -#: templates/debug_toolbar/panels/templates.html:31 +#: templates/debug_toolbar/panels/templates.html:33 msgid "Context processor" msgid_plural "Context processors" msgstr[0] "Kontekstiprosessori" @@ -647,10 +675,31 @@ msgstr "" msgid "Milliseconds since navigation start (+length)" msgstr "" -#: templates/debug_toolbar/panels/versions.html:5 +#: templates/debug_toolbar/panels/versions.html:10 +msgid "Package" +msgstr "" + +#: templates/debug_toolbar/panels/versions.html:11 msgid "Name" msgstr "Nimi" -#: templates/debug_toolbar/panels/versions.html:6 +#: templates/debug_toolbar/panels/versions.html:12 msgid "Version" msgstr "Versio" + +#: templates/debug_toolbar/redirect.html:10 +msgid "Location:" +msgstr "" + +#: templates/debug_toolbar/redirect.html:12 +msgid "" +"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." +msgstr "" + +#: views.py:16 +msgid "" +"Data for this panel isn't available anymore. Please reload the page and " +"retry." +msgstr "" diff --git a/debug_toolbar/locale/fr/LC_MESSAGES/django.mo b/debug_toolbar/locale/fr/LC_MESSAGES/django.mo index 74f7470f1..559e2d847 100644 Binary files a/debug_toolbar/locale/fr/LC_MESSAGES/django.mo and b/debug_toolbar/locale/fr/LC_MESSAGES/django.mo differ diff --git a/debug_toolbar/locale/fr/LC_MESSAGES/django.po b/debug_toolbar/locale/fr/LC_MESSAGES/django.po index 586811144..88c04b0c5 100644 --- a/debug_toolbar/locale/fr/LC_MESSAGES/django.po +++ b/debug_toolbar/locale/fr/LC_MESSAGES/django.po @@ -1,74 +1,86 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# -# +# +# # Translators: -# Pingax , 2013 -# claudep , 2013 +# Anthony Jorion , 2013 +# c1b16e6c929c50740e884a23aafc8829_00449d9 , 2014 +# Claude Paroz , 2013 +# Colin O'Brien , 2021 # David Paccoud, 2009 -# drivard , 2013 +# Dominick Rivard , 2013 +# Maxime Abry , 2016 msgid "" msgstr "" "Project-Id-Version: Django Debug Toolbar\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-04-25 21:52+0200\n" -"PO-Revision-Date: 2014-04-25 19:53+0000\n" -"Last-Translator: Aymeric Augustin \n" -"Language-Team: French (http://www.transifex.com/projects/p/django-debug-toolbar/language/fr/)\n" +"POT-Creation-Date: 2024-08-06 07:12-0500\n" +"PO-Revision-Date: 2010-11-30 00:00+0000\n" +"Last-Translator: Colin O'Brien , 2021\n" +"Language-Team: French (http://app.transifex.com/django-debug-toolbar/django-debug-toolbar/language/fr/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: fr\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" -#: apps.py:11 +#: apps.py:18 msgid "Debug Toolbar" +msgstr "Barre d'outils de débogage" + +#: panels/alerts.py:67 +#, python-brace-format +msgid "" +"Form with id \"{form_id}\" contains file input, but does not have the " +"attribute enctype=\"multipart/form-data\"." msgstr "" -#: views.py:14 +#: panels/alerts.py:70 msgid "" -"Data for this panel isn't available anymore. Please reload the page and " -"retry." -msgstr "Les données de ce panneau ne sont plus disponibles. Rechargez la page et essayez à nouveau." +"Form contains file input, but does not have the attribute " +"enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:73 +#, python-brace-format +msgid "" +"Input element references form with id \"{form_id}\", but the form does not " +"have the attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:77 +msgid "Alerts" +msgstr "" -#: panels/cache.py:191 +#: panels/cache.py:168 msgid "Cache" msgstr "Cache" -#: panels/cache.py:196 +#: panels/cache.py:174 #, python-format msgid "%(cache_calls)d call in %(time).2fms" msgid_plural "%(cache_calls)d calls in %(time).2fms" msgstr[0] "%(cache_calls)d appel en %(time).2fms" msgstr[1] "%(cache_calls)d appels en %(time).2fms" +msgstr[2] "%(cache_calls)d appels en %(time).2fms" -#: panels/cache.py:204 +#: panels/cache.py:183 #, python-format msgid "Cache calls from %(count)d backend" msgid_plural "Cache calls from %(count)d backends" msgstr[0] "Appels au cache depuis %(count)d moteur" msgstr[1] "Appels au cache depuis %(count)d moteurs" +msgstr[2] "Appels au cache depuis %(count)d moteurs" -#: panels/headers.py:35 +#: panels/headers.py:31 msgid "Headers" msgstr "En-têtes" -#: panels/logging.py:64 -msgid "Logging" -msgstr "Journaux" +#: panels/history/panel.py:19 panels/history/panel.py:20 +msgid "History" +msgstr "Historique" -#: panels/logging.py:70 -#, python-format -msgid "%(count)s message" -msgid_plural "%(count)s messages" -msgstr[0] "%(count)s message" -msgstr[1] "%(count)s messages" - -#: panels/logging.py:73 -msgid "Log messages" -msgstr "Messages du journal" - -#: panels/profiling.py:127 +#: panels/profiling.py:140 msgid "Profiling" msgstr "Profilage" @@ -76,214 +88,234 @@ msgstr "Profilage" msgid "Intercept redirects" msgstr "Interception des redirections" -#: panels/request.py:18 +#: panels/request.py:16 msgid "Request" msgstr "Requête" -#: panels/request.py:35 +#: panels/request.py:38 msgid "" msgstr "" -#: panels/request.py:47 +#: panels/request.py:55 msgid "" msgstr "" -#: panels/settings.py:20 +#: panels/settings.py:17 msgid "Settings" msgstr "Paramètres" -#: panels/settings.py:23 +#: panels/settings.py:20 #, python-format -msgid "Settings from %s" -msgstr "Paramètres de %s" +msgid "Settings from %s" +msgstr "Paramètres de %s" -#: panels/signals.py:45 +#: panels/signals.py:57 #, python-format msgid "%(num_receivers)d receiver of 1 signal" msgid_plural "%(num_receivers)d receivers of 1 signal" msgstr[0] "%(num_receivers)d receveur d'un signal" msgstr[1] "%(num_receivers)d receveurs d'un signal" +msgstr[2] "%(num_receivers)d receveurs d'un signal" -#: panels/signals.py:48 +#: panels/signals.py:62 #, python-format msgid "%(num_receivers)d receiver of %(num_signals)d signals" msgid_plural "%(num_receivers)d receivers of %(num_signals)d signals" msgstr[0] "%(num_receivers)d receveur de %(num_signals)d signaux" msgstr[1] "%(num_receivers)d receveurs de %(num_signals)d signaux" +msgstr[2] "%(num_receivers)d receveurs de %(num_signals)d signaux" -#: panels/signals.py:53 +#: panels/signals.py:67 msgid "Signals" msgstr "Signaux" -#: panels/staticfiles.py:89 +#: panels/sql/panel.py:30 panels/sql/panel.py:41 +msgid "Read uncommitted" +msgstr "Lecture non validée" + +#: panels/sql/panel.py:31 panels/sql/panel.py:43 +msgid "Read committed" +msgstr "Lecture validée" + +#: panels/sql/panel.py:32 panels/sql/panel.py:45 +msgid "Repeatable read" +msgstr "Lecture répétable" + +#: panels/sql/panel.py:33 panels/sql/panel.py:47 +msgid "Serializable" +msgstr "Sérialisable" + +#: panels/sql/panel.py:39 +msgid "Autocommit" +msgstr "Auto validation" + +#: panels/sql/panel.py:61 panels/sql/panel.py:71 +msgid "Idle" +msgstr "Inactif" + +#: panels/sql/panel.py:62 panels/sql/panel.py:72 +msgid "Active" +msgstr "Actif" + +#: panels/sql/panel.py:63 panels/sql/panel.py:73 +msgid "In transaction" +msgstr "Transaction en cours" + +#: panels/sql/panel.py:64 panels/sql/panel.py:74 +msgid "In error" +msgstr "Erreur" + +#: panels/sql/panel.py:65 panels/sql/panel.py:75 +msgid "Unknown" +msgstr "Indéterminé" + +#: panels/sql/panel.py:162 +msgid "SQL" +msgstr "SQL" + +#: panels/sql/panel.py:168 +#, python-format +msgid "%(query_count)d query in %(sql_time).2fms" +msgid_plural "%(query_count)d queries in %(sql_time).2fms" +msgstr[0] "%(query_count)d requête en %(sql_time).2f ms" +msgstr[1] "%(query_count)d requêtes en %(sql_time).2f ms" +msgstr[2] "%(query_count)d requêtes en %(sql_time).2f ms" + +#: panels/sql/panel.py:180 +#, python-format +msgid "SQL queries from %(count)d connection" +msgid_plural "SQL queries from %(count)d connections" +msgstr[0] "requêtes SQL venant de %(count)d connexion" +msgstr[1] "Requêtes SQL venant de %(count)d connexions" +msgstr[2] "Requêtes SQL venant de %(count)d connexions" + +#: panels/staticfiles.py:82 #, python-format msgid "Static files (%(num_found)s found, %(num_used)s used)" msgstr "Fichiers statiques (%(num_found)s trouvé(s), %(num_used)s utilisé(s))" -#: panels/staticfiles.py:107 +#: panels/staticfiles.py:103 msgid "Static files" msgstr "Fichiers statiques" -#: panels/staticfiles.py:112 +#: panels/staticfiles.py:109 #, python-format msgid "%(num_used)s file used" msgid_plural "%(num_used)s files used" msgstr[0] "%(num_used)s fichier utilisé" msgstr[1] "%(num_used)s fichiers utilisés" +msgstr[2] "%(num_used)s fichiers utilisés" -#: panels/timer.py:23 +#: panels/templates/panel.py:101 +msgid "Templates" +msgstr "Gabarits" + +#: panels/templates/panel.py:106 +#, python-format +msgid "Templates (%(num_templates)s rendered)" +msgstr "Gabarits (%(num_templates)s affichés)" + +#: panels/templates/panel.py:195 +msgid "No origin" +msgstr "Sans Origine" + +#: panels/timer.py:27 #, python-format msgid "CPU: %(cum)0.2fms (%(total)0.2fms)" msgstr "CPU: %(cum)0.2fms (%(total)0.2fms)" -#: panels/timer.py:28 +#: panels/timer.py:32 #, python-format msgid "Total: %0.2fms" msgstr "Total : %0.2fms" -#: panels/timer.py:34 templates/debug_toolbar/panels/logging.html:7 +#: panels/timer.py:38 templates/debug_toolbar/panels/history.html:9 #: templates/debug_toolbar/panels/sql_explain.html:11 #: templates/debug_toolbar/panels/sql_profile.html:12 #: templates/debug_toolbar/panels/sql_select.html:11 msgid "Time" msgstr "Temps" -#: panels/timer.py:42 +#: panels/timer.py:46 msgid "User CPU time" msgstr "Temps CPU de l'utilisateur" -#: panels/timer.py:42 +#: panels/timer.py:46 #, python-format msgid "%(utime)0.3f msec" msgstr "%(utime)0.3f ms" -#: panels/timer.py:43 +#: panels/timer.py:47 msgid "System CPU time" msgstr "Temps CPU du système" -#: panels/timer.py:43 +#: panels/timer.py:47 #, python-format msgid "%(stime)0.3f msec" msgstr "%(stime)0.3f ms" -#: panels/timer.py:44 +#: panels/timer.py:48 msgid "Total CPU time" msgstr "Temps total du CPU" -#: panels/timer.py:44 +#: panels/timer.py:48 #, python-format msgid "%(total)0.3f msec" msgstr "%(total)0.3f ms" -#: panels/timer.py:45 +#: panels/timer.py:49 msgid "Elapsed time" msgstr "Temps écoulé" -#: panels/timer.py:45 +#: panels/timer.py:49 #, python-format msgid "%(total_time)0.3f msec" msgstr "%(total_time)0.3f ms" -#: panels/timer.py:46 +#: panels/timer.py:51 msgid "Context switches" msgstr "Basculements de contexte" -#: panels/timer.py:46 +#: panels/timer.py:52 #, python-format msgid "%(vcsw)d voluntary, %(ivcsw)d involuntary" msgstr "%(vcsw)d volontaire, %(ivcsw)d involontaire" -#: panels/versions.py:25 +#: panels/versions.py:19 msgid "Versions" msgstr "Versions" -#: panels/sql/panel.py:22 -msgid "Autocommit" -msgstr "Auto validation" - -#: panels/sql/panel.py:23 -msgid "Read uncommitted" -msgstr "Lecture non validée" - -#: panels/sql/panel.py:24 -msgid "Read committed" -msgstr "Lecture validée" - -#: panels/sql/panel.py:25 -msgid "Repeatable read" -msgstr "Lecture répétable" - -#: panels/sql/panel.py:26 -msgid "Serializable" -msgstr "Sérialisable" - -#: panels/sql/panel.py:37 -msgid "Idle" -msgstr "Inactif" - -#: panels/sql/panel.py:38 -msgid "Active" -msgstr "Actif" - -#: panels/sql/panel.py:39 -msgid "In transaction" -msgstr "Transaction en cours" - -#: panels/sql/panel.py:40 -msgid "In error" -msgstr "Erreur" - -#: panels/sql/panel.py:41 -msgid "Unknown" -msgstr "Indéterminé" - -#: panels/sql/panel.py:105 -msgid "SQL" -msgstr "SQL" - -#: panels/templates/panel.py:141 -msgid "Templates" -msgstr "Gabarits" - -#: panels/templates/panel.py:146 -#, python-format -msgid "Templates (%(num_templates)s rendered)" -msgstr "Gabarits (%(num_templates)s affichés)" - -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide toolbar" msgstr "Masquer la barre d'outils" -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide" msgstr "Masquer" -#: templates/debug_toolbar/base.html:25 +#: templates/debug_toolbar/base.html:25 templates/debug_toolbar/base.html:26 +msgid "Toggle Theme" +msgstr "" + +#: templates/debug_toolbar/base.html:35 +msgid "Show toolbar" +msgstr "Afficher la barre d'outils" + +#: templates/debug_toolbar/includes/panel_button.html:4 msgid "Disable for next and successive requests" msgstr "Désactiver pour les requêtes suivantes" -#: templates/debug_toolbar/base.html:25 +#: templates/debug_toolbar/includes/panel_button.html:4 msgid "Enable for next and successive requests" msgstr "Activer pour les requêtes suivantes" -#: templates/debug_toolbar/base.html:47 -msgid "Show toolbar" -msgstr "Afficher la barre d'outils" - -#: templates/debug_toolbar/base.html:53 -msgid "Close" -msgstr "Fermer" - -#: templates/debug_toolbar/redirect.html:8 -msgid "Location:" -msgstr "Emplacement :" +#: templates/debug_toolbar/panels/alerts.html:4 +msgid "Alerts found" +msgstr "" -#: templates/debug_toolbar/redirect.html:10 -msgid "" -"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." -msgstr "La barre de débogage Django a intercepté une redirection vers l'URL ci-dessus afin de permettre la consultation des messages de débogage. Vous pouvez cliquer sur le lien ci-dessus pour continuer normalement avec la redirection." +#: templates/debug_toolbar/panels/alerts.html:11 +msgid "No alerts found" +msgstr "" #: templates/debug_toolbar/panels/cache.html:2 msgid "Summary" @@ -314,7 +346,7 @@ msgid "Calls" msgstr "Appels" #: templates/debug_toolbar/panels/cache.html:43 -#: templates/debug_toolbar/panels/sql.html:20 +#: templates/debug_toolbar/panels/sql.html:36 msgid "Time (ms)" msgstr "Temps (ms)" @@ -349,10 +381,8 @@ msgstr "Clé" #: templates/debug_toolbar/panels/headers.html:9 #: templates/debug_toolbar/panels/headers.html:28 #: templates/debug_toolbar/panels/headers.html:49 -#: templates/debug_toolbar/panels/request.html:33 -#: templates/debug_toolbar/panels/request.html:59 -#: templates/debug_toolbar/panels/request.html:85 -#: templates/debug_toolbar/panels/request.html:110 +#: templates/debug_toolbar/panels/history_tr.html:23 +#: templates/debug_toolbar/panels/request_variables.html:12 #: templates/debug_toolbar/panels/settings.html:6 #: templates/debug_toolbar/panels/timer.html:11 msgid "Value" @@ -372,26 +402,32 @@ msgid "" "significant subset is shown below." msgstr "Comme l'environnement WSGI hérite de celui du serveur, seul un sous-ensemble pertinent est affiché ci-dessous." -#: templates/debug_toolbar/panels/logging.html:6 -msgid "Level" -msgstr "Niveau" +#: templates/debug_toolbar/panels/history.html:10 +msgid "Method" +msgstr "Méthode" -#: templates/debug_toolbar/panels/logging.html:8 -msgid "Channel" -msgstr "Canal" +#: templates/debug_toolbar/panels/history.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:43 +msgid "Path" +msgstr "Chemin" -#: templates/debug_toolbar/panels/logging.html:9 -msgid "Message" -msgstr "Message" +#: templates/debug_toolbar/panels/history.html:12 +msgid "Request Variables" +msgstr "Variables de Requête" -#: templates/debug_toolbar/panels/logging.html:10 -#: templates/debug_toolbar/panels/staticfiles.html:45 -msgid "Location" -msgstr "Emplacement" +#: templates/debug_toolbar/panels/history.html:13 +msgid "Status" +msgstr "État" + +#: templates/debug_toolbar/panels/history.html:14 +#: templates/debug_toolbar/panels/sql.html:37 +msgid "Action" +msgstr "Action" -#: templates/debug_toolbar/panels/logging.html:26 -msgid "No messages logged" -msgstr "Aucun message dans le journal" +#: templates/debug_toolbar/panels/history_tr.html:22 +#: templates/debug_toolbar/panels/request_variables.html:11 +msgid "Variable" +msgstr "Variable" #: templates/debug_toolbar/panels/profiling.html:5 msgid "Call" @@ -430,38 +466,31 @@ msgstr "Nom d'URL" msgid "Cookies" msgstr "Cookies" -#: templates/debug_toolbar/panels/request.html:32 -#: templates/debug_toolbar/panels/request.html:58 -#: templates/debug_toolbar/panels/request.html:84 -#: templates/debug_toolbar/panels/request.html:109 -msgid "Variable" -msgstr "Variable" - -#: templates/debug_toolbar/panels/request.html:46 +#: templates/debug_toolbar/panels/request.html:27 msgid "No cookies" msgstr "Pas de cookies" -#: templates/debug_toolbar/panels/request.html:50 +#: templates/debug_toolbar/panels/request.html:31 msgid "Session data" msgstr "Données de session" -#: templates/debug_toolbar/panels/request.html:72 +#: templates/debug_toolbar/panels/request.html:34 msgid "No session data" msgstr "Pas de données de session" -#: templates/debug_toolbar/panels/request.html:76 +#: templates/debug_toolbar/panels/request.html:38 msgid "GET data" msgstr "Données GET" -#: templates/debug_toolbar/panels/request.html:98 +#: templates/debug_toolbar/panels/request.html:41 msgid "No GET data" msgstr "Aucune donnée GET" -#: templates/debug_toolbar/panels/request.html:102 +#: templates/debug_toolbar/panels/request.html:45 msgid "POST data" msgstr "Données POST" -#: templates/debug_toolbar/panels/request.html:123 +#: templates/debug_toolbar/panels/request.html:48 msgid "No POST data" msgstr "Aucune donnée POST" @@ -474,60 +503,70 @@ msgid "Signal" msgstr "Signal" #: templates/debug_toolbar/panels/signals.html:6 -msgid "Providing" -msgstr "Fournissant" - -#: templates/debug_toolbar/panels/signals.html:7 msgid "Receivers" msgstr "Receveurs" -#: templates/debug_toolbar/panels/sql.html:7 +#: templates/debug_toolbar/panels/sql.html:6 #, python-format msgid "%(num)s query" msgid_plural "%(num)s queries" msgstr[0] "%(num)s requête" msgstr[1] "%(num)s requêtes" +msgstr[2] "%(num)s requêtes" + +#: templates/debug_toolbar/panels/sql.html:8 +#, python-format +msgid "" +"including %(count)s similar" +msgstr "comprenant %(count)s similaires" + +#: templates/debug_toolbar/panels/sql.html:12 +#, python-format +msgid "" +"and %(dupes)s duplicates" +msgstr "et %(dupes)s en double" -#: templates/debug_toolbar/panels/sql.html:18 +#: templates/debug_toolbar/panels/sql.html:34 msgid "Query" msgstr "Requête" -#: templates/debug_toolbar/panels/sql.html:19 +#: templates/debug_toolbar/panels/sql.html:35 #: templates/debug_toolbar/panels/timer.html:36 msgid "Timeline" msgstr "Chronologie" -#: templates/debug_toolbar/panels/sql.html:21 -msgid "Action" -msgstr "Action" +#: templates/debug_toolbar/panels/sql.html:52 +#, python-format +msgid "%(count)s similar queries." +msgstr "%(count)s requêtes similaires." -#: templates/debug_toolbar/panels/sql.html:64 +#: templates/debug_toolbar/panels/sql.html:58 +#, python-format +msgid "Duplicated %(dupes)s times." +msgstr "Dupliqué %(dupes)s fois." + +#: templates/debug_toolbar/panels/sql.html:95 msgid "Connection:" msgstr "Connexion :" -#: templates/debug_toolbar/panels/sql.html:66 +#: templates/debug_toolbar/panels/sql.html:97 msgid "Isolation level:" msgstr "Niveau d'isolation :" -#: templates/debug_toolbar/panels/sql.html:69 +#: templates/debug_toolbar/panels/sql.html:100 msgid "Transaction status:" msgstr "État de la transaction :" -#: templates/debug_toolbar/panels/sql.html:83 +#: templates/debug_toolbar/panels/sql.html:114 msgid "(unknown)" msgstr "(indéterminé)" -#: templates/debug_toolbar/panels/sql.html:92 +#: templates/debug_toolbar/panels/sql.html:123 msgid "No SQL queries were recorded during this request." msgstr "Aucune requête SQL n'a été enregistrée durant cette requête." -#: templates/debug_toolbar/panels/sql_explain.html:3 -#: templates/debug_toolbar/panels/sql_profile.html:3 -#: templates/debug_toolbar/panels/sql_select.html:3 -#: templates/debug_toolbar/panels/template_source.html:3 -msgid "Back" -msgstr "Retour" - #: templates/debug_toolbar/panels/sql_explain.html:4 msgid "SQL explained" msgstr "SQL expliqué" @@ -560,48 +599,52 @@ msgstr "SQL sélectionné" msgid "Empty set" msgstr "Ensemble vide" -#: templates/debug_toolbar/panels/staticfiles.html:4 +#: templates/debug_toolbar/panels/staticfiles.html:3 msgid "Static file path" msgid_plural "Static file paths" msgstr[0] "Chemin de fichier statique" msgstr[1] "Chemins de fichiers statiques" +msgstr[2] "Chemins de fichiers statiques" -#: templates/debug_toolbar/panels/staticfiles.html:8 +#: templates/debug_toolbar/panels/staticfiles.html:7 #, python-format msgid "(prefix %(prefix)s)" -msgstr "" +msgstr "(préfixe %(prefix)s)" -#: templates/debug_toolbar/panels/staticfiles.html:12 -#: templates/debug_toolbar/panels/staticfiles.html:23 -#: templates/debug_toolbar/panels/staticfiles.html:35 +#: templates/debug_toolbar/panels/staticfiles.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:22 +#: templates/debug_toolbar/panels/staticfiles.html:34 #: templates/debug_toolbar/panels/templates.html:10 -#: templates/debug_toolbar/panels/templates.html:28 -#: templates/debug_toolbar/panels/templates.html:43 +#: templates/debug_toolbar/panels/templates.html:30 +#: templates/debug_toolbar/panels/templates.html:47 msgid "None" msgstr "Aucun" -#: templates/debug_toolbar/panels/staticfiles.html:15 +#: templates/debug_toolbar/panels/staticfiles.html:14 msgid "Static file app" msgid_plural "Static file apps" msgstr[0] "Application de fichiers statiques" msgstr[1] "Applications de fichiers statiques" +msgstr[2] "Applications de fichiers statiques" -#: templates/debug_toolbar/panels/staticfiles.html:26 +#: templates/debug_toolbar/panels/staticfiles.html:25 msgid "Static file" msgid_plural "Static files" msgstr[0] "" msgstr[1] "Fichiers statiques" +msgstr[2] "Fichiers statiques" -#: templates/debug_toolbar/panels/staticfiles.html:40 +#: templates/debug_toolbar/panels/staticfiles.html:39 #, python-format msgid "%(payload_count)s file" msgid_plural "%(payload_count)s files" msgstr[0] "%(payload_count)s fichier" msgstr[1] "%(payload_count)s fichiers" +msgstr[2] "%(payload_count)s fichiers" #: templates/debug_toolbar/panels/staticfiles.html:44 -msgid "Path" -msgstr "Chemin" +msgid "Location" +msgstr "Emplacement" #: templates/debug_toolbar/panels/template_source.html:4 msgid "Template source:" @@ -612,23 +655,26 @@ msgid "Template path" msgid_plural "Template paths" msgstr[0] "" msgstr[1] "Chemin du gabarit" +msgstr[2] "Chemin du gabarit" #: templates/debug_toolbar/panels/templates.html:13 msgid "Template" msgid_plural "Templates" msgstr[0] "" msgstr[1] "Gabarit" +msgstr[2] "Gabarit" -#: templates/debug_toolbar/panels/templates.html:21 -#: templates/debug_toolbar/panels/templates.html:37 +#: templates/debug_toolbar/panels/templates.html:22 +#: templates/debug_toolbar/panels/templates.html:40 msgid "Toggle context" msgstr "Afficher/masquer le contexte" -#: templates/debug_toolbar/panels/templates.html:31 +#: templates/debug_toolbar/panels/templates.html:33 msgid "Context processor" msgid_plural "Context processors" msgstr[0] "Processeur de contexte" msgstr[1] "Processeurs de contexte" +msgstr[2] "Processeurs de contexte" #: templates/debug_toolbar/panels/timer.html:2 msgid "Resource usage" @@ -650,10 +696,31 @@ msgstr "Attribut mesuré" msgid "Milliseconds since navigation start (+length)" msgstr "Millisecondes depuis le début de la navigation (+longueur)" -#: templates/debug_toolbar/panels/versions.html:5 +#: templates/debug_toolbar/panels/versions.html:10 +msgid "Package" +msgstr "Paquet" + +#: templates/debug_toolbar/panels/versions.html:11 msgid "Name" msgstr "Nom" -#: templates/debug_toolbar/panels/versions.html:6 +#: templates/debug_toolbar/panels/versions.html:12 msgid "Version" msgstr "Version" + +#: templates/debug_toolbar/redirect.html:10 +msgid "Location:" +msgstr "Emplacement :" + +#: templates/debug_toolbar/redirect.html:12 +msgid "" +"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." +msgstr "La barre de débogage Django a intercepté une redirection vers l'URL ci-dessus afin de permettre la consultation des messages de débogage. Vous pouvez cliquer sur le lien ci-dessus pour continuer normalement avec la redirection." + +#: views.py:16 +msgid "" +"Data for this panel isn't available anymore. Please reload the page and " +"retry." +msgstr "Les données de ce panneau ne sont plus disponibles. Rechargez la page et essayez à nouveau." diff --git a/debug_toolbar/locale/he/LC_MESSAGES/django.mo b/debug_toolbar/locale/he/LC_MESSAGES/django.mo index 08c8cd4a3..4f680148c 100644 Binary files a/debug_toolbar/locale/he/LC_MESSAGES/django.mo and b/debug_toolbar/locale/he/LC_MESSAGES/django.mo differ diff --git a/debug_toolbar/locale/he/LC_MESSAGES/django.po b/debug_toolbar/locale/he/LC_MESSAGES/django.po index 66f13742f..c766a77a7 100644 --- a/debug_toolbar/locale/he/LC_MESSAGES/django.po +++ b/debug_toolbar/locale/he/LC_MESSAGES/django.po @@ -1,71 +1,80 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# -# +# +# # Translators: # shaib , 2012 msgid "" msgstr "" "Project-Id-Version: Django Debug Toolbar\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-04-25 21:52+0200\n" -"PO-Revision-Date: 2014-04-25 19:53+0000\n" -"Last-Translator: Aymeric Augustin \n" -"Language-Team: Hebrew (http://www.transifex.com/projects/p/django-debug-toolbar/language/he/)\n" +"POT-Creation-Date: 2024-08-06 07:12-0500\n" +"PO-Revision-Date: 2010-11-30 00:00+0000\n" +"Last-Translator: shaib , 2012\n" +"Language-Team: Hebrew (http://app.transifex.com/django-debug-toolbar/django-debug-toolbar/language/he/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: he\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: nplurals=3; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: 2;\n" -#: apps.py:11 +#: apps.py:18 msgid "Debug Toolbar" msgstr "" -#: views.py:14 +#: panels/alerts.py:67 +#, python-brace-format msgid "" -"Data for this panel isn't available anymore. Please reload the page and " -"retry." +"Form with id \"{form_id}\" contains file input, but does not have the " +"attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:70 +msgid "" +"Form contains file input, but does not have the attribute " +"enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:73 +#, python-brace-format +msgid "" +"Input element references form with id \"{form_id}\", but the form does not " +"have the attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:77 +msgid "Alerts" msgstr "" -#: panels/cache.py:191 +#: panels/cache.py:168 msgid "Cache" msgstr "" -#: panels/cache.py:196 +#: panels/cache.py:174 #, python-format msgid "%(cache_calls)d call in %(time).2fms" msgid_plural "%(cache_calls)d calls in %(time).2fms" msgstr[0] "" msgstr[1] "" +msgstr[2] "" -#: panels/cache.py:204 +#: panels/cache.py:183 #, python-format msgid "Cache calls from %(count)d backend" msgid_plural "Cache calls from %(count)d backends" msgstr[0] "" msgstr[1] "" +msgstr[2] "" -#: panels/headers.py:35 +#: panels/headers.py:31 msgid "Headers" msgstr "" -#: panels/logging.py:64 -msgid "Logging" -msgstr "רישום יומן" - -#: panels/logging.py:70 -#, python-format -msgid "%(count)s message" -msgid_plural "%(count)s messages" -msgstr[0] "" -msgstr[1] "" - -#: panels/logging.py:73 -msgid "Log messages" +#: panels/history/panel.py:19 panels/history/panel.py:20 +msgid "History" msgstr "" -#: panels/profiling.py:127 +#: panels/profiling.py:140 msgid "Profiling" msgstr "" @@ -73,213 +82,233 @@ msgstr "" msgid "Intercept redirects" msgstr "" -#: panels/request.py:18 +#: panels/request.py:16 msgid "Request" msgstr "" -#: panels/request.py:35 +#: panels/request.py:38 msgid "" msgstr "" -#: panels/request.py:47 +#: panels/request.py:55 msgid "" msgstr "" -#: panels/settings.py:20 +#: panels/settings.py:17 msgid "Settings" msgstr "" -#: panels/settings.py:23 +#: panels/settings.py:20 #, python-format -msgid "Settings from %s" +msgid "Settings from %s" msgstr "" -#: panels/signals.py:45 +#: panels/signals.py:57 #, python-format msgid "%(num_receivers)d receiver of 1 signal" msgid_plural "%(num_receivers)d receivers of 1 signal" msgstr[0] "" msgstr[1] "" +msgstr[2] "" -#: panels/signals.py:48 +#: panels/signals.py:62 #, python-format msgid "%(num_receivers)d receiver of %(num_signals)d signals" msgid_plural "%(num_receivers)d receivers of %(num_signals)d signals" msgstr[0] "" msgstr[1] "" +msgstr[2] "" -#: panels/signals.py:53 +#: panels/signals.py:67 msgid "Signals" msgstr "סיגנלים" -#: panels/staticfiles.py:89 +#: panels/sql/panel.py:30 panels/sql/panel.py:41 +msgid "Read uncommitted" +msgstr "" + +#: panels/sql/panel.py:31 panels/sql/panel.py:43 +msgid "Read committed" +msgstr "" + +#: panels/sql/panel.py:32 panels/sql/panel.py:45 +msgid "Repeatable read" +msgstr "" + +#: panels/sql/panel.py:33 panels/sql/panel.py:47 +msgid "Serializable" +msgstr "משתנה" + +#: panels/sql/panel.py:39 +msgid "Autocommit" +msgstr "" + +#: panels/sql/panel.py:61 panels/sql/panel.py:71 +msgid "Idle" +msgstr "" + +#: panels/sql/panel.py:62 panels/sql/panel.py:72 +msgid "Active" +msgstr "פעילות" + +#: panels/sql/panel.py:63 panels/sql/panel.py:73 +msgid "In transaction" +msgstr "" + +#: panels/sql/panel.py:64 panels/sql/panel.py:74 +msgid "In error" +msgstr "שגיאה" + +#: panels/sql/panel.py:65 panels/sql/panel.py:75 +msgid "Unknown" +msgstr "" + +#: panels/sql/panel.py:162 +msgid "SQL" +msgstr "" + +#: panels/sql/panel.py:168 +#, python-format +msgid "%(query_count)d query in %(sql_time).2fms" +msgid_plural "%(query_count)d queries in %(sql_time).2fms" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +#: panels/sql/panel.py:180 +#, python-format +msgid "SQL queries from %(count)d connection" +msgid_plural "SQL queries from %(count)d connections" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +#: panels/staticfiles.py:82 #, python-format msgid "Static files (%(num_found)s found, %(num_used)s used)" msgstr "" -#: panels/staticfiles.py:107 +#: panels/staticfiles.py:103 msgid "Static files" msgstr "" -#: panels/staticfiles.py:112 +#: panels/staticfiles.py:109 #, python-format msgid "%(num_used)s file used" msgid_plural "%(num_used)s files used" msgstr[0] "" msgstr[1] "" +msgstr[2] "" + +#: panels/templates/panel.py:101 +msgid "Templates" +msgstr "תבניות" + +#: panels/templates/panel.py:106 +#, python-format +msgid "Templates (%(num_templates)s rendered)" +msgstr "" + +#: panels/templates/panel.py:195 +msgid "No origin" +msgstr "" -#: panels/timer.py:23 +#: panels/timer.py:27 #, python-format msgid "CPU: %(cum)0.2fms (%(total)0.2fms)" msgstr "" -#: panels/timer.py:28 +#: panels/timer.py:32 #, python-format msgid "Total: %0.2fms" msgstr "" -#: panels/timer.py:34 templates/debug_toolbar/panels/logging.html:7 +#: panels/timer.py:38 templates/debug_toolbar/panels/history.html:9 #: templates/debug_toolbar/panels/sql_explain.html:11 #: templates/debug_toolbar/panels/sql_profile.html:12 #: templates/debug_toolbar/panels/sql_select.html:11 msgid "Time" msgstr "זמן" -#: panels/timer.py:42 +#: panels/timer.py:46 msgid "User CPU time" msgstr "" -#: panels/timer.py:42 +#: panels/timer.py:46 #, python-format msgid "%(utime)0.3f msec" msgstr "" -#: panels/timer.py:43 +#: panels/timer.py:47 msgid "System CPU time" msgstr "" -#: panels/timer.py:43 +#: panels/timer.py:47 #, python-format msgid "%(stime)0.3f msec" msgstr "" -#: panels/timer.py:44 +#: panels/timer.py:48 msgid "Total CPU time" msgstr "" -#: panels/timer.py:44 +#: panels/timer.py:48 #, python-format msgid "%(total)0.3f msec" msgstr "" -#: panels/timer.py:45 +#: panels/timer.py:49 msgid "Elapsed time" msgstr "" -#: panels/timer.py:45 +#: panels/timer.py:49 #, python-format msgid "%(total_time)0.3f msec" msgstr "" -#: panels/timer.py:46 +#: panels/timer.py:51 msgid "Context switches" msgstr "" -#: panels/timer.py:46 +#: panels/timer.py:52 #, python-format msgid "%(vcsw)d voluntary, %(ivcsw)d involuntary" msgstr "" -#: panels/versions.py:25 +#: panels/versions.py:19 msgid "Versions" msgstr "גירסאות" -#: panels/sql/panel.py:22 -msgid "Autocommit" -msgstr "" - -#: panels/sql/panel.py:23 -msgid "Read uncommitted" -msgstr "" - -#: panels/sql/panel.py:24 -msgid "Read committed" -msgstr "" - -#: panels/sql/panel.py:25 -msgid "Repeatable read" -msgstr "" - -#: panels/sql/panel.py:26 -msgid "Serializable" -msgstr "משתנה" - -#: panels/sql/panel.py:37 -msgid "Idle" -msgstr "" - -#: panels/sql/panel.py:38 -msgid "Active" -msgstr "פעילות" - -#: panels/sql/panel.py:39 -msgid "In transaction" -msgstr "" - -#: panels/sql/panel.py:40 -msgid "In error" -msgstr "שגיאה" - -#: panels/sql/panel.py:41 -msgid "Unknown" -msgstr "" - -#: panels/sql/panel.py:105 -msgid "SQL" -msgstr "" - -#: panels/templates/panel.py:141 -msgid "Templates" -msgstr "תבניות" - -#: panels/templates/panel.py:146 -#, python-format -msgid "Templates (%(num_templates)s rendered)" -msgstr "" - -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide toolbar" msgstr "" -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide" msgstr "הסתר" -#: templates/debug_toolbar/base.html:25 -msgid "Disable for next and successive requests" +#: templates/debug_toolbar/base.html:25 templates/debug_toolbar/base.html:26 +msgid "Toggle Theme" msgstr "" -#: templates/debug_toolbar/base.html:25 -msgid "Enable for next and successive requests" +#: templates/debug_toolbar/base.html:35 +msgid "Show toolbar" msgstr "" -#: templates/debug_toolbar/base.html:47 -msgid "Show toolbar" +#: templates/debug_toolbar/includes/panel_button.html:4 +msgid "Disable for next and successive requests" msgstr "" -#: templates/debug_toolbar/base.html:53 -msgid "Close" -msgstr "סגור" +#: templates/debug_toolbar/includes/panel_button.html:4 +msgid "Enable for next and successive requests" +msgstr "" -#: templates/debug_toolbar/redirect.html:8 -msgid "Location:" +#: templates/debug_toolbar/panels/alerts.html:4 +msgid "Alerts found" msgstr "" -#: templates/debug_toolbar/redirect.html:10 -msgid "" -"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." +#: templates/debug_toolbar/panels/alerts.html:11 +msgid "No alerts found" msgstr "" #: templates/debug_toolbar/panels/cache.html:2 @@ -311,7 +340,7 @@ msgid "Calls" msgstr "" #: templates/debug_toolbar/panels/cache.html:43 -#: templates/debug_toolbar/panels/sql.html:20 +#: templates/debug_toolbar/panels/sql.html:36 msgid "Time (ms)" msgstr "" @@ -346,10 +375,8 @@ msgstr "מפתח" #: templates/debug_toolbar/panels/headers.html:9 #: templates/debug_toolbar/panels/headers.html:28 #: templates/debug_toolbar/panels/headers.html:49 -#: templates/debug_toolbar/panels/request.html:33 -#: templates/debug_toolbar/panels/request.html:59 -#: templates/debug_toolbar/panels/request.html:85 -#: templates/debug_toolbar/panels/request.html:110 +#: templates/debug_toolbar/panels/history_tr.html:23 +#: templates/debug_toolbar/panels/request_variables.html:12 #: templates/debug_toolbar/panels/settings.html:6 #: templates/debug_toolbar/panels/timer.html:11 msgid "Value" @@ -369,26 +396,32 @@ msgid "" "significant subset is shown below." msgstr "" -#: templates/debug_toolbar/panels/logging.html:6 -msgid "Level" -msgstr "רמה" +#: templates/debug_toolbar/panels/history.html:10 +msgid "Method" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:8 -msgid "Channel" +#: templates/debug_toolbar/panels/history.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:43 +msgid "Path" msgstr "" -#: templates/debug_toolbar/panels/logging.html:9 -msgid "Message" -msgstr "הודעה" +#: templates/debug_toolbar/panels/history.html:12 +msgid "Request Variables" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:10 -#: templates/debug_toolbar/panels/staticfiles.html:45 -msgid "Location" -msgstr "מקום" +#: templates/debug_toolbar/panels/history.html:13 +msgid "Status" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:26 -msgid "No messages logged" -msgstr "אין הודעות" +#: templates/debug_toolbar/panels/history.html:14 +#: templates/debug_toolbar/panels/sql.html:37 +msgid "Action" +msgstr "פעילות" + +#: templates/debug_toolbar/panels/history_tr.html:22 +#: templates/debug_toolbar/panels/request_variables.html:11 +msgid "Variable" +msgstr "משתנה" #: templates/debug_toolbar/panels/profiling.html:5 msgid "Call" @@ -427,38 +460,31 @@ msgstr "" msgid "Cookies" msgstr "" -#: templates/debug_toolbar/panels/request.html:32 -#: templates/debug_toolbar/panels/request.html:58 -#: templates/debug_toolbar/panels/request.html:84 -#: templates/debug_toolbar/panels/request.html:109 -msgid "Variable" -msgstr "משתנה" - -#: templates/debug_toolbar/panels/request.html:46 +#: templates/debug_toolbar/panels/request.html:27 msgid "No cookies" msgstr "" -#: templates/debug_toolbar/panels/request.html:50 +#: templates/debug_toolbar/panels/request.html:31 msgid "Session data" msgstr "" -#: templates/debug_toolbar/panels/request.html:72 +#: templates/debug_toolbar/panels/request.html:34 msgid "No session data" msgstr "" -#: templates/debug_toolbar/panels/request.html:76 +#: templates/debug_toolbar/panels/request.html:38 msgid "GET data" msgstr "" -#: templates/debug_toolbar/panels/request.html:98 +#: templates/debug_toolbar/panels/request.html:41 msgid "No GET data" msgstr "אין נתוני GET" -#: templates/debug_toolbar/panels/request.html:102 +#: templates/debug_toolbar/panels/request.html:45 msgid "POST data" msgstr "" -#: templates/debug_toolbar/panels/request.html:123 +#: templates/debug_toolbar/panels/request.html:48 msgid "No POST data" msgstr "אין נתוני POST" @@ -471,60 +497,70 @@ msgid "Signal" msgstr "סיגנל" #: templates/debug_toolbar/panels/signals.html:6 -msgid "Providing" -msgstr "" - -#: templates/debug_toolbar/panels/signals.html:7 msgid "Receivers" msgstr "" -#: templates/debug_toolbar/panels/sql.html:7 +#: templates/debug_toolbar/panels/sql.html:6 #, python-format msgid "%(num)s query" msgid_plural "%(num)s queries" msgstr[0] "" msgstr[1] "" +msgstr[2] "" + +#: templates/debug_toolbar/panels/sql.html:8 +#, python-format +msgid "" +"including %(count)s similar" +msgstr "" -#: templates/debug_toolbar/panels/sql.html:18 +#: templates/debug_toolbar/panels/sql.html:12 +#, python-format +msgid "" +"and %(dupes)s duplicates" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:34 msgid "Query" msgstr "" -#: templates/debug_toolbar/panels/sql.html:19 +#: templates/debug_toolbar/panels/sql.html:35 #: templates/debug_toolbar/panels/timer.html:36 msgid "Timeline" msgstr "" -#: templates/debug_toolbar/panels/sql.html:21 -msgid "Action" -msgstr "פעילות" +#: templates/debug_toolbar/panels/sql.html:52 +#, python-format +msgid "%(count)s similar queries." +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:58 +#, python-format +msgid "Duplicated %(dupes)s times." +msgstr "" -#: templates/debug_toolbar/panels/sql.html:64 +#: templates/debug_toolbar/panels/sql.html:95 msgid "Connection:" msgstr "" -#: templates/debug_toolbar/panels/sql.html:66 +#: templates/debug_toolbar/panels/sql.html:97 msgid "Isolation level:" msgstr "" -#: templates/debug_toolbar/panels/sql.html:69 +#: templates/debug_toolbar/panels/sql.html:100 msgid "Transaction status:" msgstr "" -#: templates/debug_toolbar/panels/sql.html:83 +#: templates/debug_toolbar/panels/sql.html:114 msgid "(unknown)" msgstr "" -#: templates/debug_toolbar/panels/sql.html:92 +#: templates/debug_toolbar/panels/sql.html:123 msgid "No SQL queries were recorded during this request." msgstr "" -#: templates/debug_toolbar/panels/sql_explain.html:3 -#: templates/debug_toolbar/panels/sql_profile.html:3 -#: templates/debug_toolbar/panels/sql_select.html:3 -#: templates/debug_toolbar/panels/template_source.html:3 -msgid "Back" -msgstr "חזרה" - #: templates/debug_toolbar/panels/sql_explain.html:4 msgid "SQL explained" msgstr "" @@ -557,48 +593,52 @@ msgstr "" msgid "Empty set" msgstr "קבוצה ריקה" -#: templates/debug_toolbar/panels/staticfiles.html:4 +#: templates/debug_toolbar/panels/staticfiles.html:3 msgid "Static file path" msgid_plural "Static file paths" msgstr[0] "" msgstr[1] "" +msgstr[2] "" -#: templates/debug_toolbar/panels/staticfiles.html:8 +#: templates/debug_toolbar/panels/staticfiles.html:7 #, python-format msgid "(prefix %(prefix)s)" msgstr "" -#: templates/debug_toolbar/panels/staticfiles.html:12 -#: templates/debug_toolbar/panels/staticfiles.html:23 -#: templates/debug_toolbar/panels/staticfiles.html:35 +#: templates/debug_toolbar/panels/staticfiles.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:22 +#: templates/debug_toolbar/panels/staticfiles.html:34 #: templates/debug_toolbar/panels/templates.html:10 -#: templates/debug_toolbar/panels/templates.html:28 -#: templates/debug_toolbar/panels/templates.html:43 +#: templates/debug_toolbar/panels/templates.html:30 +#: templates/debug_toolbar/panels/templates.html:47 msgid "None" msgstr "" -#: templates/debug_toolbar/panels/staticfiles.html:15 +#: templates/debug_toolbar/panels/staticfiles.html:14 msgid "Static file app" msgid_plural "Static file apps" msgstr[0] "" msgstr[1] "" +msgstr[2] "" -#: templates/debug_toolbar/panels/staticfiles.html:26 +#: templates/debug_toolbar/panels/staticfiles.html:25 msgid "Static file" msgid_plural "Static files" msgstr[0] "" msgstr[1] "" +msgstr[2] "" -#: templates/debug_toolbar/panels/staticfiles.html:40 +#: templates/debug_toolbar/panels/staticfiles.html:39 #, python-format msgid "%(payload_count)s file" msgid_plural "%(payload_count)s files" msgstr[0] "" msgstr[1] "" +msgstr[2] "" #: templates/debug_toolbar/panels/staticfiles.html:44 -msgid "Path" -msgstr "" +msgid "Location" +msgstr "מקום" #: templates/debug_toolbar/panels/template_source.html:4 msgid "Template source:" @@ -609,23 +649,26 @@ msgid "Template path" msgid_plural "Template paths" msgstr[0] "" msgstr[1] "" +msgstr[2] "" #: templates/debug_toolbar/panels/templates.html:13 msgid "Template" msgid_plural "Templates" msgstr[0] "" msgstr[1] "תבנית" +msgstr[2] "תבנית" -#: templates/debug_toolbar/panels/templates.html:21 -#: templates/debug_toolbar/panels/templates.html:37 +#: templates/debug_toolbar/panels/templates.html:22 +#: templates/debug_toolbar/panels/templates.html:40 msgid "Toggle context" msgstr "" -#: templates/debug_toolbar/panels/templates.html:31 +#: templates/debug_toolbar/panels/templates.html:33 msgid "Context processor" msgid_plural "Context processors" msgstr[0] "" msgstr[1] "" +msgstr[2] "" #: templates/debug_toolbar/panels/timer.html:2 msgid "Resource usage" @@ -647,10 +690,31 @@ msgstr "" msgid "Milliseconds since navigation start (+length)" msgstr "" -#: templates/debug_toolbar/panels/versions.html:5 +#: templates/debug_toolbar/panels/versions.html:10 +msgid "Package" +msgstr "" + +#: templates/debug_toolbar/panels/versions.html:11 msgid "Name" msgstr "" -#: templates/debug_toolbar/panels/versions.html:6 +#: templates/debug_toolbar/panels/versions.html:12 msgid "Version" msgstr "" + +#: templates/debug_toolbar/redirect.html:10 +msgid "Location:" +msgstr "" + +#: templates/debug_toolbar/redirect.html:12 +msgid "" +"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." +msgstr "" + +#: views.py:16 +msgid "" +"Data for this panel isn't available anymore. Please reload the page and " +"retry." +msgstr "" diff --git a/debug_toolbar/locale/id/LC_MESSAGES/django.mo b/debug_toolbar/locale/id/LC_MESSAGES/django.mo index c22845abe..4439e2c4d 100644 Binary files a/debug_toolbar/locale/id/LC_MESSAGES/django.mo and b/debug_toolbar/locale/id/LC_MESSAGES/django.mo differ diff --git a/debug_toolbar/locale/id/LC_MESSAGES/django.po b/debug_toolbar/locale/id/LC_MESSAGES/django.po index 8589fb5f6..f206c0b61 100644 --- a/debug_toolbar/locale/id/LC_MESSAGES/django.po +++ b/debug_toolbar/locale/id/LC_MESSAGES/django.po @@ -1,68 +1,76 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# -# +# +# # Translators: # Muhammad Panji , 2012 msgid "" msgstr "" "Project-Id-Version: Django Debug Toolbar\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-04-25 21:52+0200\n" -"PO-Revision-Date: 2014-04-25 19:53+0000\n" -"Last-Translator: Aymeric Augustin \n" -"Language-Team: Indonesian (http://www.transifex.com/projects/p/django-debug-toolbar/language/id/)\n" +"POT-Creation-Date: 2024-08-06 07:12-0500\n" +"PO-Revision-Date: 2010-11-30 00:00+0000\n" +"Last-Translator: Muhammad Panji , 2012\n" +"Language-Team: Indonesian (http://app.transifex.com/django-debug-toolbar/django-debug-toolbar/language/id/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: id\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: apps.py:11 +#: apps.py:18 msgid "Debug Toolbar" msgstr "" -#: views.py:14 +#: panels/alerts.py:67 +#, python-brace-format msgid "" -"Data for this panel isn't available anymore. Please reload the page and " -"retry." +"Form with id \"{form_id}\" contains file input, but does not have the " +"attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:70 +msgid "" +"Form contains file input, but does not have the attribute " +"enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:73 +#, python-brace-format +msgid "" +"Input element references form with id \"{form_id}\", but the form does not " +"have the attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:77 +msgid "Alerts" msgstr "" -#: panels/cache.py:191 +#: panels/cache.py:168 msgid "Cache" msgstr "" -#: panels/cache.py:196 +#: panels/cache.py:174 #, python-format msgid "%(cache_calls)d call in %(time).2fms" msgid_plural "%(cache_calls)d calls in %(time).2fms" msgstr[0] "" -#: panels/cache.py:204 +#: panels/cache.py:183 #, python-format msgid "Cache calls from %(count)d backend" msgid_plural "Cache calls from %(count)d backends" msgstr[0] "" -#: panels/headers.py:35 +#: panels/headers.py:31 msgid "Headers" msgstr "" -#: panels/logging.py:64 -msgid "Logging" +#: panels/history/panel.py:19 panels/history/panel.py:20 +msgid "History" msgstr "" -#: panels/logging.py:70 -#, python-format -msgid "%(count)s message" -msgid_plural "%(count)s messages" -msgstr[0] "%(count)s pesan" - -#: panels/logging.py:73 -msgid "Log messages" -msgstr "" - -#: panels/profiling.py:127 +#: panels/profiling.py:140 msgid "Profiling" msgstr "" @@ -70,210 +78,223 @@ msgstr "" msgid "Intercept redirects" msgstr "" -#: panels/request.py:18 +#: panels/request.py:16 msgid "Request" msgstr "" -#: panels/request.py:35 +#: panels/request.py:38 msgid "" msgstr "" -#: panels/request.py:47 +#: panels/request.py:55 msgid "" msgstr "" -#: panels/settings.py:20 +#: panels/settings.py:17 msgid "Settings" msgstr "Pengaturan" -#: panels/settings.py:23 +#: panels/settings.py:20 #, python-format -msgid "Settings from %s" -msgstr "Pengaturan dari %s" +msgid "Settings from %s" +msgstr "" -#: panels/signals.py:45 +#: panels/signals.py:57 #, python-format msgid "%(num_receivers)d receiver of 1 signal" msgid_plural "%(num_receivers)d receivers of 1 signal" msgstr[0] "" -#: panels/signals.py:48 +#: panels/signals.py:62 #, python-format msgid "%(num_receivers)d receiver of %(num_signals)d signals" msgid_plural "%(num_receivers)d receivers of %(num_signals)d signals" msgstr[0] "" -#: panels/signals.py:53 +#: panels/signals.py:67 msgid "Signals" msgstr "Sinyal" -#: panels/staticfiles.py:89 +#: panels/sql/panel.py:30 panels/sql/panel.py:41 +msgid "Read uncommitted" +msgstr "" + +#: panels/sql/panel.py:31 panels/sql/panel.py:43 +msgid "Read committed" +msgstr "" + +#: panels/sql/panel.py:32 panels/sql/panel.py:45 +msgid "Repeatable read" +msgstr "" + +#: panels/sql/panel.py:33 panels/sql/panel.py:47 +msgid "Serializable" +msgstr "Variabel" + +#: panels/sql/panel.py:39 +msgid "Autocommit" +msgstr "" + +#: panels/sql/panel.py:61 panels/sql/panel.py:71 +msgid "Idle" +msgstr "" + +#: panels/sql/panel.py:62 panels/sql/panel.py:72 +msgid "Active" +msgstr "Aksi" + +#: panels/sql/panel.py:63 panels/sql/panel.py:73 +msgid "In transaction" +msgstr "Status transaksi:" + +#: panels/sql/panel.py:64 panels/sql/panel.py:74 +msgid "In error" +msgstr "" + +#: panels/sql/panel.py:65 panels/sql/panel.py:75 +msgid "Unknown" +msgstr "(tidak diketahui)" + +#: panels/sql/panel.py:162 +msgid "SQL" +msgstr "SQL" + +#: panels/sql/panel.py:168 +#, python-format +msgid "%(query_count)d query in %(sql_time).2fms" +msgid_plural "%(query_count)d queries in %(sql_time).2fms" +msgstr[0] "" + +#: panels/sql/panel.py:180 +#, python-format +msgid "SQL queries from %(count)d connection" +msgid_plural "SQL queries from %(count)d connections" +msgstr[0] "" + +#: panels/staticfiles.py:82 #, python-format msgid "Static files (%(num_found)s found, %(num_used)s used)" msgstr "" -#: panels/staticfiles.py:107 +#: panels/staticfiles.py:103 msgid "Static files" msgstr "Berkas statik" -#: panels/staticfiles.py:112 +#: panels/staticfiles.py:109 #, python-format msgid "%(num_used)s file used" msgid_plural "%(num_used)s files used" msgstr[0] "" -#: panels/timer.py:23 +#: panels/templates/panel.py:101 +msgid "Templates" +msgstr "Template" + +#: panels/templates/panel.py:106 +#, python-format +msgid "Templates (%(num_templates)s rendered)" +msgstr "" + +#: panels/templates/panel.py:195 +msgid "No origin" +msgstr "" + +#: panels/timer.py:27 #, python-format msgid "CPU: %(cum)0.2fms (%(total)0.2fms)" msgstr "CPU: %(cum)0.2fms (%(total)0.2fms)" -#: panels/timer.py:28 +#: panels/timer.py:32 #, python-format msgid "Total: %0.2fms" msgstr "" -#: panels/timer.py:34 templates/debug_toolbar/panels/logging.html:7 +#: panels/timer.py:38 templates/debug_toolbar/panels/history.html:9 #: templates/debug_toolbar/panels/sql_explain.html:11 #: templates/debug_toolbar/panels/sql_profile.html:12 #: templates/debug_toolbar/panels/sql_select.html:11 msgid "Time" msgstr "Waktu" -#: panels/timer.py:42 +#: panels/timer.py:46 msgid "User CPU time" msgstr "CPU time pengguna" -#: panels/timer.py:42 +#: panels/timer.py:46 #, python-format msgid "%(utime)0.3f msec" msgstr "" -#: panels/timer.py:43 +#: panels/timer.py:47 msgid "System CPU time" msgstr "CPU time sistem" -#: panels/timer.py:43 +#: panels/timer.py:47 #, python-format msgid "%(stime)0.3f msec" msgstr "" -#: panels/timer.py:44 +#: panels/timer.py:48 msgid "Total CPU time" msgstr "CPU time total" -#: panels/timer.py:44 +#: panels/timer.py:48 #, python-format msgid "%(total)0.3f msec" msgstr "" -#: panels/timer.py:45 +#: panels/timer.py:49 msgid "Elapsed time" msgstr "Waktu terlampaui" -#: panels/timer.py:45 +#: panels/timer.py:49 #, python-format msgid "%(total_time)0.3f msec" msgstr "" -#: panels/timer.py:46 +#: panels/timer.py:51 msgid "Context switches" msgstr "" -#: panels/timer.py:46 +#: panels/timer.py:52 #, python-format msgid "%(vcsw)d voluntary, %(ivcsw)d involuntary" msgstr "" -#: panels/versions.py:25 +#: panels/versions.py:19 msgid "Versions" msgstr "Versi" -#: panels/sql/panel.py:22 -msgid "Autocommit" -msgstr "" - -#: panels/sql/panel.py:23 -msgid "Read uncommitted" -msgstr "" - -#: panels/sql/panel.py:24 -msgid "Read committed" -msgstr "" - -#: panels/sql/panel.py:25 -msgid "Repeatable read" -msgstr "" - -#: panels/sql/panel.py:26 -msgid "Serializable" -msgstr "Variabel" - -#: panels/sql/panel.py:37 -msgid "Idle" -msgstr "" - -#: panels/sql/panel.py:38 -msgid "Active" -msgstr "Aksi" - -#: panels/sql/panel.py:39 -msgid "In transaction" -msgstr "Status transaksi:" - -#: panels/sql/panel.py:40 -msgid "In error" -msgstr "" - -#: panels/sql/panel.py:41 -msgid "Unknown" -msgstr "(tidak diketahui)" - -#: panels/sql/panel.py:105 -msgid "SQL" -msgstr "SQL" - -#: panels/templates/panel.py:141 -msgid "Templates" -msgstr "Template" - -#: panels/templates/panel.py:146 -#, python-format -msgid "Templates (%(num_templates)s rendered)" -msgstr "" - -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide toolbar" msgstr "" -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide" msgstr "Menyembunyikan" -#: templates/debug_toolbar/base.html:25 -msgid "Disable for next and successive requests" +#: templates/debug_toolbar/base.html:25 templates/debug_toolbar/base.html:26 +msgid "Toggle Theme" msgstr "" -#: templates/debug_toolbar/base.html:25 -msgid "Enable for next and successive requests" +#: templates/debug_toolbar/base.html:35 +msgid "Show toolbar" msgstr "" -#: templates/debug_toolbar/base.html:47 -msgid "Show toolbar" +#: templates/debug_toolbar/includes/panel_button.html:4 +msgid "Disable for next and successive requests" msgstr "" -#: templates/debug_toolbar/base.html:53 -msgid "Close" -msgstr "Menutup" +#: templates/debug_toolbar/includes/panel_button.html:4 +msgid "Enable for next and successive requests" +msgstr "" -#: templates/debug_toolbar/redirect.html:8 -msgid "Location:" +#: templates/debug_toolbar/panels/alerts.html:4 +msgid "Alerts found" msgstr "" -#: templates/debug_toolbar/redirect.html:10 -msgid "" -"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." +#: templates/debug_toolbar/panels/alerts.html:11 +msgid "No alerts found" msgstr "" #: templates/debug_toolbar/panels/cache.html:2 @@ -305,7 +326,7 @@ msgid "Calls" msgstr "" #: templates/debug_toolbar/panels/cache.html:43 -#: templates/debug_toolbar/panels/sql.html:20 +#: templates/debug_toolbar/panels/sql.html:36 msgid "Time (ms)" msgstr "Waktu (milidetik)" @@ -340,10 +361,8 @@ msgstr "Kunci" #: templates/debug_toolbar/panels/headers.html:9 #: templates/debug_toolbar/panels/headers.html:28 #: templates/debug_toolbar/panels/headers.html:49 -#: templates/debug_toolbar/panels/request.html:33 -#: templates/debug_toolbar/panels/request.html:59 -#: templates/debug_toolbar/panels/request.html:85 -#: templates/debug_toolbar/panels/request.html:110 +#: templates/debug_toolbar/panels/history_tr.html:23 +#: templates/debug_toolbar/panels/request_variables.html:12 #: templates/debug_toolbar/panels/settings.html:6 #: templates/debug_toolbar/panels/timer.html:11 msgid "Value" @@ -363,26 +382,32 @@ msgid "" "significant subset is shown below." msgstr "" -#: templates/debug_toolbar/panels/logging.html:6 -msgid "Level" -msgstr "Tingkat" +#: templates/debug_toolbar/panels/history.html:10 +msgid "Method" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:8 -msgid "Channel" -msgstr "Kanal" +#: templates/debug_toolbar/panels/history.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:43 +msgid "Path" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:9 -msgid "Message" -msgstr "Pesan" +#: templates/debug_toolbar/panels/history.html:12 +msgid "Request Variables" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:10 -#: templates/debug_toolbar/panels/staticfiles.html:45 -msgid "Location" -msgstr "Lokasi" +#: templates/debug_toolbar/panels/history.html:13 +msgid "Status" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:26 -msgid "No messages logged" -msgstr "Tidak ada pesan yang dicatat" +#: templates/debug_toolbar/panels/history.html:14 +#: templates/debug_toolbar/panels/sql.html:37 +msgid "Action" +msgstr "Aksi" + +#: templates/debug_toolbar/panels/history_tr.html:22 +#: templates/debug_toolbar/panels/request_variables.html:11 +msgid "Variable" +msgstr "Variabel" #: templates/debug_toolbar/panels/profiling.html:5 msgid "Call" @@ -421,38 +446,31 @@ msgstr "" msgid "Cookies" msgstr "" -#: templates/debug_toolbar/panels/request.html:32 -#: templates/debug_toolbar/panels/request.html:58 -#: templates/debug_toolbar/panels/request.html:84 -#: templates/debug_toolbar/panels/request.html:109 -msgid "Variable" -msgstr "Variabel" - -#: templates/debug_toolbar/panels/request.html:46 +#: templates/debug_toolbar/panels/request.html:27 msgid "No cookies" msgstr "" -#: templates/debug_toolbar/panels/request.html:50 +#: templates/debug_toolbar/panels/request.html:31 msgid "Session data" msgstr "" -#: templates/debug_toolbar/panels/request.html:72 +#: templates/debug_toolbar/panels/request.html:34 msgid "No session data" msgstr "" -#: templates/debug_toolbar/panels/request.html:76 +#: templates/debug_toolbar/panels/request.html:38 msgid "GET data" msgstr "" -#: templates/debug_toolbar/panels/request.html:98 +#: templates/debug_toolbar/panels/request.html:41 msgid "No GET data" msgstr "Tidak ada data GET" -#: templates/debug_toolbar/panels/request.html:102 +#: templates/debug_toolbar/panels/request.html:45 msgid "POST data" msgstr "" -#: templates/debug_toolbar/panels/request.html:123 +#: templates/debug_toolbar/panels/request.html:48 msgid "No POST data" msgstr "Tidak ada data POST" @@ -465,59 +483,68 @@ msgid "Signal" msgstr "Sinyal" #: templates/debug_toolbar/panels/signals.html:6 -msgid "Providing" -msgstr "" - -#: templates/debug_toolbar/panels/signals.html:7 msgid "Receivers" msgstr "Penerima" -#: templates/debug_toolbar/panels/sql.html:7 +#: templates/debug_toolbar/panels/sql.html:6 #, python-format msgid "%(num)s query" msgid_plural "%(num)s queries" msgstr[0] "" -#: templates/debug_toolbar/panels/sql.html:18 +#: templates/debug_toolbar/panels/sql.html:8 +#, python-format +msgid "" +"including %(count)s similar" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:12 +#, python-format +msgid "" +"and %(dupes)s duplicates" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:34 msgid "Query" msgstr "" -#: templates/debug_toolbar/panels/sql.html:19 +#: templates/debug_toolbar/panels/sql.html:35 #: templates/debug_toolbar/panels/timer.html:36 msgid "Timeline" msgstr "" -#: templates/debug_toolbar/panels/sql.html:21 -msgid "Action" -msgstr "Aksi" +#: templates/debug_toolbar/panels/sql.html:52 +#, python-format +msgid "%(count)s similar queries." +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:58 +#, python-format +msgid "Duplicated %(dupes)s times." +msgstr "" -#: templates/debug_toolbar/panels/sql.html:64 +#: templates/debug_toolbar/panels/sql.html:95 msgid "Connection:" msgstr "Koneksi:" -#: templates/debug_toolbar/panels/sql.html:66 +#: templates/debug_toolbar/panels/sql.html:97 msgid "Isolation level:" msgstr "Tingkat isolasi:" -#: templates/debug_toolbar/panels/sql.html:69 +#: templates/debug_toolbar/panels/sql.html:100 msgid "Transaction status:" msgstr "Status transaksi:" -#: templates/debug_toolbar/panels/sql.html:83 +#: templates/debug_toolbar/panels/sql.html:114 msgid "(unknown)" msgstr "(tidak diketahui)" -#: templates/debug_toolbar/panels/sql.html:92 +#: templates/debug_toolbar/panels/sql.html:123 msgid "No SQL queries were recorded during this request." msgstr "" -#: templates/debug_toolbar/panels/sql_explain.html:3 -#: templates/debug_toolbar/panels/sql_profile.html:3 -#: templates/debug_toolbar/panels/sql_select.html:3 -#: templates/debug_toolbar/panels/template_source.html:3 -msgid "Back" -msgstr "Kembali" - #: templates/debug_toolbar/panels/sql_explain.html:4 msgid "SQL explained" msgstr "" @@ -550,44 +577,44 @@ msgstr "" msgid "Empty set" msgstr "Himpunan kosong" -#: templates/debug_toolbar/panels/staticfiles.html:4 +#: templates/debug_toolbar/panels/staticfiles.html:3 msgid "Static file path" msgid_plural "Static file paths" msgstr[0] "" -#: templates/debug_toolbar/panels/staticfiles.html:8 +#: templates/debug_toolbar/panels/staticfiles.html:7 #, python-format msgid "(prefix %(prefix)s)" msgstr "" -#: templates/debug_toolbar/panels/staticfiles.html:12 -#: templates/debug_toolbar/panels/staticfiles.html:23 -#: templates/debug_toolbar/panels/staticfiles.html:35 +#: templates/debug_toolbar/panels/staticfiles.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:22 +#: templates/debug_toolbar/panels/staticfiles.html:34 #: templates/debug_toolbar/panels/templates.html:10 -#: templates/debug_toolbar/panels/templates.html:28 -#: templates/debug_toolbar/panels/templates.html:43 +#: templates/debug_toolbar/panels/templates.html:30 +#: templates/debug_toolbar/panels/templates.html:47 msgid "None" msgstr "Tidak ada" -#: templates/debug_toolbar/panels/staticfiles.html:15 +#: templates/debug_toolbar/panels/staticfiles.html:14 msgid "Static file app" msgid_plural "Static file apps" msgstr[0] "" -#: templates/debug_toolbar/panels/staticfiles.html:26 +#: templates/debug_toolbar/panels/staticfiles.html:25 msgid "Static file" msgid_plural "Static files" msgstr[0] "Berkas statik" -#: templates/debug_toolbar/panels/staticfiles.html:40 +#: templates/debug_toolbar/panels/staticfiles.html:39 #, python-format msgid "%(payload_count)s file" msgid_plural "%(payload_count)s files" msgstr[0] "" #: templates/debug_toolbar/panels/staticfiles.html:44 -msgid "Path" -msgstr "" +msgid "Location" +msgstr "Lokasi" #: templates/debug_toolbar/panels/template_source.html:4 msgid "Template source:" @@ -603,12 +630,12 @@ msgid "Template" msgid_plural "Templates" msgstr[0] "" -#: templates/debug_toolbar/panels/templates.html:21 -#: templates/debug_toolbar/panels/templates.html:37 +#: templates/debug_toolbar/panels/templates.html:22 +#: templates/debug_toolbar/panels/templates.html:40 msgid "Toggle context" msgstr "" -#: templates/debug_toolbar/panels/templates.html:31 +#: templates/debug_toolbar/panels/templates.html:33 msgid "Context processor" msgid_plural "Context processors" msgstr[0] "" @@ -633,10 +660,31 @@ msgstr "" msgid "Milliseconds since navigation start (+length)" msgstr "" -#: templates/debug_toolbar/panels/versions.html:5 +#: templates/debug_toolbar/panels/versions.html:10 +msgid "Package" +msgstr "" + +#: templates/debug_toolbar/panels/versions.html:11 msgid "Name" msgstr "" -#: templates/debug_toolbar/panels/versions.html:6 +#: templates/debug_toolbar/panels/versions.html:12 msgid "Version" msgstr "Versi" + +#: templates/debug_toolbar/redirect.html:10 +msgid "Location:" +msgstr "" + +#: templates/debug_toolbar/redirect.html:12 +msgid "" +"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." +msgstr "" + +#: views.py:16 +msgid "" +"Data for this panel isn't available anymore. Please reload the page and " +"retry." +msgstr "" diff --git a/debug_toolbar/locale/it/LC_MESSAGES/django.mo b/debug_toolbar/locale/it/LC_MESSAGES/django.mo index 5a01b46e2..4294f8993 100644 Binary files a/debug_toolbar/locale/it/LC_MESSAGES/django.mo and b/debug_toolbar/locale/it/LC_MESSAGES/django.mo differ diff --git a/debug_toolbar/locale/it/LC_MESSAGES/django.po b/debug_toolbar/locale/it/LC_MESSAGES/django.po index eff329972..97ee9c3e4 100644 --- a/debug_toolbar/locale/it/LC_MESSAGES/django.po +++ b/debug_toolbar/locale/it/LC_MESSAGES/django.po @@ -6,284 +6,312 @@ # Dario Agliottone , 2012 # Flavio Curella , 2013 # yakky , 2013-2014 -# Andrea Rabbaglietti , 2020 msgid "" msgstr "" "Project-Id-Version: Django Debug Toolbar\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-04-25 21:52+0200\n" -"PO-Revision-Date: 2014-04-25 19:53+0000\n" -"Last-Translator: Aymeric Augustin \n" -"Language-Team: Italian (http://www.transifex.com/projects/p/django-debug-toolbar/language/it/)\n" +"POT-Creation-Date: 2024-08-06 07:12-0500\n" +"PO-Revision-Date: 2010-11-30 00:00+0000\n" +"Last-Translator: yakky , 2013-2014\n" +"Language-Team: Italian (http://app.transifex.com/django-debug-toolbar/django-debug-toolbar/language/it/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: it\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" -#: apps.py:11 +#: apps.py:18 msgid "Debug Toolbar" msgstr "" -#: views.py:14 +#: panels/alerts.py:67 +#, python-brace-format msgid "" -"Data for this panel isn't available anymore. Please reload the page and " -"retry." -msgstr "Non sono più disponibili dati per questo pannello. Ricarica la pagina e riprova." +"Form with id \"{form_id}\" contains file input, but does not have the " +"attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:70 +msgid "" +"Form contains file input, but does not have the attribute " +"enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:73 +#, python-brace-format +msgid "" +"Input element references form with id \"{form_id}\", but the form does not " +"have the attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:77 +msgid "Alerts" +msgstr "" -#: panels/cache.py:191 +#: panels/cache.py:168 msgid "Cache" msgstr "Cache" -#: panels/cache.py:196 +#: panels/cache.py:174 #, python-format msgid "%(cache_calls)d call in %(time).2fms" msgid_plural "%(cache_calls)d calls in %(time).2fms" msgstr[0] "%(cache_calls)d chiamata in %(time).2fms" msgstr[1] "%(cache_calls)d chiamate in %(time).2fms" +msgstr[2] "%(cache_calls)d chiamate in %(time).2fms" -#: panels/cache.py:204 +#: panels/cache.py:183 #, python-format msgid "Cache calls from %(count)d backend" msgid_plural "Cache calls from %(count)d backends" msgstr[0] "Chiamate alla cache da %(count)d backend" msgstr[1] "Chiamate alla cache da %(count)d backend" +msgstr[2] "Chiamate alla cache da %(count)d backend" -#: panels/headers.py:35 +#: panels/headers.py:31 msgid "Headers" -msgstr "Headers" +msgstr "Intestazioni" -#: panels/logging.py:64 -msgid "Logging" -msgstr "Logging" - -#: panels/logging.py:70 -#, python-format -msgid "%(count)s message" -msgid_plural "%(count)s messages" -msgstr[0] "%(count)s messaggio" -msgstr[1] "%(count)s messaggi" - -#: panels/logging.py:73 -msgid "Log messages" -msgstr "Messaggi di log" +#: panels/history/panel.py:19 panels/history/panel.py:20 +msgid "History" +msgstr "" -#: panels/profiling.py:127 +#: panels/profiling.py:140 msgid "Profiling" msgstr "Profilazione" #: panels/redirects.py:17 msgid "Intercept redirects" -msgstr "Intercetta redirezioni" +msgstr "Intercetta ridirezioni" -#: panels/request.py:18 +#: panels/request.py:16 msgid "Request" msgstr "Request" -#: panels/request.py:35 +#: panels/request.py:38 msgid "" msgstr "" -#: panels/request.py:47 +#: panels/request.py:55 msgid "" msgstr "" -#: panels/settings.py:20 +#: panels/settings.py:17 msgid "Settings" msgstr "Impostazioni" -#: panels/settings.py:23 +#: panels/settings.py:20 #, python-format -msgid "Settings from %s" -msgstr "Impostazioni da %s" +msgid "Settings from %s" +msgstr "" -#: panels/signals.py:45 +#: panels/signals.py:57 #, python-format msgid "%(num_receivers)d receiver of 1 signal" msgid_plural "%(num_receivers)d receivers of 1 signal" msgstr[0] "%(num_receivers)d ricevitore di 1 segnale" msgstr[1] "%(num_receivers)d ricevitori di 1 segnale" +msgstr[2] "%(num_receivers)d ricevitori di 1 segnale" -#: panels/signals.py:48 +#: panels/signals.py:62 #, python-format msgid "%(num_receivers)d receiver of %(num_signals)d signals" msgid_plural "%(num_receivers)d receivers of %(num_signals)d signals" msgstr[0] "%(num_receivers)d ricevitore di %(num_signals)d segnali" msgstr[1] "%(num_receivers)d ricevitori di %(num_signals)d segnali" +msgstr[2] "%(num_receivers)d ricevitori di %(num_signals)d segnali" -#: panels/signals.py:53 +#: panels/signals.py:67 msgid "Signals" msgstr "Segnali" -#: panels/staticfiles.py:89 +#: panels/sql/panel.py:30 panels/sql/panel.py:41 +msgid "Read uncommitted" +msgstr "Read uncommitted" + +#: panels/sql/panel.py:31 panels/sql/panel.py:43 +msgid "Read committed" +msgstr "Read committed" + +#: panels/sql/panel.py:32 panels/sql/panel.py:45 +msgid "Repeatable read" +msgstr "Repeatable read" + +#: panels/sql/panel.py:33 panels/sql/panel.py:47 +msgid "Serializable" +msgstr "Serializable" + +#: panels/sql/panel.py:39 +msgid "Autocommit" +msgstr "Autocommit" + +#: panels/sql/panel.py:61 panels/sql/panel.py:71 +msgid "Idle" +msgstr "Idle" + +#: panels/sql/panel.py:62 panels/sql/panel.py:72 +msgid "Active" +msgstr "Azione" + +#: panels/sql/panel.py:63 panels/sql/panel.py:73 +msgid "In transaction" +msgstr "Stato transazione:" + +#: panels/sql/panel.py:64 panels/sql/panel.py:74 +msgid "In error" +msgstr "Errore" + +#: panels/sql/panel.py:65 panels/sql/panel.py:75 +msgid "Unknown" +msgstr "(sconosciuto)" + +#: panels/sql/panel.py:162 +msgid "SQL" +msgstr "SQL" + +#: panels/sql/panel.py:168 +#, python-format +msgid "%(query_count)d query in %(sql_time).2fms" +msgid_plural "%(query_count)d queries in %(sql_time).2fms" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +#: panels/sql/panel.py:180 +#, python-format +msgid "SQL queries from %(count)d connection" +msgid_plural "SQL queries from %(count)d connections" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +#: panels/staticfiles.py:82 #, python-format msgid "Static files (%(num_found)s found, %(num_used)s used)" msgstr "File statici (%(num_found)s trovati, %(num_used)s usati)" -#: panels/staticfiles.py:107 +#: panels/staticfiles.py:103 msgid "Static files" msgstr "Files statici" -#: panels/staticfiles.py:112 +#: panels/staticfiles.py:109 #, python-format msgid "%(num_used)s file used" msgid_plural "%(num_used)s files used" msgstr[0] "%(num_used)s file usato" msgstr[1] "%(num_used)s file usati" +msgstr[2] "%(num_used)s file usati" + +#: panels/templates/panel.py:101 +msgid "Templates" +msgstr "Template" -#: panels/timer.py:23 +#: panels/templates/panel.py:106 +#, python-format +msgid "Templates (%(num_templates)s rendered)" +msgstr "Templates (%(num_templates)s rendered)" + +#: panels/templates/panel.py:195 +msgid "No origin" +msgstr "" + +#: panels/timer.py:27 #, python-format msgid "CPU: %(cum)0.2fms (%(total)0.2fms)" msgstr "CPU: %(cum)0.2fms (%(total)0.2fms)" -#: panels/timer.py:28 +#: panels/timer.py:32 #, python-format msgid "Total: %0.2fms" msgstr "Totale: %0.2fms" -#: panels/timer.py:34 templates/debug_toolbar/panels/logging.html:7 +#: panels/timer.py:38 templates/debug_toolbar/panels/history.html:9 #: templates/debug_toolbar/panels/sql_explain.html:11 #: templates/debug_toolbar/panels/sql_profile.html:12 #: templates/debug_toolbar/panels/sql_select.html:11 msgid "Time" msgstr "Tempo" -#: panels/timer.py:42 +#: panels/timer.py:46 msgid "User CPU time" msgstr "Tempo CPU utente" -#: panels/timer.py:42 +#: panels/timer.py:46 #, python-format msgid "%(utime)0.3f msec" msgstr "%(utime)0.3f msec" -#: panels/timer.py:43 +#: panels/timer.py:47 msgid "System CPU time" msgstr "Tempo CPU sistema" -#: panels/timer.py:43 +#: panels/timer.py:47 #, python-format msgid "%(stime)0.3f msec" msgstr "%(stime)0.3f msec" -#: panels/timer.py:44 +#: panels/timer.py:48 msgid "Total CPU time" msgstr "Tempo Totale CPU" -#: panels/timer.py:44 +#: panels/timer.py:48 #, python-format msgid "%(total)0.3f msec" msgstr "%(total)0.3f msec" -#: panels/timer.py:45 +#: panels/timer.py:49 msgid "Elapsed time" msgstr "Tempo Trascorso" -#: panels/timer.py:45 +#: panels/timer.py:49 #, python-format msgid "%(total_time)0.3f msec" msgstr "%(total_time)0.3f msec" -#: panels/timer.py:46 +#: panels/timer.py:51 msgid "Context switches" msgstr "Cambi di contesto" -#: panels/timer.py:46 +#: panels/timer.py:52 #, python-format msgid "%(vcsw)d voluntary, %(ivcsw)d involuntary" msgstr "%(vcsw)d volontario, %(ivcsw)d involontario" -#: panels/versions.py:25 +#: panels/versions.py:19 msgid "Versions" msgstr "Versioni" -#: panels/sql/panel.py:22 -msgid "Autocommit" -msgstr "Autocommit" - -#: panels/sql/panel.py:23 -msgid "Read uncommitted" -msgstr "Read uncommitted" - -#: panels/sql/panel.py:24 -msgid "Read committed" -msgstr "Read committed" - -#: panels/sql/panel.py:25 -msgid "Repeatable read" -msgstr "Repeatable read" - -#: panels/sql/panel.py:26 -msgid "Serializable" -msgstr "Serializable" - -#: panels/sql/panel.py:37 -msgid "Idle" -msgstr "Idle" - -#: panels/sql/panel.py:38 -msgid "Active" -msgstr "Attivo" - -#: panels/sql/panel.py:39 -msgid "In transaction" -msgstr "Stato transazione:" - -#: panels/sql/panel.py:40 -msgid "In error" -msgstr "Errore" - -#: panels/sql/panel.py:41 -msgid "Unknown" -msgstr "(sconosciuto)" - -#: panels/sql/panel.py:105 -msgid "SQL" -msgstr "SQL" - -#: panels/templates/panel.py:141 -msgid "Templates" -msgstr "Templates" - -#: panels/templates/panel.py:146 -#, python-format -msgid "Templates (%(num_templates)s rendered)" -msgstr "Templates (%(num_templates)s rendered)" - -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide toolbar" msgstr "Nascondi Toolbar" -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide" msgstr "Nascondi" -#: templates/debug_toolbar/base.html:25 +#: templates/debug_toolbar/base.html:25 templates/debug_toolbar/base.html:26 +msgid "Toggle Theme" +msgstr "" + +#: templates/debug_toolbar/base.html:35 +msgid "Show toolbar" +msgstr "Mostra Toolbar" + +#: templates/debug_toolbar/includes/panel_button.html:4 msgid "Disable for next and successive requests" msgstr "Disattiva per la prossima requests e le successive" -#: templates/debug_toolbar/base.html:25 +#: templates/debug_toolbar/includes/panel_button.html:4 msgid "Enable for next and successive requests" msgstr "Abilita per la prossima requests e le successive" -#: templates/debug_toolbar/base.html:47 -msgid "Show toolbar" -msgstr "Mostra Toolbar" - -#: templates/debug_toolbar/base.html:53 -msgid "Close" -msgstr "Chiudi" - -#: templates/debug_toolbar/redirect.html:8 -msgid "Location:" -msgstr "Location:" +#: templates/debug_toolbar/panels/alerts.html:4 +msgid "Alerts found" +msgstr "" -#: templates/debug_toolbar/redirect.html:10 -msgid "" -"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." -msgstr "Django Debug Toolbar ha intercettato un redirect verso la URL indicata per visualizzare il debug, Puoi cliccare sul link sopra per continuare normalmente con la redirezione." +#: templates/debug_toolbar/panels/alerts.html:11 +msgid "No alerts found" +msgstr "" #: templates/debug_toolbar/panels/cache.html:2 msgid "Summary" @@ -314,7 +342,7 @@ msgid "Calls" msgstr "Chiamate" #: templates/debug_toolbar/panels/cache.html:43 -#: templates/debug_toolbar/panels/sql.html:20 +#: templates/debug_toolbar/panels/sql.html:36 msgid "Time (ms)" msgstr "Durata (ms)" @@ -330,7 +358,7 @@ msgstr "Argomenti" #: templates/debug_toolbar/panels/cache.html:46 #: templates/debug_toolbar/panels/request.html:9 msgid "Keyword arguments" -msgstr "Keyword arguments" +msgstr "Parole chiave" #: templates/debug_toolbar/panels/cache.html:47 msgid "Backend" @@ -338,21 +366,19 @@ msgstr "Backend" #: templates/debug_toolbar/panels/headers.html:3 msgid "Request headers" -msgstr "Request headers" +msgstr "Header della request" #: templates/debug_toolbar/panels/headers.html:8 #: templates/debug_toolbar/panels/headers.html:27 #: templates/debug_toolbar/panels/headers.html:48 msgid "Key" -msgstr "Chiave" +msgstr "Nome" #: templates/debug_toolbar/panels/headers.html:9 #: templates/debug_toolbar/panels/headers.html:28 #: templates/debug_toolbar/panels/headers.html:49 -#: templates/debug_toolbar/panels/request.html:33 -#: templates/debug_toolbar/panels/request.html:59 -#: templates/debug_toolbar/panels/request.html:85 -#: templates/debug_toolbar/panels/request.html:110 +#: templates/debug_toolbar/panels/history_tr.html:23 +#: templates/debug_toolbar/panels/request_variables.html:12 #: templates/debug_toolbar/panels/settings.html:6 #: templates/debug_toolbar/panels/timer.html:11 msgid "Value" @@ -372,26 +398,32 @@ msgid "" "significant subset is shown below." msgstr "Visto che l'ambiente WSGI è ereditato dal server, sotto è mostrata solo la parte significativa." -#: templates/debug_toolbar/panels/logging.html:6 -msgid "Level" -msgstr "Livello" +#: templates/debug_toolbar/panels/history.html:10 +msgid "Method" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:8 -msgid "Channel" -msgstr "Canale" +#: templates/debug_toolbar/panels/history.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:43 +msgid "Path" +msgstr "Percorso" -#: templates/debug_toolbar/panels/logging.html:9 -msgid "Message" -msgstr "Messaggio" +#: templates/debug_toolbar/panels/history.html:12 +msgid "Request Variables" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:10 -#: templates/debug_toolbar/panels/staticfiles.html:45 -msgid "Location" -msgstr "Location" +#: templates/debug_toolbar/panels/history.html:13 +msgid "Status" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:26 -msgid "No messages logged" -msgstr "Nessun messaggio registrato" +#: templates/debug_toolbar/panels/history.html:14 +#: templates/debug_toolbar/panels/sql.html:37 +msgid "Action" +msgstr "Azione" + +#: templates/debug_toolbar/panels/history_tr.html:22 +#: templates/debug_toolbar/panels/request_variables.html:11 +msgid "Variable" +msgstr "Variabile" #: templates/debug_toolbar/panels/profiling.html:5 msgid "Call" @@ -430,38 +462,31 @@ msgstr "Nome URL" msgid "Cookies" msgstr "Cookies" -#: templates/debug_toolbar/panels/request.html:32 -#: templates/debug_toolbar/panels/request.html:58 -#: templates/debug_toolbar/panels/request.html:84 -#: templates/debug_toolbar/panels/request.html:109 -msgid "Variable" -msgstr "Variabile" - -#: templates/debug_toolbar/panels/request.html:46 +#: templates/debug_toolbar/panels/request.html:27 msgid "No cookies" msgstr "Nessun cookie" -#: templates/debug_toolbar/panels/request.html:50 +#: templates/debug_toolbar/panels/request.html:31 msgid "Session data" msgstr "Dati di sessione" -#: templates/debug_toolbar/panels/request.html:72 +#: templates/debug_toolbar/panels/request.html:34 msgid "No session data" msgstr "Nessun dato in sessione" -#: templates/debug_toolbar/panels/request.html:76 +#: templates/debug_toolbar/panels/request.html:38 msgid "GET data" msgstr "Dati GET" -#: templates/debug_toolbar/panels/request.html:98 +#: templates/debug_toolbar/panels/request.html:41 msgid "No GET data" msgstr "Nessun dato in GET" -#: templates/debug_toolbar/panels/request.html:102 +#: templates/debug_toolbar/panels/request.html:45 msgid "POST data" msgstr "Dati POST" -#: templates/debug_toolbar/panels/request.html:123 +#: templates/debug_toolbar/panels/request.html:48 msgid "No POST data" msgstr "Nessuno dato in POST" @@ -474,63 +499,73 @@ msgid "Signal" msgstr "Segnale" #: templates/debug_toolbar/panels/signals.html:6 -msgid "Providing" -msgstr "Forniti" - -#: templates/debug_toolbar/panels/signals.html:7 msgid "Receivers" msgstr "Ricevitori" -#: templates/debug_toolbar/panels/sql.html:7 +#: templates/debug_toolbar/panels/sql.html:6 #, python-format msgid "%(num)s query" msgid_plural "%(num)s queries" msgstr[0] "%(num)s query" msgstr[1] "%(num)s query" +msgstr[2] "%(num)s query" + +#: templates/debug_toolbar/panels/sql.html:8 +#, python-format +msgid "" +"including %(count)s similar" +msgstr "" -#: templates/debug_toolbar/panels/sql.html:18 +#: templates/debug_toolbar/panels/sql.html:12 +#, python-format +msgid "" +"and %(dupes)s duplicates" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:34 msgid "Query" msgstr "Query" -#: templates/debug_toolbar/panels/sql.html:19 +#: templates/debug_toolbar/panels/sql.html:35 #: templates/debug_toolbar/panels/timer.html:36 msgid "Timeline" msgstr "Timeline" -#: templates/debug_toolbar/panels/sql.html:21 -msgid "Action" -msgstr "Azione" +#: templates/debug_toolbar/panels/sql.html:52 +#, python-format +msgid "%(count)s similar queries." +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:58 +#, python-format +msgid "Duplicated %(dupes)s times." +msgstr "" -#: templates/debug_toolbar/panels/sql.html:64 +#: templates/debug_toolbar/panels/sql.html:95 msgid "Connection:" msgstr "Connessione:" -#: templates/debug_toolbar/panels/sql.html:66 +#: templates/debug_toolbar/panels/sql.html:97 msgid "Isolation level:" msgstr "Isolation level:" -#: templates/debug_toolbar/panels/sql.html:69 +#: templates/debug_toolbar/panels/sql.html:100 msgid "Transaction status:" msgstr "Stato transazione:" -#: templates/debug_toolbar/panels/sql.html:83 +#: templates/debug_toolbar/panels/sql.html:114 msgid "(unknown)" msgstr "(sconosciuto)" -#: templates/debug_toolbar/panels/sql.html:92 +#: templates/debug_toolbar/panels/sql.html:123 msgid "No SQL queries were recorded during this request." msgstr "Nessuna Query SQL è stata registrata durante questa richiesta" -#: templates/debug_toolbar/panels/sql_explain.html:3 -#: templates/debug_toolbar/panels/sql_profile.html:3 -#: templates/debug_toolbar/panels/sql_select.html:3 -#: templates/debug_toolbar/panels/template_source.html:3 -msgid "Back" -msgstr "Indietro" - #: templates/debug_toolbar/panels/sql_explain.html:4 msgid "SQL explained" -msgstr "SQL spiegato" +msgstr "SQL spigato" #: templates/debug_toolbar/panels/sql_explain.html:9 #: templates/debug_toolbar/panels/sql_profile.html:10 @@ -560,48 +595,52 @@ msgstr "SQL selezionato" msgid "Empty set" msgstr "Insieme vuoto" -#: templates/debug_toolbar/panels/staticfiles.html:4 +#: templates/debug_toolbar/panels/staticfiles.html:3 msgid "Static file path" msgid_plural "Static file paths" msgstr[0] "Percorso file statici" msgstr[1] "Percorsi file statici" +msgstr[2] "Percorsi file statici" -#: templates/debug_toolbar/panels/staticfiles.html:8 +#: templates/debug_toolbar/panels/staticfiles.html:7 #, python-format msgid "(prefix %(prefix)s)" msgstr "(prefisso %(prefix)s)" -#: templates/debug_toolbar/panels/staticfiles.html:12 -#: templates/debug_toolbar/panels/staticfiles.html:23 -#: templates/debug_toolbar/panels/staticfiles.html:35 +#: templates/debug_toolbar/panels/staticfiles.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:22 +#: templates/debug_toolbar/panels/staticfiles.html:34 #: templates/debug_toolbar/panels/templates.html:10 -#: templates/debug_toolbar/panels/templates.html:28 -#: templates/debug_toolbar/panels/templates.html:43 +#: templates/debug_toolbar/panels/templates.html:30 +#: templates/debug_toolbar/panels/templates.html:47 msgid "None" msgstr "Nessuno" -#: templates/debug_toolbar/panels/staticfiles.html:15 +#: templates/debug_toolbar/panels/staticfiles.html:14 msgid "Static file app" msgid_plural "Static file apps" msgstr[0] "App file statici" msgstr[1] "App file statici" +msgstr[2] "App file statici" -#: templates/debug_toolbar/panels/staticfiles.html:26 +#: templates/debug_toolbar/panels/staticfiles.html:25 msgid "Static file" msgid_plural "Static files" msgstr[0] "" msgstr[1] "Files statici" +msgstr[2] "Files statici" -#: templates/debug_toolbar/panels/staticfiles.html:40 +#: templates/debug_toolbar/panels/staticfiles.html:39 #, python-format msgid "%(payload_count)s file" msgid_plural "%(payload_count)s files" msgstr[0] "%(payload_count)s file" msgstr[1] "%(payload_count)s file" +msgstr[2] "%(payload_count)s file" #: templates/debug_toolbar/panels/staticfiles.html:44 -msgid "Path" -msgstr "Percorso" +msgid "Location" +msgstr "Location" #: templates/debug_toolbar/panels/template_source.html:4 msgid "Template source:" @@ -612,23 +651,26 @@ msgid "Template path" msgid_plural "Template paths" msgstr[0] "Percorso dei template" msgstr[1] "Percorsi dei template" +msgstr[2] "Percorsi dei template" #: templates/debug_toolbar/panels/templates.html:13 msgid "Template" msgid_plural "Templates" msgstr[0] "" msgstr[1] "Template" +msgstr[2] "Template" -#: templates/debug_toolbar/panels/templates.html:21 -#: templates/debug_toolbar/panels/templates.html:37 +#: templates/debug_toolbar/panels/templates.html:22 +#: templates/debug_toolbar/panels/templates.html:40 msgid "Toggle context" msgstr "Cambia contesto" -#: templates/debug_toolbar/panels/templates.html:31 +#: templates/debug_toolbar/panels/templates.html:33 msgid "Context processor" msgid_plural "Context processors" msgstr[0] "Context processor" msgstr[1] "Context processors" +msgstr[2] "Context processors" #: templates/debug_toolbar/panels/timer.html:2 msgid "Resource usage" @@ -650,10 +692,31 @@ msgstr "Attributo" msgid "Milliseconds since navigation start (+length)" msgstr "Millisecondi dall'inizio della navigazione (+lunghezza)" -#: templates/debug_toolbar/panels/versions.html:5 +#: templates/debug_toolbar/panels/versions.html:10 +msgid "Package" +msgstr "" + +#: templates/debug_toolbar/panels/versions.html:11 msgid "Name" msgstr "Nome" -#: templates/debug_toolbar/panels/versions.html:6 +#: templates/debug_toolbar/panels/versions.html:12 msgid "Version" msgstr "Versione" + +#: templates/debug_toolbar/redirect.html:10 +msgid "Location:" +msgstr "Location:" + +#: templates/debug_toolbar/redirect.html:12 +msgid "" +"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." +msgstr "Django Debug Toolbar ha intercettato un redirect verso la URL indicata per visualizzare il debug, Puoi cliccare sul link sopra per continuare normalmente con la redirezione." + +#: views.py:16 +msgid "" +"Data for this panel isn't available anymore. Please reload the page and " +"retry." +msgstr "Non sono più disponibili dati per questo pannello. Ricarica la pagina e riprova." diff --git a/debug_toolbar/locale/ja/LC_MESSAGES/django.mo b/debug_toolbar/locale/ja/LC_MESSAGES/django.mo new file mode 100644 index 000000000..55377e981 Binary files /dev/null and b/debug_toolbar/locale/ja/LC_MESSAGES/django.mo differ diff --git a/debug_toolbar/locale/ja/LC_MESSAGES/django.po b/debug_toolbar/locale/ja/LC_MESSAGES/django.po new file mode 100644 index 000000000..e985d55f5 --- /dev/null +++ b/debug_toolbar/locale/ja/LC_MESSAGES/django.po @@ -0,0 +1,690 @@ +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# +# Translators: +# Shinya Okano , 2012,2014,2020 +msgid "" +msgstr "" +"Project-Id-Version: Django Debug Toolbar\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-08-06 07:12-0500\n" +"PO-Revision-Date: 2010-11-30 00:00+0000\n" +"Last-Translator: Shinya Okano , 2012,2014,2020\n" +"Language-Team: Japanese (http://app.transifex.com/django-debug-toolbar/django-debug-toolbar/language/ja/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ja\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: apps.py:18 +msgid "Debug Toolbar" +msgstr "デバッグツールバー" + +#: panels/alerts.py:67 +#, python-brace-format +msgid "" +"Form with id \"{form_id}\" contains file input, but does not have the " +"attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:70 +msgid "" +"Form contains file input, but does not have the attribute " +"enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:73 +#, python-brace-format +msgid "" +"Input element references form with id \"{form_id}\", but the form does not " +"have the attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:77 +msgid "Alerts" +msgstr "" + +#: panels/cache.py:168 +msgid "Cache" +msgstr "キャッシュ" + +#: panels/cache.py:174 +#, python-format +msgid "%(cache_calls)d call in %(time).2fms" +msgid_plural "%(cache_calls)d calls in %(time).2fms" +msgstr[0] "" + +#: panels/cache.py:183 +#, python-format +msgid "Cache calls from %(count)d backend" +msgid_plural "Cache calls from %(count)d backends" +msgstr[0] "" + +#: panels/headers.py:31 +msgid "Headers" +msgstr "ヘッダー" + +#: panels/history/panel.py:19 panels/history/panel.py:20 +msgid "History" +msgstr "" + +#: panels/profiling.py:140 +msgid "Profiling" +msgstr "プロファイル" + +#: panels/redirects.py:17 +msgid "Intercept redirects" +msgstr "リダイレクトに割込み" + +#: panels/request.py:16 +msgid "Request" +msgstr "リクエスト" + +#: panels/request.py:38 +msgid "" +msgstr "" + +#: panels/request.py:55 +msgid "" +msgstr "<利用不可>" + +#: panels/settings.py:17 +msgid "Settings" +msgstr "設定" + +#: panels/settings.py:20 +#, python-format +msgid "Settings from %s" +msgstr "" + +#: panels/signals.py:57 +#, python-format +msgid "%(num_receivers)d receiver of 1 signal" +msgid_plural "%(num_receivers)d receivers of 1 signal" +msgstr[0] "" + +#: panels/signals.py:62 +#, python-format +msgid "%(num_receivers)d receiver of %(num_signals)d signals" +msgid_plural "%(num_receivers)d receivers of %(num_signals)d signals" +msgstr[0] "" + +#: panels/signals.py:67 +msgid "Signals" +msgstr "シグナル" + +#: panels/sql/panel.py:30 panels/sql/panel.py:41 +msgid "Read uncommitted" +msgstr "" + +#: panels/sql/panel.py:31 panels/sql/panel.py:43 +msgid "Read committed" +msgstr "" + +#: panels/sql/panel.py:32 panels/sql/panel.py:45 +msgid "Repeatable read" +msgstr "" + +#: panels/sql/panel.py:33 panels/sql/panel.py:47 +msgid "Serializable" +msgstr "" + +#: panels/sql/panel.py:39 +msgid "Autocommit" +msgstr "" + +#: panels/sql/panel.py:61 panels/sql/panel.py:71 +msgid "Idle" +msgstr "" + +#: panels/sql/panel.py:62 panels/sql/panel.py:72 +msgid "Active" +msgstr "" + +#: panels/sql/panel.py:63 panels/sql/panel.py:73 +msgid "In transaction" +msgstr "" + +#: panels/sql/panel.py:64 panels/sql/panel.py:74 +msgid "In error" +msgstr "" + +#: panels/sql/panel.py:65 panels/sql/panel.py:75 +msgid "Unknown" +msgstr "" + +#: panels/sql/panel.py:162 +msgid "SQL" +msgstr "SQL" + +#: panels/sql/panel.py:168 +#, python-format +msgid "%(query_count)d query in %(sql_time).2fms" +msgid_plural "%(query_count)d queries in %(sql_time).2fms" +msgstr[0] "" + +#: panels/sql/panel.py:180 +#, python-format +msgid "SQL queries from %(count)d connection" +msgid_plural "SQL queries from %(count)d connections" +msgstr[0] "" + +#: panels/staticfiles.py:82 +#, python-format +msgid "Static files (%(num_found)s found, %(num_used)s used)" +msgstr "" + +#: panels/staticfiles.py:103 +msgid "Static files" +msgstr "静的ファイル" + +#: panels/staticfiles.py:109 +#, python-format +msgid "%(num_used)s file used" +msgid_plural "%(num_used)s files used" +msgstr[0] "" + +#: panels/templates/panel.py:101 +msgid "Templates" +msgstr "テンプレート" + +#: panels/templates/panel.py:106 +#, python-format +msgid "Templates (%(num_templates)s rendered)" +msgstr "" + +#: panels/templates/panel.py:195 +msgid "No origin" +msgstr "" + +#: panels/timer.py:27 +#, python-format +msgid "CPU: %(cum)0.2fms (%(total)0.2fms)" +msgstr "" + +#: panels/timer.py:32 +#, python-format +msgid "Total: %0.2fms" +msgstr "" + +#: panels/timer.py:38 templates/debug_toolbar/panels/history.html:9 +#: templates/debug_toolbar/panels/sql_explain.html:11 +#: templates/debug_toolbar/panels/sql_profile.html:12 +#: templates/debug_toolbar/panels/sql_select.html:11 +msgid "Time" +msgstr "時間" + +#: panels/timer.py:46 +msgid "User CPU time" +msgstr "" + +#: panels/timer.py:46 +#, python-format +msgid "%(utime)0.3f msec" +msgstr "" + +#: panels/timer.py:47 +msgid "System CPU time" +msgstr "" + +#: panels/timer.py:47 +#, python-format +msgid "%(stime)0.3f msec" +msgstr "" + +#: panels/timer.py:48 +msgid "Total CPU time" +msgstr "" + +#: panels/timer.py:48 +#, python-format +msgid "%(total)0.3f msec" +msgstr "" + +#: panels/timer.py:49 +msgid "Elapsed time" +msgstr "" + +#: panels/timer.py:49 +#, python-format +msgid "%(total_time)0.3f msec" +msgstr "" + +#: panels/timer.py:51 +msgid "Context switches" +msgstr "" + +#: panels/timer.py:52 +#, python-format +msgid "%(vcsw)d voluntary, %(ivcsw)d involuntary" +msgstr "" + +#: panels/versions.py:19 +msgid "Versions" +msgstr "バージョン" + +#: templates/debug_toolbar/base.html:23 +msgid "Hide toolbar" +msgstr "ツールバーを隠す" + +#: templates/debug_toolbar/base.html:23 +msgid "Hide" +msgstr "隠す" + +#: templates/debug_toolbar/base.html:25 templates/debug_toolbar/base.html:26 +msgid "Toggle Theme" +msgstr "" + +#: templates/debug_toolbar/base.html:35 +msgid "Show toolbar" +msgstr "ツールバーを表示" + +#: templates/debug_toolbar/includes/panel_button.html:4 +msgid "Disable for next and successive requests" +msgstr "" + +#: templates/debug_toolbar/includes/panel_button.html:4 +msgid "Enable for next and successive requests" +msgstr "" + +#: templates/debug_toolbar/panels/alerts.html:4 +msgid "Alerts found" +msgstr "" + +#: templates/debug_toolbar/panels/alerts.html:11 +msgid "No alerts found" +msgstr "" + +#: templates/debug_toolbar/panels/cache.html:2 +msgid "Summary" +msgstr "" + +#: templates/debug_toolbar/panels/cache.html:6 +msgid "Total calls" +msgstr "" + +#: templates/debug_toolbar/panels/cache.html:7 +msgid "Total time" +msgstr "" + +#: templates/debug_toolbar/panels/cache.html:8 +msgid "Cache hits" +msgstr "" + +#: templates/debug_toolbar/panels/cache.html:9 +msgid "Cache misses" +msgstr "" + +#: templates/debug_toolbar/panels/cache.html:21 +msgid "Commands" +msgstr "" + +#: templates/debug_toolbar/panels/cache.html:39 +msgid "Calls" +msgstr "" + +#: templates/debug_toolbar/panels/cache.html:43 +#: templates/debug_toolbar/panels/sql.html:36 +msgid "Time (ms)" +msgstr "時間 (ms)" + +#: templates/debug_toolbar/panels/cache.html:44 +msgid "Type" +msgstr "" + +#: templates/debug_toolbar/panels/cache.html:45 +#: templates/debug_toolbar/panels/request.html:8 +msgid "Arguments" +msgstr "引数" + +#: templates/debug_toolbar/panels/cache.html:46 +#: templates/debug_toolbar/panels/request.html:9 +msgid "Keyword arguments" +msgstr "キーワード引数" + +#: templates/debug_toolbar/panels/cache.html:47 +msgid "Backend" +msgstr "バックエンド" + +#: templates/debug_toolbar/panels/headers.html:3 +msgid "Request headers" +msgstr "リクエストヘッダー" + +#: templates/debug_toolbar/panels/headers.html:8 +#: templates/debug_toolbar/panels/headers.html:27 +#: templates/debug_toolbar/panels/headers.html:48 +msgid "Key" +msgstr "キー" + +#: templates/debug_toolbar/panels/headers.html:9 +#: templates/debug_toolbar/panels/headers.html:28 +#: templates/debug_toolbar/panels/headers.html:49 +#: templates/debug_toolbar/panels/history_tr.html:23 +#: templates/debug_toolbar/panels/request_variables.html:12 +#: templates/debug_toolbar/panels/settings.html:6 +#: templates/debug_toolbar/panels/timer.html:11 +msgid "Value" +msgstr "値" + +#: templates/debug_toolbar/panels/headers.html:22 +msgid "Response headers" +msgstr "レスポンスヘッダー" + +#: templates/debug_toolbar/panels/headers.html:41 +msgid "WSGI environ" +msgstr "" + +#: templates/debug_toolbar/panels/headers.html:43 +msgid "" +"Since the WSGI environ inherits the environment of the server, only a " +"significant subset is shown below." +msgstr "" + +#: templates/debug_toolbar/panels/history.html:10 +msgid "Method" +msgstr "" + +#: templates/debug_toolbar/panels/history.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:43 +msgid "Path" +msgstr "パス" + +#: templates/debug_toolbar/panels/history.html:12 +msgid "Request Variables" +msgstr "" + +#: templates/debug_toolbar/panels/history.html:13 +msgid "Status" +msgstr "" + +#: templates/debug_toolbar/panels/history.html:14 +#: templates/debug_toolbar/panels/sql.html:37 +msgid "Action" +msgstr "" + +#: templates/debug_toolbar/panels/history_tr.html:22 +#: templates/debug_toolbar/panels/request_variables.html:11 +msgid "Variable" +msgstr "変数" + +#: templates/debug_toolbar/panels/profiling.html:5 +msgid "Call" +msgstr "" + +#: templates/debug_toolbar/panels/profiling.html:6 +msgid "CumTime" +msgstr "" + +#: templates/debug_toolbar/panels/profiling.html:7 +#: templates/debug_toolbar/panels/profiling.html:9 +msgid "Per" +msgstr "" + +#: templates/debug_toolbar/panels/profiling.html:8 +msgid "TotTime" +msgstr "" + +#: templates/debug_toolbar/panels/profiling.html:10 +msgid "Count" +msgstr "" + +#: templates/debug_toolbar/panels/request.html:3 +msgid "View information" +msgstr "ビューの情報" + +#: templates/debug_toolbar/panels/request.html:7 +msgid "View function" +msgstr "ビュー関数" + +#: templates/debug_toolbar/panels/request.html:10 +msgid "URL name" +msgstr "URL名" + +#: templates/debug_toolbar/panels/request.html:24 +msgid "Cookies" +msgstr "クッキー" + +#: templates/debug_toolbar/panels/request.html:27 +msgid "No cookies" +msgstr "クッキーはありません" + +#: templates/debug_toolbar/panels/request.html:31 +msgid "Session data" +msgstr "セッションデータ" + +#: templates/debug_toolbar/panels/request.html:34 +msgid "No session data" +msgstr "セッションデータはありません" + +#: templates/debug_toolbar/panels/request.html:38 +msgid "GET data" +msgstr "" + +#: templates/debug_toolbar/panels/request.html:41 +msgid "No GET data" +msgstr "" + +#: templates/debug_toolbar/panels/request.html:45 +msgid "POST data" +msgstr "" + +#: templates/debug_toolbar/panels/request.html:48 +msgid "No POST data" +msgstr "" + +#: templates/debug_toolbar/panels/settings.html:5 +msgid "Setting" +msgstr "設定項目" + +#: templates/debug_toolbar/panels/signals.html:5 +msgid "Signal" +msgstr "シグナル" + +#: templates/debug_toolbar/panels/signals.html:6 +msgid "Receivers" +msgstr "レシーバー" + +#: templates/debug_toolbar/panels/sql.html:6 +#, python-format +msgid "%(num)s query" +msgid_plural "%(num)s queries" +msgstr[0] "" + +#: templates/debug_toolbar/panels/sql.html:8 +#, python-format +msgid "" +"including %(count)s similar" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:12 +#, python-format +msgid "" +"and %(dupes)s duplicates" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:34 +msgid "Query" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:35 +#: templates/debug_toolbar/panels/timer.html:36 +msgid "Timeline" +msgstr "タイムライン" + +#: templates/debug_toolbar/panels/sql.html:52 +#, python-format +msgid "%(count)s similar queries." +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:58 +#, python-format +msgid "Duplicated %(dupes)s times." +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:95 +msgid "Connection:" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:97 +msgid "Isolation level:" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:100 +msgid "Transaction status:" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:114 +msgid "(unknown)" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:123 +msgid "No SQL queries were recorded during this request." +msgstr "" + +#: templates/debug_toolbar/panels/sql_explain.html:4 +msgid "SQL explained" +msgstr "" + +#: templates/debug_toolbar/panels/sql_explain.html:9 +#: templates/debug_toolbar/panels/sql_profile.html:10 +#: templates/debug_toolbar/panels/sql_select.html:9 +msgid "Executed SQL" +msgstr "実行されたSQL" + +#: templates/debug_toolbar/panels/sql_explain.html:13 +#: templates/debug_toolbar/panels/sql_profile.html:14 +#: templates/debug_toolbar/panels/sql_select.html:13 +msgid "Database" +msgstr "データベース" + +#: templates/debug_toolbar/panels/sql_profile.html:4 +msgid "SQL profiled" +msgstr "" + +#: templates/debug_toolbar/panels/sql_profile.html:37 +msgid "Error" +msgstr "エラー" + +#: templates/debug_toolbar/panels/sql_select.html:4 +msgid "SQL selected" +msgstr "" + +#: templates/debug_toolbar/panels/sql_select.html:36 +msgid "Empty set" +msgstr "" + +#: templates/debug_toolbar/panels/staticfiles.html:3 +msgid "Static file path" +msgid_plural "Static file paths" +msgstr[0] "静的ファイルのパス" + +#: templates/debug_toolbar/panels/staticfiles.html:7 +#, python-format +msgid "(prefix %(prefix)s)" +msgstr "" + +#: templates/debug_toolbar/panels/staticfiles.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:22 +#: templates/debug_toolbar/panels/staticfiles.html:34 +#: templates/debug_toolbar/panels/templates.html:10 +#: templates/debug_toolbar/panels/templates.html:30 +#: templates/debug_toolbar/panels/templates.html:47 +msgid "None" +msgstr "ありません" + +#: templates/debug_toolbar/panels/staticfiles.html:14 +msgid "Static file app" +msgid_plural "Static file apps" +msgstr[0] "静的ファイルを含むアプリケーション" + +#: templates/debug_toolbar/panels/staticfiles.html:25 +msgid "Static file" +msgid_plural "Static files" +msgstr[0] "静的ファイル" + +#: templates/debug_toolbar/panels/staticfiles.html:39 +#, python-format +msgid "%(payload_count)s file" +msgid_plural "%(payload_count)s files" +msgstr[0] "" + +#: templates/debug_toolbar/panels/staticfiles.html:44 +msgid "Location" +msgstr "" + +#: templates/debug_toolbar/panels/template_source.html:4 +msgid "Template source:" +msgstr "" + +#: templates/debug_toolbar/panels/templates.html:2 +msgid "Template path" +msgid_plural "Template paths" +msgstr[0] "" + +#: templates/debug_toolbar/panels/templates.html:13 +msgid "Template" +msgid_plural "Templates" +msgstr[0] "テンプレート" + +#: templates/debug_toolbar/panels/templates.html:22 +#: templates/debug_toolbar/panels/templates.html:40 +msgid "Toggle context" +msgstr "" + +#: templates/debug_toolbar/panels/templates.html:33 +msgid "Context processor" +msgid_plural "Context processors" +msgstr[0] "コンテキストプロセッサー" + +#: templates/debug_toolbar/panels/timer.html:2 +msgid "Resource usage" +msgstr "" + +#: templates/debug_toolbar/panels/timer.html:10 +msgid "Resource" +msgstr "リソース" + +#: templates/debug_toolbar/panels/timer.html:26 +msgid "Browser timing" +msgstr "" + +#: templates/debug_toolbar/panels/timer.html:35 +msgid "Timing attribute" +msgstr "" + +#: templates/debug_toolbar/panels/timer.html:37 +msgid "Milliseconds since navigation start (+length)" +msgstr "" + +#: templates/debug_toolbar/panels/versions.html:10 +msgid "Package" +msgstr "" + +#: templates/debug_toolbar/panels/versions.html:11 +msgid "Name" +msgstr "名前" + +#: templates/debug_toolbar/panels/versions.html:12 +msgid "Version" +msgstr "バージョン" + +#: templates/debug_toolbar/redirect.html:10 +msgid "Location:" +msgstr "" + +#: templates/debug_toolbar/redirect.html:12 +msgid "" +"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." +msgstr "" + +#: views.py:16 +msgid "" +"Data for this panel isn't available anymore. Please reload the page and " +"retry." +msgstr "" diff --git a/debug_toolbar/locale/ko/LC_MESSAGES/django.mo b/debug_toolbar/locale/ko/LC_MESSAGES/django.mo new file mode 100644 index 000000000..592198de1 Binary files /dev/null and b/debug_toolbar/locale/ko/LC_MESSAGES/django.mo differ diff --git a/debug_toolbar/locale/ko/LC_MESSAGES/django.po b/debug_toolbar/locale/ko/LC_MESSAGES/django.po new file mode 100644 index 000000000..97cdf8c61 --- /dev/null +++ b/debug_toolbar/locale/ko/LC_MESSAGES/django.po @@ -0,0 +1,690 @@ +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# +# Translators: +# yeongkwang, 2022 +msgid "" +msgstr "" +"Project-Id-Version: Django Debug Toolbar\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-08-06 07:12-0500\n" +"PO-Revision-Date: 2010-11-30 00:00+0000\n" +"Last-Translator: yeongkwang, 2022\n" +"Language-Team: Korean (http://app.transifex.com/django-debug-toolbar/django-debug-toolbar/language/ko/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ko\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: apps.py:18 +msgid "Debug Toolbar" +msgstr "Debug Toolbar" + +#: panels/alerts.py:67 +#, python-brace-format +msgid "" +"Form with id \"{form_id}\" contains file input, but does not have the " +"attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:70 +msgid "" +"Form contains file input, but does not have the attribute " +"enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:73 +#, python-brace-format +msgid "" +"Input element references form with id \"{form_id}\", but the form does not " +"have the attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:77 +msgid "Alerts" +msgstr "" + +#: panels/cache.py:168 +msgid "Cache" +msgstr "캐시" + +#: panels/cache.py:174 +#, python-format +msgid "%(cache_calls)d call in %(time).2fms" +msgid_plural "%(cache_calls)d calls in %(time).2fms" +msgstr[0] "%(time).2f 밀리초 동안 %(cache_calls)d번 호출" + +#: panels/cache.py:183 +#, python-format +msgid "Cache calls from %(count)d backend" +msgid_plural "Cache calls from %(count)d backends" +msgstr[0] "백엔드에서 %(count)d개의 캐시 호출" + +#: panels/headers.py:31 +msgid "Headers" +msgstr "헤더" + +#: panels/history/panel.py:19 panels/history/panel.py:20 +msgid "History" +msgstr "히스토리" + +#: panels/profiling.py:140 +msgid "Profiling" +msgstr "프로파일링" + +#: panels/redirects.py:17 +msgid "Intercept redirects" +msgstr "리다이렉션 가로채기" + +#: panels/request.py:16 +msgid "Request" +msgstr "요청" + +#: panels/request.py:38 +msgid "" +msgstr "" + +#: panels/request.py:55 +msgid "" +msgstr "<사용할 수 없음>" + +#: panels/settings.py:17 +msgid "Settings" +msgstr "설정" + +#: panels/settings.py:20 +#, python-format +msgid "Settings from %s" +msgstr "설정 %s" + +#: panels/signals.py:57 +#, python-format +msgid "%(num_receivers)d receiver of 1 signal" +msgid_plural "%(num_receivers)d receivers of 1 signal" +msgstr[0] "1개의 시그널 %(num_receivers)d개의 리시버" + +#: panels/signals.py:62 +#, python-format +msgid "%(num_receivers)d receiver of %(num_signals)d signals" +msgid_plural "%(num_receivers)d receivers of %(num_signals)d signals" +msgstr[0] "%(num_signals)d개의 시그널 %(num_receivers)d개의 리시버" + +#: panels/signals.py:67 +msgid "Signals" +msgstr "시그널" + +#: panels/sql/panel.py:30 panels/sql/panel.py:41 +msgid "Read uncommitted" +msgstr "" + +#: panels/sql/panel.py:31 panels/sql/panel.py:43 +msgid "Read committed" +msgstr "" + +#: panels/sql/panel.py:32 panels/sql/panel.py:45 +msgid "Repeatable read" +msgstr "" + +#: panels/sql/panel.py:33 panels/sql/panel.py:47 +msgid "Serializable" +msgstr "" + +#: panels/sql/panel.py:39 +msgid "Autocommit" +msgstr "" + +#: panels/sql/panel.py:61 panels/sql/panel.py:71 +msgid "Idle" +msgstr "유휴" + +#: panels/sql/panel.py:62 panels/sql/panel.py:72 +msgid "Active" +msgstr "활성" + +#: panels/sql/panel.py:63 panels/sql/panel.py:73 +msgid "In transaction" +msgstr "트랜잭션" + +#: panels/sql/panel.py:64 panels/sql/panel.py:74 +msgid "In error" +msgstr "에러" + +#: panels/sql/panel.py:65 panels/sql/panel.py:75 +msgid "Unknown" +msgstr "알 수 없음" + +#: panels/sql/panel.py:162 +msgid "SQL" +msgstr "SQL" + +#: panels/sql/panel.py:168 +#, python-format +msgid "%(query_count)d query in %(sql_time).2fms" +msgid_plural "%(query_count)d queries in %(sql_time).2fms" +msgstr[0] "%(sql_time).2f 밀리초 동안 %(query_count)d개의 쿼리" + +#: panels/sql/panel.py:180 +#, python-format +msgid "SQL queries from %(count)d connection" +msgid_plural "SQL queries from %(count)d connections" +msgstr[0] "SQL 쿼리 %(count)d개의 커넥션" + +#: panels/staticfiles.py:82 +#, python-format +msgid "Static files (%(num_found)s found, %(num_used)s used)" +msgstr "정적 파일 (%(num_found)s개 찾음, %(num_used)s개 사용됨)" + +#: panels/staticfiles.py:103 +msgid "Static files" +msgstr "정적 파일" + +#: panels/staticfiles.py:109 +#, python-format +msgid "%(num_used)s file used" +msgid_plural "%(num_used)s files used" +msgstr[0] "%(num_used)s개의 파일 사용됨" + +#: panels/templates/panel.py:101 +msgid "Templates" +msgstr "템플릿" + +#: panels/templates/panel.py:106 +#, python-format +msgid "Templates (%(num_templates)s rendered)" +msgstr "템플릿 (%(num_templates)s개 렌더링)" + +#: panels/templates/panel.py:195 +msgid "No origin" +msgstr "" + +#: panels/timer.py:27 +#, python-format +msgid "CPU: %(cum)0.2fms (%(total)0.2fms)" +msgstr "" + +#: panels/timer.py:32 +#, python-format +msgid "Total: %0.2fms" +msgstr "" + +#: panels/timer.py:38 templates/debug_toolbar/panels/history.html:9 +#: templates/debug_toolbar/panels/sql_explain.html:11 +#: templates/debug_toolbar/panels/sql_profile.html:12 +#: templates/debug_toolbar/panels/sql_select.html:11 +msgid "Time" +msgstr "시각" + +#: panels/timer.py:46 +msgid "User CPU time" +msgstr "" + +#: panels/timer.py:46 +#, python-format +msgid "%(utime)0.3f msec" +msgstr "" + +#: panels/timer.py:47 +msgid "System CPU time" +msgstr "" + +#: panels/timer.py:47 +#, python-format +msgid "%(stime)0.3f msec" +msgstr "" + +#: panels/timer.py:48 +msgid "Total CPU time" +msgstr "" + +#: panels/timer.py:48 +#, python-format +msgid "%(total)0.3f msec" +msgstr "" + +#: panels/timer.py:49 +msgid "Elapsed time" +msgstr "경과 시간" + +#: panels/timer.py:49 +#, python-format +msgid "%(total_time)0.3f msec" +msgstr "" + +#: panels/timer.py:51 +msgid "Context switches" +msgstr "컨텍스트 스위치" + +#: panels/timer.py:52 +#, python-format +msgid "%(vcsw)d voluntary, %(ivcsw)d involuntary" +msgstr "" + +#: panels/versions.py:19 +msgid "Versions" +msgstr "버전" + +#: templates/debug_toolbar/base.html:23 +msgid "Hide toolbar" +msgstr "툴바 숨기기" + +#: templates/debug_toolbar/base.html:23 +msgid "Hide" +msgstr "숨기기" + +#: templates/debug_toolbar/base.html:25 templates/debug_toolbar/base.html:26 +msgid "Toggle Theme" +msgstr "" + +#: templates/debug_toolbar/base.html:35 +msgid "Show toolbar" +msgstr "툴바 열기" + +#: templates/debug_toolbar/includes/panel_button.html:4 +msgid "Disable for next and successive requests" +msgstr "다음 요청부터 비활성화 됩니다." + +#: templates/debug_toolbar/includes/panel_button.html:4 +msgid "Enable for next and successive requests" +msgstr "다음 요청부터 활성화 됩니다." + +#: templates/debug_toolbar/panels/alerts.html:4 +msgid "Alerts found" +msgstr "" + +#: templates/debug_toolbar/panels/alerts.html:11 +msgid "No alerts found" +msgstr "" + +#: templates/debug_toolbar/panels/cache.html:2 +msgid "Summary" +msgstr "개요" + +#: templates/debug_toolbar/panels/cache.html:6 +msgid "Total calls" +msgstr "총 요청 개수" + +#: templates/debug_toolbar/panels/cache.html:7 +msgid "Total time" +msgstr "총 소요 시간" + +#: templates/debug_toolbar/panels/cache.html:8 +msgid "Cache hits" +msgstr "캐시 적중" + +#: templates/debug_toolbar/panels/cache.html:9 +msgid "Cache misses" +msgstr "캐시 비적중" + +#: templates/debug_toolbar/panels/cache.html:21 +msgid "Commands" +msgstr "명령" + +#: templates/debug_toolbar/panels/cache.html:39 +msgid "Calls" +msgstr "호출" + +#: templates/debug_toolbar/panels/cache.html:43 +#: templates/debug_toolbar/panels/sql.html:36 +msgid "Time (ms)" +msgstr "시간 (ms)" + +#: templates/debug_toolbar/panels/cache.html:44 +msgid "Type" +msgstr "타입" + +#: templates/debug_toolbar/panels/cache.html:45 +#: templates/debug_toolbar/panels/request.html:8 +msgid "Arguments" +msgstr "매개변수" + +#: templates/debug_toolbar/panels/cache.html:46 +#: templates/debug_toolbar/panels/request.html:9 +msgid "Keyword arguments" +msgstr "키워드 매개변수" + +#: templates/debug_toolbar/panels/cache.html:47 +msgid "Backend" +msgstr "백엔드" + +#: templates/debug_toolbar/panels/headers.html:3 +msgid "Request headers" +msgstr "요청 헤더" + +#: templates/debug_toolbar/panels/headers.html:8 +#: templates/debug_toolbar/panels/headers.html:27 +#: templates/debug_toolbar/panels/headers.html:48 +msgid "Key" +msgstr "키" + +#: templates/debug_toolbar/panels/headers.html:9 +#: templates/debug_toolbar/panels/headers.html:28 +#: templates/debug_toolbar/panels/headers.html:49 +#: templates/debug_toolbar/panels/history_tr.html:23 +#: templates/debug_toolbar/panels/request_variables.html:12 +#: templates/debug_toolbar/panels/settings.html:6 +#: templates/debug_toolbar/panels/timer.html:11 +msgid "Value" +msgstr "값" + +#: templates/debug_toolbar/panels/headers.html:22 +msgid "Response headers" +msgstr "응답 헤더" + +#: templates/debug_toolbar/panels/headers.html:41 +msgid "WSGI environ" +msgstr "WSGI 환경" + +#: templates/debug_toolbar/panels/headers.html:43 +msgid "" +"Since the WSGI environ inherits the environment of the server, only a " +"significant subset is shown below." +msgstr "WSGI 환경이 서버 환경을 상속하므로 아래에는 중요한 하위 집합만 표시됩니다." + +#: templates/debug_toolbar/panels/history.html:10 +msgid "Method" +msgstr "메서드" + +#: templates/debug_toolbar/panels/history.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:43 +msgid "Path" +msgstr "경로" + +#: templates/debug_toolbar/panels/history.html:12 +msgid "Request Variables" +msgstr "요청 변수" + +#: templates/debug_toolbar/panels/history.html:13 +msgid "Status" +msgstr "상태 코드" + +#: templates/debug_toolbar/panels/history.html:14 +#: templates/debug_toolbar/panels/sql.html:37 +msgid "Action" +msgstr "액션" + +#: templates/debug_toolbar/panels/history_tr.html:22 +#: templates/debug_toolbar/panels/request_variables.html:11 +msgid "Variable" +msgstr "변수" + +#: templates/debug_toolbar/panels/profiling.html:5 +msgid "Call" +msgstr "호출" + +#: templates/debug_toolbar/panels/profiling.html:6 +msgid "CumTime" +msgstr "" + +#: templates/debug_toolbar/panels/profiling.html:7 +#: templates/debug_toolbar/panels/profiling.html:9 +msgid "Per" +msgstr "" + +#: templates/debug_toolbar/panels/profiling.html:8 +msgid "TotTime" +msgstr "" + +#: templates/debug_toolbar/panels/profiling.html:10 +msgid "Count" +msgstr "개수" + +#: templates/debug_toolbar/panels/request.html:3 +msgid "View information" +msgstr "View 정보" + +#: templates/debug_toolbar/panels/request.html:7 +msgid "View function" +msgstr "View 함수" + +#: templates/debug_toolbar/panels/request.html:10 +msgid "URL name" +msgstr "URL 명칭" + +#: templates/debug_toolbar/panels/request.html:24 +msgid "Cookies" +msgstr "쿠키" + +#: templates/debug_toolbar/panels/request.html:27 +msgid "No cookies" +msgstr "쿠키 없음" + +#: templates/debug_toolbar/panels/request.html:31 +msgid "Session data" +msgstr "세션 데이터" + +#: templates/debug_toolbar/panels/request.html:34 +msgid "No session data" +msgstr "세션 데이터 없음" + +#: templates/debug_toolbar/panels/request.html:38 +msgid "GET data" +msgstr "GET 요청 데이터" + +#: templates/debug_toolbar/panels/request.html:41 +msgid "No GET data" +msgstr "GET 요청 데이터 없음" + +#: templates/debug_toolbar/panels/request.html:45 +msgid "POST data" +msgstr "POST 요청 데이터" + +#: templates/debug_toolbar/panels/request.html:48 +msgid "No POST data" +msgstr "POST 요청 데이터 없음" + +#: templates/debug_toolbar/panels/settings.html:5 +msgid "Setting" +msgstr "설정" + +#: templates/debug_toolbar/panels/signals.html:5 +msgid "Signal" +msgstr "시그널" + +#: templates/debug_toolbar/panels/signals.html:6 +msgid "Receivers" +msgstr "리시버" + +#: templates/debug_toolbar/panels/sql.html:6 +#, python-format +msgid "%(num)s query" +msgid_plural "%(num)s queries" +msgstr[0] "%(num)s개의 쿼리" + +#: templates/debug_toolbar/panels/sql.html:8 +#, python-format +msgid "" +"including %(count)s similar" +msgstr "%(count)s 개의 유사한 쿼리 포함" + +#: templates/debug_toolbar/panels/sql.html:12 +#, python-format +msgid "" +"and %(dupes)s duplicates" +msgstr "그리고 %(dupes)s개의 중복" + +#: templates/debug_toolbar/panels/sql.html:34 +msgid "Query" +msgstr "쿼리" + +#: templates/debug_toolbar/panels/sql.html:35 +#: templates/debug_toolbar/panels/timer.html:36 +msgid "Timeline" +msgstr "타임라인" + +#: templates/debug_toolbar/panels/sql.html:52 +#, python-format +msgid "%(count)s similar queries." +msgstr "%(count)s개의 유사한 쿼리" + +#: templates/debug_toolbar/panels/sql.html:58 +#, python-format +msgid "Duplicated %(dupes)s times." +msgstr "%(dupes)s번 중복됩니다." + +#: templates/debug_toolbar/panels/sql.html:95 +msgid "Connection:" +msgstr "커넥션:" + +#: templates/debug_toolbar/panels/sql.html:97 +msgid "Isolation level:" +msgstr "격리 수준:" + +#: templates/debug_toolbar/panels/sql.html:100 +msgid "Transaction status:" +msgstr "트랜잭션 상태:" + +#: templates/debug_toolbar/panels/sql.html:114 +msgid "(unknown)" +msgstr "(알 수 없음)" + +#: templates/debug_toolbar/panels/sql.html:123 +msgid "No SQL queries were recorded during this request." +msgstr "이 요청을 처리하는 동안 기록된 SQL 쿼리가 없습니다." + +#: templates/debug_toolbar/panels/sql_explain.html:4 +msgid "SQL explained" +msgstr "SQL 설명" + +#: templates/debug_toolbar/panels/sql_explain.html:9 +#: templates/debug_toolbar/panels/sql_profile.html:10 +#: templates/debug_toolbar/panels/sql_select.html:9 +msgid "Executed SQL" +msgstr "실행된 SQL 구문" + +#: templates/debug_toolbar/panels/sql_explain.html:13 +#: templates/debug_toolbar/panels/sql_profile.html:14 +#: templates/debug_toolbar/panels/sql_select.html:13 +msgid "Database" +msgstr "데이터베이스" + +#: templates/debug_toolbar/panels/sql_profile.html:4 +msgid "SQL profiled" +msgstr "SQL 성능 분석" + +#: templates/debug_toolbar/panels/sql_profile.html:37 +msgid "Error" +msgstr "에러" + +#: templates/debug_toolbar/panels/sql_select.html:4 +msgid "SQL selected" +msgstr "선택된 SQL 구문" + +#: templates/debug_toolbar/panels/sql_select.html:36 +msgid "Empty set" +msgstr "빈 셋" + +#: templates/debug_toolbar/panels/staticfiles.html:3 +msgid "Static file path" +msgid_plural "Static file paths" +msgstr[0] "정적 파일 경로" + +#: templates/debug_toolbar/panels/staticfiles.html:7 +#, python-format +msgid "(prefix %(prefix)s)" +msgstr "" + +#: templates/debug_toolbar/panels/staticfiles.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:22 +#: templates/debug_toolbar/panels/staticfiles.html:34 +#: templates/debug_toolbar/panels/templates.html:10 +#: templates/debug_toolbar/panels/templates.html:30 +#: templates/debug_toolbar/panels/templates.html:47 +msgid "None" +msgstr "" + +#: templates/debug_toolbar/panels/staticfiles.html:14 +msgid "Static file app" +msgid_plural "Static file apps" +msgstr[0] "정적 파일 앱" + +#: templates/debug_toolbar/panels/staticfiles.html:25 +msgid "Static file" +msgid_plural "Static files" +msgstr[0] "정적 파일" + +#: templates/debug_toolbar/panels/staticfiles.html:39 +#, python-format +msgid "%(payload_count)s file" +msgid_plural "%(payload_count)s files" +msgstr[0] "%(payload_count)s개 파일" + +#: templates/debug_toolbar/panels/staticfiles.html:44 +msgid "Location" +msgstr "위치" + +#: templates/debug_toolbar/panels/template_source.html:4 +msgid "Template source:" +msgstr "템플릿 소스:" + +#: templates/debug_toolbar/panels/templates.html:2 +msgid "Template path" +msgid_plural "Template paths" +msgstr[0] "템플릿 경로" + +#: templates/debug_toolbar/panels/templates.html:13 +msgid "Template" +msgid_plural "Templates" +msgstr[0] "템플릿" + +#: templates/debug_toolbar/panels/templates.html:22 +#: templates/debug_toolbar/panels/templates.html:40 +msgid "Toggle context" +msgstr "컨텍스트 토글" + +#: templates/debug_toolbar/panels/templates.html:33 +msgid "Context processor" +msgid_plural "Context processors" +msgstr[0] "컨텍스트 프로세서" + +#: templates/debug_toolbar/panels/timer.html:2 +msgid "Resource usage" +msgstr "리소스 사용량" + +#: templates/debug_toolbar/panels/timer.html:10 +msgid "Resource" +msgstr "리소스" + +#: templates/debug_toolbar/panels/timer.html:26 +msgid "Browser timing" +msgstr "브라우저 타이밍" + +#: templates/debug_toolbar/panels/timer.html:35 +msgid "Timing attribute" +msgstr "타이밍 속성" + +#: templates/debug_toolbar/panels/timer.html:37 +msgid "Milliseconds since navigation start (+length)" +msgstr "탐색 시작후 밀리초 소요 (+길이)" + +#: templates/debug_toolbar/panels/versions.html:10 +msgid "Package" +msgstr "패키지" + +#: templates/debug_toolbar/panels/versions.html:11 +msgid "Name" +msgstr "이름" + +#: templates/debug_toolbar/panels/versions.html:12 +msgid "Version" +msgstr "버전" + +#: templates/debug_toolbar/redirect.html:10 +msgid "Location:" +msgstr "위치:" + +#: templates/debug_toolbar/redirect.html:12 +msgid "" +"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." +msgstr "Django Debug Toolbar는 디버그 보기를 제공하기 위해 위 URL으로 리다이렉션을 가로챘습니다. 위 링크를 클릭하여 정상적으로 리다이렉션을 계속 할수 있습니다." + +#: views.py:16 +msgid "" +"Data for this panel isn't available anymore. Please reload the page and " +"retry." +msgstr "이 패널의 데이터는 더이상 사용할 수 없습니다. 페이지를 새로고침한 뒤 다시 시도하십시오." diff --git a/debug_toolbar/locale/nl/LC_MESSAGES/django.mo b/debug_toolbar/locale/nl/LC_MESSAGES/django.mo index 043aedede..173e26b10 100644 Binary files a/debug_toolbar/locale/nl/LC_MESSAGES/django.mo and b/debug_toolbar/locale/nl/LC_MESSAGES/django.mo differ diff --git a/debug_toolbar/locale/nl/LC_MESSAGES/django.po b/debug_toolbar/locale/nl/LC_MESSAGES/django.po index 07b95546b..b7a9bd730 100644 --- a/debug_toolbar/locale/nl/LC_MESSAGES/django.po +++ b/debug_toolbar/locale/nl/LC_MESSAGES/django.po @@ -1,71 +1,78 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# -# +# +# # Translators: # Ingo Berben , 2012-2013 msgid "" msgstr "" "Project-Id-Version: Django Debug Toolbar\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-04-25 21:52+0200\n" -"PO-Revision-Date: 2014-04-25 19:53+0000\n" -"Last-Translator: Aymeric Augustin \n" -"Language-Team: Dutch (http://www.transifex.com/projects/p/django-debug-toolbar/language/nl/)\n" +"POT-Creation-Date: 2024-08-06 07:12-0500\n" +"PO-Revision-Date: 2010-11-30 00:00+0000\n" +"Last-Translator: Ingo Berben , 2012-2013\n" +"Language-Team: Dutch (http://app.transifex.com/django-debug-toolbar/django-debug-toolbar/language/nl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: nl\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: apps.py:11 +#: apps.py:18 msgid "Debug Toolbar" msgstr "" -#: views.py:14 +#: panels/alerts.py:67 +#, python-brace-format msgid "" -"Data for this panel isn't available anymore. Please reload the page and " -"retry." +"Form with id \"{form_id}\" contains file input, but does not have the " +"attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:70 +msgid "" +"Form contains file input, but does not have the attribute " +"enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:73 +#, python-brace-format +msgid "" +"Input element references form with id \"{form_id}\", but the form does not " +"have the attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:77 +msgid "Alerts" msgstr "" -#: panels/cache.py:191 +#: panels/cache.py:168 msgid "Cache" msgstr "Cache" -#: panels/cache.py:196 +#: panels/cache.py:174 #, python-format msgid "%(cache_calls)d call in %(time).2fms" msgid_plural "%(cache_calls)d calls in %(time).2fms" msgstr[0] "" msgstr[1] "" -#: panels/cache.py:204 +#: panels/cache.py:183 #, python-format msgid "Cache calls from %(count)d backend" msgid_plural "Cache calls from %(count)d backends" msgstr[0] "" msgstr[1] "" -#: panels/headers.py:35 +#: panels/headers.py:31 msgid "Headers" msgstr "" -#: panels/logging.py:64 -msgid "Logging" +#: panels/history/panel.py:19 panels/history/panel.py:20 +msgid "History" msgstr "" -#: panels/logging.py:70 -#, python-format -msgid "%(count)s message" -msgid_plural "%(count)s messages" -msgstr[0] "%(count)s bericht" -msgstr[1] "%(count)s berichten" - -#: panels/logging.py:73 -msgid "Log messages" -msgstr "" - -#: panels/profiling.py:127 +#: panels/profiling.py:140 msgid "Profiling" msgstr "Profilering" @@ -73,213 +80,228 @@ msgstr "Profilering" msgid "Intercept redirects" msgstr "" -#: panels/request.py:18 +#: panels/request.py:16 msgid "Request" msgstr "" -#: panels/request.py:35 +#: panels/request.py:38 msgid "" msgstr "" -#: panels/request.py:47 +#: panels/request.py:55 msgid "" msgstr "" -#: panels/settings.py:20 +#: panels/settings.py:17 msgid "Settings" msgstr "Instellingen" -#: panels/settings.py:23 +#: panels/settings.py:20 #, python-format -msgid "Settings from %s" -msgstr "Instellingen van %s" +msgid "Settings from %s" +msgstr "" -#: panels/signals.py:45 +#: panels/signals.py:57 #, python-format msgid "%(num_receivers)d receiver of 1 signal" msgid_plural "%(num_receivers)d receivers of 1 signal" msgstr[0] "%(num_receivers)d ontvanger van 1 signaal" msgstr[1] "%(num_receivers)d ontvangers van 1 signaal" -#: panels/signals.py:48 +#: panels/signals.py:62 #, python-format msgid "%(num_receivers)d receiver of %(num_signals)d signals" msgid_plural "%(num_receivers)d receivers of %(num_signals)d signals" msgstr[0] "%(num_receivers)d ontvanger van %(num_signals)d signalen" msgstr[1] "%(num_receivers)d ontvangers van %(num_signals)d signalen" -#: panels/signals.py:53 +#: panels/signals.py:67 msgid "Signals" msgstr "Signalen" -#: panels/staticfiles.py:89 +#: panels/sql/panel.py:30 panels/sql/panel.py:41 +msgid "Read uncommitted" +msgstr "" + +#: panels/sql/panel.py:31 panels/sql/panel.py:43 +msgid "Read committed" +msgstr "" + +#: panels/sql/panel.py:32 panels/sql/panel.py:45 +msgid "Repeatable read" +msgstr "" + +#: panels/sql/panel.py:33 panels/sql/panel.py:47 +msgid "Serializable" +msgstr "Serializeerbaar" + +#: panels/sql/panel.py:39 +msgid "Autocommit" +msgstr "" + +#: panels/sql/panel.py:61 panels/sql/panel.py:71 +msgid "Idle" +msgstr "" + +#: panels/sql/panel.py:62 panels/sql/panel.py:72 +msgid "Active" +msgstr "Actief" + +#: panels/sql/panel.py:63 panels/sql/panel.py:73 +msgid "In transaction" +msgstr "" + +#: panels/sql/panel.py:64 panels/sql/panel.py:74 +msgid "In error" +msgstr "Foutief" + +#: panels/sql/panel.py:65 panels/sql/panel.py:75 +msgid "Unknown" +msgstr "Niet gekend" + +#: panels/sql/panel.py:162 +msgid "SQL" +msgstr "SQL" + +#: panels/sql/panel.py:168 +#, python-format +msgid "%(query_count)d query in %(sql_time).2fms" +msgid_plural "%(query_count)d queries in %(sql_time).2fms" +msgstr[0] "" +msgstr[1] "" + +#: panels/sql/panel.py:180 +#, python-format +msgid "SQL queries from %(count)d connection" +msgid_plural "SQL queries from %(count)d connections" +msgstr[0] "" +msgstr[1] "" + +#: panels/staticfiles.py:82 #, python-format msgid "Static files (%(num_found)s found, %(num_used)s used)" msgstr "" -#: panels/staticfiles.py:107 +#: panels/staticfiles.py:103 msgid "Static files" msgstr "" -#: panels/staticfiles.py:112 +#: panels/staticfiles.py:109 #, python-format msgid "%(num_used)s file used" msgid_plural "%(num_used)s files used" msgstr[0] "" msgstr[1] "" -#: panels/timer.py:23 +#: panels/templates/panel.py:101 +msgid "Templates" +msgstr "Templates" + +#: panels/templates/panel.py:106 +#, python-format +msgid "Templates (%(num_templates)s rendered)" +msgstr "Templates (%(num_templates)s gerenderd)" + +#: panels/templates/panel.py:195 +msgid "No origin" +msgstr "" + +#: panels/timer.py:27 #, python-format msgid "CPU: %(cum)0.2fms (%(total)0.2fms)" msgstr "CPU: %(cum)0.2fms (%(total)0.2fms)" -#: panels/timer.py:28 +#: panels/timer.py:32 #, python-format msgid "Total: %0.2fms" msgstr "Totaal: %0.2fms" -#: panels/timer.py:34 templates/debug_toolbar/panels/logging.html:7 +#: panels/timer.py:38 templates/debug_toolbar/panels/history.html:9 #: templates/debug_toolbar/panels/sql_explain.html:11 #: templates/debug_toolbar/panels/sql_profile.html:12 #: templates/debug_toolbar/panels/sql_select.html:11 msgid "Time" msgstr "Tijd" -#: panels/timer.py:42 +#: panels/timer.py:46 msgid "User CPU time" msgstr "Gebruikers CPU tijd" -#: panels/timer.py:42 +#: panels/timer.py:46 #, python-format msgid "%(utime)0.3f msec" msgstr "%(utime)0.3f msec" -#: panels/timer.py:43 +#: panels/timer.py:47 msgid "System CPU time" msgstr "Systeem CPU tijd" -#: panels/timer.py:43 +#: panels/timer.py:47 #, python-format msgid "%(stime)0.3f msec" msgstr "%(stime)0.3f msec" -#: panels/timer.py:44 +#: panels/timer.py:48 msgid "Total CPU time" msgstr "Totaal CPU tijd" -#: panels/timer.py:44 +#: panels/timer.py:48 #, python-format msgid "%(total)0.3f msec" msgstr "%(total)0.3f msec" -#: panels/timer.py:45 +#: panels/timer.py:49 msgid "Elapsed time" msgstr "Verlopen tijd" -#: panels/timer.py:45 +#: panels/timer.py:49 #, python-format msgid "%(total_time)0.3f msec" msgstr "%(total_time)0.3f msec" -#: panels/timer.py:46 +#: panels/timer.py:51 msgid "Context switches" msgstr "" -#: panels/timer.py:46 +#: panels/timer.py:52 #, python-format msgid "%(vcsw)d voluntary, %(ivcsw)d involuntary" msgstr "%(vcsw)d vrijwillig, %(ivcsw)d niet vrijwillig" -#: panels/versions.py:25 +#: panels/versions.py:19 msgid "Versions" msgstr "Versies" -#: panels/sql/panel.py:22 -msgid "Autocommit" -msgstr "" - -#: panels/sql/panel.py:23 -msgid "Read uncommitted" -msgstr "" - -#: panels/sql/panel.py:24 -msgid "Read committed" -msgstr "" - -#: panels/sql/panel.py:25 -msgid "Repeatable read" -msgstr "" - -#: panels/sql/panel.py:26 -msgid "Serializable" -msgstr "Serializeerbaar" - -#: panels/sql/panel.py:37 -msgid "Idle" -msgstr "" - -#: panels/sql/panel.py:38 -msgid "Active" -msgstr "Actief" - -#: panels/sql/panel.py:39 -msgid "In transaction" -msgstr "" - -#: panels/sql/panel.py:40 -msgid "In error" -msgstr "Foutief" - -#: panels/sql/panel.py:41 -msgid "Unknown" -msgstr "Niet gekend" - -#: panels/sql/panel.py:105 -msgid "SQL" -msgstr "SQL" - -#: panels/templates/panel.py:141 -msgid "Templates" -msgstr "Templates" - -#: panels/templates/panel.py:146 -#, python-format -msgid "Templates (%(num_templates)s rendered)" -msgstr "Templates (%(num_templates)s gerenderd)" - -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide toolbar" msgstr "Verberg toolbar" -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide" msgstr "Verbergen" -#: templates/debug_toolbar/base.html:25 -msgid "Disable for next and successive requests" -msgstr "" - -#: templates/debug_toolbar/base.html:25 -msgid "Enable for next and successive requests" +#: templates/debug_toolbar/base.html:25 templates/debug_toolbar/base.html:26 +msgid "Toggle Theme" msgstr "" -#: templates/debug_toolbar/base.html:47 +#: templates/debug_toolbar/base.html:35 msgid "Show toolbar" msgstr "Bekijk toolbar" -#: templates/debug_toolbar/base.html:53 -msgid "Close" -msgstr "Sluiten" +#: templates/debug_toolbar/includes/panel_button.html:4 +msgid "Disable for next and successive requests" +msgstr "" -#: templates/debug_toolbar/redirect.html:8 -msgid "Location:" +#: templates/debug_toolbar/includes/panel_button.html:4 +msgid "Enable for next and successive requests" msgstr "" -#: templates/debug_toolbar/redirect.html:10 -msgid "" -"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." +#: templates/debug_toolbar/panels/alerts.html:4 +msgid "Alerts found" +msgstr "" + +#: templates/debug_toolbar/panels/alerts.html:11 +msgid "No alerts found" msgstr "" #: templates/debug_toolbar/panels/cache.html:2 @@ -311,7 +333,7 @@ msgid "Calls" msgstr "" #: templates/debug_toolbar/panels/cache.html:43 -#: templates/debug_toolbar/panels/sql.html:20 +#: templates/debug_toolbar/panels/sql.html:36 msgid "Time (ms)" msgstr "Tijd (ms)" @@ -346,10 +368,8 @@ msgstr "Sleutel" #: templates/debug_toolbar/panels/headers.html:9 #: templates/debug_toolbar/panels/headers.html:28 #: templates/debug_toolbar/panels/headers.html:49 -#: templates/debug_toolbar/panels/request.html:33 -#: templates/debug_toolbar/panels/request.html:59 -#: templates/debug_toolbar/panels/request.html:85 -#: templates/debug_toolbar/panels/request.html:110 +#: templates/debug_toolbar/panels/history_tr.html:23 +#: templates/debug_toolbar/panels/request_variables.html:12 #: templates/debug_toolbar/panels/settings.html:6 #: templates/debug_toolbar/panels/timer.html:11 msgid "Value" @@ -369,26 +389,32 @@ msgid "" "significant subset is shown below." msgstr "" -#: templates/debug_toolbar/panels/logging.html:6 -msgid "Level" -msgstr "Niveau" +#: templates/debug_toolbar/panels/history.html:10 +msgid "Method" +msgstr "Methode" -#: templates/debug_toolbar/panels/logging.html:8 -msgid "Channel" -msgstr "Kanaal" +#: templates/debug_toolbar/panels/history.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:43 +msgid "Path" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:9 -msgid "Message" -msgstr "Bericht" +#: templates/debug_toolbar/panels/history.html:12 +msgid "Request Variables" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:10 -#: templates/debug_toolbar/panels/staticfiles.html:45 -msgid "Location" -msgstr "Locatie" +#: templates/debug_toolbar/panels/history.html:13 +msgid "Status" +msgstr "" + +#: templates/debug_toolbar/panels/history.html:14 +#: templates/debug_toolbar/panels/sql.html:37 +msgid "Action" +msgstr "Actie" -#: templates/debug_toolbar/panels/logging.html:26 -msgid "No messages logged" -msgstr "Geen berichten gelogd" +#: templates/debug_toolbar/panels/history_tr.html:22 +#: templates/debug_toolbar/panels/request_variables.html:11 +msgid "Variable" +msgstr "Parameter" #: templates/debug_toolbar/panels/profiling.html:5 msgid "Call" @@ -427,38 +453,31 @@ msgstr "" msgid "Cookies" msgstr "" -#: templates/debug_toolbar/panels/request.html:32 -#: templates/debug_toolbar/panels/request.html:58 -#: templates/debug_toolbar/panels/request.html:84 -#: templates/debug_toolbar/panels/request.html:109 -msgid "Variable" -msgstr "Parameter" - -#: templates/debug_toolbar/panels/request.html:46 +#: templates/debug_toolbar/panels/request.html:27 msgid "No cookies" msgstr "" -#: templates/debug_toolbar/panels/request.html:50 +#: templates/debug_toolbar/panels/request.html:31 msgid "Session data" msgstr "" -#: templates/debug_toolbar/panels/request.html:72 +#: templates/debug_toolbar/panels/request.html:34 msgid "No session data" msgstr "" -#: templates/debug_toolbar/panels/request.html:76 +#: templates/debug_toolbar/panels/request.html:38 msgid "GET data" msgstr "GET data" -#: templates/debug_toolbar/panels/request.html:98 +#: templates/debug_toolbar/panels/request.html:41 msgid "No GET data" msgstr "Geen GET data" -#: templates/debug_toolbar/panels/request.html:102 +#: templates/debug_toolbar/panels/request.html:45 msgid "POST data" msgstr "POST data" -#: templates/debug_toolbar/panels/request.html:123 +#: templates/debug_toolbar/panels/request.html:48 msgid "No POST data" msgstr "Geen POST data" @@ -471,60 +490,69 @@ msgid "Signal" msgstr "Signaal" #: templates/debug_toolbar/panels/signals.html:6 -msgid "Providing" -msgstr "" - -#: templates/debug_toolbar/panels/signals.html:7 msgid "Receivers" msgstr "Ontvangers" -#: templates/debug_toolbar/panels/sql.html:7 +#: templates/debug_toolbar/panels/sql.html:6 #, python-format msgid "%(num)s query" msgid_plural "%(num)s queries" msgstr[0] "" msgstr[1] "" -#: templates/debug_toolbar/panels/sql.html:18 +#: templates/debug_toolbar/panels/sql.html:8 +#, python-format +msgid "" +"including %(count)s similar" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:12 +#, python-format +msgid "" +"and %(dupes)s duplicates" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:34 msgid "Query" msgstr "Query" -#: templates/debug_toolbar/panels/sql.html:19 +#: templates/debug_toolbar/panels/sql.html:35 #: templates/debug_toolbar/panels/timer.html:36 msgid "Timeline" msgstr "Tijdslijn" -#: templates/debug_toolbar/panels/sql.html:21 -msgid "Action" -msgstr "Actie" +#: templates/debug_toolbar/panels/sql.html:52 +#, python-format +msgid "%(count)s similar queries." +msgstr "" -#: templates/debug_toolbar/panels/sql.html:64 +#: templates/debug_toolbar/panels/sql.html:58 +#, python-format +msgid "Duplicated %(dupes)s times." +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:95 msgid "Connection:" msgstr "Verbinding:" -#: templates/debug_toolbar/panels/sql.html:66 +#: templates/debug_toolbar/panels/sql.html:97 msgid "Isolation level:" msgstr "" -#: templates/debug_toolbar/panels/sql.html:69 +#: templates/debug_toolbar/panels/sql.html:100 msgid "Transaction status:" msgstr "Transactiestatus:" -#: templates/debug_toolbar/panels/sql.html:83 +#: templates/debug_toolbar/panels/sql.html:114 msgid "(unknown)" msgstr "(niet gekend)" -#: templates/debug_toolbar/panels/sql.html:92 +#: templates/debug_toolbar/panels/sql.html:123 msgid "No SQL queries were recorded during this request." msgstr "" -#: templates/debug_toolbar/panels/sql_explain.html:3 -#: templates/debug_toolbar/panels/sql_profile.html:3 -#: templates/debug_toolbar/panels/sql_select.html:3 -#: templates/debug_toolbar/panels/template_source.html:3 -msgid "Back" -msgstr "Terug" - #: templates/debug_toolbar/panels/sql_explain.html:4 msgid "SQL explained" msgstr "SQL uitgelegd" @@ -557,39 +585,39 @@ msgstr "" msgid "Empty set" msgstr "Lege set" -#: templates/debug_toolbar/panels/staticfiles.html:4 +#: templates/debug_toolbar/panels/staticfiles.html:3 msgid "Static file path" msgid_plural "Static file paths" msgstr[0] "" msgstr[1] "" -#: templates/debug_toolbar/panels/staticfiles.html:8 +#: templates/debug_toolbar/panels/staticfiles.html:7 #, python-format msgid "(prefix %(prefix)s)" msgstr "" -#: templates/debug_toolbar/panels/staticfiles.html:12 -#: templates/debug_toolbar/panels/staticfiles.html:23 -#: templates/debug_toolbar/panels/staticfiles.html:35 +#: templates/debug_toolbar/panels/staticfiles.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:22 +#: templates/debug_toolbar/panels/staticfiles.html:34 #: templates/debug_toolbar/panels/templates.html:10 -#: templates/debug_toolbar/panels/templates.html:28 -#: templates/debug_toolbar/panels/templates.html:43 +#: templates/debug_toolbar/panels/templates.html:30 +#: templates/debug_toolbar/panels/templates.html:47 msgid "None" msgstr "None" -#: templates/debug_toolbar/panels/staticfiles.html:15 +#: templates/debug_toolbar/panels/staticfiles.html:14 msgid "Static file app" msgid_plural "Static file apps" msgstr[0] "" msgstr[1] "" -#: templates/debug_toolbar/panels/staticfiles.html:26 +#: templates/debug_toolbar/panels/staticfiles.html:25 msgid "Static file" msgid_plural "Static files" msgstr[0] "" msgstr[1] "" -#: templates/debug_toolbar/panels/staticfiles.html:40 +#: templates/debug_toolbar/panels/staticfiles.html:39 #, python-format msgid "%(payload_count)s file" msgid_plural "%(payload_count)s files" @@ -597,8 +625,8 @@ msgstr[0] "" msgstr[1] "" #: templates/debug_toolbar/panels/staticfiles.html:44 -msgid "Path" -msgstr "" +msgid "Location" +msgstr "Locatie" #: templates/debug_toolbar/panels/template_source.html:4 msgid "Template source:" @@ -616,12 +644,12 @@ msgid_plural "Templates" msgstr[0] "Template" msgstr[1] "Templates" -#: templates/debug_toolbar/panels/templates.html:21 -#: templates/debug_toolbar/panels/templates.html:37 +#: templates/debug_toolbar/panels/templates.html:22 +#: templates/debug_toolbar/panels/templates.html:40 msgid "Toggle context" msgstr "" -#: templates/debug_toolbar/panels/templates.html:31 +#: templates/debug_toolbar/panels/templates.html:33 msgid "Context processor" msgid_plural "Context processors" msgstr[0] "" @@ -647,10 +675,31 @@ msgstr "" msgid "Milliseconds since navigation start (+length)" msgstr "" -#: templates/debug_toolbar/panels/versions.html:5 +#: templates/debug_toolbar/panels/versions.html:10 +msgid "Package" +msgstr "Pakket" + +#: templates/debug_toolbar/panels/versions.html:11 msgid "Name" msgstr "Naam" -#: templates/debug_toolbar/panels/versions.html:6 +#: templates/debug_toolbar/panels/versions.html:12 msgid "Version" msgstr "Versie" + +#: templates/debug_toolbar/redirect.html:10 +msgid "Location:" +msgstr "" + +#: templates/debug_toolbar/redirect.html:12 +msgid "" +"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." +msgstr "" + +#: views.py:16 +msgid "" +"Data for this panel isn't available anymore. Please reload the page and " +"retry." +msgstr "" diff --git a/debug_toolbar/locale/pl/LC_MESSAGES/django.mo b/debug_toolbar/locale/pl/LC_MESSAGES/django.mo index 6c1c79142..839619342 100644 Binary files a/debug_toolbar/locale/pl/LC_MESSAGES/django.mo and b/debug_toolbar/locale/pl/LC_MESSAGES/django.mo differ diff --git a/debug_toolbar/locale/pl/LC_MESSAGES/django.po b/debug_toolbar/locale/pl/LC_MESSAGES/django.po index 0d674f1aa..337ad376e 100644 --- a/debug_toolbar/locale/pl/LC_MESSAGES/django.po +++ b/debug_toolbar/locale/pl/LC_MESSAGES/django.po @@ -1,291 +1,321 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# -# +# +# # Translators: -# Konrad Mosoń , 2013 +# Konrad Mosoń , 2013,2015 msgid "" msgstr "" "Project-Id-Version: Django Debug Toolbar\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-04-25 21:52+0200\n" -"PO-Revision-Date: 2014-04-25 19:53+0000\n" -"Last-Translator: Aymeric Augustin \n" -"Language-Team: Polish (http://www.transifex.com/projects/p/django-debug-toolbar/language/pl/)\n" +"POT-Creation-Date: 2024-08-06 07:12-0500\n" +"PO-Revision-Date: 2010-11-30 00:00+0000\n" +"Last-Translator: Konrad Mosoń , 2013,2015\n" +"Language-Team: Polish (http://app.transifex.com/django-debug-toolbar/django-debug-toolbar/language/pl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: pl\n" -"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n" -#: apps.py:11 +#: apps.py:18 msgid "Debug Toolbar" +msgstr "Debug Toolbar" + +#: panels/alerts.py:67 +#, python-brace-format +msgid "" +"Form with id \"{form_id}\" contains file input, but does not have the " +"attribute enctype=\"multipart/form-data\"." msgstr "" -#: views.py:14 +#: panels/alerts.py:70 msgid "" -"Data for this panel isn't available anymore. Please reload the page and " -"retry." +"Form contains file input, but does not have the attribute " +"enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:73 +#, python-brace-format +msgid "" +"Input element references form with id \"{form_id}\", but the form does not " +"have the attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:77 +msgid "Alerts" msgstr "" -#: panels/cache.py:191 +#: panels/cache.py:168 msgid "Cache" msgstr "Cache" -#: panels/cache.py:196 +#: panels/cache.py:174 #, python-format msgid "%(cache_calls)d call in %(time).2fms" msgid_plural "%(cache_calls)d calls in %(time).2fms" msgstr[0] "%(cache_calls)d wywołanie w %(time).2fms" msgstr[1] "%(cache_calls)d wywołania w %(time).2fms" msgstr[2] "%(cache_calls)d wywołań w %(time).2fms" +msgstr[3] "%(cache_calls)d wywołań w %(time).2fms" -#: panels/cache.py:204 +#: panels/cache.py:183 #, python-format msgid "Cache calls from %(count)d backend" msgid_plural "Cache calls from %(count)d backends" msgstr[0] "Wywołań z cache z %(count)d backendu" msgstr[1] "Wywołań z cache z %(count)d backendów" msgstr[2] "Wywołań z cache z %(count)d backendów" +msgstr[3] "Wywołań z cache z %(count)d backendów" -#: panels/headers.py:35 +#: panels/headers.py:31 msgid "Headers" -msgstr "" - -#: panels/logging.py:64 -msgid "Logging" -msgstr "Logi" - -#: panels/logging.py:70 -#, python-format -msgid "%(count)s message" -msgid_plural "%(count)s messages" -msgstr[0] "%(count)s wiadomość" -msgstr[1] "%(count)s wiadomości" -msgstr[2] "%(count)s wiadomości" +msgstr "Nagłówki" -#: panels/logging.py:73 -msgid "Log messages" +#: panels/history/panel.py:19 panels/history/panel.py:20 +msgid "History" msgstr "" -#: panels/profiling.py:127 +#: panels/profiling.py:140 msgid "Profiling" msgstr "Profilowanie" #: panels/redirects.py:17 msgid "Intercept redirects" -msgstr "" +msgstr "Przechwycone przekierowania" -#: panels/request.py:18 +#: panels/request.py:16 msgid "Request" -msgstr "" +msgstr "Zapytania" -#: panels/request.py:35 +#: panels/request.py:38 msgid "" msgstr "" -#: panels/request.py:47 +#: panels/request.py:55 msgid "" msgstr "" -#: panels/settings.py:20 +#: panels/settings.py:17 msgid "Settings" msgstr "Ustawienia" -#: panels/settings.py:23 +#: panels/settings.py:20 #, python-format -msgid "Settings from %s" -msgstr "Ustawienia z %s" +msgid "Settings from %s" +msgstr "" -#: panels/signals.py:45 +#: panels/signals.py:57 #, python-format msgid "%(num_receivers)d receiver of 1 signal" msgid_plural "%(num_receivers)d receivers of 1 signal" msgstr[0] "%(num_receivers)d orbiorca 1 sygnału" msgstr[1] "%(num_receivers)d odbiorców 1 sygnału" msgstr[2] "%(num_receivers)d odbiorców 1 sygnału" +msgstr[3] "%(num_receivers)d odbiorców 1 sygnału" -#: panels/signals.py:48 +#: panels/signals.py:62 #, python-format msgid "%(num_receivers)d receiver of %(num_signals)d signals" msgid_plural "%(num_receivers)d receivers of %(num_signals)d signals" msgstr[0] "%(num_receivers)d odbiora %(num_signals)d sygnału" msgstr[1] "%(num_receivers)d odbiorców %(num_signals)d sygnałów" msgstr[2] "%(num_receivers)d odbiorców %(num_signals)d sygnałów" +msgstr[3] "%(num_receivers)d odbiorców %(num_signals)d sygnałów" -#: panels/signals.py:53 +#: panels/signals.py:67 msgid "Signals" msgstr "Sygnały" -#: panels/staticfiles.py:89 -#, python-format -msgid "Static files (%(num_found)s found, %(num_used)s used)" +#: panels/sql/panel.py:30 panels/sql/panel.py:41 +msgid "Read uncommitted" +msgstr "Przeczaj niepopełnione" + +#: panels/sql/panel.py:31 panels/sql/panel.py:43 +msgid "Read committed" +msgstr "Przeczytaj popełnione" + +#: panels/sql/panel.py:32 panels/sql/panel.py:45 +msgid "Repeatable read" msgstr "" -#: panels/staticfiles.py:107 -msgid "Static files" +#: panels/sql/panel.py:33 panels/sql/panel.py:47 +msgid "Serializable" msgstr "" -#: panels/staticfiles.py:112 +#: panels/sql/panel.py:39 +msgid "Autocommit" +msgstr "Autocommit" + +#: panels/sql/panel.py:61 panels/sql/panel.py:71 +msgid "Idle" +msgstr "Bezczynny" + +#: panels/sql/panel.py:62 panels/sql/panel.py:72 +msgid "Active" +msgstr "Aktywne" + +#: panels/sql/panel.py:63 panels/sql/panel.py:73 +msgid "In transaction" +msgstr "W transakcji" + +#: panels/sql/panel.py:64 panels/sql/panel.py:74 +msgid "In error" +msgstr "W błędzie" + +#: panels/sql/panel.py:65 panels/sql/panel.py:75 +msgid "Unknown" +msgstr "Nieznane" + +#: panels/sql/panel.py:162 +msgid "SQL" +msgstr "SQL" + +#: panels/sql/panel.py:168 #, python-format -msgid "%(num_used)s file used" -msgid_plural "%(num_used)s files used" +msgid "%(query_count)d query in %(sql_time).2fms" +msgid_plural "%(query_count)d queries in %(sql_time).2fms" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" + +#: panels/sql/panel.py:180 +#, python-format +msgid "SQL queries from %(count)d connection" +msgid_plural "SQL queries from %(count)d connections" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" + +#: panels/staticfiles.py:82 +#, python-format +msgid "Static files (%(num_found)s found, %(num_used)s used)" +msgstr "Pliki statyczne (znaleziono %(num_found)s, użyto %(num_used)s)" + +#: panels/staticfiles.py:103 +msgid "Static files" +msgstr "Pliki statyczne" -#: panels/timer.py:23 +#: panels/staticfiles.py:109 +#, python-format +msgid "%(num_used)s file used" +msgid_plural "%(num_used)s files used" +msgstr[0] "%(num_used)s użyty plików" +msgstr[1] "%(num_used)s użyte plików" +msgstr[2] "%(num_used)s użytych plików" +msgstr[3] "%(num_used)s użytych plików" + +#: panels/templates/panel.py:101 +msgid "Templates" +msgstr "Templatki" + +#: panels/templates/panel.py:106 +#, python-format +msgid "Templates (%(num_templates)s rendered)" +msgstr "Templatki (%(num_templates)s wyrenderowano)" + +#: panels/templates/panel.py:195 +msgid "No origin" +msgstr "" + +#: panels/timer.py:27 #, python-format msgid "CPU: %(cum)0.2fms (%(total)0.2fms)" msgstr "CPU: %(cum)0.2fms (%(total)0.2fms)" -#: panels/timer.py:28 +#: panels/timer.py:32 #, python-format msgid "Total: %0.2fms" -msgstr "" +msgstr "Całkowity czas: %0.2fms" -#: panels/timer.py:34 templates/debug_toolbar/panels/logging.html:7 +#: panels/timer.py:38 templates/debug_toolbar/panels/history.html:9 #: templates/debug_toolbar/panels/sql_explain.html:11 #: templates/debug_toolbar/panels/sql_profile.html:12 #: templates/debug_toolbar/panels/sql_select.html:11 msgid "Time" msgstr "Czas" -#: panels/timer.py:42 +#: panels/timer.py:46 msgid "User CPU time" msgstr "" -#: panels/timer.py:42 +#: panels/timer.py:46 #, python-format msgid "%(utime)0.3f msec" msgstr "%(utime)0.3f msec" -#: panels/timer.py:43 +#: panels/timer.py:47 msgid "System CPU time" msgstr "" -#: panels/timer.py:43 +#: panels/timer.py:47 #, python-format msgid "%(stime)0.3f msec" msgstr "%(stime)0.3f msec" -#: panels/timer.py:44 +#: panels/timer.py:48 msgid "Total CPU time" msgstr "" -#: panels/timer.py:44 +#: panels/timer.py:48 #, python-format msgid "%(total)0.3f msec" msgstr "%(total)0.3f msec" -#: panels/timer.py:45 +#: panels/timer.py:49 msgid "Elapsed time" -msgstr "" +msgstr "Całkowity czas" -#: panels/timer.py:45 +#: panels/timer.py:49 #, python-format msgid "%(total_time)0.3f msec" msgstr "%(total_time)0.3f msec" -#: panels/timer.py:46 +#: panels/timer.py:51 msgid "Context switches" -msgstr "" +msgstr "Przełączenia kontekstu" -#: panels/timer.py:46 +#: panels/timer.py:52 #, python-format msgid "%(vcsw)d voluntary, %(ivcsw)d involuntary" msgstr "" -#: panels/versions.py:25 +#: panels/versions.py:19 msgid "Versions" msgstr "Wersje" -#: panels/sql/panel.py:22 -msgid "Autocommit" -msgstr "Autocommit" - -#: panels/sql/panel.py:23 -msgid "Read uncommitted" -msgstr "" - -#: panels/sql/panel.py:24 -msgid "Read committed" -msgstr "" - -#: panels/sql/panel.py:25 -msgid "Repeatable read" -msgstr "" - -#: panels/sql/panel.py:26 -msgid "Serializable" -msgstr "" - -#: panels/sql/panel.py:37 -msgid "Idle" -msgstr "" - -#: panels/sql/panel.py:38 -msgid "Active" -msgstr "Aktywne" - -#: panels/sql/panel.py:39 -msgid "In transaction" -msgstr "W transakcji" - -#: panels/sql/panel.py:40 -msgid "In error" -msgstr "W błędzie" - -#: panels/sql/panel.py:41 -msgid "Unknown" -msgstr "Nieznane" - -#: panels/sql/panel.py:105 -msgid "SQL" -msgstr "SQL" - -#: panels/templates/panel.py:141 -msgid "Templates" -msgstr "Templatki" - -#: panels/templates/panel.py:146 -#, python-format -msgid "Templates (%(num_templates)s rendered)" -msgstr "Templatki (%(num_templates)s wyrenderowano)" - -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide toolbar" -msgstr "" +msgstr "Ukryj toolbar" -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide" msgstr "Ukryj" -#: templates/debug_toolbar/base.html:25 -msgid "Disable for next and successive requests" +#: templates/debug_toolbar/base.html:25 templates/debug_toolbar/base.html:26 +msgid "Toggle Theme" msgstr "" -#: templates/debug_toolbar/base.html:25 -msgid "Enable for next and successive requests" +#: templates/debug_toolbar/base.html:35 +msgid "Show toolbar" msgstr "" -#: templates/debug_toolbar/base.html:47 -msgid "Show toolbar" +#: templates/debug_toolbar/includes/panel_button.html:4 +msgid "Disable for next and successive requests" msgstr "" -#: templates/debug_toolbar/base.html:53 -msgid "Close" -msgstr "Zamknij" +#: templates/debug_toolbar/includes/panel_button.html:4 +msgid "Enable for next and successive requests" +msgstr "" -#: templates/debug_toolbar/redirect.html:8 -msgid "Location:" +#: templates/debug_toolbar/panels/alerts.html:4 +msgid "Alerts found" msgstr "" -#: templates/debug_toolbar/redirect.html:10 -msgid "" -"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." +#: templates/debug_toolbar/panels/alerts.html:11 +msgid "No alerts found" msgstr "" #: templates/debug_toolbar/panels/cache.html:2 @@ -317,7 +347,7 @@ msgid "Calls" msgstr "Wywołania" #: templates/debug_toolbar/panels/cache.html:43 -#: templates/debug_toolbar/panels/sql.html:20 +#: templates/debug_toolbar/panels/sql.html:36 msgid "Time (ms)" msgstr "Czas (ms)" @@ -352,10 +382,8 @@ msgstr "Klucz" #: templates/debug_toolbar/panels/headers.html:9 #: templates/debug_toolbar/panels/headers.html:28 #: templates/debug_toolbar/panels/headers.html:49 -#: templates/debug_toolbar/panels/request.html:33 -#: templates/debug_toolbar/panels/request.html:59 -#: templates/debug_toolbar/panels/request.html:85 -#: templates/debug_toolbar/panels/request.html:110 +#: templates/debug_toolbar/panels/history_tr.html:23 +#: templates/debug_toolbar/panels/request_variables.html:12 #: templates/debug_toolbar/panels/settings.html:6 #: templates/debug_toolbar/panels/timer.html:11 msgid "Value" @@ -375,26 +403,32 @@ msgid "" "significant subset is shown below." msgstr "" -#: templates/debug_toolbar/panels/logging.html:6 -msgid "Level" -msgstr "Poziom" +#: templates/debug_toolbar/panels/history.html:10 +msgid "Method" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:8 -msgid "Channel" -msgstr "Kanał" +#: templates/debug_toolbar/panels/history.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:43 +msgid "Path" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:9 -msgid "Message" -msgstr "Wiadomość" +#: templates/debug_toolbar/panels/history.html:12 +msgid "Request Variables" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:10 -#: templates/debug_toolbar/panels/staticfiles.html:45 -msgid "Location" -msgstr "Lokalizacja" +#: templates/debug_toolbar/panels/history.html:13 +msgid "Status" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:26 -msgid "No messages logged" -msgstr "Nie zalogowano żadnych wiadomości" +#: templates/debug_toolbar/panels/history.html:14 +#: templates/debug_toolbar/panels/sql.html:37 +msgid "Action" +msgstr "Akcja" + +#: templates/debug_toolbar/panels/history_tr.html:22 +#: templates/debug_toolbar/panels/request_variables.html:11 +msgid "Variable" +msgstr "Zmienna" #: templates/debug_toolbar/panels/profiling.html:5 msgid "Call" @@ -433,38 +467,31 @@ msgstr "" msgid "Cookies" msgstr "" -#: templates/debug_toolbar/panels/request.html:32 -#: templates/debug_toolbar/panels/request.html:58 -#: templates/debug_toolbar/panels/request.html:84 -#: templates/debug_toolbar/panels/request.html:109 -msgid "Variable" -msgstr "Zmienna" - -#: templates/debug_toolbar/panels/request.html:46 +#: templates/debug_toolbar/panels/request.html:27 msgid "No cookies" msgstr "" -#: templates/debug_toolbar/panels/request.html:50 +#: templates/debug_toolbar/panels/request.html:31 msgid "Session data" msgstr "" -#: templates/debug_toolbar/panels/request.html:72 +#: templates/debug_toolbar/panels/request.html:34 msgid "No session data" msgstr "" -#: templates/debug_toolbar/panels/request.html:76 +#: templates/debug_toolbar/panels/request.html:38 msgid "GET data" msgstr "" -#: templates/debug_toolbar/panels/request.html:98 +#: templates/debug_toolbar/panels/request.html:41 msgid "No GET data" msgstr "Brak danych GET" -#: templates/debug_toolbar/panels/request.html:102 +#: templates/debug_toolbar/panels/request.html:45 msgid "POST data" msgstr "" -#: templates/debug_toolbar/panels/request.html:123 +#: templates/debug_toolbar/panels/request.html:48 msgid "No POST data" msgstr "Brak danych POST" @@ -477,61 +504,71 @@ msgid "Signal" msgstr "Sygnał" #: templates/debug_toolbar/panels/signals.html:6 -msgid "Providing" -msgstr "" - -#: templates/debug_toolbar/panels/signals.html:7 msgid "Receivers" msgstr "Odbiorcy" -#: templates/debug_toolbar/panels/sql.html:7 +#: templates/debug_toolbar/panels/sql.html:6 #, python-format msgid "%(num)s query" msgid_plural "%(num)s queries" msgstr[0] "%(num)s zapytanie" msgstr[1] "%(num)s zapytania" msgstr[2] "%(num)s zapytań" +msgstr[3] "%(num)s zapytań" + +#: templates/debug_toolbar/panels/sql.html:8 +#, python-format +msgid "" +"including %(count)s similar" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:12 +#, python-format +msgid "" +"and %(dupes)s duplicates" +msgstr "" -#: templates/debug_toolbar/panels/sql.html:18 +#: templates/debug_toolbar/panels/sql.html:34 msgid "Query" msgstr "Zapytanie" -#: templates/debug_toolbar/panels/sql.html:19 +#: templates/debug_toolbar/panels/sql.html:35 #: templates/debug_toolbar/panels/timer.html:36 msgid "Timeline" msgstr "Oś czasu" -#: templates/debug_toolbar/panels/sql.html:21 -msgid "Action" -msgstr "Akcja" +#: templates/debug_toolbar/panels/sql.html:52 +#, python-format +msgid "%(count)s similar queries." +msgstr "" -#: templates/debug_toolbar/panels/sql.html:64 +#: templates/debug_toolbar/panels/sql.html:58 +#, python-format +msgid "Duplicated %(dupes)s times." +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:95 msgid "Connection:" msgstr "Połączenie:" -#: templates/debug_toolbar/panels/sql.html:66 +#: templates/debug_toolbar/panels/sql.html:97 msgid "Isolation level:" msgstr "Poziom izolacji:" -#: templates/debug_toolbar/panels/sql.html:69 +#: templates/debug_toolbar/panels/sql.html:100 msgid "Transaction status:" msgstr "Status transakcji:" -#: templates/debug_toolbar/panels/sql.html:83 +#: templates/debug_toolbar/panels/sql.html:114 msgid "(unknown)" msgstr "(nieznany)" -#: templates/debug_toolbar/panels/sql.html:92 +#: templates/debug_toolbar/panels/sql.html:123 msgid "No SQL queries were recorded during this request." msgstr "Żadne zapytania SQL nie zostały odnotowane podczas tego zapytania." -#: templates/debug_toolbar/panels/sql_explain.html:3 -#: templates/debug_toolbar/panels/sql_profile.html:3 -#: templates/debug_toolbar/panels/sql_select.html:3 -#: templates/debug_toolbar/panels/template_source.html:3 -msgid "Back" -msgstr "Wstecz" - #: templates/debug_toolbar/panels/sql_explain.html:4 msgid "SQL explained" msgstr "" @@ -564,52 +601,56 @@ msgstr "" msgid "Empty set" msgstr "Pusty zbiór" -#: templates/debug_toolbar/panels/staticfiles.html:4 +#: templates/debug_toolbar/panels/staticfiles.html:3 msgid "Static file path" msgid_plural "Static file paths" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" -#: templates/debug_toolbar/panels/staticfiles.html:8 +#: templates/debug_toolbar/panels/staticfiles.html:7 #, python-format msgid "(prefix %(prefix)s)" msgstr "" -#: templates/debug_toolbar/panels/staticfiles.html:12 -#: templates/debug_toolbar/panels/staticfiles.html:23 -#: templates/debug_toolbar/panels/staticfiles.html:35 +#: templates/debug_toolbar/panels/staticfiles.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:22 +#: templates/debug_toolbar/panels/staticfiles.html:34 #: templates/debug_toolbar/panels/templates.html:10 -#: templates/debug_toolbar/panels/templates.html:28 -#: templates/debug_toolbar/panels/templates.html:43 +#: templates/debug_toolbar/panels/templates.html:30 +#: templates/debug_toolbar/panels/templates.html:47 msgid "None" msgstr "Brak" -#: templates/debug_toolbar/panels/staticfiles.html:15 +#: templates/debug_toolbar/panels/staticfiles.html:14 msgid "Static file app" msgid_plural "Static file apps" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" -#: templates/debug_toolbar/panels/staticfiles.html:26 +#: templates/debug_toolbar/panels/staticfiles.html:25 msgid "Static file" msgid_plural "Static files" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" -#: templates/debug_toolbar/panels/staticfiles.html:40 +#: templates/debug_toolbar/panels/staticfiles.html:39 #, python-format msgid "%(payload_count)s file" msgid_plural "%(payload_count)s files" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" #: templates/debug_toolbar/panels/staticfiles.html:44 -msgid "Path" -msgstr "" +msgid "Location" +msgstr "Lokalizacja" #: templates/debug_toolbar/panels/template_source.html:4 msgid "Template source:" @@ -621,6 +662,7 @@ msgid_plural "Template paths" msgstr[0] "Ścieżka templatki" msgstr[1] "Ścieżki templatek" msgstr[2] "Ścieżki templatek" +msgstr[3] "Ścieżki templatek" #: templates/debug_toolbar/panels/templates.html:13 msgid "Template" @@ -628,18 +670,20 @@ msgid_plural "Templates" msgstr[0] "Templatki" msgstr[1] "Templatki" msgstr[2] "Templatki" +msgstr[3] "Templatki" -#: templates/debug_toolbar/panels/templates.html:21 -#: templates/debug_toolbar/panels/templates.html:37 +#: templates/debug_toolbar/panels/templates.html:22 +#: templates/debug_toolbar/panels/templates.html:40 msgid "Toggle context" msgstr "" -#: templates/debug_toolbar/panels/templates.html:31 +#: templates/debug_toolbar/panels/templates.html:33 msgid "Context processor" msgid_plural "Context processors" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" #: templates/debug_toolbar/panels/timer.html:2 msgid "Resource usage" @@ -661,10 +705,31 @@ msgstr "" msgid "Milliseconds since navigation start (+length)" msgstr "" -#: templates/debug_toolbar/panels/versions.html:5 +#: templates/debug_toolbar/panels/versions.html:10 +msgid "Package" +msgstr "" + +#: templates/debug_toolbar/panels/versions.html:11 msgid "Name" msgstr "Nazwa" -#: templates/debug_toolbar/panels/versions.html:6 +#: templates/debug_toolbar/panels/versions.html:12 msgid "Version" msgstr "Wersja" + +#: templates/debug_toolbar/redirect.html:10 +msgid "Location:" +msgstr "" + +#: templates/debug_toolbar/redirect.html:12 +msgid "" +"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." +msgstr "" + +#: views.py:16 +msgid "" +"Data for this panel isn't available anymore. Please reload the page and " +"retry." +msgstr "" diff --git a/debug_toolbar/locale/pt/LC_MESSAGES/django.mo b/debug_toolbar/locale/pt/LC_MESSAGES/django.mo index aa6f9951f..b08e0d39d 100644 Binary files a/debug_toolbar/locale/pt/LC_MESSAGES/django.mo and b/debug_toolbar/locale/pt/LC_MESSAGES/django.mo differ diff --git a/debug_toolbar/locale/pt/LC_MESSAGES/django.po b/debug_toolbar/locale/pt/LC_MESSAGES/django.po index 038fdec7b..b1b0f5b77 100644 --- a/debug_toolbar/locale/pt/LC_MESSAGES/django.po +++ b/debug_toolbar/locale/pt/LC_MESSAGES/django.po @@ -1,71 +1,80 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# -# +# +# # Translators: -# joseduraes , 2014 +# José Durães , 2014 msgid "" msgstr "" "Project-Id-Version: Django Debug Toolbar\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-04-25 21:52+0200\n" -"PO-Revision-Date: 2014-04-25 19:53+0000\n" -"Last-Translator: Aymeric Augustin \n" -"Language-Team: Portuguese (http://www.transifex.com/projects/p/django-debug-toolbar/language/pt/)\n" +"POT-Creation-Date: 2024-08-06 07:12-0500\n" +"PO-Revision-Date: 2010-11-30 00:00+0000\n" +"Last-Translator: José Durães , 2014\n" +"Language-Team: Portuguese (http://app.transifex.com/django-debug-toolbar/django-debug-toolbar/language/pt/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: pt\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" -#: apps.py:11 +#: apps.py:18 msgid "Debug Toolbar" msgstr "" -#: views.py:14 +#: panels/alerts.py:67 +#, python-brace-format msgid "" -"Data for this panel isn't available anymore. Please reload the page and " -"retry." +"Form with id \"{form_id}\" contains file input, but does not have the " +"attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:70 +msgid "" +"Form contains file input, but does not have the attribute " +"enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:73 +#, python-brace-format +msgid "" +"Input element references form with id \"{form_id}\", but the form does not " +"have the attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:77 +msgid "Alerts" msgstr "" -#: panels/cache.py:191 +#: panels/cache.py:168 msgid "Cache" msgstr "" -#: panels/cache.py:196 +#: panels/cache.py:174 #, python-format msgid "%(cache_calls)d call in %(time).2fms" msgid_plural "%(cache_calls)d calls in %(time).2fms" msgstr[0] "" msgstr[1] "" +msgstr[2] "" -#: panels/cache.py:204 +#: panels/cache.py:183 #, python-format msgid "Cache calls from %(count)d backend" msgid_plural "Cache calls from %(count)d backends" msgstr[0] "" msgstr[1] "" +msgstr[2] "" -#: panels/headers.py:35 +#: panels/headers.py:31 msgid "Headers" msgstr "" -#: panels/logging.py:64 -msgid "Logging" -msgstr "Registo" - -#: panels/logging.py:70 -#, python-format -msgid "%(count)s message" -msgid_plural "%(count)s messages" -msgstr[0] "" -msgstr[1] "" - -#: panels/logging.py:73 -msgid "Log messages" +#: panels/history/panel.py:19 panels/history/panel.py:20 +msgid "History" msgstr "" -#: panels/profiling.py:127 +#: panels/profiling.py:140 msgid "Profiling" msgstr "" @@ -73,213 +82,233 @@ msgstr "" msgid "Intercept redirects" msgstr "Intercetar redirecionamentos" -#: panels/request.py:18 +#: panels/request.py:16 msgid "Request" msgstr "Pedido" -#: panels/request.py:35 +#: panels/request.py:38 msgid "" msgstr "" -#: panels/request.py:47 +#: panels/request.py:55 msgid "" msgstr "" -#: panels/settings.py:20 +#: panels/settings.py:17 msgid "Settings" msgstr "Configurações" -#: panels/settings.py:23 +#: panels/settings.py:20 #, python-format -msgid "Settings from %s" +msgid "Settings from %s" msgstr "" -#: panels/signals.py:45 +#: panels/signals.py:57 #, python-format msgid "%(num_receivers)d receiver of 1 signal" msgid_plural "%(num_receivers)d receivers of 1 signal" msgstr[0] "" msgstr[1] "" +msgstr[2] "" -#: panels/signals.py:48 +#: panels/signals.py:62 #, python-format msgid "%(num_receivers)d receiver of %(num_signals)d signals" msgid_plural "%(num_receivers)d receivers of %(num_signals)d signals" msgstr[0] "" msgstr[1] "" +msgstr[2] "" -#: panels/signals.py:53 +#: panels/signals.py:67 msgid "Signals" msgstr "Sinais" -#: panels/staticfiles.py:89 +#: panels/sql/panel.py:30 panels/sql/panel.py:41 +msgid "Read uncommitted" +msgstr "" + +#: panels/sql/panel.py:31 panels/sql/panel.py:43 +msgid "Read committed" +msgstr "" + +#: panels/sql/panel.py:32 panels/sql/panel.py:45 +msgid "Repeatable read" +msgstr "" + +#: panels/sql/panel.py:33 panels/sql/panel.py:47 +msgid "Serializable" +msgstr "Variável" + +#: panels/sql/panel.py:39 +msgid "Autocommit" +msgstr "" + +#: panels/sql/panel.py:61 panels/sql/panel.py:71 +msgid "Idle" +msgstr "" + +#: panels/sql/panel.py:62 panels/sql/panel.py:72 +msgid "Active" +msgstr "Acção" + +#: panels/sql/panel.py:63 panels/sql/panel.py:73 +msgid "In transaction" +msgstr "" + +#: panels/sql/panel.py:64 panels/sql/panel.py:74 +msgid "In error" +msgstr "Erro" + +#: panels/sql/panel.py:65 panels/sql/panel.py:75 +msgid "Unknown" +msgstr "Desconhecido" + +#: panels/sql/panel.py:162 +msgid "SQL" +msgstr "" + +#: panels/sql/panel.py:168 +#, python-format +msgid "%(query_count)d query in %(sql_time).2fms" +msgid_plural "%(query_count)d queries in %(sql_time).2fms" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +#: panels/sql/panel.py:180 +#, python-format +msgid "SQL queries from %(count)d connection" +msgid_plural "SQL queries from %(count)d connections" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +#: panels/staticfiles.py:82 #, python-format msgid "Static files (%(num_found)s found, %(num_used)s used)" msgstr "" -#: panels/staticfiles.py:107 +#: panels/staticfiles.py:103 msgid "Static files" msgstr "Ficheiros estáticos" -#: panels/staticfiles.py:112 +#: panels/staticfiles.py:109 #, python-format msgid "%(num_used)s file used" msgid_plural "%(num_used)s files used" msgstr[0] "" msgstr[1] "" +msgstr[2] "" + +#: panels/templates/panel.py:101 +msgid "Templates" +msgstr "Templates" -#: panels/timer.py:23 +#: panels/templates/panel.py:106 +#, python-format +msgid "Templates (%(num_templates)s rendered)" +msgstr "Templates (%(num_templates)s renderizados)" + +#: panels/templates/panel.py:195 +msgid "No origin" +msgstr "" + +#: panels/timer.py:27 #, python-format msgid "CPU: %(cum)0.2fms (%(total)0.2fms)" msgstr "" -#: panels/timer.py:28 +#: panels/timer.py:32 #, python-format msgid "Total: %0.2fms" msgstr "Total: %0.2fms" -#: panels/timer.py:34 templates/debug_toolbar/panels/logging.html:7 +#: panels/timer.py:38 templates/debug_toolbar/panels/history.html:9 #: templates/debug_toolbar/panels/sql_explain.html:11 #: templates/debug_toolbar/panels/sql_profile.html:12 #: templates/debug_toolbar/panels/sql_select.html:11 msgid "Time" msgstr "Tempo" -#: panels/timer.py:42 +#: panels/timer.py:46 msgid "User CPU time" msgstr "" -#: panels/timer.py:42 +#: panels/timer.py:46 #, python-format msgid "%(utime)0.3f msec" msgstr "" -#: panels/timer.py:43 +#: panels/timer.py:47 msgid "System CPU time" msgstr "" -#: panels/timer.py:43 +#: panels/timer.py:47 #, python-format msgid "%(stime)0.3f msec" msgstr "" -#: panels/timer.py:44 +#: panels/timer.py:48 msgid "Total CPU time" msgstr "" -#: panels/timer.py:44 +#: panels/timer.py:48 #, python-format msgid "%(total)0.3f msec" msgstr "" -#: panels/timer.py:45 +#: panels/timer.py:49 msgid "Elapsed time" msgstr "" -#: panels/timer.py:45 +#: panels/timer.py:49 #, python-format msgid "%(total_time)0.3f msec" msgstr "" -#: panels/timer.py:46 +#: panels/timer.py:51 msgid "Context switches" msgstr "" -#: panels/timer.py:46 +#: panels/timer.py:52 #, python-format msgid "%(vcsw)d voluntary, %(ivcsw)d involuntary" msgstr "" -#: panels/versions.py:25 +#: panels/versions.py:19 msgid "Versions" msgstr "Versões" -#: panels/sql/panel.py:22 -msgid "Autocommit" -msgstr "" - -#: panels/sql/panel.py:23 -msgid "Read uncommitted" -msgstr "" - -#: panels/sql/panel.py:24 -msgid "Read committed" -msgstr "" - -#: panels/sql/panel.py:25 -msgid "Repeatable read" -msgstr "" - -#: panels/sql/panel.py:26 -msgid "Serializable" -msgstr "Variável" - -#: panels/sql/panel.py:37 -msgid "Idle" -msgstr "" - -#: panels/sql/panel.py:38 -msgid "Active" -msgstr "Acção" - -#: panels/sql/panel.py:39 -msgid "In transaction" -msgstr "" - -#: panels/sql/panel.py:40 -msgid "In error" -msgstr "Erro" - -#: panels/sql/panel.py:41 -msgid "Unknown" -msgstr "Desconhecido" - -#: panels/sql/panel.py:105 -msgid "SQL" -msgstr "" - -#: panels/templates/panel.py:141 -msgid "Templates" -msgstr "Templates" - -#: panels/templates/panel.py:146 -#, python-format -msgid "Templates (%(num_templates)s rendered)" -msgstr "Templates (%(num_templates)s renderizados)" - -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide toolbar" msgstr "Ocultar barra" -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide" msgstr "Ocultar" -#: templates/debug_toolbar/base.html:25 +#: templates/debug_toolbar/base.html:25 templates/debug_toolbar/base.html:26 +msgid "Toggle Theme" +msgstr "" + +#: templates/debug_toolbar/base.html:35 +msgid "Show toolbar" +msgstr "Mostrar barra" + +#: templates/debug_toolbar/includes/panel_button.html:4 msgid "Disable for next and successive requests" msgstr "Desactivar para o seguinte e sucessivos pedidos" -#: templates/debug_toolbar/base.html:25 +#: templates/debug_toolbar/includes/panel_button.html:4 msgid "Enable for next and successive requests" msgstr "Activar para o próximo e sucessivos pedidos" -#: templates/debug_toolbar/base.html:47 -msgid "Show toolbar" -msgstr "Mostrar barra" - -#: templates/debug_toolbar/base.html:53 -msgid "Close" -msgstr "Fechar" - -#: templates/debug_toolbar/redirect.html:8 -msgid "Location:" -msgstr "Localização" +#: templates/debug_toolbar/panels/alerts.html:4 +msgid "Alerts found" +msgstr "" -#: templates/debug_toolbar/redirect.html:10 -msgid "" -"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." +#: templates/debug_toolbar/panels/alerts.html:11 +msgid "No alerts found" msgstr "" #: templates/debug_toolbar/panels/cache.html:2 @@ -311,7 +340,7 @@ msgid "Calls" msgstr "" #: templates/debug_toolbar/panels/cache.html:43 -#: templates/debug_toolbar/panels/sql.html:20 +#: templates/debug_toolbar/panels/sql.html:36 msgid "Time (ms)" msgstr "" @@ -346,10 +375,8 @@ msgstr "Chave" #: templates/debug_toolbar/panels/headers.html:9 #: templates/debug_toolbar/panels/headers.html:28 #: templates/debug_toolbar/panels/headers.html:49 -#: templates/debug_toolbar/panels/request.html:33 -#: templates/debug_toolbar/panels/request.html:59 -#: templates/debug_toolbar/panels/request.html:85 -#: templates/debug_toolbar/panels/request.html:110 +#: templates/debug_toolbar/panels/history_tr.html:23 +#: templates/debug_toolbar/panels/request_variables.html:12 #: templates/debug_toolbar/panels/settings.html:6 #: templates/debug_toolbar/panels/timer.html:11 msgid "Value" @@ -369,26 +396,32 @@ msgid "" "significant subset is shown below." msgstr "" -#: templates/debug_toolbar/panels/logging.html:6 -msgid "Level" -msgstr "Nível" +#: templates/debug_toolbar/panels/history.html:10 +msgid "Method" +msgstr "Método" -#: templates/debug_toolbar/panels/logging.html:8 -msgid "Channel" +#: templates/debug_toolbar/panels/history.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:43 +msgid "Path" msgstr "" -#: templates/debug_toolbar/panels/logging.html:9 -msgid "Message" -msgstr "Mensagem" +#: templates/debug_toolbar/panels/history.html:12 +msgid "Request Variables" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:10 -#: templates/debug_toolbar/panels/staticfiles.html:45 -msgid "Location" -msgstr "Localização" +#: templates/debug_toolbar/panels/history.html:13 +msgid "Status" +msgstr "" + +#: templates/debug_toolbar/panels/history.html:14 +#: templates/debug_toolbar/panels/sql.html:37 +msgid "Action" +msgstr "Acção" -#: templates/debug_toolbar/panels/logging.html:26 -msgid "No messages logged" -msgstr "Nenhuma mensagem registada" +#: templates/debug_toolbar/panels/history_tr.html:22 +#: templates/debug_toolbar/panels/request_variables.html:11 +msgid "Variable" +msgstr "Variável" #: templates/debug_toolbar/panels/profiling.html:5 msgid "Call" @@ -427,38 +460,31 @@ msgstr "" msgid "Cookies" msgstr "" -#: templates/debug_toolbar/panels/request.html:32 -#: templates/debug_toolbar/panels/request.html:58 -#: templates/debug_toolbar/panels/request.html:84 -#: templates/debug_toolbar/panels/request.html:109 -msgid "Variable" -msgstr "Variável" - -#: templates/debug_toolbar/panels/request.html:46 +#: templates/debug_toolbar/panels/request.html:27 msgid "No cookies" msgstr "" -#: templates/debug_toolbar/panels/request.html:50 +#: templates/debug_toolbar/panels/request.html:31 msgid "Session data" msgstr "" -#: templates/debug_toolbar/panels/request.html:72 +#: templates/debug_toolbar/panels/request.html:34 msgid "No session data" msgstr "" -#: templates/debug_toolbar/panels/request.html:76 +#: templates/debug_toolbar/panels/request.html:38 msgid "GET data" msgstr "" -#: templates/debug_toolbar/panels/request.html:98 +#: templates/debug_toolbar/panels/request.html:41 msgid "No GET data" msgstr "Sem dados GET" -#: templates/debug_toolbar/panels/request.html:102 +#: templates/debug_toolbar/panels/request.html:45 msgid "POST data" msgstr "dados POST" -#: templates/debug_toolbar/panels/request.html:123 +#: templates/debug_toolbar/panels/request.html:48 msgid "No POST data" msgstr "Sem variáveis POST" @@ -471,60 +497,70 @@ msgid "Signal" msgstr "Sinal" #: templates/debug_toolbar/panels/signals.html:6 -msgid "Providing" -msgstr "" - -#: templates/debug_toolbar/panels/signals.html:7 msgid "Receivers" msgstr "Receptores" -#: templates/debug_toolbar/panels/sql.html:7 +#: templates/debug_toolbar/panels/sql.html:6 #, python-format msgid "%(num)s query" msgid_plural "%(num)s queries" msgstr[0] "" msgstr[1] "" +msgstr[2] "" -#: templates/debug_toolbar/panels/sql.html:18 +#: templates/debug_toolbar/panels/sql.html:8 +#, python-format +msgid "" +"including %(count)s similar" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:12 +#, python-format +msgid "" +"and %(dupes)s duplicates" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:34 msgid "Query" msgstr "" -#: templates/debug_toolbar/panels/sql.html:19 +#: templates/debug_toolbar/panels/sql.html:35 #: templates/debug_toolbar/panels/timer.html:36 msgid "Timeline" msgstr "" -#: templates/debug_toolbar/panels/sql.html:21 -msgid "Action" -msgstr "Acção" +#: templates/debug_toolbar/panels/sql.html:52 +#, python-format +msgid "%(count)s similar queries." +msgstr "" -#: templates/debug_toolbar/panels/sql.html:64 +#: templates/debug_toolbar/panels/sql.html:58 +#, python-format +msgid "Duplicated %(dupes)s times." +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:95 msgid "Connection:" msgstr "" -#: templates/debug_toolbar/panels/sql.html:66 +#: templates/debug_toolbar/panels/sql.html:97 msgid "Isolation level:" msgstr "" -#: templates/debug_toolbar/panels/sql.html:69 +#: templates/debug_toolbar/panels/sql.html:100 msgid "Transaction status:" msgstr "Estado da transacção:" -#: templates/debug_toolbar/panels/sql.html:83 +#: templates/debug_toolbar/panels/sql.html:114 msgid "(unknown)" msgstr "(desconhecido)" -#: templates/debug_toolbar/panels/sql.html:92 +#: templates/debug_toolbar/panels/sql.html:123 msgid "No SQL queries were recorded during this request." msgstr "Nenhuma query SQL foi registada durante este pedido." -#: templates/debug_toolbar/panels/sql_explain.html:3 -#: templates/debug_toolbar/panels/sql_profile.html:3 -#: templates/debug_toolbar/panels/sql_select.html:3 -#: templates/debug_toolbar/panels/template_source.html:3 -msgid "Back" -msgstr "Voltar" - #: templates/debug_toolbar/panels/sql_explain.html:4 msgid "SQL explained" msgstr "" @@ -557,48 +593,52 @@ msgstr "" msgid "Empty set" msgstr "Set vazio" -#: templates/debug_toolbar/panels/staticfiles.html:4 +#: templates/debug_toolbar/panels/staticfiles.html:3 msgid "Static file path" msgid_plural "Static file paths" msgstr[0] "" msgstr[1] "" +msgstr[2] "" -#: templates/debug_toolbar/panels/staticfiles.html:8 +#: templates/debug_toolbar/panels/staticfiles.html:7 #, python-format msgid "(prefix %(prefix)s)" msgstr "(prefixo %(prefix)s)" -#: templates/debug_toolbar/panels/staticfiles.html:12 -#: templates/debug_toolbar/panels/staticfiles.html:23 -#: templates/debug_toolbar/panels/staticfiles.html:35 +#: templates/debug_toolbar/panels/staticfiles.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:22 +#: templates/debug_toolbar/panels/staticfiles.html:34 #: templates/debug_toolbar/panels/templates.html:10 -#: templates/debug_toolbar/panels/templates.html:28 -#: templates/debug_toolbar/panels/templates.html:43 +#: templates/debug_toolbar/panels/templates.html:30 +#: templates/debug_toolbar/panels/templates.html:47 msgid "None" msgstr "Nenhum" -#: templates/debug_toolbar/panels/staticfiles.html:15 +#: templates/debug_toolbar/panels/staticfiles.html:14 msgid "Static file app" msgid_plural "Static file apps" msgstr[0] "" msgstr[1] "" +msgstr[2] "" -#: templates/debug_toolbar/panels/staticfiles.html:26 +#: templates/debug_toolbar/panels/staticfiles.html:25 msgid "Static file" msgid_plural "Static files" msgstr[0] "Ficheiro estático" msgstr[1] "Ficheiros estáticos" +msgstr[2] "Ficheiros estáticos" -#: templates/debug_toolbar/panels/staticfiles.html:40 +#: templates/debug_toolbar/panels/staticfiles.html:39 #, python-format msgid "%(payload_count)s file" msgid_plural "%(payload_count)s files" msgstr[0] "" msgstr[1] "" +msgstr[2] "" #: templates/debug_toolbar/panels/staticfiles.html:44 -msgid "Path" -msgstr "" +msgid "Location" +msgstr "Localização" #: templates/debug_toolbar/panels/template_source.html:4 msgid "Template source:" @@ -609,23 +649,26 @@ msgid "Template path" msgid_plural "Template paths" msgstr[0] "" msgstr[1] "Caminho da Template" +msgstr[2] "Caminho da Template" #: templates/debug_toolbar/panels/templates.html:13 msgid "Template" msgid_plural "Templates" msgstr[0] "" msgstr[1] "" +msgstr[2] "" -#: templates/debug_toolbar/panels/templates.html:21 -#: templates/debug_toolbar/panels/templates.html:37 +#: templates/debug_toolbar/panels/templates.html:22 +#: templates/debug_toolbar/panels/templates.html:40 msgid "Toggle context" msgstr "" -#: templates/debug_toolbar/panels/templates.html:31 +#: templates/debug_toolbar/panels/templates.html:33 msgid "Context processor" msgid_plural "Context processors" msgstr[0] "" msgstr[1] "Processador de Contexto" +msgstr[2] "Processador de Contexto" #: templates/debug_toolbar/panels/timer.html:2 msgid "Resource usage" @@ -647,10 +690,31 @@ msgstr "" msgid "Milliseconds since navigation start (+length)" msgstr "" -#: templates/debug_toolbar/panels/versions.html:5 +#: templates/debug_toolbar/panels/versions.html:10 +msgid "Package" +msgstr "Pacote" + +#: templates/debug_toolbar/panels/versions.html:11 msgid "Name" msgstr "Nome" -#: templates/debug_toolbar/panels/versions.html:6 +#: templates/debug_toolbar/panels/versions.html:12 msgid "Version" msgstr "Versão" + +#: templates/debug_toolbar/redirect.html:10 +msgid "Location:" +msgstr "Localização" + +#: templates/debug_toolbar/redirect.html:12 +msgid "" +"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." +msgstr "" + +#: views.py:16 +msgid "" +"Data for this panel isn't available anymore. Please reload the page and " +"retry." +msgstr "" diff --git a/debug_toolbar/locale/pt_BR/LC_MESSAGES/django.mo b/debug_toolbar/locale/pt_BR/LC_MESSAGES/django.mo index 808c489ae..24c3060e6 100644 Binary files a/debug_toolbar/locale/pt_BR/LC_MESSAGES/django.mo and b/debug_toolbar/locale/pt_BR/LC_MESSAGES/django.mo differ diff --git a/debug_toolbar/locale/pt_BR/LC_MESSAGES/django.po b/debug_toolbar/locale/pt_BR/LC_MESSAGES/django.po index ed25331b6..2ec8be420 100644 --- a/debug_toolbar/locale/pt_BR/LC_MESSAGES/django.po +++ b/debug_toolbar/locale/pt_BR/LC_MESSAGES/django.po @@ -1,72 +1,82 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# -# +# +# # Translators: -# Fábio , 2013-2014 +# Fábio C. Barrionuevo da Luz , 2013-2014 +# Gladson , 2017 # Percy Pérez-Pinedo, 2009 msgid "" msgstr "" "Project-Id-Version: Django Debug Toolbar\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-04-25 21:52+0200\n" -"PO-Revision-Date: 2014-04-25 19:53+0000\n" -"Last-Translator: Aymeric Augustin \n" -"Language-Team: Portuguese (Brazil) (http://www.transifex.com/projects/p/django-debug-toolbar/language/pt_BR/)\n" +"POT-Creation-Date: 2024-08-06 07:12-0500\n" +"PO-Revision-Date: 2010-11-30 00:00+0000\n" +"Last-Translator: Gladson , 2017\n" +"Language-Team: Portuguese (Brazil) (http://app.transifex.com/django-debug-toolbar/django-debug-toolbar/language/pt_BR/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: pt_BR\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" -#: apps.py:11 +#: apps.py:18 msgid "Debug Toolbar" +msgstr "Debug Toolbar" + +#: panels/alerts.py:67 +#, python-brace-format +msgid "" +"Form with id \"{form_id}\" contains file input, but does not have the " +"attribute enctype=\"multipart/form-data\"." msgstr "" -#: views.py:14 +#: panels/alerts.py:70 msgid "" -"Data for this panel isn't available anymore. Please reload the page and " -"retry." -msgstr "Os dados para este painel não está mais disponível. Por favor, recarregue a página e tente novamente." +"Form contains file input, but does not have the attribute " +"enctype=\"multipart/form-data\"." +msgstr "" -#: panels/cache.py:191 +#: panels/alerts.py:73 +#, python-brace-format +msgid "" +"Input element references form with id \"{form_id}\", but the form does not " +"have the attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:77 +msgid "Alerts" +msgstr "" + +#: panels/cache.py:168 msgid "Cache" msgstr "Cache" -#: panels/cache.py:196 +#: panels/cache.py:174 #, python-format msgid "%(cache_calls)d call in %(time).2fms" msgid_plural "%(cache_calls)d calls in %(time).2fms" msgstr[0] "%(cache_calls)d chamada em %(time).2fms" msgstr[1] "%(cache_calls)d chamadas em %(time).2fms" +msgstr[2] "%(cache_calls)d chamadas em %(time).2fms" -#: panels/cache.py:204 +#: panels/cache.py:183 #, python-format msgid "Cache calls from %(count)d backend" msgid_plural "Cache calls from %(count)d backends" msgstr[0] "Chamadas ao cache de %(count)d backend" msgstr[1] "Chamadas ao cache de %(count)d backends" +msgstr[2] "Chamadas ao cache de %(count)d backends" -#: panels/headers.py:35 +#: panels/headers.py:31 msgid "Headers" msgstr "Cabeçalhos" -#: panels/logging.py:64 -msgid "Logging" -msgstr "Logs" - -#: panels/logging.py:70 -#, python-format -msgid "%(count)s message" -msgid_plural "%(count)s messages" -msgstr[0] "%(count)s mensagem" -msgstr[1] "%(count)s mensagens" - -#: panels/logging.py:73 -msgid "Log messages" -msgstr "Mensagens de log" +#: panels/history/panel.py:19 panels/history/panel.py:20 +msgid "History" +msgstr "" -#: panels/profiling.py:127 +#: panels/profiling.py:140 msgid "Profiling" msgstr "Profiling" @@ -74,214 +84,234 @@ msgstr "Profiling" msgid "Intercept redirects" msgstr "Interceptar redirecionamentos" -#: panels/request.py:18 +#: panels/request.py:16 msgid "Request" msgstr "Requisição" -#: panels/request.py:35 +#: panels/request.py:38 msgid "" msgstr "" -#: panels/request.py:47 +#: panels/request.py:55 msgid "" msgstr "" -#: panels/settings.py:20 +#: panels/settings.py:17 msgid "Settings" msgstr "Configurações" -#: panels/settings.py:23 +#: panels/settings.py:20 #, python-format -msgid "Settings from %s" -msgstr "Configurações em: %s" +msgid "Settings from %s" +msgstr "" -#: panels/signals.py:45 +#: panels/signals.py:57 #, python-format msgid "%(num_receivers)d receiver of 1 signal" msgid_plural "%(num_receivers)d receivers of 1 signal" msgstr[0] "%(num_receivers)d receptor de 1 sinal" msgstr[1] "%(num_receivers)d receptores de 1 sinal" +msgstr[2] "%(num_receivers)d receptores de 1 sinal" -#: panels/signals.py:48 +#: panels/signals.py:62 #, python-format msgid "%(num_receivers)d receiver of %(num_signals)d signals" msgid_plural "%(num_receivers)d receivers of %(num_signals)d signals" msgstr[0] "%(num_receivers)d receptor de %(num_signals)d sinais" msgstr[1] "%(num_receivers)d receptores de %(num_signals)d sinais" +msgstr[2] "%(num_receivers)d receptores de %(num_signals)d sinais" -#: panels/signals.py:53 +#: panels/signals.py:67 msgid "Signals" msgstr "Sinais" -#: panels/staticfiles.py:89 +#: panels/sql/panel.py:30 panels/sql/panel.py:41 +msgid "Read uncommitted" +msgstr "Read uncommitted" + +#: panels/sql/panel.py:31 panels/sql/panel.py:43 +msgid "Read committed" +msgstr "Read committed" + +#: panels/sql/panel.py:32 panels/sql/panel.py:45 +msgid "Repeatable read" +msgstr "Leitura repetida" + +#: panels/sql/panel.py:33 panels/sql/panel.py:47 +msgid "Serializable" +msgstr "Variável" + +#: panels/sql/panel.py:39 +msgid "Autocommit" +msgstr "Autocommit" + +#: panels/sql/panel.py:61 panels/sql/panel.py:71 +msgid "Idle" +msgstr "Ocioso" + +#: panels/sql/panel.py:62 panels/sql/panel.py:72 +msgid "Active" +msgstr "Ação" + +#: panels/sql/panel.py:63 panels/sql/panel.py:73 +msgid "In transaction" +msgstr "Na transação" + +#: panels/sql/panel.py:64 panels/sql/panel.py:74 +msgid "In error" +msgstr "Erro" + +#: panels/sql/panel.py:65 panels/sql/panel.py:75 +msgid "Unknown" +msgstr "Desconhecido" + +#: panels/sql/panel.py:162 +msgid "SQL" +msgstr "SQL" + +#: panels/sql/panel.py:168 +#, python-format +msgid "%(query_count)d query in %(sql_time).2fms" +msgid_plural "%(query_count)d queries in %(sql_time).2fms" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +#: panels/sql/panel.py:180 +#, python-format +msgid "SQL queries from %(count)d connection" +msgid_plural "SQL queries from %(count)d connections" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" + +#: panels/staticfiles.py:82 #, python-format msgid "Static files (%(num_found)s found, %(num_used)s used)" msgstr "Arquivos estáticos (%(num_found)s encontrados, %(num_used)s sendo utilizados)" -#: panels/staticfiles.py:107 +#: panels/staticfiles.py:103 msgid "Static files" msgstr "Arquivos estáticos" -#: panels/staticfiles.py:112 +#: panels/staticfiles.py:109 #, python-format msgid "%(num_used)s file used" msgid_plural "%(num_used)s files used" msgstr[0] "%(num_used)s arquivo utilizado" msgstr[1] "%(num_used)s arquivos utilizados" +msgstr[2] "%(num_used)s arquivos utilizados" -#: panels/timer.py:23 +#: panels/templates/panel.py:101 +msgid "Templates" +msgstr "Templates" + +#: panels/templates/panel.py:106 +#, python-format +msgid "Templates (%(num_templates)s rendered)" +msgstr "Templates (%(num_templates)s renderizados)" + +#: panels/templates/panel.py:195 +msgid "No origin" +msgstr "Sem origem" + +#: panels/timer.py:27 #, python-format msgid "CPU: %(cum)0.2fms (%(total)0.2fms)" msgstr "CPU: %(cum)0.2fms (%(total)0.2fms)" -#: panels/timer.py:28 +#: panels/timer.py:32 #, python-format msgid "Total: %0.2fms" msgstr "Total: %0.2fms" -#: panels/timer.py:34 templates/debug_toolbar/panels/logging.html:7 +#: panels/timer.py:38 templates/debug_toolbar/panels/history.html:9 #: templates/debug_toolbar/panels/sql_explain.html:11 #: templates/debug_toolbar/panels/sql_profile.html:12 #: templates/debug_toolbar/panels/sql_select.html:11 msgid "Time" msgstr "Tempo" -#: panels/timer.py:42 +#: panels/timer.py:46 msgid "User CPU time" msgstr "Tempo de CPU do usuário" -#: panels/timer.py:42 +#: panels/timer.py:46 #, python-format msgid "%(utime)0.3f msec" msgstr "%(utime)0.3f ms" -#: panels/timer.py:43 +#: panels/timer.py:47 msgid "System CPU time" msgstr "Tempo de CPU do sistema" -#: panels/timer.py:43 +#: panels/timer.py:47 #, python-format msgid "%(stime)0.3f msec" msgstr "%(stime)0.3f ms" -#: panels/timer.py:44 +#: panels/timer.py:48 msgid "Total CPU time" msgstr "Tempo total de CPU" -#: panels/timer.py:44 +#: panels/timer.py:48 #, python-format msgid "%(total)0.3f msec" msgstr "%(total)0.3f ms" -#: panels/timer.py:45 +#: panels/timer.py:49 msgid "Elapsed time" msgstr "Tempo decorrido" -#: panels/timer.py:45 +#: panels/timer.py:49 #, python-format msgid "%(total_time)0.3f msec" msgstr "%(total_time)0.3f ms" -#: panels/timer.py:46 +#: panels/timer.py:51 msgid "Context switches" msgstr "Mudanças de contexto" -#: panels/timer.py:46 +#: panels/timer.py:52 #, python-format msgid "%(vcsw)d voluntary, %(ivcsw)d involuntary" msgstr "%(vcsw)d voluntário, %(ivcsw)d involuntário" -#: panels/versions.py:25 +#: panels/versions.py:19 msgid "Versions" msgstr "Versões" -#: panels/sql/panel.py:22 -msgid "Autocommit" -msgstr "Autocommit" - -#: panels/sql/panel.py:23 -msgid "Read uncommitted" -msgstr "Read uncommitted" - -#: panels/sql/panel.py:24 -msgid "Read committed" -msgstr "Read committed" - -#: panels/sql/panel.py:25 -msgid "Repeatable read" -msgstr "Leitura repetida" - -#: panels/sql/panel.py:26 -msgid "Serializable" -msgstr "Variável" - -#: panels/sql/panel.py:37 -msgid "Idle" -msgstr "Ocioso" - -#: panels/sql/panel.py:38 -msgid "Active" -msgstr "Ação" - -#: panels/sql/panel.py:39 -msgid "In transaction" -msgstr "Na transação" - -#: panels/sql/panel.py:40 -msgid "In error" -msgstr "Erro" - -#: panels/sql/panel.py:41 -msgid "Unknown" -msgstr "Desconhecido" - -#: panels/sql/panel.py:105 -msgid "SQL" -msgstr "SQL" - -#: panels/templates/panel.py:141 -msgid "Templates" -msgstr "Templates" - -#: panels/templates/panel.py:146 -#, python-format -msgid "Templates (%(num_templates)s rendered)" -msgstr "Templates (%(num_templates)s renderizados)" - -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide toolbar" msgstr "Ocultar barra de ferramentas" -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide" msgstr "Esconder" -#: templates/debug_toolbar/base.html:25 +#: templates/debug_toolbar/base.html:25 templates/debug_toolbar/base.html:26 +msgid "Toggle Theme" +msgstr "" + +#: templates/debug_toolbar/base.html:35 +msgid "Show toolbar" +msgstr "Mostrar barra de ferramentas" + +#: templates/debug_toolbar/includes/panel_button.html:4 msgid "Disable for next and successive requests" msgstr "Desativar para próximas requisições" -#: templates/debug_toolbar/base.html:25 +#: templates/debug_toolbar/includes/panel_button.html:4 msgid "Enable for next and successive requests" msgstr "Habilitar para próximas requisições" -#: templates/debug_toolbar/base.html:47 -msgid "Show toolbar" -msgstr "Mostrar barra de ferramentas" - -#: templates/debug_toolbar/base.html:53 -msgid "Close" -msgstr "Fechar" - -#: templates/debug_toolbar/redirect.html:8 -msgid "Location:" -msgstr "Localização:" +#: templates/debug_toolbar/panels/alerts.html:4 +msgid "Alerts found" +msgstr "" -#: templates/debug_toolbar/redirect.html:10 -msgid "" -"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." -msgstr "O Django Debug Toolbar interceptou um redirecionamento para a URL acima para fins de visualização de depuração. Você pode clicar no link acima para continuar com o redirecionamento normalmente." +#: templates/debug_toolbar/panels/alerts.html:11 +msgid "No alerts found" +msgstr "" #: templates/debug_toolbar/panels/cache.html:2 msgid "Summary" @@ -312,7 +342,7 @@ msgid "Calls" msgstr "Chamadas" #: templates/debug_toolbar/panels/cache.html:43 -#: templates/debug_toolbar/panels/sql.html:20 +#: templates/debug_toolbar/panels/sql.html:36 msgid "Time (ms)" msgstr "Tempo (ms)" @@ -347,10 +377,8 @@ msgstr "Chave" #: templates/debug_toolbar/panels/headers.html:9 #: templates/debug_toolbar/panels/headers.html:28 #: templates/debug_toolbar/panels/headers.html:49 -#: templates/debug_toolbar/panels/request.html:33 -#: templates/debug_toolbar/panels/request.html:59 -#: templates/debug_toolbar/panels/request.html:85 -#: templates/debug_toolbar/panels/request.html:110 +#: templates/debug_toolbar/panels/history_tr.html:23 +#: templates/debug_toolbar/panels/request_variables.html:12 #: templates/debug_toolbar/panels/settings.html:6 #: templates/debug_toolbar/panels/timer.html:11 msgid "Value" @@ -370,26 +398,32 @@ msgid "" "significant subset is shown below." msgstr "Uma vez que o ambiente WSGI herda o ambiente do servidor, apenas um subconjunto significativo é mostrado abaixo." -#: templates/debug_toolbar/panels/logging.html:6 -msgid "Level" -msgstr "Nível" +#: templates/debug_toolbar/panels/history.html:10 +msgid "Method" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:8 -msgid "Channel" -msgstr "Canal" +#: templates/debug_toolbar/panels/history.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:43 +msgid "Path" +msgstr "Caminho" -#: templates/debug_toolbar/panels/logging.html:9 -msgid "Message" -msgstr "Mensagem" +#: templates/debug_toolbar/panels/history.html:12 +msgid "Request Variables" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:10 -#: templates/debug_toolbar/panels/staticfiles.html:45 -msgid "Location" -msgstr "Localização" +#: templates/debug_toolbar/panels/history.html:13 +msgid "Status" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:26 -msgid "No messages logged" -msgstr "Nenhuma mensagem logada" +#: templates/debug_toolbar/panels/history.html:14 +#: templates/debug_toolbar/panels/sql.html:37 +msgid "Action" +msgstr "Ação" + +#: templates/debug_toolbar/panels/history_tr.html:22 +#: templates/debug_toolbar/panels/request_variables.html:11 +msgid "Variable" +msgstr "Variável" #: templates/debug_toolbar/panels/profiling.html:5 msgid "Call" @@ -428,38 +462,31 @@ msgstr "Nome da URL" msgid "Cookies" msgstr "Cookies" -#: templates/debug_toolbar/panels/request.html:32 -#: templates/debug_toolbar/panels/request.html:58 -#: templates/debug_toolbar/panels/request.html:84 -#: templates/debug_toolbar/panels/request.html:109 -msgid "Variable" -msgstr "Variável" - -#: templates/debug_toolbar/panels/request.html:46 +#: templates/debug_toolbar/panels/request.html:27 msgid "No cookies" msgstr "Sem Cookies" -#: templates/debug_toolbar/panels/request.html:50 +#: templates/debug_toolbar/panels/request.html:31 msgid "Session data" msgstr "Dados de Sessão" -#: templates/debug_toolbar/panels/request.html:72 +#: templates/debug_toolbar/panels/request.html:34 msgid "No session data" msgstr "Sem dados de Sessão" -#: templates/debug_toolbar/panels/request.html:76 +#: templates/debug_toolbar/panels/request.html:38 msgid "GET data" msgstr "Dados de GET" -#: templates/debug_toolbar/panels/request.html:98 +#: templates/debug_toolbar/panels/request.html:41 msgid "No GET data" msgstr "Não há dados de GET" -#: templates/debug_toolbar/panels/request.html:102 +#: templates/debug_toolbar/panels/request.html:45 msgid "POST data" msgstr "Dados de POST" -#: templates/debug_toolbar/panels/request.html:123 +#: templates/debug_toolbar/panels/request.html:48 msgid "No POST data" msgstr "Não há dados de POST" @@ -472,60 +499,70 @@ msgid "Signal" msgstr "Sinais" #: templates/debug_toolbar/panels/signals.html:6 -msgid "Providing" -msgstr "Fornecendo" - -#: templates/debug_toolbar/panels/signals.html:7 msgid "Receivers" msgstr "Recebedores" -#: templates/debug_toolbar/panels/sql.html:7 +#: templates/debug_toolbar/panels/sql.html:6 #, python-format msgid "%(num)s query" msgid_plural "%(num)s queries" msgstr[0] "%(num)s consulta" msgstr[1] "%(num)s consultas" +msgstr[2] "%(num)s consultas" -#: templates/debug_toolbar/panels/sql.html:18 +#: templates/debug_toolbar/panels/sql.html:8 +#, python-format +msgid "" +"including %(count)s similar" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:12 +#, python-format +msgid "" +"and %(dupes)s duplicates" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:34 msgid "Query" msgstr "Query" -#: templates/debug_toolbar/panels/sql.html:19 +#: templates/debug_toolbar/panels/sql.html:35 #: templates/debug_toolbar/panels/timer.html:36 msgid "Timeline" msgstr "Linha do tempo" -#: templates/debug_toolbar/panels/sql.html:21 -msgid "Action" -msgstr "Ação" +#: templates/debug_toolbar/panels/sql.html:52 +#, python-format +msgid "%(count)s similar queries." +msgstr "" -#: templates/debug_toolbar/panels/sql.html:64 +#: templates/debug_toolbar/panels/sql.html:58 +#, python-format +msgid "Duplicated %(dupes)s times." +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:95 msgid "Connection:" msgstr "Conexão:" -#: templates/debug_toolbar/panels/sql.html:66 +#: templates/debug_toolbar/panels/sql.html:97 msgid "Isolation level:" msgstr "Nível de isolamento:" -#: templates/debug_toolbar/panels/sql.html:69 +#: templates/debug_toolbar/panels/sql.html:100 msgid "Transaction status:" msgstr "Status da transação:" -#: templates/debug_toolbar/panels/sql.html:83 +#: templates/debug_toolbar/panels/sql.html:114 msgid "(unknown)" msgstr "(unknown)" -#: templates/debug_toolbar/panels/sql.html:92 +#: templates/debug_toolbar/panels/sql.html:123 msgid "No SQL queries were recorded during this request." msgstr "Nenhuma consulta SQL foi registrada durante esta requisição." -#: templates/debug_toolbar/panels/sql_explain.html:3 -#: templates/debug_toolbar/panels/sql_profile.html:3 -#: templates/debug_toolbar/panels/sql_select.html:3 -#: templates/debug_toolbar/panels/template_source.html:3 -msgid "Back" -msgstr "Voltar" - #: templates/debug_toolbar/panels/sql_explain.html:4 msgid "SQL explained" msgstr "SQL explicada" @@ -558,48 +595,52 @@ msgstr "SQL selecionada" msgid "Empty set" msgstr "Conjunto vazio" -#: templates/debug_toolbar/panels/staticfiles.html:4 +#: templates/debug_toolbar/panels/staticfiles.html:3 msgid "Static file path" msgid_plural "Static file paths" msgstr[0] "Caminho do arquivo estático" msgstr[1] "Caminho dos arquivos estáticos" +msgstr[2] "Caminho dos arquivos estáticos" -#: templates/debug_toolbar/panels/staticfiles.html:8 +#: templates/debug_toolbar/panels/staticfiles.html:7 #, python-format msgid "(prefix %(prefix)s)" msgstr "(prefixo %(prefix)s)" -#: templates/debug_toolbar/panels/staticfiles.html:12 -#: templates/debug_toolbar/panels/staticfiles.html:23 -#: templates/debug_toolbar/panels/staticfiles.html:35 +#: templates/debug_toolbar/panels/staticfiles.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:22 +#: templates/debug_toolbar/panels/staticfiles.html:34 #: templates/debug_toolbar/panels/templates.html:10 -#: templates/debug_toolbar/panels/templates.html:28 -#: templates/debug_toolbar/panels/templates.html:43 +#: templates/debug_toolbar/panels/templates.html:30 +#: templates/debug_toolbar/panels/templates.html:47 msgid "None" msgstr "Nenhum" -#: templates/debug_toolbar/panels/staticfiles.html:15 +#: templates/debug_toolbar/panels/staticfiles.html:14 msgid "Static file app" msgid_plural "Static file apps" msgstr[0] "Arquivo estático de app" msgstr[1] "Arquivos estáticos de apps" +msgstr[2] "Arquivos estáticos de apps" -#: templates/debug_toolbar/panels/staticfiles.html:26 +#: templates/debug_toolbar/panels/staticfiles.html:25 msgid "Static file" msgid_plural "Static files" msgstr[0] "Arquivo estático" msgstr[1] "Arquivos estáticos" +msgstr[2] "Arquivos estáticos" -#: templates/debug_toolbar/panels/staticfiles.html:40 +#: templates/debug_toolbar/panels/staticfiles.html:39 #, python-format msgid "%(payload_count)s file" msgid_plural "%(payload_count)s files" msgstr[0] "%(payload_count)s arquivo" msgstr[1] "%(payload_count)s arquivos" +msgstr[2] "%(payload_count)s arquivos" #: templates/debug_toolbar/panels/staticfiles.html:44 -msgid "Path" -msgstr "Caminho" +msgid "Location" +msgstr "Localização" #: templates/debug_toolbar/panels/template_source.html:4 msgid "Template source:" @@ -610,23 +651,26 @@ msgid "Template path" msgid_plural "Template paths" msgstr[0] "Caminho do Template" msgstr[1] "Caminho do Templates" +msgstr[2] "Caminho do Templates" #: templates/debug_toolbar/panels/templates.html:13 msgid "Template" msgid_plural "Templates" msgstr[0] "Template" msgstr[1] "Templates" +msgstr[2] "Templates" -#: templates/debug_toolbar/panels/templates.html:21 -#: templates/debug_toolbar/panels/templates.html:37 +#: templates/debug_toolbar/panels/templates.html:22 +#: templates/debug_toolbar/panels/templates.html:40 msgid "Toggle context" msgstr "Alternar contexto" -#: templates/debug_toolbar/panels/templates.html:31 +#: templates/debug_toolbar/panels/templates.html:33 msgid "Context processor" msgid_plural "Context processors" msgstr[0] "" msgstr[1] "Processador do Contexto" +msgstr[2] "Processador do Contexto" #: templates/debug_toolbar/panels/timer.html:2 msgid "Resource usage" @@ -648,10 +692,31 @@ msgstr "Atributo de Cronometragem" msgid "Milliseconds since navigation start (+length)" msgstr "Milissegundos desde início de navegação (+length)" -#: templates/debug_toolbar/panels/versions.html:5 +#: templates/debug_toolbar/panels/versions.html:10 +msgid "Package" +msgstr "Pacote" + +#: templates/debug_toolbar/panels/versions.html:11 msgid "Name" msgstr "Nome" -#: templates/debug_toolbar/panels/versions.html:6 +#: templates/debug_toolbar/panels/versions.html:12 msgid "Version" msgstr "Versão" + +#: templates/debug_toolbar/redirect.html:10 +msgid "Location:" +msgstr "Localização:" + +#: templates/debug_toolbar/redirect.html:12 +msgid "" +"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." +msgstr "O Django Debug Toolbar interceptou um redirecionamento para a URL acima para fins de visualização de depuração. Você pode clicar no link acima para continuar com o redirecionamento normalmente." + +#: views.py:16 +msgid "" +"Data for this panel isn't available anymore. Please reload the page and " +"retry." +msgstr "Os dados para este painel não está mais disponível. Por favor, recarregue a página e tente novamente." diff --git a/debug_toolbar/locale/ru/LC_MESSAGES/django.mo b/debug_toolbar/locale/ru/LC_MESSAGES/django.mo index f67bfb07c..b388ed98d 100644 Binary files a/debug_toolbar/locale/ru/LC_MESSAGES/django.mo and b/debug_toolbar/locale/ru/LC_MESSAGES/django.mo differ diff --git a/debug_toolbar/locale/ru/LC_MESSAGES/django.po b/debug_toolbar/locale/ru/LC_MESSAGES/django.po index 7e8aa783e..28cc6b947 100644 --- a/debug_toolbar/locale/ru/LC_MESSAGES/django.po +++ b/debug_toolbar/locale/ru/LC_MESSAGES/django.po @@ -3,278 +3,324 @@ # # # Translators: +# Andrei Satsevich, 2025 +# Dmitri Bogomolov <4glitch@gmail.com>, 2014 # Ilya Baryshev , 2013 # Mikhail Korobov, 2009 -# Алексей Борискин , 2013 +# Алексей Борискин , 2013,2015,2024 msgid "" msgstr "" "Project-Id-Version: Django Debug Toolbar\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-21 16:45+0600\n" -"PO-Revision-Date: 2016-07-21 17:22+0600\n" -"Last-Translator: Igor 'idle sign' Starikov \n" -"Language-Team: Russian (http://www.transifex.com/projects/p/django-debug-" -"toolbar/language/ru/)\n" -"Language: ru\n" +"POT-Creation-Date: 2024-08-06 07:12-0500\n" +"PO-Revision-Date: 2010-11-30 00:00+0000\n" +"Last-Translator: Andrei Satsevich, 2025\n" +"Language-Team: Russian (http://app.transifex.com/django-debug-toolbar/django-debug-toolbar/language/ru/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" -"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" -"X-Generator: Poedit 1.8.7.1\n" +"Language: ru\n" +"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n" -#: apps.py:11 +#: apps.py:18 msgid "Debug Toolbar" -msgstr "Отладочная панель" +msgstr "Панель отладки" + +#: panels/alerts.py:67 +#, python-brace-format +msgid "" +"Form with id \"{form_id}\" contains file input, but does not have the " +"attribute enctype=\"multipart/form-data\"." +msgstr "Форма с идентификатором \"{form_id}\" содержит файл, но не имеет атрибута enctype=\"multipart/form-data\"." + +#: panels/alerts.py:70 +msgid "" +"Form contains file input, but does not have the attribute " +"enctype=\"multipart/form-data\"." +msgstr "Форма содержит файл, но не имеет атрибута enctype=\"multipart/form-data\"." + +#: panels/alerts.py:73 +#, python-brace-format +msgid "" +"Input element references form with id \"{form_id}\", but the form does not " +"have the attribute enctype=\"multipart/form-data\"." +msgstr "Элемент ввода ссылается на форму с id \"{form_id}\", но форма не имеет атрибута enctype=\"multipart/form-data\"." -#: panels/cache.py:204 +#: panels/alerts.py:77 +msgid "Alerts" +msgstr "Оповещения" + +#: panels/cache.py:168 msgid "Cache" -msgstr "Кеш" +msgstr "Кэш" -#: panels/cache.py:209 +#: panels/cache.py:174 #, python-format msgid "%(cache_calls)d call in %(time).2fms" msgid_plural "%(cache_calls)d calls in %(time).2fms" -msgstr[0] "%(cache_calls)d обращение за %(time).2f мс" -msgstr[1] "%(cache_calls)d обращения за %(time).2f мс" -msgstr[2] "%(cache_calls)d обращений за %(time).2f мс" +msgstr[0] "%(cache_calls)d обращение за %(time).2fms" +msgstr[1] "%(cache_calls)d обращения за %(time).2fms" +msgstr[2] "%(cache_calls)d обращений за %(time).2fms" +msgstr[3] "%(cache_calls)d обращений за %(time).2fms" -#: panels/cache.py:217 +#: panels/cache.py:183 #, python-format msgid "Cache calls from %(count)d backend" msgid_plural "Cache calls from %(count)d backends" -msgstr[0] "Обращения к кешу от %(count)d бэкенда" -msgstr[1] "Обращения к кешу от %(count)d бэкендов" -msgstr[2] "Обращения к кешу от %(count)d бэкендов" +msgstr[0] "Обращения к кэшу от %(count)d бэкенда" +msgstr[1] "Обращения к кэшу от %(count)d бэкендов" +msgstr[2] "Обращения к кэшу от %(count)d бэкендов" +msgstr[3] "Обращения к кэшу от %(count)d бэкендов" -#: panels/headers.py:34 +#: panels/headers.py:31 msgid "Headers" msgstr "Заголовки" -#: panels/logging.py:66 -msgid "Logging" -msgstr "Логи" - -#: panels/logging.py:72 -#, python-format -msgid "%(count)s message" -msgid_plural "%(count)s messages" -msgstr[0] "%(count)s сообщение" -msgstr[1] "%(count)s сообщений" -msgstr[2] "%(count)s сообщений" - -#: panels/logging.py:75 -msgid "Log messages" -msgstr "Сообщения в логе" +#: panels/history/panel.py:19 panels/history/panel.py:20 +msgid "History" +msgstr "История" -#: panels/profiling.py:144 +#: panels/profiling.py:140 msgid "Profiling" msgstr "Профилирование" -#: panels/redirects.py:16 +#: panels/redirects.py:17 msgid "Intercept redirects" -msgstr "Перехватывать перенаправления" +msgstr "Перехват редиректов" -#: panels/request.py:18 +#: panels/request.py:16 msgid "Request" msgstr "Запрос" -#: panels/request.py:35 +#: panels/request.py:38 msgid "" -msgstr "<нет представления>" +msgstr "<нет view>" -#: panels/request.py:47 +#: panels/request.py:55 msgid "" msgstr "<недоступно>" -#: panels/settings.py:18 +#: panels/settings.py:17 msgid "Settings" msgstr "Настройки" -#: panels/settings.py:21 +#: panels/settings.py:20 #, python-format -msgid "Settings from %s" -msgstr "Настройки из %s" +msgid "Settings from %s" +msgstr "Настройки из %s" -#: panels/signals.py:44 +#: panels/signals.py:57 #, python-format msgid "%(num_receivers)d receiver of 1 signal" msgid_plural "%(num_receivers)d receivers of 1 signal" msgstr[0] "%(num_receivers)d получатель 1 сигнала" msgstr[1] "%(num_receivers)d получателя 1 сигнала" msgstr[2] "%(num_receivers)d получателей 1 сигнала" +msgstr[3] "%(num_receivers)d получателей 1 сигнала" -#: panels/signals.py:47 +#: panels/signals.py:62 #, python-format msgid "%(num_receivers)d receiver of %(num_signals)d signals" msgid_plural "%(num_receivers)d receivers of %(num_signals)d signals" msgstr[0] "%(num_receivers)d получатель %(num_signals)d сигнала(ов)" msgstr[1] "%(num_receivers)d получателя %(num_signals)d сигнала(ов)" msgstr[2] "%(num_receivers)d получателей %(num_signals)d сигнала(ов)" +msgstr[3] "%(num_receivers)d получателей %(num_signals)d сигнала(ов)" -#: panels/signals.py:52 +#: panels/signals.py:67 msgid "Signals" msgstr "Сигналы" -#: panels/sql/panel.py:25 -msgid "Autocommit" -msgstr "Autocommit" - -#: panels/sql/panel.py:26 +#: panels/sql/panel.py:30 panels/sql/panel.py:41 msgid "Read uncommitted" msgstr "Read uncommitted" -#: panels/sql/panel.py:27 +#: panels/sql/panel.py:31 panels/sql/panel.py:43 msgid "Read committed" msgstr "Read committed" -#: panels/sql/panel.py:28 +#: panels/sql/panel.py:32 panels/sql/panel.py:45 msgid "Repeatable read" msgstr "Repeatable read" -#: panels/sql/panel.py:29 +#: panels/sql/panel.py:33 panels/sql/panel.py:47 msgid "Serializable" -msgstr "Сериализуемый" +msgstr "Serializable" -#: panels/sql/panel.py:40 +#: panels/sql/panel.py:39 +msgid "Autocommit" +msgstr "Autocommit" + +#: panels/sql/panel.py:61 panels/sql/panel.py:71 msgid "Idle" msgstr "Ожидание" -#: panels/sql/panel.py:41 +#: panels/sql/panel.py:62 panels/sql/panel.py:72 msgid "Active" msgstr "Действие" -#: panels/sql/panel.py:42 +#: panels/sql/panel.py:63 panels/sql/panel.py:73 msgid "In transaction" msgstr "В транзакции" -#: panels/sql/panel.py:43 +#: panels/sql/panel.py:64 panels/sql/panel.py:74 msgid "In error" msgstr "Ошибка" -#: panels/sql/panel.py:44 +#: panels/sql/panel.py:65 panels/sql/panel.py:75 msgid "Unknown" msgstr "Неизвестно" -#: panels/sql/panel.py:108 +#: panels/sql/panel.py:162 msgid "SQL" msgstr "SQL" -#: panels/staticfiles.py:87 +#: panels/sql/panel.py:168 +#, python-format +msgid "%(query_count)d query in %(sql_time).2fms" +msgid_plural "%(query_count)d queries in %(sql_time).2fms" +msgstr[0] "%(query_count)d запрос за %(sql_time).2f мс " +msgstr[1] "%(query_count)d запросов за %(sql_time).2f мс" +msgstr[2] "%(query_count)d запросов за %(sql_time).2f мс" +msgstr[3] "%(query_count)d запросов за %(sql_time).2f мс" + +#: panels/sql/panel.py:180 +#, python-format +msgid "SQL queries from %(count)d connection" +msgid_plural "SQL queries from %(count)d connections" +msgstr[0] "SQL-запросы из %(count)d соединения" +msgstr[1] "SQL-запросы из %(count)d соединений" +msgstr[2] "SQL-запросы из %(count)d соединений" +msgstr[3] "SQL-запросы из %(count)d соединений" + +#: panels/staticfiles.py:82 #, python-format msgid "Static files (%(num_found)s found, %(num_used)s used)" -msgstr "Файлов статики (вего: %(num_found)s, использовано: %(num_used)s)" +msgstr "Статические файлы (найдено %(num_found)s, используется %(num_used)s)" -#: panels/staticfiles.py:105 +#: panels/staticfiles.py:103 msgid "Static files" -msgstr "Статика" +msgstr "Статические файлы" -#: panels/staticfiles.py:110 +#: panels/staticfiles.py:109 #, python-format msgid "%(num_used)s file used" msgid_plural "%(num_used)s files used" msgstr[0] "%(num_used)s файл используется" -msgstr[1] "%(num_used)s файла используются" -msgstr[2] "%(num_used)s файлов используются" +msgstr[1] " %(num_used)s файла используется" +msgstr[2] "%(num_used)s файлов используется" +msgstr[3] "%(num_used)s файлов используется" -#: panels/templates/panel.py:128 +#: panels/templates/panel.py:101 msgid "Templates" msgstr "Шаблоны" -#: panels/templates/panel.py:133 +#: panels/templates/panel.py:106 #, python-format msgid "Templates (%(num_templates)s rendered)" msgstr "Шаблоны (обработано %(num_templates)s)" -#: panels/templates/panel.py:164 +#: panels/templates/panel.py:195 msgid "No origin" -msgstr "Нет источника" +msgstr "Без происхождения" -#: panels/timer.py:26 +#: panels/timer.py:27 #, python-format msgid "CPU: %(cum)0.2fms (%(total)0.2fms)" -msgstr "CPU: %(cum)0.2f мс (%(total)0.2f мс)" +msgstr "CPU: %(cum)0.2fms (%(total)0.2fms)" -#: panels/timer.py:31 +#: panels/timer.py:32 #, python-format msgid "Total: %0.2fms" -msgstr "Итого: %0.2f мс" +msgstr "Итого: %0.2fms" -#: panels/timer.py:37 templates/debug_toolbar/panels/logging.html:7 +#: panels/timer.py:38 templates/debug_toolbar/panels/history.html:9 #: templates/debug_toolbar/panels/sql_explain.html:11 #: templates/debug_toolbar/panels/sql_profile.html:12 #: templates/debug_toolbar/panels/sql_select.html:11 msgid "Time" msgstr "Время" -#: panels/timer.py:45 +#: panels/timer.py:46 msgid "User CPU time" -msgstr "Пользовательское время ядра" +msgstr "User CPU time" -#: panels/timer.py:45 +#: panels/timer.py:46 #, python-format msgid "%(utime)0.3f msec" msgstr "%(utime)0.3f мс" -#: panels/timer.py:46 +#: panels/timer.py:47 msgid "System CPU time" -msgstr "Системное время ядра" +msgstr "System CPU time" -#: panels/timer.py:46 +#: panels/timer.py:47 #, python-format msgid "%(stime)0.3f msec" msgstr "%(stime)0.3f мс" -#: panels/timer.py:47 +#: panels/timer.py:48 msgid "Total CPU time" -msgstr "Общее время ядра" +msgstr "Total CPU time" -#: panels/timer.py:47 +#: panels/timer.py:48 #, python-format msgid "%(total)0.3f msec" msgstr "%(total)0.3f мс" -#: panels/timer.py:48 +#: panels/timer.py:49 msgid "Elapsed time" msgstr "Затраченное время" -#: panels/timer.py:48 +#: panels/timer.py:49 #, python-format msgid "%(total_time)0.3f msec" msgstr "%(total_time)0.3f мс" -#: panels/timer.py:49 +#: panels/timer.py:51 msgid "Context switches" -msgstr "Переключения контекста" +msgstr "Переключений контекста" -#: panels/timer.py:49 +#: panels/timer.py:52 #, python-format msgid "%(vcsw)d voluntary, %(ivcsw)d involuntary" msgstr "%(vcsw)d намеренных, %(ivcsw)d вынужденных" -#: panels/versions.py:20 +#: panels/versions.py:19 msgid "Versions" msgstr "Версии" -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide toolbar" msgstr "Скрыть панель" -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide" msgstr "Скрыть" -#: templates/debug_toolbar/base.html:25 +#: templates/debug_toolbar/base.html:25 templates/debug_toolbar/base.html:26 +msgid "Toggle Theme" +msgstr "Переключатель темы" + +#: templates/debug_toolbar/base.html:35 +msgid "Show toolbar" +msgstr "Показать панель" + +#: templates/debug_toolbar/includes/panel_button.html:4 msgid "Disable for next and successive requests" msgstr "Отключить для последующих запросов" -#: templates/debug_toolbar/base.html:25 +#: templates/debug_toolbar/includes/panel_button.html:4 msgid "Enable for next and successive requests" msgstr "Включить для последующих запросов" -#: templates/debug_toolbar/base.html:47 -msgid "Show toolbar" -msgstr "Показать панель" +#: templates/debug_toolbar/panels/alerts.html:4 +msgid "Alerts found" +msgstr "Найдены оповещения" + +#: templates/debug_toolbar/panels/alerts.html:11 +msgid "No alerts found" +msgstr "Оповещения не найдены" #: templates/debug_toolbar/panels/cache.html:2 msgid "Summary" @@ -290,11 +336,11 @@ msgstr "Общее время" #: templates/debug_toolbar/panels/cache.html:8 msgid "Cache hits" -msgstr "Попадания" +msgstr "Cache хитов" #: templates/debug_toolbar/panels/cache.html:9 msgid "Cache misses" -msgstr "Промахи" +msgstr "Промахи кэша" #: templates/debug_toolbar/panels/cache.html:21 msgid "Commands" @@ -305,7 +351,7 @@ msgid "Calls" msgstr "Вызовы" #: templates/debug_toolbar/panels/cache.html:43 -#: templates/debug_toolbar/panels/sql.html:23 +#: templates/debug_toolbar/panels/sql.html:36 msgid "Time (ms)" msgstr "Время (мс)" @@ -340,10 +386,8 @@ msgstr "Заголовок" #: templates/debug_toolbar/panels/headers.html:9 #: templates/debug_toolbar/panels/headers.html:28 #: templates/debug_toolbar/panels/headers.html:49 -#: templates/debug_toolbar/panels/request.html:33 -#: templates/debug_toolbar/panels/request.html:59 -#: templates/debug_toolbar/panels/request.html:85 -#: templates/debug_toolbar/panels/request.html:110 +#: templates/debug_toolbar/panels/history_tr.html:23 +#: templates/debug_toolbar/panels/request_variables.html:12 #: templates/debug_toolbar/panels/settings.html:6 #: templates/debug_toolbar/panels/timer.html:11 msgid "Value" @@ -361,30 +405,34 @@ msgstr "WSGI-окружение" msgid "" "Since the WSGI environ inherits the environment of the server, only a " "significant subset is shown below." -msgstr "" -"Так как WSGI-окружение наследует окружение сервера, ниже отображены лишь те " -"из переменных, которые важны для нужд отладки." +msgstr "Так как WSGI-окружение наследует окружение сервера, ниже отображены лишь те из переменных, которые важны для нужд отладки." -#: templates/debug_toolbar/panels/logging.html:6 -msgid "Level" -msgstr "Уровень" +#: templates/debug_toolbar/panels/history.html:10 +msgid "Method" +msgstr "Метод" -#: templates/debug_toolbar/panels/logging.html:8 -msgid "Channel" -msgstr "Канал" +#: templates/debug_toolbar/panels/history.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:43 +msgid "Path" +msgstr "Путь" -#: templates/debug_toolbar/panels/logging.html:9 -msgid "Message" -msgstr "Сообщение" +#: templates/debug_toolbar/panels/history.html:12 +msgid "Request Variables" +msgstr "Запрос переменных" -#: templates/debug_toolbar/panels/logging.html:10 -#: templates/debug_toolbar/panels/staticfiles.html:44 -msgid "Location" -msgstr "Место" +#: templates/debug_toolbar/panels/history.html:13 +msgid "Status" +msgstr "Статус" -#: templates/debug_toolbar/panels/logging.html:26 -msgid "No messages logged" -msgstr "Сообщений нет" +#: templates/debug_toolbar/panels/history.html:14 +#: templates/debug_toolbar/panels/sql.html:37 +msgid "Action" +msgstr "Действие" + +#: templates/debug_toolbar/panels/history_tr.html:22 +#: templates/debug_toolbar/panels/request_variables.html:11 +msgid "Variable" +msgstr "Переменная" #: templates/debug_toolbar/panels/profiling.html:5 msgid "Call" @@ -409,52 +457,45 @@ msgstr "Кол-во" #: templates/debug_toolbar/panels/request.html:3 msgid "View information" -msgstr "Информация о представлении" +msgstr "View" #: templates/debug_toolbar/panels/request.html:7 msgid "View function" -msgstr "Функция представления" +msgstr "View функция" #: templates/debug_toolbar/panels/request.html:10 msgid "URL name" -msgstr "Имя URL" +msgstr "URL Name" #: templates/debug_toolbar/panels/request.html:24 msgid "Cookies" -msgstr "Куки" +msgstr "Cookies" -#: templates/debug_toolbar/panels/request.html:32 -#: templates/debug_toolbar/panels/request.html:58 -#: templates/debug_toolbar/panels/request.html:84 -#: templates/debug_toolbar/panels/request.html:109 -msgid "Variable" -msgstr "Переменная" - -#: templates/debug_toolbar/panels/request.html:46 +#: templates/debug_toolbar/panels/request.html:27 msgid "No cookies" -msgstr "Нет куков" +msgstr "Нет cookies" -#: templates/debug_toolbar/panels/request.html:50 +#: templates/debug_toolbar/panels/request.html:31 msgid "Session data" msgstr "Сессия" -#: templates/debug_toolbar/panels/request.html:72 +#: templates/debug_toolbar/panels/request.html:34 msgid "No session data" msgstr "Нет данных в сессии" -#: templates/debug_toolbar/panels/request.html:76 +#: templates/debug_toolbar/panels/request.html:38 msgid "GET data" msgstr "GET" -#: templates/debug_toolbar/panels/request.html:98 +#: templates/debug_toolbar/panels/request.html:41 msgid "No GET data" msgstr "Нет GET данных" -#: templates/debug_toolbar/panels/request.html:102 +#: templates/debug_toolbar/panels/request.html:45 msgid "POST data" msgstr "POST" -#: templates/debug_toolbar/panels/request.html:123 +#: templates/debug_toolbar/panels/request.html:48 msgid "No POST data" msgstr "Нет POST данных" @@ -467,64 +508,70 @@ msgid "Signal" msgstr "Сигнал" #: templates/debug_toolbar/panels/signals.html:6 -msgid "Providing" -msgstr "Аргументы" - -#: templates/debug_toolbar/panels/signals.html:7 msgid "Receivers" msgstr "Получатели сигнала" -#: templates/debug_toolbar/panels/sql.html:7 +#: templates/debug_toolbar/panels/sql.html:6 #, python-format msgid "%(num)s query" msgid_plural "%(num)s queries" msgstr[0] "%(num)s запрос" msgstr[1] "%(num)s запроса" msgstr[2] "%(num)s запросов" +msgstr[3] "%(num)s запросов" -#: templates/debug_toolbar/panels/sql.html:9 +#: templates/debug_toolbar/panels/sql.html:8 #, python-format -msgid "including %(dupes)s duplicates" -msgstr "включая дублей: %(dupes)s" +msgid "" +"including %(count)s similar" +msgstr "включая %(count)s похожий" -#: templates/debug_toolbar/panels/sql.html:21 +#: templates/debug_toolbar/panels/sql.html:12 +#, python-format +msgid "" +"and %(dupes)s duplicates" +msgstr "и %(dupes)s дубликаты" + +#: templates/debug_toolbar/panels/sql.html:34 msgid "Query" msgstr "Запрос" -#: templates/debug_toolbar/panels/sql.html:22 +#: templates/debug_toolbar/panels/sql.html:35 #: templates/debug_toolbar/panels/timer.html:36 msgid "Timeline" msgstr "Временная диаграмма" -#: templates/debug_toolbar/panels/sql.html:24 -msgid "Action" -msgstr "Действие" +#: templates/debug_toolbar/panels/sql.html:52 +#, python-format +msgid "%(count)s similar queries." +msgstr "%(count)s похожих запросов." -#: templates/debug_toolbar/panels/sql.html:41 +#: templates/debug_toolbar/panels/sql.html:58 #, python-format msgid "Duplicated %(dupes)s times." -msgstr "Дублей: %(dupes)s." +msgstr "Дублируется %(dupes)s раз." -#: templates/debug_toolbar/panels/sql.html:73 +#: templates/debug_toolbar/panels/sql.html:95 msgid "Connection:" msgstr "Соединение:" -#: templates/debug_toolbar/panels/sql.html:75 +#: templates/debug_toolbar/panels/sql.html:97 msgid "Isolation level:" msgstr "Уровень изоляции:" -#: templates/debug_toolbar/panels/sql.html:78 +#: templates/debug_toolbar/panels/sql.html:100 msgid "Transaction status:" msgstr "Статус транзакции:" -#: templates/debug_toolbar/panels/sql.html:92 +#: templates/debug_toolbar/panels/sql.html:114 msgid "(unknown)" msgstr "(неизвестно)" -#: templates/debug_toolbar/panels/sql.html:101 +#: templates/debug_toolbar/panels/sql.html:123 msgid "No SQL queries were recorded during this request." -msgstr "" -"Во время обработки этого HTTP-запроса не было записано ни одного SQL-запроса." +msgstr "Во время обработки этого HTTP-запроса не было записано ни одного SQL-запроса." #: templates/debug_toolbar/panels/sql_explain.html:4 msgid "SQL explained" @@ -561,9 +608,10 @@ msgstr "Ничего, ноль строк" #: templates/debug_toolbar/panels/staticfiles.html:3 msgid "Static file path" msgid_plural "Static file paths" -msgstr[0] "Путь к статике" -msgstr[1] "Пути к статике" -msgstr[2] "Пути к статике" +msgstr[0] "Путь к статическим файлам" +msgstr[1] "Пути к статическим файлам" +msgstr[2] "Пути к статическим файлам" +msgstr[3] "Пути к статическим файлам" #: templates/debug_toolbar/panels/staticfiles.html:7 #, python-format @@ -574,24 +622,26 @@ msgstr "(префикс %(prefix)s)" #: templates/debug_toolbar/panels/staticfiles.html:22 #: templates/debug_toolbar/panels/staticfiles.html:34 #: templates/debug_toolbar/panels/templates.html:10 -#: templates/debug_toolbar/panels/templates.html:28 -#: templates/debug_toolbar/panels/templates.html:43 +#: templates/debug_toolbar/panels/templates.html:30 +#: templates/debug_toolbar/panels/templates.html:47 msgid "None" msgstr "Нет" #: templates/debug_toolbar/panels/staticfiles.html:14 msgid "Static file app" msgid_plural "Static file apps" -msgstr[0] "Приложение со статикой" -msgstr[1] "Приложения со статикой" -msgstr[2] "Приложения со статикой" +msgstr[0] "Приложение, использующее статические файлы" +msgstr[1] "Приложения, использующие статические файлы" +msgstr[2] "Приложения, использующие статические файлы" +msgstr[3] "Приложения, использующие статические файлы" #: templates/debug_toolbar/panels/staticfiles.html:25 msgid "Static file" msgid_plural "Static files" -msgstr[0] "Файл статики" -msgstr[1] "Файлы статики" -msgstr[2] "Файлы статики" +msgstr[0] "Статический файл" +msgstr[1] "Статические файлы" +msgstr[2] "Статические файлы" +msgstr[3] "Статические файлы" #: templates/debug_toolbar/panels/staticfiles.html:39 #, python-format @@ -600,10 +650,11 @@ msgid_plural "%(payload_count)s files" msgstr[0] "%(payload_count)s файл" msgstr[1] "%(payload_count)s файла" msgstr[2] "%(payload_count)s файлов" +msgstr[3] "%(payload_count)s файлов" -#: templates/debug_toolbar/panels/staticfiles.html:43 -msgid "Path" -msgstr "Путь" +#: templates/debug_toolbar/panels/staticfiles.html:44 +msgid "Location" +msgstr "Место" #: templates/debug_toolbar/panels/template_source.html:4 msgid "Template source:" @@ -615,6 +666,7 @@ msgid_plural "Template paths" msgstr[0] "Путь к шаблонам" msgstr[1] "Пути к шаблонам" msgstr[2] "Пути к шаблонам" +msgstr[3] "Пути к шаблонам" #: templates/debug_toolbar/panels/templates.html:13 msgid "Template" @@ -622,18 +674,20 @@ msgid_plural "Templates" msgstr[0] "Шаблон" msgstr[1] "Шаблоны" msgstr[2] "Шаблоны" +msgstr[3] "Шаблоны" -#: templates/debug_toolbar/panels/templates.html:21 -#: templates/debug_toolbar/panels/templates.html:37 +#: templates/debug_toolbar/panels/templates.html:22 +#: templates/debug_toolbar/panels/templates.html:40 msgid "Toggle context" msgstr "Контекст" -#: templates/debug_toolbar/panels/templates.html:31 +#: templates/debug_toolbar/panels/templates.html:33 msgid "Context processor" msgid_plural "Context processors" msgstr[0] "Контекст процессор" msgstr[1] "Контекст процессоры" msgstr[2] "Контекст процессоры" +msgstr[3] "Контекст процессоры" #: templates/debug_toolbar/panels/timer.html:2 msgid "Resource usage" @@ -655,41 +709,31 @@ msgstr "Событие" msgid "Milliseconds since navigation start (+length)" msgstr "С начала навигации в мс (+продолжительность)" -#: templates/debug_toolbar/panels/versions.html:7 +#: templates/debug_toolbar/panels/versions.html:10 msgid "Package" msgstr "Пакет" -#: templates/debug_toolbar/panels/versions.html:8 +#: templates/debug_toolbar/panels/versions.html:11 msgid "Name" msgstr "Название" -#: templates/debug_toolbar/panels/versions.html:9 +#: templates/debug_toolbar/panels/versions.html:12 msgid "Version" msgstr "Версия" -#: templates/debug_toolbar/redirect.html:8 +#: templates/debug_toolbar/redirect.html:10 msgid "Location:" -msgstr "Место:" +msgstr "Адрес:" -#: templates/debug_toolbar/redirect.html:10 +#: templates/debug_toolbar/redirect.html:12 msgid "" "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." -msgstr "" -"Django Debug Toolbar перехватил перенаправление на адрес, указанный выше. Вы " -"можете нажать на ссылку, чтобы выполнить переход самостоятельно." +msgstr "Django Debug Toolbar в перехватил редирект на адрес, указанный выше. Вы можете нажать на ссылку, чтобы выполнить переход самостоятельно." -#: views.py:14 +#: views.py:16 msgid "" "Data for this panel isn't available anymore. Please reload the page and " "retry." -msgstr "" -"Данные этой панели больше недоступны. Пожалуйста, перезагрузите страницу и " -"попробуйте ещё раз." - -#~ msgid "Close" -#~ msgstr "Закрыть" - -#~ msgid "Back" -#~ msgstr "Назад" +msgstr "Данные этой панели больше недоступны. Пожалуйста, перезагрузите страницу и попробуйте ещё раз." diff --git a/debug_toolbar/locale/sk/LC_MESSAGES/django.mo b/debug_toolbar/locale/sk/LC_MESSAGES/django.mo index a26e3cc7b..52934bf7a 100644 Binary files a/debug_toolbar/locale/sk/LC_MESSAGES/django.mo and b/debug_toolbar/locale/sk/LC_MESSAGES/django.mo differ diff --git a/debug_toolbar/locale/sk/LC_MESSAGES/django.po b/debug_toolbar/locale/sk/LC_MESSAGES/django.po index 2c8a9c7e3..d5c5b722b 100644 --- a/debug_toolbar/locale/sk/LC_MESSAGES/django.po +++ b/debug_toolbar/locale/sk/LC_MESSAGES/django.po @@ -1,76 +1,84 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# -# +# +# # Translators: -# Juraj Bubniak , 2012 -# Juraj Bubniak , 2013 +# 18f25ad6fa9930fc67cb11aca9d16a27, 2012 +# 18f25ad6fa9930fc67cb11aca9d16a27, 2013 # Rastislav Kober , 2012 msgid "" msgstr "" "Project-Id-Version: Django Debug Toolbar\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-04-25 21:52+0200\n" -"PO-Revision-Date: 2014-04-25 19:53+0000\n" -"Last-Translator: Aymeric Augustin \n" -"Language-Team: Slovak (http://www.transifex.com/projects/p/django-debug-toolbar/language/sk/)\n" +"POT-Creation-Date: 2024-08-06 07:12-0500\n" +"PO-Revision-Date: 2010-11-30 00:00+0000\n" +"Last-Translator: 18f25ad6fa9930fc67cb11aca9d16a27, 2013\n" +"Language-Team: Slovak (http://app.transifex.com/django-debug-toolbar/django-debug-toolbar/language/sk/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: sk\n" -"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" +"Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n >= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);\n" -#: apps.py:11 +#: apps.py:18 msgid "Debug Toolbar" msgstr "" -#: views.py:14 +#: panels/alerts.py:67 +#, python-brace-format msgid "" -"Data for this panel isn't available anymore. Please reload the page and " -"retry." -msgstr "Dáta pre tento panel už nie sú k dispozícii. Načítajte si prosím stránku a skúste to znova." +"Form with id \"{form_id}\" contains file input, but does not have the " +"attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:70 +msgid "" +"Form contains file input, but does not have the attribute " +"enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:73 +#, python-brace-format +msgid "" +"Input element references form with id \"{form_id}\", but the form does not " +"have the attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:77 +msgid "Alerts" +msgstr "" -#: panels/cache.py:191 +#: panels/cache.py:168 msgid "Cache" msgstr "Cache" -#: panels/cache.py:196 +#: panels/cache.py:174 #, python-format msgid "%(cache_calls)d call in %(time).2fms" msgid_plural "%(cache_calls)d calls in %(time).2fms" msgstr[0] "%(cache_calls)d volanie za %(time).2fms" msgstr[1] "%(cache_calls)d volaní za %(time).2fms" msgstr[2] "%(cache_calls)d volaní za %(time).2fms" +msgstr[3] "%(cache_calls)d volaní za %(time).2fms" -#: panels/cache.py:204 +#: panels/cache.py:183 #, python-format msgid "Cache calls from %(count)d backend" msgid_plural "Cache calls from %(count)d backends" msgstr[0] "Cache volania z %(count)d backendu" msgstr[1] "Cache volania z %(count)d backendov" msgstr[2] "Cache volania z %(count)d backendov" +msgstr[3] "Cache volania z %(count)d backendov" -#: panels/headers.py:35 +#: panels/headers.py:31 msgid "Headers" msgstr "Hlavičky" -#: panels/logging.py:64 -msgid "Logging" -msgstr "Zápis" - -#: panels/logging.py:70 -#, python-format -msgid "%(count)s message" -msgid_plural "%(count)s messages" -msgstr[0] "%(count)s správa" -msgstr[1] "%(count)s správ" -msgstr[2] "%(count)s správ" - -#: panels/logging.py:73 -msgid "Log messages" -msgstr "Správy zápisu" +#: panels/history/panel.py:19 panels/history/panel.py:20 +msgid "History" +msgstr "" -#: panels/profiling.py:127 +#: panels/profiling.py:140 msgid "Profiling" msgstr "Analýza" @@ -78,217 +86,239 @@ msgstr "Analýza" msgid "Intercept redirects" msgstr "Zachytiť presmerovania" -#: panels/request.py:18 +#: panels/request.py:16 msgid "Request" msgstr "Požiadavka" -#: panels/request.py:35 +#: panels/request.py:38 msgid "" msgstr "" -#: panels/request.py:47 +#: panels/request.py:55 msgid "" msgstr "" -#: panels/settings.py:20 +#: panels/settings.py:17 msgid "Settings" msgstr "Nastavenia" -#: panels/settings.py:23 +#: panels/settings.py:20 #, python-format -msgid "Settings from %s" -msgstr "Nastavenia z %s" +msgid "Settings from %s" +msgstr "" -#: panels/signals.py:45 +#: panels/signals.py:57 #, python-format msgid "%(num_receivers)d receiver of 1 signal" msgid_plural "%(num_receivers)d receivers of 1 signal" msgstr[0] "%(num_receivers)d príjemca 1 signálu" msgstr[1] "%(num_receivers)d príjemcov 1 signálu" msgstr[2] "%(num_receivers)d príjemcov 1 signálu" +msgstr[3] "%(num_receivers)d príjemcov 1 signálu" -#: panels/signals.py:48 +#: panels/signals.py:62 #, python-format msgid "%(num_receivers)d receiver of %(num_signals)d signals" msgid_plural "%(num_receivers)d receivers of %(num_signals)d signals" msgstr[0] "%(num_receivers)d príjemca %(num_signals)d signálov" msgstr[1] "%(num_receivers)d príjemcov %(num_signals)d signálov" msgstr[2] "%(num_receivers)d príjemcov %(num_signals)d signálov" +msgstr[3] "%(num_receivers)d príjemcov %(num_signals)d signálov" -#: panels/signals.py:53 +#: panels/signals.py:67 msgid "Signals" msgstr "Signály" -#: panels/staticfiles.py:89 +#: panels/sql/panel.py:30 panels/sql/panel.py:41 +msgid "Read uncommitted" +msgstr "Read uncommitted" + +#: panels/sql/panel.py:31 panels/sql/panel.py:43 +msgid "Read committed" +msgstr "Read committed" + +#: panels/sql/panel.py:32 panels/sql/panel.py:45 +msgid "Repeatable read" +msgstr "Opakovateľné čítanie" + +#: panels/sql/panel.py:33 panels/sql/panel.py:47 +msgid "Serializable" +msgstr "Premenná" + +#: panels/sql/panel.py:39 +msgid "Autocommit" +msgstr "Autocommit" + +#: panels/sql/panel.py:61 panels/sql/panel.py:71 +msgid "Idle" +msgstr "Nečinný" + +#: panels/sql/panel.py:62 panels/sql/panel.py:72 +msgid "Active" +msgstr "Akcia" + +#: panels/sql/panel.py:63 panels/sql/panel.py:73 +msgid "In transaction" +msgstr "Stav transakcie:" + +#: panels/sql/panel.py:64 panels/sql/panel.py:74 +msgid "In error" +msgstr "Chyba" + +#: panels/sql/panel.py:65 panels/sql/panel.py:75 +msgid "Unknown" +msgstr "(neznámy)" + +#: panels/sql/panel.py:162 +msgid "SQL" +msgstr "SQL" + +#: panels/sql/panel.py:168 +#, python-format +msgid "%(query_count)d query in %(sql_time).2fms" +msgid_plural "%(query_count)d queries in %(sql_time).2fms" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" + +#: panels/sql/panel.py:180 +#, python-format +msgid "SQL queries from %(count)d connection" +msgid_plural "SQL queries from %(count)d connections" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" + +#: panels/staticfiles.py:82 #, python-format msgid "Static files (%(num_found)s found, %(num_used)s used)" msgstr "Statické súbory (%(num_found)s nájdených, %(num_used)s použitých)" -#: panels/staticfiles.py:107 +#: panels/staticfiles.py:103 msgid "Static files" msgstr "Statické súbory" -#: panels/staticfiles.py:112 +#: panels/staticfiles.py:109 #, python-format msgid "%(num_used)s file used" msgid_plural "%(num_used)s files used" msgstr[0] "%(num_used)s použitý súbor" msgstr[1] "%(num_used)s použitých súborov" msgstr[2] "%(num_used)s použitých súborov" +msgstr[3] "%(num_used)s použitých súborov" + +#: panels/templates/panel.py:101 +msgid "Templates" +msgstr "Šablóny" + +#: panels/templates/panel.py:106 +#, python-format +msgid "Templates (%(num_templates)s rendered)" +msgstr "Šablóny (%(num_templates)s spracovaných)" -#: panels/timer.py:23 +#: panels/templates/panel.py:195 +msgid "No origin" +msgstr "" + +#: panels/timer.py:27 #, python-format msgid "CPU: %(cum)0.2fms (%(total)0.2fms)" msgstr "CPU: %(cum)0.2fms (%(total)0.2fms)" -#: panels/timer.py:28 +#: panels/timer.py:32 #, python-format msgid "Total: %0.2fms" msgstr "Celkovo: %0.2fms" -#: panels/timer.py:34 templates/debug_toolbar/panels/logging.html:7 +#: panels/timer.py:38 templates/debug_toolbar/panels/history.html:9 #: templates/debug_toolbar/panels/sql_explain.html:11 #: templates/debug_toolbar/panels/sql_profile.html:12 #: templates/debug_toolbar/panels/sql_select.html:11 msgid "Time" msgstr "Čas" -#: panels/timer.py:42 +#: panels/timer.py:46 msgid "User CPU time" msgstr "Užívateľský čas CPU" -#: panels/timer.py:42 +#: panels/timer.py:46 #, python-format msgid "%(utime)0.3f msec" msgstr "%(utime)0.3f msek" -#: panels/timer.py:43 +#: panels/timer.py:47 msgid "System CPU time" msgstr "Systémový čas CPU" -#: panels/timer.py:43 +#: panels/timer.py:47 #, python-format msgid "%(stime)0.3f msec" msgstr "%(stime)0.3f msek" -#: panels/timer.py:44 +#: panels/timer.py:48 msgid "Total CPU time" msgstr "Celkový čas CPU" -#: panels/timer.py:44 +#: panels/timer.py:48 #, python-format msgid "%(total)0.3f msec" msgstr "%(total)0.3f msek" -#: panels/timer.py:45 +#: panels/timer.py:49 msgid "Elapsed time" msgstr "Uplynutý čas" -#: panels/timer.py:45 +#: panels/timer.py:49 #, python-format msgid "%(total_time)0.3f msec" msgstr "%(total_time)0.3f msek" -#: panels/timer.py:46 +#: panels/timer.py:51 msgid "Context switches" msgstr "Prepnutí kontextu" -#: panels/timer.py:46 +#: panels/timer.py:52 #, python-format msgid "%(vcsw)d voluntary, %(ivcsw)d involuntary" msgstr "%(vcsw)d dobrovoľných, %(ivcsw)d nedobrovoľných" -#: panels/versions.py:25 +#: panels/versions.py:19 msgid "Versions" msgstr "Verzie" -#: panels/sql/panel.py:22 -msgid "Autocommit" -msgstr "Autocommit" - -#: panels/sql/panel.py:23 -msgid "Read uncommitted" -msgstr "Read uncommitted" - -#: panels/sql/panel.py:24 -msgid "Read committed" -msgstr "Read committed" - -#: panels/sql/panel.py:25 -msgid "Repeatable read" -msgstr "Opakovateľné čítanie" - -#: panels/sql/panel.py:26 -msgid "Serializable" -msgstr "Premenná" - -#: panels/sql/panel.py:37 -msgid "Idle" -msgstr "Nečinný" - -#: panels/sql/panel.py:38 -msgid "Active" -msgstr "Akcia" - -#: panels/sql/panel.py:39 -msgid "In transaction" -msgstr "Stav transakcie:" - -#: panels/sql/panel.py:40 -msgid "In error" -msgstr "Chyba" - -#: panels/sql/panel.py:41 -msgid "Unknown" -msgstr "(neznámy)" - -#: panels/sql/panel.py:105 -msgid "SQL" -msgstr "SQL" - -#: panels/templates/panel.py:141 -msgid "Templates" -msgstr "Šablóny" - -#: panels/templates/panel.py:146 -#, python-format -msgid "Templates (%(num_templates)s rendered)" -msgstr "Šablóny (%(num_templates)s spracovaných)" - -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide toolbar" msgstr "Skryť panel nástrojov" -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide" msgstr "Skryť" -#: templates/debug_toolbar/base.html:25 +#: templates/debug_toolbar/base.html:25 templates/debug_toolbar/base.html:26 +msgid "Toggle Theme" +msgstr "" + +#: templates/debug_toolbar/base.html:35 +msgid "Show toolbar" +msgstr "Zobraziť panel nástrojov" + +#: templates/debug_toolbar/includes/panel_button.html:4 msgid "Disable for next and successive requests" msgstr "Zakázať pre ďalšie a nasledujúce požiadavky" -#: templates/debug_toolbar/base.html:25 +#: templates/debug_toolbar/includes/panel_button.html:4 msgid "Enable for next and successive requests" msgstr "Povoliť pre ďalšie a nasledujúce požiadavky" -#: templates/debug_toolbar/base.html:47 -msgid "Show toolbar" -msgstr "Zobraziť panel nástrojov" - -#: templates/debug_toolbar/base.html:53 -msgid "Close" -msgstr "Zatvoriť" - -#: templates/debug_toolbar/redirect.html:8 -msgid "Location:" -msgstr "Poloha:" +#: templates/debug_toolbar/panels/alerts.html:4 +msgid "Alerts found" +msgstr "" -#: templates/debug_toolbar/redirect.html:10 -msgid "" -"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." -msgstr "Django Debug Toolbar zachytil presmerovanie na vyššie uvedenú URL pre účely ladenia. Pre normálne presmerovanie môžete kliknúť na vyššie uvedený odkaz." +#: templates/debug_toolbar/panels/alerts.html:11 +msgid "No alerts found" +msgstr "" #: templates/debug_toolbar/panels/cache.html:2 msgid "Summary" @@ -319,7 +349,7 @@ msgid "Calls" msgstr "Volania" #: templates/debug_toolbar/panels/cache.html:43 -#: templates/debug_toolbar/panels/sql.html:20 +#: templates/debug_toolbar/panels/sql.html:36 msgid "Time (ms)" msgstr "Čas (ms)" @@ -354,10 +384,8 @@ msgstr "Kľúč" #: templates/debug_toolbar/panels/headers.html:9 #: templates/debug_toolbar/panels/headers.html:28 #: templates/debug_toolbar/panels/headers.html:49 -#: templates/debug_toolbar/panels/request.html:33 -#: templates/debug_toolbar/panels/request.html:59 -#: templates/debug_toolbar/panels/request.html:85 -#: templates/debug_toolbar/panels/request.html:110 +#: templates/debug_toolbar/panels/history_tr.html:23 +#: templates/debug_toolbar/panels/request_variables.html:12 #: templates/debug_toolbar/panels/settings.html:6 #: templates/debug_toolbar/panels/timer.html:11 msgid "Value" @@ -377,26 +405,32 @@ msgid "" "significant subset is shown below." msgstr "Keďže WSGI prostredie dedí z prostredia servera, je nižšie zobrazená iba významná podmnožina." -#: templates/debug_toolbar/panels/logging.html:6 -msgid "Level" -msgstr "Úroveň" +#: templates/debug_toolbar/panels/history.html:10 +msgid "Method" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:8 -msgid "Channel" -msgstr "Kanál" +#: templates/debug_toolbar/panels/history.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:43 +msgid "Path" +msgstr "Cesta" -#: templates/debug_toolbar/panels/logging.html:9 -msgid "Message" -msgstr "Správa" +#: templates/debug_toolbar/panels/history.html:12 +msgid "Request Variables" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:10 -#: templates/debug_toolbar/panels/staticfiles.html:45 -msgid "Location" -msgstr "Poloha" +#: templates/debug_toolbar/panels/history.html:13 +msgid "Status" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:26 -msgid "No messages logged" -msgstr "Žiadne správy neboli zaznamenané" +#: templates/debug_toolbar/panels/history.html:14 +#: templates/debug_toolbar/panels/sql.html:37 +msgid "Action" +msgstr "Akcia" + +#: templates/debug_toolbar/panels/history_tr.html:22 +#: templates/debug_toolbar/panels/request_variables.html:11 +msgid "Variable" +msgstr "Premenná" #: templates/debug_toolbar/panels/profiling.html:5 msgid "Call" @@ -435,38 +469,31 @@ msgstr "URL meno" msgid "Cookies" msgstr "Cookies" -#: templates/debug_toolbar/panels/request.html:32 -#: templates/debug_toolbar/panels/request.html:58 -#: templates/debug_toolbar/panels/request.html:84 -#: templates/debug_toolbar/panels/request.html:109 -msgid "Variable" -msgstr "Premenná" - -#: templates/debug_toolbar/panels/request.html:46 +#: templates/debug_toolbar/panels/request.html:27 msgid "No cookies" msgstr "Žiadne cookies" -#: templates/debug_toolbar/panels/request.html:50 +#: templates/debug_toolbar/panels/request.html:31 msgid "Session data" msgstr "Dáta relácie" -#: templates/debug_toolbar/panels/request.html:72 +#: templates/debug_toolbar/panels/request.html:34 msgid "No session data" msgstr "Žiadne dáta relácie" -#: templates/debug_toolbar/panels/request.html:76 +#: templates/debug_toolbar/panels/request.html:38 msgid "GET data" msgstr "GET dáta" -#: templates/debug_toolbar/panels/request.html:98 +#: templates/debug_toolbar/panels/request.html:41 msgid "No GET data" msgstr "Žiadne GET dáta" -#: templates/debug_toolbar/panels/request.html:102 +#: templates/debug_toolbar/panels/request.html:45 msgid "POST data" msgstr "POST dáta" -#: templates/debug_toolbar/panels/request.html:123 +#: templates/debug_toolbar/panels/request.html:48 msgid "No POST data" msgstr "Žiadne POST dáta" @@ -479,61 +506,71 @@ msgid "Signal" msgstr "Signál" #: templates/debug_toolbar/panels/signals.html:6 -msgid "Providing" -msgstr "Poskytuje" - -#: templates/debug_toolbar/panels/signals.html:7 msgid "Receivers" msgstr "Príjemcovia" -#: templates/debug_toolbar/panels/sql.html:7 +#: templates/debug_toolbar/panels/sql.html:6 #, python-format msgid "%(num)s query" msgid_plural "%(num)s queries" msgstr[0] "%(num)s dopyt" msgstr[1] "%(num)s dopytov" msgstr[2] "%(num)s dopytov" +msgstr[3] "%(num)s dopytov" -#: templates/debug_toolbar/panels/sql.html:18 +#: templates/debug_toolbar/panels/sql.html:8 +#, python-format +msgid "" +"including %(count)s similar" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:12 +#, python-format +msgid "" +"and %(dupes)s duplicates" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:34 msgid "Query" msgstr "Dopyt" -#: templates/debug_toolbar/panels/sql.html:19 +#: templates/debug_toolbar/panels/sql.html:35 #: templates/debug_toolbar/panels/timer.html:36 msgid "Timeline" msgstr "Časová os" -#: templates/debug_toolbar/panels/sql.html:21 -msgid "Action" -msgstr "Akcia" +#: templates/debug_toolbar/panels/sql.html:52 +#, python-format +msgid "%(count)s similar queries." +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:58 +#, python-format +msgid "Duplicated %(dupes)s times." +msgstr "" -#: templates/debug_toolbar/panels/sql.html:64 +#: templates/debug_toolbar/panels/sql.html:95 msgid "Connection:" msgstr "Pripojenie:" -#: templates/debug_toolbar/panels/sql.html:66 +#: templates/debug_toolbar/panels/sql.html:97 msgid "Isolation level:" msgstr "Úroveň izolácie:" -#: templates/debug_toolbar/panels/sql.html:69 +#: templates/debug_toolbar/panels/sql.html:100 msgid "Transaction status:" msgstr "Stav transakcie:" -#: templates/debug_toolbar/panels/sql.html:83 +#: templates/debug_toolbar/panels/sql.html:114 msgid "(unknown)" msgstr "(neznámy)" -#: templates/debug_toolbar/panels/sql.html:92 +#: templates/debug_toolbar/panels/sql.html:123 msgid "No SQL queries were recorded during this request." msgstr "V priebehu tejto požiadavky neboli zaznamenané žiadne SQL dopyty." -#: templates/debug_toolbar/panels/sql_explain.html:3 -#: templates/debug_toolbar/panels/sql_profile.html:3 -#: templates/debug_toolbar/panels/sql_select.html:3 -#: templates/debug_toolbar/panels/template_source.html:3 -msgid "Back" -msgstr "Späť" - #: templates/debug_toolbar/panels/sql_explain.html:4 msgid "SQL explained" msgstr "SQL vysvetlené" @@ -566,52 +603,56 @@ msgstr "SQL označené" msgid "Empty set" msgstr "Prázdny rad" -#: templates/debug_toolbar/panels/staticfiles.html:4 +#: templates/debug_toolbar/panels/staticfiles.html:3 msgid "Static file path" msgid_plural "Static file paths" msgstr[0] "Cesta k statickému súboru" msgstr[1] "Cesty k statickým súborom" msgstr[2] "Cesty k statickým súborom" +msgstr[3] "Cesty k statickým súborom" -#: templates/debug_toolbar/panels/staticfiles.html:8 +#: templates/debug_toolbar/panels/staticfiles.html:7 #, python-format msgid "(prefix %(prefix)s)" msgstr "(prefix %(prefix)s)" -#: templates/debug_toolbar/panels/staticfiles.html:12 -#: templates/debug_toolbar/panels/staticfiles.html:23 -#: templates/debug_toolbar/panels/staticfiles.html:35 +#: templates/debug_toolbar/panels/staticfiles.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:22 +#: templates/debug_toolbar/panels/staticfiles.html:34 #: templates/debug_toolbar/panels/templates.html:10 -#: templates/debug_toolbar/panels/templates.html:28 -#: templates/debug_toolbar/panels/templates.html:43 +#: templates/debug_toolbar/panels/templates.html:30 +#: templates/debug_toolbar/panels/templates.html:47 msgid "None" msgstr "Žiadny" -#: templates/debug_toolbar/panels/staticfiles.html:15 +#: templates/debug_toolbar/panels/staticfiles.html:14 msgid "Static file app" msgid_plural "Static file apps" msgstr[0] "Aplikácia pre statické súbory" msgstr[1] "Aplikácie pre statické súbory" msgstr[2] "Aplikácie pre statické súbory" +msgstr[3] "Aplikácie pre statické súbory" -#: templates/debug_toolbar/panels/staticfiles.html:26 +#: templates/debug_toolbar/panels/staticfiles.html:25 msgid "Static file" msgid_plural "Static files" msgstr[0] "" msgstr[1] "" msgstr[2] "Statické súbory" +msgstr[3] "Statické súbory" -#: templates/debug_toolbar/panels/staticfiles.html:40 +#: templates/debug_toolbar/panels/staticfiles.html:39 #, python-format msgid "%(payload_count)s file" msgid_plural "%(payload_count)s files" msgstr[0] "%(payload_count)s súbor" msgstr[1] "%(payload_count)s súborov" msgstr[2] "%(payload_count)s súborov" +msgstr[3] "%(payload_count)s súborov" #: templates/debug_toolbar/panels/staticfiles.html:44 -msgid "Path" -msgstr "Cesta" +msgid "Location" +msgstr "Poloha" #: templates/debug_toolbar/panels/template_source.html:4 msgid "Template source:" @@ -623,6 +664,7 @@ msgid_plural "Template paths" msgstr[0] "Cesta k šablóne" msgstr[1] "Cesta k šablóne" msgstr[2] "Cesta k šablóne" +msgstr[3] "Cesta k šablóne" #: templates/debug_toolbar/panels/templates.html:13 msgid "Template" @@ -630,18 +672,20 @@ msgid_plural "Templates" msgstr[0] "Šablóna" msgstr[1] "Šablóna" msgstr[2] "Šablóna" +msgstr[3] "Šablóna" -#: templates/debug_toolbar/panels/templates.html:21 -#: templates/debug_toolbar/panels/templates.html:37 +#: templates/debug_toolbar/panels/templates.html:22 +#: templates/debug_toolbar/panels/templates.html:40 msgid "Toggle context" msgstr "Prepnúť kontext" -#: templates/debug_toolbar/panels/templates.html:31 +#: templates/debug_toolbar/panels/templates.html:33 msgid "Context processor" msgid_plural "Context processors" msgstr[0] "Spracovateľ kontextu" msgstr[1] "Spracovateľ kontextu" msgstr[2] "Spracovateľ kontextu" +msgstr[3] "Spracovateľ kontextu" #: templates/debug_toolbar/panels/timer.html:2 msgid "Resource usage" @@ -663,10 +707,31 @@ msgstr "Časový atribút" msgid "Milliseconds since navigation start (+length)" msgstr "Milisekúnd od spustenia navigácie (+dĺžka)" -#: templates/debug_toolbar/panels/versions.html:5 +#: templates/debug_toolbar/panels/versions.html:10 +msgid "Package" +msgstr "" + +#: templates/debug_toolbar/panels/versions.html:11 msgid "Name" msgstr "Meno" -#: templates/debug_toolbar/panels/versions.html:6 +#: templates/debug_toolbar/panels/versions.html:12 msgid "Version" msgstr "Verzia" + +#: templates/debug_toolbar/redirect.html:10 +msgid "Location:" +msgstr "Poloha:" + +#: templates/debug_toolbar/redirect.html:12 +msgid "" +"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." +msgstr "Django Debug Toolbar zachytil presmerovanie na vyššie uvedenú URL pre účely ladenia. Pre normálne presmerovanie môžete kliknúť na vyššie uvedený odkaz." + +#: views.py:16 +msgid "" +"Data for this panel isn't available anymore. Please reload the page and " +"retry." +msgstr "Dáta pre tento panel už nie sú k dispozícii. Načítajte si prosím stránku a skúste to znova." diff --git a/debug_toolbar/locale/sv_SE/LC_MESSAGES/django.mo b/debug_toolbar/locale/sv_SE/LC_MESSAGES/django.mo index ae733443d..849592ff0 100644 Binary files a/debug_toolbar/locale/sv_SE/LC_MESSAGES/django.mo and b/debug_toolbar/locale/sv_SE/LC_MESSAGES/django.mo differ diff --git a/debug_toolbar/locale/sv_SE/LC_MESSAGES/django.po b/debug_toolbar/locale/sv_SE/LC_MESSAGES/django.po index 0c8b2e897..5848929d1 100644 --- a/debug_toolbar/locale/sv_SE/LC_MESSAGES/django.po +++ b/debug_toolbar/locale/sv_SE/LC_MESSAGES/django.po @@ -1,7 +1,7 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# -# +# +# # Translators: # Alex Nordlund , 2012-2013 # Alex Nordlund , 2012 @@ -9,64 +9,71 @@ msgid "" msgstr "" "Project-Id-Version: Django Debug Toolbar\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-04-25 21:52+0200\n" -"PO-Revision-Date: 2014-04-25 19:53+0000\n" -"Last-Translator: Aymeric Augustin \n" -"Language-Team: Swedish (Sweden) (http://www.transifex.com/projects/p/django-debug-toolbar/language/sv_SE/)\n" +"POT-Creation-Date: 2024-08-06 07:12-0500\n" +"PO-Revision-Date: 2010-11-30 00:00+0000\n" +"Last-Translator: Alex Nordlund , 2012-2013\n" +"Language-Team: Swedish (Sweden) (http://app.transifex.com/django-debug-toolbar/django-debug-toolbar/language/sv_SE/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: sv_SE\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: apps.py:11 +#: apps.py:18 msgid "Debug Toolbar" msgstr "" -#: views.py:14 +#: panels/alerts.py:67 +#, python-brace-format msgid "" -"Data for this panel isn't available anymore. Please reload the page and " -"retry." +"Form with id \"{form_id}\" contains file input, but does not have the " +"attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:70 +msgid "" +"Form contains file input, but does not have the attribute " +"enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:73 +#, python-brace-format +msgid "" +"Input element references form with id \"{form_id}\", but the form does not " +"have the attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:77 +msgid "Alerts" msgstr "" -#: panels/cache.py:191 +#: panels/cache.py:168 msgid "Cache" msgstr "Cache" -#: panels/cache.py:196 +#: panels/cache.py:174 #, python-format msgid "%(cache_calls)d call in %(time).2fms" msgid_plural "%(cache_calls)d calls in %(time).2fms" msgstr[0] "" msgstr[1] "" -#: panels/cache.py:204 +#: panels/cache.py:183 #, python-format msgid "Cache calls from %(count)d backend" msgid_plural "Cache calls from %(count)d backends" msgstr[0] "" msgstr[1] "" -#: panels/headers.py:35 +#: panels/headers.py:31 msgid "Headers" msgstr "" -#: panels/logging.py:64 -msgid "Logging" +#: panels/history/panel.py:19 panels/history/panel.py:20 +msgid "History" msgstr "" -#: panels/logging.py:70 -#, python-format -msgid "%(count)s message" -msgid_plural "%(count)s messages" -msgstr[0] "%(count)s meddelande" -msgstr[1] "%(count)s meddelanden" - -#: panels/logging.py:73 -msgid "Log messages" -msgstr "" - -#: panels/profiling.py:127 +#: panels/profiling.py:140 msgid "Profiling" msgstr "Profilering" @@ -74,213 +81,228 @@ msgstr "Profilering" msgid "Intercept redirects" msgstr "" -#: panels/request.py:18 +#: panels/request.py:16 msgid "Request" msgstr "" -#: panels/request.py:35 +#: panels/request.py:38 msgid "" msgstr "" -#: panels/request.py:47 +#: panels/request.py:55 msgid "" msgstr "" -#: panels/settings.py:20 +#: panels/settings.py:17 msgid "Settings" msgstr "Inställningar" -#: panels/settings.py:23 +#: panels/settings.py:20 #, python-format -msgid "Settings from %s" +msgid "Settings from %s" msgstr "" -#: panels/signals.py:45 +#: panels/signals.py:57 #, python-format msgid "%(num_receivers)d receiver of 1 signal" msgid_plural "%(num_receivers)d receivers of 1 signal" msgstr[0] "" msgstr[1] "" -#: panels/signals.py:48 +#: panels/signals.py:62 #, python-format msgid "%(num_receivers)d receiver of %(num_signals)d signals" msgid_plural "%(num_receivers)d receivers of %(num_signals)d signals" msgstr[0] "" msgstr[1] "" -#: panels/signals.py:53 +#: panels/signals.py:67 msgid "Signals" msgstr "Signaler" -#: panels/staticfiles.py:89 +#: panels/sql/panel.py:30 panels/sql/panel.py:41 +msgid "Read uncommitted" +msgstr "" + +#: panels/sql/panel.py:31 panels/sql/panel.py:43 +msgid "Read committed" +msgstr "" + +#: panels/sql/panel.py:32 panels/sql/panel.py:45 +msgid "Repeatable read" +msgstr "" + +#: panels/sql/panel.py:33 panels/sql/panel.py:47 +msgid "Serializable" +msgstr "Variabel" + +#: panels/sql/panel.py:39 +msgid "Autocommit" +msgstr "" + +#: panels/sql/panel.py:61 panels/sql/panel.py:71 +msgid "Idle" +msgstr "" + +#: panels/sql/panel.py:62 panels/sql/panel.py:72 +msgid "Active" +msgstr "Åtgärd" + +#: panels/sql/panel.py:63 panels/sql/panel.py:73 +msgid "In transaction" +msgstr "" + +#: panels/sql/panel.py:64 panels/sql/panel.py:74 +msgid "In error" +msgstr "Felmeddelande" + +#: panels/sql/panel.py:65 panels/sql/panel.py:75 +msgid "Unknown" +msgstr "(okänd)" + +#: panels/sql/panel.py:162 +msgid "SQL" +msgstr "SQL" + +#: panels/sql/panel.py:168 +#, python-format +msgid "%(query_count)d query in %(sql_time).2fms" +msgid_plural "%(query_count)d queries in %(sql_time).2fms" +msgstr[0] "" +msgstr[1] "" + +#: panels/sql/panel.py:180 +#, python-format +msgid "SQL queries from %(count)d connection" +msgid_plural "SQL queries from %(count)d connections" +msgstr[0] "" +msgstr[1] "" + +#: panels/staticfiles.py:82 #, python-format msgid "Static files (%(num_found)s found, %(num_used)s used)" msgstr "" -#: panels/staticfiles.py:107 +#: panels/staticfiles.py:103 msgid "Static files" msgstr "Statiska filer" -#: panels/staticfiles.py:112 +#: panels/staticfiles.py:109 #, python-format msgid "%(num_used)s file used" msgid_plural "%(num_used)s files used" msgstr[0] "" msgstr[1] "" -#: panels/timer.py:23 +#: panels/templates/panel.py:101 +msgid "Templates" +msgstr "Mallar" + +#: panels/templates/panel.py:106 +#, python-format +msgid "Templates (%(num_templates)s rendered)" +msgstr "" + +#: panels/templates/panel.py:195 +msgid "No origin" +msgstr "" + +#: panels/timer.py:27 #, python-format msgid "CPU: %(cum)0.2fms (%(total)0.2fms)" msgstr "" -#: panels/timer.py:28 +#: panels/timer.py:32 #, python-format msgid "Total: %0.2fms" msgstr "" -#: panels/timer.py:34 templates/debug_toolbar/panels/logging.html:7 +#: panels/timer.py:38 templates/debug_toolbar/panels/history.html:9 #: templates/debug_toolbar/panels/sql_explain.html:11 #: templates/debug_toolbar/panels/sql_profile.html:12 #: templates/debug_toolbar/panels/sql_select.html:11 msgid "Time" msgstr "Tid" -#: panels/timer.py:42 +#: panels/timer.py:46 msgid "User CPU time" msgstr "" -#: panels/timer.py:42 +#: panels/timer.py:46 #, python-format msgid "%(utime)0.3f msec" msgstr "" -#: panels/timer.py:43 +#: panels/timer.py:47 msgid "System CPU time" msgstr "" -#: panels/timer.py:43 +#: panels/timer.py:47 #, python-format msgid "%(stime)0.3f msec" msgstr "" -#: panels/timer.py:44 +#: panels/timer.py:48 msgid "Total CPU time" msgstr "" -#: panels/timer.py:44 +#: panels/timer.py:48 #, python-format msgid "%(total)0.3f msec" msgstr "" -#: panels/timer.py:45 +#: panels/timer.py:49 msgid "Elapsed time" msgstr "" -#: panels/timer.py:45 +#: panels/timer.py:49 #, python-format msgid "%(total_time)0.3f msec" msgstr "" -#: panels/timer.py:46 +#: panels/timer.py:51 msgid "Context switches" msgstr "" -#: panels/timer.py:46 +#: panels/timer.py:52 #, python-format msgid "%(vcsw)d voluntary, %(ivcsw)d involuntary" msgstr "" -#: panels/versions.py:25 +#: panels/versions.py:19 msgid "Versions" msgstr "Versioner" -#: panels/sql/panel.py:22 -msgid "Autocommit" -msgstr "" - -#: panels/sql/panel.py:23 -msgid "Read uncommitted" -msgstr "" - -#: panels/sql/panel.py:24 -msgid "Read committed" -msgstr "" - -#: panels/sql/panel.py:25 -msgid "Repeatable read" -msgstr "" - -#: panels/sql/panel.py:26 -msgid "Serializable" -msgstr "Variabel" - -#: panels/sql/panel.py:37 -msgid "Idle" -msgstr "" - -#: panels/sql/panel.py:38 -msgid "Active" -msgstr "Åtgärd" - -#: panels/sql/panel.py:39 -msgid "In transaction" -msgstr "" - -#: panels/sql/panel.py:40 -msgid "In error" -msgstr "Felmeddelande" - -#: panels/sql/panel.py:41 -msgid "Unknown" -msgstr "(okänd)" - -#: panels/sql/panel.py:105 -msgid "SQL" -msgstr "SQL" - -#: panels/templates/panel.py:141 -msgid "Templates" -msgstr "Mallar" - -#: panels/templates/panel.py:146 -#, python-format -msgid "Templates (%(num_templates)s rendered)" -msgstr "" - -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide toolbar" msgstr "" -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide" msgstr "Dölj" -#: templates/debug_toolbar/base.html:25 -msgid "Disable for next and successive requests" +#: templates/debug_toolbar/base.html:25 templates/debug_toolbar/base.html:26 +msgid "Toggle Theme" msgstr "" -#: templates/debug_toolbar/base.html:25 -msgid "Enable for next and successive requests" +#: templates/debug_toolbar/base.html:35 +msgid "Show toolbar" msgstr "" -#: templates/debug_toolbar/base.html:47 -msgid "Show toolbar" +#: templates/debug_toolbar/includes/panel_button.html:4 +msgid "Disable for next and successive requests" msgstr "" -#: templates/debug_toolbar/base.html:53 -msgid "Close" -msgstr "Stäng" +#: templates/debug_toolbar/includes/panel_button.html:4 +msgid "Enable for next and successive requests" +msgstr "" -#: templates/debug_toolbar/redirect.html:8 -msgid "Location:" +#: templates/debug_toolbar/panels/alerts.html:4 +msgid "Alerts found" msgstr "" -#: templates/debug_toolbar/redirect.html:10 -msgid "" -"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." +#: templates/debug_toolbar/panels/alerts.html:11 +msgid "No alerts found" msgstr "" #: templates/debug_toolbar/panels/cache.html:2 @@ -312,7 +334,7 @@ msgid "Calls" msgstr "" #: templates/debug_toolbar/panels/cache.html:43 -#: templates/debug_toolbar/panels/sql.html:20 +#: templates/debug_toolbar/panels/sql.html:36 msgid "Time (ms)" msgstr "Tid (ms)" @@ -347,10 +369,8 @@ msgstr "Nyckel" #: templates/debug_toolbar/panels/headers.html:9 #: templates/debug_toolbar/panels/headers.html:28 #: templates/debug_toolbar/panels/headers.html:49 -#: templates/debug_toolbar/panels/request.html:33 -#: templates/debug_toolbar/panels/request.html:59 -#: templates/debug_toolbar/panels/request.html:85 -#: templates/debug_toolbar/panels/request.html:110 +#: templates/debug_toolbar/panels/history_tr.html:23 +#: templates/debug_toolbar/panels/request_variables.html:12 #: templates/debug_toolbar/panels/settings.html:6 #: templates/debug_toolbar/panels/timer.html:11 msgid "Value" @@ -370,27 +390,33 @@ msgid "" "significant subset is shown below." msgstr "" -#: templates/debug_toolbar/panels/logging.html:6 -msgid "Level" -msgstr "Nivå" - -#: templates/debug_toolbar/panels/logging.html:8 -msgid "Channel" +#: templates/debug_toolbar/panels/history.html:10 +msgid "Method" msgstr "" -#: templates/debug_toolbar/panels/logging.html:9 -msgid "Message" -msgstr "Meddelande" +#: templates/debug_toolbar/panels/history.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:43 +msgid "Path" +msgstr "Sökväg" -#: templates/debug_toolbar/panels/logging.html:10 -#: templates/debug_toolbar/panels/staticfiles.html:45 -msgid "Location" -msgstr "Plats" +#: templates/debug_toolbar/panels/history.html:12 +msgid "Request Variables" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:26 -msgid "No messages logged" +#: templates/debug_toolbar/panels/history.html:13 +msgid "Status" msgstr "" +#: templates/debug_toolbar/panels/history.html:14 +#: templates/debug_toolbar/panels/sql.html:37 +msgid "Action" +msgstr "Åtgärd" + +#: templates/debug_toolbar/panels/history_tr.html:22 +#: templates/debug_toolbar/panels/request_variables.html:11 +msgid "Variable" +msgstr "Variabel" + #: templates/debug_toolbar/panels/profiling.html:5 msgid "Call" msgstr "" @@ -428,38 +454,31 @@ msgstr "" msgid "Cookies" msgstr "" -#: templates/debug_toolbar/panels/request.html:32 -#: templates/debug_toolbar/panels/request.html:58 -#: templates/debug_toolbar/panels/request.html:84 -#: templates/debug_toolbar/panels/request.html:109 -msgid "Variable" -msgstr "Variabel" - -#: templates/debug_toolbar/panels/request.html:46 +#: templates/debug_toolbar/panels/request.html:27 msgid "No cookies" msgstr "" -#: templates/debug_toolbar/panels/request.html:50 +#: templates/debug_toolbar/panels/request.html:31 msgid "Session data" msgstr "" -#: templates/debug_toolbar/panels/request.html:72 +#: templates/debug_toolbar/panels/request.html:34 msgid "No session data" msgstr "" -#: templates/debug_toolbar/panels/request.html:76 +#: templates/debug_toolbar/panels/request.html:38 msgid "GET data" msgstr "" -#: templates/debug_toolbar/panels/request.html:98 +#: templates/debug_toolbar/panels/request.html:41 msgid "No GET data" msgstr "Ingen GET data" -#: templates/debug_toolbar/panels/request.html:102 +#: templates/debug_toolbar/panels/request.html:45 msgid "POST data" msgstr "" -#: templates/debug_toolbar/panels/request.html:123 +#: templates/debug_toolbar/panels/request.html:48 msgid "No POST data" msgstr "Ingen POST data" @@ -472,60 +491,69 @@ msgid "Signal" msgstr "Signal" #: templates/debug_toolbar/panels/signals.html:6 -msgid "Providing" -msgstr "" - -#: templates/debug_toolbar/panels/signals.html:7 msgid "Receivers" msgstr "Mottagare" -#: templates/debug_toolbar/panels/sql.html:7 +#: templates/debug_toolbar/panels/sql.html:6 #, python-format msgid "%(num)s query" msgid_plural "%(num)s queries" msgstr[0] "" msgstr[1] "" -#: templates/debug_toolbar/panels/sql.html:18 +#: templates/debug_toolbar/panels/sql.html:8 +#, python-format +msgid "" +"including %(count)s similar" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:12 +#, python-format +msgid "" +"and %(dupes)s duplicates" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:34 msgid "Query" msgstr "Fråga" -#: templates/debug_toolbar/panels/sql.html:19 +#: templates/debug_toolbar/panels/sql.html:35 #: templates/debug_toolbar/panels/timer.html:36 msgid "Timeline" msgstr "" -#: templates/debug_toolbar/panels/sql.html:21 -msgid "Action" -msgstr "Åtgärd" +#: templates/debug_toolbar/panels/sql.html:52 +#, python-format +msgid "%(count)s similar queries." +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:58 +#, python-format +msgid "Duplicated %(dupes)s times." +msgstr "" -#: templates/debug_toolbar/panels/sql.html:64 +#: templates/debug_toolbar/panels/sql.html:95 msgid "Connection:" msgstr "Anslutning:" -#: templates/debug_toolbar/panels/sql.html:66 +#: templates/debug_toolbar/panels/sql.html:97 msgid "Isolation level:" msgstr "" -#: templates/debug_toolbar/panels/sql.html:69 +#: templates/debug_toolbar/panels/sql.html:100 msgid "Transaction status:" msgstr "" -#: templates/debug_toolbar/panels/sql.html:83 +#: templates/debug_toolbar/panels/sql.html:114 msgid "(unknown)" msgstr "(okänd)" -#: templates/debug_toolbar/panels/sql.html:92 +#: templates/debug_toolbar/panels/sql.html:123 msgid "No SQL queries were recorded during this request." msgstr "" -#: templates/debug_toolbar/panels/sql_explain.html:3 -#: templates/debug_toolbar/panels/sql_profile.html:3 -#: templates/debug_toolbar/panels/sql_select.html:3 -#: templates/debug_toolbar/panels/template_source.html:3 -msgid "Back" -msgstr "Bakåt" - #: templates/debug_toolbar/panels/sql_explain.html:4 msgid "SQL explained" msgstr "" @@ -558,39 +586,39 @@ msgstr "" msgid "Empty set" msgstr "Tomt set" -#: templates/debug_toolbar/panels/staticfiles.html:4 +#: templates/debug_toolbar/panels/staticfiles.html:3 msgid "Static file path" msgid_plural "Static file paths" msgstr[0] "" msgstr[1] "" -#: templates/debug_toolbar/panels/staticfiles.html:8 +#: templates/debug_toolbar/panels/staticfiles.html:7 #, python-format msgid "(prefix %(prefix)s)" msgstr "" -#: templates/debug_toolbar/panels/staticfiles.html:12 -#: templates/debug_toolbar/panels/staticfiles.html:23 -#: templates/debug_toolbar/panels/staticfiles.html:35 +#: templates/debug_toolbar/panels/staticfiles.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:22 +#: templates/debug_toolbar/panels/staticfiles.html:34 #: templates/debug_toolbar/panels/templates.html:10 -#: templates/debug_toolbar/panels/templates.html:28 -#: templates/debug_toolbar/panels/templates.html:43 +#: templates/debug_toolbar/panels/templates.html:30 +#: templates/debug_toolbar/panels/templates.html:47 msgid "None" msgstr "Inget" -#: templates/debug_toolbar/panels/staticfiles.html:15 +#: templates/debug_toolbar/panels/staticfiles.html:14 msgid "Static file app" msgid_plural "Static file apps" msgstr[0] "" msgstr[1] "" -#: templates/debug_toolbar/panels/staticfiles.html:26 +#: templates/debug_toolbar/panels/staticfiles.html:25 msgid "Static file" msgid_plural "Static files" msgstr[0] "" msgstr[1] "Statiska filer" -#: templates/debug_toolbar/panels/staticfiles.html:40 +#: templates/debug_toolbar/panels/staticfiles.html:39 #, python-format msgid "%(payload_count)s file" msgid_plural "%(payload_count)s files" @@ -598,8 +626,8 @@ msgstr[0] "" msgstr[1] "" #: templates/debug_toolbar/panels/staticfiles.html:44 -msgid "Path" -msgstr "Sökväg" +msgid "Location" +msgstr "Plats" #: templates/debug_toolbar/panels/template_source.html:4 msgid "Template source:" @@ -617,12 +645,12 @@ msgid_plural "Templates" msgstr[0] "Mall" msgstr[1] "Mallar" -#: templates/debug_toolbar/panels/templates.html:21 -#: templates/debug_toolbar/panels/templates.html:37 +#: templates/debug_toolbar/panels/templates.html:22 +#: templates/debug_toolbar/panels/templates.html:40 msgid "Toggle context" msgstr "" -#: templates/debug_toolbar/panels/templates.html:31 +#: templates/debug_toolbar/panels/templates.html:33 msgid "Context processor" msgid_plural "Context processors" msgstr[0] "" @@ -648,10 +676,31 @@ msgstr "" msgid "Milliseconds since navigation start (+length)" msgstr "" -#: templates/debug_toolbar/panels/versions.html:5 +#: templates/debug_toolbar/panels/versions.html:10 +msgid "Package" +msgstr "" + +#: templates/debug_toolbar/panels/versions.html:11 msgid "Name" msgstr "Namn" -#: templates/debug_toolbar/panels/versions.html:6 +#: templates/debug_toolbar/panels/versions.html:12 msgid "Version" msgstr "Version" + +#: templates/debug_toolbar/redirect.html:10 +msgid "Location:" +msgstr "" + +#: templates/debug_toolbar/redirect.html:12 +msgid "" +"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." +msgstr "" + +#: views.py:16 +msgid "" +"Data for this panel isn't available anymore. Please reload the page and " +"retry." +msgstr "" diff --git a/debug_toolbar/locale/uk/LC_MESSAGES/django.mo b/debug_toolbar/locale/uk/LC_MESSAGES/django.mo index 6ea928e1b..0c00501e3 100644 Binary files a/debug_toolbar/locale/uk/LC_MESSAGES/django.mo and b/debug_toolbar/locale/uk/LC_MESSAGES/django.mo differ diff --git a/debug_toolbar/locale/uk/LC_MESSAGES/django.po b/debug_toolbar/locale/uk/LC_MESSAGES/django.po index 9a642f694..4d752a52d 100644 --- a/debug_toolbar/locale/uk/LC_MESSAGES/django.po +++ b/debug_toolbar/locale/uk/LC_MESSAGES/django.po @@ -1,323 +1,354 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# -# +# +# # Translators: +# Illia Volochii , 2017 # Sergey Lysach , 2013 msgid "" msgstr "" "Project-Id-Version: Django Debug Toolbar\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-04-25 21:52+0200\n" -"PO-Revision-Date: 2014-04-25 19:53+0000\n" -"Last-Translator: Aymeric Augustin \n" -"Language-Team: Ukrainian (http://www.transifex.com/projects/p/django-debug-toolbar/language/uk/)\n" +"POT-Creation-Date: 2024-08-06 07:12-0500\n" +"PO-Revision-Date: 2010-11-30 00:00+0000\n" +"Last-Translator: Illia Volochii , 2017\n" +"Language-Team: Ukrainian (http://app.transifex.com/django-debug-toolbar/django-debug-toolbar/language/uk/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: uk\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);\n" -#: apps.py:11 +#: apps.py:18 msgid "Debug Toolbar" +msgstr "Панель Інструментів Налагодження" + +#: panels/alerts.py:67 +#, python-brace-format +msgid "" +"Form with id \"{form_id}\" contains file input, but does not have the " +"attribute enctype=\"multipart/form-data\"." msgstr "" -#: views.py:14 +#: panels/alerts.py:70 msgid "" -"Data for this panel isn't available anymore. Please reload the page and " -"retry." +"Form contains file input, but does not have the attribute " +"enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:73 +#, python-brace-format +msgid "" +"Input element references form with id \"{form_id}\", but the form does not " +"have the attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:77 +msgid "Alerts" msgstr "" -#: panels/cache.py:191 +#: panels/cache.py:168 msgid "Cache" msgstr "Кеш" -#: panels/cache.py:196 +#: panels/cache.py:174 #, python-format msgid "%(cache_calls)d call in %(time).2fms" msgid_plural "%(cache_calls)d calls in %(time).2fms" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "%(cache_calls)d виклик за %(time).2f мс" +msgstr[1] "%(cache_calls)d виклики за %(time).2f мс" +msgstr[2] "%(cache_calls)d викликів за %(time).2f мс" +msgstr[3] "%(cache_calls)d викликів за %(time).2f мс" -#: panels/cache.py:204 +#: panels/cache.py:183 #, python-format msgid "Cache calls from %(count)d backend" msgid_plural "Cache calls from %(count)d backends" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "Виклики кешу з %(count)d бекенду" +msgstr[1] "Виклики кешу із %(count)d бекендів" +msgstr[2] "Виклики кешу із %(count)d бекендів" +msgstr[3] "Виклики кешу із %(count)d бекендів" -#: panels/headers.py:35 +#: panels/headers.py:31 msgid "Headers" -msgstr "" - -#: panels/logging.py:64 -msgid "Logging" -msgstr "Логи" +msgstr "Заголовки" -#: panels/logging.py:70 -#, python-format -msgid "%(count)s message" -msgid_plural "%(count)s messages" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" - -#: panels/logging.py:73 -msgid "Log messages" +#: panels/history/panel.py:19 panels/history/panel.py:20 +msgid "History" msgstr "" -#: panels/profiling.py:127 +#: panels/profiling.py:140 msgid "Profiling" -msgstr "" +msgstr "Профілювання" #: panels/redirects.py:17 msgid "Intercept redirects" -msgstr "" +msgstr "Переривати запити" -#: panels/request.py:18 +#: panels/request.py:16 msgid "Request" -msgstr "" +msgstr "Запит" -#: panels/request.py:35 +#: panels/request.py:38 msgid "" -msgstr "" +msgstr "<немає відображення>" -#: panels/request.py:47 +#: panels/request.py:55 msgid "" -msgstr "" +msgstr "<відсутнє>" -#: panels/settings.py:20 +#: panels/settings.py:17 msgid "Settings" msgstr "Налаштування" -#: panels/settings.py:23 +#: panels/settings.py:20 #, python-format -msgid "Settings from %s" +msgid "Settings from %s" msgstr "" -#: panels/signals.py:45 +#: panels/signals.py:57 #, python-format msgid "%(num_receivers)d receiver of 1 signal" msgid_plural "%(num_receivers)d receivers of 1 signal" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "%(num_receivers)d отримувач 1 сигналу" +msgstr[1] "%(num_receivers)d отримувача 1 сигналу" +msgstr[2] "%(num_receivers)d отримувачів 1 сигналу" +msgstr[3] "%(num_receivers)d отримувачів 1 сигналу" -#: panels/signals.py:48 +#: panels/signals.py:62 #, python-format msgid "%(num_receivers)d receiver of %(num_signals)d signals" msgid_plural "%(num_receivers)d receivers of %(num_signals)d signals" +msgstr[0] "%(num_receivers)d отримувач %(num_signals)d сигналів" +msgstr[1] "%(num_receivers)d отримувача %(num_signals)d сигналів" +msgstr[2] "%(num_receivers)d отримувачів %(num_signals)d сигналів" +msgstr[3] "%(num_receivers)d отримувачів %(num_signals)d сигналів" + +#: panels/signals.py:67 +msgid "Signals" +msgstr "Сигнали" + +#: panels/sql/panel.py:30 panels/sql/panel.py:41 +msgid "Read uncommitted" +msgstr "" + +#: panels/sql/panel.py:31 panels/sql/panel.py:43 +msgid "Read committed" +msgstr "" + +#: panels/sql/panel.py:32 panels/sql/panel.py:45 +msgid "Repeatable read" +msgstr "" + +#: panels/sql/panel.py:33 panels/sql/panel.py:47 +msgid "Serializable" +msgstr "" + +#: panels/sql/panel.py:39 +msgid "Autocommit" +msgstr "Автофіксація" + +#: panels/sql/panel.py:61 panels/sql/panel.py:71 +msgid "Idle" +msgstr "" + +#: panels/sql/panel.py:62 panels/sql/panel.py:72 +msgid "Active" +msgstr "" + +#: panels/sql/panel.py:63 panels/sql/panel.py:73 +msgid "In transaction" +msgstr "" + +#: panels/sql/panel.py:64 panels/sql/panel.py:74 +msgid "In error" +msgstr "" + +#: panels/sql/panel.py:65 panels/sql/panel.py:75 +msgid "Unknown" +msgstr "" + +#: panels/sql/panel.py:162 +msgid "SQL" +msgstr "SQL" + +#: panels/sql/panel.py:168 +#, python-format +msgid "%(query_count)d query in %(sql_time).2fms" +msgid_plural "%(query_count)d queries in %(sql_time).2fms" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" -#: panels/signals.py:53 -msgid "Signals" -msgstr "Сигнали" +#: panels/sql/panel.py:180 +#, python-format +msgid "SQL queries from %(count)d connection" +msgid_plural "SQL queries from %(count)d connections" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" -#: panels/staticfiles.py:89 +#: panels/staticfiles.py:82 #, python-format msgid "Static files (%(num_found)s found, %(num_used)s used)" -msgstr "" +msgstr "Статичні файли (знайдено %(num_found)s, використано %(num_used)s)" -#: panels/staticfiles.py:107 +#: panels/staticfiles.py:103 msgid "Static files" -msgstr "" +msgstr "Статичні файли" -#: panels/staticfiles.py:112 +#: panels/staticfiles.py:109 #, python-format msgid "%(num_used)s file used" msgid_plural "%(num_used)s files used" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "Використано %(num_used)s файл" +msgstr[1] "Використано %(num_used)s файли" +msgstr[2] "Використано %(num_used)s файлів" +msgstr[3] "Використано %(num_used)s файлів" + +#: panels/templates/panel.py:101 +msgid "Templates" +msgstr "Шаблони" + +#: panels/templates/panel.py:106 +#, python-format +msgid "Templates (%(num_templates)s rendered)" +msgstr "Шаблони (оброблено %(num_templates)s)" -#: panels/timer.py:23 +#: panels/templates/panel.py:195 +msgid "No origin" +msgstr "Немає походження" + +#: panels/timer.py:27 #, python-format msgid "CPU: %(cum)0.2fms (%(total)0.2fms)" -msgstr "" +msgstr "CPU: %(cum)0.2f мс (%(total)0.2f мс)" -#: panels/timer.py:28 +#: panels/timer.py:32 #, python-format msgid "Total: %0.2fms" -msgstr "" +msgstr "Загалом: %0.2f мс" -#: panels/timer.py:34 templates/debug_toolbar/panels/logging.html:7 +#: panels/timer.py:38 templates/debug_toolbar/panels/history.html:9 #: templates/debug_toolbar/panels/sql_explain.html:11 #: templates/debug_toolbar/panels/sql_profile.html:12 #: templates/debug_toolbar/panels/sql_select.html:11 msgid "Time" msgstr "Час" -#: panels/timer.py:42 +#: panels/timer.py:46 msgid "User CPU time" -msgstr "" +msgstr "Користувацький час CPU" -#: panels/timer.py:42 +#: panels/timer.py:46 #, python-format msgid "%(utime)0.3f msec" -msgstr "" +msgstr "%(utime)0.3f мс" -#: panels/timer.py:43 +#: panels/timer.py:47 msgid "System CPU time" -msgstr "" +msgstr "Системний час CPU" -#: panels/timer.py:43 +#: panels/timer.py:47 #, python-format msgid "%(stime)0.3f msec" -msgstr "" +msgstr "%(stime)0.3f мс" -#: panels/timer.py:44 +#: panels/timer.py:48 msgid "Total CPU time" -msgstr "" +msgstr "Загальний час CPU" -#: panels/timer.py:44 +#: panels/timer.py:48 #, python-format msgid "%(total)0.3f msec" -msgstr "" +msgstr "%(total)0.3f мс" -#: panels/timer.py:45 +#: panels/timer.py:49 msgid "Elapsed time" -msgstr "" +msgstr "Витрачений час" -#: panels/timer.py:45 +#: panels/timer.py:49 #, python-format msgid "%(total_time)0.3f msec" -msgstr "" +msgstr "%(total_time)0.3f мс" -#: panels/timer.py:46 +#: panels/timer.py:51 msgid "Context switches" -msgstr "" +msgstr "Перемикачів контексту" -#: panels/timer.py:46 +#: panels/timer.py:52 #, python-format msgid "%(vcsw)d voluntary, %(ivcsw)d involuntary" -msgstr "" +msgstr "навмисних - %(vcsw)d, мимовільних - %(ivcsw)d" -#: panels/versions.py:25 +#: panels/versions.py:19 msgid "Versions" msgstr "Версії" -#: panels/sql/panel.py:22 -msgid "Autocommit" -msgstr "" - -#: panels/sql/panel.py:23 -msgid "Read uncommitted" -msgstr "" - -#: panels/sql/panel.py:24 -msgid "Read committed" -msgstr "" - -#: panels/sql/panel.py:25 -msgid "Repeatable read" -msgstr "" - -#: panels/sql/panel.py:26 -msgid "Serializable" -msgstr "" - -#: panels/sql/panel.py:37 -msgid "Idle" -msgstr "" - -#: panels/sql/panel.py:38 -msgid "Active" -msgstr "" - -#: panels/sql/panel.py:39 -msgid "In transaction" -msgstr "" - -#: panels/sql/panel.py:40 -msgid "In error" -msgstr "" - -#: panels/sql/panel.py:41 -msgid "Unknown" -msgstr "" - -#: panels/sql/panel.py:105 -msgid "SQL" -msgstr "" - -#: panels/templates/panel.py:141 -msgid "Templates" -msgstr "Шаблони" - -#: panels/templates/panel.py:146 -#, python-format -msgid "Templates (%(num_templates)s rendered)" -msgstr "Шаблони (оброблено %(num_templates)s)" - -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide toolbar" -msgstr "" +msgstr "Сховати панель інструментів" -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide" msgstr "Сховати" -#: templates/debug_toolbar/base.html:25 -msgid "Disable for next and successive requests" -msgstr "" - -#: templates/debug_toolbar/base.html:25 -msgid "Enable for next and successive requests" +#: templates/debug_toolbar/base.html:25 templates/debug_toolbar/base.html:26 +msgid "Toggle Theme" msgstr "" -#: templates/debug_toolbar/base.html:47 +#: templates/debug_toolbar/base.html:35 msgid "Show toolbar" -msgstr "" +msgstr "Показати панель інструментів" + +#: templates/debug_toolbar/includes/panel_button.html:4 +msgid "Disable for next and successive requests" +msgstr "Відключити для наступного і подальших запитів" -#: templates/debug_toolbar/base.html:53 -msgid "Close" -msgstr "Закрити" +#: templates/debug_toolbar/includes/panel_button.html:4 +msgid "Enable for next and successive requests" +msgstr "Включити для наступного і подальших запитів" -#: templates/debug_toolbar/redirect.html:8 -msgid "Location:" +#: templates/debug_toolbar/panels/alerts.html:4 +msgid "Alerts found" msgstr "" -#: templates/debug_toolbar/redirect.html:10 -msgid "" -"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." +#: templates/debug_toolbar/panels/alerts.html:11 +msgid "No alerts found" msgstr "" #: templates/debug_toolbar/panels/cache.html:2 msgid "Summary" -msgstr "" +msgstr "Резюме" #: templates/debug_toolbar/panels/cache.html:6 msgid "Total calls" -msgstr "" +msgstr "Загальна кількість викликів" #: templates/debug_toolbar/panels/cache.html:7 msgid "Total time" -msgstr "" +msgstr "Загальний час" #: templates/debug_toolbar/panels/cache.html:8 msgid "Cache hits" -msgstr "" +msgstr "Кеш-попадання" #: templates/debug_toolbar/panels/cache.html:9 msgid "Cache misses" -msgstr "" +msgstr "Кеш-промахи" #: templates/debug_toolbar/panels/cache.html:21 msgid "Commands" -msgstr "" +msgstr "Команди" #: templates/debug_toolbar/panels/cache.html:39 msgid "Calls" -msgstr "" +msgstr "Виклики" #: templates/debug_toolbar/panels/cache.html:43 -#: templates/debug_toolbar/panels/sql.html:20 +#: templates/debug_toolbar/panels/sql.html:36 msgid "Time (ms)" msgstr "Час (мс)" @@ -328,20 +359,20 @@ msgstr "Тип" #: templates/debug_toolbar/panels/cache.html:45 #: templates/debug_toolbar/panels/request.html:8 msgid "Arguments" -msgstr "" +msgstr "Аргументи" #: templates/debug_toolbar/panels/cache.html:46 #: templates/debug_toolbar/panels/request.html:9 msgid "Keyword arguments" -msgstr "" +msgstr "Іменовані аргументи" #: templates/debug_toolbar/panels/cache.html:47 msgid "Backend" -msgstr "" +msgstr "Бекенд" #: templates/debug_toolbar/panels/headers.html:3 msgid "Request headers" -msgstr "" +msgstr "Заголовки запиту" #: templates/debug_toolbar/panels/headers.html:8 #: templates/debug_toolbar/panels/headers.html:27 @@ -352,10 +383,8 @@ msgstr "Ключ" #: templates/debug_toolbar/panels/headers.html:9 #: templates/debug_toolbar/panels/headers.html:28 #: templates/debug_toolbar/panels/headers.html:49 -#: templates/debug_toolbar/panels/request.html:33 -#: templates/debug_toolbar/panels/request.html:59 -#: templates/debug_toolbar/panels/request.html:85 -#: templates/debug_toolbar/panels/request.html:110 +#: templates/debug_toolbar/panels/history_tr.html:23 +#: templates/debug_toolbar/panels/request_variables.html:12 #: templates/debug_toolbar/panels/settings.html:6 #: templates/debug_toolbar/panels/timer.html:11 msgid "Value" @@ -363,174 +392,183 @@ msgstr "Значення" #: templates/debug_toolbar/panels/headers.html:22 msgid "Response headers" -msgstr "" +msgstr "Заголовки відповіді" #: templates/debug_toolbar/panels/headers.html:41 msgid "WSGI environ" -msgstr "" +msgstr "Середовище WSGI" #: templates/debug_toolbar/panels/headers.html:43 msgid "" "Since the WSGI environ inherits the environment of the server, only a " "significant subset is shown below." +msgstr "Оскільки середовище WSGI успадковує середовище сервера, тут показано лише найважливішу частину." + +#: templates/debug_toolbar/panels/history.html:10 +msgid "Method" msgstr "" -#: templates/debug_toolbar/panels/logging.html:6 -msgid "Level" -msgstr "Рівень" +#: templates/debug_toolbar/panels/history.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:43 +msgid "Path" +msgstr "Шлях" -#: templates/debug_toolbar/panels/logging.html:8 -msgid "Channel" +#: templates/debug_toolbar/panels/history.html:12 +msgid "Request Variables" msgstr "" -#: templates/debug_toolbar/panels/logging.html:9 -msgid "Message" -msgstr "Повідомлення" +#: templates/debug_toolbar/panels/history.html:13 +msgid "Status" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:10 -#: templates/debug_toolbar/panels/staticfiles.html:45 -msgid "Location" -msgstr "Місце" +#: templates/debug_toolbar/panels/history.html:14 +#: templates/debug_toolbar/panels/sql.html:37 +msgid "Action" +msgstr "Подія" -#: templates/debug_toolbar/panels/logging.html:26 -msgid "No messages logged" -msgstr "Повідомлень немає" +#: templates/debug_toolbar/panels/history_tr.html:22 +#: templates/debug_toolbar/panels/request_variables.html:11 +msgid "Variable" +msgstr "Змінна" #: templates/debug_toolbar/panels/profiling.html:5 msgid "Call" -msgstr "" +msgstr "Виклик" #: templates/debug_toolbar/panels/profiling.html:6 msgid "CumTime" -msgstr "" +msgstr "Кумул. час" #: templates/debug_toolbar/panels/profiling.html:7 #: templates/debug_toolbar/panels/profiling.html:9 msgid "Per" -msgstr "" +msgstr "За виклик" #: templates/debug_toolbar/panels/profiling.html:8 msgid "TotTime" -msgstr "" +msgstr "Заг. час" #: templates/debug_toolbar/panels/profiling.html:10 msgid "Count" -msgstr "" +msgstr "Кількість" #: templates/debug_toolbar/panels/request.html:3 msgid "View information" -msgstr "" +msgstr "Інформація про відображення" #: templates/debug_toolbar/panels/request.html:7 msgid "View function" -msgstr "" +msgstr "Функція відображення" #: templates/debug_toolbar/panels/request.html:10 msgid "URL name" -msgstr "" +msgstr "Імʼя URL" #: templates/debug_toolbar/panels/request.html:24 msgid "Cookies" -msgstr "" +msgstr "Куки" -#: templates/debug_toolbar/panels/request.html:32 -#: templates/debug_toolbar/panels/request.html:58 -#: templates/debug_toolbar/panels/request.html:84 -#: templates/debug_toolbar/panels/request.html:109 -msgid "Variable" -msgstr "Змінна" - -#: templates/debug_toolbar/panels/request.html:46 +#: templates/debug_toolbar/panels/request.html:27 msgid "No cookies" -msgstr "" +msgstr "Немає куків" -#: templates/debug_toolbar/panels/request.html:50 +#: templates/debug_toolbar/panels/request.html:31 msgid "Session data" -msgstr "" +msgstr "Дані сесії" -#: templates/debug_toolbar/panels/request.html:72 +#: templates/debug_toolbar/panels/request.html:34 msgid "No session data" -msgstr "" +msgstr "Немає даних сесії" -#: templates/debug_toolbar/panels/request.html:76 +#: templates/debug_toolbar/panels/request.html:38 msgid "GET data" -msgstr "" +msgstr "GET дані" -#: templates/debug_toolbar/panels/request.html:98 +#: templates/debug_toolbar/panels/request.html:41 msgid "No GET data" msgstr "Немає GET даних" -#: templates/debug_toolbar/panels/request.html:102 +#: templates/debug_toolbar/panels/request.html:45 msgid "POST data" -msgstr "" +msgstr "POST дані" -#: templates/debug_toolbar/panels/request.html:123 +#: templates/debug_toolbar/panels/request.html:48 msgid "No POST data" msgstr "Немає POST даних" #: templates/debug_toolbar/panels/settings.html:5 msgid "Setting" -msgstr "" +msgstr "Налаштування" #: templates/debug_toolbar/panels/signals.html:5 msgid "Signal" msgstr "Сигнал" #: templates/debug_toolbar/panels/signals.html:6 -msgid "Providing" -msgstr "" - -#: templates/debug_toolbar/panels/signals.html:7 msgid "Receivers" msgstr "Отримувачі сигнала" -#: templates/debug_toolbar/panels/sql.html:7 +#: templates/debug_toolbar/panels/sql.html:6 #, python-format msgid "%(num)s query" msgid_plural "%(num)s queries" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "%(num)s запит" +msgstr[1] "%(num)s запити" +msgstr[2] "%(num)s запитів" +msgstr[3] "%(num)s запитів" -#: templates/debug_toolbar/panels/sql.html:18 +#: templates/debug_toolbar/panels/sql.html:8 +#, python-format +msgid "" +"including %(count)s similar" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:12 +#, python-format +msgid "" +"and %(dupes)s duplicates" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:34 msgid "Query" msgstr "Запит" -#: templates/debug_toolbar/panels/sql.html:19 +#: templates/debug_toolbar/panels/sql.html:35 #: templates/debug_toolbar/panels/timer.html:36 msgid "Timeline" +msgstr "Лінія часу" + +#: templates/debug_toolbar/panels/sql.html:52 +#, python-format +msgid "%(count)s similar queries." msgstr "" -#: templates/debug_toolbar/panels/sql.html:21 -msgid "Action" -msgstr "Подія" +#: templates/debug_toolbar/panels/sql.html:58 +#, python-format +msgid "Duplicated %(dupes)s times." +msgstr "" -#: templates/debug_toolbar/panels/sql.html:64 +#: templates/debug_toolbar/panels/sql.html:95 msgid "Connection:" -msgstr "" +msgstr "Підключення:" -#: templates/debug_toolbar/panels/sql.html:66 +#: templates/debug_toolbar/panels/sql.html:97 msgid "Isolation level:" -msgstr "" +msgstr "Рівень ізоляції:" -#: templates/debug_toolbar/panels/sql.html:69 +#: templates/debug_toolbar/panels/sql.html:100 msgid "Transaction status:" -msgstr "" +msgstr "Статус транзакції:" -#: templates/debug_toolbar/panels/sql.html:83 +#: templates/debug_toolbar/panels/sql.html:114 msgid "(unknown)" msgstr "" -#: templates/debug_toolbar/panels/sql.html:92 +#: templates/debug_toolbar/panels/sql.html:123 msgid "No SQL queries were recorded during this request." -msgstr "" - -#: templates/debug_toolbar/panels/sql_explain.html:3 -#: templates/debug_toolbar/panels/sql_profile.html:3 -#: templates/debug_toolbar/panels/sql_select.html:3 -#: templates/debug_toolbar/panels/template_source.html:3 -msgid "Back" -msgstr "Назад" +msgstr "Жодного SQL запиту не було записано протягом цього запиту" #: templates/debug_toolbar/panels/sql_explain.html:4 msgid "SQL explained" @@ -540,7 +578,7 @@ msgstr "" #: templates/debug_toolbar/panels/sql_profile.html:10 #: templates/debug_toolbar/panels/sql_select.html:9 msgid "Executed SQL" -msgstr "" +msgstr "Виконаний SQL запит" #: templates/debug_toolbar/panels/sql_explain.html:13 #: templates/debug_toolbar/panels/sql_profile.html:14 @@ -562,65 +600,70 @@ msgstr "" #: templates/debug_toolbar/panels/sql_select.html:36 msgid "Empty set" -msgstr "" +msgstr "Порожня множина" -#: templates/debug_toolbar/panels/staticfiles.html:4 +#: templates/debug_toolbar/panels/staticfiles.html:3 msgid "Static file path" msgid_plural "Static file paths" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "Шлях до статичних файлів" +msgstr[1] "Шляхи до статичних файлів" +msgstr[2] "Шляхи до статичних файлів" +msgstr[3] "Шляхи до статичних файлів" -#: templates/debug_toolbar/panels/staticfiles.html:8 +#: templates/debug_toolbar/panels/staticfiles.html:7 #, python-format msgid "(prefix %(prefix)s)" -msgstr "" +msgstr "(префікс %(prefix)s)" -#: templates/debug_toolbar/panels/staticfiles.html:12 -#: templates/debug_toolbar/panels/staticfiles.html:23 -#: templates/debug_toolbar/panels/staticfiles.html:35 +#: templates/debug_toolbar/panels/staticfiles.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:22 +#: templates/debug_toolbar/panels/staticfiles.html:34 #: templates/debug_toolbar/panels/templates.html:10 -#: templates/debug_toolbar/panels/templates.html:28 -#: templates/debug_toolbar/panels/templates.html:43 +#: templates/debug_toolbar/panels/templates.html:30 +#: templates/debug_toolbar/panels/templates.html:47 msgid "None" -msgstr "" +msgstr "Немає" -#: templates/debug_toolbar/panels/staticfiles.html:15 +#: templates/debug_toolbar/panels/staticfiles.html:14 msgid "Static file app" msgid_plural "Static file apps" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "Застосунок, який використовує статичні файли" +msgstr[1] "Застосунки, які використовують статичні файли" +msgstr[2] "Застосунки, які використовують статичні файли" +msgstr[3] "Застосунки, які використовують статичні файли" -#: templates/debug_toolbar/panels/staticfiles.html:26 +#: templates/debug_toolbar/panels/staticfiles.html:25 msgid "Static file" msgid_plural "Static files" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "Статичний файл" +msgstr[1] "Статичні файли" +msgstr[2] "Статичні файли" +msgstr[3] "Статичні файли" -#: templates/debug_toolbar/panels/staticfiles.html:40 +#: templates/debug_toolbar/panels/staticfiles.html:39 #, python-format msgid "%(payload_count)s file" msgid_plural "%(payload_count)s files" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "%(payload_count)s файл" +msgstr[1] "%(payload_count)s файли" +msgstr[2] "%(payload_count)s файлів" +msgstr[3] "%(payload_count)s файлів" #: templates/debug_toolbar/panels/staticfiles.html:44 -msgid "Path" -msgstr "" +msgid "Location" +msgstr "Місце" #: templates/debug_toolbar/panels/template_source.html:4 msgid "Template source:" -msgstr "" +msgstr "Джерело шаблону:" #: templates/debug_toolbar/panels/templates.html:2 msgid "Template path" msgid_plural "Template paths" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "Шлях до шаблонів" +msgstr[1] "Шляхи до шаблонів" +msgstr[2] "Шляхи до шаблонів" +msgstr[3] "Шляхи до шаблонів" #: templates/debug_toolbar/panels/templates.html:13 msgid "Template" @@ -628,43 +671,66 @@ msgid_plural "Templates" msgstr[0] "Шаблон" msgstr[1] "Шаблони" msgstr[2] "Шаблонів" +msgstr[3] "Шаблонів" -#: templates/debug_toolbar/panels/templates.html:21 -#: templates/debug_toolbar/panels/templates.html:37 +#: templates/debug_toolbar/panels/templates.html:22 +#: templates/debug_toolbar/panels/templates.html:40 msgid "Toggle context" -msgstr "" +msgstr "Контекст" -#: templates/debug_toolbar/panels/templates.html:31 +#: templates/debug_toolbar/panels/templates.html:33 msgid "Context processor" msgid_plural "Context processors" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "Процесор контексту" +msgstr[1] "Процесори контексту" +msgstr[2] "Процесори контексту" +msgstr[3] "Процесори контексту" #: templates/debug_toolbar/panels/timer.html:2 msgid "Resource usage" -msgstr "" +msgstr "Використання ресурсів" #: templates/debug_toolbar/panels/timer.html:10 msgid "Resource" -msgstr "" +msgstr "Ресурс" #: templates/debug_toolbar/panels/timer.html:26 msgid "Browser timing" -msgstr "" +msgstr "Хронометраж браузера" #: templates/debug_toolbar/panels/timer.html:35 msgid "Timing attribute" -msgstr "" +msgstr "Атрибут хронометражу" #: templates/debug_toolbar/panels/timer.html:37 msgid "Milliseconds since navigation start (+length)" +msgstr "Мілісекунд від початку навігації (+тривалість)" + +#: templates/debug_toolbar/panels/versions.html:10 +msgid "Package" msgstr "" -#: templates/debug_toolbar/panels/versions.html:5 +#: templates/debug_toolbar/panels/versions.html:11 msgid "Name" -msgstr "" +msgstr "Імʼя" -#: templates/debug_toolbar/panels/versions.html:6 +#: templates/debug_toolbar/panels/versions.html:12 msgid "Version" msgstr "Версія" + +#: templates/debug_toolbar/redirect.html:10 +msgid "Location:" +msgstr "Місцезнаходження:" + +#: templates/debug_toolbar/redirect.html:12 +msgid "" +"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." +msgstr "Панель Інструментів Налагодження Django перервала перенаправлення до вищевказаного URL задля налагодження перегляду. Ви можете натиснути на посилання вище, щоб продовжити перенаправлення у звичайному режимі." + +#: views.py:16 +msgid "" +"Data for this panel isn't available anymore. Please reload the page and " +"retry." +msgstr "Дані для цієї панелі більше недоступні. Будь ласка, перезавантажте сторінку і спробуйте знову." diff --git a/debug_toolbar/locale/zh_CN/LC_MESSAGES/django.mo b/debug_toolbar/locale/zh_CN/LC_MESSAGES/django.mo index 0dad76948..d0ba24256 100644 Binary files a/debug_toolbar/locale/zh_CN/LC_MESSAGES/django.mo and b/debug_toolbar/locale/zh_CN/LC_MESSAGES/django.mo differ diff --git a/debug_toolbar/locale/zh_CN/LC_MESSAGES/django.po b/debug_toolbar/locale/zh_CN/LC_MESSAGES/django.po index 521d57761..81e5651d0 100644 --- a/debug_toolbar/locale/zh_CN/LC_MESSAGES/django.po +++ b/debug_toolbar/locale/zh_CN/LC_MESSAGES/django.po @@ -1,68 +1,76 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# -# +# +# # Translators: # mozillazg , 2013-2014 msgid "" msgstr "" "Project-Id-Version: Django Debug Toolbar\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-04-25 21:52+0200\n" -"PO-Revision-Date: 2014-04-25 19:53+0000\n" -"Last-Translator: Aymeric Augustin \n" -"Language-Team: Chinese (China) (http://www.transifex.com/projects/p/django-debug-toolbar/language/zh_CN/)\n" +"POT-Creation-Date: 2024-08-06 07:12-0500\n" +"PO-Revision-Date: 2010-11-30 00:00+0000\n" +"Last-Translator: mozillazg , 2013-2014\n" +"Language-Team: Chinese (China) (http://app.transifex.com/django-debug-toolbar/django-debug-toolbar/language/zh_CN/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: zh_CN\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: apps.py:11 +#: apps.py:18 msgid "Debug Toolbar" +msgstr "Debug Toolbar" + +#: panels/alerts.py:67 +#, python-brace-format +msgid "" +"Form with id \"{form_id}\" contains file input, but does not have the " +"attribute enctype=\"multipart/form-data\"." msgstr "" -#: views.py:14 +#: panels/alerts.py:70 msgid "" -"Data for this panel isn't available anymore. Please reload the page and " -"retry." -msgstr "当前面板的数据暂不可用。请刷新页面并重试。" +"Form contains file input, but does not have the attribute " +"enctype=\"multipart/form-data\"." +msgstr "" -#: panels/cache.py:191 +#: panels/alerts.py:73 +#, python-brace-format +msgid "" +"Input element references form with id \"{form_id}\", but the form does not " +"have the attribute enctype=\"multipart/form-data\"." +msgstr "" + +#: panels/alerts.py:77 +msgid "Alerts" +msgstr "" + +#: panels/cache.py:168 msgid "Cache" msgstr "缓存" -#: panels/cache.py:196 +#: panels/cache.py:174 #, python-format msgid "%(cache_calls)d call in %(time).2fms" msgid_plural "%(cache_calls)d calls in %(time).2fms" msgstr[0] "%(time).2f 毫秒内 %(cache_calls)d 次调用" -#: panels/cache.py:204 +#: panels/cache.py:183 #, python-format msgid "Cache calls from %(count)d backend" msgid_plural "Cache calls from %(count)d backends" msgstr[0] "来自 %(count)d 个后端的缓存调用" -#: panels/headers.py:35 +#: panels/headers.py:31 msgid "Headers" msgstr "HTTP 头" -#: panels/logging.py:64 -msgid "Logging" -msgstr "日志" - -#: panels/logging.py:70 -#, python-format -msgid "%(count)s message" -msgid_plural "%(count)s messages" -msgstr[0] "%(count)s 条消息" - -#: panels/logging.py:73 -msgid "Log messages" -msgstr "日志信息" +#: panels/history/panel.py:19 panels/history/panel.py:20 +msgid "History" +msgstr "" -#: panels/profiling.py:127 +#: panels/profiling.py:140 msgid "Profiling" msgstr "性能分析" @@ -70,211 +78,224 @@ msgstr "性能分析" msgid "Intercept redirects" msgstr "拦截重定向" -#: panels/request.py:18 +#: panels/request.py:16 msgid "Request" msgstr "请求" -#: panels/request.py:35 +#: panels/request.py:38 msgid "" msgstr "<没有 view>" -#: panels/request.py:47 +#: panels/request.py:55 msgid "" msgstr "<不可用>" -#: panels/settings.py:20 +#: panels/settings.py:17 msgid "Settings" msgstr "设置" -#: panels/settings.py:23 +#: panels/settings.py:20 #, python-format -msgid "Settings from %s" -msgstr "来自 %s 的设置" +msgid "Settings from %s" +msgstr "" -#: panels/signals.py:45 +#: panels/signals.py:57 #, python-format msgid "%(num_receivers)d receiver of 1 signal" msgid_plural "%(num_receivers)d receivers of 1 signal" msgstr[0] "1个信号 %(num_receivers)d 个接收者" -#: panels/signals.py:48 +#: panels/signals.py:62 #, python-format msgid "%(num_receivers)d receiver of %(num_signals)d signals" msgid_plural "%(num_receivers)d receivers of %(num_signals)d signals" msgstr[0] "%(num_signals)d 个信号 %(num_receivers)d 个接收者" -#: panels/signals.py:53 +#: panels/signals.py:67 msgid "Signals" msgstr "信号" -#: panels/staticfiles.py:89 +#: panels/sql/panel.py:30 panels/sql/panel.py:41 +msgid "Read uncommitted" +msgstr "读取未提交的" + +#: panels/sql/panel.py:31 panels/sql/panel.py:43 +msgid "Read committed" +msgstr "读取已提交的" + +#: panels/sql/panel.py:32 panels/sql/panel.py:45 +msgid "Repeatable read" +msgstr "可重复读取" + +#: panels/sql/panel.py:33 panels/sql/panel.py:47 +msgid "Serializable" +msgstr "可序列化" + +#: panels/sql/panel.py:39 +msgid "Autocommit" +msgstr "自动提交" + +#: panels/sql/panel.py:61 panels/sql/panel.py:71 +msgid "Idle" +msgstr "空闲" + +#: panels/sql/panel.py:62 panels/sql/panel.py:72 +msgid "Active" +msgstr "活跃" + +#: panels/sql/panel.py:63 panels/sql/panel.py:73 +msgid "In transaction" +msgstr "事务" + +#: panels/sql/panel.py:64 panels/sql/panel.py:74 +msgid "In error" +msgstr "错误" + +#: panels/sql/panel.py:65 panels/sql/panel.py:75 +msgid "Unknown" +msgstr "未知" + +#: panels/sql/panel.py:162 +msgid "SQL" +msgstr "SQL" + +#: panels/sql/panel.py:168 +#, python-format +msgid "%(query_count)d query in %(sql_time).2fms" +msgid_plural "%(query_count)d queries in %(sql_time).2fms" +msgstr[0] "" + +#: panels/sql/panel.py:180 +#, python-format +msgid "SQL queries from %(count)d connection" +msgid_plural "SQL queries from %(count)d connections" +msgstr[0] "" + +#: panels/staticfiles.py:82 #, python-format msgid "Static files (%(num_found)s found, %(num_used)s used)" msgstr "静态文件 (%(num_found)s 个找到,%(num_used)s 个被使用)" -#: panels/staticfiles.py:107 +#: panels/staticfiles.py:103 msgid "Static files" msgstr "静态文件" -#: panels/staticfiles.py:112 +#: panels/staticfiles.py:109 #, python-format msgid "%(num_used)s file used" msgid_plural "%(num_used)s files used" msgstr[0] "%(num_used)s 个文件被使用" -#: panels/timer.py:23 +#: panels/templates/panel.py:101 +msgid "Templates" +msgstr "模板" + +#: panels/templates/panel.py:106 +#, python-format +msgid "Templates (%(num_templates)s rendered)" +msgstr "模板 (%(num_templates)s 个被渲染)" + +#: panels/templates/panel.py:195 +msgid "No origin" +msgstr "" + +#: panels/timer.py:27 #, python-format msgid "CPU: %(cum)0.2fms (%(total)0.2fms)" msgstr "CPU: %(cum)0.2f 毫秒 (总耗时: %(total)0.2f 毫秒)" -#: panels/timer.py:28 +#: panels/timer.py:32 #, python-format msgid "Total: %0.2fms" msgstr "总共:%0.2f 毫秒" -#: panels/timer.py:34 templates/debug_toolbar/panels/logging.html:7 +#: panels/timer.py:38 templates/debug_toolbar/panels/history.html:9 #: templates/debug_toolbar/panels/sql_explain.html:11 #: templates/debug_toolbar/panels/sql_profile.html:12 #: templates/debug_toolbar/panels/sql_select.html:11 msgid "Time" msgstr "时间" -#: panels/timer.py:42 +#: panels/timer.py:46 msgid "User CPU time" msgstr "用户 CPU 时间" -#: panels/timer.py:42 +#: panels/timer.py:46 #, python-format msgid "%(utime)0.3f msec" msgstr "%(utime)0.3f 毫秒" -#: panels/timer.py:43 +#: panels/timer.py:47 msgid "System CPU time" msgstr "系统 CPU 时间" -#: panels/timer.py:43 +#: panels/timer.py:47 #, python-format msgid "%(stime)0.3f msec" msgstr "%(stime)0.3f 毫秒" -#: panels/timer.py:44 +#: panels/timer.py:48 msgid "Total CPU time" msgstr "总的 CPU 时间" -#: panels/timer.py:44 +#: panels/timer.py:48 #, python-format msgid "%(total)0.3f msec" msgstr "%(total)0.3f 毫秒" -#: panels/timer.py:45 +#: panels/timer.py:49 msgid "Elapsed time" msgstr "耗时" -#: panels/timer.py:45 +#: panels/timer.py:49 #, python-format msgid "%(total_time)0.3f msec" msgstr "%(total_time)0.3f 毫秒" -#: panels/timer.py:46 +#: panels/timer.py:51 msgid "Context switches" msgstr "上下文切换" -#: panels/timer.py:46 +#: panels/timer.py:52 #, python-format msgid "%(vcsw)d voluntary, %(ivcsw)d involuntary" msgstr "%(vcsw)d 主动, %(ivcsw)d 被动" -#: panels/versions.py:25 +#: panels/versions.py:19 msgid "Versions" msgstr "版本" -#: panels/sql/panel.py:22 -msgid "Autocommit" -msgstr "自动提交" - -#: panels/sql/panel.py:23 -msgid "Read uncommitted" -msgstr "读取未提交的" - -#: panels/sql/panel.py:24 -msgid "Read committed" -msgstr "读取已提交的" - -#: panels/sql/panel.py:25 -msgid "Repeatable read" -msgstr "可重复读取" - -#: panels/sql/panel.py:26 -msgid "Serializable" -msgstr "可序列化" - -#: panels/sql/panel.py:37 -msgid "Idle" -msgstr "空闲" - -#: panels/sql/panel.py:38 -msgid "Active" -msgstr "活跃" - -#: panels/sql/panel.py:39 -msgid "In transaction" -msgstr "事务" - -#: panels/sql/panel.py:40 -msgid "In error" -msgstr "错误" - -#: panels/sql/panel.py:41 -msgid "Unknown" -msgstr "未知" - -#: panels/sql/panel.py:105 -msgid "SQL" -msgstr "SQL" - -#: panels/templates/panel.py:141 -msgid "Templates" -msgstr "模板" - -#: panels/templates/panel.py:146 -#, python-format -msgid "Templates (%(num_templates)s rendered)" -msgstr "模板 (%(num_templates)s 个被渲染)" - -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide toolbar" msgstr "隐藏工具栏" -#: templates/debug_toolbar/base.html:19 +#: templates/debug_toolbar/base.html:23 msgid "Hide" msgstr "隐藏" -#: templates/debug_toolbar/base.html:25 +#: templates/debug_toolbar/base.html:25 templates/debug_toolbar/base.html:26 +msgid "Toggle Theme" +msgstr "" + +#: templates/debug_toolbar/base.html:35 +msgid "Show toolbar" +msgstr "显示工具栏" + +#: templates/debug_toolbar/includes/panel_button.html:4 msgid "Disable for next and successive requests" msgstr "针对下一个连续的请求禁用该功能" -#: templates/debug_toolbar/base.html:25 +#: templates/debug_toolbar/includes/panel_button.html:4 msgid "Enable for next and successive requests" msgstr "针对下一个连续的请求启用该功能" -#: templates/debug_toolbar/base.html:47 -msgid "Show toolbar" -msgstr "显示工具栏" - -#: templates/debug_toolbar/base.html:53 -msgid "Close" -msgstr "关闭" - -#: templates/debug_toolbar/redirect.html:8 -msgid "Location:" -msgstr "位置:" +#: templates/debug_toolbar/panels/alerts.html:4 +msgid "Alerts found" +msgstr "" -#: templates/debug_toolbar/redirect.html:10 -msgid "" -"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." -msgstr "Django Debug Toolbar 为了调试目的拦截了一个重定向到上面 URL 的请求。 您可以点击上面的链接继续执行重定向操作。" +#: templates/debug_toolbar/panels/alerts.html:11 +msgid "No alerts found" +msgstr "" #: templates/debug_toolbar/panels/cache.html:2 msgid "Summary" @@ -305,7 +326,7 @@ msgid "Calls" msgstr "调用" #: templates/debug_toolbar/panels/cache.html:43 -#: templates/debug_toolbar/panels/sql.html:20 +#: templates/debug_toolbar/panels/sql.html:36 msgid "Time (ms)" msgstr "时间(毫秒)" @@ -340,10 +361,8 @@ msgstr "键" #: templates/debug_toolbar/panels/headers.html:9 #: templates/debug_toolbar/panels/headers.html:28 #: templates/debug_toolbar/panels/headers.html:49 -#: templates/debug_toolbar/panels/request.html:33 -#: templates/debug_toolbar/panels/request.html:59 -#: templates/debug_toolbar/panels/request.html:85 -#: templates/debug_toolbar/panels/request.html:110 +#: templates/debug_toolbar/panels/history_tr.html:23 +#: templates/debug_toolbar/panels/request_variables.html:12 #: templates/debug_toolbar/panels/settings.html:6 #: templates/debug_toolbar/panels/timer.html:11 msgid "Value" @@ -363,26 +382,32 @@ msgid "" "significant subset is shown below." msgstr "由于 WSGI 的环境变量继承自 server,所以下面只显示了一些重要的子集。" -#: templates/debug_toolbar/panels/logging.html:6 -msgid "Level" -msgstr "级别" +#: templates/debug_toolbar/panels/history.html:10 +msgid "Method" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:8 -msgid "Channel" -msgstr "频道" +#: templates/debug_toolbar/panels/history.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:43 +msgid "Path" +msgstr "路径" -#: templates/debug_toolbar/panels/logging.html:9 -msgid "Message" -msgstr "消息" +#: templates/debug_toolbar/panels/history.html:12 +msgid "Request Variables" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:10 -#: templates/debug_toolbar/panels/staticfiles.html:45 -msgid "Location" -msgstr "位置" +#: templates/debug_toolbar/panels/history.html:13 +msgid "Status" +msgstr "" -#: templates/debug_toolbar/panels/logging.html:26 -msgid "No messages logged" -msgstr "没有消息被记录" +#: templates/debug_toolbar/panels/history.html:14 +#: templates/debug_toolbar/panels/sql.html:37 +msgid "Action" +msgstr "功能" + +#: templates/debug_toolbar/panels/history_tr.html:22 +#: templates/debug_toolbar/panels/request_variables.html:11 +msgid "Variable" +msgstr "变量" #: templates/debug_toolbar/panels/profiling.html:5 msgid "Call" @@ -421,38 +446,31 @@ msgstr "URL 名称" msgid "Cookies" msgstr "Cookies" -#: templates/debug_toolbar/panels/request.html:32 -#: templates/debug_toolbar/panels/request.html:58 -#: templates/debug_toolbar/panels/request.html:84 -#: templates/debug_toolbar/panels/request.html:109 -msgid "Variable" -msgstr "变量" - -#: templates/debug_toolbar/panels/request.html:46 +#: templates/debug_toolbar/panels/request.html:27 msgid "No cookies" msgstr "没有 cookies" -#: templates/debug_toolbar/panels/request.html:50 +#: templates/debug_toolbar/panels/request.html:31 msgid "Session data" msgstr "Session 数据" -#: templates/debug_toolbar/panels/request.html:72 +#: templates/debug_toolbar/panels/request.html:34 msgid "No session data" msgstr "没有 session 数据" -#: templates/debug_toolbar/panels/request.html:76 +#: templates/debug_toolbar/panels/request.html:38 msgid "GET data" msgstr "GET 请求数据" -#: templates/debug_toolbar/panels/request.html:98 +#: templates/debug_toolbar/panels/request.html:41 msgid "No GET data" msgstr "没有 GET 请求数据" -#: templates/debug_toolbar/panels/request.html:102 +#: templates/debug_toolbar/panels/request.html:45 msgid "POST data" msgstr "POST 请求数据" -#: templates/debug_toolbar/panels/request.html:123 +#: templates/debug_toolbar/panels/request.html:48 msgid "No POST data" msgstr "没有 POST 请求数据" @@ -465,59 +483,68 @@ msgid "Signal" msgstr "信号" #: templates/debug_toolbar/panels/signals.html:6 -msgid "Providing" -msgstr "提供" - -#: templates/debug_toolbar/panels/signals.html:7 msgid "Receivers" msgstr "接收者" -#: templates/debug_toolbar/panels/sql.html:7 +#: templates/debug_toolbar/panels/sql.html:6 #, python-format msgid "%(num)s query" msgid_plural "%(num)s queries" msgstr[0] "%(num)s 个查询" -#: templates/debug_toolbar/panels/sql.html:18 +#: templates/debug_toolbar/panels/sql.html:8 +#, python-format +msgid "" +"including %(count)s similar" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:12 +#, python-format +msgid "" +"and %(dupes)s duplicates" +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:34 msgid "Query" msgstr "查询" -#: templates/debug_toolbar/panels/sql.html:19 +#: templates/debug_toolbar/panels/sql.html:35 #: templates/debug_toolbar/panels/timer.html:36 msgid "Timeline" msgstr "时间线" -#: templates/debug_toolbar/panels/sql.html:21 -msgid "Action" -msgstr "功能" +#: templates/debug_toolbar/panels/sql.html:52 +#, python-format +msgid "%(count)s similar queries." +msgstr "" + +#: templates/debug_toolbar/panels/sql.html:58 +#, python-format +msgid "Duplicated %(dupes)s times." +msgstr "" -#: templates/debug_toolbar/panels/sql.html:64 +#: templates/debug_toolbar/panels/sql.html:95 msgid "Connection:" msgstr "连接:" -#: templates/debug_toolbar/panels/sql.html:66 +#: templates/debug_toolbar/panels/sql.html:97 msgid "Isolation level:" msgstr "隔离级别" -#: templates/debug_toolbar/panels/sql.html:69 +#: templates/debug_toolbar/panels/sql.html:100 msgid "Transaction status:" msgstr "事务状态:" -#: templates/debug_toolbar/panels/sql.html:83 +#: templates/debug_toolbar/panels/sql.html:114 msgid "(unknown)" msgstr "(未知)" -#: templates/debug_toolbar/panels/sql.html:92 +#: templates/debug_toolbar/panels/sql.html:123 msgid "No SQL queries were recorded during this request." msgstr "在处理这个请求期间没有记录到 SQL 查询。" -#: templates/debug_toolbar/panels/sql_explain.html:3 -#: templates/debug_toolbar/panels/sql_profile.html:3 -#: templates/debug_toolbar/panels/sql_select.html:3 -#: templates/debug_toolbar/panels/template_source.html:3 -msgid "Back" -msgstr "返回" - #: templates/debug_toolbar/panels/sql_explain.html:4 msgid "SQL explained" msgstr "SQL explain 分析" @@ -550,44 +577,44 @@ msgstr "选中的 SQL 语句" msgid "Empty set" msgstr "空集合" -#: templates/debug_toolbar/panels/staticfiles.html:4 +#: templates/debug_toolbar/panels/staticfiles.html:3 msgid "Static file path" msgid_plural "Static file paths" msgstr[0] "静态文件路径" -#: templates/debug_toolbar/panels/staticfiles.html:8 +#: templates/debug_toolbar/panels/staticfiles.html:7 #, python-format msgid "(prefix %(prefix)s)" msgstr "(前缀 %(prefix)s)" -#: templates/debug_toolbar/panels/staticfiles.html:12 -#: templates/debug_toolbar/panels/staticfiles.html:23 -#: templates/debug_toolbar/panels/staticfiles.html:35 +#: templates/debug_toolbar/panels/staticfiles.html:11 +#: templates/debug_toolbar/panels/staticfiles.html:22 +#: templates/debug_toolbar/panels/staticfiles.html:34 #: templates/debug_toolbar/panels/templates.html:10 -#: templates/debug_toolbar/panels/templates.html:28 -#: templates/debug_toolbar/panels/templates.html:43 +#: templates/debug_toolbar/panels/templates.html:30 +#: templates/debug_toolbar/panels/templates.html:47 msgid "None" msgstr "空" -#: templates/debug_toolbar/panels/staticfiles.html:15 +#: templates/debug_toolbar/panels/staticfiles.html:14 msgid "Static file app" msgid_plural "Static file apps" msgstr[0] "包含静态文件的应用" -#: templates/debug_toolbar/panels/staticfiles.html:26 +#: templates/debug_toolbar/panels/staticfiles.html:25 msgid "Static file" msgid_plural "Static files" msgstr[0] "静态文件" -#: templates/debug_toolbar/panels/staticfiles.html:40 +#: templates/debug_toolbar/panels/staticfiles.html:39 #, python-format msgid "%(payload_count)s file" msgid_plural "%(payload_count)s files" msgstr[0] "%(payload_count)s 个文件" #: templates/debug_toolbar/panels/staticfiles.html:44 -msgid "Path" -msgstr "路径" +msgid "Location" +msgstr "位置" #: templates/debug_toolbar/panels/template_source.html:4 msgid "Template source:" @@ -603,12 +630,12 @@ msgid "Template" msgid_plural "Templates" msgstr[0] "模板" -#: templates/debug_toolbar/panels/templates.html:21 -#: templates/debug_toolbar/panels/templates.html:37 +#: templates/debug_toolbar/panels/templates.html:22 +#: templates/debug_toolbar/panels/templates.html:40 msgid "Toggle context" msgstr "切换上下文" -#: templates/debug_toolbar/panels/templates.html:31 +#: templates/debug_toolbar/panels/templates.html:33 msgid "Context processor" msgid_plural "Context processors" msgstr[0] "Context processors" @@ -633,10 +660,31 @@ msgstr "计时属性" msgid "Milliseconds since navigation start (+length)" msgstr "导航开始后的毫秒 (+长度)" -#: templates/debug_toolbar/panels/versions.html:5 +#: templates/debug_toolbar/panels/versions.html:10 +msgid "Package" +msgstr "" + +#: templates/debug_toolbar/panels/versions.html:11 msgid "Name" msgstr "名称" -#: templates/debug_toolbar/panels/versions.html:6 +#: templates/debug_toolbar/panels/versions.html:12 msgid "Version" msgstr "版本" + +#: templates/debug_toolbar/redirect.html:10 +msgid "Location:" +msgstr "位置:" + +#: templates/debug_toolbar/redirect.html:12 +msgid "" +"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." +msgstr "Django Debug Toolbar 为了调试目的拦截了一个重定向到上面 URL 的请求。 您可以点击上面的链接继续执行重定向操作。" + +#: views.py:16 +msgid "" +"Data for this panel isn't available anymore. Please reload the page and " +"retry." +msgstr "当前面板的数据暂不可用。请刷新页面并重试。" diff --git a/debug_toolbar/management/commands/debugsqlshell.py b/debug_toolbar/management/commands/debugsqlshell.py index 78e09e27d..b80577232 100644 --- a/debug_toolbar/management/commands/debugsqlshell.py +++ b/debug_toolbar/management/commands/debugsqlshell.py @@ -1,11 +1,10 @@ -from time import time +from time import perf_counter -import django import sqlparse -from django.core.management.commands.shell import Command # noqa +from django.core.management.commands.shell import Command from django.db import connection -if connection.vendor == "postgresql" and django.VERSION >= (3, 0, 0): +if connection.vendor == "postgresql": from django.db.backends.postgresql import base as base_module else: from django.db.backends import utils as base_module @@ -13,17 +12,22 @@ # 'debugsqlshell' is the same as the 'shell'. +# Command is required to exist to be loaded via +# django.core.managementload_command_class +__all__ = ["Command", "PrintQueryWrapper"] + + class PrintQueryWrapper(base_module.CursorDebugWrapper): def execute(self, sql, params=()): - start_time = time() + start_time = perf_counter() try: return self.cursor.execute(sql, params) finally: raw_sql = self.db.ops.last_executed_query(self.cursor, sql, params) - end_time = time() + end_time = perf_counter() duration = (end_time - start_time) * 1000 formatted_sql = sqlparse.format(raw_sql, reindent=True) - print("{} [{:.2f}ms]".format(formatted_sql, duration)) + print(f"{formatted_sql} [{duration:.2f}ms]") base_module.CursorDebugWrapper = PrintQueryWrapper diff --git a/debug_toolbar/middleware.py b/debug_toolbar/middleware.py index 69100b20d..598ff3eef 100644 --- a/debug_toolbar/middleware.py +++ b/debug_toolbar/middleware.py @@ -3,26 +3,61 @@ """ import re -from functools import lru_cache - +import socket +from functools import cache + +from asgiref.sync import ( + async_to_sync, + iscoroutinefunction, + markcoroutinefunction, + sync_to_async, +) from django.conf import settings from django.utils.module_loading import import_string from debug_toolbar import settings as dt_settings from debug_toolbar.toolbar import DebugToolbar - -_HTML_TYPES = ("text/html", "application/xhtml+xml") +from debug_toolbar.utils import clear_stack_trace_caches, is_processable_html_response def show_toolbar(request): """ Default function to determine whether to show the toolbar on a given page. """ - return settings.DEBUG and request.META.get("REMOTE_ADDR") in settings.INTERNAL_IPS - + if not settings.DEBUG: + return False + + # Test: settings + if request.META.get("REMOTE_ADDR") in settings.INTERNAL_IPS: + return True + + # Test: Docker + try: + # This is a hack for docker installations. It attempts to look + # up the IP address of the docker host. + # This is not guaranteed to work. + docker_ip = ( + # Convert the last segment of the IP address to be .1 + ".".join(socket.gethostbyname("host.docker.internal").rsplit(".")[:-1]) + + ".1" + ) + if request.META.get("REMOTE_ADDR") == docker_ip: + return True + except socket.gaierror: + # It's fine if the lookup errored since they may not be using docker + pass + + # No test passed + return False + + +@cache +def show_toolbar_func_or_path(): + """ + Fetch the show toolbar callback from settings -@lru_cache() -def get_show_toolbar(): + Cached to avoid importing multiple times. + """ # If SHOW_TOOLBAR_CALLBACK is a string, which is the recommended # setup, resolve it to the corresponding callable. func_or_path = dt_settings.get_config()["SHOW_TOOLBAR_CALLBACK"] @@ -32,23 +67,53 @@ def get_show_toolbar(): return func_or_path +def get_show_toolbar(async_mode): + """ + Get the callback function to show the toolbar. + + Will wrap the function with sync_to_async or + async_to_sync depending on the status of async_mode + and whether the underlying function is a coroutine. + """ + show_toolbar = show_toolbar_func_or_path() + is_coroutine = iscoroutinefunction(show_toolbar) + if is_coroutine and not async_mode: + show_toolbar = async_to_sync(show_toolbar) + elif not is_coroutine and async_mode: + show_toolbar = sync_to_async(show_toolbar) + return show_toolbar + + class DebugToolbarMiddleware: """ Middleware to set up Debug Toolbar on incoming request and render toolbar on outgoing response. """ + sync_capable = True + async_capable = True + def __init__(self, get_response): self.get_response = get_response + # If get_response is a coroutine function, turns us into async mode so + # a thread is not consumed during a whole request. + self.async_mode = iscoroutinefunction(self.get_response) + + if self.async_mode: + # Mark the class as async-capable, but do the actual switch inside + # __call__ to avoid swapping out dunder methods. + markcoroutinefunction(self) def __call__(self, request): # Decide whether the toolbar is active for this request. - show_toolbar = get_show_toolbar() - if not show_toolbar(request) or request.path.startswith("/__debug__/"): - return self.get_response(request) + if self.async_mode: + return self.__acall__(request) + # Decide whether the toolbar is active for this request. + show_toolbar = get_show_toolbar(async_mode=self.async_mode) + if not show_toolbar(request) or DebugToolbar.is_toolbar_request(request): + return self.get_response(request) toolbar = DebugToolbar(request, self.get_response) - # Activate instrumentation ie. monkey-patch. for panel in toolbar.enabled_panels: panel.enable_instrumentation() @@ -56,31 +121,61 @@ def __call__(self, request): # Run panels like Django middleware. response = toolbar.process_request(request) finally: + clear_stack_trace_caches() + # Deactivate instrumentation ie. monkey-unpatch. This must run + # regardless of the response. Keep 'return' clauses below. + for panel in reversed(toolbar.enabled_panels): + panel.disable_instrumentation() + + return self._postprocess(request, response, toolbar) + + async def __acall__(self, request): + # Decide whether the toolbar is active for this request. + show_toolbar = get_show_toolbar(async_mode=self.async_mode) + + if not await show_toolbar(request) or DebugToolbar.is_toolbar_request(request): + response = await self.get_response(request) + return response + + toolbar = DebugToolbar(request, self.get_response) + + # Activate instrumentation ie. monkey-patch. + for panel in toolbar.enabled_panels: + if hasattr(panel, "aenable_instrumentation"): + await panel.aenable_instrumentation() + else: + panel.enable_instrumentation() + try: + # Run panels like Django middleware. + response = await toolbar.process_request(request) + finally: + clear_stack_trace_caches() # Deactivate instrumentation ie. monkey-unpatch. This must run # regardless of the response. Keep 'return' clauses below. for panel in reversed(toolbar.enabled_panels): panel.disable_instrumentation() + return self._postprocess(request, response, toolbar) + + def _postprocess(self, request, response, toolbar): + """ + Post-process the response. + """ # Generate the stats for all requests when the toolbar is being shown, # but not necessarily inserted. for panel in reversed(toolbar.enabled_panels): panel.generate_stats(request, response) panel.generate_server_timing(request, response) - response = self.generate_server_timing_header(response, toolbar.enabled_panels) - # Always render the toolbar for the history panel, even if it is not # included in the response. rendered = toolbar.render_toolbar() + for header, value in self.get_headers(request, toolbar.enabled_panels).items(): + response.headers[header] = value + # Check for responses where the toolbar can't be inserted. - content_encoding = response.get("Content-Encoding", "") - content_type = response.get("Content-Type", "").split(";")[0] - if ( - getattr(response, "streaming", False) - or "gzip" in content_encoding - or content_type not in _HTML_TYPES - ): + if not is_processable_html_response(response): return response # Insert the toolbar in the response. @@ -96,22 +191,12 @@ def __call__(self, request): return response @staticmethod - def generate_server_timing_header(response, panels): - data = [] - + def get_headers(request, panels): + headers = {} for panel in panels: - stats = panel.get_server_timing_stats() - if not stats: - continue - - for key, record in stats.items(): - # example: `SQLPanel_sql_time;dur=0;desc="SQL 0 queries"` - data.append( - '{}_{};dur={};desc="{}"'.format( - panel.panel_id, key, record.get("value"), record.get("title") - ) - ) - - if data: - response["Server-Timing"] = ", ".join(data) - return response + for header, value in panel.get_headers(request).items(): + if header in headers: + headers[header] += f", {value}" + else: + headers[header] = value + return headers diff --git a/debug_toolbar/panels/__init__.py b/debug_toolbar/panels/__init__.py index ec3445c1e..217708ec2 100644 --- a/debug_toolbar/panels/__init__.py +++ b/debug_toolbar/panels/__init__.py @@ -1,3 +1,4 @@ +from django.core.handlers.asgi import ASGIRequest from django.template.loader import render_to_string from debug_toolbar import settings as dt_settings @@ -9,6 +10,8 @@ class Panel: Base class for panels. """ + is_async = False + def __init__(self, toolbar, get_response): self.toolbar = toolbar self.get_response = get_response @@ -20,7 +23,16 @@ def panel_id(self): return self.__class__.__name__ @property - def enabled(self): + def enabled(self) -> bool: + # check if the panel is async compatible + if not self.is_async and isinstance(self.toolbar.request, ASGIRequest): + return False + + # The user's cookies should override the default value + cookie_value = self.toolbar.request.COOKIES.get("djdt" + self.panel_id) + if cookie_value is not None: + return cookie_value == "on" + # Check to see if settings has a default value for it disabled_panels = dt_settings.get_config()["DISABLE_PANELS"] panel_path = get_name_from_obj(self) @@ -28,16 +40,10 @@ def enabled(self): # panel module, but can be disabled without panel in the path. # For that reason, replace .panel. in the path and check for that # value in the disabled panels as well. - disable_panel = ( - panel_path in disabled_panels - or panel_path.replace(".panel.", ".") in disabled_panels + return ( + panel_path not in disabled_panels + and panel_path.replace(".panel.", ".") not in disabled_panels ) - if disable_panel: - default = "off" - else: - default = "on" - # The user's cookies should override the default value - return self.toolbar.request.COOKIES.get("djdt" + self.panel_id, default) == "on" # Titles and content @@ -87,7 +93,7 @@ def template(self): Template used to render :attr:`content`. Mandatory, unless the panel sets :attr:`has_content` to ``False`` or - overrides `attr`:content`. + overrides :attr:`content`. """ raise NotImplementedError @@ -107,9 +113,25 @@ def content(self): def scripts(self): """ Scripts used by the HTML content of the panel when it's displayed. + + When a panel is rendered on the frontend, the ``djdt.panel.render`` + JavaScript event will be dispatched. The scripts can listen for + this event to support dynamic functionality. """ return [] + # Panel early initialization + + @classmethod + def ready(cls): + """ + Perform early initialization for the panel. + + This should only include initialization or instrumentation that needs to + be done unconditionally for the panel regardless of whether it is + enabled for a particular request. It should be idempotent. + """ + # URLs for panel-specific views @classmethod @@ -132,6 +154,9 @@ def enable_instrumentation(self): Unless the toolbar or this panel is disabled, this method will be called early in ``DebugToolbarMiddleware``. It should be idempotent. + + Add the ``aenable_instrumentation`` method to a panel subclass + to support async logic for instrumentation. """ def disable_instrumentation(self): @@ -188,16 +213,41 @@ def process_request(self, request): """ return self.get_response(request) - def generate_stats(self, request, response): + def get_headers(self, request): """ + Get headers the panel needs to set. + Called after :meth:`process_request - `, but may not be executed - on every request. This will only be called if the toolbar will be - inserted into the request. + ` and + :meth:`process_request` + + Header values will be appended if multiple panels need to set it. + + By default it sets the Server-Timing header. + + Return dict of headers to be appended. + """ + headers = {} + stats = self.get_server_timing_stats() + if stats: + headers["Server-Timing"] = ", ".join( + # example: `SQLPanel_sql_time;dur=0;desc="SQL 0 queries"` + '{}_{};dur={};desc="{}"'.format( + self.panel_id, key, record.get("value"), record.get("title") + ) + for key, record in stats.items() + ) + return headers + def generate_stats(self, request, response): + """ Write panel logic related to the response there. Post-process data gathered while the view executed. Save data with :meth:`record_stats`. + Called after :meth:`process_request + `. + + Does not return a value. """ @@ -219,6 +269,6 @@ def run_checks(cls): This will be called as a part of the Django checks system when the application is being setup. - Return a list of :class: `django.core.checks.CheckMessage` instances. + Return a list of :class:`django.core.checks.CheckMessage` instances. """ return [] diff --git a/debug_toolbar/panels/alerts.py b/debug_toolbar/panels/alerts.py new file mode 100644 index 000000000..c8e59002c --- /dev/null +++ b/debug_toolbar/panels/alerts.py @@ -0,0 +1,153 @@ +from html.parser import HTMLParser + +from django.utils.translation import gettext_lazy as _ + +from debug_toolbar.panels import Panel +from debug_toolbar.utils import is_processable_html_response + + +class FormParser(HTMLParser): + """ + HTML form parser, used to check for invalid configurations of forms that + take file inputs. + """ + + def __init__(self): + super().__init__() + self.in_form = False + self.current_form = {} + self.forms = [] + self.form_ids = [] + self.referenced_file_inputs = [] + + def handle_starttag(self, tag, attrs): + attrs = dict(attrs) + if tag == "form": + self.in_form = True + form_id = attrs.get("id") + if form_id: + self.form_ids.append(form_id) + self.current_form = { + "file_form": False, + "form_attrs": attrs, + "submit_element_attrs": [], + } + elif ( + self.in_form + and tag == "input" + and attrs.get("type") == "file" + and (not attrs.get("form") or attrs.get("form") == "") + ): + self.current_form["file_form"] = True + elif ( + self.in_form + and ( + (tag == "input" and attrs.get("type") in {"submit", "image"}) + or tag == "button" + ) + and (not attrs.get("form") or attrs.get("form") == "") + ): + self.current_form["submit_element_attrs"].append(attrs) + elif tag == "input" and attrs.get("form"): + self.referenced_file_inputs.append(attrs) + + def handle_endtag(self, tag): + if tag == "form" and self.in_form: + self.forms.append(self.current_form) + self.in_form = False + + +class AlertsPanel(Panel): + """ + A panel to alert users to issues. + """ + + messages = { + "form_id_missing_enctype": _( + 'Form with id "{form_id}" contains file input, but does not have the attribute enctype="multipart/form-data".' + ), + "form_missing_enctype": _( + 'Form contains file input, but does not have the attribute enctype="multipart/form-data".' + ), + "input_refs_form_missing_enctype": _( + 'Input element references form with id "{form_id}", but the form does not have the attribute enctype="multipart/form-data".' + ), + } + + title = _("Alerts") + + is_async = True + + template = "debug_toolbar/panels/alerts.html" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.alerts = [] + + @property + def nav_subtitle(self): + if alerts := self.get_stats().get("alerts"): + alert_text = "alert" if len(alerts) == 1 else "alerts" + return f"{len(alerts)} {alert_text}" + else: + return "" + + def add_alert(self, alert): + self.alerts.append(alert) + + def check_invalid_file_form_configuration(self, html_content): + """ + Inspects HTML content for a form that includes a file input but does + not have the encoding type set to multipart/form-data, and warns the + user if so. + """ + parser = FormParser() + parser.feed(html_content) + + # Check for file inputs directly inside a form that do not reference + # any form through the form attribute + for form in parser.forms: + if ( + form["file_form"] + and form["form_attrs"].get("enctype") != "multipart/form-data" + and not any( + elem.get("formenctype") == "multipart/form-data" + for elem in form["submit_element_attrs"] + ) + ): + if form_id := form["form_attrs"].get("id"): + alert = self.messages["form_id_missing_enctype"].format( + form_id=form_id + ) + else: + alert = self.messages["form_missing_enctype"] + self.add_alert({"alert": alert}) + + # Check for file inputs that reference a form + form_attrs_by_id = { + form["form_attrs"].get("id"): form["form_attrs"] for form in parser.forms + } + + for attrs in parser.referenced_file_inputs: + form_id = attrs.get("form") + if form_id and attrs.get("type") == "file": + form_attrs = form_attrs_by_id.get(form_id) + if form_attrs and form_attrs.get("enctype") != "multipart/form-data": + alert = self.messages["input_refs_form_missing_enctype"].format( + form_id=form_id + ) + self.add_alert({"alert": alert}) + + return self.alerts + + def generate_stats(self, request, response): + if not is_processable_html_response(response): + return + + html_content = response.content.decode(response.charset) + self.check_invalid_file_form_configuration(html_content) + + # Further alert checks can go here + + # Write all alerts to record_stats + self.record_stats({"alerts": self.alerts}) diff --git a/debug_toolbar/panels/cache.py b/debug_toolbar/panels/cache.py index bd65d7d1c..1b15b446f 100644 --- a/debug_toolbar/panels/cache.py +++ b/debug_toolbar/panels/cache.py @@ -1,142 +1,54 @@ -import inspect -import sys -import time -from collections import OrderedDict +import functools +from time import perf_counter +from asgiref.local import Local from django.conf import settings -from django.core import cache -from django.core.cache import CacheHandler, caches as original_caches -from django.core.cache.backends.base import BaseCache -from django.dispatch import Signal -from django.middleware import cache as middleware_cache -from django.utils.translation import gettext_lazy as _, ngettext as __ - -from debug_toolbar import settings as dt_settings -from debug_toolbar.panels import Panel -from debug_toolbar.utils import ( - get_stack, - get_template_info, - render_stacktrace, - tidy_stacktrace, -) - -cache_called = Signal() - +from django.core.cache import CacheHandler, caches +from django.utils.translation import gettext_lazy as _, ngettext -def send_signal(method): - def wrapped(self, *args, **kwargs): - t = time.time() - value = method(self, *args, **kwargs) - t = time.time() - t - - if dt_settings.get_config()["ENABLE_STACKTRACES"]: - stacktrace = tidy_stacktrace(reversed(get_stack())) +from debug_toolbar.panels import Panel +from debug_toolbar.utils import get_stack_trace, get_template_info, render_stacktrace + +# The order of the methods in this list determines the order in which they are listed in +# the Commands table in the panel content. +WRAPPED_CACHE_METHODS = [ + "add", + "get", + "set", + "get_or_set", + "touch", + "delete", + "clear", + "get_many", + "set_many", + "delete_many", + "has_key", + "incr", + "decr", + "incr_version", + "decr_version", +] + + +def _monkey_patch_method(cache, name): + original_method = getattr(cache, name) + + @functools.wraps(original_method) + def wrapper(*args, **kwargs): + panel = cache._djdt_panel + if panel is None: + return original_method(*args, **kwargs) else: - stacktrace = [] - - template_info = get_template_info() - cache_called.send( - sender=self.__class__, - time_taken=t, - name=method.__name__, - return_value=value, - args=args, - kwargs=kwargs, - trace=stacktrace, - template_info=template_info, - backend=self.cache, - ) - return value - - return wrapped - - -class CacheStatTracker(BaseCache): - """A small class used to track cache calls.""" - - def __init__(self, cache): - self.cache = cache - - def __repr__(self): - return str("") % repr(self.cache) - - def _get_func_info(self): - frame = sys._getframe(3) - info = inspect.getframeinfo(frame) - return (info[0], info[1], info[2], info[3]) - - def __contains__(self, key): - return self.cache.__contains__(key) - - def __getattr__(self, name): - return getattr(self.cache, name) - - @send_signal - def add(self, *args, **kwargs): - return self.cache.add(*args, **kwargs) - - @send_signal - def get(self, *args, **kwargs): - return self.cache.get(*args, **kwargs) - - @send_signal - def set(self, *args, **kwargs): - return self.cache.set(*args, **kwargs) - - @send_signal - def touch(self, *args, **kwargs): - return self.cache.touch(*args, **kwargs) + return panel._record_call(cache, name, original_method, args, kwargs) - @send_signal - def delete(self, *args, **kwargs): - return self.cache.delete(*args, **kwargs) + setattr(cache, name, wrapper) - @send_signal - def clear(self, *args, **kwargs): - return self.cache.clear(*args, **kwargs) - @send_signal - def has_key(self, *args, **kwargs): - # Ignore flake8 rules for has_key since we need to support caches - # that may be using has_key. - return self.cache.has_key(*args, **kwargs) # noqa - - @send_signal - def incr(self, *args, **kwargs): - return self.cache.incr(*args, **kwargs) - - @send_signal - def decr(self, *args, **kwargs): - return self.cache.decr(*args, **kwargs) - - @send_signal - def get_many(self, *args, **kwargs): - return self.cache.get_many(*args, **kwargs) - - @send_signal - def set_many(self, *args, **kwargs): - self.cache.set_many(*args, **kwargs) - - @send_signal - def delete_many(self, *args, **kwargs): - self.cache.delete_many(*args, **kwargs) - - @send_signal - def incr_version(self, *args, **kwargs): - return self.cache.incr_version(*args, **kwargs) - - @send_signal - def decr_version(self, *args, **kwargs): - return self.cache.decr_version(*args, **kwargs) - - -class CacheHandlerPatch(CacheHandler): - def __getitem__(self, alias): - actual_cache = super().__getitem__(alias) - return CacheStatTracker(actual_cache) - - -middleware_cache.caches = CacheHandlerPatch() +def _monkey_patch_cache(cache): + if not hasattr(cache, "_djdt_patched"): + for name in WRAPPED_CACHE_METHODS: + _monkey_patch_method(cache, name) + cache._djdt_patched = True class CachePanel(Panel): @@ -146,56 +58,70 @@ class CachePanel(Panel): template = "debug_toolbar/panels/cache.html" + is_async = True + + _context_locals = Local() + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.total_time = 0 self.hits = 0 self.misses = 0 self.calls = [] - self.counts = OrderedDict( - ( - ("add", 0), - ("get", 0), - ("set", 0), - ("touch", 0), - ("delete", 0), - ("clear", 0), - ("get_many", 0), - ("set_many", 0), - ("delete_many", 0), - ("has_key", 0), - ("incr", 0), - ("decr", 0), - ("incr_version", 0), - ("decr_version", 0), - ) - ) - cache_called.connect(self._store_call_info) + self.counts = dict.fromkeys(WRAPPED_CACHE_METHODS, 0) + + @classmethod + def current_instance(cls): + """ + Return the currently enabled CachePanel instance or None. + + If a request is in process with a CachePanel enabled, this will return that + panel (based on the current thread or async task). Otherwise it will return + None. + """ + return getattr(cls._context_locals, "current_instance", None) + + @classmethod + def ready(cls): + if not hasattr(CacheHandler, "_djdt_patched"): + # Wrap the CacheHander.create_connection() method to monkey patch any new + # cache connections that are opened while instrumentation is enabled. In + # the interests of thread safety, this is done once at startup time and + # never removed. + original_method = CacheHandler.create_connection + + @functools.wraps(original_method) + def wrapper(self, alias): + cache = original_method(self, alias) + panel = cls.current_instance() + if panel is not None: + _monkey_patch_cache(cache) + cache._djdt_panel = panel + return cache + + CacheHandler.create_connection = wrapper + CacheHandler._djdt_patched = True def _store_call_info( self, - sender, - name=None, - time_taken=0, - return_value=None, - args=None, - kwargs=None, - trace=None, - template_info=None, - backend=None, - **kw + name, + time_taken, + return_value, + args, + kwargs, + trace, + template_info, + backend, ): - if name == "get": + if name == "get" or name == "get_or_set": if return_value is None: self.misses += 1 else: self.hits += 1 elif name == "get_many": - for key, value in return_value.items(): - if value is None: - self.misses += 1 - else: - self.hits += 1 + keys = kwargs["keys"] if "keys" in kwargs else args[0] + self.hits += len(return_value) + self.misses += len(keys) - len(return_value) time_taken *= 1000 self.total_time += time_taken @@ -212,6 +138,33 @@ def _store_call_info( } ) + def _record_call(self, cache, name, original_method, args, kwargs): + # Some cache backends implement certain cache methods in terms of other cache + # methods (e.g. get_or_set() in terms of get() and add()). In order to only + # record the calls made directly by the user code, set the cache's _djdt_panel + # attribute to None before invoking the original method, which will cause the + # monkey-patched cache methods to skip recording additional calls made during + # the course of this call, and then reset it back afterward. + cache._djdt_panel = None + try: + start_time = perf_counter() + value = original_method(*args, **kwargs) + t = perf_counter() - start_time + finally: + cache._djdt_panel = self + + self._store_call_info( + name=name, + time_taken=t, + return_value=value, + args=args, + kwargs=kwargs, + trace=get_stack_trace(skip=2), + template_info=get_template_info(), + backend=cache, + ) + return value + # Implement the Panel API nav_title = _("Cache") @@ -219,39 +172,40 @@ def _store_call_info( @property def nav_subtitle(self): cache_calls = len(self.calls) - return ( - __( - "%(cache_calls)d call in %(time).2fms", - "%(cache_calls)d calls in %(time).2fms", - cache_calls, - ) - % {"cache_calls": cache_calls, "time": self.total_time} - ) + return ngettext( + "%(cache_calls)d call in %(time).2fms", + "%(cache_calls)d calls in %(time).2fms", + cache_calls, + ) % {"cache_calls": cache_calls, "time": self.total_time} @property def title(self): count = len(getattr(settings, "CACHES", ["default"])) - return ( - __( - "Cache calls from %(count)d backend", - "Cache calls from %(count)d backends", - count, - ) - % {"count": count} - ) + return ngettext( + "Cache calls from %(count)d backend", + "Cache calls from %(count)d backends", + count, + ) % {"count": count} def enable_instrumentation(self): - if isinstance(middleware_cache.caches, CacheHandlerPatch): - cache.caches = middleware_cache.caches - else: - cache.caches = CacheHandlerPatch() + # Monkey patch all open cache connections. Django maintains cache connections + # on a per-thread/async task basis, so this will not affect any concurrent + # requests. The monkey patch of CacheHander.create_connection() installed in + # the .ready() method will ensure that any new cache connections that get opened + # during this request will also be monkey patched. + for cache in caches.all(initialized_only=True): + _monkey_patch_cache(cache) + cache._djdt_panel = self + # Mark this panel instance as the current one for the active thread/async task + # context. This will be used by the CacheHander.create_connection() monkey + # patch. + self._context_locals.current_instance = self def disable_instrumentation(self): - cache.caches = original_caches - # While it can be restored to the original, any views that were - # wrapped with the cache_page decorator will continue to use a - # monkey patched cache. - middleware_cache.caches = original_caches + if hasattr(self._context_locals, "current_instance"): + del self._context_locals.current_instance + for cache in caches.all(initialized_only=True): + cache._djdt_panel = None def generate_stats(self, request, response): self.record_stats( diff --git a/debug_toolbar/panels/headers.py b/debug_toolbar/panels/headers.py index 280cc5df0..f6086d6e6 100644 --- a/debug_toolbar/panels/headers.py +++ b/debug_toolbar/panels/headers.py @@ -1,5 +1,3 @@ -from collections import OrderedDict - from django.utils.translation import gettext_lazy as _ from debug_toolbar.panels import Panel @@ -32,25 +30,25 @@ class HeadersPanel(Panel): title = _("Headers") + is_async = True + template = "debug_toolbar/panels/headers.html" def process_request(self, request): - wsgi_env = list(sorted(request.META.items())) - self.request_headers = OrderedDict( - (unmangle(k), v) for (k, v) in wsgi_env if is_http_header(k) - ) + wsgi_env = sorted(request.META.items()) + self.request_headers = { + unmangle(k): v for (k, v) in wsgi_env if is_http_header(k) + } if "Cookie" in self.request_headers: self.request_headers["Cookie"] = "=> see Request panel" - self.environ = OrderedDict( - (k, v) for (k, v) in wsgi_env if k in self.ENVIRON_FILTER - ) + self.environ = {k: v for (k, v) in wsgi_env if k in self.ENVIRON_FILTER} self.record_stats( {"request_headers": self.request_headers, "environ": self.environ} ) return super().process_request(request) def generate_stats(self, request, response): - self.response_headers = OrderedDict(sorted(response.items())) + self.response_headers = dict(sorted(response.items())) self.record_stats({"response_headers": self.response_headers}) diff --git a/debug_toolbar/panels/history/__init__.py b/debug_toolbar/panels/history/__init__.py index 78c876b81..52ceb7984 100644 --- a/debug_toolbar/panels/history/__init__.py +++ b/debug_toolbar/panels/history/__init__.py @@ -1 +1,3 @@ -from debug_toolbar.panels.history.panel import HistoryPanel # noqa +from debug_toolbar.panels.history.panel import HistoryPanel + +__all__ = ["HistoryPanel"] diff --git a/debug_toolbar/panels/history/forms.py b/debug_toolbar/panels/history/forms.py index b4d9ff6f8..952b2409d 100644 --- a/debug_toolbar/panels/history/forms.py +++ b/debug_toolbar/panels/history/forms.py @@ -1,11 +1,4 @@ -import hashlib -import hmac - from django import forms -from django.conf import settings -from django.core.exceptions import ValidationError -from django.utils.crypto import constant_time_compare -from django.utils.encoding import force_bytes class HistoryStoreForm(forms.Form): @@ -16,26 +9,4 @@ class HistoryStoreForm(forms.Form): """ store_id = forms.CharField(widget=forms.HiddenInput()) - hash = forms.CharField(widget=forms.HiddenInput()) - - def __init__(self, *args, **kwargs): - initial = kwargs.get("initial", None) - - if initial is not None: - initial["hash"] = self.make_hash(initial) - - super().__init__(*args, **kwargs) - - @staticmethod - def make_hash(data): - m = hmac.new(key=force_bytes(settings.SECRET_KEY), digestmod=hashlib.sha1) - m.update(force_bytes(data["store_id"])) - return m.hexdigest() - - def clean_hash(self): - hash = self.cleaned_data["hash"] - - if not constant_time_compare(hash, self.make_hash(self.data)): - raise ValidationError("Tamper alert") - - return hash + exclude_history = forms.BooleanField(widget=forms.HiddenInput(), required=False) diff --git a/debug_toolbar/panels/history/panel.py b/debug_toolbar/panels/history/panel.py index 64c985b84..56a891848 100644 --- a/debug_toolbar/panels/history/panel.py +++ b/debug_toolbar/panels/history/panel.py @@ -1,8 +1,6 @@ +import contextlib import json -import sys -from collections import OrderedDict -from django.conf import settings from django.http.request import RawPostDataException from django.template.loader import render_to_string from django.templatetags.static import static @@ -16,12 +14,27 @@ class HistoryPanel(Panel): - """ A panel to display History """ + """A panel to display History""" + is_async = True title = _("History") nav_title = _("History") template = "debug_toolbar/panels/history.html" + def get_headers(self, request): + headers = super().get_headers(request) + observe_request = self.toolbar.get_observe_request() + store_id = self.toolbar.store_id + if store_id and observe_request(request): + headers["djdt-store-id"] = store_id + return headers + + @property + def enabled(self): + # Do not show the history panel if the panels are rendered on request + # rather than loaded via ajax. + return super().enabled and not self.toolbar.should_render_panels() + @property def is_historical(self): """The HistoryPanel should not be included in the historical panels.""" @@ -49,16 +62,11 @@ def generate_stats(self, request, response): if ( not data and request.body - and request.META.get("CONTENT_TYPE") == "application/json" + and request.headers.get("content-type") == "application/json" ): - # Python <= 3.5's json.loads expects a string. - data = json.loads( - request.body - if sys.version_info[:2] > (3, 5) - else request.body.decode( - request.encoding or settings.DEFAULT_CHARSET - ) - ) + with contextlib.suppress(ValueError): + data = json.loads(request.body) + except RawPostDataException: # It is not guaranteed that we may read the request data (again). data = None @@ -67,6 +75,7 @@ def generate_stats(self, request, response): { "request_url": request.get_full_path(), "request_method": request.method, + "status_code": response.status_code, "data": data, "time": timezone.now(), } @@ -78,11 +87,13 @@ def content(self): Fetch every store for the toolbar and include it in the template. """ - stores = OrderedDict() + stores = {} for id, toolbar in reversed(self.toolbar._store.items()): stores[id] = { "toolbar": toolbar, - "form": HistoryStoreForm(initial={"store_id": id}), + "form": HistoryStoreForm( + initial={"store_id": id, "exclude_history": True} + ), } return render_to_string( @@ -91,7 +102,10 @@ def content(self): "current_store_id": self.toolbar.store_id, "stores": stores, "refresh_form": HistoryStoreForm( - initial={"store_id": self.toolbar.store_id} + initial={ + "store_id": self.toolbar.store_id, + "exclude_history": True, + } ), }, ) diff --git a/debug_toolbar/panels/history/views.py b/debug_toolbar/panels/history/views.py index f6f086131..fb6e28c93 100644 --- a/debug_toolbar/panels/history/views.py +++ b/debug_toolbar/panels/history/views.py @@ -1,12 +1,15 @@ from django.http import HttpResponseBadRequest, JsonResponse from django.template.loader import render_to_string -from debug_toolbar.decorators import require_show_toolbar +from debug_toolbar._compat import login_not_required +from debug_toolbar.decorators import render_with_toolbar_language, require_show_toolbar from debug_toolbar.panels.history.forms import HistoryStoreForm from debug_toolbar.toolbar import DebugToolbar +@login_not_required @require_show_toolbar +@render_with_toolbar_language def history_sidebar(request): """Returns the selected debug toolbar history snapshot.""" form = HistoryStoreForm(request.GET) @@ -14,9 +17,14 @@ def history_sidebar(request): if form.is_valid(): store_id = form.cleaned_data["store_id"] toolbar = DebugToolbar.fetch(store_id) + exclude_history = form.cleaned_data["exclude_history"] context = {} + if toolbar is None: + # When the store_id has been popped already due to + # RESULTS_CACHE_SIZE + return JsonResponse(context) for panel in toolbar.panels: - if not panel.is_historical: + if exclude_history and not panel.is_historical: continue panel_context = {"panel": panel} context[panel.panel_id] = { @@ -31,14 +39,17 @@ def history_sidebar(request): return HttpResponseBadRequest("Form errors") +@login_not_required @require_show_toolbar +@render_with_toolbar_language def history_refresh(request): """Returns the refreshed list of table rows for the History Panel.""" form = HistoryStoreForm(request.GET) if form.is_valid(): requests = [] - for id, toolbar in reversed(DebugToolbar._store.items()): + # Convert to list to handle mutations happening in parallel + for id, toolbar in list(DebugToolbar._store.items()): requests.append( { "id": id, @@ -48,7 +59,12 @@ def history_refresh(request): "id": id, "store_context": { "toolbar": toolbar, - "form": HistoryStoreForm(initial={"store_id": id}), + "form": HistoryStoreForm( + initial={ + "store_id": id, + "exclude_history": True, + } + ), }, }, ), diff --git a/debug_toolbar/panels/logging.py b/debug_toolbar/panels/logging.py deleted file mode 100644 index f296fc882..000000000 --- a/debug_toolbar/panels/logging.py +++ /dev/null @@ -1,83 +0,0 @@ -import datetime -import logging - -from django.utils.translation import gettext_lazy as _, ngettext as __ - -from debug_toolbar.panels import Panel -from debug_toolbar.utils import ThreadCollector - -try: - import threading -except ImportError: - threading = None - -MESSAGE_IF_STRING_REPRESENTATION_INVALID = "[Could not get log message]" - - -class LogCollector(ThreadCollector): - def collect(self, item, thread=None): - # Avoid logging SQL queries since they are already in the SQL panel - # TODO: Make this check whether SQL panel is enabled - if item.get("channel", "") == "django.db.backends": - return - super().collect(item, thread) - - -class ThreadTrackingHandler(logging.Handler): - def __init__(self, collector): - logging.Handler.__init__(self) - self.collector = collector - - def emit(self, record): - try: - message = record.getMessage() - except Exception: - message = MESSAGE_IF_STRING_REPRESENTATION_INVALID - - record = { - "message": message, - "time": datetime.datetime.fromtimestamp(record.created), - "level": record.levelname, - "file": record.pathname, - "line": record.lineno, - "channel": record.name, - } - self.collector.collect(record) - - -# We don't use enable/disable_instrumentation because logging is global. -# We can't add thread-local logging handlers. Hopefully logging is cheap. - -collector = LogCollector() -logging_handler = ThreadTrackingHandler(collector) -logging.root.addHandler(logging_handler) - - -class LoggingPanel(Panel): - template = "debug_toolbar/panels/logging.html" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._records = {} - - nav_title = _("Logging") - - @property - def nav_subtitle(self): - stats = self.get_stats() - record_count = len(stats["records"]) if stats else None - return __("%(count)s message", "%(count)s messages", record_count) % { - "count": record_count - } - - title = _("Log messages") - - def process_request(self, request): - collector.clear_collection() - return super().process_request(request) - - def generate_stats(self, request, response): - records = collector.get_collection() - self._records[threading.currentThread()] = records - collector.clear_collection() - self.record_stats({"records": records}) diff --git a/debug_toolbar/panels/profiling.py b/debug_toolbar/panels/profiling.py index fdd5ed06e..4613a3cad 100644 --- a/debug_toolbar/panels/profiling.py +++ b/debug_toolbar/panels/profiling.py @@ -3,43 +3,17 @@ from colorsys import hsv_to_rgb from pstats import Stats +from django.conf import settings from django.utils.html import format_html from django.utils.translation import gettext_lazy as _ from debug_toolbar import settings as dt_settings from debug_toolbar.panels import Panel -# Occasionally the disable method on the profiler is listed before -# the actual view functions. This function call should be ignored as -# it leads to an error within the tests. -INVALID_PROFILER_FUNC = "_lsprof.Profiler" - - -def contains_profiler(func_tuple): - """Helper function that checks to see if the tuple contains - the INVALID_PROFILE_FUNC in any string value of the tuple.""" - has_profiler = False - for value in func_tuple: - if isinstance(value, str): - has_profiler |= INVALID_PROFILER_FUNC in value - return has_profiler - - -class DjangoDebugToolbarStats(Stats): - __root = None - - def get_root_func(self): - if self.__root is None: - for func, (cc, nc, tt, ct, callers) in self.stats.items(): - if len(callers) == 0 and not contains_profiler(func): - self.__root = func - break - return self.__root - class FunctionCall: def __init__( - self, statobj, func, depth=0, stats=None, id=0, parent_ids=[], hsv=(0, 0.5, 1) + self, statobj, func, depth=0, stats=None, id=0, parent_ids=None, hsv=(0, 0.5, 1) ): self.statobj = statobj self.func = func @@ -49,7 +23,7 @@ def __init__( self.stats = statobj.stats[func][:4] self.depth = depth self.id = id - self.parent_ids = parent_ids + self.parent_ids = parent_ids or [] self.hsv = hsv def parent_classes(self): @@ -57,7 +31,27 @@ def parent_classes(self): def background(self): r, g, b = hsv_to_rgb(*self.hsv) - return "rgb({:f}%,{:f}%,{:f}%)".format(r * 100, g * 100, b * 100) + return f"rgb({r * 100:f}%,{g * 100:f}%,{b * 100:f}%)" + + def is_project_func(self): + """ + Check if the function is from the project code. + + Project code is identified by the BASE_DIR setting + which is used in Django projects by default. + """ + if hasattr(settings, "BASE_DIR"): + file_name, _, _ = self.func + base_dir = str(settings.BASE_DIR) + + file_name = os.path.normpath(file_name) + base_dir = os.path.normpath(base_dir) + + return file_name.startswith(base_dir) and not any( + directory in file_name.split(os.path.sep) + for directory in ["site-packages", "dist-packages"] + ) + return None def func_std_string(self): # match what old profile produced func_name = self.func @@ -65,7 +59,7 @@ def func_std_string(self): # match what old profile produced # special case for built-in functions name = func_name[2] if name.startswith("<") and name.endswith(">"): - return "{%s}" % name[1:-1] + return f"{{{name[1:-1]}}}" else: return name else: @@ -92,16 +86,11 @@ def func_std_string(self): # match what old profile produced ) def subfuncs(self): - i = 0 h, s, v = self.hsv count = len(self.statobj.all_callees[self.func]) - for func, stats in self.statobj.all_callees[self.func].items(): - i += 1 - h1 = h + (i / count) / (self.depth + 1) - if stats[3] == 0: - s1 = 0 - else: - s1 = s * (stats[3] / self.stats[3]) + for i, (func, stats) in enumerate(self.statobj.all_callees[self.func].items()): + h1 = h + ((i + 1) / count) / (self.depth + 1) + s1 = 0 if self.stats[3] == 0 else s * (stats[3] / self.stats[3]) yield FunctionCall( self.statobj, func, @@ -147,40 +136,50 @@ class ProfilingPanel(Panel): Panel that displays profiling information. """ + is_async = False title = _("Profiling") template = "debug_toolbar/panels/profiling.html" + capture_project_code = dt_settings.get_config()["PROFILER_CAPTURE_PROJECT_CODE"] def process_request(self, request): self.profiler = cProfile.Profile() return self.profiler.runcall(super().process_request, request) - def add_node(self, func_list, func, max_depth, cum_time=0.1): + def add_node(self, func_list, func, max_depth, cum_time): func_list.append(func) func.has_subfuncs = False if func.depth < max_depth: for subfunc in func.subfuncs(): - if subfunc.stats[3] >= cum_time: + # Always include the user's code + if subfunc.stats[3] >= cum_time or ( + self.capture_project_code + and subfunc.is_project_func() + and subfunc.stats[3] > 0 + ): func.has_subfuncs = True - self.add_node(func_list, subfunc, max_depth, cum_time=cum_time) + self.add_node(func_list, subfunc, max_depth, cum_time) def generate_stats(self, request, response): if not hasattr(self, "profiler"): return None # Could be delayed until the panel content is requested (perf. optim.) self.profiler.create_stats() - self.stats = DjangoDebugToolbarStats(self.profiler) + self.stats = Stats(self.profiler) self.stats.calc_callees() - root_func = self.stats.get_root_func() - # Ensure root function exists before continuing with function call analysis - if root_func: + root_func = cProfile.label(super().process_request.__code__) + + if root_func in self.stats.stats: root = FunctionCall(self.stats, root_func, depth=0) func_list = [] + cum_time_threshold = ( + root.stats[3] / dt_settings.get_config()["PROFILER_THRESHOLD_RATIO"] + ) self.add_node( func_list, root, dt_settings.get_config()["PROFILER_MAX_DEPTH"], - root.stats[3] / 8, + cum_time_threshold, ) self.record_stats({"func_list": func_list}) diff --git a/debug_toolbar/panels/redirects.py b/debug_toolbar/panels/redirects.py index f6a00b574..8055c67ad 100644 --- a/debug_toolbar/panels/redirects.py +++ b/debug_toolbar/panels/redirects.py @@ -1,3 +1,5 @@ +from inspect import iscoroutine + from django.template.response import SimpleTemplateResponse from django.utils.translation import gettext_lazy as _ @@ -11,22 +13,50 @@ class RedirectsPanel(Panel): has_content = False + is_async = True + nav_title = _("Intercept redirects") - def process_request(self, request): - response = super().process_request(request) + def _process_response(self, response): + """ + Common response processing logic. + """ if 300 <= response.status_code < 400: - redirect_to = response.get("Location") - if redirect_to: - status_line = "{} {}".format( - response.status_code, response.reason_phrase - ) - cookies = response.cookies - context = {"redirect_to": redirect_to, "status_line": status_line} - # Using SimpleTemplateResponse avoids running global context processors. - response = SimpleTemplateResponse( - "debug_toolbar/redirect.html", context - ) - response.cookies = cookies + if redirect_to := response.get("Location"): + response = self.get_interception_response(response, redirect_to) response.render() return response + + async def aprocess_request(self, request, response_coroutine): + """ + Async version of process_request. used for accessing the response + by awaiting it when running in ASGI. + """ + + response = await response_coroutine + return self._process_response(response) + + def process_request(self, request): + response = super().process_request(request) + if iscoroutine(response): + return self.aprocess_request(request, response) + return self._process_response(response) + + def get_interception_response(self, response, redirect_to): + """ + Hook method to allow subclasses to customize the interception response. + """ + status_line = f"{response.status_code} {response.reason_phrase}" + cookies = response.cookies + original_response = response + context = { + "redirect_to": redirect_to, + "status_line": status_line, + "toolbar": self.toolbar, + "original_response": original_response, + } + # Using SimpleTemplateResponse avoids running global context processors. + response = SimpleTemplateResponse("debug_toolbar/redirect.html", context) + response.cookies = cookies + response.original_response = original_response + return response diff --git a/debug_toolbar/panels/request.py b/debug_toolbar/panels/request.py index c75d0077b..5a24d6179 100644 --- a/debug_toolbar/panels/request.py +++ b/debug_toolbar/panels/request.py @@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _ from debug_toolbar.panels import Panel -from debug_toolbar.utils import get_name_from_obj +from debug_toolbar.utils import get_name_from_obj, sanitize_and_sort_request_vars class RequestPanel(Panel): @@ -26,13 +26,12 @@ def nav_subtitle(self): def generate_stats(self, request, response): self.record_stats( { - "get": [(k, request.GET.getlist(k)) for k in sorted(request.GET)], - "post": [(k, request.POST.getlist(k)) for k in sorted(request.POST)], - "cookies": [ - (k, request.COOKIES.get(k)) for k in sorted(request.COOKIES) - ], + "get": sanitize_and_sort_request_vars(request.GET), + "post": sanitize_and_sort_request_vars(request.POST), + "cookies": sanitize_and_sort_request_vars(request.COOKIES), } ) + view_info = { "view_func": _(""), "view_args": "None", @@ -40,22 +39,25 @@ def generate_stats(self, request, response): "view_urlname": "None", } try: - match = resolve(request.path) + match = resolve(request.path_info) func, args, kwargs = match view_info["view_func"] = get_name_from_obj(func) view_info["view_args"] = args view_info["view_kwargs"] = kwargs - view_info["view_urlname"] = getattr(match, "url_name", _("")) + + if getattr(match, "url_name", False): + url_name = match.url_name + if match.namespaces: + url_name = ":".join([*match.namespaces, url_name]) + else: + url_name = _("") + + view_info["view_urlname"] = url_name + except Http404: pass self.record_stats(view_info) if hasattr(request, "session"): - self.record_stats( - { - "session": [ - (k, request.session.get(k)) - for k in sorted(request.session.keys()) - ] - } - ) + session_data = dict(request.session) + self.record_stats({"session": sanitize_and_sort_request_vars(session_data)}) diff --git a/debug_toolbar/panels/settings.py b/debug_toolbar/panels/settings.py index 85a92788e..ff32bd2c0 100644 --- a/debug_toolbar/panels/settings.py +++ b/debug_toolbar/panels/settings.py @@ -1,17 +1,10 @@ -from collections import OrderedDict - -import django from django.conf import settings from django.utils.translation import gettext_lazy as _ +from django.views.debug import get_default_exception_reporter_filter from debug_toolbar.panels import Panel -if django.VERSION >= (3, 1): - from django.views.debug import get_default_exception_reporter_filter - - get_safe_settings = get_default_exception_reporter_filter().get_safe_settings -else: - from django.views.debug import get_safe_settings +get_safe_settings = get_default_exception_reporter_filter().get_safe_settings class SettingsPanel(Panel): @@ -21,16 +14,12 @@ class SettingsPanel(Panel): template = "debug_toolbar/panels/settings.html" + is_async = True + nav_title = _("Settings") def title(self): return _("Settings from %s") % settings.SETTINGS_MODULE def generate_stats(self, request, response): - self.record_stats( - { - "settings": OrderedDict( - sorted(get_safe_settings().items(), key=lambda s: s[0]) - ) - } - ) + self.record_stats({"settings": dict(sorted(get_safe_settings().items()))}) diff --git a/debug_toolbar/panels/signals.py b/debug_toolbar/panels/signals.py index d186f1ebf..db0d4961d 100644 --- a/debug_toolbar/panels/signals.py +++ b/debug_toolbar/panels/signals.py @@ -1,19 +1,26 @@ import weakref -from django.core.signals import got_request_exception, request_finished, request_started +from django.core.signals import ( + got_request_exception, + request_finished, + request_started, + setting_changed, +) from django.db.backends.signals import connection_created from django.db.models.signals import ( class_prepared, + m2m_changed, post_delete, post_init, post_migrate, post_save, pre_delete, pre_init, + pre_migrate, pre_save, ) from django.utils.module_loading import import_string -from django.utils.translation import gettext_lazy as _, ngettext as __ +from django.utils.translation import gettext_lazy as _, ngettext from debug_toolbar.panels import Panel @@ -21,6 +28,8 @@ class SignalsPanel(Panel): template = "debug_toolbar/panels/signals.html" + is_async = True + SIGNALS = { "request_started": request_started, "request_finished": request_finished, @@ -33,7 +42,10 @@ class SignalsPanel(Panel): "post_save": post_save, "pre_delete": pre_delete, "post_delete": post_delete, + "m2m_changed": m2m_changed, + "pre_migrate": pre_migrate, "post_migrate": post_migrate, + "setting_changed": setting_changed, } def nav_subtitle(self): @@ -43,22 +55,16 @@ def nav_subtitle(self): # here we have to handle a double count translation, hence the # hard coding of one signal if num_signals == 1: - return ( - __( - "%(num_receivers)d receiver of 1 signal", - "%(num_receivers)d receivers of 1 signal", - num_receivers, - ) - % {"num_receivers": num_receivers} - ) - return ( - __( - "%(num_receivers)d receiver of %(num_signals)d signals", - "%(num_receivers)d receivers of %(num_signals)d signals", + return ngettext( + "%(num_receivers)d receiver of 1 signal", + "%(num_receivers)d receivers of 1 signal", num_receivers, - ) - % {"num_receivers": num_receivers, "num_signals": num_signals} - ) + ) % {"num_receivers": num_receivers} + return ngettext( + "%(num_receivers)d receiver of %(num_signals)d signals", + "%(num_receivers)d receivers of %(num_signals)d signals", + num_receivers, + ) % {"num_receivers": num_receivers, "num_signals": num_signals} title = _("Signals") @@ -72,7 +78,7 @@ def signals(self): def generate_stats(self, request, response): signals = [] - for name, signal in sorted(self.signals.items(), key=lambda x: x[0]): + for name, signal in sorted(self.signals.items()): receivers = [] for receiver in signal.receivers: receiver = receiver[1] @@ -87,7 +93,7 @@ def generate_stats(self, request, response): receiver_class_name = getattr( receiver.__self__, "__class__", type ).__name__ - text = "{}.{}".format(receiver_class_name, receiver_name) + text = f"{receiver_class_name}.{receiver_name}" else: text = receiver_name receivers.append(text) diff --git a/debug_toolbar/panels/sql/__init__.py b/debug_toolbar/panels/sql/__init__.py index a4f9cd4bf..46c68a3c6 100644 --- a/debug_toolbar/panels/sql/__init__.py +++ b/debug_toolbar/panels/sql/__init__.py @@ -1 +1,3 @@ -from debug_toolbar.panels.sql.panel import SQLPanel # noqa +from debug_toolbar.panels.sql.panel import SQLPanel + +__all__ = ["SQLPanel"] diff --git a/debug_toolbar/panels/sql/forms.py b/debug_toolbar/panels/sql/forms.py index 4a04ee67b..1fa90ace4 100644 --- a/debug_toolbar/panels/sql/forms.py +++ b/debug_toolbar/panels/sql/forms.py @@ -1,16 +1,11 @@ -import hashlib -import hmac import json from django import forms -from django.conf import settings from django.core.exceptions import ValidationError from django.db import connections -from django.utils.crypto import constant_time_compare -from django.utils.encoding import force_bytes from django.utils.functional import cached_property -from debug_toolbar.panels.sql.utils import reformat_sql +from debug_toolbar.panels.sql.utils import is_select_query, reformat_sql class SQLSelectForm(forms.Form): @@ -21,7 +16,6 @@ class SQLSelectForm(forms.Form): raw_sql: The sql statement with placeholders params: JSON encoded parameter values duration: time for SQL to execute passed in from toolbar just for redisplay - hash: the hash of (secret + sql + params) for tamper checking """ sql = forms.CharField() @@ -29,22 +23,11 @@ class SQLSelectForm(forms.Form): params = forms.CharField() alias = forms.CharField(required=False, initial="default") duration = forms.FloatField() - hash = forms.CharField() - - def __init__(self, *args, **kwargs): - initial = kwargs.get("initial") - if initial is not None: - initial["hash"] = self.make_hash(initial) - - super().__init__(*args, **kwargs) - - for name in self.fields: - self.fields[name].widget = forms.HiddenInput() def clean_raw_sql(self): value = self.cleaned_data["raw_sql"] - if not value.lower().strip().startswith("select"): + if not is_select_query(value): raise ValidationError("Only 'select' queries are allowed.") return value @@ -54,34 +37,20 @@ def clean_params(self): try: return json.loads(value) - except ValueError: - raise ValidationError("Is not valid JSON") + except ValueError as exc: + raise ValidationError("Is not valid JSON") from exc def clean_alias(self): value = self.cleaned_data["alias"] if value not in connections: - raise ValidationError("Database alias '%s' not found" % value) + raise ValidationError(f"Database alias '{value}' not found") return value - def clean_hash(self): - hash = self.cleaned_data["hash"] - - if not constant_time_compare(hash, self.make_hash(self.data)): - raise ValidationError("Tamper alert") - - return hash - def reformat_sql(self): return reformat_sql(self.cleaned_data["sql"], with_toggle=False) - def make_hash(self, data): - m = hmac.new(key=force_bytes(settings.SECRET_KEY), digestmod=hashlib.sha1) - for item in [data["sql"], data["params"]]: - m.update(force_bytes(item)) - return m.hexdigest() - @property def connection(self): return connections[self.cleaned_data["alias"]] diff --git a/debug_toolbar/panels/sql/panel.py b/debug_toolbar/panels/sql/panel.py index 683ede292..206686352 100644 --- a/debug_toolbar/panels/sql/panel.py +++ b/debug_toolbar/panels/sql/panel.py @@ -1,31 +1,53 @@ import uuid from collections import defaultdict from copy import copy -from pprint import saferepr +from asgiref.sync import sync_to_async from django.db import connections from django.urls import path -from django.utils.translation import gettext_lazy as _, ngettext_lazy as __ +from django.utils.translation import gettext_lazy as _, ngettext +from debug_toolbar import settings as dt_settings +from debug_toolbar.forms import SignedDataForm from debug_toolbar.panels import Panel from debug_toolbar.panels.sql import views from debug_toolbar.panels.sql.forms import SQLSelectForm -from debug_toolbar.panels.sql.tracking import unwrap_cursor, wrap_cursor -from debug_toolbar.panels.sql.utils import contrasting_color_generator, reformat_sql +from debug_toolbar.panels.sql.tracking import wrap_cursor +from debug_toolbar.panels.sql.utils import ( + contrasting_color_generator, + is_select_query, + reformat_sql, +) from debug_toolbar.utils import render_stacktrace def get_isolation_level_display(vendor, level): if vendor == "postgresql": - import psycopg2.extensions - - choices = { - psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT: _("Autocommit"), - psycopg2.extensions.ISOLATION_LEVEL_READ_UNCOMMITTED: _("Read uncommitted"), - psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED: _("Read committed"), - psycopg2.extensions.ISOLATION_LEVEL_REPEATABLE_READ: _("Repeatable read"), - psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE: _("Serializable"), - } + try: + import psycopg + + choices = { + # AUTOCOMMIT level does not exists in psycopg3 + psycopg.IsolationLevel.READ_UNCOMMITTED: _("Read uncommitted"), + psycopg.IsolationLevel.READ_COMMITTED: _("Read committed"), + psycopg.IsolationLevel.REPEATABLE_READ: _("Repeatable read"), + psycopg.IsolationLevel.SERIALIZABLE: _("Serializable"), + } + except ImportError: + import psycopg2.extensions + + choices = { + psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT: _("Autocommit"), + psycopg2.extensions.ISOLATION_LEVEL_READ_UNCOMMITTED: _( + "Read uncommitted" + ), + psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED: _("Read committed"), + psycopg2.extensions.ISOLATION_LEVEL_REPEATABLE_READ: _( + "Repeatable read" + ), + psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE: _("Serializable"), + } + else: raise ValueError(vendor) return choices.get(level) @@ -33,65 +55,99 @@ def get_isolation_level_display(vendor, level): def get_transaction_status_display(vendor, level): if vendor == "postgresql": - import psycopg2.extensions - - choices = { - psycopg2.extensions.TRANSACTION_STATUS_IDLE: _("Idle"), - psycopg2.extensions.TRANSACTION_STATUS_ACTIVE: _("Active"), - psycopg2.extensions.TRANSACTION_STATUS_INTRANS: _("In transaction"), - psycopg2.extensions.TRANSACTION_STATUS_INERROR: _("In error"), - psycopg2.extensions.TRANSACTION_STATUS_UNKNOWN: _("Unknown"), - } + try: + import psycopg + + choices = { + psycopg.pq.TransactionStatus.IDLE: _("Idle"), + psycopg.pq.TransactionStatus.ACTIVE: _("Active"), + psycopg.pq.TransactionStatus.INTRANS: _("In transaction"), + psycopg.pq.TransactionStatus.INERROR: _("In error"), + psycopg.pq.TransactionStatus.UNKNOWN: _("Unknown"), + } + except ImportError: + import psycopg2.extensions + + choices = { + psycopg2.extensions.TRANSACTION_STATUS_IDLE: _("Idle"), + psycopg2.extensions.TRANSACTION_STATUS_ACTIVE: _("Active"), + psycopg2.extensions.TRANSACTION_STATUS_INTRANS: _("In transaction"), + psycopg2.extensions.TRANSACTION_STATUS_INERROR: _("In error"), + psycopg2.extensions.TRANSACTION_STATUS_UNKNOWN: _("Unknown"), + } + else: raise ValueError(vendor) return choices.get(level) +def _similar_query_key(query): + return query["raw_sql"] + + +def _duplicate_query_key(query): + raw_params = () if query["raw_params"] is None else tuple(query["raw_params"]) + # repr() avoids problems because of unhashable types + # (e.g. lists) when used as dictionary keys. + # https://github.com/django-commons/django-debug-toolbar/issues/1091 + return (query["raw_sql"], repr(raw_params)) + + +def _process_query_groups(query_groups, databases, colors, name): + counts = defaultdict(int) + for (alias, _key), query_group in query_groups.items(): + count = len(query_group) + # Queries are similar / duplicates only if there are at least 2 of them. + if count > 1: + color = next(colors) + for query in query_group: + query[f"{name}_count"] = count + query[f"{name}_color"] = color + counts[alias] += count + for alias, db_info in databases.items(): + db_info[f"{name}_count"] = counts[alias] + + class SQLPanel(Panel): """ Panel that displays information about the SQL queries run while processing the request. """ + is_async = True + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self._offset = {k: len(connections[k].queries) for k in connections} self._sql_time = 0 - self._num_queries = 0 self._queries = [] self._databases = {} - self._transaction_status = {} + # synthetic transaction IDs, keyed by DB alias self._transaction_ids = {} - def get_transaction_id(self, alias): - if alias not in connections: - return - conn = connections[alias].connection - if not conn: - return - - if conn.vendor == "postgresql": - cur_status = conn.get_transaction_status() - else: - raise ValueError(conn.vendor) - - last_status = self._transaction_status.get(alias) - self._transaction_status[alias] = cur_status - - if not cur_status: - # No available state - return None - - if cur_status != last_status: - if cur_status: - self._transaction_ids[alias] = uuid.uuid4().hex - else: - self._transaction_ids[alias] = None - - return self._transaction_ids[alias] - - def record(self, alias, **kwargs): - self._queries.append((alias, kwargs)) + def new_transaction_id(self, alias): + """ + Generate and return a new synthetic transaction ID for the specified DB alias. + """ + trans_id = uuid.uuid4().hex + self._transaction_ids[alias] = trans_id + return trans_id + + def current_transaction_id(self, alias): + """ + Return the current synthetic transaction ID for the specified DB alias. + """ + trans_id = self._transaction_ids.get(alias) + # Sometimes it is not possible to detect the beginning of the first transaction, + # so current_transaction_id() will be called before new_transaction_id(). In + # that case there won't yet be a transaction ID. so it is necessary to generate + # one using new_transaction_id(). + if trans_id is None: + trans_id = self.new_transaction_id(alias) + return trans_id + + def record(self, **kwargs): + self._queries.append(kwargs) + alias = kwargs["alias"] if alias not in self._databases: self._databases[alias] = { "time_spent": kwargs["duration"], @@ -101,7 +157,6 @@ def record(self, alias, **kwargs): self._databases[alias]["time_spent"] += kwargs["duration"] self._databases[alias]["num_queries"] += 1 self._sql_time += kwargs["duration"] - self._num_queries += 1 # Implement the Panel API @@ -109,22 +164,24 @@ def record(self, alias, **kwargs): @property def nav_subtitle(self): - return __("%d query in %.2fms", "%d queries in %.2fms", self._num_queries) % ( - self._num_queries, - self._sql_time, - ) + query_count = len(self._queries) + return ngettext( + "%(query_count)d query in %(sql_time).2fms", + "%(query_count)d queries in %(sql_time).2fms", + query_count, + ) % { + "query_count": query_count, + "sql_time": self._sql_time, + } @property def title(self): count = len(self._databases) - return ( - __( - "SQL queries from %(count)d connection", - "SQL queries from %(count)d connections", - count, - ) - % {"count": count} - ) + return ngettext( + "SQL queries from %(count)d connection", + "SQL queries from %(count)d connections", + count, + ) % {"count": count} template = "debug_toolbar/panels/sql.html" @@ -136,35 +193,32 @@ def get_urls(cls): path("sql_profile/", views.sql_profile, name="sql_profile"), ] + async def aenable_instrumentation(self): + """ + Async version of enable instrumentation. + For async capable panels having async logic for instrumentation. + """ + await sync_to_async(self.enable_instrumentation)() + def enable_instrumentation(self): # This is thread-safe because database connections are thread-local. for connection in connections.all(): - wrap_cursor(connection, self) + wrap_cursor(connection) + connection._djdt_logger = self def disable_instrumentation(self): for connection in connections.all(): - unwrap_cursor(connection) + connection._djdt_logger = None def generate_stats(self, request, response): colors = contrasting_color_generator() trace_colors = defaultdict(lambda: next(colors)) - query_similar = defaultdict(lambda: defaultdict(int)) - query_duplicates = defaultdict(lambda: defaultdict(int)) - - # The keys used to determine similar and duplicate queries. - def similar_key(query): - return query["raw_sql"] - - def duplicate_key(query): - raw_params = ( - () if query["raw_params"] is None else tuple(query["raw_params"]) - ) - # saferepr() avoids problems because of unhashable types - # (e.g. lists) when used as dictionary keys. - # https://github.com/jazzband/django-debug-toolbar/issues/1091 - return (query["raw_sql"], saferepr(raw_params)) + similar_query_groups = defaultdict(list) + duplicate_query_groups = defaultdict(list) if self._queries: + sql_warning_threshold = dt_settings.get_config()["SQL_WARNING_THRESHOLD"] + width_ratio_tally = 0 factor = int(256.0 / (len(self._databases) * 2.5)) for n, db in enumerate(self._databases.values()): @@ -182,26 +236,31 @@ def duplicate_key(query): rgb[nn] = nc db["rgb_color"] = rgb - trans_ids = {} - trans_id = None - i = 0 - for alias, query in self._queries: - query_similar[alias][similar_key(query)] += 1 - query_duplicates[alias][duplicate_key(query)] += 1 + # the last query recorded for each DB alias + last_by_alias = {} + for query in self._queries: + alias = query["alias"] - trans_id = query.get("trans_id") - last_trans_id = trans_ids.get(alias) + similar_query_groups[(alias, _similar_query_key(query))].append(query) + duplicate_query_groups[(alias, _duplicate_query_key(query))].append( + query + ) - if trans_id != last_trans_id: - if last_trans_id: - self._queries[(i - 1)][1]["ends_trans"] = True - trans_ids[alias] = trans_id - if trans_id: + trans_id = query.get("trans_id") + prev_query = last_by_alias.get(alias, {}) + prev_trans_id = prev_query.get("trans_id") + + # If two consecutive queries for a given DB alias have different + # transaction ID values, a transaction started, finished, or both, so + # annotate the queries as appropriate. + if trans_id != prev_trans_id: + if prev_trans_id is not None: + prev_query["ends_trans"] = True + if trans_id is not None: query["starts_trans"] = True - if trans_id: + if trans_id is not None: query["in_trans"] = True - query["alias"] = alias if "iso_level" in query: query["iso_level"] = get_isolation_level_display( query["vendor"], query["iso_level"] @@ -211,10 +270,16 @@ def duplicate_key(query): query["vendor"], query["trans_status"] ) - query["form"] = SQLSelectForm(auto_id=None, initial=copy(query)) + query["form"] = SignedDataForm( + auto_id=None, initial=SQLSelectForm(initial=copy(query)).initial + ) if query["sql"]: query["sql"] = reformat_sql(query["sql"], with_toggle=True) + + query["is_slow"] = query["duration"] > sql_warning_threshold + query["is_select"] = is_select_query(query["raw_sql"]) + query["rgb_color"] = self._databases[alias]["rgb_color"] try: query["width_ratio"] = (query["duration"] / self._sql_time) * 100 @@ -224,62 +289,31 @@ def duplicate_key(query): query["end_offset"] = query["width_ratio"] + query["start_offset"] width_ratio_tally += query["width_ratio"] query["stacktrace"] = render_stacktrace(query["stacktrace"]) - i += 1 query["trace_color"] = trace_colors[query["stacktrace"]] - if trans_id: - self._queries[(i - 1)][1]["ends_trans"] = True - - # Queries are similar / duplicates only if there's as least 2 of them. - # Also, to hide queries, we need to give all the duplicate groups an id - query_colors = contrasting_color_generator() - query_similar_colors = { - alias: { - query: (similar_count, next(query_colors)) - for query, similar_count in queries.items() - if similar_count >= 2 - } - for alias, queries in query_similar.items() - } - query_duplicates_colors = { - alias: { - query: (duplicate_count, next(query_colors)) - for query, duplicate_count in queries.items() - if duplicate_count >= 2 - } - for alias, queries in query_duplicates.items() - } + last_by_alias[alias] = query - for alias, query in self._queries: - try: - (query["similar_count"], query["similar_color"]) = query_similar_colors[ - alias - ][similar_key(query)] - ( - query["duplicate_count"], - query["duplicate_color"], - ) = query_duplicates_colors[alias][duplicate_key(query)] - except KeyError: - pass - - for alias, alias_info in self._databases.items(): - try: - alias_info["similar_count"] = sum( - e[0] for e in query_similar_colors[alias].values() - ) - alias_info["duplicate_count"] = sum( - e[0] for e in query_duplicates_colors[alias].values() - ) - except KeyError: - pass + # Close out any transactions that were in progress, since there is no + # explicit way to know when a transaction finishes. + for final_query in last_by_alias.values(): + if final_query.get("trans_id") is not None: + final_query["ends_trans"] = True + + group_colors = contrasting_color_generator() + _process_query_groups( + similar_query_groups, self._databases, group_colors, "similar" + ) + _process_query_groups( + duplicate_query_groups, self._databases, group_colors, "duplicate" + ) self.record_stats( { "databases": sorted( self._databases.items(), key=lambda x: -x[1]["time_spent"] ), - "queries": [q for a, q in self._queries], + "queries": self._queries, "sql_time": self._sql_time, } ) diff --git a/debug_toolbar/panels/sql/tracking.py b/debug_toolbar/panels/sql/tracking.py index 75366802c..477106fdd 100644 --- a/debug_toolbar/panels/sql/tracking.py +++ b/debug_toolbar/panels/sql/tracking.py @@ -1,117 +1,123 @@ +import contextlib +import contextvars import datetime import json -from threading import local -from time import time +from time import perf_counter +import django.test.testcases from django.utils.encoding import force_str -from debug_toolbar import settings as dt_settings -from debug_toolbar.utils import get_stack, get_template_info, tidy_stacktrace +from debug_toolbar.utils import get_stack_trace, get_template_info try: - from psycopg2._json import Json as PostgresJson + import psycopg + + PostgresJson = psycopg.types.json.Jsonb + STATUS_IN_TRANSACTION = psycopg.pq.TransactionStatus.INTRANS except ImportError: - PostgresJson = None + try: + from psycopg2._json import Json as PostgresJson + from psycopg2.extensions import STATUS_IN_TRANSACTION + except ImportError: + PostgresJson = None + STATUS_IN_TRANSACTION = None + +# Prevents SQL queries from being sent to the DB. It's used +# by the TemplatePanel to prevent the toolbar from issuing +# additional queries. +allow_sql = contextvars.ContextVar("debug-toolbar-allow-sql", default=True) class SQLQueryTriggered(Exception): """Thrown when template panel triggers a query""" - pass - - -class ThreadLocalState(local): - def __init__(self): - self.enabled = True - - @property - def Wrapper(self): - if self.enabled: - return NormalCursorWrapper - return ExceptionCursorWrapper - - def recording(self, v): - self.enabled = v - -state = ThreadLocalState() -recording = state.recording # export function - - -def wrap_cursor(connection, panel): +def wrap_cursor(connection): + # When running a SimpleTestCase, Django monkey patches some DatabaseWrapper + # methods, including .cursor() and .chunked_cursor(), to raise an exception + # if the test code tries to access the database, and then undoes the monkey + # patching when the test case is finished. If we monkey patch those methods + # also, Django's process of undoing those monkey patches will fail. To + # avoid this failure, and because database access is not allowed during a + # SimpleTestCase anyway, skip applying our instrumentation monkey patches if + # we detect that Django has already monkey patched DatabaseWrapper.cursor(). + if isinstance(connection.cursor, django.test.testcases._DatabaseFailure): + return if not hasattr(connection, "_djdt_cursor"): connection._djdt_cursor = connection.cursor connection._djdt_chunked_cursor = connection.chunked_cursor + connection._djdt_logger = None def cursor(*args, **kwargs): # Per the DB API cursor() does not accept any arguments. There's # some code in the wild which does not follow that convention, # so we pass on the arguments even though it's not clean. # See: - # https://github.com/jazzband/django-debug-toolbar/pull/615 - # https://github.com/jazzband/django-debug-toolbar/pull/896 - return state.Wrapper( - connection._djdt_cursor(*args, **kwargs), connection, panel + # https://github.com/django-commons/django-debug-toolbar/pull/615 + # https://github.com/django-commons/django-debug-toolbar/pull/896 + logger = connection._djdt_logger + cursor = connection._djdt_cursor(*args, **kwargs) + if logger is None: + return cursor + mixin = NormalCursorMixin if allow_sql.get() else ExceptionCursorMixin + return patch_cursor_wrapper_with_mixin(cursor.__class__, mixin)( + cursor.cursor, connection, logger ) def chunked_cursor(*args, **kwargs): - return state.Wrapper( - connection._djdt_chunked_cursor(*args, **kwargs), connection, panel - ) + # prevent double wrapping + # solves https://github.com/django-commons/django-debug-toolbar/issues/1239 + logger = connection._djdt_logger + cursor = connection._djdt_chunked_cursor(*args, **kwargs) + if logger is not None and not isinstance(cursor, DjDTCursorWrapperMixin): + mixin = NormalCursorMixin if allow_sql.get() else ExceptionCursorMixin + return patch_cursor_wrapper_with_mixin(cursor.__class__, mixin)( + cursor.cursor, connection, logger + ) + return cursor connection.cursor = cursor connection.chunked_cursor = chunked_cursor - return cursor -def unwrap_cursor(connection): - if hasattr(connection, "_djdt_cursor"): - del connection._djdt_cursor - del connection.cursor - del connection.chunked_cursor +def patch_cursor_wrapper_with_mixin(base_wrapper, mixin): + class DjDTCursorWrapper(mixin, base_wrapper): + pass + + return DjDTCursorWrapper + +class DjDTCursorWrapperMixin: + def __init__(self, cursor, db, logger): + super().__init__(cursor, db) + # logger must implement a ``record`` method + self.logger = logger -class ExceptionCursorWrapper: + +class ExceptionCursorMixin(DjDTCursorWrapperMixin): """ Wraps a cursor and raises an exception on any operation. Used in Templates panel. """ - def __init__(self, cursor, db, logger): - pass - def __getattr__(self, attr): raise SQLQueryTriggered() -class NormalCursorWrapper: +class NormalCursorMixin(DjDTCursorWrapperMixin): """ Wraps a cursor and logs queries. """ - def __init__(self, cursor, db, logger): - self.cursor = cursor - # Instance of a BaseDatabaseWrapper subclass - self.db = db - # logger must implement a ``record`` method - self.logger = logger - - def _quote_expr(self, element): - if isinstance(element, str): - return "'%s'" % element.replace("'", "''") - else: - return repr(element) - - def _quote_params(self, params): - if not params: - return params - if isinstance(params, dict): - return {key: self._quote_expr(value) for key, value in params.items()} - return [self._quote_expr(p) for p in params] - def _decode(self, param): if PostgresJson and isinstance(param, PostgresJson): - return param.dumps(param.adapted) + # psycopg3 + if hasattr(param, "obj"): + return param.dumps(param.obj) + # psycopg2 + if hasattr(param, "adapted"): + return param.dumps(param.adapted) + # If a sequence type, decode each element separately if isinstance(param, (tuple, list)): return [self._decode(element) for element in param] @@ -127,47 +133,59 @@ def _decode(self, param): except UnicodeDecodeError: return "(encoded string)" + def _last_executed_query(self, sql, params): + """Get the last executed query from the connection.""" + # Django's psycopg3 backend creates a new cursor in its implementation of the + # .last_executed_query() method. To avoid wrapping that cursor, temporarily set + # the DatabaseWrapper's ._djdt_logger attribute to None. This will cause the + # monkey-patched .cursor() and .chunked_cursor() methods to skip the wrapping + # process during the .last_executed_query() call. + self.db._djdt_logger = None + try: + return self.db.ops.last_executed_query(self.cursor, sql, params) + finally: + self.db._djdt_logger = self.logger + def _record(self, method, sql, params): - start_time = time() + alias = self.db.alias + vendor = self.db.vendor + + if vendor == "postgresql": + # The underlying DB connection (as opposed to Django's wrapper) + conn = self.db.connection + initial_conn_status = conn.info.transaction_status + + start_time = perf_counter() try: return method(sql, params) finally: - stop_time = time() + stop_time = perf_counter() duration = (stop_time - start_time) * 1000 - if dt_settings.get_config()["ENABLE_STACKTRACES"]: - stacktrace = tidy_stacktrace(reversed(get_stack())) - else: - stacktrace = [] _params = "" - try: + with contextlib.suppress(TypeError): + # object JSON serializable? _params = json.dumps(self._decode(params)) - except TypeError: - pass # object not JSON serializable template_info = get_template_info() - alias = getattr(self.db, "alias", "default") - conn = self.db.connection - vendor = getattr(conn, "vendor", "unknown") - # Sql might be an object (such as psycopg Composed). # For logging purposes, make sure it's str. - sql = str(sql) + if vendor == "postgresql" and not isinstance(sql, str): + if isinstance(sql, bytes): + sql = sql.decode("utf-8") + else: + sql = sql.as_string(conn) + else: + sql = str(sql) - params = { + kwargs = { "vendor": vendor, "alias": alias, - "sql": self.db.ops.last_executed_query( - self.cursor, sql, self._quote_params(params) - ), + "sql": self._last_executed_query(sql, params), "duration": duration, "raw_sql": sql, "params": _params, "raw_params": params, - "stacktrace": stacktrace, - "start_time": start_time, - "stop_time": stop_time, - "is_slow": duration > dt_settings.get_config()["SQL_WARNING_THRESHOLD"], - "is_select": sql.lower().strip().startswith("select"), + "stacktrace": get_stack_trace(skip=2), "template_info": template_info, } @@ -179,35 +197,42 @@ def _record(self, method, sql, params): iso_level = conn.isolation_level except conn.InternalError: iso_level = "unknown" - params.update( + # PostgreSQL does not expose any sort of transaction ID, so it is + # necessary to generate synthetic transaction IDs here. If the + # connection was not in a transaction when the query started, and was + # after the query finished, a new transaction definitely started, so get + # a new transaction ID from logger.new_transaction_id(). If the query + # was in a transaction both before and after executing, make the + # assumption that it is the same transaction and get the current + # transaction ID from logger.current_transaction_id(). There is an edge + # case where Django can start a transaction before the first query + # executes, so in that case logger.current_transaction_id() will + # generate a new transaction ID since one does not already exist. + final_conn_status = conn.info.transaction_status + if final_conn_status == STATUS_IN_TRANSACTION: + if initial_conn_status == STATUS_IN_TRANSACTION: + trans_id = self.logger.current_transaction_id(alias) + else: + trans_id = self.logger.new_transaction_id(alias) + else: + trans_id = None + + kwargs.update( { - "trans_id": self.logger.get_transaction_id(alias), - "trans_status": conn.get_transaction_status(), + "trans_id": trans_id, + "trans_status": conn.info.transaction_status, "iso_level": iso_level, - "encoding": conn.encoding, } ) # We keep `sql` to maintain backwards compatibility - self.logger.record(**params) + self.logger.record(**kwargs) def callproc(self, procname, params=None): - return self._record(self.cursor.callproc, procname, params) + return self._record(super().callproc, procname, params) def execute(self, sql, params=None): - return self._record(self.cursor.execute, sql, params) + return self._record(super().execute, sql, params) def executemany(self, sql, param_list): - return self._record(self.cursor.executemany, sql, param_list) - - def __getattr__(self, attr): - return getattr(self.cursor, attr) - - def __iter__(self): - return iter(self.cursor) - - def __enter__(self): - return self - - def __exit__(self, type, value, traceback): - self.close() + return self._record(super().executemany, sql, param_list) diff --git a/debug_toolbar/panels/sql/utils.py b/debug_toolbar/panels/sql/utils.py index a5645f6da..305543aec 100644 --- a/debug_toolbar/panels/sql/utils.py +++ b/debug_toolbar/panels/sql/utils.py @@ -1,60 +1,143 @@ -import re +from functools import cache, lru_cache +from html import escape import sqlparse -from django.utils.html import escape +from django.dispatch import receiver +from django.test.signals import setting_changed from sqlparse import tokens as T +from debug_toolbar import settings as dt_settings -class BoldKeywordFilter: - """sqlparse filter to bold SQL keywords""" + +class ElideSelectListsFilter: + """sqlparse filter to elide the select list from top-level SELECT ... FROM clauses, + if present""" def process(self, stream): - """Process the token stream""" + allow_elision = True for token_type, value in stream: - is_keyword = token_type in T.Keyword - if is_keyword: - yield T.Text, "" - yield token_type, escape(value) - if is_keyword: - yield T.Text, "" + yield token_type, value + if token_type in T.Keyword: + keyword = value.upper() + if allow_elision and keyword == "SELECT": + yield from self.elide_until_from(stream) + allow_elision = keyword in ["EXCEPT", "INTERSECT", "UNION"] + + @staticmethod + def elide_until_from(stream): + has_dot = False + saved_tokens = [] + for token_type, value in stream: + if token_type in T.Keyword and value.upper() == "FROM": + # Do not elide a select lists that do not contain dots (used to separate + # table names from column names) in order to preserve + # SELECT COUNT(*) AS `__count` FROM ... + # and + # SELECT (1) AS `a` FROM ... + # queries. + if not has_dot: + yield from saved_tokens + else: + # U+2022: Unicode character 'BULLET' + yield T.Other, " \u2022\u2022\u2022 " + yield token_type, value + break + if not has_dot: + if token_type in T.Punctuation and value == ".": + has_dot = True + else: + saved_tokens.append((token_type, value)) + + +class BoldKeywordFilter: + """sqlparse filter to bold SQL keywords""" + + def process(self, stmt): + idx = 0 + while idx < len(stmt.tokens): + token = stmt[idx] + if token.is_keyword: + stmt.insert_before(idx, sqlparse.sql.Token(T.Other, "")) + stmt.insert_after( + idx + 1, + sqlparse.sql.Token(T.Other, ""), + skip_ws=False, + ) + idx += 2 + elif token.is_group: + self.process(token) + idx += 1 + +def escaped_value(token): + # Don't escape T.Whitespace tokens because AlignedIndentFilter inserts its tokens as + # T.Whitesapce, and in our case those tokens are actually HTML. + if token.ttype in (T.Other, T.Whitespace): + return token.value + return escape(token.value, quote=False) -def reformat_sql(sql, with_toggle=False): - formatted = parse_sql(sql, aligned_indent=True) + +class EscapedStringSerializer: + """sqlparse post-processor to convert a Statement into a string escaped for + inclusion in HTML .""" + + @staticmethod + def process(stmt): + return "".join(escaped_value(token) for token in stmt.flatten()) + + +def is_select_query(sql): + # UNION queries can start with "(". + return sql.lower().lstrip(" (").startswith("select") + + +def reformat_sql(sql, *, with_toggle=False): + formatted = parse_sql(sql) if not with_toggle: return formatted - simple = simplify(parse_sql(sql, aligned_indent=False)) - uncollapsed = '{}'.format(simple) - collapsed = '{}'.format(formatted) + simplified = parse_sql(sql, simplify=True) + uncollapsed = f'{simplified}' + collapsed = f'{formatted}' return collapsed + uncollapsed -def parse_sql(sql, aligned_indent=False): +@lru_cache(maxsize=128) +def parse_sql(sql, *, simplify=False): + stack = get_filter_stack(simplify=simplify) + return "".join(stack.run(sql)) + + +@cache +def get_filter_stack(*, simplify): stack = sqlparse.engine.FilterStack() - stack.enable_grouping() - if aligned_indent: + if simplify: + stack.preprocess.append(ElideSelectListsFilter()) + else: + if dt_settings.get_config()["PRETTIFY_SQL"]: + stack.enable_grouping() stack.stmtprocess.append( sqlparse.filters.AlignedIndentFilter(char=" ", n="
") ) - stack.preprocess.append(BoldKeywordFilter()) # add our custom filter - stack.postprocess.append(sqlparse.filters.SerializerUnicode()) # tokens -> strings - return "".join(stack.run(sql)) + stack.stmtprocess.append(BoldKeywordFilter()) + stack.postprocess.append(EscapedStringSerializer()) # Statement -> str + return stack -def simplify(sql): - expr = r"SELECT (...........*?) FROM" - sub = r"SELECT ••• FROM" - return re.sub(expr, sub, sql) +@receiver(setting_changed) +def clear_caches(*, setting, **kwargs): + if setting == "DEBUG_TOOLBAR_CONFIG": + parse_sql.cache_clear() + get_filter_stack.cache_clear() def contrasting_color_generator(): """ - Generate constrasting colors by varying most significant bit of RGB first, + Generate contrasting colors by varying most significant bit of RGB first, and then vary subsequent bits systematically. """ def rgb_to_hex(rgb): - return "#%02x%02x%02x" % tuple(rgb) + return "#{:02x}{:02x}{:02x}".format(*tuple(rgb)) triples = [ (1, 0, 0), diff --git a/debug_toolbar/panels/sql/views.py b/debug_toolbar/panels/sql/views.py index 776a4c967..b3ad6debb 100644 --- a/debug_toolbar/panels/sql/views.py +++ b/debug_toolbar/panels/sql/views.py @@ -2,15 +2,31 @@ from django.template.loader import render_to_string from django.views.decorators.csrf import csrf_exempt -from debug_toolbar.decorators import require_show_toolbar +from debug_toolbar._compat import login_not_required +from debug_toolbar.decorators import render_with_toolbar_language, require_show_toolbar +from debug_toolbar.forms import SignedDataForm from debug_toolbar.panels.sql.forms import SQLSelectForm +def get_signed_data(request): + """Unpack a signed data form, if invalid returns None""" + data = request.GET if request.method == "GET" else request.POST + signed_form = SignedDataForm(data) + if signed_form.is_valid(): + return signed_form.verified_data() + return None + + @csrf_exempt +@login_not_required @require_show_toolbar +@render_with_toolbar_language def sql_select(request): """Returns the output of the SQL SELECT statement""" - form = SQLSelectForm(request.POST or None) + verified_data = get_signed_data(request) + if not verified_data: + return HttpResponseBadRequest("Invalid signature") + form = SQLSelectForm(verified_data) if form.is_valid(): sql = form.cleaned_data["raw_sql"] @@ -33,10 +49,15 @@ def sql_select(request): @csrf_exempt +@login_not_required @require_show_toolbar +@render_with_toolbar_language def sql_explain(request): """Returns the output of the SQL EXPLAIN on the given query""" - form = SQLSelectForm(request.POST or None) + verified_data = get_signed_data(request) + if not verified_data: + return HttpResponseBadRequest("Invalid signature") + form = SQLSelectForm(verified_data) if form.is_valid(): sql = form.cleaned_data["raw_sql"] @@ -47,11 +68,11 @@ def sql_explain(request): # SQLite's EXPLAIN dumps the low-level opcodes generated for a query; # EXPLAIN QUERY PLAN dumps a more human-readable summary # See https://www.sqlite.org/lang_explain.html for details - cursor.execute("EXPLAIN QUERY PLAN {}".format(sql), params) + cursor.execute(f"EXPLAIN QUERY PLAN {sql}", params) elif vendor == "postgresql": - cursor.execute("EXPLAIN ANALYZE {}".format(sql), params) + cursor.execute(f"EXPLAIN ANALYZE {sql}", params) else: - cursor.execute("EXPLAIN {}".format(sql), params) + cursor.execute(f"EXPLAIN {sql}", params) headers = [d[0] for d in cursor.description] result = cursor.fetchall() @@ -68,10 +89,15 @@ def sql_explain(request): @csrf_exempt +@login_not_required @require_show_toolbar +@render_with_toolbar_language def sql_profile(request): """Returns the output of running the SQL and getting the profiling statistics""" - form = SQLSelectForm(request.POST or None) + verified_data = get_signed_data(request) + if not verified_data: + return HttpResponseBadRequest("Invalid signature") + form = SQLSelectForm(verified_data) if form.is_valid(): sql = form.cleaned_data["raw_sql"] diff --git a/debug_toolbar/panels/staticfiles.py b/debug_toolbar/panels/staticfiles.py index 9a15c0f28..9f1970ef6 100644 --- a/debug_toolbar/panels/staticfiles.py +++ b/debug_toolbar/panels/staticfiles.py @@ -1,20 +1,13 @@ -from collections import OrderedDict +import contextlib +import uuid +from contextvars import ContextVar from os.path import join, normpath -from django.conf import settings from django.contrib.staticfiles import finders, storage -from django.core.checks import Warning -from django.core.files.storage import get_storage_class -from django.utils.functional import LazyObject -from django.utils.translation import gettext_lazy as _, ngettext as __ +from django.dispatch import Signal +from django.utils.translation import gettext_lazy as _, ngettext from debug_toolbar import panels -from debug_toolbar.utils import ThreadCollector - -try: - import threading -except ImportError: - threading = None class StaticFile: @@ -22,8 +15,9 @@ class StaticFile: Representing the different properties of a static file. """ - def __init__(self, path): + def __init__(self, *, path, url): self.path = path + self._url = url def __str__(self): return self.path @@ -32,44 +26,30 @@ def real_path(self): return finders.find(self.path) def url(http://github.com/self): - return storage.staticfiles_storage.url(http://github.com/self.path) - - -class FileCollector(ThreadCollector): - def collect(self, path, thread=None): - # handle the case of {% static "admin/" %} - if path.endswith("/"): - return - super().collect(StaticFile(path), thread) - - -collector = FileCollector() - - -class DebugConfiguredStorage(LazyObject): - """ - A staticfiles storage class to be used for collecting which paths - are resolved by using the {% static %} template tag (which uses the - `url` method). - """ - - def _setup(self): - - configured_storage_cls = get_storage_class(settings.STATICFILES_STORAGE) + return self._url - class DebugStaticFilesStorage(configured_storage_cls): - def __init__(self, collector, *args, **kwargs): - super().__init__(*args, **kwargs) - self.collector = collector - def url(http://github.com/self,%20path): - self.collector.collect(path) - return super().url(http://github.com/path) +# This will record and map the StaticFile instances with its associated +# request across threads and async concurrent requests state. +request_id_context_var = ContextVar("djdt_request_id_store") +record_static_file_signal = Signal() - self._wrapped = DebugStaticFilesStorage(collector) - -_original_storage = storage.staticfiles_storage +class URLMixin: + def url(http://github.com/self,%20path): + url = super().url(http://github.com/path) + with contextlib.suppress(LookupError): + # For LookupError: + # The ContextVar wasn't set yet. Since the toolbar wasn't properly + # configured to handle this request, we don't need to capture + # the static file. + request_id = request_id_context_var.get() + record_static_file_signal.send( + sender=self, + staticfile=StaticFile(path=str(path), url=url), + request_id=request_id, + ) + return url class StaticFilesPanel(panels.Panel): @@ -77,6 +57,7 @@ class StaticFilesPanel(panels.Panel): A panel to display the found staticfiles. """ + is_async = True name = "Static files" template = "debug_toolbar/panels/staticfiles.html" @@ -90,13 +71,31 @@ def title(self): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.num_found = 0 - self._paths = {} + self.used_paths = [] + self.request_id = str(uuid.uuid4()) + + @classmethod + def ready(cls): + cls = storage.staticfiles_storage.__class__ + if URLMixin not in cls.mro(): + cls.__bases__ = (URLMixin, *cls.__bases__) + + def _store_static_files_signal_handler(self, sender, staticfile, **kwargs): + # Only record the static file if the request_id matches the one + # that was used to create the panel. + # as sender of the signal and this handler will have multiple + # concurrent connections and we want to avoid storing of same + # staticfile from other connections as well. + if request_id_context_var.get() == self.request_id: + self.used_paths.append(staticfile) def enable_instrumentation(self): - storage.staticfiles_storage = DebugConfiguredStorage() + self.ctx_token = request_id_context_var.set(self.request_id) + record_static_file_signal.connect(self._store_static_files_signal_handler) def disable_instrumentation(self): - storage.staticfiles_storage = _original_storage + record_static_file_signal.disconnect(self._store_static_files_signal_handler) + request_id_context_var.reset(self.ctx_token) @property def num_used(self): @@ -108,23 +107,16 @@ def num_used(self): @property def nav_subtitle(self): num_used = self.num_used - return __("%(num_used)s file used", "%(num_used)s files used", num_used) % { - "num_used": num_used - } - - def process_request(self, request): - collector.clear_collection() - return super().process_request(request) + return ngettext( + "%(num_used)s file used", "%(num_used)s files used", num_used + ) % {"num_used": num_used} def generate_stats(self, request, response): - used_paths = collector.get_collection() - self._paths[threading.currentThread()] = used_paths - self.record_stats( { "num_found": self.num_found, - "num_used": len(used_paths), - "staticfiles": used_paths, + "num_used": len(self.used_paths), + "staticfiles": self.used_paths, "staticfiles_apps": self.get_staticfiles_apps(), "staticfiles_dirs": self.get_staticfiles_dirs(), "staticfiles_finders": self.get_staticfiles_finders(), @@ -137,7 +129,7 @@ def get_staticfiles_finders(self): of relative and file system paths which that finder was able to find. """ - finders_mapping = OrderedDict() + finders_mapping = {} for finder in finders.get_finders(): try: for path, finder_storage in finder.list([]): @@ -177,27 +169,3 @@ def get_staticfiles_apps(self): if app not in apps: apps.append(app) return apps - - @classmethod - def run_checks(cls): - """ - Check that the integration is configured correctly for the panel. - - Specifically look for static files that haven't been collected yet. - - Return a list of :class: `django.core.checks.CheckMessage` instances. - """ - errors = [] - for finder in finders.get_finders(): - try: - for path, finder_storage in finder.list([]): - finder_storage.path(path) - except OSError: - errors.append( - Warning( - "debug_toolbar requires the STATICFILES_DIRS directories to exist.", - hint="Running manage.py collectstatic may help uncover the issue.", - id="debug_toolbar.staticfiles.W001", - ) - ) - return errors diff --git a/debug_toolbar/panels/templates/__init__.py b/debug_toolbar/panels/templates/__init__.py index 1f768f57b..a1d509b9e 100644 --- a/debug_toolbar/panels/templates/__init__.py +++ b/debug_toolbar/panels/templates/__init__.py @@ -1 +1,3 @@ -from debug_toolbar.panels.templates.panel import TemplatesPanel # noqa +from debug_toolbar.panels.templates.panel import TemplatesPanel + +__all__ = ["TemplatesPanel"] diff --git a/debug_toolbar/panels/templates/jinja2.py b/debug_toolbar/panels/templates/jinja2.py new file mode 100644 index 000000000..d343cb140 --- /dev/null +++ b/debug_toolbar/panels/templates/jinja2.py @@ -0,0 +1,23 @@ +import functools + +from django.template.backends.jinja2 import Template as JinjaTemplate +from django.template.context import make_context +from django.test.signals import template_rendered + + +def patch_jinja_render(): + orig_render = JinjaTemplate.render + + @functools.wraps(orig_render) + def wrapped_render(self, context=None, request=None): + # This patching of render only instruments the rendering + # of the immediate template. It won't include the parent template(s). + self.name = self.template.name + template_rendered.send( + sender=self, template=self, context=make_context(context, request) + ) + return orig_render(self, context, request) + + if JinjaTemplate.render != wrapped_render: + JinjaTemplate.original_render = JinjaTemplate.render + JinjaTemplate.render = wrapped_render diff --git a/debug_toolbar/panels/templates/panel.py b/debug_toolbar/panels/templates/panel.py index 8ff06e27d..e9a5b4e83 100644 --- a/debug_toolbar/panels/templates/panel.py +++ b/debug_toolbar/panels/templates/panel.py @@ -1,5 +1,5 @@ -from collections import OrderedDict from contextlib import contextmanager +from importlib.util import find_spec from os.path import normpath from pprint import pformat, saferepr @@ -13,9 +13,14 @@ from django.utils.translation import gettext_lazy as _ from debug_toolbar.panels import Panel -from debug_toolbar.panels.sql.tracking import SQLQueryTriggered, recording +from debug_toolbar.panels.sql.tracking import SQLQueryTriggered, allow_sql from debug_toolbar.panels.templates import views +if find_spec("jinja2"): + from debug_toolbar.panels.templates.jinja2 import patch_jinja_render + + patch_jinja_render() + # Monkey-patch to enable the template_rendered signal. The receiver returns # immediately when the panel is disabled to keep the overhead small. @@ -26,7 +31,6 @@ Template.original_render = Template._render Template._render = instrumented_test_render - # Monkey-patch to store items added by template context processors. The # overhead is sufficiently small to justify enabling it unconditionally. @@ -39,10 +43,10 @@ def _request_context_bind_template(self, template): self.template = template # Set context processors according to the template engine's settings. processors = template.engine.template_context_processors + self._processors - self.context_processors = OrderedDict() + self.context_processors = {} updates = {} for processor in processors: - name = "{}.{}".format(processor.__module__, processor.__name__) + name = f"{processor.__module__}.{processor.__name__}" context = processor(self.request) self.context_processors[name] = context updates.update(context) @@ -64,6 +68,8 @@ class TemplatesPanel(Panel): A panel that lists all templates used during processing of a response. """ + is_async = True + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.templates = [] @@ -84,58 +90,11 @@ def _store_template_info(self, sender, **kwargs): if is_debug_toolbar_template: return - context_list = [] - for context_layer in context.dicts: - if hasattr(context_layer, "items") and context_layer: - # Check if the layer is in the cache. - pformatted = None - for key_values, _pformatted in self.pformat_layers: - if key_values == context_layer: - pformatted = _pformatted - break - - if pformatted is None: - temp_layer = {} - for key, value in context_layer.items(): - # Replace any request elements - they have a large - # Unicode representation and the request data is - # already made available from the Request panel. - if isinstance(value, http.HttpRequest): - temp_layer[key] = "<>" - # Replace the debugging sql_queries element. The SQL - # data is already made available from the SQL panel. - elif key == "sql_queries" and isinstance(value, list): - temp_layer[key] = "<>" - # Replace LANGUAGES, which is available in i18n context - # processor - elif key == "LANGUAGES" and isinstance(value, tuple): - temp_layer[key] = "<>" - # QuerySet would trigger the database: user can run the - # query from SQL Panel - elif isinstance(value, (QuerySet, RawQuerySet)): - temp_layer[key] = "<<{} of {}>>".format( - value.__class__.__name__.lower(), - value.model._meta.label, - ) - else: - recording(False) - try: - saferepr(value) # this MAY trigger a db query - except SQLQueryTriggered: - temp_layer[key] = "<>" - except UnicodeEncodeError: - temp_layer[key] = "<>" - except Exception: - temp_layer[key] = "<>" - else: - temp_layer[key] = value - finally: - recording(True) - pformatted = pformat(temp_layer) - self.pformat_layers.append((context_layer, pformatted)) - context_list.append(pformatted) - - kwargs["context"] = context_list + kwargs["context"] = [ + context_layer + for context_layer in context.dicts + if hasattr(context_layer, "items") and context_layer + ] kwargs["context_processors"] = getattr(context, "context_processors", None) self.templates.append(kwargs) @@ -168,6 +127,63 @@ def enable_instrumentation(self): def disable_instrumentation(self): template_rendered.disconnect(self._store_template_info) + def process_context_list(self, context_layers): + context_list = [] + for context_layer in context_layers: + # Check if the layer is in the cache. + pformatted = None + for key_values, _pformatted in self.pformat_layers: + if key_values == context_layer: + pformatted = _pformatted + break + + if pformatted is None: + temp_layer = {} + for key, value in context_layer.items(): + # Do not force evaluating LazyObject + if hasattr(value, "_wrapped"): + # SimpleLazyObject has __repr__ which includes actual value + # if it has been already evaluated + temp_layer[key] = repr(value) + # Replace any request elements - they have a large + # Unicode representation and the request data is + # already made available from the Request panel. + elif isinstance(value, http.HttpRequest): + temp_layer[key] = "<>" + # Replace the debugging sql_queries element. The SQL + # data is already made available from the SQL panel. + elif key == "sql_queries" and isinstance(value, list): + temp_layer[key] = "<>" + # Replace LANGUAGES, which is available in i18n context + # processor + elif key == "LANGUAGES" and isinstance(value, tuple): + temp_layer[key] = "<>" + # QuerySet would trigger the database: user can run the + # query from SQL Panel + elif isinstance(value, (QuerySet, RawQuerySet)): + temp_layer[key] = ( + f"<<{value.__class__.__name__.lower()} of {value.model._meta.label}>>" + ) + else: + token = allow_sql.set(False) + try: + saferepr(value) # this MAY trigger a db query + except SQLQueryTriggered: + temp_layer[key] = "<>" + except UnicodeEncodeError: + temp_layer[key] = "<>" + except Exception: + temp_layer[key] = "<>" + else: + temp_layer[key] = value + finally: + allow_sql.reset(token) + pformatted = pformat(temp_layer) + self.pformat_layers.append((context_layer, pformatted)) + context_list.append(pformatted) + + return context_list + def generate_stats(self, request, response): template_context = [] for template_data in self.templates: @@ -183,8 +199,11 @@ def generate_stats(self, request, response): info["template"] = template # Clean up context for better readability if self.toolbar.config["SHOW_TEMPLATE_CONTEXT"]: - context_list = template_data.get("context", []) - info["context"] = "\n".join(context_list) + if "context_list" not in template_data: + template_data["context_list"] = self.process_context_list( + template_data.get("context", []) + ) + info["context"] = "\n".join(template_data["context_list"]) template_context.append(info) # Fetch context_processors/template_dirs from any template @@ -193,9 +212,7 @@ def generate_stats(self, request, response): template = self.templates[0]["template"] # django templates have the 'engine' attribute, while jinja # templates use 'backend' - engine_backend = getattr(template, "engine", None) or getattr( - template, "backend" - ) + engine_backend = getattr(template, "engine", None) or template.backend template_dirs = engine_backend.dirs else: context_processors = None diff --git a/debug_toolbar/panels/templates/views.py b/debug_toolbar/panels/templates/views.py index 9e74ec6d5..b8a0a376f 100644 --- a/debug_toolbar/panels/templates/views.py +++ b/debug_toolbar/panels/templates/views.py @@ -3,12 +3,15 @@ from django.template import Origin, TemplateDoesNotExist from django.template.engine import Engine from django.template.loader import render_to_string -from django.utils.safestring import mark_safe +from django.utils.html import format_html, mark_safe -from debug_toolbar.decorators import require_show_toolbar +from debug_toolbar._compat import login_not_required +from debug_toolbar.decorators import render_with_toolbar_language, require_show_toolbar +@login_not_required @require_show_toolbar +@render_with_toolbar_language def template_source(request): """ Return the source of a template, syntax-highlighted by Pygments if @@ -24,15 +27,18 @@ def template_source(request): template_name = request.GET.get("template", template_origin_name) final_loaders = [] - loaders = Engine.get_default().template_loaders + loaders = list(Engine.get_default().template_loaders) + + while loaders: + loader = loaders.pop(0) - for loader in loaders: if loader is not None: - # When the loader has loaders associated with it, - # append those loaders to the list. This occurs with - # django.template.loaders.cached.Loader + # Recursively unwrap loaders until we get to loaders which do not + # themselves wrap other loaders. This adds support for + # django.template.loaders.cached.Loader and the + # django-template-partials loader (possibly among others) if hasattr(loader, "loaders"): - final_loaders += loader.loaders + loaders.extend(loader.loaders) else: final_loaders.append(loader) @@ -44,18 +50,17 @@ def template_source(request): except TemplateDoesNotExist: pass else: - source = "Template Does Not Exist: {}".format(template_origin_name) + source = f"Template Does Not Exist: {template_origin_name}" try: from pygments import highlight from pygments.formatters import HtmlFormatter from pygments.lexers import HtmlDjangoLexer - - source = highlight(source, HtmlDjangoLexer(), HtmlFormatter()) + except ModuleNotFoundError: + source = format_html("{}", source) + else: + source = highlight(source, HtmlDjangoLexer(), HtmlFormatter(wrapcode=True)) source = mark_safe(source) - source.pygmentized = True - except ImportError: - pass content = render_to_string( "debug_toolbar/panels/template_source.html", diff --git a/debug_toolbar/panels/timer.py b/debug_toolbar/panels/timer.py index 801c9c6fd..554798e7d 100644 --- a/debug_toolbar/panels/timer.py +++ b/debug_toolbar/panels/timer.py @@ -1,4 +1,4 @@ -import time +from time import perf_counter from django.template.loader import render_to_string from django.templatetags.static import static @@ -59,7 +59,7 @@ def scripts(self): return scripts def process_request(self, request): - self._start_time = time.time() + self._start_time = perf_counter() if self.has_content: self._start_rusage = resource.getrusage(resource.RUSAGE_SELF) return super().process_request(request) @@ -67,7 +67,7 @@ def process_request(self, request): def generate_stats(self, request, response): stats = {} if hasattr(self, "_start_time"): - stats["total_time"] = (time.time() - self._start_time) * 1000 + stats["total_time"] = (perf_counter() - self._start_time) * 1000 if hasattr(self, "_start_rusage"): self._end_rusage = resource.getrusage(resource.RUSAGE_SELF) stats["utime"] = 1000 * self._elapsed_ru("ru_utime") diff --git a/debug_toolbar/panels/versions.py b/debug_toolbar/panels/versions.py index d517ecfb3..77915e78b 100644 --- a/debug_toolbar/panels/versions.py +++ b/debug_toolbar/panels/versions.py @@ -12,9 +12,11 @@ class VersionsPanel(Panel): Shows versions of Python, Django, and installed apps if possible. """ + is_async = True + @property def nav_subtitle(self): - return "Django %s" % django.get_version() + return f"Django {django.get_version()}" title = _("Versions") diff --git a/debug_toolbar/settings.py b/debug_toolbar/settings.py index 7ebedff39..4dc801c2c 100644 --- a/debug_toolbar/settings.py +++ b/debug_toolbar/settings.py @@ -1,12 +1,19 @@ -from functools import lru_cache +import os +import sys +import warnings +from functools import cache from django.conf import settings +from django.dispatch import receiver +from django.test.signals import setting_changed -# Always import this module as follows: -# from debug_toolbar import settings [as dt_settings] -# Don't import directly CONFIG or PANELs, or you will miss changes performed -# with override_settings in tests. +def _is_running_tests(): + """ + Helper function to support testing default value for + IS_RUNNING_TESTS + """ + return "test" in sys.argv or "PYTEST_VERSION" in os.environ CONFIG_DEFAULTS = { @@ -37,14 +44,21 @@ "django.utils.deprecation", "django.utils.functional", ), + "PRETTIFY_SQL": True, + "PROFILER_CAPTURE_PROJECT_CODE": True, "PROFILER_MAX_DEPTH": 10, + "PROFILER_THRESHOLD_RATIO": 8, "SHOW_TEMPLATE_CONTEXT": True, "SKIP_TEMPLATE_PREFIXES": ("django/forms/widgets/", "admin/widgets/"), "SQL_WARNING_THRESHOLD": 500, # milliseconds + "OBSERVE_REQUEST_CALLBACK": "debug_toolbar.toolbar.observe_request", + "TOOLBAR_LANGUAGE": None, + "IS_RUNNING_TESTS": _is_running_tests(), + "UPDATE_ON_FETCH": False, } -@lru_cache() +@cache def get_config(): USER_CONFIG = getattr(settings, "DEBUG_TOOLBAR_CONFIG", {}) CONFIG = CONFIG_DEFAULTS.copy() @@ -62,18 +76,42 @@ def get_config(): "debug_toolbar.panels.sql.SQLPanel", "debug_toolbar.panels.staticfiles.StaticFilesPanel", "debug_toolbar.panels.templates.TemplatesPanel", + "debug_toolbar.panels.alerts.AlertsPanel", "debug_toolbar.panels.cache.CachePanel", "debug_toolbar.panels.signals.SignalsPanel", - "debug_toolbar.panels.logging.LoggingPanel", "debug_toolbar.panels.redirects.RedirectsPanel", "debug_toolbar.panels.profiling.ProfilingPanel", ] -@lru_cache() +@cache def get_panels(): try: PANELS = list(settings.DEBUG_TOOLBAR_PANELS) except AttributeError: PANELS = PANELS_DEFAULTS + + logging_panel = "debug_toolbar.panels.logging.LoggingPanel" + if logging_panel in PANELS: + PANELS = [panel for panel in PANELS if panel != logging_panel] + warnings.warn( + f"Please remove {logging_panel} from your DEBUG_TOOLBAR_PANELS setting.", + DeprecationWarning, + stacklevel=1, + ) return PANELS + + +@receiver(setting_changed) +def update_toolbar_config(*, setting, **kwargs): + """ + Refresh configuration when overriding settings. + """ + if setting == "DEBUG_TOOLBAR_CONFIG": + get_config.cache_clear() + elif setting == "DEBUG_TOOLBAR_PANELS": + from debug_toolbar.toolbar import DebugToolbar + + get_panels.cache_clear() + DebugToolbar._panel_classes = None + # Not implemented: invalidate debug_toolbar.urls. diff --git a/debug_toolbar/static/debug_toolbar/css/toolbar.css b/debug_toolbar/static/debug_toolbar/css/toolbar.css index bb5f3fb1f..3a8d5628f 100644 --- a/debug_toolbar/static/debug_toolbar/css/toolbar.css +++ b/debug_toolbar/static/debug_toolbar/css/toolbar.css @@ -1,7 +1,66 @@ +/* Variable definitions */ +:root { + /* Font families are the same as in Django admin/css/base.css */ + --djdt-font-family-primary: "Segoe UI", system-ui, Roboto, "Helvetica Neue", + Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", + "Segoe UI Symbol", "Noto Color Emoji"; + --djdt-font-family-monospace: + ui-monospace, Menlo, Monaco, "Cascadia Mono", + "Segoe UI Mono", "Roboto Mono", "Oxygen Mono", "Ubuntu Monospace", + "Source Code Pro", "Fira Mono", "Droid Sans Mono", "Courier New", + monospace, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", + "Noto Color Emoji"; +} + +:root, +#djDebug[data-theme="light"] { + --djdt-font-color: black; + --djdt-background-color: white; + --djdt-panel-content-background-color: #eee; + --djdt-panel-content-table-background-color: var(--djdt-background-color); + --djdt-panel-title-background-color: #ffc; + --djdt-djdt-panel-content-table-strip-background-color: #f5f5f5; + --djdt--highlighted-background-color: lightgrey; + --djdt-toggle-template-background-color: #bbb; + + --djdt-sql-font-color: #333; + --djdt-pre-text-color: #555; + --djdt-path-and-locals: #777; + --djdt-stack-span-color: black; + --djdt-template-highlight-color: #333; + + --djdt-table-border-color: #ccc; + --djdt-button-border-color: var(--djdt-table-border-color); + --djdt-pre-border-color: var(--djdt-table-border-color); + --djdt-raw-border-color: var(--djdt-table-border-color); +} + +#djDebug[data-theme="dark"] { + --djdt-font-color: #f8f8f2; + --djdt-background-color: #1e293bff; + --djdt-panel-content-background-color: #0f1729ff; + --djdt-panel-content-table-background-color: var(--djdt-background-color); + --djdt-panel-title-background-color: #242432; + --djdt-djdt-panel-content-table-strip-background-color: #324154ff; + --djdt--highlighted-background-color: #2c2a7dff; + --djdt-toggle-template-background-color: #282755; + + --djdt-sql-font-color: var(--djdt-font-color); + --djdt-pre-text-color: var(--djdt-font-color); + --djdt-path-and-locals: #65758cff; + --djdt-stack-span-color: #7c8fa4; + --djdt-template-highlight-color: var(--djdt-stack-span-color); + + --djdt-table-border-color: #324154ff; + --djdt-button-border-color: var(--djdt-table-border-color); + --djdt-pre-border-color: var(--djdt-table-border-color); + --djdt-raw-border-color: var(--djdt-table-border-color); +} + /* Debug Toolbar CSS Reset, adapted from Eric Meyer's CSS Reset */ #djDebug { - color: #000; - background: #fff; + color: var(--djdt-font-color); + background: var(--djdt-background-color); } #djDebug, #djDebug div, @@ -64,19 +123,22 @@ #djDebug tr, #djDebug th, #djDebug td, +#djDebug summary, #djDebug button { margin: 0; padding: 0; - min-width: 0; + min-width: auto; width: auto; + min-height: auto; + height: auto; border: 0; outline: 0; font-size: 12px; line-height: 1.5em; - color: #000; + color: var(--djdt-font-color); vertical-align: baseline; background-color: transparent; - font-family: sans-serif; + font-family: var(--djdt-font-family-primary); text-align: left; text-shadow: none; white-space: normal; @@ -86,7 +148,7 @@ #djDebug button { background-color: #eee; background-image: linear-gradient(to bottom, #eee, #cccccc); - border: 1px solid #ccc; + border: 1px solid var(--djdt-button-border-color); border-bottom: 1px solid #bbb; border-radius: 3px; color: #333; @@ -108,7 +170,9 @@ #djDebug button:active { border: 1px solid #aaa; border-bottom: 1px solid #888; - box-shadow: inset 0 0 5px 2px #aaa, 0 1px 0 0 #eee; + box-shadow: + inset 0 0 5px 2px #aaa, + 0 1px 0 0 #eee; } #djDebug #djDebugToolbar { @@ -157,7 +221,7 @@ text-decoration: none; display: block; font-size: 16px; - padding: 10px 10px 5px 25px; + padding: 7px 10px 8px 25px; color: #fff; } #djDebug #djDebugToolbar li > div.djdt-disabled { @@ -176,6 +240,7 @@ #djDebug #djDebugToolbar li.djdt-active:before { content: "▶"; + font-family: var(--djdt-font-family-primary); position: absolute; left: 0; top: 50%; @@ -198,7 +263,7 @@ #djDebug #djDebugToolbarHandle { position: fixed; - transform: rotate(-90deg); + transform: translateY(-100%) rotate(-90deg); transform-origin: right bottom; background-color: #fff; border: 1px solid #111; @@ -217,7 +282,7 @@ font-size: 22px; font-weight: bold; background: #000; - opacity: 0.5; + opacity: 0.6; } #djDebug #djShowToolBarButton:hover { @@ -237,13 +302,26 @@ font-size: 16px; } +#djDebug pre, #djDebug code { display: block; - font-family: Consolas, Monaco, "Bitstream Vera Sans Mono", "Lucida Console", - monospace; + font-family: var(--djdt-font-family-monospace); + overflow: auto; +} + +#djDebug code { font-size: 12px; white-space: pre; - overflow: auto; +} + +#djDebug pre { + white-space: pre-wrap; + color: var(--djdt-pre-text-color); + border: 1px solid var(--djdt-pre-border-color); + border-collapse: collapse; + background-color: var(--djdt-background-color); + padding: 2px 3px; + margin-bottom: 3px; } #djDebug .djdt-panelContent { @@ -253,7 +331,7 @@ right: 220px; bottom: 0; left: 0px; - background-color: #eee; + background-color: var(--djdt-panel-content-background-color); color: #666; z-index: 100000000; } @@ -264,7 +342,7 @@ #djDebug .djDebugPanelTitle { position: absolute; - background-color: #ffc; + background-color: var(--djdt-panel-title-background-color); color: #666; padding-left: 20px; top: 0; @@ -327,16 +405,16 @@ } #djDebug .djdt-panelContent table { - border: 1px solid #ccc; + border: 1px solid var(--djdt-table-border-color); border-collapse: collapse; width: 100%; - background-color: #fff; + background-color: var(--djdt-panel-content-table-background-color); display: table; margin-top: 0.8em; overflow: auto; } -#djDebug .djdt-panelContent tbody > tr:nth-child(odd) { - background-color: #f5f5f5; +#djDebug .djdt-panelContent tbody > tr:nth-child(odd):not(.djdt-highlighted) { + background-color: var(--djdt-panel-content-table-strip-background-color); } #djDebug .djdt-panelContent tbody td, #djDebug .djdt-panelContent tbody th { @@ -362,25 +440,26 @@ } #djDebug .djTemplateContext { - background-color: #fff; + background-color: var(--djdt-background-color); } #djDebug .djdt-panelContent .djDebugClose { position: absolute; top: 4px; right: 15px; - height: 16px; - width: 16px; line-height: 16px; - padding: 5px; border: 6px solid #ddd; border-radius: 50%; background: #fff; color: #ddd; - text-align: center; font-weight: 900; font-size: 20px; - box-sizing: content-box; + height: 36px; + width: 36px; + padding: 0 0 5px; + box-sizing: border-box; + display: grid; + place-items: center; } #djDebug .djdt-panelContent .djDebugClose:hover { @@ -402,7 +481,7 @@ #djDebug a.toggleTemplate { padding: 4px; - background-color: #bbb; + background-color: var(--djdt-toggle-template-background-color); border-radius: 3px; } @@ -414,11 +493,11 @@ } #djDebug .djDebugCollapsed { - color: #333; + color: var(--djdt-sql-font-color); } #djDebug .djDebugUncollapsed { - color: #333; + color: var(--djdt-sql-font-color); } #djDebug .djUnselected { @@ -452,67 +531,530 @@ } #djDebug .highlight { - color: #000; + color: var(--djdt-font-color); } #djDebug .highlight .err { - color: #000; + color: var(--djdt-font-color); } /* Error */ -#djDebug .highlight .g { - color: #000; -} /* Generic */ -#djDebug .highlight .k { - color: #000; + +/* +Styles for pygments HTMLFormatter + +- This should match debug_toolbar/panels/templates/views.py::template_source +- Each line needs to be prefixed with #djDebug .highlight as well. +- The .w definition needs to include: + white-space: pre-wrap + +To regenerate: + + from pygments.formatters import HtmlFormatter + print(HtmlFormatter(wrapcode=True).get_style_defs()) + */ +#djDebug[data-theme="light"] .highlight pre { + line-height: 125%; +} +#djDebug[data-theme="light"] .highlight td.linenos .normal { + color: inherit; + background-color: transparent; + padding-left: 5px; + padding-right: 5px; +} +#djDebug[data-theme="light"] .highlight span.linenos { + color: inherit; + background-color: transparent; + padding-left: 5px; + padding-right: 5px; +} +#djDebug[data-theme="light"] .highlight td.linenos .special { + color: #000000; + background-color: #ffffc0; + padding-left: 5px; + padding-right: 5px; +} +#djDebug[data-theme="light"] .highlight span.linenos.special { + color: #000000; + background-color: #ffffc0; + padding-left: 5px; + padding-right: 5px; +} +#djDebug[data-theme="light"] .highlight .hll { + background-color: #ffffcc; +} +#djDebug[data-theme="light"] .highlight .c { + color: #3d7b7b; + font-style: italic; +} /* Comment */ +#djDebug[data-theme="light"] .highlight .err { + border: 1px solid #ff0000; +} /* Error */ +#djDebug[data-theme="light"] .highlight .k { + color: #008000; font-weight: bold; } /* Keyword */ -#djDebug .highlight .o { - color: #000; +#djDebug[data-theme="light"] .highlight .o { + color: #666666; } /* Operator */ -#djDebug .highlight .n { - color: #000; -} /* Name */ -#djDebug .highlight .mi { - color: #000; +#djDebug[data-theme="light"] .highlight .ch { + color: #3d7b7b; + font-style: italic; +} /* Comment.Hashbang */ +#djDebug[data-theme="light"] .highlight .cm { + color: #3d7b7b; + font-style: italic; +} /* Comment.Multiline */ +#djDebug[data-theme="light"] .highlight .cp { + color: #9c6500; +} /* Comment.Preproc */ +#djDebug[data-theme="light"] .highlight .cpf { + color: #3d7b7b; + font-style: italic; +} /* Comment.PreprocFile */ +#djDebug[data-theme="light"] .highlight .c1 { + color: #3d7b7b; + font-style: italic; +} /* Comment.Single */ +#djDebug[data-theme="light"] .highlight .cs { + color: #3d7b7b; + font-style: italic; +} /* Comment.Special */ +#djDebug[data-theme="light"] .highlight .gd { + color: #a00000; +} /* Generic.Deleted */ +#djDebug[data-theme="light"] .highlight .ge { + font-style: italic; +} /* Generic.Emph */ +#djDebug[data-theme="light"] .highlight .ges { + font-weight: bold; + font-style: italic; +} /* Generic.EmphStrong */ +#djDebug[data-theme="light"] .highlight .gr { + color: #e40000; +} /* Generic.Error */ +#djDebug[data-theme="light"] .highlight .gh { + color: #000080; font-weight: bold; +} /* Generic.Heading */ +#djDebug[data-theme="light"] .highlight .gi { + color: #008400; +} /* Generic.Inserted */ +#djDebug[data-theme="light"] .highlight .go { + color: #717171; +} /* Generic.Output */ +#djDebug[data-theme="light"] .highlight .gp { + color: #000080; + font-weight: bold; +} /* Generic.Prompt */ +#djDebug[data-theme="light"] .highlight .gs { + font-weight: bold; +} /* Generic.Strong */ +#djDebug[data-theme="light"] .highlight .gu { + color: #800080; + font-weight: bold; +} /* Generic.Subheading */ +#djDebug[data-theme="light"] .highlight .gt { + color: #0044dd; +} /* Generic.Traceback */ +#djDebug[data-theme="light"] .highlight .kc { + color: #008000; + font-weight: bold; +} /* Keyword.Constant */ +#djDebug[data-theme="light"] .highlight .kd { + color: #008000; + font-weight: bold; +} /* Keyword.Declaration */ +#djDebug[data-theme="light"] .highlight .kn { + color: #008000; + font-weight: bold; +} /* Keyword.Namespace */ +#djDebug[data-theme="light"] .highlight .kp { + color: #008000; +} /* Keyword.Pseudo */ +#djDebug[data-theme="light"] .highlight .kr { + color: #008000; + font-weight: bold; +} /* Keyword.Reserved */ +#djDebug[data-theme="light"] .highlight .kt { + color: #b00040; +} /* Keyword.Type */ +#djDebug[data-theme="light"] .highlight .m { + color: #666666; +} /* Literal.Number */ +#djDebug[data-theme="light"] .highlight .s { + color: #ba2121; +} /* Literal.String */ +#djDebug[data-theme="light"] .highlight .na { + color: #687822; +} /* Name.Attribute */ +#djDebug[data-theme="light"] .highlight .nb { + color: #008000; +} /* Name.Builtin */ +#djDebug[data-theme="light"] .highlight .nc { + color: #0000ff; + font-weight: bold; +} /* Name.Class */ +#djDebug[data-theme="light"] .highlight .no { + color: #880000; +} /* Name.Constant */ +#djDebug[data-theme="light"] .highlight .nd { + color: #aa22ff; +} /* Name.Decorator */ +#djDebug[data-theme="light"] .highlight .ni { + color: #717171; + font-weight: bold; +} /* Name.Entity */ +#djDebug[data-theme="light"] .highlight .ne { + color: #cb3f38; + font-weight: bold; +} /* Name.Exception */ +#djDebug[data-theme="light"] .highlight .nf { + color: #0000ff; +} /* Name.Function */ +#djDebug[data-theme="light"] .highlight .nl { + color: #767600; +} /* Name.Label */ +#djDebug[data-theme="light"] .highlight .nn { + color: #0000ff; + font-weight: bold; +} /* Name.Namespace */ +#djDebug[data-theme="light"] .highlight .nt { + color: #008000; + font-weight: bold; +} /* Name.Tag */ +#djDebug[data-theme="light"] .highlight .nv { + color: #19177c; +} /* Name.Variable */ +#djDebug[data-theme="light"] .highlight .ow { + color: #aa22ff; + font-weight: bold; +} /* Operator.Word */ +#djDebug[data-theme="light"] .highlight .w { + color: #bbbbbb; + white-space: pre-wrap; +} /* Text.Whitespace */ +#djDebug[data-theme="light"] .highlight .mb { + color: #666666; +} /* Literal.Number.Bin */ +#djDebug[data-theme="light"] .highlight .mf { + color: #666666; +} /* Literal.Number.Float */ +#djDebug[data-theme="light"] .highlight .mh { + color: #666666; +} /* Literal.Number.Hex */ +#djDebug[data-theme="light"] .highlight .mi { + color: #666666; } /* Literal.Number.Integer */ -#djDebug .highlight .l { - color: #000; +#djDebug[data-theme="light"] .highlight .mo { + color: #666666; +} /* Literal.Number.Oct */ +#djDebug[data-theme="light"] .highlight .sa { + color: #ba2121; +} /* Literal.String.Affix */ +#djDebug[data-theme="light"] .highlight .sb { + color: #ba2121; +} /* Literal.String.Backtick */ +#djDebug[data-theme="light"] .highlight .sc { + color: #ba2121; +} /* Literal.String.Char */ +#djDebug[data-theme="light"] .highlight .dl { + color: #ba2121; +} /* Literal.String.Delimiter */ +#djDebug[data-theme="light"] .highlight .sd { + color: #ba2121; + font-style: italic; +} /* Literal.String.Doc */ +#djDebug[data-theme="light"] .highlight .s2 { + color: #ba2121; +} /* Literal.String.Double */ +#djDebug[data-theme="light"] .highlight .se { + color: #aa5d1f; + font-weight: bold; +} /* Literal.String.Escape */ +#djDebug[data-theme="light"] .highlight .sh { + color: #ba2121; +} /* Literal.String.Heredoc */ +#djDebug[data-theme="light"] .highlight .si { + color: #a45a77; + font-weight: bold; +} /* Literal.String.Interpol */ +#djDebug[data-theme="light"] .highlight .sx { + color: #008000; +} /* Literal.String.Other */ +#djDebug[data-theme="light"] .highlight .sr { + color: #a45a77; +} /* Literal.String.Regex */ +#djDebug[data-theme="light"] .highlight .s1 { + color: #ba2121; +} /* Literal.String.Single */ +#djDebug[data-theme="light"] .highlight .ss { + color: #19177c; +} /* Literal.String.Symbol */ +#djDebug[data-theme="light"] .highlight .bp { + color: #008000; +} /* Name.Builtin.Pseudo */ +#djDebug[data-theme="light"] .highlight .fm { + color: #0000ff; +} /* Name.Function.Magic */ +#djDebug[data-theme="light"] .highlight .vc { + color: #19177c; +} /* Name.Variable.Class */ +#djDebug[data-theme="light"] .highlight .vg { + color: #19177c; +} /* Name.Variable.Global */ +#djDebug[data-theme="light"] .highlight .vi { + color: #19177c; +} /* Name.Variable.Instance */ +#djDebug[data-theme="light"] .highlight .vm { + color: #19177c; +} /* Name.Variable.Magic */ +#djDebug[data-theme="light"] .highlight .il { + color: #666666; +} /* Literal.Number.Integer.Long */ + +#djDebug[data-theme="dark"] .highlight .hll { + background-color: #f1fa8c; +} +#djDebug[data-theme="dark"] .highlight { + background: #282a36; + color: #f8f8f2; +} +#djDebug[data-theme="dark"] .highlight .c { + color: #6272a4; +} /* Comment */ +#djDebug[data-theme="dark"] .highlight .err { + color: #f8f8f2; +} /* Error */ +#djDebug[data-theme="dark"] .highlight .g { + color: #f8f8f2; +} /* Generic */ +#djDebug[data-theme="dark"] .highlight .k { + color: #ff79c6; +} /* Keyword */ +#djDebug[data-theme="dark"] .highlight .l { + color: #f8f8f2; } /* Literal */ -#djDebug .highlight .x { - color: #000; +#djDebug[data-theme="dark"] .highlight .n { + color: #f8f8f2; +} /* Name */ +#djDebug[data-theme="dark"] .highlight .o { + color: #ff79c6; +} /* Operator */ +#djDebug[data-theme="dark"] .highlight .x { + color: #f8f8f2; } /* Other */ -#djDebug .highlight .p { - color: #000; +#djDebug[data-theme="dark"] .highlight .p { + color: #f8f8f2; } /* Punctuation */ -#djDebug .highlight .m { - color: #000; +#djDebug[data-theme="dark"] .highlight .ch { + color: #6272a4; +} /* Comment.Hashbang */ +#djDebug[data-theme="dark"] .highlight .cm { + color: #6272a4; +} /* Comment.Multiline */ +#djDebug[data-theme="dark"] .highlight .cp { + color: #ff79c6; +} /* Comment.Preproc */ +#djDebug[data-theme="dark"] .highlight .cpf { + color: #6272a4; +} /* Comment.PreprocFile */ +#djDebug[data-theme="dark"] .highlight .c1 { + color: #6272a4; +} /* Comment.Single */ +#djDebug[data-theme="dark"] .highlight .cs { + color: #6272a4; +} /* Comment.Special */ +#djDebug[data-theme="dark"] .highlight .gd { + color: #8b080b; +} /* Generic.Deleted */ +#djDebug[data-theme="dark"] .highlight .ge { + color: #f8f8f2; + text-decoration: underline; +} /* Generic.Emph */ +#djDebug[data-theme="dark"] .highlight .gr { + color: #f8f8f2; +} /* Generic.Error */ +#djDebug[data-theme="dark"] .highlight .gh { + color: #f8f8f2; + font-weight: bold; +} /* Generic.Heading */ +#djDebug[data-theme="dark"] .highlight .gi { + color: #f8f8f2; font-weight: bold; +} /* Generic.Inserted */ +#djDebug[data-theme="dark"] .highlight .go { + color: #44475a; +} /* Generic.Output */ +#djDebug[data-theme="dark"] .highlight .gp { + color: #f8f8f2; +} /* Generic.Prompt */ +#djDebug[data-theme="dark"] .highlight .gs { + color: #f8f8f2; +} /* Generic.Strong */ +#djDebug[data-theme="dark"] .highlight .gu { + color: #f8f8f2; + font-weight: bold; +} /* Generic.Subheading */ +#djDebug[data-theme="dark"] .highlight .gt { + color: #f8f8f2; +} /* Generic.Traceback */ +#djDebug[data-theme="dark"] .highlight .kc { + color: #ff79c6; +} /* Keyword.Constant */ +#djDebug[data-theme="dark"] .highlight .kd { + color: #8be9fd; + font-style: italic; +} /* Keyword.Declaration */ +#djDebug[data-theme="dark"] .highlight .kn { + color: #ff79c6; +} /* Keyword.Namespace */ +#djDebug[data-theme="dark"] .highlight .kp { + color: #ff79c6; +} /* Keyword.Pseudo */ +#djDebug[data-theme="dark"] .highlight .kr { + color: #ff79c6; +} /* Keyword.Reserved */ +#djDebug[data-theme="dark"] .highlight .kt { + color: #8be9fd; +} /* Keyword.Type */ +#djDebug[data-theme="dark"] .highlight .ld { + color: #f8f8f2; +} /* Literal.Date */ +#djDebug[data-theme="dark"] .highlight .m { + color: #bd93f9; } /* Literal.Number */ -#djDebug .highlight .s { - color: #333; +#djDebug[data-theme="dark"] .highlight .s { + color: #f1fa8c; } /* Literal.String */ -#djDebug .highlight .w { - color: #888888; -} /* Text.Whitespace */ -#djDebug .highlight .il { - color: #000; - font-weight: bold; -} /* Literal.Number.Integer.Long */ -#djDebug .highlight .na { - color: #333; +#djDebug[data-theme="dark"] .highlight .na { + color: #50fa7b; } /* Name.Attribute */ -#djDebug .highlight .nt { - color: #000; - font-weight: bold; +#djDebug[data-theme="dark"] .highlight .nb { + color: #8be9fd; + font-style: italic; +} /* Name.Builtin */ +#djDebug[data-theme="dark"] .highlight .nc { + color: #50fa7b; +} /* Name.Class */ +#djDebug[data-theme="dark"] .highlight .no { + color: #f8f8f2; +} /* Name.Constant */ +#djDebug[data-theme="dark"] .highlight .nd { + color: #f8f8f2; +} /* Name.Decorator */ +#djDebug[data-theme="dark"] .highlight .ni { + color: #f8f8f2; +} /* Name.Entity */ +#djDebug[data-theme="dark"] .highlight .ne { + color: #f8f8f2; +} /* Name.Exception */ +#djDebug[data-theme="dark"] .highlight .nf { + color: #50fa7b; +} /* Name.Function */ +#djDebug[data-theme="dark"] .highlight .nl { + color: #8be9fd; + font-style: italic; +} /* Name.Label */ +#djDebug[data-theme="dark"] .highlight .nn { + color: #f8f8f2; +} /* Name.Namespace */ +#djDebug[data-theme="dark"] .highlight .nx { + color: #f8f8f2; +} /* Name.Other */ +#djDebug[data-theme="dark"] .highlight .py { + color: #f8f8f2; +} /* Name.Property */ +#djDebug[data-theme="dark"] .highlight .nt { + color: #ff79c6; } /* Name.Tag */ -#djDebug .highlight .nv { - color: #333; +#djDebug[data-theme="dark"] .highlight .nv { + color: #8be9fd; + font-style: italic; } /* Name.Variable */ -#djDebug .highlight .s2 { - color: #333; +#djDebug[data-theme="dark"] .highlight .ow { + color: #ff79c6; +} /* Operator.Word */ +#djDebug[data-theme="dark"] .highlight .w { + color: #f8f8f2; +} /* Text.Whitespace */ +#djDebug[data-theme="dark"] .highlight .mb { + color: #bd93f9; +} /* Literal.Number.Bin */ +#djDebug[data-theme="dark"] .highlight .mf { + color: #bd93f9; +} /* Literal.Number.Float */ +#djDebug[data-theme="dark"] .highlight .mh { + color: #bd93f9; +} /* Literal.Number.Hex */ +#djDebug[data-theme="dark"] .highlight .mi { + color: #bd93f9; +} /* Literal.Number.Integer */ +#djDebug[data-theme="dark"] .highlight .mo { + color: #bd93f9; +} /* Literal.Number.Oct */ +#djDebug[data-theme="dark"] .highlight .sa { + color: #f1fa8c; +} /* Literal.String.Affix */ +#djDebug[data-theme="dark"] .highlight .sb { + color: #f1fa8c; +} /* Literal.String.Backtick */ +#djDebug[data-theme="dark"] .highlight .sc { + color: #f1fa8c; +} /* Literal.String.Char */ +#djDebug[data-theme="dark"] .highlight .dl { + color: #f1fa8c; +} /* Literal.String.Delimiter */ +#djDebug[data-theme="dark"] .highlight .sd { + color: #f1fa8c; +} /* Literal.String.Doc */ +#djDebug[data-theme="dark"] .highlight .s2 { + color: #f1fa8c; } /* Literal.String.Double */ -#djDebug .highlight .cp { - color: #333; -} /* Comment.Preproc */ +#djDebug[data-theme="dark"] .highlight .se { + color: #f1fa8c; +} /* Literal.String.Escape */ +#djDebug[data-theme="dark"] .highlight .sh { + color: #f1fa8c; +} /* Literal.String.Heredoc */ +#djDebug[data-theme="dark"] .highlight .si { + color: #f1fa8c; +} /* Literal.String.Interpol */ +#djDebug[data-theme="dark"] .highlight .sx { + color: #f1fa8c; +} /* Literal.String.Other */ +#djDebug[data-theme="dark"] .highlight .sr { + color: #f1fa8c; +} /* Literal.String.Regex */ +#djDebug[data-theme="dark"] .highlight .s1 { + color: #f1fa8c; +} /* Literal.String.Single */ +#djDebug[data-theme="dark"] .highlight .ss { + color: #f1fa8c; +} /* Literal.String.Symbol */ +#djDebug[data-theme="dark"] .highlight .bp { + color: #f8f8f2; + font-style: italic; +} /* Name.Builtin.Pseudo */ +#djDebug[data-theme="dark"] .highlight .fm { + color: #50fa7b; +} /* Name.Function.Magic */ +#djDebug[data-theme="dark"] .highlight .vc { + color: #8be9fd; + font-style: italic; +} /* Name.Variable.Class */ +#djDebug[data-theme="dark"] .highlight .vg { + color: #8be9fd; + font-style: italic; +} /* Name.Variable.Global */ +#djDebug[data-theme="dark"] .highlight .vi { + color: #8be9fd; + font-style: italic; +} /* Name.Variable.Instance */ +#djDebug[data-theme="dark"] .highlight .vm { + color: #8be9fd; + font-style: italic; +} /* Name.Variable.Magic */ +#djDebug[data-theme="dark"] .highlight .il { + color: #bd93f9; +} /* Literal.Number.Integer.Long */ #djDebug svg.djDebugLineChart { width: 100%; @@ -562,27 +1104,15 @@ #djDebug .djSQLDetailsDiv { margin-top: 0.8em; } -#djDebug pre { - white-space: pre-wrap; - color: #555; - border: 1px solid #ccc; - border-collapse: collapse; - background-color: #fff; - display: block; - overflow: auto; - padding: 2px 3px; - margin-bottom: 3px; - font-family: Consolas, Monaco, "Bitstream Vera Sans Mono", "Lucida Console", - monospace; -} + #djDebug .djdt-stack span { - color: #000; + color: var(--djdt-stack-span-color); font-weight: bold; } #djDebug .djdt-stack span.djdt-path, #djDebug .djdt-stack pre.djdt-locals, #djDebug .djdt-stack pre.djdt-locals span { - color: #777; + color: var(--djdt-path-and-locals); font-weight: normal; } #djDebug .djdt-stack span.djdt-code { @@ -591,6 +1121,13 @@ #djDebug .djdt-stack pre.djdt-locals { margin: 0 27px 27px 27px; } +#djDebug .djdt-raw { + background-color: #fff; + border: 1px solid var(--djdt-raw-border-color); + margin-top: 0.8em; + padding: 5px; + white-space: pre-wrap; +} #djDebug .djdt-width-20 { width: 20%; @@ -601,9 +1138,46 @@ #djDebug .djdt-width-60 { width: 60%; } +#djDebug .djdt-max-height-100 { + max-height: 100%; +} #djDebug .djdt-highlighted { - background-color: lightgrey; + background-color: var(--djdt--highlighted-background-color); +} +#djDebug tr.djdt-highlighted.djdt-profile-row { + background-color: #ffc; +} +#djDebug tr.djdt-highlighted.djdt-profile-row:nth-child(2n + 1) { + background-color: #dd9; +} +@keyframes djdt-flash-new { + from { + background-color: green; + } + to { + background-color: inherit; + } } +#djDebug .flash-new { + animation: djdt-flash-new 1s; +} + .djdt-hidden { display: none; } + +#djDebug #djDebugToolbar a#djToggleThemeButton { + display: flex; + align-items: center; + cursor: pointer; +} +#djToggleThemeButton > svg { + margin-left: auto; +} +#djDebug[data-user-theme="light"] #djToggleThemeButton svg.theme-light, +#djDebug[data-user-theme="dark"] #djToggleThemeButton svg.theme-dark, +#djDebug[data-user-theme="auto"] #djToggleThemeButton svg.theme-auto { + display: block; + height: 1rem; + width: 1rem; +} diff --git a/debug_toolbar/static/debug_toolbar/js/history.js b/debug_toolbar/static/debug_toolbar/js/history.js index e20c85438..d10156660 100644 --- a/debug_toolbar/static/debug_toolbar/js/history.js +++ b/debug_toolbar/static/debug_toolbar/js/history.js @@ -1,40 +1,105 @@ -import { $$, ajaxForm } from "./utils.js"; +import { $$, ajaxForm, replaceToolbarState } from "./utils.js"; const djDebug = document.getElementById("djDebug"); -$$.on(djDebug, "click", ".switchHistory", function (event) { - event.preventDefault(); - const newStoreId = this.dataset.storeId; - const tbody = this.closest("tbody"); - - tbody - .querySelector(".djdt-highlighted") - .classList.remove("djdt-highlighted"); - this.closest("tr").classList.add("djdt-highlighted"); - - ajaxForm(this).then(function (data) { - djDebug.setAttribute("data-store-id", newStoreId); - Object.keys(data).forEach(function (panelId) { - const panel = document.getElementById(panelId); - if (panel) { - panel.outerHTML = data[panelId].content; - document.getElementById("djdt-" + panelId).outerHTML = - data[panelId].button; - } - }); - }); -}); +function difference(setA, setB) { + const _difference = new Set(setA); + for (const elem of setB) { + _difference.delete(elem); + } + return _difference; +} -$$.on(djDebug, "click", ".refreshHistory", function (event) { - event.preventDefault(); +/** + * Create an array of dataset properties from a NodeList. + */ +function pluckData(nodes, key) { + return [...nodes].map((obj) => obj.dataset[key]); +} + +function refreshHistory() { + const formTarget = djDebug.querySelector(".refreshHistory"); const container = document.getElementById("djdtHistoryRequests"); - ajaxForm(this).then(function (data) { - data.requests.forEach(function (request) { - if ( - !container.querySelector('[data-store-id="' + request.id + '"]') - ) { + const oldIds = new Set( + pluckData(container.querySelectorAll("tr[data-store-id]"), "storeId") + ); + + ajaxForm(formTarget) + .then((data) => { + // Remove existing rows first then re-populate with new data + for (const node of container.querySelectorAll( + "tr[data-store-id]" + )) { + node.remove(); + } + for (const request of data.requests) { container.innerHTML = request.content + container.innerHTML; } + }) + .then(() => { + const allIds = new Set( + pluckData( + container.querySelectorAll("tr[data-store-id]"), + "storeId" + ) + ); + const newIds = difference(allIds, oldIds); + const lastRequestId = newIds.values().next().value; + return { + allIds, + newIds, + lastRequestId, + }; + }) + .then((refreshInfo) => { + for (const newId of refreshInfo.newIds) { + const row = container.querySelector( + `tr[data-store-id="${newId}"]` + ); + row.classList.add("flash-new"); + } + setTimeout(() => { + for (const row of container.querySelectorAll( + "tr[data-store-id]" + )) { + row.classList.remove("flash-new"); + } + }, 2000); }); +} + +function switchHistory(newStoreId) { + const formTarget = djDebug.querySelector( + `.switchHistory[data-store-id='${newStoreId}']` + ); + const tbody = formTarget.closest("tbody"); + + const highlighted = tbody.querySelector(".djdt-highlighted"); + if (highlighted) { + highlighted.classList.remove("djdt-highlighted"); + } + formTarget.closest("tr").classList.add("djdt-highlighted"); + + ajaxForm(formTarget).then((data) => { + if (Object.keys(data).length === 0) { + const container = document.getElementById("djdtHistoryRequests"); + container.querySelector( + `button[data-store-id="${newStoreId}"]` + ).innerHTML = "Switch [EXPIRED]"; + } + replaceToolbarState(newStoreId, data); }); +} + +$$.on(djDebug, "click", ".switchHistory", function (event) { + event.preventDefault(); + switchHistory(this.dataset.storeId); +}); + +$$.on(djDebug, "click", ".refreshHistory", (event) => { + event.preventDefault(); + refreshHistory(); }); +// We don't refresh the whole toolbar each fetch or ajax request, +// so we need to refresh the history when we open the panel +$$.onPanelRender(djDebug, "HistoryPanel", refreshHistory); diff --git a/debug_toolbar/static/debug_toolbar/js/timer.js b/debug_toolbar/static/debug_toolbar/js/timer.js index 1d4ac19d8..a01dbb2d1 100644 --- a/debug_toolbar/static/debug_toolbar/js/timer.js +++ b/debug_toolbar/static/debug_toolbar/js/timer.js @@ -1,59 +1,82 @@ -const timingOffset = performance.timing.navigationStart, - timingEnd = performance.timing.loadEventEnd, - totalTime = timingEnd - timingOffset; -function getLeft(stat) { - return ((performance.timing[stat] - timingOffset) / totalTime) * 100.0; -} -function getCSSWidth(stat, endStat) { - let width = - ((performance.timing[endStat] - performance.timing[stat]) / totalTime) * - 100.0; - // Calculate relative percent (same as sql panel logic) - width = (100.0 * width) / (100.0 - getLeft(stat)); - return width < 1 ? "2px" : width + "%"; -} -function addRow(tbody, stat, endStat) { - const row = document.createElement("tr"); - if (endStat) { - // Render a start through end bar - row.innerHTML = - "" + - stat.replace("Start", "") + - "" + - '' + - "" + - (performance.timing[stat] - timingOffset) + - " (+" + - (performance.timing[endStat] - performance.timing[stat]) + - ")"; - row.querySelector("rect").setAttribute( - "width", - getCSSWidth(stat, endStat) - ); - } else { - // Render a point in time - row.innerHTML = - "" + - stat + - "" + - '' + - "" + - (performance.timing[stat] - timingOffset) + - ""; - row.querySelector("rect").setAttribute("width", 2); +import { $$ } from "./utils.js"; + +function insertBrowserTiming() { + const timingOffset = performance.timing.navigationStart; + const timingEnd = performance.timing.loadEventEnd; + const totalTime = timingEnd - timingOffset; + function getLeft(stat) { + if (totalTime !== 0) { + return ( + ((performance.timing[stat] - timingOffset) / totalTime) * 100.0 + ); + } + return 0; + } + function getCSSWidth(stat, endStat) { + let width = 0; + if (totalTime !== 0) { + width = + ((performance.timing[endStat] - performance.timing[stat]) / + totalTime) * + 100.0; + } + const denominator = 100.0 - getLeft(stat); + if (denominator !== 0) { + // Calculate relative percent (same as sql panel logic) + width = (100.0 * width) / denominator; + } else { + width = 0; + } + return width < 1 ? "2px" : `${width}%`; + } + function addRow(tbody, stat, endStat) { + const row = document.createElement("tr"); + const elapsed = performance.timing[stat] - timingOffset; + if (endStat) { + const duration = + performance.timing[endStat] - performance.timing[stat]; + // Render a start through end bar + row.innerHTML = ` +${stat.replace("Start", "")} + +${elapsed} (+${duration}) +`; + row.querySelector("rect").setAttribute( + "width", + getCSSWidth(stat, endStat) + ); + } else { + // Render a point in time + row.innerHTML = ` +${stat} + +${elapsed} +`; + row.querySelector("rect").setAttribute("width", 2); + } + row.querySelector("rect").setAttribute("x", getLeft(stat)); + tbody.appendChild(row); + } + + const browserTiming = document.getElementById("djDebugBrowserTiming"); + // Determine if the browser timing section has already been rendered. + if (browserTiming.classList.contains("djdt-hidden")) { + const tbody = document.getElementById("djDebugBrowserTimingTableBody"); + // This is a reasonably complete and ordered set of timing periods (2 params) and events (1 param) + addRow(tbody, "domainLookupStart", "domainLookupEnd"); + addRow(tbody, "connectStart", "connectEnd"); + addRow(tbody, "requestStart", "responseEnd"); // There is no requestEnd + addRow(tbody, "responseStart", "responseEnd"); + addRow(tbody, "domLoading", "domComplete"); // Spans the events below + addRow(tbody, "domInteractive"); + addRow(tbody, "domContentLoadedEventStart", "domContentLoadedEventEnd"); + addRow(tbody, "loadEventStart", "loadEventEnd"); + browserTiming.classList.remove("djdt-hidden"); } - row.querySelector("rect").setAttribute("x", getLeft(stat)); - tbody.appendChild(row); } -const tbody = document.getElementById("djDebugBrowserTimingTableBody"); -// This is a reasonably complete and ordered set of timing periods (2 params) and events (1 param) -addRow(tbody, "domainLookupStart", "domainLookupEnd"); -addRow(tbody, "connectStart", "connectEnd"); -addRow(tbody, "requestStart", "responseEnd"); // There is no requestEnd -addRow(tbody, "responseStart", "responseEnd"); -addRow(tbody, "domLoading", "domComplete"); // Spans the events below -addRow(tbody, "domInteractive"); -addRow(tbody, "domContentLoadedEventStart", "domContentLoadedEventEnd"); -addRow(tbody, "loadEventStart", "loadEventEnd"); -document.getElementById("djDebugBrowserTiming").classList.remove("djdt-hidden"); +const djDebug = document.getElementById("djDebug"); +// Insert the browser timing now since it's possible for this +// script to miss the initial panel load event. +insertBrowserTiming(); +$$.onPanelRender(djDebug, "TimerPanel", insertBrowserTiming); diff --git a/debug_toolbar/static/debug_toolbar/js/toolbar.js b/debug_toolbar/static/debug_toolbar/js/toolbar.js index 6e67d5a95..19658f76e 100644 --- a/debug_toolbar/static/debug_toolbar/js/toolbar.js +++ b/debug_toolbar/static/debug_toolbar/js/toolbar.js @@ -1,56 +1,74 @@ -import { $$, ajax } from "./utils.js"; +import { $$, ajax, debounce, replaceToolbarState } from "./utils.js"; function onKeyDown(event) { if (event.keyCode === 27) { - djdt.hide_one_level(); + djdt.hideOneLevel(); } } +function getDebugElement() { + // Fetch the debug element from the DOM. + // This is used to avoid writing the element's id + // everywhere the element is being selected. A fixed reference + // to the element should be avoided because the entire DOM could + // be reloaded such as via HTMX boosting. + return document.getElementById("djDebug"); +} + const djdt = { handleDragged: false, + needUpdateOnFetch: false, init() { - const djDebug = document.getElementById("djDebug"); - $$.show(djDebug); - $$.on( - document.getElementById("djDebugPanelList"), - "click", - "li a", - function (event) { - event.preventDefault(); - if (!this.className) { - return; - } - const current = document.getElementById(this.className); - if ($$.visible(current)) { - djdt.hide_panels(); - } else { - djdt.hide_panels(); + const djDebug = getDebugElement(); + djdt.needUpdateOnFetch = djDebug.dataset.updateOnFetch === "True"; + $$.on(djDebug, "click", "#djDebugPanelList li a", function (event) { + event.preventDefault(); + if (!this.className) { + return; + } + const panelId = this.className; + const current = document.getElementById(panelId); + if ($$.visible(current)) { + djdt.hidePanels(); + } else { + djdt.hidePanels(); - $$.show(current); - this.parentElement.classList.add("djdt-active"); + $$.show(current); + this.parentElement.classList.add("djdt-active"); - const inner = current.querySelector( - ".djDebugPanelContent .djdt-scroll" - ), - store_id = djDebug.dataset.storeId; - if (store_id && inner.children.length === 0) { - const url = new URL( - djDebug.dataset.renderPanelUrl, - window.location + const inner = current.querySelector( + ".djDebugPanelContent .djdt-scroll" + ); + const storeId = djDebug.dataset.storeId; + if (storeId && inner.children.length === 0) { + const url = new URL( + djDebug.dataset.renderPanelUrl, + window.location + ); + url.searchParams.append("store_id", storeId); + url.searchParams.append("panel_id", panelId); + ajax(url).then((data) => { + inner.previousElementSibling.remove(); // Remove AJAX loader + inner.innerHTML = data.content; + $$.executeScripts(data.scripts); + $$.applyStyles(inner); + djDebug.dispatchEvent( + new CustomEvent("djdt.panel.render", { + detail: { panelId: panelId }, + }) ); - url.searchParams.append("store_id", store_id); - url.searchParams.append("panel_id", this.className); - ajax(url).then(function (data) { - inner.previousElementSibling.remove(); // Remove AJAX loader - inner.innerHTML = data.content; - $$.executeScripts(data.scripts); - }); - } + }); + } else { + djDebug.dispatchEvent( + new CustomEvent("djdt.panel.render", { + detail: { panelId: panelId }, + }) + ); } } - ); - $$.on(djDebug, "click", ".djDebugClose", function () { - djdt.hide_one_level(); + }); + $$.on(djDebug, "click", ".djDebugClose", () => { + djdt.hideOneLevel(); }); $$.on( djDebug, @@ -73,18 +91,18 @@ const djdt = { event.preventDefault(); let url; - const ajax_data = {}; + const ajaxData = {}; if (this.tagName === "BUTTON") { const form = this.closest("form"); url = this.formAction; - ajax_data.method = form.method.toUpperCase(); - ajax_data.body = new FormData(form); + ajaxData.method = form.method.toUpperCase(); + ajaxData.body = new FormData(form); } else if (this.tagName === "A") { url = this.href; } - ajax(url, ajax_data).then(function (data) { + ajax(url, ajaxData).then((data) => { const win = document.getElementById("djDebugWindow"); win.innerHTML = data.content; $$.show(win); @@ -96,53 +114,48 @@ const djdt = { const id = this.dataset.toggleId; const toggleOpen = "+"; const toggleClose = "-"; - const open_me = this.textContent === toggleOpen; + const openMe = this.textContent === toggleOpen; const name = this.dataset.toggleName; - const container = document.getElementById(name + "_" + id); - container - .querySelectorAll(".djDebugCollapsed") - .forEach(function (e) { - $$.toggle(e, open_me); - }); - container - .querySelectorAll(".djDebugUncollapsed") - .forEach(function (e) { - $$.toggle(e, !open_me); - }); - const self = this; - this.closest(".djDebugPanelContent") - .querySelectorAll(".djToggleDetails_" + id) - .forEach(function (e) { - if (open_me) { - e.classList.add("djSelected"); - e.classList.remove("djUnselected"); - self.textContent = toggleClose; - } else { - e.classList.remove("djSelected"); - e.classList.add("djUnselected"); - self.textContent = toggleOpen; - } - const switch_ = e.querySelector(".djToggleSwitch"); - if (switch_) { - switch_.textContent = self.textContent; - } - }); + const container = document.getElementById(`${name}_${id}`); + for (const el of container.querySelectorAll(".djDebugCollapsed")) { + $$.toggle(el, openMe); + } + for (const el of container.querySelectorAll( + ".djDebugUncollapsed" + )) { + $$.toggle(el, !openMe); + } + for (const el of this.closest( + ".djDebugPanelContent" + ).querySelectorAll(`.djToggleDetails_${id}`)) { + if (openMe) { + el.classList.add("djSelected"); + el.classList.remove("djUnselected"); + this.textContent = toggleClose; + } else { + el.classList.remove("djSelected"); + el.classList.add("djUnselected"); + this.textContent = toggleOpen; + } + const switch_ = el.querySelector(".djToggleSwitch"); + if (switch_) { + switch_.textContent = this.textContent; + } + } }); - document - .getElementById("djHideToolBarButton") - .addEventListener("click", function (event) { - event.preventDefault(); - djdt.hide_toolbar(); - }); - document - .getElementById("djShowToolBarButton") - .addEventListener("click", function () { - if (!djdt.handleDragged) { - djdt.show_toolbar(); - } - }); - let startPageY, baseY; + $$.on(djDebug, "click", "#djHideToolBarButton", (event) => { + event.preventDefault(); + djdt.hideToolbar(); + }); + + $$.on(djDebug, "click", "#djShowToolBarButton", () => { + if (!djdt.handleDragged) { + djdt.showToolbar(); + } + }); + let startPageY; + let baseY; const handle = document.getElementById("djDebugToolbarHandle"); function onHandleMove(event) { // Chrome can send spurious mousemove events, so don't do anything unless the @@ -157,85 +170,180 @@ const djdt = { top = window.innerHeight - handle.offsetHeight; } - handle.style.top = top + "px"; + handle.style.top = `${top}px`; djdt.handleDragged = true; } } - document - .getElementById("djShowToolBarButton") - .addEventListener("mousedown", function (event) { - event.preventDefault(); - startPageY = event.pageY; - baseY = handle.offsetTop - startPageY; - document.addEventListener("mousemove", onHandleMove); - }); - document.addEventListener("mouseup", function (event) { - document.removeEventListener("mousemove", onHandleMove); - if (djdt.handleDragged) { - event.preventDefault(); - localStorage.setItem("djdt.top", handle.offsetTop); - requestAnimationFrame(function () { - djdt.handleDragged = false; - }); - } + $$.on(djDebug, "mousedown", "#djShowToolBarButton", (event) => { + event.preventDefault(); + startPageY = event.pageY; + baseY = handle.offsetTop - startPageY; + document.addEventListener("mousemove", onHandleMove); + + document.addEventListener( + "mouseup", + (event) => { + document.removeEventListener("mousemove", onHandleMove); + if (djdt.handleDragged) { + event.preventDefault(); + localStorage.setItem("djdt.top", handle.offsetTop); + requestAnimationFrame(() => { + djdt.handleDragged = false; + }); + djdt.ensureHandleVisibility(); + } + }, + { once: true } + ); }); + + // Make sure the debug element is rendered at least once. + // showToolbar will continue to show it in the future if the + // entire DOM is reloaded. + $$.show(djDebug); const show = localStorage.getItem("djdt.show") || djDebug.dataset.defaultShow; if (show === "true") { - djdt.show_toolbar(); + djdt.showToolbar(); } else { - djdt.hide_toolbar(); + djdt.hideToolbar(); } + if (djDebug.dataset.sidebarUrl !== undefined) { + djdt.updateOnAjax(); + } + + const prefersDark = window.matchMedia( + "(prefers-color-scheme: dark)" + ).matches; + const themeList = prefersDark + ? ["auto", "light", "dark"] + : ["auto", "dark", "light"]; + const setTheme = (theme) => { + djDebug.setAttribute( + "data-theme", + theme === "auto" ? (prefersDark ? "dark" : "light") : theme + ); + djDebug.setAttribute("data-user-theme", theme); + }; + + // Updates the theme using user settings + let userTheme = localStorage.getItem("djdt.user-theme") || "auto"; + setTheme(userTheme); + + // Adds the listener to the Theme Toggle Button + $$.on(djDebug, "click", "#djToggleThemeButton", () => { + const index = themeList.indexOf(userTheme); + userTheme = themeList[(index + 1) % themeList.length]; + localStorage.setItem("djdt.user-theme", userTheme); + setTheme(userTheme); + }); }, - hide_panels() { - const djDebug = document.getElementById("djDebug"); + hidePanels() { + const djDebug = getDebugElement(); $$.hide(document.getElementById("djDebugWindow")); - djDebug.querySelectorAll(".djdt-panelContent").forEach(function (e) { - $$.hide(e); - }); - document.querySelectorAll("#djDebugToolbar li").forEach(function (e) { - e.classList.remove("djdt-active"); - }); + for (const el of djDebug.querySelectorAll(".djdt-panelContent")) { + $$.hide(el); + } + for (const el of document.querySelectorAll("#djDebugToolbar li")) { + el.classList.remove("djdt-active"); + } }, - hide_toolbar() { - djdt.hide_panels(); + ensureHandleVisibility() { + const handle = document.getElementById("djDebugToolbarHandle"); + // set handle position + const handleTop = Math.min( + localStorage.getItem("djdt.top") || 265, + window.innerHeight - handle.offsetWidth + ); + handle.style.top = `${handleTop}px`; + }, + hideToolbar() { + djdt.hidePanels(); $$.hide(document.getElementById("djDebugToolbar")); const handle = document.getElementById("djDebugToolbarHandle"); $$.show(handle); - // set handle position - let handleTop = localStorage.getItem("djdt.top"); - if (handleTop) { - handleTop = Math.min( - handleTop, - window.innerHeight - handle.offsetHeight - ); - handle.style.top = handleTop + "px"; - } - + djdt.ensureHandleVisibility(); + window.addEventListener("resize", djdt.ensureHandleVisibility); document.removeEventListener("keydown", onKeyDown); localStorage.setItem("djdt.show", "false"); }, - hide_one_level() { + hideOneLevel() { const win = document.getElementById("djDebugWindow"); if ($$.visible(win)) { $$.hide(win); } else { const toolbar = document.getElementById("djDebugToolbar"); if (toolbar.querySelector("li.djdt-active")) { - djdt.hide_panels(); + djdt.hidePanels(); } else { - djdt.hide_toolbar(); + djdt.hideToolbar(); } } }, - show_toolbar() { + showToolbar() { document.addEventListener("keydown", onKeyDown); + $$.show(document.getElementById("djDebug")); $$.hide(document.getElementById("djDebugToolbarHandle")); $$.show(document.getElementById("djDebugToolbar")); localStorage.setItem("djdt.show", "true"); + window.removeEventListener("resize", djdt.ensureHandleVisibility); + }, + updateOnAjax() { + const sidebarUrl = + document.getElementById("djDebug").dataset.sidebarUrl; + const slowjax = debounce(ajax, 200); + + function handleAjaxResponse(storeId) { + const encodedStoreId = encodeURIComponent(storeId); + const dest = `${sidebarUrl}?store_id=${encodedStoreId}`; + slowjax(dest).then((data) => { + if (djdt.needUpdateOnFetch) { + replaceToolbarState(encodedStoreId, data); + } + }); + } + + // Patch XHR / traditional AJAX requests + const origOpen = XMLHttpRequest.prototype.open; + XMLHttpRequest.prototype.open = function (...args) { + this.addEventListener("load", function () { + // Chromium emits a "Refused to get unsafe header" uncatchable warning + // when the header can't be fetched. While it doesn't impede execution + // it's worrisome to developers. + if ( + this.getAllResponseHeaders().indexOf("djdt-store-id") >= 0 + ) { + handleAjaxResponse(this.getResponseHeader("djdt-store-id")); + } + }); + origOpen.apply(this, args); + }; + + const origFetch = window.fetch; + window.fetch = function (...args) { + // Heads up! Before modifying this code, please be aware of the + // possible unhandled errors that might arise from changing this. + // For details, see + // https://github.com/django-commons/django-debug-toolbar/pull/2100 + const promise = origFetch.apply(this, args); + return promise.then((response) => { + if (response.headers.get("djdt-store-id") !== null) { + try { + handleAjaxResponse( + response.headers.get("djdt-store-id") + ); + } catch (err) { + throw new Error( + `"${err.name}" occurred within django-debug-toolbar: ${err.message}` + ); + } + } + return response; + }); + }; }, cookie: { get(key) { @@ -243,33 +351,35 @@ const djdt = { return null; } - const cookieArray = document.cookie.split("; "), - cookies = {}; + const cookieArray = document.cookie.split("; "); + const cookies = {}; - cookieArray.forEach(function (e) { + for (const e of cookieArray) { const parts = e.split("="); cookies[parts[0]] = parts[1]; - }); + } return cookies[key]; }, - set(key, value, options) { - options = options || {}; - + set(key, value, options = {}) { if (typeof options.expires === "number") { - const days = options.expires, - t = (options.expires = new Date()); - t.setDate(t.getDate() + days); + const days = options.expires; + const expires = new Date(); + expires.setDate(expires.setDate() + days); + options.expires = expires; } document.cookie = [ - encodeURIComponent(key) + "=" + String(value), + `${encodeURIComponent(key)}=${String(value)}`, options.expires - ? "; expires=" + options.expires.toUTCString() + ? `; expires=${options.expires.toUTCString()}` : "", - options.path ? "; path=" + options.path : "", - options.domain ? "; domain=" + options.domain : "", + options.path ? `; path=${options.path}` : "", + options.domain ? `; domain=${options.domain}` : "", options.secure ? "; secure" : "", + "samesite" in options + ? `; samesite=${options.samesite}` + : "; samesite=lax", ].join(""); return value; @@ -277,10 +387,10 @@ const djdt = { }, }; window.djdt = { - show_toolbar: djdt.show_toolbar, - hide_toolbar: djdt.hide_toolbar, + show_toolbar: djdt.showToolbar, + hide_toolbar: djdt.hideToolbar, init: djdt.init, - close: djdt.hide_one_level, + close: djdt.hideOneLevel, cookie: djdt.cookie, }; diff --git a/debug_toolbar/static/debug_toolbar/js/utils.js b/debug_toolbar/static/debug_toolbar/js/utils.js index 98e7e4122..0cfa80474 100644 --- a/debug_toolbar/static/debug_toolbar/js/utils.js +++ b/debug_toolbar/static/debug_toolbar/js/utils.js @@ -1,12 +1,28 @@ const $$ = { on(root, eventName, selector, fn) { - root.addEventListener(eventName, function (event) { + root.removeEventListener(eventName, fn); + root.addEventListener(eventName, (event) => { const target = event.target.closest(selector); if (root.contains(target)) { fn.call(target, event); } }); }, + onPanelRender(root, panelId, fn) { + /* + This is a helper function to attach a handler for a `djdt.panel.render` + event of a specific panel. + + root: The container element that the listener should be attached to. + panelId: The Id of the panel. + fn: A function to execute when the event is triggered. + */ + root.addEventListener("djdt.panel.render", (event) => { + if (event.detail.panelId === panelId) { + fn.call(event); + } + }); + }, show(element) { element.classList.remove("djdt-hidden"); }, @@ -24,33 +40,57 @@ const $$ = { return !element.classList.contains("djdt-hidden"); }, executeScripts(scripts) { - scripts.forEach(function (script) { + for (const script of scripts) { const el = document.createElement("script"); el.type = "module"; el.src = script; el.async = true; document.head.appendChild(el); - }); + } + }, + applyStyles(container) { + /* + * Given a container element, apply styles set via data-djdt-styles attribute. + * The format is data-djdt-styles="styleName1:value;styleName2:value2" + * The style names should use the CSSStyleDeclaration camel cased names. + */ + for (const element of container.querySelectorAll( + "[data-djdt-styles]" + )) { + const styles = element.dataset.djdtStyles || ""; + for (const styleText of styles.split(";")) { + const styleKeyPair = styleText.split(":"); + if (styleKeyPair.length === 2) { + const name = styleKeyPair[0].trim(); + const value = styleKeyPair[1].trim(); + element.style[name] = value; + } + } + } }, }; function ajax(url, init) { - init = Object.assign({ credentials: "same-origin" }, init); - return fetch(url, init) - .then(function (response) { + return fetch(url, Object.assign({ credentials: "same-origin" }, init)) + .then((response) => { if (response.ok) { - return response.json(); + return response + .json() + .catch((error) => + Promise.reject( + new Error( + `The response is a invalid Json object : ${error}` + ) + ) + ); } return Promise.reject( - new Error(response.status + ": " + response.statusText) + new Error(`${response.status}: ${response.statusText}`) ); }) - .catch(function (error) { + .catch((error) => { const win = document.getElementById("djDebugWindow"); - win.innerHTML = - '

' + - error.message + - "

"; + win.innerHTML = `

${error.message}

`; $$.show(win); throw error; }); @@ -69,4 +109,36 @@ function ajaxForm(element) { return ajax(url, ajaxData); } -export { $$, ajax, ajaxForm }; +function replaceToolbarState(newStoreId, data) { + const djDebug = document.getElementById("djDebug"); + djDebug.setAttribute("data-store-id", newStoreId); + // Check if response is empty, it could be due to an expired storeId. + for (const panelId of Object.keys(data)) { + const panel = document.getElementById(panelId); + if (panel) { + panel.outerHTML = data[panelId].content; + document.getElementById(`djdt-${panelId}`).outerHTML = + data[panelId].button; + } + } +} + +function debounce(func, delay) { + let timer = null; + let resolves = []; + + return (...args) => { + clearTimeout(timer); + timer = setTimeout(() => { + const result = func(...args); + for (const r of resolves) { + r(result); + } + resolves = []; + }, delay); + + return new Promise((r) => resolves.push(r)); + }; +} + +export { $$, ajax, ajaxForm, replaceToolbarState, debounce }; diff --git a/debug_toolbar/templates/debug_toolbar/base.html b/debug_toolbar/templates/debug_toolbar/base.html index 28003e224..607863104 100644 --- a/debug_toolbar/templates/debug_toolbar/base.html +++ b/debug_toolbar/templates/debug_toolbar/base.html @@ -1,21 +1,37 @@ -{% load i18n %}{% load static %} - - - +{% load i18n static %} +{% block css %} + + +{% endblock css %} +{% block js %} + +{% endblock js %}
+ {{ toolbar.config.ROOT_TAG_EXTRA_ATTRS|safe }} data-update-on-fetch="{{ toolbar.config.UPDATE_ON_FETCH }}">
-
+
DJDT
diff --git a/debug_toolbar/templates/debug_toolbar/includes/panel_button.html b/debug_toolbar/templates/debug_toolbar/includes/panel_button.html index 344331d8d..bc6f03ad9 100644 --- a/debug_toolbar/templates/debug_toolbar/includes/panel_button.html +++ b/debug_toolbar/templates/debug_toolbar/includes/panel_button.html @@ -1,7 +1,7 @@ {% load i18n %}
  • - + {% if panel.has_content and panel.enabled %} {% else %} @@ -9,7 +9,7 @@ {% endif %} {{ panel.nav_title }} {% if panel.enabled %} - {% with panel.nav_subtitle as subtitle %} + {% with subtitle=panel.nav_subtitle %} {% if subtitle %}
    {{ subtitle }}{% endif %} {% endwith %} {% endif %} diff --git a/debug_toolbar/templates/debug_toolbar/includes/panel_content.html b/debug_toolbar/templates/debug_toolbar/includes/panel_content.html index 2c1a1b195..d797421a5 100644 --- a/debug_toolbar/templates/debug_toolbar/includes/panel_content.html +++ b/debug_toolbar/templates/debug_toolbar/includes/panel_content.html @@ -3,15 +3,16 @@ {% if panel.has_content and panel.enabled %}
    -

    {{ panel.title }}

    +
    - {% if toolbar.store_id %} + {% if toolbar.should_render_panels %} + {% for script in panel.scripts %}{% endfor %} +
    {{ panel.content }}
    + {% else %}
    - {% else %} -
    {{ panel.content }}
    {% endif %}
    diff --git a/debug_toolbar/templates/debug_toolbar/includes/theme_selector.html b/debug_toolbar/templates/debug_toolbar/includes/theme_selector.html new file mode 100644 index 000000000..926ff250b --- /dev/null +++ b/debug_toolbar/templates/debug_toolbar/includes/theme_selector.html @@ -0,0 +1,41 @@ + + + diff --git a/debug_toolbar/templates/debug_toolbar/panels/alerts.html b/debug_toolbar/templates/debug_toolbar/panels/alerts.html new file mode 100644 index 000000000..6665033fb --- /dev/null +++ b/debug_toolbar/templates/debug_toolbar/panels/alerts.html @@ -0,0 +1,12 @@ +{% load i18n %} + +{% if alerts %} +

    {% translate "Alerts found" %}

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

    {% translate "No alerts found" %}

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

    {% trans "Summary" %}

    +

    {% translate "Summary" %}

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

    {% trans "Summary" %}

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

    {% trans "Commands" %}

    +

    {% translate "Commands" %}

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

    {% trans "Commands" %}

    {% if calls %} -

    {% trans "Calls" %}

    +

    {% translate "Calls" %}

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

    {% trans "Request headers" %}

    +

    {% translate "Request headers" %}

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

    {% trans "Request headers" %}

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

    {% trans "Response headers" %}

    +

    {% translate "Response headers" %}

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

    {% trans "Response headers" %}

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

    {% trans "WSGI environ" %}

    +

    {% translate "WSGI environ" %}

    -

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

    +

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

    - - + + diff --git a/debug_toolbar/templates/debug_toolbar/panels/history.html b/debug_toolbar/templates/debug_toolbar/panels/history.html index c43869bc5..ba7823d22 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/history.html +++ b/debug_toolbar/templates/debug_toolbar/panels/history.html @@ -1,16 +1,17 @@ -{% load i18n %}{% load static %} +{% load i18n static %} - {{ refresh_form }} + {{ refresh_form.as_div }} -
    {% trans "Key" %}{% trans "Value" %}{% translate "Key" %}{% translate "Value" %}
    +
    - - - - - + + + + + + diff --git a/debug_toolbar/templates/debug_toolbar/panels/history_tr.html b/debug_toolbar/templates/debug_toolbar/panels/history_tr.html index 9ce984396..1642b4a47 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/history_tr.html +++ b/debug_toolbar/templates/debug_toolbar/panels/history_tr.html @@ -19,8 +19,8 @@ - - + + @@ -38,9 +38,12 @@
    {% trans "Time" %}{% trans "Method" %}{% trans "Path" %}{% trans "Request Variables" %}{% trans "Action" %}{% translate "Time" %}{% translate "Method" %}{% translate "Path" %}{% translate "Request Variables" %}{% translate "Status" %}{% translate "Action" %}
    {% trans "Variable" %}{% trans "Value" %}{% translate "Variable" %}{% translate "Value" %}
  • + +

    {{ store_context.toolbar.stats.HistoryPanel.status_code|escape }}

    +
    - {{ store_context.form }} + {{ store_context.form.as_div }}
    diff --git a/debug_toolbar/templates/debug_toolbar/panels/logging.html b/debug_toolbar/templates/debug_toolbar/panels/logging.html deleted file mode 100644 index 54fe3bebe..000000000 --- a/debug_toolbar/templates/debug_toolbar/panels/logging.html +++ /dev/null @@ -1,27 +0,0 @@ -{% load i18n %} -{% if records %} - - - - - - - - - - - - {% for record in records %} - - - - - - - - {% endfor %} - -
    {% trans "Level" %}{% trans "Time" %}{% trans "Channel" %}{% trans "Message" %}{% trans "Location" %}
    {{ record.level }}{{ record.time|date:"h:i:s m/d/Y" }}{{ record.channel|default:"-" }}{{ record.message|linebreaksbr }}{{ record.file }}:{{ record.line }}
    -{% else %} -

    {% trans "No messages logged" %}.

    -{% endif %} diff --git a/debug_toolbar/templates/debug_toolbar/panels/profiling.html b/debug_toolbar/templates/debug_toolbar/panels/profiling.html index ac48f8a3a..0c2206a13 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/profiling.html +++ b/debug_toolbar/templates/debug_toolbar/panels/profiling.html @@ -2,19 +2,19 @@ - - - - - - + + + + + + {% for call in func_list %} - +
    {% trans "Call" %}{% trans "CumTime" %}{% trans "Per" %}{% trans "TotTime" %}{% trans "Per" %}{% trans "Count" %}{% translate "Call" %}{% translate "CumTime" %}{% translate "Per" %}{% translate "TotTime" %}{% translate "Per" %}{% translate "Count" %}
    -
    +
    {% if call.has_subfuncs %} {% else %} diff --git a/debug_toolbar/templates/debug_toolbar/panels/request.html b/debug_toolbar/templates/debug_toolbar/panels/request.html index 3f9b068be..4a16468b5 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/request.html +++ b/debug_toolbar/templates/debug_toolbar/panels/request.html @@ -1,13 +1,13 @@ {% load i18n %} -

    {% trans "View information" %}

    +

    {% translate "View information" %}

    - - - - + + + + @@ -20,30 +20,30 @@

    {% trans "View information" %}

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

    {% trans "Cookies" %}

    +{% if cookies.list or cookies.raw %} +

    {% translate "Cookies" %}

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

    {% trans "No cookies" %}

    +

    {% translate "No cookies" %}

    {% endif %} -{% if session %} -

    {% trans "Session data" %}

    +{% if session.list or session.raw %} +

    {% translate "Session data" %}

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

    {% trans "No session data" %}

    +

    {% translate "No session data" %}

    {% endif %} -{% if get %} -

    {% trans "GET data" %}

    +{% if get.list or get.raw %} +

    {% translate "GET data" %}

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

    {% trans "No GET data" %}

    +

    {% translate "No GET data" %}

    {% endif %} -{% if post %} -

    {% trans "POST data" %}

    +{% if post.list or post.raw %} +

    {% translate "POST data" %}

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

    {% trans "No POST data" %}

    +

    {% translate "No POST data" %}

    {% endif %} diff --git a/debug_toolbar/templates/debug_toolbar/panels/request_variables.html b/debug_toolbar/templates/debug_toolbar/panels/request_variables.html index 7e9118c7d..26b487ab0 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/request_variables.html +++ b/debug_toolbar/templates/debug_toolbar/panels/request_variables.html @@ -1,5 +1,6 @@ {% load i18n %} +{% if variables.list %} @@ -7,12 +8,12 @@ - - + + - {% for key, value in variables %} + {% for key, value in variables.list %} @@ -20,3 +21,6 @@ {% endfor %}
    {% trans "Variable" %}{% trans "Value" %}{% translate "Variable" %}{% translate "Value" %}
    {{ key|pprint }} {{ value|pprint }}
    +{% elif variables.raw %} +{{ variables.raw|pprint }} +{% endif %} diff --git a/debug_toolbar/templates/debug_toolbar/panels/settings.html b/debug_toolbar/templates/debug_toolbar/panels/settings.html index 14763e4e6..5214c1b42 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/settings.html +++ b/debug_toolbar/templates/debug_toolbar/panels/settings.html @@ -2,8 +2,8 @@ - - + + diff --git a/debug_toolbar/templates/debug_toolbar/panels/signals.html b/debug_toolbar/templates/debug_toolbar/panels/signals.html index cd9f42c4a..abd648924 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/signals.html +++ b/debug_toolbar/templates/debug_toolbar/panels/signals.html @@ -2,8 +2,8 @@
    {% trans "Setting" %}{% trans "Value" %}{% translate "Setting" %}{% translate "Value" %}
    - - + + diff --git a/debug_toolbar/templates/debug_toolbar/panels/sql.html b/debug_toolbar/templates/debug_toolbar/panels/sql.html index 8f81b7e09..63cf293c1 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/sql.html +++ b/debug_toolbar/templates/debug_toolbar/panels/sql.html @@ -2,16 +2,16 @@
      {% for alias, info in databases %}
    • - {{ alias }} - {{ info.time_spent|floatformat:"2" }} ms ({% blocktrans count info.num_queries as num %}{{ num }} query{% plural %}{{ num }} queries{% endblocktrans %} + {{ alias }} + {{ info.time_spent|floatformat:"2" }} ms ({% blocktranslate count num=info.num_queries %}{{ num }} query{% plural %}{{ num }} queries{% endblocktranslate %} {% if info.similar_count %} - {% blocktrans with count=info.similar_count trimmed %} + {% blocktranslate with count=info.similar_count trimmed %} including {{ count }} similar - {% endblocktrans %} + {% endblocktranslate %} {% if info.duplicate_count %} - {% blocktrans with dupes=info.duplicate_count trimmed %} + {% blocktranslate with dupes=info.duplicate_count trimmed %} and {{ dupes }} duplicates - {% endblocktrans %} + {% endblocktranslate %} {% endif %} {% endif %})
    • @@ -31,16 +31,16 @@
    - - - - + + + + {% for query in queries %} - + @@ -48,14 +48,14 @@
    {{ query.sql|safe }}
    {% if query.similar_count %} - - {% blocktrans with count=query.similar_count %}{{ count }} similar queries.{% endblocktrans %} + + {% blocktranslate with count=query.similar_count %}{{ count }} similar queries.{% endblocktranslate %} {% endif %} {% if query.duplicate_count %} - - {% blocktrans with dupes=query.duplicate_count %}Duplicated {{ dupes }} times.{% endblocktrans %} + + {% blocktranslate with dupes=query.duplicate_count %}Duplicated {{ dupes }} times.{% endblocktranslate %} {% endif %} @@ -77,7 +77,7 @@ {% if query.params %} {% if query.is_select %} - {{ query.form }} + {{ query.form.as_div }} {% if query.vendor == 'mysql' %} @@ -92,12 +92,12 @@
    {% trans "Signal" %}{% trans "Receivers" %}{% translate "Signal" %}{% translate "Receivers" %}
    {% trans "Query" %}{% trans "Timeline" %}{% trans "Time (ms)" %}{% trans "Action" %}{% translate "Query" %}{% translate "Timeline" %}{% translate "Time (ms)" %}{% translate "Action" %}
    -

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

    +

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

    {% if query.iso_level %} -

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

    +

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

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

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

    +

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

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

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

    +

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

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

    {% translate "SQL explained" %}

    -

    {% trans "SQL explained" %}

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

    {% translate "SQL profiled" %}

    -

    {% trans "SQL profiled" %}

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

    {% trans "SQL profiled" %}

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

    {% translate "SQL selected" %}

    -

    {% trans "SQL selected" %}

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

    {% trans "SQL selected" %}

    {% else %} -

    {% trans "Empty set" %}

    +

    {% translate "Empty set" %}

    {% endif %}
    diff --git a/debug_toolbar/templates/debug_toolbar/panels/sql_stacktrace.html b/debug_toolbar/templates/debug_toolbar/panels/sql_stacktrace.html deleted file mode 100644 index 426783b93..000000000 --- a/debug_toolbar/templates/debug_toolbar/panels/sql_stacktrace.html +++ /dev/null @@ -1,4 +0,0 @@ -{% for s in stacktrace %}{{s.0}}/{{s.1}} in {{s.3}}({{s.2}}) - {{s.4}} - {% if show_locals %}
    {{s.5|pprint}}
    {% endif %} -{% endfor %} diff --git a/debug_toolbar/templates/debug_toolbar/panels/staticfiles.html b/debug_toolbar/templates/debug_toolbar/panels/staticfiles.html index 9aa519f67..aaa7c78ab 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/staticfiles.html +++ b/debug_toolbar/templates/debug_toolbar/panels/staticfiles.html @@ -1,17 +1,17 @@ {% load i18n %} -

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

    +

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

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

    {% trans "None" %}

    +

    {% translate "None" %}

    {% endif %} -

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

    +

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

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

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

    {% else %} -

    {% trans "None" %}

    +

    {% translate "None" %}

    {% endif %} -

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

    +

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

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

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

    {% else %} -

    {% trans "None" %}

    +

    {% translate "None" %}

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

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

    +

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

    - - + + diff --git a/debug_toolbar/templates/debug_toolbar/panels/template_source.html b/debug_toolbar/templates/debug_toolbar/panels/template_source.html index 229ea83e4..4d47fd3c3 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/template_source.html +++ b/debug_toolbar/templates/debug_toolbar/panels/template_source.html @@ -1,14 +1,10 @@ {% load i18n %}
    +

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

    -

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

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

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

    +

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

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

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

    {% else %} -

    {% trans "None" %}

    +

    {% translate "None" %}

    {% endif %} -

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

    +

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

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

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

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

    {% else %} -

    {% trans "None" %}

    +

    {% translate "None" %}

    {% endif %} -

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

    +

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

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

    {% trans "None" %}

    +

    {% translate "None" %}

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

    {% trans "Resource usage" %}

    +

    {% translate "Resource usage" %}

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

    {% trans "Resource usage" %}

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

    {% trans "Resource usage" %}

    -

    {% trans "Browser timing" %}

    +

    {% translate "Browser timing" %}

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

    {% trans "Browser timing" %}

    - - - + + + diff --git a/debug_toolbar/templates/debug_toolbar/panels/versions.html b/debug_toolbar/templates/debug_toolbar/panels/versions.html index d0ade6cfb..3428c0561 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/versions.html +++ b/debug_toolbar/templates/debug_toolbar/panels/versions.html @@ -7,9 +7,9 @@ - - - + + + diff --git a/debug_toolbar/templates/debug_toolbar/redirect.html b/debug_toolbar/templates/debug_toolbar/redirect.html index 96b97de2d..46897846d 100644 --- a/debug_toolbar/templates/debug_toolbar/redirect.html +++ b/debug_toolbar/templates/debug_toolbar/redirect.html @@ -3,13 +3,13 @@ Django Debug Toolbar Redirects Panel: {{ status_line }} - +

    {{ status_line }}

    -

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

    +

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

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

    diff --git a/debug_toolbar/toolbar.py b/debug_toolbar/toolbar.py index 9638ba1f8..04e5894c5 100644 --- a/debug_toolbar/toolbar.py +++ b/debug_toolbar/toolbar.py @@ -2,20 +2,31 @@ The main DebugToolbar class that loads and renders the Toolbar. """ +import re import uuid from collections import OrderedDict +from functools import cache from django.apps import apps +from django.conf import settings from django.core.exceptions import ImproperlyConfigured +from django.core.handlers.asgi import ASGIRequest +from django.dispatch import Signal from django.template import TemplateSyntaxError from django.template.loader import render_to_string -from django.urls import path +from django.urls import include, path, re_path, resolve +from django.urls.exceptions import Resolver404 from django.utils.module_loading import import_string +from django.utils.translation import get_language, override as lang_override -from debug_toolbar import settings as dt_settings +from debug_toolbar import APP_NAME, settings as dt_settings +from debug_toolbar.panels import Panel class DebugToolbar: + # for internal testing use only + _created = Signal() + def __init__(self, request, get_response): self.request = request self.config = dt_settings.get_config().copy() @@ -26,13 +37,17 @@ def __init__(self, request, get_response): if panel.enabled: get_response = panel.process_request self.process_request = get_response - self._panels = OrderedDict() + # Use OrderedDict for the _panels attribute so that items can be efficiently + # removed using FIFO order in the DebugToolbar.store() method. The .popitem() + # method of Python's built-in dict only supports LIFO removal. + self._panels = OrderedDict[str, Panel]() while panels: panel = panels.pop() self._panels[panel.panel_id] = panel self.stats = {} self.server_timing_stats = {} self.store_id = None + self._created.send(request, toolbar=self) # Manage panels @@ -50,6 +65,16 @@ def enabled_panels(self): """ return [panel for panel in self._panels.values() if panel.enabled] + @property + def csp_nonce(self): + """ + Look up the Content Security Policy nonce if there is one. + + This is built specifically for django-csp, which may not always + have a nonce associated with the request. + """ + return getattr(self.request, "csp_nonce", None) + def get_panel_by_id(self, panel_id): """ Get the panel with the given id, which is the class name by default. @@ -66,21 +91,37 @@ def render_toolbar(self): self.store() try: context = {"toolbar": self} - return render_to_string("debug_toolbar/base.html", context) + lang = self.config["TOOLBAR_LANGUAGE"] or get_language() + with lang_override(lang): + return render_to_string("debug_toolbar/base.html", context) except TemplateSyntaxError: if not apps.is_installed("django.contrib.staticfiles"): raise ImproperlyConfigured( "The debug toolbar requires the staticfiles contrib app. " "Add 'django.contrib.staticfiles' to INSTALLED_APPS and " "define STATIC_URL in your settings." - ) + ) from None else: raise def should_render_panels(self): - render_panels = self.config["RENDER_PANELS"] - if render_panels is None: - render_panels = self.request.META["wsgi.multiprocess"] + """Determine whether the panels should be rendered during the request + + If False, the panels will be loaded via Ajax. + """ + if (render_panels := self.config["RENDER_PANELS"]) is None: + # If wsgi.multiprocess is true then it is either being served + # from ASGI or multithreaded third-party WSGI server eg gunicorn. + # we need to make special check for ASGI for supporting + # async context based requests. + if isinstance(self.request, ASGIRequest): + render_panels = False + else: + # The wsgi.multiprocess case of being True isn't supported until the + # toolbar has resolved the following issue: + # This type of set up is most likely + # https://github.com/django-commons/django-debug-toolbar/issues/1430 + render_panels = self.request.META.get("wsgi.multiprocess", True) return render_panels # Handle storing toolbars in memory and fetching them later on @@ -125,7 +166,7 @@ def get_urls(cls): # Load URLs in a temporary variable for thread safety. # Global URLs urlpatterns = [ - path("render_panel/", views.render_panel, name="render_panel") + path("render_panel/", views.render_panel, name="render_panel"), ] # Per-panel URLs for panel_class in cls.get_panel_classes(): @@ -133,6 +174,59 @@ def get_urls(cls): cls._urlpatterns = urlpatterns return cls._urlpatterns - -app_name = "djdt" -urlpatterns = DebugToolbar.get_urls() + @classmethod + def is_toolbar_request(cls, request): + """ + Determine if the request is for a DebugToolbar view. + """ + # The primary caller of this function is in the middleware which may + # not have resolver_match set. + try: + resolver_match = request.resolver_match or resolve( + request.path_info, getattr(request, "urlconf", None) + ) + except Resolver404: + return False + return resolver_match.namespaces and resolver_match.namespaces[-1] == APP_NAME + + @staticmethod + @cache + def get_observe_request(): + # If OBSERVE_REQUEST_CALLBACK is a string, which is the recommended + # setup, resolve it to the corresponding callable. + func_or_path = dt_settings.get_config()["OBSERVE_REQUEST_CALLBACK"] + if isinstance(func_or_path, str): + return import_string(func_or_path) + else: + return func_or_path + + +def observe_request(request): + """ + Determine whether to update the toolbar from a client side request. + """ + return True + + +def debug_toolbar_urls(prefix="__debug__"): + """ + Return a URL pattern for serving toolbar in debug mode. + + from django.conf import settings + from debug_toolbar.toolbar import debug_toolbar_urls + + urlpatterns = [ + # ... the rest of your URLconf goes here ... + ] + debug_toolbar_urls() + """ + if not prefix: + raise ImproperlyConfigured("Empty urls prefix not permitted") + elif not settings.DEBUG: + # No-op if not in debug mode. + return [] + return [ + re_path( + r"^{}/".format(re.escape(prefix.lstrip("/"))), + include("debug_toolbar.urls"), + ), + ] diff --git a/debug_toolbar/urls.py b/debug_toolbar/urls.py new file mode 100644 index 000000000..5aa0d69e9 --- /dev/null +++ b/debug_toolbar/urls.py @@ -0,0 +1,5 @@ +from debug_toolbar import APP_NAME +from debug_toolbar.toolbar import DebugToolbar + +app_name = APP_NAME +urlpatterns = DebugToolbar.get_urls() diff --git a/debug_toolbar/utils.py b/debug_toolbar/utils.py index 30344dd03..f4b3eac38 100644 --- a/debug_toolbar/utils.py +++ b/debug_toolbar/utils.py @@ -1,66 +1,67 @@ +from __future__ import annotations + import inspect +import linecache import os.path -import re import sys -from importlib import import_module -from itertools import chain +import warnings +from collections.abc import Sequence +from pprint import PrettyPrinter, pformat +from typing import Any -import django -from django.core.exceptions import ImproperlyConfigured +from asgiref.local import Local +from django.http import QueryDict from django.template import Node -from django.template.loader import render_to_string -from django.utils.safestring import mark_safe - -from debug_toolbar import settings as dt_settings - -try: - import threading -except ImportError: - threading = None - - -# Figure out some paths -django_path = os.path.realpath(os.path.dirname(django.__file__)) - - -def get_module_path(module_name): - try: - module = import_module(module_name) - except ImportError as e: - raise ImproperlyConfigured("Error importing HIDE_IN_STACKTRACES: {}".format(e)) - else: - source_path = inspect.getsourcefile(module) - if source_path.endswith("__init__.py"): - source_path = os.path.dirname(source_path) - return os.path.realpath(source_path) - - -hidden_paths = [ - get_module_path(module_name) - for module_name in dt_settings.get_config()["HIDE_IN_STACKTRACES"] -] +from django.utils.html import format_html +from django.utils.safestring import SafeString, mark_safe +from django.views.debug import get_default_exception_reporter_filter + +from debug_toolbar import _stubs as stubs, settings as dt_settings + +_local_data = Local() +safe_filter = get_default_exception_reporter_filter() + + +def _is_excluded_frame(frame: Any, excluded_modules: Sequence[str] | None) -> bool: + if not excluded_modules: + return False + frame_module = frame.f_globals.get("__name__") + if not isinstance(frame_module, str): + return False + return any( + frame_module == excluded_module + or frame_module.startswith(excluded_module + ".") + for excluded_module in excluded_modules + ) -def omit_path(path): - return any(path.startswith(hidden_path) for hidden_path in hidden_paths) +def _stack_trace_deprecation_warning() -> None: + warnings.warn( + "get_stack() and tidy_stacktrace() are deprecated in favor of" + " get_stack_trace()", + DeprecationWarning, + stacklevel=2, + ) -def tidy_stacktrace(stack): +def tidy_stacktrace(stack: list[stubs.InspectStack]) -> stubs.TidyStackTrace: """ - Clean up stacktrace and remove all entries that: - 1. Are part of Django (except contrib apps) - 2. Are part of socketserver (used by Django's dev server) - 3. Are the last entry (which is part of our stacktracing code) + Clean up stacktrace and remove all entries that are excluded by the + HIDE_IN_STACKTRACES setting. - ``stack`` should be a list of frame tuples from ``inspect.stack()`` + ``stack`` should be a list of frame tuples from ``inspect.stack()`` or + ``debug_toolbar.utils.get_stack()``. """ + _stack_trace_deprecation_warning() + trace = [] + excluded_modules = dt_settings.get_config()["HIDE_IN_STACKTRACES"] for frame, path, line_no, func_name, text in (f[:5] for f in stack): - if omit_path(os.path.realpath(path)): + if _is_excluded_frame(frame, excluded_modules): continue text = "".join(text).strip() if text else "" frame_locals = ( - frame.f_locals + pformat(frame.f_locals) if dt_settings.get_config()["ENABLE_STACKTRACES_LOCALS"] else None ) @@ -68,30 +69,42 @@ def tidy_stacktrace(stack): return trace -def render_stacktrace(trace): - stacktrace = [] - for frame in trace: - params = (v for v in chain(frame[0].rsplit(os.path.sep, 1), frame[1:])) - params_dict = {str(idx): v for idx, v in enumerate(params)} - try: - stacktrace.append(params_dict) - except KeyError: - # This frame doesn't have the expected format, so skip it and move - # on to the next one - continue - - return mark_safe( - render_to_string( - "debug_toolbar/panels/sql_stacktrace.html", - { - "stacktrace": stacktrace, - "show_locals": dt_settings.get_config()["ENABLE_STACKTRACES_LOCALS"], - }, +def render_stacktrace(trace: stubs.TidyStackTrace) -> SafeString: + show_locals = dt_settings.get_config()["ENABLE_STACKTRACES_LOCALS"] + html = "" + for abspath, lineno, func, code, locals_ in trace: + if os.path.sep in abspath: + directory, filename = abspath.rsplit(os.path.sep, 1) + # We want the separator to appear in the UI so add it back. + directory += os.path.sep + else: + # abspath could be something like "" + directory = "" + filename = abspath + html += format_html( + ( + '{}' + + '{} in' + + ' {}' + + '({})\n' + + ' {}\n' + ), + directory, + filename, + func, + lineno, + code, ) - ) + if show_locals: + html += format_html( + '
    {}
    \n', + locals_, + ) + html += "\n" + return mark_safe(html) -def get_template_info(): +def get_template_info() -> dict[str, Any] | None: template_info = None cur_frame = sys._getframe().f_back try: @@ -119,7 +132,9 @@ def get_template_info(): return template_info -def get_template_context(node, context, context_lines=3): +def get_template_context( + node: Node, context: stubs.RequestContext, context_lines: int = 3 +) -> dict[str, Any]: line, source_lines, name = get_template_source_from_exception_info(node, context) debug_context = [] start = max(1, line - context_lines) @@ -134,28 +149,36 @@ def get_template_context(node, context, context_lines=3): return {"name": name, "context": debug_context} -def get_template_source_from_exception_info(node, context): - exception_info = context.template.get_exception_info(Exception("DDT"), node.token) +def get_template_source_from_exception_info( + node: Node, context: stubs.RequestContext +) -> tuple[int, list[tuple[int, str]], str]: + if context.template.origin == node.origin: + exception_info = context.template.get_exception_info( + Exception("DDT"), node.token + ) + else: + exception_info = context.render_context.template.get_exception_info( + Exception("DDT"), node.token + ) line = exception_info["line"] source_lines = exception_info["source_lines"] name = exception_info["name"] return line, source_lines, name -def get_name_from_obj(obj): - if hasattr(obj, "__name__"): - name = obj.__name__ - else: - name = obj.__class__.__name__ - - if hasattr(obj, "__module__"): - module = obj.__module__ - name = "{}.{}".format(module, name) - - return name +def get_name_from_obj(obj: Any) -> str: + """Get the best name as `str` from a view or a object.""" + # This is essentially a rewrite of the `django.contrib.admindocs.utils.get_view_name` + # https://github.com/django/django/blob/9a22d1769b042a88741f0ff3087f10d94f325d86/django/contrib/admindocs/utils.py#L26-L32 + if hasattr(obj, "view_class"): + klass = obj.view_class + return f"{klass.__module__}.{klass.__qualname__}" + mod_name = obj.__module__ + view_name = getattr(obj, "__qualname__", obj.__class__.__name__) + return mod_name + "." + view_name -def getframeinfo(frame, context=1): +def getframeinfo(frame: Any, context: int = 1) -> inspect.Traceback: """ Get information about a frame or traceback object. @@ -182,35 +205,65 @@ def getframeinfo(frame, context=1): try: lines, lnum = inspect.findsource(frame) except Exception: # findsource raises platform-dependant exceptions - first_lines = lines = index = None + lines = index = None else: start = max(start, 1) start = max(0, min(start, len(lines) - context)) - first_lines = lines[:2] lines = lines[start : (start + context)] index = lineno - 1 - start else: - first_lines = lines = index = None - - # Code taken from Django's ExceptionReporter._get_lines_from_file - if first_lines and isinstance(first_lines[0], bytes): - encoding = "ascii" - for line in first_lines[:2]: - # File coding may be specified. Match pattern from PEP-263 - # (https://www.python.org/dev/peps/pep-0263/) - match = re.search(br"coding[:=]\s*([-\w.]+)", line) - if match: - encoding = match.group(1).decode("ascii") - break - lines = [line.decode(encoding, "replace") for line in lines] + lines = index = None + + return inspect.Traceback(filename, lineno, frame.f_code.co_name, lines, index) + - if hasattr(inspect, "Traceback"): - return inspect.Traceback(filename, lineno, frame.f_code.co_name, lines, index) +def sanitize_and_sort_request_vars( + variable: dict[str, Any] | QueryDict, +) -> dict[str, list[tuple[str, Any]] | Any]: + """ + Get a data structure for showing a sorted list of variables from the + request data with sensitive values redacted. + """ + if not isinstance(variable, (dict, QueryDict)): + return {"raw": variable} + + # Get sorted keys if possible, otherwise just list them + keys = _get_sorted_keys(variable) + + # Process the variable based on its type + if isinstance(variable, QueryDict): + result = _process_query_dict(variable, keys) else: - return (filename, lineno, frame.f_code.co_name, lines, index) + result = _process_dict(variable, keys) + + return {"list": result} + + +def _get_sorted_keys(variable): + """Helper function to get sorted keys if possible.""" + try: + return sorted(variable) + except TypeError: + return list(variable) -def get_stack(context=1): +def _process_query_dict(query_dict, keys): + """Process a QueryDict into a list of (key, sanitized_value) tuples.""" + result = [] + for k in keys: + values = query_dict.getlist(k) + # Return single value if there's only one, otherwise keep as list + value = values[0] if len(values) == 1 else values + result.append((k, safe_filter.cleanse_setting(k, value))) + return result + + +def _process_dict(dictionary, keys): + """Process a dictionary into a list of (key, sanitized_value) tuples.""" + return [(k, safe_filter.cleanse_setting(k, dictionary.get(k))) for k in keys] + + +def get_stack(context=1) -> list[stubs.InspectStack]: """ Get a list of records for a frame and all higher (calling) frames. @@ -219,6 +272,8 @@ def get_stack(context=1): Modified version of ``inspect.stack()`` which calls our own ``getframeinfo()`` """ + _stack_trace_deprecation_warning() + frame = sys._getframe(1) framelist = [] while frame: @@ -227,31 +282,122 @@ def get_stack(context=1): return framelist -class ThreadCollector: +def _stack_frames(*, skip=0): + skip += 1 # Skip the frame for this generator. + frame = inspect.currentframe() + while frame is not None: + if skip > 0: + skip -= 1 + else: + yield frame + frame = frame.f_back + + +class _StackTraceRecorder: + pretty_printer = PrettyPrinter() + def __init__(self): - if threading is None: - raise NotImplementedError( - "threading module is not available, " - "this panel cannot be used without it" - ) - self.collections = {} # a dictionary that maps threads to collections - - def get_collection(self, thread=None): - """ - Returns a list of collected items for the provided thread, of if none - is provided, returns a list for the current thread. - """ - if thread is None: - thread = threading.currentThread() - if thread not in self.collections: - self.collections[thread] = [] - return self.collections[thread] - - def clear_collection(self, thread=None): - if thread is None: - thread = threading.currentThread() - if thread in self.collections: - del self.collections[thread] - - def collect(self, item, thread=None): - self.get_collection(thread).append(item) + self.filename_cache = {} + + def get_source_file(self, frame): + frame_filename = frame.f_code.co_filename + + value = self.filename_cache.get(frame_filename) + if value is None: + filename = inspect.getsourcefile(frame) + if filename is None: + is_source = False + filename = frame_filename + else: + is_source = True + # Ensure linecache validity the first time this recorder + # encounters the filename in this frame. + linecache.checkcache(filename) + value = (filename, is_source) + self.filename_cache[frame_filename] = value + + return value + + def get_stack_trace( + self, + *, + excluded_modules: Sequence[str] | None = None, + include_locals: bool = False, + skip: int = 0, + ): + trace = [] + skip += 1 # Skip the frame for this method. + for frame in _stack_frames(skip=skip): + if _is_excluded_frame(frame, excluded_modules): + continue + + filename, is_source = self.get_source_file(frame) + + line_no = frame.f_lineno + func_name = frame.f_code.co_name + + if is_source: + module = inspect.getmodule(frame, filename) + module_globals = module.__dict__ if module is not None else None + source_line = linecache.getline( + filename, line_no, module_globals + ).strip() + else: + source_line = "" + + if include_locals: + frame_locals = self.pretty_printer.pformat(frame.f_locals) + else: + frame_locals = None + + trace.append((filename, line_no, func_name, source_line, frame_locals)) + trace.reverse() + return trace + + +def get_stack_trace(*, skip=0): + """ + Return a processed stack trace for the current call stack. + + If the ``ENABLE_STACKTRACES`` setting is False, return an empty :class:`list`. + Otherwise return a :class:`list` of processed stack frame tuples (file name, line + number, function name, source line, frame locals) for the current call stack. The + first entry in the list will be for the bottom of the stack and the last entry will + be for the top of the stack. + + ``skip`` is an :class:`int` indicating the number of stack frames above the frame + for this function to omit from the stack trace. The default value of ``0`` means + that the entry for the caller of this function will be the last entry in the + returned stack trace. + """ + config = dt_settings.get_config() + if not config["ENABLE_STACKTRACES"]: + return [] + skip += 1 # Skip the frame for this function. + stack_trace_recorder = getattr(_local_data, "stack_trace_recorder", None) + if stack_trace_recorder is None: + stack_trace_recorder = _StackTraceRecorder() + _local_data.stack_trace_recorder = stack_trace_recorder + return stack_trace_recorder.get_stack_trace( + excluded_modules=config["HIDE_IN_STACKTRACES"], + include_locals=config["ENABLE_STACKTRACES_LOCALS"], + skip=skip, + ) + + +def clear_stack_trace_caches(): + if hasattr(_local_data, "stack_trace_recorder"): + del _local_data.stack_trace_recorder + + +_HTML_TYPES = ("text/html", "application/xhtml+xml") + + +def is_processable_html_response(response): + content_encoding = response.get("Content-Encoding", "") + content_type = response.get("Content-Type", "").split(";")[0] + return ( + not getattr(response, "streaming", False) + and content_encoding == "" + and content_type in _HTML_TYPES + ) diff --git a/debug_toolbar/views.py b/debug_toolbar/views.py index 1d319027d..b9a410db5 100644 --- a/debug_toolbar/views.py +++ b/debug_toolbar/views.py @@ -2,11 +2,14 @@ from django.utils.html import escape from django.utils.translation import gettext as _ -from debug_toolbar.decorators import require_show_toolbar +from debug_toolbar._compat import login_not_required +from debug_toolbar.decorators import render_with_toolbar_language, require_show_toolbar from debug_toolbar.toolbar import DebugToolbar +@login_not_required @require_show_toolbar +@render_with_toolbar_language def render_panel(request): """Render the contents of a panel""" toolbar = DebugToolbar.fetch(request.GET["store_id"]) @@ -15,7 +18,7 @@ def render_panel(request): "Data for this panel isn't available anymore. " "Please reload the page and retry." ) - content = "

    %s

    " % escape(content) + content = f"

    {escape(content)}

    " scripts = [] else: panel = toolbar.get_panel_by_id(request.GET["panel_id"]) diff --git a/docs/architecture.rst b/docs/architecture.rst new file mode 100644 index 000000000..54b3b9318 --- /dev/null +++ b/docs/architecture.rst @@ -0,0 +1,87 @@ +Architecture +============ + +The Django Debug Toolbar is designed to be flexible and extensible for +developers and third-party panel creators. + +Core Components +--------------- + +While there are several components, the majority of logic and complexity +lives within the following: + +- ``debug_toolbar.middleware.DebugToolbarMiddleware`` +- ``debug_toolbar.toolbar.DebugToolbar`` +- ``debug_toolbar.panels`` + +^^^^^^^^^^^^^^^^^^^^^^ +DebugToolbarMiddleware +^^^^^^^^^^^^^^^^^^^^^^ + +The middleware is how the toolbar integrates with Django projects. +It determines if the toolbar should instrument the request, which +panels to use, facilitates the processing of the request and augmenting +the response with the toolbar. Most logic for how the toolbar interacts +with the user's Django project belongs here. + +^^^^^^^^^^^^ +DebugToolbar +^^^^^^^^^^^^ + +The ``DebugToolbar`` class orchestrates the processing of a request +for each of the panels. It contains the logic that needs to be aware +of all the panels, but doesn't need to interact with the user's Django +project. + +^^^^^^ +Panels +^^^^^^ + +The majority of the complex logic lives within the panels themselves. This +is because the panels are responsible for collecting the various metrics. +Some of the metrics are collected via +`monkey-patching `_, such as +``TemplatesPanel``. Others, such as ``SettingsPanel`` don't need to collect +anything and include the data directly in the response. + +Some panels such as ``SQLPanel`` have additional functionality. This tends +to involve a user clicking on something, and the toolbar presenting a new +page with additional data. That additional data is handled in views defined +in the panels package (for example, ``debug_toolbar.panels.sql.views``). + +Logic Flow +---------- + +When a request comes in, the toolbar first interacts with it in the +middleware. If the middleware determines the request should be instrumented, +it will instantiate the toolbar and pass the request for processing. The +toolbar will use the enabled panels to collect information on the request +and/or response. When the toolbar has completed collecting its metrics on +both the request and response, the middleware will collect the results +from the toolbar. It will inject the HTML and JavaScript to render the +toolbar as well as any headers into the response. + +After the browser renders the panel and the user interacts with it, the +toolbar's JavaScript will send requests to the server. If the view handling +the request needs to fetch data from the toolbar, the request must supply +the store ID. This is so that the toolbar can load the collected metrics +for that particular request. + +The history panel allows a user to view the metrics for any request since +the application was started. The toolbar maintains its state entirely in +memory for the process running ``runserver``. If the application is +restarted the toolbar will lose its state. + +Problematic Parts +----------------- + +- ``debug.panels.templates.panel``: This monkey-patches template rendering + when the panel module is loaded +- ``debug.panels.sql``: This package is particularly complex, but provides + the main benefit of the toolbar +- Support for async and multi-threading: ``debug_toolbar.middleware.DebugToolbarMiddleware`` + is now async compatible and can process async requests. However certain + panels such as ``TimerPanel``, ``RequestPanel`` and ``ProfilingPanel`` aren't + fully compatible and currently being worked on. For now, these panels + are disabled by default when running in async environment. + follow the progress of this issue in `Async compatible toolbar project `_. diff --git a/docs/changes.rst b/docs/changes.rst index 13766d80e..6d6f34b2d 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -1,6 +1,437 @@ Change log ========== +Pending +------- + +* Added support for checking if pytest as the test runner when determining + if tests are running. + +5.2.0 (2025-04-29) +------------------ + +* Added hook to RedirectsPanel for subclass customization. +* Added feature to sanitize sensitive data in the Request Panel. +* Fixed dark mode conflict in code block toolbar CSS. +* Properly allowed overriding the system theme preference by using the theme + selector. Removed the ``DEFAULT_THEME`` setting, we should always default to + system-level defaults where possible. +* Added support for using django-template-partials with the template panel's + source view functionality. The same change possibly adds support for other + template loaders. +* Introduced `djade `__ to format Django + templates. +* Swapped display order of panel header and close button to prevent style + conflicts +* Added CSS for resetting the height of elements too to avoid problems with + global CSS of a website where the toolbar is used. + +5.1.0 (2025-03-20) +------------------ + +* Added Django 5.2 to the tox matrix. +* Updated package metadata to include well-known labels. +* Added resources section to the documentation. +* Wrap ``SHOW_TOOLBAR_CALLBACK`` function with ``sync_to_async`` + or ``async_to_sync`` to allow sync/async compatibility. +* Make ``require_toolbar`` decorator compatible to async views. +* Added link to contributing documentation in ``CONTRIBUTING.md``. +* Replaced ESLint and prettier with biome in our pre-commit configuration. +* Added a Makefile target (``make help``) to get a quick overview + of each target. +* Avoided reinitializing the staticfiles storage during instrumentation. +* Avoided a "forked" Promise chain in the rebound ``window.fetch`` function + with missing exception handling. +* Fixed the pygments code highlighting when using dark mode. +* Fix for exception-unhandled "forked" Promise chain in rebound window.fetch +* Create a CSP nonce property on the toolbar ``Toolbar().csp_nonce``. + +5.0.1 (2025-01-13) +------------------ +* Fixing the build and release process. No functional changes. + +5.0.0 (2025-01-11) +------------------ + +* Added Python 3.13 to the CI matrix. +* Removed support for Python 3.8 as it has reached end of life. +* Converted to Django Commons PyPI release process. +* Fixed a crash which occurred when using non-``str`` static file values. +* Documented experimental async support. +* Improved troubleshooting doc for incorrect mime types for .js static files + +Please see everything under 5.0.0-alpha as well. + +5.0.0-alpha (2024-09-01) +------------------------ + +* Support async applications and ASGI from + `Google Summer of Code Project 2024 + `__. +* Added Django 5.1 to the CI matrix. +* Added support for the ``LoginRequiredMiddleware`` introduced in Django 5.1. +* Support select and explain buttons for ``UNION`` queries on PostgreSQL. +* Fixed internal toolbar requests being instrumented if the Django setting + ``FORCE_SCRIPT_NAME`` was set. +* Increase opacity of show Debug Toolbar handle to improve accessibility. +* Changed the ``RedirectsPanel`` to be async compatible. +* Increased the contrast of text with dark mode enabled. +* Add translations for Bulgarian and Korean. +* Update translations for several languages. +* Include new translatable strings for translation. +* Fixed a crash which happened in the fallback case when session keys cannot be + sorted. + +4.4.6 (2024-07-10) +------------------ + +* Changed ordering (and grammatical number) of panels and their titles in + documentation to match actual panel ordering and titles. +* Skipped processing the alerts panel when response isn't a HTML response. + +4.4.5 (2024-07-05) +------------------ + +* Avoided crashing when the alerts panel was skipped. +* Removed the inadvertently added hard dependency on Jinja2. + +4.4.4 (2024-07-05) +------------------ + +* Added check for StreamingHttpResponse in alerts panel. +* Instrument the Django Jinja2 template backend. This only instruments + the immediate template that's rendered. It will not provide stats on + any parent templates. + +4.4.3 (2024-07-04) +------------------ + +* Added alerts panel with warning when form is using file fields + without proper encoding type. +* Fixed overriding font-family for both light and dark themes. +* Restored compatibility with ``iptools.IpRangeList``. +* Limit ``E001`` check to likely error cases when the + ``SHOW_TOOLBAR_CALLBACK`` has changed, but the toolbar's URL + paths aren't installed. +* Introduce helper function ``debug_toolbar_urls`` to + simplify installation. +* Moved "1rem" height/width for SVGs to CSS properties. + +4.4.2 (2024-05-27) +------------------ + +* Removed some CSS which wasn't carefully limited to the toolbar's elements. +* Stopped assuming that ``INTERNAL_IPS`` is a list. +* Added a section to the installation docs about running tests in projects + where the toolbar is being used. + + +4.4.1 (2024-05-26) +------------------ + +* Pin metadata version to 2.2 to be compatible with Jazzband release + process. + +4.4.0 (2024-05-26) +------------------ + +* Raised the minimum Django version to 4.2. +* Automatically support Docker rather than having the developer write a + workaround for ``INTERNAL_IPS``. +* Display a better error message when the toolbar's requests + return invalid json. +* Render forms with ``as_div`` to silence Django 5.0 deprecation warnings. +* Stayed on top of pre-commit hook updates. +* Added :doc:`architecture documentation ` to help + on-board new contributors. +* Removed the static file path validation check in + :class:`StaticFilesPanel ` + since that check is made redundant by a similar check in Django 4.0 and + later. +* Deprecated the ``OBSERVE_REQUEST_CALLBACK`` setting and added check + ``debug_toolbar.W008`` to warn when it is present in + ``DEBUG_TOOLBAR_SETTINGS``. +* Add a note on the profiling panel about using Python 3.12 and later + about needing ``--nothreading`` +* Added ``IS_RUNNING_TESTS`` setting to allow overriding the + ``debug_toolbar.E001`` check to avoid including the toolbar when running + tests. +* Fixed the bug causing ``'djdt' is not a registered namespace`` and updated + docs to help in initial configuration while running tests. +* Added a link in the installation docs to a more complete installation + example in the example app. +* Added check to prevent the toolbar from being installed when tests + are running. +* Added test to example app and command to run the example app's tests. +* Implemented dark mode theme and button to toggle the theme, + introduced the ``DEFAULT_THEME`` setting which sets the default theme + to use. + +4.3.0 (2024-02-01) +------------------ + +* Dropped support for Django 4.0. +* Added Python 3.12 to test matrix. +* Removed outdated third-party panels from the list. +* Avoided the unnecessary work of recursively quoting SQL parameters. +* Postponed context process in templates panel to include lazy evaluated + content. +* Fixed template panel to avoid evaluating ``LazyObject`` when not already + evaluated. +* Added support for Django 5.0. +* Refactor the ``utils.get_name_from_obj`` to simulate the behavior of + ``django.contrib.admindocs.utils.get_view_name``. +* Switched from black to the `ruff formatter + `__. +* Changed the default position of the toolbar from top to the upper top + position. +* Added the setting, ``UPDATE_ON_FETCH`` to control whether the + toolbar automatically updates to the latest AJAX request or not. + It defaults to ``False``. + +4.2.0 (2023-08-10) +------------------ + +* Adjusted app directories system check to allow for nested template loaders. +* Switched from flake8, isort and pyupgrade to `ruff + `__. +* Converted cookie keys to lowercase. Fixed the ``samesite`` argument to + ``djdt.cookie.set``. +* Converted ``StaticFilesPanel`` to no longer use a thread collector. Instead, + it collects the used static files in a ``ContextVar``. +* Added check ``debug_toolbar.W007`` to warn when JavaScript files are + resolving to the wrong content type. +* Fixed SQL statement recording under PostgreSQL for queries encoded as byte + strings. +* Patch the ``CursorWrapper`` class with a mixin class to support multiple + base wrapper classes. + +4.1.0 (2023-05-15) +------------------ + +* Improved SQL statement formatting performance. Additionally, fixed the + indentation of ``CASE`` statements and stopped simplifying ``.count()`` + queries. +* Added support for the new STORAGES setting in Django 4.2 for static files. +* Added support for theme overrides. +* Reworked the cache panel instrumentation code to no longer attempt to undo + monkey patching of cache methods, as that turned out to be fragile in the + presence of other code which also monkey patches those methods. +* Update all timing code that used :py:func:`time.time()` to use + :py:func:`time.perf_counter()` instead. +* Made the check on ``request.META["wsgi.multiprocess"]`` optional, but + defaults to forcing the toolbar to render the panels on each request. This + is because it's likely an ASGI application that's serving the responses + and that's more likely to be an incompatible setup. If you find that this + is incorrect for you in particular, you can use the ``RENDER_PANELS`` + setting to forcibly control this logic. + +4.0.0 (2023-04-03) +------------------ + +* Added Django 4.2 to the CI. +* Dropped support for Python 3.7. +* Fixed PostgreSQL raw query with a tuple parameter during on explain. +* Use ``TOOLBAR_LANGUAGE`` setting when rendering individual panels + that are loaded via AJAX. +* Add decorator for rendering toolbar views with ``TOOLBAR_LANGUAGE``. +* Removed the logging panel. The panel's implementation was too complex, caused + memory leaks and sometimes very verbose and hard to silence output in some + environments (but not others). The maintainers judged that time and effort is + better invested elsewhere. +* Added support for psycopg3. +* When ``ENABLE_STACKTRACE_LOCALS`` is ``True``, the stack frames' locals dicts + will be converted to strings when the stack trace is captured rather when it + is rendered, so that the correct values will be displayed in the rendered + stack trace, as they may have changed between the time the stack trace was + captured and when it is rendered. + +3.8.1 (2022-12-03) +------------------ + +* Fixed release process by re-adding twine to release dependencies. No + functional change. + +3.8.0 (2022-12-03) +------------------ + +* Added protection against division by 0 in timer.js +* Auto-update History panel for JavaScript ``fetch`` requests. +* Support `HTMX boosting `__ and + `Turbo `__ pages. +* Simplify logic for ``Panel.enabled`` property by checking cookies earlier. +* Include panel scripts in content when ``RENDER_PANELS`` is set to True. +* Create one-time mouseup listener for each mousedown when dragging the + handle. +* Update package metadata to use Hatchling. +* Fix highlighting on history panel so odd rows are highlighted when + selected. +* Formalize support for Python 3.11. +* Added ``TOOLBAR_LANGUAGE`` setting. + +3.7.0 (2022-09-25) +------------------ + +* Added Profiling panel setting ``PROFILER_THRESHOLD_RATIO`` to give users + better control over how many function calls are included. A higher value + will include more data, but increase render time. +* Update Profiling panel to include try to always include user code. This + code is more important to developers than dependency code. +* Highlight the project function calls in the profiling panel. +* Added Profiling panel setting ``PROFILER_CAPTURE_PROJECT_CODE`` to allow + users to disable the inclusion of all project code. This will be useful + to project setups that have dependencies installed under + ``settings.BASE_DIR``. +* The toolbar's font stack now prefers system UI fonts. Tweaked paddings, + margins and alignments a bit in the CSS code. +* Only sort the session dictionary when the keys are all strings. Fixes a + bug that causes the toolbar to crash when non-strings are used as keys. + +3.6.0 (2022-08-17) +------------------ + +* Remove decorator ``signed_data_view`` as it was causing issues with + `django-urlconfchecks `__. +* Added pygments to the test environment and fixed a crash when using the + template panel with Django 4.1 and pygments installed. +* Stayed on top of pre-commit hook and GitHub actions updates. +* Added some workarounds to avoid a Chromium warning which was worrisome to + developers. +* Avoided using deprecated Selenium methods to find elements. +* Raised the minimum Django version from 3.2 to 3.2.4 so that we can take + advantage of backported improvements to the cache connection handler. + +3.5.0 (2022-06-23) +------------------ + +* Properly implemented tracking and display of PostgreSQL transactions. +* Removed third party panels which have been archived on GitHub. +* Added Django 4.1b1 to the CI matrix. +* Stopped crashing when ``request.GET`` and ``request.POST`` are neither + dictionaries nor ``QueryDict`` instances. Using anything but ``QueryDict`` + instances isn't a valid use of Django but, again, django-debug-toolbar + shouldn't crash. +* Fixed the cache panel to work correctly in the presence of concurrency by + avoiding the use of signals. +* Reworked the cache panel instrumentation mechanism to monkey patch methods on + the cache instances directly instead of replacing cache instances with + wrapper classes. +* Added a :meth:`debug_toolbar.panels.Panel.ready` class method that panels can + override to perform any initialization or instrumentation that needs to be + done unconditionally at startup time. +* Added pyflame (for flame graphs) to the list of third-party panels. +* Fixed the cache panel to correctly count cache misses from the get_many() + cache method. +* Removed some obsolete compatibility code from the stack trace recording code. +* Added a new mechanism for capturing stack traces which includes per-request + caching to reduce expensive file system operations. Updated the cache and + SQL panels to record stack traces using this new mechanism. +* Changed the ``docs`` tox environment to allow passing positional arguments. + This allows e.g. building a HTML version of the docs using ``tox -e docs + html``. +* Stayed on top of pre-commit hook updates. +* Replaced ``OrderedDict`` by ``dict`` where possible. + +Deprecated features +~~~~~~~~~~~~~~~~~~~ + +* The ``debug_toolbar.utils.get_stack()`` and + ``debug_toolbar.utils.tidy_stacktrace()`` functions are deprecated in favor + of the new ``debug_toolbar.utils.get_stack_trace()`` function. They will + removed in the next major version of the Debug Toolbar. + +3.4.0 (2022-05-03) +------------------ + +* Fixed issue of stacktrace having frames that have no path to the file, + but are instead a string of the code such as + ``''``. +* Renamed internal SQL tracking context var from ``recording`` to + ``allow_sql``. + +3.3.0 (2022-04-28) +------------------ + +* Track calls to :py:meth:`django.core.cache.cache.get_or_set`. +* Removed support for Django < 3.2. +* Updated check ``W006`` to look for + ``django.template.loaders.app_directories.Loader``. +* Reset settings when overridden in tests. Packages or projects using + django-debug-toolbar can now use Django’s test settings tools, like + ``@override_settings``, to reconfigure the toolbar during tests. +* Optimize rendering of SQL panel, saving about 30% of its run time. +* New records in history panel will flash green. +* Automatically update History panel on AJAX requests from client. + +3.2.4 (2021-12-15) +------------------ + +* Revert PR 1426 - Fixes issue with SQL parameters having leading and + trailing characters stripped away. + +3.2.3 (2021-12-12) +------------------ + +* Changed cache monkey-patching for Django 3.2+ to iterate over existing + caches and patch them individually rather than attempting to patch + ``django.core.cache`` as a whole. The ``middleware.cache`` is still + being patched as a whole in order to attempt to catch any cache + usages before ``enable_instrumentation`` is called. +* Add check ``W006`` to warn that the toolbar is incompatible with + ``TEMPLATES`` settings configurations with ``APP_DIRS`` set to ``False``. +* Create ``urls`` module and update documentation to no longer require + importing the toolbar package. + + +3.2.2 (2021-08-14) +------------------ + +* Ensured that the handle stays within bounds when resizing the window. +* Disabled ``HistoryPanel`` when ``RENDER_PANELS`` is ``True`` + or if ``RENDER_PANELS`` is ``None`` and the WSGI container is + running with multiple processes. +* Fixed ``RENDER_PANELS`` functionality so that when ``True`` panels are + rendered during the request and not loaded asynchronously. +* HistoryPanel now shows status codes of responses. +* Support ``request.urlconf`` override when checking for toolbar requests. + + +3.2.1 (2021-04-14) +------------------ + +* Fixed SQL Injection vulnerability, CVE-2021-30459. The toolbar now + calculates a signature on all fields for the SQL select, explain, + and analyze forms. +* Changed ``djdt.cookie.set()`` to set ``sameSite=Lax`` by default if + callers do not provide a value. +* Added ``PRETTIFY_SQL`` configuration option to support controlling + SQL token grouping. By default it's set to True. When set to False, + a performance improvement can be seen by the SQL panel. +* Added a JavaScript event when a panel loads of the format + ``djdt.panel.[PanelId]`` where PanelId is the ``panel_id`` property + of the panel's Python class. Listening for this event corrects the bug + in the Timer Panel in which it didn't insert the browser timings + after switching requests in the History Panel. +* Fixed issue with the toolbar expecting URL paths to start with + ``/__debug__/`` while the documentation indicates it's not required. + +3.2 (2020-12-03) +---------------- + +* Moved CI to GitHub Actions: https://github.com/jazzband/django-debug-toolbar/actions +* Stopped crashing when ``request.GET`` and ``request.POST`` are + dictionaries instead of ``QueryDict`` instances. This isn't a valid + use of Django but django-debug-toolbar shouldn't crash anyway. +* Fixed a crash in the history panel when sending a JSON POST request + with invalid JSON. +* Added missing signals to the signals panel by default. +* Documented how to avoid CORS errors now that we're using JavaScript + modules. +* Verified support for Python 3.9. +* Added a ``css`` and a ``js`` template block to + ``debug_toolbar/base.html`` to allow overriding CSS and JS. + + 3.2a1 (2020-10-19) ------------------ @@ -10,8 +441,7 @@ Change log * Continued refactoring the HTML and CSS code for simplicity, continued improving the use of semantic HTML. * Stopped caring about prehistoric browsers for good. Started splitting - up the JavaScript code to take advantage of - JavaScript modules. + up the JavaScript code to take advantage of JavaScript modules. * Continued removing unused CSS. * Started running Selenium tests on Travis CI. * Added a system check which prevents using django-debug-toolbar without @@ -29,6 +459,7 @@ Change log * Started spellchecking the documentation. * Removed calls to the deprecated ``request.is_ajax()`` method. These calls were unnecessary now that most endpoints return JSON anyway. +* Removed support for Python 3.5. 3.1 (2020-09-21) @@ -65,7 +496,7 @@ Change log ``localStorage``. * Updated the code to avoid a few deprecation warnings and resource warnings. * Started loading JavaScript as ES6 modules. -* Added support for :meth:`cache.touch() ` when +* Added support for :meth:`cache.touch() ` when using django-debug-toolbar. * Eliminated more inline CSS. * Updated ``tox.ini`` and ``Makefile`` to use isort>=5. @@ -85,7 +516,14 @@ Change log :attr:`Panel.scripts ` property. * Removed support for end of life Django 1.11. The minimum supported Django is now 2.2. +* The Debug Toolbar now loads a `JavaScript module`_. Typical local development + using Django ``runserver`` is not impacted. However, if your application + server and static files server are at different origins, you may see CORS + errors in your browser's development console. See the "Cross-Origin Request + Blocked" section of the :doc:`installation docs ` for details + on how to resolve this issue. +.. _JavaScript module: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules 2.2 (2020-01-31) ---------------- @@ -311,9 +749,9 @@ This version is compatible with Django 1.9 and requires Django 1.7 or later. New features ~~~~~~~~~~~~ -* New panel method :meth:`debug_toolbar.panels.Panel.generate_stats` allows panels - to only record stats when the toolbar is going to be inserted into the - response. +* New panel method :meth:`debug_toolbar.panels.Panel.generate_stats` allows + panels to only record stats when the toolbar is going to be inserted into + the response. Bug fixes ~~~~~~~~~ diff --git a/docs/checks.rst b/docs/checks.rst index 8575ed565..1c41d04fc 100644 --- a/docs/checks.rst +++ b/docs/checks.rst @@ -2,8 +2,8 @@ System checks ============= -The following :doc:`system checks ` help verify the Django -Debug Toolbar setup and configuration: +The following :external:doc:`system checks ` help verify the +Django Debug Toolbar setup and configuration: * **debug_toolbar.W001**: ``debug_toolbar.middleware.DebugToolbarMiddleware`` is missing from ``MIDDLEWARE``. @@ -14,3 +14,13 @@ Debug Toolbar setup and configuration: * **debug_toolbar.W004**: ``debug_toolbar`` is incompatible with ``MIDDLEWARE_CLASSES`` setting. * **debug_toolbar.W005**: Setting ``DEBUG_TOOLBAR_PANELS`` is empty. +* **debug_toolbar.W006**: At least one ``DjangoTemplates`` ``TEMPLATES`` + configuration needs to have + ``django.template.loaders.app_directories.Loader`` included in + ``["OPTIONS"]["loaders"]`` or ``APP_DIRS`` set to ``True``. +* **debug_toolbar.W007**: JavaScript files are resolving to the wrong content + type. Refer to :external:ref:`Django's explanation of + mimetypes on Windows `. +* **debug_toolbar.W008**: The deprecated ``OBSERVE_REQUEST_CALLBACK`` setting + is present in ``DEBUG_TOOLBAR_CONFIG``. Use the ``UPDATE_ON_FETCH`` and/or + ``SHOW_TOOLBAR_CALLBACK`` settings instead. diff --git a/docs/conf.py b/docs/conf.py index 59d886ada..6e67aac2e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -25,7 +25,7 @@ copyright = copyright.format(datetime.date.today().year) # The full version, including alpha/beta/rc tags -release = "3.2a1" +release = "5.2.0" # -- General configuration --------------------------------------------------- @@ -51,7 +51,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = "default" +html_theme = "sphinx_rtd_theme" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, @@ -59,8 +59,11 @@ # html_static_path = ['_static'] intersphinx_mapping = { - "https://docs.python.org/": None, - "https://docs.djangoproject.com/en/dev/": "https://docs.djangoproject.com/en/dev/_objects/", + "python": ("https://docs.python.org/", None), + "django": ( + "https://docs.djangoproject.com/en/dev/", + "https://docs.djangoproject.com/en/dev/_objects/", + ), } # -- Options for Read the Docs ----------------------------------------------- diff --git a/docs/configuration.rst b/docs/configuration.rst index b1e55cdc3..377c97da8 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -20,6 +20,7 @@ included in the toolbar. It works like Django's ``MIDDLEWARE`` setting. The default value is:: DEBUG_TOOLBAR_PANELS = [ + 'debug_toolbar.panels.history.HistoryPanel', 'debug_toolbar.panels.versions.VersionsPanel', 'debug_toolbar.panels.timer.TimerPanel', 'debug_toolbar.panels.settings.SettingsPanel', @@ -28,9 +29,9 @@ default value is:: 'debug_toolbar.panels.sql.SQLPanel', 'debug_toolbar.panels.staticfiles.StaticFilesPanel', 'debug_toolbar.panels.templates.TemplatesPanel', + 'debug_toolbar.panels.alerts.AlertsPanel', 'debug_toolbar.panels.cache.CachePanel', 'debug_toolbar.panels.signals.SignalsPanel', - 'debug_toolbar.panels.logging.LoggingPanel', 'debug_toolbar.panels.redirects.RedirectsPanel', 'debug_toolbar.panels.profiling.ProfilingPanel', ] @@ -53,7 +54,14 @@ Toolbar options * ``DISABLE_PANELS`` - Default: ``{'debug_toolbar.panels.redirects.RedirectsPanel'}`` + Default: + + .. code-block:: python + + { + "debug_toolbar.panels.profiling.ProfilingPanel", + "debug_toolbar.panels.redirects.RedirectsPanel", + } This setting is a set of the full Python paths to each panel that you want disabled (but still displayed) by default. @@ -65,19 +73,37 @@ Toolbar options The toolbar searches for this string in the HTML and inserts itself just before. +.. _IS_RUNNING_TESTS: + +* ``IS_RUNNING_TESTS`` + + Default: ``"test" in sys.argv or "PYTEST_VERSION" in os.environ`` + + This setting whether the application is running tests. If this resolves to + ``True``, the toolbar will prevent you from running tests. This should only + be changed if your test command doesn't include ``test`` or if you wish to + test your application with the toolbar configured. If you do wish to test + your application with the toolbar configured, set this setting to + ``False``. + +.. _RENDER_PANELS: + * ``RENDER_PANELS`` Default: ``None`` If set to ``False``, the debug toolbar will keep the contents of panels in - memory on the server and load them on demand. If set to ``True``, it will - render panels inside every page. This may slow down page rendering but it's + memory on the server and load them on demand. + + If set to ``True``, it will disable ``HistoryPanel`` and render panels + inside every page. This may slow down page rendering but it's required on multi-process servers, for example if you deploy the toolbar in production (which isn't recommended). The default value of ``None`` tells the toolbar to automatically do the right thing depending on whether the WSGI container runs multiple processes. - This setting allows you to force a different behavior if needed. + This setting allows you to force a different behavior if needed. If the + WSGI container runs multiple processes, it will disable ``HistoryPanel``. * ``RESULTS_CACHE_SIZE`` @@ -85,6 +111,8 @@ Toolbar options The toolbar keeps up to this many results in memory. +.. _ROOT_TAG_EXTRA_ATTRS: + * ``ROOT_TAG_EXTRA_ATTRS`` Default: ``''`` @@ -117,6 +145,63 @@ Toolbar options the callback. This allows reusing the callback to verify access to panel views requested via AJAX. + .. warning:: + + Please note that the debug toolbar isn't hardened for use in production + environments or on public servers. You should be aware of the implications + to the security of your servers when using your own callback. One known + implication is that it is possible to execute arbitrary SQL through the + SQL panel when the ``SECRET_KEY`` value is leaked somehow. + + .. warning:: + + Do not use + ``DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": lambda request: DEBUG}`` + in your project's settings.py file. The toolbar expects to use + ``django.conf.settings.DEBUG``. Using your project's setting's ``DEBUG`` + is likely to cause unexpected results when running your tests. This is because + Django automatically sets ``settings.DEBUG = False``, but your project's + setting's ``DEBUG`` will still be set to ``True``. + +.. _OBSERVE_REQUEST_CALLBACK: + +* ``OBSERVE_REQUEST_CALLBACK`` + + Default: ``'debug_toolbar.toolbar.observe_request'`` + + .. note:: + + This setting is deprecated in favor of the ``UPDATE_ON_FETCH`` and + ``SHOW_TOOLBAR_CALLBACK`` settings. + + This is the dotted path to a function used for determining whether the + toolbar should update on AJAX requests or not. The default implementation + always returns ``True``. + +.. _TOOLBAR_LANGUAGE: + +* ``TOOLBAR_LANGUAGE`` + + Default: ``None`` + + The language used to render the toolbar. If no value is supplied, then the + application's current language will be used. This setting can be used to + render the toolbar in a different language than what the application is + rendered in. For example, if you wish to use English for development, + but want to render your application in French, you would set this to + ``"en-us"`` and :setting:`LANGUAGE_CODE` to ``"fr"``. + +.. _UPDATE_ON_FETCH: + +* ``UPDATE_ON_FETCH`` + + Default: ``False`` + + This controls whether the toolbar should update to the latest AJAX + request when it occurs. This is especially useful when using htmx + boosting or similar JavaScript techniques. + + Panel options ~~~~~~~~~~~~~ @@ -179,6 +264,54 @@ Panel options Useful for eliminating server-related entries which can result in enormous DOM structures and toolbar rendering delays. +* ``PRETTIFY_SQL`` + + Default: ``True`` + + Panel: SQL + + Controls SQL token grouping. + + Token grouping allows pretty print of similar tokens, + like aligned indentation for every selected field. + + When set to ``True``, it might cause render slowdowns + when a view make long SQL textual queries. + + **Without grouping**:: + + 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" + FROM "auth_user" + WHERE "auth_user"."username" = '''test_username''' + LIMIT 21 + + **With grouping**:: + + 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", + FROM "auth_user" + WHERE "auth_user"."username" = '''test_username''' + LIMIT 21 + +* ``PROFILER_CAPTURE_PROJECT_CODE`` + + Default: ``True`` + + Panel: profiling + + When enabled this setting will include all project function calls in the + panel. Project code is defined as files in the path defined at + ``settings.BASE_DIR``. If you install dependencies under + ``settings.BASE_DIR`` in a directory other than ``sites-packages`` or + ``dist-packages`` you may need to disable this setting. * ``PROFILER_MAX_DEPTH`` @@ -189,6 +322,20 @@ Panel options This setting affects the depth of function calls in the profiler's analysis. +* ``PROFILER_THRESHOLD_RATIO`` + + Default: ``8`` + + Panel: profiling + + This setting affects the which calls are included in the profile. A higher + value will include more function calls. A lower value will result in a faster + render of the profiling panel, but will exclude data. + + This value is used to determine the threshold of cumulative time to include + the nested functions. The threshold is calculated by the root calls' + cumulative time divided by this ratio. + * ``SHOW_TEMPLATE_CONTEXT`` Default: ``True`` @@ -230,3 +377,26 @@ Here's what a slightly customized toolbar configuration might look like:: # Panel options 'SQL_WARNING_THRESHOLD': 100, # milliseconds } + +Theming support +--------------- +The debug toolbar uses CSS variables to define fonts and colors. This allows +changing fonts and colors without having to override many individual CSS rules. +For example, if you preferred Roboto instead of the default list of fonts you +could add a **debug_toolbar/base.html** template override to your project: + +.. code-block:: django + + {% extends 'debug_toolbar/base.html' %} + + {% block css %}{{ block.super }} + + {% endblock %} + +The list of CSS variables are defined at +`debug_toolbar/static/debug_toolbar/css/toolbar.css +`_ diff --git a/docs/contributing.rst b/docs/contributing.rst index c17dbd287..1ab7077aa 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -1,19 +1,14 @@ Contributing ============ -.. image:: https://jazzband.co/static/img/jazzband.svg - :target: https://jazzband.co/ - :alt: Jazzband - -This is a `Jazzband `_ project. By contributing you agree -to abide by the `Contributor Code of Conduct `_ -and follow the `guidelines `_. +This is a `Django Commons `_ project. By contributing you agree +to abide by the `Contributor Code of Conduct `_. Bug reports and feature requests -------------------------------- You can report bugs and request features in the `bug tracker -`_. +`_. Please search the existing database for duplicates before filing an issue. @@ -21,13 +16,13 @@ Code ---- The code is available `on GitHub -`_. Unfortunately, the +`_. Unfortunately, the repository contains old and flawed objects, so if you have set `fetch.fsckObjects `_ you'll have to deactivate it for this repository:: - git clone --config fetch.fsckobjects=false https://github.com/jazzband/django-debug-toolbar.git + git clone --config fetch.fsckobjects=false https://github.com/django-commons/django-debug-toolbar.git Once you've obtained a checkout, you should create a virtualenv_ and install the libraries required for working on the Debug Toolbar:: @@ -48,12 +43,18 @@ For convenience, there's an alias for the second command:: Look at ``example/settings.py`` for running the example with another database than SQLite. +Architecture +------------ + +There is high-level information on how the Django Debug Toolbar is structured +in the :doc:`architecture documentation `. + Tests ----- Once you've set up a development environment as explained above, you can run the test suite for the versions of Django and Python installed in that -environment:: +environment using the SQLite database:: $ make test @@ -79,23 +80,67 @@ or by setting the ``DJANGO_SELENIUM_TESTS`` environment variable:: $ DJANGO_SELENIUM_TESTS=true make coverage $ DJANGO_SELENIUM_TESTS=true tox -At this time, there isn't an easy way to test against databases other than -SQLite. +Note that by default, ``tox`` enables the Selenium tests for a single test +environment. To run the entire ``tox`` test suite with all Selenium tests +disabled, run the following:: + + $ DJANGO_SELENIUM_TESTS= tox + +To test via ``tox`` against other databases, you'll need to create the user, +database and assign the proper permissions. For PostgreSQL in a ``psql`` +shell (note this allows the debug_toolbar user the permission to create +databases):: + + psql> CREATE USER debug_toolbar WITH PASSWORD 'debug_toolbar'; + psql> ALTER USER debug_toolbar CREATEDB; + psql> CREATE DATABASE debug_toolbar; + psql> GRANT ALL PRIVILEGES ON DATABASE debug_toolbar to debug_toolbar; + +For MySQL/MariaDB in a ``mysql`` shell:: + + mysql> CREATE DATABASE debug_toolbar; + mysql> CREATE USER 'debug_toolbar'@'localhost' IDENTIFIED BY 'debug_toolbar'; + mysql> GRANT ALL PRIVILEGES ON debug_toolbar.* TO 'debug_toolbar'@'localhost'; + mysql> GRANT ALL PRIVILEGES ON test_debug_toolbar.* TO 'debug_toolbar'@'localhost'; + Style ----- -The Django Debug Toolbar uses `black `__ to -format code and additionally uses flake8 and isort. You can reformat the code -using:: +The Django Debug Toolbar uses `ruff `__ to +format and lint Python code. The toolbar uses `pre-commit +`__ to automatically apply our style guidelines when a +commit is made. Set up pre-commit before committing with:: + + $ pre-commit install + +If necessary you can bypass pre-commit locally with:: + + $ git commit --no-verify + +Note that it runs on CI. + +To reformat the code manually use:: + + $ pre-commit run --all-files + + +Typing +------ + +The Debug Toolbar has been accepting patches which add type hints to the code +base, as long as the types themselves do not cause any problems or obfuscate +the intent. + +The maintainers are not committed to adding type hints and are not requiring +new code to have type hints at this time. This may change in the future. - $ make style Patches ------- Please submit `pull requests -`_! +`_! The Debug Toolbar includes a limited but growing test suite. If you fix a bug or add a feature code, please consider adding proper coverage in the test @@ -105,7 +150,7 @@ Translations ------------ Translation efforts are coordinated on `Transifex -`_. +`_. Help translate the Debug Toolbar in your language! @@ -122,12 +167,18 @@ Prior to a release, the English ``.po`` file must be updated with ``make translatable_strings`` and pushed to Transifex. Once translators have done their job, ``.po`` files must be downloaded with ``make update_translations``. +You will need to +`install the Transifex CLI `_. + +To publish a release you have to be a `django-debug-toolbar project lead at +Django Commons `__. + The release itself requires the following steps: #. Update supported Python and Django versions: - - ``setup.py`` ``python_requires`` list - - ``setup.py`` trove classifiers + - ``pyproject.toml`` options ``requires-python``, ``dependencies``, + and ``classifiers`` - ``README.rst`` Commit. @@ -141,14 +192,15 @@ The release itself requires the following steps: Commit. #. Bump version numbers in ``docs/changes.rst``, ``docs/conf.py``, - ``README.rst`` and ``setup.py``. Add the release date to - ``docs/changes.rst``. Commit. + ``README.rst``, and ``debug_toolbar/__init__.py``. + Add the release date to ``docs/changes.rst``. Commit. #. Tag the new version. -#. ``python setup.py sdist bdist_wheel upload``. - #. Push the commit and the tag. -#. Change the default version of the docs to point to the latest release: - https://readthedocs.org/dashboard/django-debug-toolbar/versions/ +#. Publish the release from the GitHub actions workflow. + +#. **After the publishing completed** edit the automatically created GitHub + release to include the release notes (you may use GitHub's "Generate release + notes" button for this). diff --git a/docs/index.rst b/docs/index.rst index e53703d4f..48c217b1a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,5 +10,7 @@ Django Debug Toolbar tips panels commands + resources changes contributing + architecture diff --git a/docs/installation.rst b/docs/installation.rst index 55add1620..b89a2f563 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -1,13 +1,23 @@ Installation ============ +Process +------- + Each of the following steps needs to be configured for the Debug Toolbar to be fully functional. -Getting the code ----------------- +.. warning:: + + The Debug Toolbar now supports `Django's asynchronous views `_ and ASGI environment, but + still lacks the capability for handling concurrent requests. + +1. Install the Package +^^^^^^^^^^^^^^^^^^^^^^ + +The recommended way to install the Debug Toolbar is via pip_: -The recommended way to install the Debug Toolbar is via pip_:: +.. code-block:: console $ python -m pip install django-debug-toolbar @@ -17,56 +27,96 @@ If you aren't familiar with pip, you may also obtain a copy of the .. _pip: https://pip.pypa.io/ To test an upcoming release, you can install the in-development version -instead with the following command:: +instead with the following command: + +.. code-block:: console + + $ python -m pip install -e git+https://github.com/django-commons/django-debug-toolbar.git#egg=django-debug-toolbar + +If you're upgrading from a previous version, you should review the +:doc:`change log ` and look for specific upgrade instructions. - $ python -m pip install -e git+https://github.com/jazzband/django-debug-toolbar.git#egg=django-debug-toolbar +2. Check for Prerequisites +^^^^^^^^^^^^^^^^^^^^^^^^^^ -Prerequisites -------------- +The Debug Toolbar requires two things from core Django. These are already +configured in Django’s default ``startproject`` template, so in most cases you +will already have these set up. -Make sure that ``'django.contrib.staticfiles'`` is `set up properly -`_ and add -``'debug_toolbar'`` to your ``INSTALLED_APPS`` setting:: +First, ensure that ``'django.contrib.staticfiles'`` is in your +``INSTALLED_APPS`` setting, and `configured properly +`_: + +.. code-block:: python INSTALLED_APPS = [ # ... - 'django.contrib.staticfiles', + "django.contrib.staticfiles", # ... - 'debug_toolbar', ] - STATIC_URL = '/static/' + STATIC_URL = "static/" -If you're upgrading from a previous version, you should review the -:doc:`change log ` and look for specific upgrade instructions. +Second, ensure that your ``TEMPLATES`` setting contains a +``DjangoTemplates`` backend whose ``APP_DIRS`` options is set to ``True``: -Setting up URLconf ------------------- +.. code-block:: python -Add the Debug Toolbar's URLs to your project's URLconf:: + TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "APP_DIRS": True, + # ... + } + ] + +3. Install the App +^^^^^^^^^^^^^^^^^^ + +Add ``"debug_toolbar"`` to your ``INSTALLED_APPS`` setting: + +.. code-block:: python + + INSTALLED_APPS = [ + # ... + "debug_toolbar", + # ... + ] +.. note:: Check out the configuration example in the + `example app + `_ + to learn how to set up the toolbar to function smoothly while running + your tests. + +4. Add the URLs +^^^^^^^^^^^^^^^ + +Add django-debug-toolbar's URLs to your project's URLconf: + +.. code-block:: python - import debug_toolbar - from django.conf import settings from django.urls import include, path + from debug_toolbar.toolbar import debug_toolbar_urls urlpatterns = [ - ... - path('__debug__/', include(debug_toolbar.urls)), - ] + # ... the rest of your URLconf goes here ... + ] + debug_toolbar_urls() + +By default this uses the ``__debug__`` prefix for the paths, but you can +use any prefix that doesn't clash with your application's URLs. -This example uses the ``__debug__`` prefix, but you can use any prefix that -doesn't clash with your application's URLs. Note the lack of quotes around -``debug_toolbar.urls``. -Enabling middleware -------------------- +5. Add the Middleware +^^^^^^^^^^^^^^^^^^^^^ -The Debug Toolbar is mostly implemented in a middleware. Enable it in your -settings module as follows:: +The Debug Toolbar is mostly implemented in a middleware. Add it to your +``MIDDLEWARE`` setting: + +.. code-block:: python MIDDLEWARE = [ # ... - 'debug_toolbar.middleware.DebugToolbarMiddleware', + "debug_toolbar.middleware.DebugToolbarMiddleware", # ... ] @@ -79,34 +129,184 @@ settings module as follows:: .. _internal-ips: -Configuring Internal IPs ------------------------- +6. Configure Internal IPs +^^^^^^^^^^^^^^^^^^^^^^^^^ + +The Debug Toolbar is shown only if your IP address is listed in Django’s +:setting:`INTERNAL_IPS` setting. This means that for local +development, you *must* add ``"127.0.0.1"`` to :setting:`INTERNAL_IPS`. +You'll need to create this setting if it doesn't already exist in your +settings module: -The Debug Toolbar is shown only if your IP address is listed in the -:django:setting:`INTERNAL_IPS` setting. This means that for local -development, you *must* add ``'127.0.0.1'`` to :django:setting:`INTERNAL_IPS`; -you'll need to create this setting if it doesn't already exist in your -settings module:: +.. code-block:: python INTERNAL_IPS = [ # ... - '127.0.0.1', + "127.0.0.1", # ... ] You can change the logic of determining whether or not the Debug Toolbar should be shown with the :ref:`SHOW_TOOLBAR_CALLBACK ` -option. This option allows you to specify a custom function for this purpose. +option. + +.. warning:: + + If using Docker, the toolbar will attempt to look up your host name + automatically and treat it as an allowable internal IP. If you're not + able to get the toolbar to work with your docker installation, review + the code in ``debug_toolbar.middleware.show_toolbar``. + +7. Disable the toolbar when running tests (optional) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you're running tests in your project you shouldn't activate the toolbar. You +can do this by adding another setting: + +.. code-block:: python + + TESTING = "test" in sys.argv or "PYTEST_VERSION" in os.environ + + if not TESTING: + INSTALLED_APPS = [ + *INSTALLED_APPS, + "debug_toolbar", + ] + MIDDLEWARE = [ + "debug_toolbar.middleware.DebugToolbarMiddleware", + *MIDDLEWARE, + ] + +You should also modify your URLconf file: + +.. code-block:: python + + from django.conf import settings + from debug_toolbar.toolbar import debug_toolbar_urls + + if not settings.TESTING: + urlpatterns = [ + *urlpatterns, + ] + debug_toolbar_urls() + +Alternatively, you can check out the :ref:`IS_RUNNING_TESTS ` +option. Troubleshooting --------------- -On some platforms, the Django ``runserver`` command may use incorrect content -types for static assets. To guess content types, Django relies on the -:mod:`mimetypes` module from the Python standard library, which itself relies -on the underlying platform's map files. If you find improper content types for -certain files, it is most likely that the platform's map files are incorrect or -need to be updated. This can be achieved, for example, by installing or -updating the ``mailcap`` package on a Red Hat distribution, ``mime-support`` on -a Debian distribution, or by editing the keys under ``HKEY_CLASSES_ROOT`` in -the Windows registry. +If the toolbar doesn't appear, check your browser's development console for +errors. These errors can often point to one of the issues discussed in the +section below. Note that the toolbar only shows up for pages with an HTML body +tag, which is absent in the templates of the Django Polls tutorial. + +Incorrect MIME type for toolbar.js +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When this error occurs, the development console shows an error similar to: + +.. code-block:: text + + Loading module from “http://127.0.0.1:8000/static/debug_toolbar/js/toolbar.js” was blocked because of a disallowed MIME type (“text/plain”). + +On some platforms (commonly on Windows O.S.), the Django ``runserver`` +command may use incorrect content types for static assets. To guess content +types, Django relies on the :mod:`mimetypes` module from the Python standard +library, which itself relies on the underlying platform's map files. + +The easiest workaround is to add the following to your ``settings.py`` file. +This forces the MIME type for ``.js`` files: + +.. code-block:: python + + import mimetypes + mimetypes.add_type("application/javascript", ".js", True) + +Alternatively, you can try to fix your O.S. configuration. If you find improper +content types for certain files, it is most likely that the platform's map +files are incorrect or need to be updated. This can be achieved, for example: + +- On Red Hat distributions, install or update the ``mailcap`` package. +- On Debian distributions, install or update the ``mime-support`` package. +- On Windows O.S., edit the keys under ``HKEY_CLASSES_ROOT`` in the Windows + registry. + +Cross-Origin Request Blocked +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The Debug Toolbar loads a `JavaScript module`_. Typical local development using +Django ``runserver`` is not impacted. However, if your application server and +static files server are at different origins, you may see `CORS errors`_ in +your browser's development console: + +.. code-block:: text + + 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). + +Or + +.. code-block:: text + + 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. + +To resolve, configure your static files server to add the +`Access-Control-Allow-Origin header`_ with the origin of the application +server. For example, if your application server is at ``http://example.com``, +and your static files are served by NGINX, add: + +.. code-block:: nginx + + add_header Access-Control-Allow-Origin http://example.com; + +And for Apache: + +.. code-block:: apache + + Header add Access-Control-Allow-Origin http://example.com + +.. _JavaScript module: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules +.. _CORS errors: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSMissingAllowOrigin +.. _Access-Control-Allow-Origin header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin + +Django Channels & Async +^^^^^^^^^^^^^^^^^^^^^^^ + +The Debug Toolbar currently has experimental support for Django Channels and +async projects. The Debug Toolbar is compatible with the following exceptions: + +- Concurrent requests aren't supported +- ``TimerPanel``, ``RequestPanel`` and ``ProfilingPanel`` can't be used + in async contexts. + +HTMX +^^^^ + +If you're using `HTMX`_ to `boost a page`_ you will need to add the following +event handler to your code: + +.. code-block:: javascript + + {% if debug %} + if (typeof window.htmx !== "undefined") { + htmx.on("htmx:afterSettle", function(detail) { + if ( + typeof window.djdt !== "undefined" + && detail.target instanceof HTMLBodyElement + ) { + djdt.show_toolbar(); + } + }); + } + {% endif %} + + +The use of ``{% if debug %}`` requires +`django.template.context_processors.debug`_ be included in the +``'context_processors'`` option of the `TEMPLATES`_ setting. Django's +default configuration includes this context processor. + + +.. _HTMX: https://htmx.org/ +.. _boost a page: https://htmx.org/docs/#boosting +.. _django.template.context_processors.debug: https://docs.djangoproject.com/en/4.1/ref/templates/api/#django-template-context-processors-debug +.. _TEMPLATES: https://docs.djangoproject.com/en/4.1/ref/settings/#std-setting-TEMPLATES diff --git a/docs/panels.rst b/docs/panels.rst index 31e116037..a116bff1e 100644 --- a/docs/panels.rst +++ b/docs/panels.rst @@ -17,8 +17,13 @@ History This panel shows the history of requests made and allows switching to a past snapshot of the toolbar to view that request's stats. -Version -~~~~~~~ +.. caution:: + If :ref:`RENDER_PANELS ` configuration option is set to + ``True`` or if the server runs with multiple processes, the History Panel + will be disabled. + +Versions +~~~~~~~~ .. class:: debug_toolbar.panels.versions.VersionsPanel @@ -64,19 +69,30 @@ SQL SQL queries including time to execute and links to EXPLAIN each query. -Template -~~~~~~~~ +Static files +~~~~~~~~~~~~ + +.. class:: debug_toolbar.panels.staticfiles.StaticFilesPanel + +Used static files and their locations (via the ``staticfiles`` finders). + +Templates +~~~~~~~~~ .. class:: debug_toolbar.panels.templates.TemplatesPanel Templates and context used, and their template paths. -Static files -~~~~~~~~~~~~ +Alerts +~~~~~~~ -.. class:: debug_toolbar.panels.staticfiles.StaticFilesPanel +.. class:: debug_toolbar.panels.alerts.AlertsPanel -Used static files and their locations (via the ``staticfiles`` finders). +This panel shows alerts for a set of pre-defined cases: + +- Alerts when the response has a form without the + ``enctype="multipart/form-data"`` attribute and the form contains + a file input. Cache ~~~~~ @@ -85,20 +101,13 @@ Cache Cache queries. Is incompatible with Django's per-site caching. -Signal -~~~~~~ +Signals +~~~~~~~ .. class:: debug_toolbar.panels.signals.SignalsPanel List of signals and receivers. -Logging -~~~~~~~ - -.. class:: debug_toolbar.panels.logging.LoggingPanel - -Logging output via Python's built-in :mod:`logging` module. - Redirects ~~~~~~~~~ @@ -113,6 +122,11 @@ Since this behavior is annoying when you aren't debugging a redirect, this panel is included but inactive by default. You can activate it by default with the ``DISABLE_PANELS`` configuration option. +To further customize the behavior, you can subclass the ``RedirectsPanel`` +and override the ``get_interception_response`` method to manipulate the +response directly. To use a custom ``RedirectsPanel``, you need to replace +the original one in ``DEBUG_TOOLBAR_PANELS`` in your ``settings.py``. + .. _profiling-panel: Profiling @@ -125,6 +139,16 @@ Profiling information for the processing of the request. This panel is included but inactive by default. You can activate it by default with the ``DISABLE_PANELS`` configuration option. +For version of Python 3.12 and later you need to use +``python -m manage runserver --nothreading`` +Concurrent requests don't work with the profiling panel. + +The panel will include all function calls made by your project if you're using +the setting ``settings.BASE_DIR`` to point to your project's root directory. +If a function is in a file within that directory and does not include +``"/site-packages/"`` or ``"/dist-packages/"`` in the path, it will be +included. + Third-party panels ------------------ @@ -136,46 +160,29 @@ Third-party panels If you'd like to add a panel to this list, please submit a pull request! -Flamegraph -~~~~~~~~~~ - -URL: https://github.com/23andMe/djdt-flamegraph - -Path: ``djdt_flamegraph.FlamegraphPanel`` - -Generates a flame graph from your current request. - -Haystack -~~~~~~~~ - -URL: https://github.com/streeter/django-haystack-panel - -Path: ``haystack_panel.panel.HaystackDebugPanel`` - -See queries made by your Haystack_ backends. - -.. _Haystack: http://haystacksearch.org/ - -HTML Tidy/Validator -~~~~~~~~~~~~~~~~~~~ +Flame Graphs +~~~~~~~~~~~~ -URL: https://github.com/joymax/django-dtpanel-htmltidy +URL: https://gitlab.com/living180/pyflame -Path: ``debug_toolbar_htmltidy.panels.HTMLTidyDebugPanel`` +Path: ``pyflame.djdt.panel.FlamegraphPanel`` -HTML Tidy or HTML Validator is a custom panel that validates your HTML and -displays warnings and errors. +Displays a flame graph for visualizing the performance profile of the request, +using Brendan Gregg's `flamegraph.pl script +`_ to perform the +heavy lifting. -Inspector -~~~~~~~~~ +LDAP Tracing +~~~~~~~~~~~~ -URL: https://github.com/santiagobasulto/debug-inspector-panel +URL: https://github.com/danyi1212/django-windowsauth -Path: ``inspector_panel.panels.inspector.InspectorPanel`` +Path: ``windows_auth.panels.LDAPPanel`` -Retrieves and displays information you specify using the ``debug`` statement. -Inspector panel also logs to the console by default, but may be instructed not -to. +LDAP Operations performed during the request, including timing, request and response messages, +the entries received, write changes list, stack-tracing and error debugging. +This panel also shows connection usage metrics when it is collected. +`Check out the docs `_. Line Profiler ~~~~~~~~~~~~~ @@ -203,7 +210,8 @@ Memcache URL: https://github.com/ross/memcache-debug-panel -Path: ``memcache_toolbar.panels.memcache.MemcachePanel`` or ``memcache_toolbar.panels.pylibmc.PylibmcPanel`` +Path: ``memcache_toolbar.panels.memcache.MemcachePanel`` or +``memcache_toolbar.panels.pylibmc.PylibmcPanel`` This panel tracks memcached usage. It currently supports both the pylibmc and memcache libraries. @@ -217,6 +225,17 @@ Path: ``debug_toolbar_mongo.panel.MongoDebugPanel`` Adds MongoDB debugging information. +MrBenn Toolbar Plugin +~~~~~~~~~~~~~~~~~~~~~ + +URL: https://github.com/andytwoods/mrbenn + +Path: ``mrbenn_panel.panel.MrBennPanel`` + +Allows you to quickly open template files and views directly in your IDE! +In addition to the path above, you need to add ``mrbenn_panel`` in +``INSTALLED_APPS`` + Neo4j ~~~~~ @@ -224,7 +243,8 @@ URL: https://github.com/robinedwards/django-debug-toolbar-neo4j-panel Path: ``neo4j_panel.Neo4jPanel`` -Trace neo4j rest API calls in your Django application, this also works for neo4django and neo4jrestclient, support for py2neo is on its way. +Trace neo4j rest API calls in your Django application, this also works for +neo4django and neo4jrestclient, support for py2neo is on its way. Pympler ~~~~~~~ @@ -233,7 +253,8 @@ URL: https://pythonhosted.org/Pympler/django.html Path: ``pympler.panels.MemoryPanel`` -Shows process memory information (virtual size, resident set size) and model instances for the current request. +Shows process memory information (virtual size, resident set size) and model +instances for the current request. Request History ~~~~~~~~~~~~~~~ @@ -242,7 +263,8 @@ URL: https://github.com/djsutho/django-debug-toolbar-request-history Path: ``ddt_request_history.panels.request_history.RequestHistoryPanel`` -Switch between requests to view their stats. Also adds support for viewing stats for AJAX requests. +Switch between requests to view their stats. Also adds support for viewing +stats for AJAX requests. Requests ~~~~~~~~ @@ -253,18 +275,6 @@ Path: ``requests_panel.panel.RequestsDebugPanel`` Lists HTTP requests made with the popular `requests `_ library. -Sites -~~~~~ - -URL: https://github.com/elvard/django-sites-toolbar - -Path: ``sites_toolbar.panels.SitesDebugPanel`` - -Browse Sites registered in ``django.contrib.sites`` and switch between them. -Useful to debug project when you use `django-dynamicsites -`_ which sets SITE_ID -dynamically. - Template Profiler ~~~~~~~~~~~~~~~~~ @@ -272,8 +282,9 @@ URL: https://github.com/node13h/django-debug-toolbar-template-profiler Path: ``template_profiler_panel.panels.template.TemplateProfilerPanel`` -Shows template render call duration and distribution on the timeline. Lightweight. -Compatible with WSGI servers which reuse threads for multiple requests (Werkzeug). +Shows template render call duration and distribution on the timeline. +Lightweight. Compatible with WSGI servers which reuse threads for multiple +requests (Werkzeug). Template Timings ~~~~~~~~~~~~~~~~ @@ -284,15 +295,6 @@ Path: ``template_timings_panel.panels.TemplateTimings.TemplateTimings`` Displays template rendering times for your Django application. -User -~~~~ - -URL: https://github.com/playfire/django-debug-toolbar-user-panel - -Path: ``debug_toolbar_user_panel.panels.UserPanel`` - -Easily switch between logged in users, see properties of current user. - VCS Info ~~~~~~~~ @@ -300,7 +302,8 @@ URL: https://github.com/giginet/django-debug-toolbar-vcs-info Path: ``vcs_info_panel.panels.GitInfoPanel`` -Displays VCS status (revision, branch, latest commit log and more) of your Django application. +Displays VCS status (revision, branch, latest commit log and more) of your +Django application. uWSGI Stats ~~~~~~~~~~~ @@ -318,9 +321,18 @@ Third-party panels must subclass :class:`~debug_toolbar.panels.Panel`, according to the public API described below. Unless noted otherwise, all methods are optional. -Panels can ship their own templates, static files and views. All views should -be decorated with ``debug_toolbar.decorators.require_show_toolbar`` to prevent -unauthorized access. There is no public CSS API at this time. +Panels can ship their own templates, static files and views. + +Any views defined for the third-party panel use the following decorators: + +- ``debug_toolbar.decorators.require_show_toolbar`` - Prevents unauthorized + access to the view. This decorator is compatible with async views. +- ``debug_toolbar.decorators.render_with_toolbar_language`` - Supports + internationalization for any content rendered by the view. This will render + the response with the :ref:`TOOLBAR_LANGUAGE ` rather than + :setting:`LANGUAGE_CODE`. + +There is no public CSS API at this time. .. autoclass:: debug_toolbar.panels.Panel @@ -338,6 +350,8 @@ unauthorized access. There is no public CSS API at this time. .. autoattribute:: debug_toolbar.panels.Panel.scripts + .. automethod:: debug_toolbar.panels.Panel.ready + .. automethod:: debug_toolbar.panels.Panel.get_urls .. automethod:: debug_toolbar.panels.Panel.enable_instrumentation @@ -350,8 +364,12 @@ unauthorized access. There is no public CSS API at this time. .. automethod:: debug_toolbar.panels.Panel.process_request + .. automethod:: debug_toolbar.panels.Panel.generate_server_timing + .. automethod:: debug_toolbar.panels.Panel.generate_stats + .. automethod:: debug_toolbar.panels.Panel.get_headers + .. automethod:: debug_toolbar.panels.Panel.run_checks .. _javascript-api: @@ -381,7 +399,9 @@ common methods available. :param value: The value to be set. :param options: The options for the value to be set. It should contain the - properties ``expires`` and ``path``. + properties ``expires`` and ``path``. The properties ``domain``, + ``secure`` and ``samesite`` are also supported. ``samesite`` defaults + to ``lax`` if not provided. .. js:function:: djdt.hide_toolbar @@ -389,4 +409,36 @@ common methods available. .. js:function:: djdt.show_toolbar - Shows the toolbar. + Shows the toolbar. This can be used to re-render the toolbar when reloading the + entire DOM. For example, then using `HTMX's boosting`_. + +.. _HTMX's boosting: https://htmx.org/docs/#boosting + +Events +^^^^^^ + +.. js:attribute:: djdt.panel.render + + This is an event raised when a panel is rendered. It has the property + ``detail.panelId`` which identifies which panel has been loaded. This + event can be useful when creating custom scripts to process the HTML + further. + + An example of this for the ``CustomPanel`` would be: + +.. code-block:: javascript + + import { $$ } from "./utils.js"; + function addCustomMetrics() { + // Logic to process/add custom metrics here. + + // Be sure to cover the case of this function being called twice + // due to file being loaded asynchronously. + } + const djDebug = document.getElementById("djDebug"); + $$.onPanelRender(djDebug, "CustomPanel", addCustomMetrics); + // Since a panel's scripts are loaded asynchronously, it's possible that + // the above statement would occur after the djdt.panel.render event has + // been raised. To account for that, the rendering function should be + // called here as well. + addCustomMetrics(); diff --git a/docs/resources.rst b/docs/resources.rst new file mode 100644 index 000000000..d5974badb --- /dev/null +++ b/docs/resources.rst @@ -0,0 +1,78 @@ +Resources +========= + +This section includes resources that can be used to learn more about +the Django Debug Toolbar. + +Tutorials +--------- + +Django Debugging Tutorial +^^^^^^^^^^^^^^^^^^^^^^^^^ + +Originally presented as an in-person workshop at DjangoCon US 2022, this +tutorial by **Tim Schilling** covers debugging techniques in Django. Follow +along independently using the slides and GitHub repository. + +* `View the tutorial details on the conference website `__ +* `Follow along with the GitHub repository `__ +* `View the slides on Google Docs `__ +* Last updated: February 13, 2025. +* Estimated time to complete: 1-2 hours. + +Mastering Django Debug Toolbar: Efficient Debugging and Optimization Techniques +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This tutorial by **Bob Belderbos** provides an in-depth look at effectively +using Django Debug Toolbar to debug Django applications, covering installation, +configuration, and practical usage. + +* `Watch on YouTube `__ +* Published: May 13, 2023. +* Duration: 11 minutes. + +Talks +----- + +A Related Matter: Optimizing Your Web App by Using Django Debug Toolbar +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Presented at DjangoCon US 2024 by **Christopher Adams**, this talk delves into +optimizing web applications using Django Debug Toolbar, focusing on SQL query +analysis and performance improvements. + +* `View the talk details on the conference website `__ +* `Watch on DjangoTV `__ +* Published: December 6, 2024. +* Duration: 26 minutes. + +Fast on My Machine: How to Debug Slow Requests in Production +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Presented at DjangoCon Europe 2024 by **Raphael Michel**, this talk explores +debugging slow requests in production. While not focused on Django Debug +Toolbar, it highlights performance issues the tool can help diagnose. + +* `View the talk details on the conference website `__ +* `Watch on DjangoTV `__ +* Published: July 11, 2024. +* Duration: 23 minutes. + +Want to Add Your Content Here? +------------------------------ + +Have a great tutorial or talk about Django Debug Toolbar? We'd love to +showcase it! If your content helps developers improve their debugging skills, +follow our :doc:`contributing guidelines ` to submit it. + +To ensure relevant and accessible content, please check the following +before submitting: + +1. Does it at least partially focus on the Django Debug Toolbar? +2. Does the content show a version of Django that is currently supported? +3. What language is the tutorial in and what languages are the captions + available in? + +Talks and tutorials that cover advanced debugging techniques, +performance optimization, and real-world applications are particularly +welcome. diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index ede7915a1..79b05cb06 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -1,39 +1,71 @@ +Hatchling +Hotwire +Jazzband +Makefile +Pympler +Roboto +Transifex +Werkzeug +aenable +ajax +asgi +async backend backends +backported +biome checkbox contrib +csp +dicts django fallbacks flamegraph flatpages frontend +htmx inlining +instrumentation isort -Jazzband -jinja jQuery +jinja jrestclient js -Makefile +margins memcache memcached middleware middlewares +mixin +mousedown +mouseup multi neo +nothreading +paddings +pre profiler psycopg py +pyflame pylibmc -Pympler +pytest +pyupgrade querysets refactoring +reinitializing +resizing +runserver +spellchecking spooler stacktrace stacktraces +startup +staticfiles +theming timeline -Transifex -unhashable +tox uWSGI +unhandled +unhashable validator -Werkzeug diff --git a/docs/tips.rst b/docs/tips.rst index f7a31e927..c79d12523 100644 --- a/docs/tips.rst +++ b/docs/tips.rst @@ -20,6 +20,44 @@ Browsers have become more aggressive with caching static assets, such as JavaScript and CSS files. Check your browser's development console, and if you see errors, try a hard browser refresh or clearing your cache. +Working with htmx and Turbo +---------------------------- + +Libraries such as `htmx `_ and +`Turbo `_ need additional configuration to retain +the toolbar handle element through page renders. This can be done by +configuring the :ref:`ROOT_TAG_EXTRA_ATTRS ` to include +the relevant JavaScript library's attribute. + +htmx +~~~~ + +The attribute `htmx `_ uses is +`hx-preserve `_. + +Update your settings to include: + +.. code-block:: python + + DEBUG_TOOLBAR_CONFIG = { + "ROOT_TAG_EXTRA_ATTRS": "hx-preserve" + } + +Hotwire Turbo +~~~~~~~~~~~~~ + +The attribute `Turbo `_ uses is +`data-turbo-permanent `_ + +Update your settings to include: + +.. code-block:: python + + DEBUG_TOOLBAR_CONFIG = { + "ROOT_TAG_EXTRA_ATTRS": "data-turbo-permanent" + } + + Performance considerations -------------------------- @@ -51,8 +89,9 @@ development. The cache panel is very similar to the SQL panel, except it isn't always a bad practice to make many cache queries in a view. -The template panel becomes slow if your views or context processors return large -contexts and your templates have complex inheritance or inclusion schemes. +The template panel becomes slow if your views or context processors return +large contexts and your templates have complex inheritance or inclusion +schemes. Solutions ~~~~~~~~~ @@ -76,6 +115,8 @@ by disabling some configuration options that are enabled by default: - ``ENABLE_STACKTRACES`` for the SQL and cache panels, - ``SHOW_TEMPLATE_CONTEXT`` for the template panel. +- ``PROFILER_CAPTURE_PROJECT_CODE`` and ``PROFILER_THRESHOLD_RATIO`` for the + profiling panel. Also, check ``SKIP_TEMPLATE_PREFIXES`` when you're using template-based form widgets. diff --git a/example/README.rst b/example/README.rst index 2b9f41fc2..5102abd18 100644 --- a/example/README.rst +++ b/example/README.rst @@ -13,9 +13,10 @@ interfere with common JavaScript frameworks. How to ------ -The example project requires a working installation of Django:: +The example project requires a working installation of Django and a few other +packages:: - $ python -m pip install Django + $ python -m pip install -r requirements_dev.txt The following command must run from the root directory of Django Debug Toolbar, i.e. the directory that contains ``example/``:: @@ -41,8 +42,18 @@ Run the Django development server:: $ python example/manage.py runserver -You can change the database used by specifying the ``DJANGO_DATABASE_ENGINE`` +You can change the database used by specifying the ``DB_BACKEND`` environment variable:: - $ DJANGO_DATABASE_ENGINE=postgresql python example/manage.py migrate - $ DJANGO_DATABASE_ENGINE=postgresql python example/manage.py runserver + $ DB_BACKEND=postgresql python example/manage.py migrate + $ DB_BACKEND=postgresql python example/manage.py runserver + +Using an asynchronous (ASGI) server: + +Install [Daphne](https://pypi.org/project/daphne/) first: + + $ python -m pip install daphne + +Then run the Django development server: + + $ ASYNC_SERVER=true python example/manage.py runserver diff --git a/example/asgi.py b/example/asgi.py new file mode 100644 index 000000000..9d7c78703 --- /dev/null +++ b/example/asgi.py @@ -0,0 +1,9 @@ +"""ASGI config for example project.""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings") + +application = get_asgi_application() diff --git a/example/django-debug-toolbar.png b/example/django-debug-toolbar.png index 762411772..e074973e6 100644 Binary files a/example/django-debug-toolbar.png and b/example/django-debug-toolbar.png differ diff --git a/example/screenshot.py b/example/screenshot.py index 0d0ae8dc5..129465d79 100644 --- a/example/screenshot.py +++ b/example/screenshot.py @@ -3,7 +3,9 @@ import os import signal import subprocess +from time import sleep +from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait @@ -33,7 +35,10 @@ def create_webdriver(browser, headless): def example_server(): - return subprocess.Popen(["make", "example"]) + proc = subprocess.Popen(["make", "example"]) + # `make example` runs a few things before runserver. + sleep(2) + return proc def set_viewport_size(selenium, width, height): @@ -50,7 +55,7 @@ def set_viewport_size(selenium, width, height): def submit_form(selenium, data): url = selenium.current_url for name, value in data.items(): - el = selenium.find_element_by_name(name) + el = selenium.find_element(By.NAME, name) el.send_keys(value) el.send_keys(Keys.RETURN) WebDriverWait(selenium, timeout=5).until(EC.url_changes(url)) @@ -67,12 +72,15 @@ def main(): submit_form(selenium, {"username": os.environ["USER"], "password": "p"}) selenium.get("http://localhost:8000/admin/auth/user/") - # Close the admin sidebar. - el = selenium.find_element_by_id("toggle-nav-sidebar") - el.click() + # Check if SQL Panel is already visible: + sql_panel = selenium.find_element(By.ID, "djdt-SQLPanel") + if not sql_panel: + # Open the admin sidebar. + el = selenium.find_element(By.ID, "djDebugToolbarHandle") + el.click() + sql_panel = selenium.find_element(By.ID, "djdt-SQLPanel") # Open the SQL panel. - el = selenium.find_element_by_id("djdt-SQLPanel") - el.click() + sql_panel.click() selenium.save_screenshot(args.outfile) finally: diff --git a/example/settings.py b/example/settings.py index b04d283c8..06b70f7fa 100644 --- a/example/settings.py +++ b/example/settings.py @@ -1,12 +1,14 @@ """Django settings for example project.""" import os +import sys BASE_DIR = os.path.dirname(os.path.dirname(__file__)) # Quick-start development settings - unsuitable for production + SECRET_KEY = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" DEBUG = True @@ -16,17 +18,16 @@ # Application definition INSTALLED_APPS = [ + *(["daphne"] if os.getenv("ASYNC_SERVER", False) else []), # noqa: FBT003 "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", - "debug_toolbar", ] MIDDLEWARE = [ - "debug_toolbar.middleware.DebugToolbarMiddleware", "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", @@ -41,6 +42,12 @@ STATIC_URL = "/static/" TEMPLATES = [ + { + "NAME": "jinja2", + "BACKEND": "django.template.backends.jinja2.Jinja2", + "APP_DIRS": True, + "DIRS": [os.path.join(BASE_DIR, "example", "templates", "jinja2")], + }, { "BACKEND": "django.template.backends.django.DjangoTemplates", "APP_DIRS": True, @@ -54,10 +61,13 @@ "django.contrib.messages.context_processors.messages", ], }, - } + }, ] +USE_TZ = True + WSGI_APPLICATION = "example.wsgi.application" +ASGI_APPLICATION = "example.asgi.application" # Cache and database @@ -71,29 +81,40 @@ } } -# To use another database, set the DJANGO_DATABASE_ENGINE environment variable. -if os.environ.get("DJANGO_DATABASE_ENGINE", "").lower() == "postgresql": - # % su postgres - # % createuser debug_toolbar - # % createdb debug_toolbar -O debug_toolbar +# To use another database, set the DB_BACKEND environment variable. +if os.environ.get("DB_BACKEND", "").lower() == "postgresql": + # See docs/contributing for instructions on configuring PostgreSQL. DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", "NAME": "debug_toolbar", "USER": "debug_toolbar", + "PASSWORD": "debug_toolbar", } } -if os.environ.get("DJANGO_DATABASE_ENGINE", "").lower() == "mysql": - # % mysql - # mysql> CREATE DATABASE debug_toolbar; - # mysql> CREATE USER 'debug_toolbar'@'localhost'; - # mysql> GRANT ALL PRIVILEGES ON debug_toolbar.* TO 'debug_toolbar'@'localhost'; +if os.environ.get("DB_BACKEND", "").lower() == "mysql": + # See docs/contributing for instructions on configuring MySQL/MariaDB. DATABASES = { "default": { "ENGINE": "django.db.backends.mysql", "NAME": "debug_toolbar", "USER": "debug_toolbar", + "PASSWORD": "debug_toolbar", } } STATICFILES_DIRS = [os.path.join(BASE_DIR, "example", "static")] + +# Only enable the toolbar when we're in debug mode and we're +# not running tests. Django will change DEBUG to be False for +# tests, so we can't rely on DEBUG alone. +ENABLE_DEBUG_TOOLBAR = DEBUG and "test" not in sys.argv +if ENABLE_DEBUG_TOOLBAR: + INSTALLED_APPS += [ + "debug_toolbar", + ] + MIDDLEWARE += [ + "debug_toolbar.middleware.DebugToolbarMiddleware", + ] + # Customize the config to support turbo and htmx boosting. + DEBUG_TOOLBAR_CONFIG = {"ROOT_TAG_EXTRA_ATTRS": "data-turbo-permanent hx-preserve"} diff --git a/example/templates/async_db.html b/example/templates/async_db.html new file mode 100644 index 000000000..771c039e3 --- /dev/null +++ b/example/templates/async_db.html @@ -0,0 +1,14 @@ + + + + + Async DB + + +

    Async DB

    +

    + Value + {{ user_count }} +

    + + diff --git a/example/templates/bad_form.html b/example/templates/bad_form.html new file mode 100644 index 000000000..f50662c6e --- /dev/null +++ b/example/templates/bad_form.html @@ -0,0 +1,14 @@ +{% load cache %} + + + + + Bad form + + +

    Bad form test

    + + + + + diff --git a/example/templates/htmx/boost.html b/example/templates/htmx/boost.html new file mode 100644 index 000000000..7153a79ee --- /dev/null +++ b/example/templates/htmx/boost.html @@ -0,0 +1,30 @@ +{% load cache %} + + + + + Index of Tests (htmx) + + + +

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

    + +

    + For the debug panel to remain through page navigation, add the setting: +

    +DEBUG_TOOLBAR_CONFIG = {
    +  "ROOT_TAG_EXTRA_ATTRS": "hx-preserve"
    +}
    +      
    +

    + + + + Home + + + diff --git a/example/templates/index.html b/example/templates/index.html index 1616d3248..4b25aefca 100644 --- a/example/templates/index.html +++ b/example/templates/index.html @@ -9,11 +9,43 @@

    Index of Tests

    {% cache 10 index_cache %}

    Django Admin

    {% endcache %} +

    + Value + {{ request.session.value|default:0 }} + + +

    + diff --git a/example/templates/jinja2/index.jinja b/example/templates/jinja2/index.jinja new file mode 100644 index 000000000..ffd1ada6f --- /dev/null +++ b/example/templates/jinja2/index.jinja @@ -0,0 +1,12 @@ + + + + + jinja Test + + +

    jinja Test

    + {{ foo }} + {% for i in range(10) %}{{ i }}{% endfor %} {# Jinja2 supports range(), Django templates do not #} + + diff --git a/example/templates/turbo/index.html b/example/templates/turbo/index.html new file mode 100644 index 000000000..16ca9f2c6 --- /dev/null +++ b/example/templates/turbo/index.html @@ -0,0 +1,56 @@ +{% load cache %} + + + + + Index of Tests + + + +

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

    + +

    + For the debug panel to remain through page navigation, add the setting: +

    +DEBUG_TOOLBAR_CONFIG = {
    +  "ROOT_TAG_EXTRA_ATTRS": "data-turbo-permanent"
    +}
    +      
    +

    + + +

    + Value + {{ request.session.value|default:0 }} + + +

    + + Home + + diff --git a/example/test_views.py b/example/test_views.py new file mode 100644 index 000000000..f31a8b3c8 --- /dev/null +++ b/example/test_views.py @@ -0,0 +1,12 @@ +# Add tests to example app to check how the toolbar is used +# when running tests for a project. +# See https://github.com/django-commons/django-debug-toolbar/issues/1405 + +from django.test import TestCase +from django.urls import reverse + + +class ViewTestCase(TestCase): + def test_index(self): + response = self.client.get(reverse("home")) + assert response.status_code == 200 diff --git a/example/urls.py b/example/urls.py index a190deaaa..86e6827fc 100644 --- a/example/urls.py +++ b/example/urls.py @@ -1,14 +1,52 @@ from django.contrib import admin -from django.urls import include, path +from django.urls import path from django.views.generic import TemplateView -import debug_toolbar +from debug_toolbar.toolbar import debug_toolbar_urls +from example.views import ( + async_db, + async_db_concurrent, + async_home, + increment, + jinja2_view, +) urlpatterns = [ - path("", TemplateView.as_view(template_name="index.html")), + path("", TemplateView.as_view(template_name="index.html"), name="home"), + path( + "bad-form/", + TemplateView.as_view(template_name="bad_form.html"), + name="bad_form", + ), + path("jinja/", jinja2_view, name="jinja"), + path("async/", async_home, name="async_home"), + path("async/db/", async_db, name="async_db"), + path("async/db-concurrent/", async_db_concurrent, name="async_db_concurrent"), path("jquery/", TemplateView.as_view(template_name="jquery/index.html")), path("mootools/", TemplateView.as_view(template_name="mootools/index.html")), path("prototype/", TemplateView.as_view(template_name="prototype/index.html")), + path( + "htmx/boost/", + TemplateView.as_view(template_name="htmx/boost.html"), + name="htmx", + ), + path( + "htmx/boost/2", + TemplateView.as_view( + template_name="htmx/boost.html", extra_context={"page_num": "2"} + ), + name="htmx2", + ), + path( + "turbo/", TemplateView.as_view(template_name="turbo/index.html"), name="turbo" + ), + path( + "turbo/2", + TemplateView.as_view( + template_name="turbo/index.html", extra_context={"page_num": "2"} + ), + name="turbo2", + ), path("admin/", admin.site.urls), - path("__debug__/", include(debug_toolbar.urls)), -] + path("ajax/increment", increment, name="ajax_increment"), +] + debug_toolbar_urls() diff --git a/example/views.py b/example/views.py new file mode 100644 index 000000000..3e1cb04a6 --- /dev/null +++ b/example/views.py @@ -0,0 +1,42 @@ +import asyncio + +from asgiref.sync import sync_to_async +from django.contrib.auth.models import User +from django.http import JsonResponse +from django.shortcuts import render + + +def increment(request): + try: + value = int(request.session.get("value", 0)) + 1 + except ValueError: + value = 1 + request.session["value"] = value + return JsonResponse({"value": value}) + + +def jinja2_view(request): + return render(request, "index.jinja", {"foo": "bar"}, using="jinja2") + + +async def async_home(request): + return await sync_to_async(render)(request, "index.html") + + +async def async_db(request): + user_count = await User.objects.acount() + + return await sync_to_async(render)( + request, "async_db.html", {"user_count": user_count} + ) + + +async def async_db_concurrent(request): + # Do database queries concurrently + (user_count, _) = await asyncio.gather( + User.objects.acount(), User.objects.filter(username="test").acount() + ) + + return await sync_to_async(render)( + request, "async_db.html", {"user_count": user_count} + ) diff --git a/package.json b/package.json deleted file mode 100644 index 2e0e180bb..000000000 --- a/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "devDependencies": { - "eslint": "^7.10.0", - "prettier": "^2.1.2" - } -} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..adba4bb40 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,114 @@ +[build-system] +build-backend = "hatchling.build" +requires = [ + "hatchling", +] + +[project] +name = "django-debug-toolbar" +description = "A configurable set of panels that display various debug information about the current request/response." +readme = "README.rst" +license = { text = "BSD-3-Clause" } +authors = [ + { name = "Rob Hudson" }, +] +requires-python = ">=3.9" +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Web Environment", + "Framework :: Django", + "Framework :: Django :: 4.2", + "Framework :: Django :: 5.0", + "Framework :: Django :: 5.1", + "Framework :: Django :: 5.2", + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Topic :: Software Development :: Libraries :: Python Modules", +] +dynamic = [ + "version", +] +dependencies = [ + "django>=4.2.9", + "sqlparse>=0.2", +] + +urls.Changelog = "https://django-debug-toolbar.readthedocs.io/en/latest/changes.html" +urls.Documentation = "https://django-debug-toolbar.readthedocs.io/" +urls.Download = "https://pypi.org/project/django-debug-toolbar/" +urls.Homepage = "https://github.com/django-commons/django-debug-toolbar" +urls.Issues = "https://github.com/django-commons/django-debug-toolbar/issues" +urls.Source = "https://github.com/django-commons/django-debug-toolbar" + +[tool.hatch.build.targets.wheel] +packages = [ + "debug_toolbar", +] + +[tool.hatch.version] +path = "debug_toolbar/__init__.py" + +[tool.ruff] +target-version = "py39" + +fix = true +show-fixes = true +lint.extend-select = [ + "ASYNC", # flake8-async + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "C90", # McCabe cyclomatic complexity + "DJ", # flake8-django + "E", # pycodestyle errors + "F", # Pyflakes + "FBT", # flake8-boolean-trap + "I", # isort + "INT", # flake8-gettext + "PGH", # pygrep-hooks + "PIE", # flake8-pie + "RUF100", # Unused noqa directive + "SLOT", # flake8-slots + "UP", # pyupgrade + "W", # pycodestyle warnings +] +lint.extend-ignore = [ + "B905", # Allow zip() without strict= + "E501", # Ignore line length violations + "UP031", # It's not always wrong to use percent-formatting +] +lint.per-file-ignores."*/migrat*/*" = [ + "N806", # Allow using PascalCase model names in migrations + "N999", # Ignore the fact that migration files are invalid module names +] +lint.isort.combine-as-imports = true +lint.mccabe.max-complexity = 16 + +[tool.coverage.html] +skip_covered = true +skip_empty = true + +[tool.coverage.run] +branch = true +parallel = true +source = [ + "debug_toolbar", +] + +[tool.coverage.paths] +source = [ + "src", + ".tox/*/site-packages", +] + +[tool.coverage.report] +# Update coverage badge link in README.rst when fail_under changes +fail_under = 94 +show_missing = true diff --git a/requirements_dev.txt b/requirements_dev.txt index 6010ea4f7..6915226fd 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -6,20 +6,24 @@ Jinja2 # Testing -coverage -flake8 +coverage[toml] html5lib -isort selenium tox black +django-template-partials +django-csp # Used in tests/test_csp_rendering + +# Integration support + +daphne # async in Example app # Documentation Sphinx sphinxcontrib-spelling +sphinx-rtd-theme>1 # Other tools -transifex-client -wheel +pre-commit diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 0f95e92dc..000000000 --- a/setup.cfg +++ /dev/null @@ -1,51 +0,0 @@ -[metadata] -name = django-debug-toolbar -version = 3.2a1 -description = A configurable set of panels that display various debug information about the current request/response. -long_description = file: README.rst -author = Rob Hudson -author_email = rob@cogit8.org -url = https://github.com/jazzband/django-debug-toolbar -download_url = https://pypi.org/project/django-debug-toolbar/ -license = BSD -license_files = LICENSE -classifiers = - Development Status :: 5 - Production/Stable - Environment :: Web Environment - Framework :: Django - Framework :: Django :: 2.2 - Framework :: Django :: 3.0 - Framework :: Django :: 3.1 - Intended Audience :: Developers - License :: OSI Approved :: BSD License - Operating System :: OS Independent - Programming Language :: Python - Programming Language :: Python :: 3 - Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.5 - Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 - Topic :: Software Development :: Libraries :: Python Modules - -[options] -python_requires = >=3.5 -install_requires = - Django >= 2.2 - sqlparse >= 0.2.0 -packages = find: -include_package_data = true -zip_safe = false - -[options.packages.find] -exclude = - example - tests - tests.* - -[flake8] -extend-ignore = E203, E501 - -[isort] -combine_as_imports = true -profile = black diff --git a/setup.py b/setup.py index 229b2ebbb..3893c8d49 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,14 @@ #!/usr/bin/env python3 -from setuptools import setup +import sys -setup() +sys.stderr.write( + """\ +=============================== +Unsupported installation method +=============================== +This project no longer supports installation with `python setup.py install`. +Please use `python -m pip install .` instead. +""" +) +sys.exit(1) diff --git a/tests/__init__.py b/tests/__init__.py index c8813783f..e69de29bb 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,21 +0,0 @@ -# Refresh the debug toolbar's configuration when overriding settings. - -from django.dispatch import receiver -from django.test.signals import setting_changed - -from debug_toolbar import settings as dt_settings -from debug_toolbar.toolbar import DebugToolbar - - -@receiver(setting_changed) -def update_toolbar_config(**kwargs): - if kwargs["setting"] == "DEBUG_TOOLBAR_CONFIG": - dt_settings.get_config.cache_clear() - - -@receiver(setting_changed) -def update_toolbar_panels(**kwargs): - if kwargs["setting"] == "DEBUG_TOOLBAR_PANELS": - dt_settings.get_panels.cache_clear() - DebugToolbar._panel_classes = None - # Not implemented: invalidate debug_toolbar.urls. diff --git a/tests/base.py b/tests/base.py index c09828b4f..3f40261fe 100644 --- a/tests/base.py +++ b/tests/base.py @@ -1,20 +1,86 @@ +import contextvars +from typing import Optional + import html5lib +from asgiref.local import Local from django.http import HttpResponse -from django.test import RequestFactory, TestCase +from django.test import ( + AsyncClient, + AsyncRequestFactory, + Client, + RequestFactory, + TestCase, + TransactionTestCase, +) +from debug_toolbar.panels import Panel from debug_toolbar.toolbar import DebugToolbar +data_contextvar = contextvars.ContextVar("djdt_toolbar_test_client") + + +class ToolbarTestClient(Client): + def request(self, **request): + # Use a thread/async task context-local variable to guard against a + # concurrent _created signal from a different thread/task. + data = Local() + data.toolbar = None + + def handle_toolbar_created(sender, toolbar=None, **kwargs): + data.toolbar = toolbar + + DebugToolbar._created.connect(handle_toolbar_created) + try: + response = super().request(**request) + finally: + DebugToolbar._created.disconnect(handle_toolbar_created) + response.toolbar = data.toolbar + + return response + + +class AsyncToolbarTestClient(AsyncClient): + async def request(self, **request): + # Use a thread/async task context-local variable to guard against a + # concurrent _created signal from a different thread/task. + # In cases testsuite will have both regular and async tests or + # multiple async tests running in an eventloop making async_client calls. + data_contextvar.set(None) + + def handle_toolbar_created(sender, toolbar=None, **kwargs): + data_contextvar.set(toolbar) + + DebugToolbar._created.connect(handle_toolbar_created) + try: + response = await super().request(**request) + finally: + DebugToolbar._created.disconnect(handle_toolbar_created) + response.toolbar = data_contextvar.get() + + return response + + rf = RequestFactory() +arf = AsyncRequestFactory() -class BaseTestCase(TestCase): +class BaseMixin: + _is_async = False + client_class = ToolbarTestClient + async_client_class = AsyncToolbarTestClient + + panel: Optional[Panel] = None panel_id = None def setUp(self): super().setUp() self._get_response = lambda request: HttpResponse() self.request = rf.get("/") - self.toolbar = DebugToolbar(self.request, self.get_response) + if self._is_async: + self.request = arf.get("/") + self.toolbar = DebugToolbar(self.request, self.get_response_async) + else: + self.toolbar = DebugToolbar(self.request, self.get_response) self.toolbar.stats = {} if self.panel_id: @@ -31,18 +97,27 @@ def tearDown(self): def get_response(self, request): return self._get_response(request) - def assertValidHTML(self, content, msg=None): + async def get_response_async(self, request): + return self._get_response(request) + + def assertValidHTML(self, content): parser = html5lib.HTMLParser() - parser.parseFragment(self.panel.content) + parser.parseFragment(content) if parser.errors: - default_msg = ["Content is invalid HTML:"] + msg_parts = ["Invalid HTML:"] lines = content.split("\n") for position, errorcode, datavars in parser.errors: - default_msg.append(" %s" % html5lib.constants.E[errorcode] % datavars) - default_msg.append(" %s" % lines[position[0] - 1]) + msg_parts.append(f" {html5lib.constants.E[errorcode]}" % datavars) + msg_parts.append(f" {lines[position[0] - 1]}") + raise self.failureException("\n".join(msg_parts)) + + +class BaseTestCase(BaseMixin, TestCase): + pass + - msg = self._formatMessage(msg, "\n".join(default_msg)) - raise self.failureException(msg) +class BaseMultiDBTestCase(BaseMixin, TransactionTestCase): + databases = {"default", "replica"} class IntegrationTestCase(TestCase): diff --git a/tests/commands/test_debugsqlshell.py b/tests/commands/test_debugsqlshell.py index 9520d0dd8..9939c5ca9 100644 --- a/tests/commands/test_debugsqlshell.py +++ b/tests/commands/test_debugsqlshell.py @@ -1,14 +1,13 @@ import io import sys -import django from django.contrib.auth.models import User from django.core import management from django.db import connection from django.test import TestCase from django.test.utils import override_settings -if connection.vendor == "postgresql" and django.VERSION >= (3, 0, 0): +if connection.vendor == "postgresql": from django.db.backends.postgresql import base as base_module else: from django.db.backends import utils as base_module diff --git a/tests/context_processors.py b/tests/context_processors.py index 6fe220dba..69e112a39 100644 --- a/tests/context_processors.py +++ b/tests/context_processors.py @@ -1,2 +1,2 @@ def broken(request): - request.non_existing_attribute + _read = request.non_existing_attribute diff --git a/tests/middleware.py b/tests/middleware.py new file mode 100644 index 000000000..ce46e2066 --- /dev/null +++ b/tests/middleware.py @@ -0,0 +1,17 @@ +from django.core.cache import cache + + +class UseCacheAfterToolbar: + """ + This middleware exists to use the cache before and after + the toolbar is setup. + """ + + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + cache.set("UseCacheAfterToolbar.before", 1) + response = self.get_response(request) + cache.set("UseCacheAfterToolbar.after", 1) + return response diff --git a/tests/models.py b/tests/models.py index d6829eabc..e19bfe59d 100644 --- a/tests/models.py +++ b/tests/models.py @@ -1,4 +1,6 @@ +from django.conf import settings from django.db import models +from django.db.models import JSONField class NonAsciiRepr: @@ -9,17 +11,22 @@ def __repr__(self): class Binary(models.Model): field = models.BinaryField() + def __str__(self): + return "" -try: - from django.db.models import JSONField -except ImportError: # Django<3.1 - try: - from django.contrib.postgres.fields import JSONField - except ImportError: # psycopg2 not installed - JSONField = None +class PostgresJSON(models.Model): + field = JSONField() -if JSONField: + def __str__(self): + return "" - class PostgresJSON(models.Model): - field = JSONField() + +if settings.USE_GIS: + from django.contrib.gis.db import models as gismodels + + class Location(gismodels.Model): + point = gismodels.PointField() + + def __str__(self): + return "" diff --git a/tests/panels/test_alerts.py b/tests/panels/test_alerts.py new file mode 100644 index 000000000..5c926f275 --- /dev/null +++ b/tests/panels/test_alerts.py @@ -0,0 +1,112 @@ +from django.http import HttpResponse, StreamingHttpResponse +from django.template import Context, Template + +from ..base import BaseTestCase + + +class AlertsPanelTestCase(BaseTestCase): + panel_id = "AlertsPanel" + + def test_alert_warning_display(self): + """ + Test that the panel (does not) display[s] an alert when there are + (no) problems. + """ + self.panel.record_stats({"alerts": []}) + self.assertNotIn("alerts", self.panel.nav_subtitle) + + self.panel.record_stats({"alerts": ["Alert 1", "Alert 2"]}) + self.assertIn("2 alerts", self.panel.nav_subtitle) + + def test_file_form_without_enctype_multipart_form_data(self): + """ + Test that the panel displays a form invalid message when there is + a file input but encoding not set to multipart/form-data. + """ + test_form = '
    ' + result = self.panel.check_invalid_file_form_configuration(test_form) + expected_error = ( + 'Form with id "test-form" contains file input, ' + 'but does not have the attribute enctype="multipart/form-data".' + ) + self.assertEqual(result[0]["alert"], expected_error) + self.assertEqual(len(result), 1) + + def test_file_form_no_id_without_enctype_multipart_form_data(self): + """ + Test that the panel displays a form invalid message when there is + a file input but encoding not set to multipart/form-data. + + This should use the message when the form has no id. + """ + test_form = '
    ' + result = self.panel.check_invalid_file_form_configuration(test_form) + expected_error = ( + "Form contains file input, but does not have " + 'the attribute enctype="multipart/form-data".' + ) + self.assertEqual(result[0]["alert"], expected_error) + self.assertEqual(len(result), 1) + + def test_file_form_with_enctype_multipart_form_data(self): + test_form = """
    + + """ + result = self.panel.check_invalid_file_form_configuration(test_form) + + self.assertEqual(len(result), 0) + + def test_file_form_with_enctype_multipart_form_data_in_button(self): + test_form = """
    + + + """ + result = self.panel.check_invalid_file_form_configuration(test_form) + + self.assertEqual(len(result), 0) + + def test_referenced_file_input_without_enctype_multipart_form_data(self): + test_file_input = """
    + """ + result = self.panel.check_invalid_file_form_configuration(test_file_input) + + expected_error = ( + 'Input element references form with id "test-form", ' + 'but the form does not have the attribute enctype="multipart/form-data".' + ) + self.assertEqual(result[0]["alert"], expected_error) + self.assertEqual(len(result), 1) + + def test_referenced_file_input_with_enctype_multipart_form_data(self): + test_file_input = """
    + + """ + result = self.panel.check_invalid_file_form_configuration(test_file_input) + + self.assertEqual(len(result), 0) + + def test_integration_file_form_without_enctype_multipart_form_data(self): + t = Template('
    ') + c = Context({}) + rendered_template = t.render(c) + response = HttpResponse(content=rendered_template) + + self.panel.generate_stats(self.request, response) + + self.assertIn("1 alert", self.panel.nav_subtitle) + self.assertIn( + "Form with id "test-form" contains file input, " + "but does not have the attribute enctype="multipart/form-data".", + self.panel.content, + ) + + def test_streaming_response(self): + """Test to check for a streaming response.""" + + def _render(): + yield "ok" + + response = StreamingHttpResponse(_render()) + + self.panel.generate_stats(self.request, response) + self.assertEqual(self.panel.get_stats(), {}) diff --git a/tests/panels/test_async_panel_compatibility.py b/tests/panels/test_async_panel_compatibility.py new file mode 100644 index 000000000..d5a85ffbb --- /dev/null +++ b/tests/panels/test_async_panel_compatibility.py @@ -0,0 +1,39 @@ +from django.http import HttpResponse +from django.test import AsyncRequestFactory, RequestFactory, TestCase + +from debug_toolbar.panels import Panel +from debug_toolbar.toolbar import DebugToolbar + + +class MockAsyncPanel(Panel): + is_async = True + + +class MockSyncPanel(Panel): + is_async = False + + +class PanelAsyncCompatibilityTestCase(TestCase): + def setUp(self): + self.async_factory = AsyncRequestFactory() + self.wsgi_factory = RequestFactory() + + def test_panels_with_asgi(self): + async_request = self.async_factory.get("/") + toolbar = DebugToolbar(async_request, lambda request: HttpResponse()) + + async_panel = MockAsyncPanel(toolbar, async_request) + sync_panel = MockSyncPanel(toolbar, async_request) + + self.assertTrue(async_panel.enabled) + self.assertFalse(sync_panel.enabled) + + def test_panels_with_wsgi(self): + wsgi_request = self.wsgi_factory.get("/") + toolbar = DebugToolbar(wsgi_request, lambda request: HttpResponse()) + + async_panel = MockAsyncPanel(toolbar, wsgi_request) + sync_panel = MockSyncPanel(toolbar, wsgi_request) + + self.assertTrue(async_panel.enabled) + self.assertTrue(sync_panel.enabled) diff --git a/tests/panels/test_cache.py b/tests/panels/test_cache.py index 1ffdddc97..aacf521cb 100644 --- a/tests/panels/test_cache.py +++ b/tests/panels/test_cache.py @@ -26,6 +26,92 @@ def test_recording_caches(self): second_cache.get("foo") self.assertEqual(len(self.panel.calls), 2) + def test_hits_and_misses(self): + cache.cache.clear() + cache.cache.get("foo") + self.assertEqual(self.panel.hits, 0) + self.assertEqual(self.panel.misses, 1) + cache.cache.set("foo", 1) + cache.cache.get("foo") + self.assertEqual(self.panel.hits, 1) + self.assertEqual(self.panel.misses, 1) + cache.cache.get_many(["foo", "bar"]) + self.assertEqual(self.panel.hits, 2) + self.assertEqual(self.panel.misses, 2) + cache.cache.set("bar", 2) + cache.cache.get_many(keys=["foo", "bar"]) + self.assertEqual(self.panel.hits, 4) + self.assertEqual(self.panel.misses, 2) + + def test_get_or_set_value(self): + cache.cache.get_or_set("baz", "val") + self.assertEqual(cache.cache.get("baz"), "val") + calls = [ + (call["name"], call["args"], call["kwargs"]) for call in self.panel.calls + ] + self.assertEqual( + calls, + [ + ("get_or_set", ("baz", "val"), {}), + ("get", ("baz",), {}), + ], + ) + self.assertEqual( + self.panel.counts, + { + "add": 0, + "get": 1, + "set": 0, + "get_or_set": 1, + "touch": 0, + "delete": 0, + "clear": 0, + "get_many": 0, + "set_many": 0, + "delete_many": 0, + "has_key": 0, + "incr": 0, + "decr": 0, + "incr_version": 0, + "decr_version": 0, + }, + ) + + def test_get_or_set_does_not_override_existing_value(self): + cache.cache.set("foo", "bar") + cached_value = cache.cache.get_or_set("foo", "other") + self.assertEqual(cached_value, "bar") + calls = [ + (call["name"], call["args"], call["kwargs"]) for call in self.panel.calls + ] + self.assertEqual( + calls, + [ + ("set", ("foo", "bar"), {}), + ("get_or_set", ("foo", "other"), {}), + ], + ) + self.assertEqual( + self.panel.counts, + { + "add": 0, + "get": 0, + "set": 1, + "get_or_set": 1, + "touch": 0, + "delete": 0, + "clear": 0, + "get_many": 0, + "set_many": 0, + "delete_many": 0, + "has_key": 0, + "incr": 0, + "decr": 0, + "incr_version": 0, + "decr_version": 0, + }, + ) + def test_insert_content(self): """ Test that the panel only inserts content after generate_stats and diff --git a/tests/panels/test_custom.py b/tests/panels/test_custom.py index f13c4ef62..661a5cc53 100644 --- a/tests/panels/test_custom.py +++ b/tests/panels/test_custom.py @@ -33,8 +33,8 @@ def test_escapes_panel_title(self): """
    -

    Title with special chars &"'<>

    +
    diff --git a/tests/panels/test_history.py b/tests/panels/test_history.py index 6d65b6e9d..4c5244934 100644 --- a/tests/panels/test_history.py +++ b/tests/panels/test_history.py @@ -1,9 +1,9 @@ -from unittest.mock import patch +import copy +import html from django.test import RequestFactory, override_settings from django.urls import resolve, reverse -from debug_toolbar.panels.history.forms import HistoryStoreForm from debug_toolbar.toolbar import DebugToolbar from ..base import BaseTestCase, IntegrationTestCase @@ -30,7 +30,8 @@ def test_post(self): def test_post_json(self): for data, expected_stats_data in ( ({"foo": "bar"}, {"foo": "bar"}), - ("", {}), + ("", {}), # Empty JSON + ("'", {}), # Invalid JSON ): with self.subTest(data=data): self.request = rf.post( @@ -65,6 +66,21 @@ def test_urls(self): @override_settings(DEBUG=True) class HistoryViewsTestCase(IntegrationTestCase): + PANEL_KEYS = { + "VersionsPanel", + "TimerPanel", + "SettingsPanel", + "HeadersPanel", + "RequestPanel", + "SQLPanel", + "StaticFilesPanel", + "TemplatesPanel", + "AlertsPanel", + "CachePanel", + "SignalsPanel", + "ProfilingPanel", + } + def test_history_panel_integration_content(self): """Verify the history panel's content renders properly..""" self.assertEqual(len(DebugToolbar._store), 0) @@ -77,80 +93,105 @@ def test_history_panel_integration_content(self): toolbar = list(DebugToolbar._store.values())[0] content = toolbar.get_panel_by_id("HistoryPanel").content self.assertIn("bar", content) + self.assertIn('name="exclude_history" value="True"', content) def test_history_sidebar_invalid(self): response = self.client.get(reverse("djdt:history_sidebar")) self.assertEqual(response.status_code, 400) - data = { - "store_id": "foo", - "hash": "invalid", - } + def test_history_headers(self): + """Validate the headers injected from the history panel.""" + response = self.client.get("/json_view/") + store_id = list(DebugToolbar._store)[0] + self.assertEqual(response.headers["djdt-store-id"], store_id) + + @override_settings( + DEBUG_TOOLBAR_CONFIG={"OBSERVE_REQUEST_CALLBACK": lambda request: False} + ) + def test_history_headers_unobserved(self): + """Validate the headers aren't injected from the history panel.""" + response = self.client.get("/json_view/") + self.assertNotIn("djdt-store-id", response.headers) + + def test_history_sidebar(self): + """Validate the history sidebar view.""" + self.client.get("/json_view/") + store_id = list(DebugToolbar._store)[0] + data = {"store_id": store_id, "exclude_history": True} response = self.client.get(reverse("djdt:history_sidebar"), data=data) - self.assertEqual(response.status_code, 400) + self.assertEqual(response.status_code, 200) + self.assertEqual( + set(response.json()), + self.PANEL_KEYS, + ) - @patch("debug_toolbar.panels.history.views.DebugToolbar.fetch") - def test_history_sidebar_hash(self, fetch): - """Validate the hashing mechanism.""" - fetch.return_value.panels = [] - data = { - "store_id": "foo", - "hash": "3280d66a3cca10098a44907c5a1fd255265eed31", - } + def test_history_sidebar_includes_history(self): + """Validate the history sidebar view.""" + self.client.get("/json_view/") + panel_keys = copy.copy(self.PANEL_KEYS) + panel_keys.add("HistoryPanel") + panel_keys.add("RedirectsPanel") + store_id = list(DebugToolbar._store)[0] + data = {"store_id": store_id} response = self.client.get(reverse("djdt:history_sidebar"), data=data) self.assertEqual(response.status_code, 200) - self.assertEqual(response.json(), {}) + self.assertEqual( + set(response.json()), + panel_keys, + ) - def test_history_sidebar(self): + @override_settings( + DEBUG_TOOLBAR_CONFIG={"RESULTS_CACHE_SIZE": 1, "RENDER_PANELS": False} + ) + def test_history_sidebar_expired_store_id(self): """Validate the history sidebar view.""" self.client.get("/json_view/") - store_id = list(DebugToolbar._store.keys())[0] - data = { - "store_id": store_id, - "hash": HistoryStoreForm.make_hash({"store_id": store_id}), - } + store_id = list(DebugToolbar._store)[0] + data = {"store_id": store_id, "exclude_history": True} response = self.client.get(reverse("djdt:history_sidebar"), data=data) self.assertEqual(response.status_code, 200) self.assertEqual( - set(response.json().keys()), - { - "VersionsPanel", - "TimerPanel", - "SettingsPanel", - "HeadersPanel", - "RequestPanel", - "SQLPanel", - "StaticFilesPanel", - "TemplatesPanel", - "CachePanel", - "SignalsPanel", - "LoggingPanel", - "ProfilingPanel", - }, + set(response.json()), + self.PANEL_KEYS, ) + self.client.get("/json_view/") - def test_history_refresh_invalid(self): - response = self.client.get(reverse("djdt:history_refresh")) - self.assertEqual(response.status_code, 400) + # Querying old store_id should return in empty response + data = {"store_id": store_id, "exclude_history": True} + response = self.client.get(reverse("djdt:history_sidebar"), data=data) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json(), {}) - data = { - "store_id": "foo", - "hash": "invalid", - } - response = self.client.get(reverse("djdt:history_refresh"), data=data) - self.assertEqual(response.status_code, 400) + # Querying with latest store_id + latest_store_id = list(DebugToolbar._store)[0] + data = {"store_id": latest_store_id, "exclude_history": True} + response = self.client.get(reverse("djdt:history_sidebar"), data=data) + self.assertEqual(response.status_code, 200) + self.assertEqual( + set(response.json()), + self.PANEL_KEYS, + ) def test_history_refresh(self): """Verify refresh history response has request variables.""" - data = {"foo": "bar"} - self.client.get("/json_view/", data, content_type="application/json") - data = { - "store_id": "foo", - "hash": "3280d66a3cca10098a44907c5a1fd255265eed31", - } - response = self.client.get(reverse("djdt:history_refresh"), data=data) + self.client.get("/json_view/", {"foo": "bar"}, content_type="application/json") + self.client.get( + "/json_view/", {"spam": "eggs"}, content_type="application/json" + ) + + response = self.client.get( + reverse("djdt:history_refresh"), data={"store_id": "foo"} + ) self.assertEqual(response.status_code, 200) data = response.json() - self.assertEqual(len(data["requests"]), 1) + self.assertEqual(len(data["requests"]), 2) + + store_ids = list(DebugToolbar._store) + self.assertIn(html.escape(store_ids[0]), data["requests"][0]["content"]) + self.assertIn(html.escape(store_ids[1]), data["requests"][1]["content"]) + for val in ["foo", "bar"]: self.assertIn(val, data["requests"][0]["content"]) + + for val in ["spam", "eggs"]: + self.assertIn(val, data["requests"][1]["content"]) diff --git a/tests/panels/test_logging.py b/tests/panels/test_logging.py deleted file mode 100644 index 87f152ae3..000000000 --- a/tests/panels/test_logging.py +++ /dev/null @@ -1,88 +0,0 @@ -import logging - -from debug_toolbar.panels.logging import ( - MESSAGE_IF_STRING_REPRESENTATION_INVALID, - collector, -) - -from ..base import BaseTestCase -from ..views import regular_view - - -class LoggingPanelTestCase(BaseTestCase): - panel_id = "LoggingPanel" - - def setUp(self): - super().setUp() - self.logger = logging.getLogger(__name__) - collector.clear_collection() - - # Assume the root logger has been configured with level=DEBUG. - # Previously DDT forcefully set this itself to 0 (NOTSET). - logging.root.setLevel(logging.DEBUG) - - def test_happy_case(self): - def view(request): - self.logger.info("Nothing to see here, move along!") - return regular_view(request, "logging") - - self._get_response = view - response = self.panel.process_request(self.request) - self.panel.generate_stats(self.request, response) - records = self.panel.get_stats()["records"] - - self.assertEqual(1, len(records)) - self.assertEqual("Nothing to see here, move along!", records[0]["message"]) - - def test_formatting(self): - def view(request): - self.logger.info("There are %d %s", 5, "apples") - return regular_view(request, "logging") - - self._get_response = view - response = self.panel.process_request(self.request) - self.panel.generate_stats(self.request, response) - records = self.panel.get_stats()["records"] - - self.assertEqual(1, len(records)) - self.assertEqual("There are 5 apples", records[0]["message"]) - - def test_insert_content(self): - """ - Test that the panel only inserts content after generate_stats and - not the process_request. - """ - - def view(request): - self.logger.info("café") - return regular_view(request, "logging") - - self._get_response = view - response = self.panel.process_request(self.request) - # ensure the panel does not have content yet. - self.assertNotIn("café", self.panel.content) - self.panel.generate_stats(self.request, response) - # ensure the panel renders correctly. - content = self.panel.content - self.assertIn("café", content) - self.assertValidHTML(content) - - def test_failing_formatting(self): - class BadClass: - def __str__(self): - raise Exception("Please not stringify me!") - - def view(request): - # should not raise exception, but fail silently - self.logger.debug("This class is misbehaving: %s", BadClass()) - return regular_view(request, "logging") - - self._get_response = view - response = self.panel.process_request(self.request) - self.panel.generate_stats(self.request, response) - records = self.panel.get_stats()["records"] - - self.assertEqual(1, len(records)) - self.assertEqual( - MESSAGE_IF_STRING_REPRESENTATION_INVALID, records[0]["message"] - ) diff --git a/tests/panels/test_profiling.py b/tests/panels/test_profiling.py index ca5c2463b..88ec57dd6 100644 --- a/tests/panels/test_profiling.py +++ b/tests/panels/test_profiling.py @@ -1,3 +1,6 @@ +import sys +import unittest + from django.contrib.auth.models import User from django.db import IntegrityError, transaction from django.http import HttpResponse @@ -33,8 +36,27 @@ def test_insert_content(self): # ensure the panel renders correctly. content = self.panel.content self.assertIn("regular_view", content) + self.assertIn("render", content) + self.assertValidHTML(content) + + @override_settings(DEBUG_TOOLBAR_CONFIG={"PROFILER_THRESHOLD_RATIO": 1}) + def test_cum_time_threshold(self): + """ + Test that cumulative time threshold excludes calls + """ + self._get_response = lambda request: regular_view(request, "profiling") + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) + # ensure the panel renders but doesn't include our function. + content = self.panel.content + self.assertIn("regular_view", content) + self.assertNotIn("render", content) self.assertValidHTML(content) + @unittest.skipUnless( + sys.version_info < (3, 12, 0), + "Python 3.12 no longer contains a frame for list comprehensions.", + ) def test_listcomp_escaped(self): self._get_response = lambda request: listcomp_view(request) response = self.panel.process_request(self.request) @@ -73,7 +95,6 @@ def test_view_executed_once(self): self.assertContains(response, "Profiling") self.assertEqual(User.objects.count(), 1) - with self.assertRaises(IntegrityError): - with transaction.atomic(): - response = self.client.get("/new_user/") + with self.assertRaises(IntegrityError), transaction.atomic(): + response = self.client.get("/new_user/") self.assertEqual(User.objects.count(), 1) diff --git a/tests/panels/test_redirects.py b/tests/panels/test_redirects.py index 6b67e6f1d..7d6d5ac06 100644 --- a/tests/panels/test_redirects.py +++ b/tests/panels/test_redirects.py @@ -2,6 +2,7 @@ from django.conf import settings from django.http import HttpResponse +from django.test import AsyncRequestFactory from ..base import BaseTestCase @@ -70,3 +71,30 @@ def test_insert_content(self): self.assertIsNotNone(response) response = self.panel.generate_stats(self.request, redirect) self.assertIsNone(response) + + async def test_async_compatibility(self): + redirect = HttpResponse(status=302) + + async def get_response(request): + return redirect + + await_response = await get_response(self.request) + self._get_response = get_response + + self.request = AsyncRequestFactory().get("/") + response = await self.panel.process_request(self.request) + self.assertIsInstance(response, HttpResponse) + self.assertTrue(response is await_response) + + def test_original_response_preserved(self): + redirect = HttpResponse(status=302) + redirect["Location"] = "http://somewhere/else/" + self._get_response = lambda request: redirect + response = self.panel.process_request(self.request) + self.assertFalse(response is redirect) + self.assertTrue(hasattr(response, "original_response")) + self.assertTrue(response.original_response is redirect) + self.assertIsNone(response.get("Location")) + self.assertEqual( + response.original_response.get("Location"), "http://somewhere/else/" + ) diff --git a/tests/panels/test_request.py b/tests/panels/test_request.py index 9e7f487fb..2eb7ba610 100644 --- a/tests/panels/test_request.py +++ b/tests/panels/test_request.py @@ -1,5 +1,10 @@ +from django.http import QueryDict +from django.test import RequestFactory + from ..base import BaseTestCase +rf = RequestFactory() + class RequestPanelTestCase(BaseTestCase): panel_id = "RequestPanel" @@ -11,9 +16,9 @@ def test_non_ascii_session(self): self.assertIn("où", self.panel.content) def test_object_with_non_ascii_repr_in_request_params(self): - self.request.path = "/non_ascii_request/" - response = self.panel.process_request(self.request) - self.panel.generate_stats(self.request, response) + request = rf.get("/non_ascii_request/") + response = self.panel.process_request(request) + self.panel.generate_stats(request, response) self.assertIn("nôt åscíì", self.panel.content) def test_insert_content(self): @@ -21,12 +26,186 @@ def test_insert_content(self): Test that the panel only inserts content after generate_stats and not the process_request. """ - self.request.path = "/non_ascii_request/" - response = self.panel.process_request(self.request) + request = rf.get("/non_ascii_request/") + response = self.panel.process_request(request) # ensure the panel does not have content yet. self.assertNotIn("nôt åscíì", self.panel.content) - self.panel.generate_stats(self.request, response) + self.panel.generate_stats(request, response) # ensure the panel renders correctly. content = self.panel.content self.assertIn("nôt åscíì", content) self.assertValidHTML(content) + + def test_query_dict_for_request_in_method_get(self): + """ + Test verifies the correctness of the statistics generation method + in the case when the GET request is class QueryDict + """ + self.request.GET = QueryDict("foo=bar") + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) + # ensure the panel GET request data is processed correctly. + content = self.panel.content + self.assertIn("foo", content) + self.assertIn("bar", content) + + def test_dict_for_request_in_method_get(self): + """ + Test verifies the correctness of the statistics generation method + in the case when the GET request is class Dict + """ + self.request.GET = {"foo": "bar"} + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) + # ensure the panel GET request data is processed correctly. + content = self.panel.content + self.assertIn("foo", content) + self.assertIn("bar", content) + + def test_query_dict_for_request_in_method_post(self): + """ + Test verifies the correctness of the statistics generation method + in the case when the POST request is class QueryDict + """ + self.request.POST = QueryDict("foo=bar") + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) + # ensure the panel POST request data is processed correctly. + content = self.panel.content + self.assertIn("foo", content) + self.assertIn("bar", content) + + def test_dict_for_request_in_method_post(self): + """ + Test verifies the correctness of the statistics generation method + in the case when the POST request is class Dict + """ + self.request.POST = {"foo": "bar"} + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) + # ensure the panel POST request data is processed correctly. + content = self.panel.content + self.assertIn("foo", content) + self.assertIn("bar", content) + + def test_list_for_request_in_method_post(self): + """ + Verify that the toolbar doesn't crash if request.POST contains unexpected data. + + See https://github.com/django-commons/django-debug-toolbar/issues/1621 + """ + self.request.POST = [{"a": 1}, {"b": 2}] + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) + # ensure the panel POST request data is processed correctly. + content = self.panel.content + self.assertIn("[{'a': 1}, {'b': 2}]", content) + + def test_namespaced_url(http://github.com/self): + request = rf.get("/admin/login/") + response = self.panel.process_request(request) + self.panel.generate_stats(request, response) + panel_stats = self.panel.get_stats() + self.assertEqual(panel_stats["view_urlname"], "admin:login") + + def test_session_list_sorted_or_not(self): + """ + Verify the session is sorted when all keys are strings. + + See https://github.com/django-commons/django-debug-toolbar/issues/1668 + """ + self.request.session = { + 1: "value", + "data": ["foo", "bar", 1], + (2, 3): "tuple_key", + } + data = { + "list": [(1, "value"), ("data", ["foo", "bar", 1]), ((2, 3), "tuple_key")] + } + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) + panel_stats = self.panel.get_stats() + self.assertEqual(panel_stats["session"], data) + + self.request.session = { + "b": "b-value", + "a": "a-value", + } + data = {"list": [("a", "a-value"), ("b", "b-value")]} + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) + panel_stats = self.panel.get_stats() + self.assertEqual(panel_stats["session"], data) + + def test_sensitive_post_data_sanitized(self): + """Test that sensitive POST data is redacted.""" + self.request.POST = {"username": "testuser", "password": "secret123"} + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) + + # Check that password is redacted in panel content + content = self.panel.content + self.assertIn("username", content) + self.assertIn("testuser", content) + self.assertIn("password", content) + self.assertNotIn("secret123", content) + self.assertIn("********************", content) + + def test_sensitive_get_data_sanitized(self): + """Test that sensitive GET data is redacted.""" + self.request.GET = {"api_key": "abc123", "q": "search term"} + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) + + # Check that api_key is redacted in panel content + content = self.panel.content + self.assertIn("api_key", content) + self.assertNotIn("abc123", content) + self.assertIn("********************", content) + self.assertIn("q", content) + self.assertIn("search term", content) + + def test_sensitive_cookie_data_sanitized(self): + """Test that sensitive cookie data is redacted.""" + self.request.COOKIES = {"session_id": "abc123", "auth_token": "xyz789"} + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) + + # Check that auth_token is redacted in panel content + content = self.panel.content + self.assertIn("session_id", content) + self.assertIn("abc123", content) + self.assertIn("auth_token", content) + self.assertNotIn("xyz789", content) + self.assertIn("********************", content) + + def test_sensitive_session_data_sanitized(self): + """Test that sensitive session data is redacted.""" + self.request.session = {"user_id": 123, "auth_token": "xyz789"} + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) + + # Check that auth_token is redacted in panel content + content = self.panel.content + self.assertIn("user_id", content) + self.assertIn("123", content) + self.assertIn("auth_token", content) + self.assertNotIn("xyz789", content) + self.assertIn("********************", content) + + def test_querydict_sanitized(self): + """Test that sensitive data in QueryDict objects is properly redacted.""" + query_dict = QueryDict("username=testuser&password=secret123&token=abc456") + self.request.GET = query_dict + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) + + # Check that sensitive data is redacted in panel content + content = self.panel.content + self.assertIn("username", content) + self.assertIn("testuser", content) + self.assertIn("password", content) + self.assertNotIn("secret123", content) + self.assertIn("token", content) + self.assertNotIn("abc456", content) + self.assertIn("********************", content) diff --git a/tests/panels/test_settings.py b/tests/panels/test_settings.py index 5bf29d322..89b016dc0 100644 --- a/tests/panels/test_settings.py +++ b/tests/panels/test_settings.py @@ -24,8 +24,8 @@ def test_panel_title(self): """
    -

    Settings from None

    +
    diff --git a/tests/panels/test_sql.py b/tests/panels/test_sql.py index 479bbc31c..a411abb5d 100644 --- a/tests/panels/test_sql.py +++ b/tests/panels/test_sql.py @@ -1,25 +1,49 @@ +import asyncio import datetime +import os import unittest +from unittest.mock import call, patch import django +from asgiref.sync import sync_to_async from django.contrib.auth.models import User -from django.db import connection +from django.db import connection, transaction +from django.db.backends.utils import CursorDebugWrapper, CursorWrapper from django.db.models import Count from django.db.utils import DatabaseError from django.shortcuts import render from django.test.utils import override_settings -from ..base import BaseTestCase +import debug_toolbar.panels.sql.tracking as sql_tracking try: - from psycopg2._json import Json as PostgresJson + import psycopg except ImportError: - PostgresJson = None + psycopg = None -if connection.vendor == "postgresql": - from ..models import PostgresJSON as PostgresJSONModel -else: - PostgresJSONModel = None +from ..base import BaseMultiDBTestCase, BaseTestCase +from ..models import Binary, PostgresJSON + + +def sql_call(*, use_iterator=False): + qs = User.objects.all() + if use_iterator: + qs = qs.iterator() + return list(qs) + + +async def async_sql_call(*, use_iterator=False): + qs = User.objects.all() + if use_iterator: + qs = qs.iterator() + return await sync_to_async(list)(qs) + + +async def concurrent_async_sql_call(*, use_iterator=False): + qs = User.objects.all() + if use_iterator: + qs = qs.iterator() + return await asyncio.gather(sync_to_async(list)(qs), User.objects.acount()) class SQLPanelTestCase(BaseTestCase): @@ -34,18 +58,50 @@ def test_disabled(self): def test_recording(self): self.assertEqual(len(self.panel._queries), 0) - list(User.objects.all()) + sql_call() + + # ensure query was logged + self.assertEqual(len(self.panel._queries), 1) + query = self.panel._queries[0] + self.assertEqual(query["alias"], "default") + self.assertTrue("sql" in query) + self.assertTrue("duration" in query) + self.assertTrue("stacktrace" in query) + + # ensure the stacktrace is populated + self.assertTrue(len(query["stacktrace"]) > 0) + + async def test_recording_async(self): + self.assertEqual(len(self.panel._queries), 0) + + await async_sql_call() # ensure query was logged self.assertEqual(len(self.panel._queries), 1) query = self.panel._queries[0] - self.assertEqual(query[0], "default") - self.assertTrue("sql" in query[1]) - self.assertTrue("duration" in query[1]) - self.assertTrue("stacktrace" in query[1]) + self.assertEqual(query["alias"], "default") + self.assertTrue("sql" in query) + self.assertTrue("duration" in query) + self.assertTrue("stacktrace" in query) # ensure the stacktrace is populated - self.assertTrue(len(query[1]["stacktrace"]) > 0) + self.assertTrue(len(query["stacktrace"]) > 0) + + async def test_recording_concurrent_async(self): + self.assertEqual(len(self.panel._queries), 0) + + await concurrent_async_sql_call() + + # ensure query was logged + self.assertEqual(len(self.panel._queries), 2) + query = self.panel._queries[0] + self.assertEqual(query["alias"], "default") + self.assertTrue("sql" in query) + self.assertTrue("duration" in query) + self.assertTrue("stacktrace" in query) + + # ensure the stacktrace is populated + self.assertTrue(len(query["stacktrace"]) > 0) @unittest.skipUnless( connection.vendor == "postgresql", "Test valid only on PostgreSQL" @@ -53,15 +109,100 @@ def test_recording(self): def test_recording_chunked_cursor(self): self.assertEqual(len(self.panel._queries), 0) - list(User.objects.all().iterator()) + sql_call(use_iterator=True) # ensure query was logged self.assertEqual(len(self.panel._queries), 1) + @patch( + "debug_toolbar.panels.sql.tracking.patch_cursor_wrapper_with_mixin", + wraps=sql_tracking.patch_cursor_wrapper_with_mixin, + ) + def test_cursor_wrapper_singleton(self, mock_patch_cursor_wrapper): + sql_call() + # ensure that cursor wrapping is applied only once + self.assertIn( + mock_patch_cursor_wrapper.mock_calls, + [ + [call(CursorWrapper, sql_tracking.NormalCursorMixin)], + # CursorDebugWrapper is used if the test is called with `--debug-sql` + [call(CursorDebugWrapper, sql_tracking.NormalCursorMixin)], + ], + ) + + @patch( + "debug_toolbar.panels.sql.tracking.patch_cursor_wrapper_with_mixin", + wraps=sql_tracking.patch_cursor_wrapper_with_mixin, + ) + def test_chunked_cursor_wrapper_singleton(self, mock_patch_cursor_wrapper): + sql_call(use_iterator=True) + + # ensure that cursor wrapping is applied only once + self.assertIn( + mock_patch_cursor_wrapper.mock_calls, + [ + [call(CursorWrapper, sql_tracking.NormalCursorMixin)], + # CursorDebugWrapper is used if the test is called with `--debug-sql` + [call(CursorDebugWrapper, sql_tracking.NormalCursorMixin)], + ], + ) + + @patch( + "debug_toolbar.panels.sql.tracking.patch_cursor_wrapper_with_mixin", + wraps=sql_tracking.patch_cursor_wrapper_with_mixin, + ) + async def test_cursor_wrapper_async(self, mock_patch_cursor_wrapper): + await sync_to_async(sql_call)() + + self.assertIn( + mock_patch_cursor_wrapper.mock_calls, + [ + [call(CursorWrapper, sql_tracking.NormalCursorMixin)], + # CursorDebugWrapper is used if the test is called with `--debug-sql` + [call(CursorDebugWrapper, sql_tracking.NormalCursorMixin)], + ], + ) + + @patch( + "debug_toolbar.panels.sql.tracking.patch_cursor_wrapper_with_mixin", + wraps=sql_tracking.patch_cursor_wrapper_with_mixin, + ) + async def test_cursor_wrapper_asyncio_ctx(self, mock_patch_cursor_wrapper): + self.assertTrue(sql_tracking.allow_sql.get()) + await sync_to_async(sql_call)() + + async def task(): + sql_tracking.allow_sql.set(False) + # By disabling sql_tracking.allow_sql, we are indicating that any + # future SQL queries should be stopped. If SQL query occurs, + # it raises an exception. + with self.assertRaises(sql_tracking.SQLQueryTriggered): + await sync_to_async(sql_call)() + + # Ensure this is called in another context + await asyncio.create_task(task()) + # Because it was called in another context, it should not have affected ours + self.assertTrue(sql_tracking.allow_sql.get()) + + self.assertIn( + mock_patch_cursor_wrapper.mock_calls, + [ + [ + call(CursorWrapper, sql_tracking.NormalCursorMixin), + call(CursorWrapper, sql_tracking.ExceptionCursorMixin), + ], + # CursorDebugWrapper is used if the test is called with `--debug-sql` + [ + call(CursorDebugWrapper, sql_tracking.NormalCursorMixin), + call(CursorDebugWrapper, sql_tracking.ExceptionCursorMixin), + ], + ], + ) + def test_generate_server_timing(self): self.assertEqual(len(self.panel._queries), 0) - list(User.objects.all()) + sql_call() response = self.panel.process_request(self.request) self.panel.generate_stats(self.request, response) @@ -72,7 +213,7 @@ def test_generate_server_timing(self): query = self.panel._queries[0] expected_data = { - "sql_time": {"title": "SQL 1 queries", "value": query[1]["duration"]} + "sql_time": {"title": "SQL 1 queries", "value": query["duration"]} } self.assertEqual(self.panel.get_server_timing_stats(), expected_data) @@ -89,7 +230,7 @@ def test_non_ascii_query(self): self.assertEqual(len(self.panel._queries), 2) # non-ASCII bytes parameters - list(User.objects.filter(username="café".encode())) + list(Binary.objects.filter(field__in=["café".encode()])) self.assertEqual(len(self.panel._queries), 3) response = self.panel.process_request(self.request) @@ -98,6 +239,17 @@ def test_non_ascii_query(self): # ensure the panel renders correctly self.assertIn("café", self.panel.content) + @unittest.skipUnless( + connection.vendor == "postgresql", "Test valid only on PostgreSQL" + ) + def test_bytes_query(self): + self.assertEqual(len(self.panel._queries), 0) + + with connection.cursor() as cursor: + cursor.execute(b"SELECT 1") + + self.assertEqual(len(self.panel._queries), 1) + def test_param_conversion(self): self.assertEqual(len(self.panel._queries), 0) @@ -111,7 +263,13 @@ def test_param_conversion(self): .filter(group_count__lt=10) .filter(group_count__gt=1) ) - list(User.objects.filter(date_joined=datetime.datetime(2017, 12, 22, 16, 7, 1))) + list( + User.objects.filter( + date_joined=datetime.datetime( + 2017, 12, 22, 16, 7, 1, tzinfo=datetime.timezone.utc + ) + ) + ) response = self.panel.process_request(self.request) self.panel.generate_stats(self.request, response) @@ -119,16 +277,27 @@ def test_param_conversion(self): # ensure query was logged self.assertEqual(len(self.panel._queries), 3) - if django.VERSION >= (3, 1): - self.assertEqual( - tuple([q[1]["params"] for q in self.panel._queries]), - ('["Foo"]', "[10, 1]", '["2017-12-22 16:07:01"]'), - ) + if connection.vendor == "mysql" and django.VERSION >= (4, 1): + # Django 4.1 started passing true/false back for boolean + # comparisons in MySQL. + expected_bools = '["Foo", true, false]' else: - self.assertEqual( - tuple([q[1]["params"] for q in self.panel._queries]), - ('["Foo", true, false]', "[10, 1]", '["2017-12-22 16:07:01"]'), - ) + expected_bools = '["Foo"]' + + if connection.vendor == "postgresql": + # PostgreSQL always includes timezone + expected_datetime = '["2017-12-22 16:07:01+00:00"]' + else: + expected_datetime = '["2017-12-22 16:07:01"]' + + self.assertEqual( + tuple(query["params"] for query in self.panel._queries), + ( + expected_bools, + "[10, 1]", + expected_datetime, + ), + ) @unittest.skipUnless( connection.vendor == "postgresql", "Test valid only on PostgreSQL" @@ -136,7 +305,7 @@ def test_param_conversion(self): def test_json_param_conversion(self): self.assertEqual(len(self.panel._queries), 0) - list(PostgresJSONModel.objects.filter(field__contains={"foo": "bar"})) + list(PostgresJSON.objects.filter(field__contains={"foo": "bar"})) response = self.panel.process_request(self.request) self.panel.generate_stats(self.request, response) @@ -144,14 +313,33 @@ def test_json_param_conversion(self): # ensure query was logged self.assertEqual(len(self.panel._queries), 1) self.assertEqual( - self.panel._queries[0][1]["params"], + self.panel._queries[0]["params"], '["{\\"foo\\": \\"bar\\"}"]', ) - if django.VERSION < (3, 1): - self.assertIsInstance( - self.panel._queries[0][1]["raw_params"][0], - PostgresJson, + + @unittest.skipUnless( + connection.vendor == "postgresql" and psycopg is None, + "Test valid only on PostgreSQL with psycopg2", + ) + def test_tuple_param_conversion(self): + """ + Regression test for tuple parameter conversion. + """ + self.assertEqual(len(self.panel._queries), 0) + + list( + PostgresJSON.objects.raw( + "SELECT * FROM tests_postgresjson WHERE field ->> 'key' IN %s", + [("a", "b'")], ) + ) + + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) + + # ensure query was logged + self.assertEqual(len(self.panel._queries), 1) + self.assertEqual(self.panel._queries[0]["params"], '[["a", "b\'"]]') def test_binary_param_force_text(self): self.assertEqual(len(self.panel._queries), 0) @@ -169,7 +357,7 @@ def test_binary_param_force_text(self): self.assertIn( "SELECT * FROM" " tests_binary WHERE field =", - self.panel._queries[0][1]["sql"], + self.panel._queries[0]["sql"], ) @unittest.skipUnless(connection.vendor != "sqlite", "Test invalid for SQLite") @@ -220,7 +408,7 @@ def test_raw_query_param_conversion(self): self.assertEqual(len(self.panel._queries), 2) self.assertEqual( - tuple([q[1]["params"] for q in self.panel._queries]), + tuple(query["params"] for query in self.panel._queries), ( '["Foo", true, false, "2017-12-22 16:07:01"]', " ".join( @@ -239,7 +427,7 @@ def test_insert_content(self): Test that the panel only inserts content after generate_stats and not the process_request. """ - list(User.objects.filter(username="café".encode("utf-8"))) + list(User.objects.filter(username="café")) response = self.panel.process_request(self.request) # ensure the panel does not have content yet. self.assertNotIn("café", self.panel.content) @@ -254,8 +442,8 @@ def test_insert_locals(self): """ Test that the panel inserts locals() content. """ - local_var = "" # noqa - list(User.objects.filter(username="café".encode("utf-8"))) + local_var = "" # noqa: F841 + list(User.objects.filter(username="café")) response = self.panel.process_request(self.request) self.panel.generate_stats(self.request, response) self.assertIn("local_var", self.panel.content) @@ -269,7 +457,7 @@ def test_not_insert_locals(self): """ Test that the panel does not insert locals() content. """ - list(User.objects.filter(username="café".encode("utf-8"))) + list(User.objects.filter(username="café")) response = self.panel.process_request(self.request) self.panel.generate_stats(self.request, response) self.assertNotIn("djdt-locals", self.panel.content) @@ -289,12 +477,15 @@ def test_erroneous_query(self): @unittest.skipUnless( connection.vendor == "postgresql", "Test valid only on PostgreSQL" ) - def test_execute_with_psycopg2_composed_sql(self): + def test_execute_with_psycopg_composed_sql(self): """ - Test command executed using a Composed psycopg2 object is logged. - Ref: http://initd.org/psycopg/docs/sql.html + Test command executed using a Composed psycopg object is logged. + Ref: https://www.psycopg.org/psycopg3/docs/api/sql.html """ - from psycopg2 import sql + try: + from psycopg import sql + except ImportError: + from psycopg2 import sql self.assertEqual(len(self.panel._queries), 0) @@ -307,26 +498,26 @@ def test_execute_with_psycopg2_composed_sql(self): self.assertEqual(len(self.panel._queries), 1) query = self.panel._queries[0] - self.assertEqual(query[0], "default") - self.assertTrue("sql" in query[1]) - self.assertEqual(query[1]["sql"], 'select "username" from "auth_user"') + self.assertEqual(query["alias"], "default") + self.assertTrue("sql" in query) + self.assertEqual(query["sql"], 'select "username" from "auth_user"') def test_disable_stacktraces(self): self.assertEqual(len(self.panel._queries), 0) with self.settings(DEBUG_TOOLBAR_CONFIG={"ENABLE_STACKTRACES": False}): - list(User.objects.all()) + sql_call() # ensure query was logged self.assertEqual(len(self.panel._queries), 1) query = self.panel._queries[0] - self.assertEqual(query[0], "default") - self.assertTrue("sql" in query[1]) - self.assertTrue("duration" in query[1]) - self.assertTrue("stacktrace" in query[1]) + self.assertEqual(query["alias"], "default") + self.assertTrue("sql" in query) + self.assertTrue("duration" in query) + self.assertTrue("stacktrace" in query) # ensure the stacktrace is empty - self.assertEqual([], query[1]["stacktrace"]) + self.assertEqual([], query["stacktrace"]) @override_settings( DEBUG=True, @@ -350,10 +541,300 @@ def test_regression_infinite_recursion(self): # template is loaded and basic.html extends base.html. self.assertEqual(len(self.panel._queries), 2) query = self.panel._queries[0] - self.assertEqual(query[0], "default") - self.assertTrue("sql" in query[1]) - self.assertTrue("duration" in query[1]) - self.assertTrue("stacktrace" in query[1]) + self.assertEqual(query["alias"], "default") + self.assertTrue("sql" in query) + self.assertTrue("duration" in query) + self.assertTrue("stacktrace" in query) # ensure the stacktrace is populated - self.assertTrue(len(query[1]["stacktrace"]) > 0) + self.assertTrue(len(query["stacktrace"]) > 0) + + def test_prettify_sql(self): + """ + Test case to validate that the PRETTIFY_SQL setting changes the output + of the sql when it's toggled. It does not validate what it does + though. + """ + with override_settings(DEBUG_TOOLBAR_CONFIG={"PRETTIFY_SQL": True}): + list(User.objects.filter(username__istartswith="spam")) + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) + pretty_sql = self.panel._queries[-1]["sql"] + self.assertEqual(len(self.panel._queries), 1) + + # Reset the queries + self.panel._queries = [] + # Run it again, but with prettify off. Verify that it's different. + with override_settings(DEBUG_TOOLBAR_CONFIG={"PRETTIFY_SQL": False}): + list(User.objects.filter(username__istartswith="spam")) + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) + self.assertEqual(len(self.panel._queries), 1) + self.assertNotEqual(pretty_sql, self.panel._queries[-1]["sql"]) + + self.panel._queries = [] + # Run it again, but with prettify back on. + # This is so we don't have to check what PRETTIFY_SQL does exactly, + # but we know it's doing something. + with override_settings(DEBUG_TOOLBAR_CONFIG={"PRETTIFY_SQL": True}): + list(User.objects.filter(username__istartswith="spam")) + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) + self.assertEqual(len(self.panel._queries), 1) + self.assertEqual(pretty_sql, self.panel._queries[-1]["sql"]) + + def test_simplification(self): + """ + Test case to validate that select lists for .count() and .exist() queries do not + get elided, but other select lists do. + """ + User.objects.count() + User.objects.exists() + list(User.objects.values_list("id")) + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) + self.assertEqual(len(self.panel._queries), 3) + self.assertNotIn("\u2022", self.panel._queries[0]["sql"]) + self.assertNotIn("\u2022", self.panel._queries[1]["sql"]) + self.assertIn("\u2022", self.panel._queries[2]["sql"]) + + def test_top_level_simplification(self): + """ + Test case to validate that top-level select lists get elided, but other select + lists for subselects do not. + """ + list(User.objects.filter(id__in=User.objects.filter(is_staff=True))) + list(User.objects.filter(id__lt=20).union(User.objects.filter(id__gt=10))) + if connection.vendor != "mysql": + list( + User.objects.filter(id__lt=20).intersection( + User.objects.filter(id__gt=10) + ) + ) + list( + User.objects.filter(id__lt=20).difference( + User.objects.filter(id__gt=10) + ) + ) + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) + if connection.vendor != "mysql": + self.assertEqual(len(self.panel._queries), 4) + else: + self.assertEqual(len(self.panel._queries), 2) + # WHERE ... IN SELECT ... queries should have only one elided select list + self.assertEqual(self.panel._queries[0]["sql"].count("SELECT"), 4) + self.assertEqual(self.panel._queries[0]["sql"].count("\u2022"), 3) + # UNION queries should have two elidid select lists + self.assertEqual(self.panel._queries[1]["sql"].count("SELECT"), 4) + self.assertEqual(self.panel._queries[1]["sql"].count("\u2022"), 6) + if connection.vendor != "mysql": + # INTERSECT queries should have two elidid select lists + self.assertEqual(self.panel._queries[2]["sql"].count("SELECT"), 4) + self.assertEqual(self.panel._queries[2]["sql"].count("\u2022"), 6) + # EXCEPT queries should have two elidid select lists + self.assertEqual(self.panel._queries[3]["sql"].count("SELECT"), 4) + self.assertEqual(self.panel._queries[3]["sql"].count("\u2022"), 6) + + @override_settings( + DEBUG=True, + ) + def test_flat_template_information(self): + """ + Test case for when the query is used in a flat template hierarchy + (without included templates). + """ + self.assertEqual(len(self.panel._queries), 0) + + users = User.objects.all() + render(self.request, "sql/flat.html", {"users": users}) + + self.assertEqual(len(self.panel._queries), 1) + + query = self.panel._queries[0] + template_info = query["template_info"] + template_name = os.path.basename(template_info["name"]) + self.assertEqual(template_name, "flat.html") + self.assertEqual(template_info["context"][3]["content"].strip(), "{{ users }}") + self.assertEqual(template_info["context"][3]["highlight"], True) + + @override_settings( + DEBUG=True, + ) + def test_nested_template_information(self): + """ + Test case for when the query is used in a nested template + hierarchy (with included templates). + """ + self.assertEqual(len(self.panel._queries), 0) + + users = User.objects.all() + render(self.request, "sql/nested.html", {"users": users}) + + self.assertEqual(len(self.panel._queries), 1) + + query = self.panel._queries[0] + template_info = query["template_info"] + template_name = os.path.basename(template_info["name"]) + self.assertEqual(template_name, "included.html") + self.assertEqual(template_info["context"][0]["content"].strip(), "{{ users }}") + self.assertEqual(template_info["context"][0]["highlight"], True) + + def test_similar_and_duplicate_grouping(self): + self.assertEqual(len(self.panel._queries), 0) + + User.objects.filter(id=1).count() + User.objects.filter(id=1).count() + User.objects.filter(id=2).count() + User.objects.filter(id__lt=10).count() + User.objects.filter(id__lt=20).count() + User.objects.filter(id__gt=10, id__lt=20).count() + + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) + + self.assertEqual(len(self.panel._queries), 6) + + queries = self.panel._queries + query = queries[0] + self.assertEqual(query["similar_count"], 3) + self.assertEqual(query["duplicate_count"], 2) + + query = queries[1] + self.assertEqual(query["similar_count"], 3) + self.assertEqual(query["duplicate_count"], 2) + + query = queries[2] + self.assertEqual(query["similar_count"], 3) + self.assertTrue("duplicate_count" not in query) + + query = queries[3] + self.assertEqual(query["similar_count"], 2) + self.assertTrue("duplicate_count" not in query) + + query = queries[4] + self.assertEqual(query["similar_count"], 2) + self.assertTrue("duplicate_count" not in query) + + query = queries[5] + self.assertTrue("similar_count" not in query) + self.assertTrue("duplicate_count" not in query) + + self.assertEqual(queries[0]["similar_color"], queries[1]["similar_color"]) + self.assertEqual(queries[0]["similar_color"], queries[2]["similar_color"]) + self.assertEqual(queries[0]["duplicate_color"], queries[1]["duplicate_color"]) + self.assertNotEqual(queries[0]["similar_color"], queries[0]["duplicate_color"]) + + self.assertEqual(queries[3]["similar_color"], queries[4]["similar_color"]) + self.assertNotEqual(queries[0]["similar_color"], queries[3]["similar_color"]) + self.assertNotEqual(queries[0]["duplicate_color"], queries[3]["similar_color"]) + + def test_explain_with_union(self): + list(User.objects.filter(id__lt=20).union(User.objects.filter(id__gt=10))) + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) + query = self.panel._queries[0] + self.assertTrue(query["is_select"]) + + +class SQLPanelMultiDBTestCase(BaseMultiDBTestCase): + panel_id = "SQLPanel" + + def test_aliases(self): + self.assertFalse(self.panel._queries) + + list(User.objects.all()) + list(User.objects.using("replica").all()) + + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) + + self.assertTrue(self.panel._queries) + + query = self.panel._queries[0] + self.assertEqual(query["alias"], "default") + + query = self.panel._queries[-1] + self.assertEqual(query["alias"], "replica") + + def test_transaction_status(self): + """ + Test case for tracking the transaction status is properly associated with + queries on PostgreSQL, and that transactions aren't broken on other database + engines. + """ + self.assertEqual(len(self.panel._queries), 0) + + with transaction.atomic(): + list(User.objects.all()) + list(User.objects.using("replica").all()) + + with transaction.atomic(using="replica"): + list(User.objects.all()) + list(User.objects.using("replica").all()) + + with transaction.atomic(): + list(User.objects.all()) + + list(User.objects.using("replica").all()) + + response = self.panel.process_request(self.request) + self.panel.generate_stats(self.request, response) + + if connection.vendor == "postgresql": + # Connection tracking is currently only implemented for PostgreSQL. + self.assertEqual(len(self.panel._queries), 6) + + query = self.panel._queries[0] + self.assertEqual(query["alias"], "default") + self.assertIsNotNone(query["trans_id"]) + self.assertTrue(query["starts_trans"]) + self.assertTrue(query["in_trans"]) + self.assertFalse("end_trans" in query) + + query = self.panel._queries[-1] + self.assertEqual(query["alias"], "replica") + self.assertIsNone(query["trans_id"]) + self.assertFalse("starts_trans" in query) + self.assertFalse("in_trans" in query) + self.assertFalse("end_trans" in query) + + query = self.panel._queries[2] + self.assertEqual(query["alias"], "default") + self.assertIsNotNone(query["trans_id"]) + self.assertEqual(query["trans_id"], self.panel._queries[0]["trans_id"]) + self.assertFalse("starts_trans" in query) + self.assertTrue(query["in_trans"]) + self.assertTrue(query["ends_trans"]) + + query = self.panel._queries[3] + self.assertEqual(query["alias"], "replica") + self.assertIsNotNone(query["trans_id"]) + self.assertNotEqual(query["trans_id"], self.panel._queries[0]["trans_id"]) + self.assertTrue(query["starts_trans"]) + self.assertTrue(query["in_trans"]) + self.assertTrue(query["ends_trans"]) + + query = self.panel._queries[4] + self.assertEqual(query["alias"], "default") + self.assertIsNotNone(query["trans_id"]) + self.assertNotEqual(query["trans_id"], self.panel._queries[0]["trans_id"]) + self.assertNotEqual(query["trans_id"], self.panel._queries[3]["trans_id"]) + self.assertTrue(query["starts_trans"]) + self.assertTrue(query["in_trans"]) + self.assertTrue(query["ends_trans"]) + + query = self.panel._queries[5] + self.assertEqual(query["alias"], "replica") + self.assertIsNone(query["trans_id"]) + self.assertFalse("starts_trans" in query) + self.assertFalse("in_trans" in query) + self.assertFalse("end_trans" in query) + else: + # Ensure that nothing was recorded for other database engines. + self.assertTrue(self.panel._queries) + for query in self.panel._queries: + self.assertFalse("trans_id" in query) + self.assertFalse("starts_trans" in query) + self.assertFalse("in_trans" in query) + self.assertFalse("end_trans" in query) diff --git a/tests/panels/test_staticfiles.py b/tests/panels/test_staticfiles.py index 7aa85d889..2306c8365 100644 --- a/tests/panels/test_staticfiles.py +++ b/tests/panels/test_staticfiles.py @@ -1,17 +1,14 @@ -import os +from pathlib import Path from django.conf import settings -from django.contrib.staticfiles import finders -from django.core.checks import Warning -from django.test import SimpleTestCase -from django.test.utils import override_settings +from django.contrib.staticfiles import finders, storage +from django.shortcuts import render +from django.test import AsyncRequestFactory, RequestFactory -from debug_toolbar.panels.staticfiles import StaticFilesPanel +from debug_toolbar.panels.staticfiles import URLMixin from ..base import BaseTestCase -PATH_DOES_NOT_EXIST = os.path.join(settings.BASE_DIR, "tests", "invalid_static") - class StaticFilesPanelTestCase(BaseTestCase): panel_id = "StaticFilesPanel" @@ -28,13 +25,25 @@ def test_default_case(self): ) self.assertEqual(self.panel.num_used, 0) self.assertNotEqual(self.panel.num_found, 0) - self.assertEqual( - self.panel.get_staticfiles_apps(), ["django.contrib.admin", "debug_toolbar"] - ) + expected_apps = ["django.contrib.admin", "debug_toolbar"] + if settings.USE_GIS: + expected_apps = ["django.contrib.gis"] + expected_apps + self.assertEqual(self.panel.get_staticfiles_apps(), expected_apps) self.assertEqual( self.panel.get_staticfiles_dirs(), finders.FileSystemFinder().locations ) + async def test_store_staticfiles_with_async_context(self): + async def get_response(request): + # template contains one static file + return render(request, "staticfiles/async_static.html") + + self._get_response = get_response + async_request = AsyncRequestFactory().get("/") + response = await self.panel.process_request(async_request) + self.panel.generate_stats(self.request, response) + self.assertEqual(self.panel.num_used, 1) + def test_insert_content(self): """ Test that the panel only inserts content after generate_stats and @@ -54,47 +63,59 @@ def test_insert_content(self): ) self.assertValidHTML(content) - @override_settings( - STATICFILES_DIRS=[PATH_DOES_NOT_EXIST] + settings.STATICFILES_DIRS, - STATIC_ROOT=PATH_DOES_NOT_EXIST, - ) - def test_finder_directory_does_not_exist(self): - """Misconfigure the static files settings and verify the toolbar runs. + def test_path(self): + def get_response(request): + # template contains one static file + return render( + request, + "staticfiles/path.html", + {"path": Path("additional_static/base.css")}, + ) - The test case is that the STATIC_ROOT is in STATICFILES_DIRS and that - the directory of STATIC_ROOT does not exist. - """ - response = self.panel.process_request(self.request) + self._get_response = get_response + request = RequestFactory().get("/") + response = self.panel.process_request(request) self.panel.generate_stats(self.request, response) - content = self.panel.content - self.assertIn( - "django.contrib.staticfiles.finders.AppDirectoriesFinder", content - ) - self.assertNotIn( - "django.contrib.staticfiles.finders.FileSystemFinder (2 files)", content - ) - self.assertEqual(self.panel.num_used, 0) - self.assertNotEqual(self.panel.num_found, 0) - self.assertEqual( - self.panel.get_staticfiles_apps(), ["django.contrib.admin", "debug_toolbar"] - ) - self.assertEqual( - self.panel.get_staticfiles_dirs(), finders.FileSystemFinder().locations - ) + self.assertEqual(self.panel.num_used, 1) + self.assertIn('"/static/additional_static/base.css"', self.panel.content) + def test_storage_state_preservation(self): + """Ensure the URLMixin doesn't affect storage state""" + original_storage = storage.staticfiles_storage + original_attrs = dict(original_storage.__dict__) -@override_settings(DEBUG=True) -class StaticFilesPanelChecksTestCase(SimpleTestCase): - @override_settings(STATICFILES_DIRS=[PATH_DOES_NOT_EXIST]) - def test_run_checks(self): - messages = StaticFilesPanel.run_checks() - self.assertEqual( - messages, - [ - Warning( - "debug_toolbar requires the STATICFILES_DIRS directories to exist.", - hint="Running manage.py collectstatic may help uncover the issue.", - id="debug_toolbar.staticfiles.W001", - ) - ], - ) + # Trigger mixin injection + self.panel.ready() + + # Verify all original attributes are preserved + self.assertEqual(original_attrs, dict(original_storage.__dict__)) + + def test_context_variable_lifecycle(self): + """Test the request_id context variable lifecycle""" + from debug_toolbar.panels.staticfiles import request_id_context_var + + # Should not raise when context not set + url = storage.staticfiles_storage.url("http://github.com/test.css") + self.assertTrue(url.startswith("/static/")) + + # Should track when context is set + token = request_id_context_var.set("test-request-id") + try: + url = storage.staticfiles_storage.url("http://github.com/test.css") + self.assertTrue(url.startswith("/static/")) + # Verify file was tracked + self.assertIn("test.css", [f.path for f in self.panel.used_paths]) + finally: + request_id_context_var.reset(token) + + def test_multiple_initialization(self): + """Ensure multiple panel initializations don't stack URLMixin""" + storage_class = storage.staticfiles_storage.__class__ + + # Initialize panel multiple times + for _ in range(3): + self.panel.ready() + + # Verify URLMixin appears exactly once in bases + mixin_count = sum(1 for base in storage_class.__bases__ if base == URLMixin) + self.assertEqual(mixin_count, 1) diff --git a/tests/panels/test_template.py b/tests/panels/test_template.py index 9ff39543f..44ac4ff0d 100644 --- a/tests/panels/test_template.py +++ b/tests/panels/test_template.py @@ -1,6 +1,10 @@ +from unittest import expectedFailure + +import django from django.contrib.auth.models import User from django.template import Context, RequestContext, Template from django.test import override_settings +from django.utils.functional import SimpleLazyObject from ..base import BaseTestCase, IntegrationTestCase from ..forms import TemplateReprForm @@ -20,6 +24,7 @@ def tearDown(self): super().tearDown() def test_queryset_hook(self): + response = self.panel.process_request(self.request) t = Template("No context variables here!") c = Context( { @@ -28,12 +33,13 @@ def test_queryset_hook(self): } ) t.render(c) + self.panel.generate_stats(self.request, response) # ensure the query was NOT logged self.assertEqual(len(self.sql_panel._queries), 0) self.assertEqual( - self.panel.templates[0]["context"], + self.panel.templates[0]["context_list"], [ "{'False': False, 'None': None, 'True': True}", "{'deep_queryset': '<>',\n" @@ -47,7 +53,10 @@ def test_template_repr(self): User.objects.create(username="admin") bad_repr = TemplateReprForm() - t = Template("{{ bad_repr }}") + if django.VERSION < (5,): + t = Template("
    {% trans "Timing attribute" %}{% trans "Timeline" %}{% trans "Milliseconds since navigation start (+length)" %}{% translate "Timing attribute" %}{% translate "Timeline" %}{% translate "Milliseconds since navigation start (+length)" %}
    {% trans "Package" %}{% trans "Name" %}{% trans "Version" %}{% translate "Package" %}{% translate "Name" %}{% translate "Version" %}
    {{ bad_repr }}
    ") + else: + t = Template("{{ bad_repr }}") c = Context({"bad_repr": bad_repr}) html = t.render(c) self.assertIsNotNone(html) @@ -95,26 +104,75 @@ def test_disabled(self): self.assertFalse(self.panel.enabled) def test_empty_context(self): + response = self.panel.process_request(self.request) t = Template("") c = Context({}) t.render(c) + self.panel.generate_stats(self.request, response) # Includes the builtin context but not the empty one. self.assertEqual( - self.panel.templates[0]["context"], + self.panel.templates[0]["context_list"], ["{'False': False, 'None': None, 'True': True}"], ) + def test_lazyobject(self): + response = self.panel.process_request(self.request) + t = Template("") + c = Context({"lazy": SimpleLazyObject(lambda: "lazy_value")}) + t.render(c) + self.panel.generate_stats(self.request, response) + self.assertNotIn("lazy_value", self.panel.content) + + def test_lazyobject_eval(self): + response = self.panel.process_request(self.request) + t = Template("{{lazy}}") + c = Context({"lazy": SimpleLazyObject(lambda: "lazy_value")}) + self.assertEqual(t.render(c), "lazy_value") + self.panel.generate_stats(self.request, response) + self.assertIn("lazy_value", self.panel.content) + + @override_settings( + DEBUG=True, + DEBUG_TOOLBAR_PANELS=["debug_toolbar.panels.templates.TemplatesPanel"], + ) + def test_template_source(self): + from django.core import signing + from django.template.loader import get_template + + template = get_template("basic.html") + url = "/__debug__/template_source/" + data = { + "template": template.template.name, + "template_origin": signing.dumps(template.template.origin.name), + } + + response = self.client.get(url, data) + self.assertEqual(response.status_code, 200) + @override_settings( DEBUG=True, DEBUG_TOOLBAR_PANELS=["debug_toolbar.panels.templates.TemplatesPanel"] ) class JinjaTemplateTestCase(IntegrationTestCase): def test_django_jinja2(self): + r = self.client.get("/regular_jinja/foobar/") + self.assertContains(r, "Test for foobar (Jinja)") + # This should be 2 templates because of the parent template. + # See test_django_jinja2_parent_template_instrumented + self.assertContains(r, "

    Templates (1 rendered)

    ") + self.assertContains(r, "basic.jinja") + + @expectedFailure + def test_django_jinja2_parent_template_instrumented(self): + """ + When Jinja2 templates are properly instrumented, the + parent template should be instrumented. + """ r = self.client.get("/regular_jinja/foobar/") self.assertContains(r, "Test for foobar (Jinja)") self.assertContains(r, "

    Templates (2 rendered)

    ") - self.assertContains(r, "jinja2/basic.jinja") + self.assertContains(r, "basic.jinja") def context_processor(request): diff --git a/tests/settings.py b/tests/settings.py index dbbbb79b2..12561fb11 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -7,11 +7,15 @@ # Quick-start development settings - unsuitable for production +DEBUG = False SECRET_KEY = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" INTERNAL_IPS = ["127.0.0.1"] -LOGGING_CONFIG = None # avoids spurious output in tests +LOGGING = { # avoids spurious output in tests + "version": 1, + "disable_existing_loggers": True, +} # Application definition @@ -24,12 +28,23 @@ "django.contrib.messages", "django.contrib.staticfiles", "debug_toolbar", + # We are not actively using template-partials; we just want more nesting + # in our template loader configuration, see + # https://github.com/django-commons/django-debug-toolbar/issues/2109 + "template_partials", "tests", ] + +USE_GIS = os.getenv("DB_BACKEND") == "postgis" + +if USE_GIS: + INSTALLED_APPS = ["django.contrib.gis"] + INSTALLED_APPS + MEDIA_URL = "/media/" # Avoids https://code.djangoproject.com/ticket/21451 MIDDLEWARE = [ + "tests.middleware.UseCacheAfterToolbar", "debug_toolbar.middleware.DebugToolbarMiddleware", "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", @@ -63,6 +78,8 @@ }, ] +USE_TZ = True + STATIC_ROOT = os.path.join(BASE_DIR, "tests", "static") STATIC_URL = "/static/" @@ -79,21 +96,43 @@ "second": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"}, } -if os.environ.get("DJANGO_DATABASE_ENGINE") == "postgresql": - DATABASES = { - "default": {"ENGINE": "django.db.backends.postgresql", "NAME": "debug-toolbar"} - } -elif os.environ.get("DJANGO_DATABASE_ENGINE") == "mysql": - DATABASES = { - "default": {"ENGINE": "django.db.backends.mysql", "NAME": "debug_toolbar"} - } -else: - DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3"}} +DATABASES = { + "default": { + "ENGINE": "django.{}db.backends.{}".format( + "contrib.gis." if USE_GIS else "", os.getenv("DB_BACKEND", "sqlite3") + ), + "NAME": os.getenv("DB_NAME", ":memory:"), + "USER": os.getenv("DB_USER"), + "PASSWORD": os.getenv("DB_PASSWORD"), + "HOST": os.getenv("DB_HOST", ""), + "PORT": os.getenv("DB_PORT", ""), + "TEST": { + "USER": "default_test", + }, + }, + "replica": { + "ENGINE": "django.{}db.backends.{}".format( + "contrib.gis." if USE_GIS else "", os.getenv("DB_BACKEND", "sqlite3") + ), + "NAME": os.getenv("DB_NAME", ":memory:"), + "USER": os.getenv("DB_USER"), + "PASSWORD": os.getenv("DB_PASSWORD"), + "HOST": os.getenv("DB_HOST", ""), + "PORT": os.getenv("DB_PORT", ""), + "TEST": { + "USER": "default_test", + "MIRROR": "default", + }, + }, +} +DEFAULT_AUTO_FIELD = "django.db.models.AutoField" # Debug Toolbar configuration DEBUG_TOOLBAR_CONFIG = { # Django's test client sets wsgi.multiprocess to True inappropriately - "RENDER_PANELS": False + "RENDER_PANELS": False, + # IS_RUNNING_TESTS must be False even though we're running tests because we're running the toolbar's own tests. + "IS_RUNNING_TESTS": False, } diff --git a/tests/sync.py b/tests/sync.py new file mode 100644 index 000000000..d7a9872fd --- /dev/null +++ b/tests/sync.py @@ -0,0 +1,23 @@ +""" +Taken from channels.db +""" + +from asgiref.sync import SyncToAsync +from django.db import close_old_connections + + +class DatabaseSyncToAsync(SyncToAsync): + """ + SyncToAsync version that cleans up old database connections when it exits. + """ + + def thread_handler(self, loop, *args, **kwargs): + close_old_connections() + try: + return super().thread_handler(loop, *args, **kwargs) + finally: + close_old_connections() + + +# The class is TitleCased, but we want to encourage use as a callable/decorator +database_sync_to_async = DatabaseSyncToAsync diff --git a/tests/templates/ajax/ajax.html b/tests/templates/ajax/ajax.html new file mode 100644 index 000000000..7955456de --- /dev/null +++ b/tests/templates/ajax/ajax.html @@ -0,0 +1,22 @@ +{% extends "base.html" %} + +{% block content %} +
    click for ajax
    + + +{% endblock content %} diff --git a/tests/templates/base.html b/tests/templates/base.html index ea0d773ac..272c316f0 100644 --- a/tests/templates/base.html +++ b/tests/templates/base.html @@ -2,6 +2,7 @@ {{ title }} + {% block head %}{% endblock %} {% block content %}{% endblock %} diff --git a/tests/templates/basic.html b/tests/templates/basic.html index 46f88e4da..02f87200a 100644 --- a/tests/templates/basic.html +++ b/tests/templates/basic.html @@ -1,2 +1,3 @@ {% extends "base.html" %} + {% block content %}Test for {{ title }}{% endblock %} diff --git a/tests/templates/jinja2/base.html b/tests/templates/jinja2/base.html new file mode 100644 index 000000000..ea0d773ac --- /dev/null +++ b/tests/templates/jinja2/base.html @@ -0,0 +1,9 @@ + + + + {{ title }} + + + {% block content %}{% endblock %} + + diff --git a/tests/templates/jinja2/basic.jinja b/tests/templates/jinja2/basic.jinja index 812acbcac..1ebced724 100644 --- a/tests/templates/jinja2/basic.jinja +++ b/tests/templates/jinja2/basic.jinja @@ -1,2 +1,6 @@ {% extends 'base.html' %} -{% block content %}Test for {{ title }} (Jinja){% endblock %} + +{% block content %} +Test for {{ title }} (Jinja) +{% for i in range(10) %}{{ i }}{% endfor %} {# Jinja2 supports range(), Django templates do not #} +{% endblock content %} diff --git a/tests/templates/sql/flat.html b/tests/templates/sql/flat.html new file mode 100644 index 000000000..ee5386c55 --- /dev/null +++ b/tests/templates/sql/flat.html @@ -0,0 +1,5 @@ +{% extends "base.html" %} + +{% block content %} + {{ users }} +{% endblock content %} diff --git a/tests/templates/sql/included.html b/tests/templates/sql/included.html new file mode 100644 index 000000000..87d2e1f70 --- /dev/null +++ b/tests/templates/sql/included.html @@ -0,0 +1 @@ +{{ users }} diff --git a/tests/templates/sql/nested.html b/tests/templates/sql/nested.html new file mode 100644 index 000000000..e23a53af1 --- /dev/null +++ b/tests/templates/sql/nested.html @@ -0,0 +1,5 @@ +{% extends "base.html" %} + +{% block content %} + {% include "sql/included.html" %} +{% endblock content %} diff --git a/tests/templates/staticfiles/async_static.html b/tests/templates/staticfiles/async_static.html new file mode 100644 index 000000000..80f636cce --- /dev/null +++ b/tests/templates/staticfiles/async_static.html @@ -0,0 +1,6 @@ +{% extends "base.html" %} +{% load static %} + +{% block head %} + +{% endblock head %} diff --git a/tests/templates/staticfiles/path.html b/tests/templates/staticfiles/path.html new file mode 100644 index 000000000..bf3781c3b --- /dev/null +++ b/tests/templates/staticfiles/path.html @@ -0,0 +1 @@ +{% load static %}{% static path %} diff --git a/tests/test_checks.py b/tests/test_checks.py index 935750d3f..27db92a9d 100644 --- a/tests/test_checks.py +++ b/tests/test_checks.py @@ -1,10 +1,10 @@ -import os +from unittest.mock import patch -from django.conf import settings from django.core.checks import Warning, run_checks from django.test import SimpleTestCase, override_settings +from django.urls import NoReverseMatch -PATH_DOES_NOT_EXIST = os.path.join(settings.BASE_DIR, "tests", "invalid_static") +from debug_toolbar.apps import debug_toolbar_installed_when_running_tests_check class ChecksTestCase(SimpleTestCase): @@ -89,33 +89,228 @@ def test_check_middleware_classes_error(self): messages, ) + @override_settings(DEBUG_TOOLBAR_PANELS=[]) + def test_panels_is_empty(self): + errors = run_checks() + self.assertEqual( + errors, + [ + Warning( + "Setting DEBUG_TOOLBAR_PANELS is empty.", + hint="Set DEBUG_TOOLBAR_PANELS to a non-empty list in your " + "settings.py.", + id="debug_toolbar.W005", + ), + ], + ) + @override_settings( - STATICFILES_DIRS=[PATH_DOES_NOT_EXIST], + TEMPLATES=[ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "APP_DIRS": False, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + "loaders": [ + "django.template.loaders.filesystem.Loader", + ], + }, + }, + ] ) - def test_panel_check_errors(self): - messages = run_checks() + def test_check_w006_invalid(self): + errors = run_checks() self.assertEqual( - messages, + errors, [ Warning( - "debug_toolbar requires the STATICFILES_DIRS directories to exist.", - hint="Running manage.py collectstatic may help uncover the issue.", - id="debug_toolbar.staticfiles.W001", + "At least one DjangoTemplates TEMPLATES configuration needs " + "to use django.template.loaders.app_directories.Loader or " + "have APP_DIRS set to True.", + hint=( + "Include django.template.loaders.app_directories.Loader " + 'in ["OPTIONS"]["loaders"]. Alternatively use ' + "APP_DIRS=True for at least one " + "django.template.backends.django.DjangoTemplates " + "backend configuration." + ), + id="debug_toolbar.W006", ) ], ) - @override_settings(DEBUG_TOOLBAR_PANELS=[]) - def test_panels_is_empty(self): - errors = run_checks() + @override_settings( + TEMPLATES=[ + { + "NAME": "use_loaders", + "BACKEND": "django.template.backends.django.DjangoTemplates", + "APP_DIRS": False, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + "loaders": [ + "django.template.loaders.app_directories.Loader", + ], + }, + }, + { + "NAME": "use_app_dirs", + "BACKEND": "django.template.backends.django.DjangoTemplates", + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, + ] + ) + def test_check_w006_valid(self): + self.assertEqual(run_checks(), []) + + @override_settings( + TEMPLATES=[ + { + "NAME": "use_loaders", + "BACKEND": "django.template.backends.django.DjangoTemplates", + "APP_DIRS": False, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + "loaders": [ + ( + "django.template.loaders.cached.Loader", + [ + "django.template.loaders.filesystem.Loader", + "django.template.loaders.app_directories.Loader", + ], + ), + ], + }, + }, + ] + ) + def test_check_w006_valid_nested_loaders(self): + self.assertEqual(run_checks(), []) + + @patch("debug_toolbar.apps.mimetypes.guess_type") + def test_check_w007_valid(self, mocked_guess_type): + mocked_guess_type.return_value = ("text/javascript", None) + self.assertEqual(run_checks(), []) + mocked_guess_type.return_value = ("application/javascript", None) + self.assertEqual(run_checks(), []) + + @patch("debug_toolbar.apps.mimetypes.guess_type") + def test_check_w007_invalid(self, mocked_guess_type): + mocked_guess_type.return_value = ("text/plain", None) self.assertEqual( - errors, + run_checks(), [ Warning( - "Setting DEBUG_TOOLBAR_PANELS is empty.", - hint="Set DEBUG_TOOLBAR_PANELS to a non-empty list in your " - "settings.py.", - id="debug_toolbar.W005", + "JavaScript files are resolving to the wrong content type.", + hint="The Django Debug Toolbar may not load properly while mimetypes are misconfigured. " + "See the Django documentation for an explanation of why this occurs.\n" + "https://docs.djangoproject.com/en/stable/ref/contrib/staticfiles/#static-file-development-view\n" + "\n" + "This typically occurs on Windows machines. The suggested solution is to modify " + "HKEY_CLASSES_ROOT in the registry to specify the content type for JavaScript " + "files.\n" + "\n" + "[HKEY_CLASSES_ROOT\\.js]\n" + '"Content Type"="application/javascript"', + id="debug_toolbar.W007", ) ], ) + + @patch("debug_toolbar.apps.reverse") + def test_debug_toolbar_installed_when_running_tests(self, reverse): + params = [ + { + "debug": True, + "running_tests": True, + "show_callback_changed": True, + "urls_installed": False, + "errors": False, + }, + { + "debug": False, + "running_tests": False, + "show_callback_changed": True, + "urls_installed": False, + "errors": False, + }, + { + "debug": False, + "running_tests": True, + "show_callback_changed": False, + "urls_installed": False, + "errors": False, + }, + { + "debug": False, + "running_tests": True, + "show_callback_changed": True, + "urls_installed": True, + "errors": False, + }, + { + "debug": False, + "running_tests": True, + "show_callback_changed": True, + "urls_installed": False, + "errors": True, + }, + ] + for config in params: + with self.subTest(**config): + config_setting = { + "RENDER_PANELS": False, + "IS_RUNNING_TESTS": config["running_tests"], + "SHOW_TOOLBAR_CALLBACK": ( + (lambda *args: True) + if config["show_callback_changed"] + else "debug_toolbar.middleware.show_toolbar" + ), + } + if config["urls_installed"]: + reverse.side_effect = lambda *args: None + else: + reverse.side_effect = NoReverseMatch() + + with self.settings( + DEBUG=config["debug"], DEBUG_TOOLBAR_CONFIG=config_setting + ): + errors = debug_toolbar_installed_when_running_tests_check(None) + if config["errors"]: + self.assertEqual(len(errors), 1) + self.assertEqual(errors[0].id, "debug_toolbar.E001") + else: + self.assertEqual(len(errors), 0) + + @override_settings( + DEBUG_TOOLBAR_CONFIG={ + "OBSERVE_REQUEST_CALLBACK": lambda request: False, + "IS_RUNNING_TESTS": False, + } + ) + def test_observe_request_callback_specified(self): + errors = run_checks() + self.assertEqual(len(errors), 1) + self.assertEqual(errors[0].id, "debug_toolbar.W008") diff --git a/tests/test_csp_rendering.py b/tests/test_csp_rendering.py new file mode 100644 index 000000000..144e65ba0 --- /dev/null +++ b/tests/test_csp_rendering.py @@ -0,0 +1,176 @@ +from __future__ import annotations + +from typing import cast +from xml.etree.ElementTree import Element + +from django.conf import settings +from django.http.response import HttpResponse +from django.test.utils import ContextList, override_settings +from html5lib.constants import E +from html5lib.html5parser import HTMLParser + +from debug_toolbar.toolbar import DebugToolbar + +from .base import IntegrationTestCase + +MIDDLEWARE_CSP_BEFORE = settings.MIDDLEWARE.copy() +MIDDLEWARE_CSP_BEFORE.insert( + MIDDLEWARE_CSP_BEFORE.index("debug_toolbar.middleware.DebugToolbarMiddleware"), + "csp.middleware.CSPMiddleware", +) +MIDDLEWARE_CSP_LAST = settings.MIDDLEWARE + ["csp.middleware.CSPMiddleware"] + + +def get_namespaces(element: Element) -> dict[str, str]: + """ + Return the default `xmlns`. See + https://docs.python.org/3/library/xml.etree.elementtree.html#parsing-xml-with-namespaces + """ + if not element.tag.startswith("{"): + return {} + return {"": element.tag[1:].split("}", maxsplit=1)[0]} + + +@override_settings(DEBUG=True) +class CspRenderingTestCase(IntegrationTestCase): + """Testing if `csp-nonce` renders.""" + + def setUp(self): + super().setUp() + self.parser = HTMLParser() + + def _fail_if_missing( + self, root: Element, path: str, namespaces: dict[str, str], nonce: str + ): + """ + Search elements, fail if a `nonce` attribute is missing on them. + """ + elements = root.findall(path=path, namespaces=namespaces) + for item in elements: + if item.attrib.get("nonce") != nonce: + raise self.failureException(f"{item} has no nonce attribute.") + + def _fail_if_found(self, root: Element, path: str, namespaces: dict[str, str]): + """ + Search elements, fail if a `nonce` attribute is found on them. + """ + elements = root.findall(path=path, namespaces=namespaces) + for item in elements: + if "nonce" in item.attrib: + raise self.failureException(f"{item} has a nonce attribute.") + + def _fail_on_invalid_html(self, content: bytes, parser: HTMLParser): + """Fail if the passed HTML is invalid.""" + if parser.errors: + default_msg = ["Content is invalid HTML:"] + lines = content.split(b"\n") + for position, error_code, data_vars in parser.errors: + default_msg.append(f" {E[error_code]}" % data_vars) + default_msg.append(f" {lines[position[0] - 1]!r}") + msg = self._formatMessage(None, "\n".join(default_msg)) + raise self.failureException(msg) + + def test_exists(self): + """A `nonce` should exist when using the `CSPMiddleware`.""" + for middleware in [MIDDLEWARE_CSP_BEFORE, MIDDLEWARE_CSP_LAST]: + with self.settings(MIDDLEWARE=middleware): + response = cast(HttpResponse, self.client.get(path="/csp_view/")) + self.assertEqual(response.status_code, 200) + + html_root: Element = self.parser.parse(stream=response.content) + self._fail_on_invalid_html(content=response.content, parser=self.parser) + self.assertContains(response, "djDebug") + + namespaces = get_namespaces(element=html_root) + toolbar = list(DebugToolbar._store.values())[-1] + nonce = str(toolbar.csp_nonce) + self._fail_if_missing( + root=html_root, path=".//link", namespaces=namespaces, nonce=nonce + ) + self._fail_if_missing( + root=html_root, path=".//script", namespaces=namespaces, nonce=nonce + ) + + def test_does_not_exist_nonce_wasnt_used(self): + """ + A `nonce` should not exist even when using the `CSPMiddleware` + if the view didn't access the request.csp_nonce attribute. + """ + for middleware in [MIDDLEWARE_CSP_BEFORE, MIDDLEWARE_CSP_LAST]: + with self.settings(MIDDLEWARE=middleware): + response = cast(HttpResponse, self.client.get(path="/regular/basic/")) + self.assertEqual(response.status_code, 200) + + html_root: Element = self.parser.parse(stream=response.content) + self._fail_on_invalid_html(content=response.content, parser=self.parser) + self.assertContains(response, "djDebug") + + namespaces = get_namespaces(element=html_root) + self._fail_if_found( + root=html_root, path=".//link", namespaces=namespaces + ) + self._fail_if_found( + root=html_root, path=".//script", namespaces=namespaces + ) + + @override_settings( + DEBUG_TOOLBAR_CONFIG={"DISABLE_PANELS": set()}, + ) + def test_redirects_exists(self): + for middleware in [MIDDLEWARE_CSP_BEFORE, MIDDLEWARE_CSP_LAST]: + with self.settings(MIDDLEWARE=middleware): + response = cast(HttpResponse, self.client.get(path="/csp_view/")) + self.assertEqual(response.status_code, 200) + + html_root: Element = self.parser.parse(stream=response.content) + self._fail_on_invalid_html(content=response.content, parser=self.parser) + self.assertContains(response, "djDebug") + + namespaces = get_namespaces(element=html_root) + context: ContextList = response.context # pyright: ignore[reportAttributeAccessIssue] + nonce = str(context["toolbar"].csp_nonce) + self._fail_if_missing( + root=html_root, path=".//link", namespaces=namespaces, nonce=nonce + ) + self._fail_if_missing( + root=html_root, path=".//script", namespaces=namespaces, nonce=nonce + ) + + def test_panel_content_nonce_exists(self): + for middleware in [MIDDLEWARE_CSP_BEFORE, MIDDLEWARE_CSP_LAST]: + with self.settings(MIDDLEWARE=middleware): + response = cast(HttpResponse, self.client.get(path="/csp_view/")) + self.assertEqual(response.status_code, 200) + + toolbar = list(DebugToolbar._store.values())[-1] + panels_to_check = ["HistoryPanel", "TimerPanel"] + for panel in panels_to_check: + content = toolbar.get_panel_by_id(panel).content + html_root: Element = self.parser.parse(stream=content) + namespaces = get_namespaces(element=html_root) + nonce = str(toolbar.csp_nonce) + self._fail_if_missing( + root=html_root, + path=".//link", + namespaces=namespaces, + nonce=nonce, + ) + self._fail_if_missing( + root=html_root, + path=".//script", + namespaces=namespaces, + nonce=nonce, + ) + + def test_missing(self): + """A `nonce` should not exist when not using the `CSPMiddleware`.""" + response = cast(HttpResponse, self.client.get(path="/regular/basic/")) + self.assertEqual(response.status_code, 200) + + html_root: Element = self.parser.parse(stream=response.content) + self._fail_on_invalid_html(content=response.content, parser=self.parser) + self.assertContains(response, "djDebug") + + namespaces = get_namespaces(element=html_root) + self._fail_if_found(root=html_root, path=".//link", namespaces=namespaces) + self._fail_if_found(root=html_root, path=".//script", namespaces=namespaces) diff --git a/tests/test_decorators.py b/tests/test_decorators.py new file mode 100644 index 000000000..9840a6390 --- /dev/null +++ b/tests/test_decorators.py @@ -0,0 +1,66 @@ +from unittest.mock import patch + +from django.http import Http404, HttpResponse +from django.test import AsyncRequestFactory, RequestFactory, TestCase +from django.test.utils import override_settings + +from debug_toolbar.decorators import render_with_toolbar_language, require_show_toolbar + + +@render_with_toolbar_language +def stub_view(request): + return HttpResponse(200) + + +@require_show_toolbar +def stub_require_toolbar_view(request): + return HttpResponse(200) + + +@require_show_toolbar +async def stub_require_toolbar_async_view(request): + return HttpResponse(200) + + +class TestRequireToolbar(TestCase): + """ + Tests require_toolbar functionality and async compatibility. + """ + + def setUp(self): + self.factory = RequestFactory() + self.async_factory = AsyncRequestFactory() + + @override_settings(DEBUG=True) + def test_require_toolbar_debug_true(self): + response = stub_require_toolbar_view(self.factory.get("/")) + self.assertEqual(response.status_code, 200) + + def test_require_toolbar_debug_false(self): + with self.assertRaises(Http404): + stub_require_toolbar_view(self.factory.get("/")) + + # Following tests additionally tests async compatibility + # of require_toolbar decorator + @override_settings(DEBUG=True) + async def test_require_toolbar_async_debug_true(self): + response = await stub_require_toolbar_async_view(self.async_factory.get("/")) + self.assertEqual(response.status_code, 200) + + async def test_require_toolbar_async_debug_false(self): + with self.assertRaises(Http404): + await stub_require_toolbar_async_view(self.async_factory.get("/")) + + +@override_settings(DEBUG=True, LANGUAGE_CODE="fr") +class RenderWithToolbarLanguageTestCase(TestCase): + @override_settings(DEBUG_TOOLBAR_CONFIG={"TOOLBAR_LANGUAGE": "de"}) + @patch("debug_toolbar.decorators.language_override") + def test_uses_toolbar_language(self, mock_language_override): + stub_view(RequestFactory().get("/")) + mock_language_override.assert_called_once_with("de") + + @patch("debug_toolbar.decorators.language_override") + def test_defaults_to_django_language_code(self, mock_language_override): + stub_view(RequestFactory().get("/")) + mock_language_override.assert_called_once_with("fr") diff --git a/tests/test_forms.py b/tests/test_forms.py new file mode 100644 index 000000000..a619ae89d --- /dev/null +++ b/tests/test_forms.py @@ -0,0 +1,50 @@ +from datetime import datetime, timezone + +from django import forms +from django.test import TestCase + +from debug_toolbar.forms import SignedDataForm + +SIGNATURE = "-WiogJKyy4E8Om00CrFSy0T6XHObwBa6Zb46u-vmeYE" + +DATA = {"date": datetime(2020, 1, 1, tzinfo=timezone.utc), "value": "foo"} +SIGNED_DATA = f'{{"date": "2020-01-01 00:00:00+00:00", "value": "foo"}}:{SIGNATURE}' + + +class FooForm(forms.Form): + value = forms.CharField() + # Include a datetime in the tests because it's not serializable back + # to a datetime by SignedDataForm + date = forms.DateTimeField() + + +class TestSignedDataForm(TestCase): + def test_signed_data(self): + data = {"signed": SignedDataForm.sign(DATA)} + form = SignedDataForm(data=data) + self.assertTrue(form.is_valid()) + # Check the signature value + self.assertEqual(data["signed"], SIGNED_DATA) + + def test_verified_data(self): + form = SignedDataForm(data={"signed": SignedDataForm.sign(DATA)}) + self.assertEqual( + form.verified_data(), + { + "value": "foo", + "date": "2020-01-01 00:00:00+00:00", + }, + ) + # Take it back to the foo form to validate the datetime is serialized + foo_form = FooForm(data=form.verified_data()) + self.assertTrue(foo_form.is_valid()) + self.assertDictEqual(foo_form.cleaned_data, DATA) + + def test_initial_set_signed(self): + form = SignedDataForm(initial=DATA) + self.assertEqual(form.initial["signed"], SIGNED_DATA) + + def test_prevents_tampering(self): + data = {"signed": SIGNED_DATA.replace('"value": "foo"', '"value": "bar"')} + form = SignedDataForm(data=data) + self.assertFalse(form.is_valid()) diff --git a/tests/test_integration.py b/tests/test_integration.py index 1ab053ab9..a431ba29f 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -1,17 +1,20 @@ import os import re +import time import unittest +from unittest.mock import patch -import django import html5lib from django.contrib.staticfiles.testing import StaticLiveServerTestCase from django.core import signing +from django.core.cache import cache from django.db import connection from django.http import HttpResponse from django.template.loader import get_template -from django.test import RequestFactory +from django.test import AsyncRequestFactory, RequestFactory from django.test.utils import override_settings +from debug_toolbar.forms import SignedDataForm from debug_toolbar.middleware import DebugToolbarMiddleware, show_toolbar from debug_toolbar.panels import Panel from debug_toolbar.toolbar import DebugToolbar @@ -33,6 +36,15 @@ rf = RequestFactory() +def toolbar_store_id(): + def get_response(request): + return HttpResponse() + + toolbar = DebugToolbar(rf.get("/"), get_response) + toolbar.store() + return toolbar.store_id + + class BuggyPanel(Panel): def title(self): return "BuggyPanel" @@ -55,12 +67,87 @@ def test_show_toolbar_INTERNAL_IPS(self): with self.settings(INTERNAL_IPS=[]): self.assertFalse(show_toolbar(self.request)) + @patch("socket.gethostbyname", return_value="127.0.0.255") + def test_show_toolbar_docker(self, mocked_gethostbyname): + with self.settings(INTERNAL_IPS=[]): + # Is true because REMOTE_ADDR is 127.0.0.1 and the 255 + # is shifted to be 1. + self.assertTrue(show_toolbar(self.request)) + mocked_gethostbyname.assert_called_once_with("host.docker.internal") + + def test_not_iterating_over_INTERNAL_IPS(self): + """Verify that the middleware does not iterate over INTERNAL_IPS in some way. + + Some people use iptools.IpRangeList for their INTERNAL_IPS. This is a class + that can quickly answer the question if the setting contain a certain IP address, + but iterating over this object will drain all performance / blow up. + """ + + class FailOnIteration: + def __iter__(self): + raise RuntimeError( + "The testcase failed: the code should not have iterated over INTERNAL_IPS" + ) + + def __contains__(self, x): + return True + + with self.settings(INTERNAL_IPS=FailOnIteration()): + response = self.client.get("/regular/basic/") + self.assertEqual(response.status_code, 200) + self.assertContains(response, "djDebug") # toolbar + + def test_should_render_panels_RENDER_PANELS(self): + """ + The toolbar should force rendering panels on each request + based on the RENDER_PANELS setting. + """ + toolbar = DebugToolbar(self.request, self.get_response) + self.assertFalse(toolbar.should_render_panels()) + toolbar.config["RENDER_PANELS"] = True + self.assertTrue(toolbar.should_render_panels()) + toolbar.config["RENDER_PANELS"] = None + self.assertTrue(toolbar.should_render_panels()) + + def test_should_render_panels_multiprocess(self): + """ + The toolbar should render the panels on each request when wsgi.multiprocess + is True or missing. + """ + request = rf.get("/") + request.META["wsgi.multiprocess"] = True + toolbar = DebugToolbar(request, self.get_response) + toolbar.config["RENDER_PANELS"] = None + self.assertTrue(toolbar.should_render_panels()) + + request.META["wsgi.multiprocess"] = False + self.assertFalse(toolbar.should_render_panels()) + + request.META.pop("wsgi.multiprocess") + self.assertTrue(toolbar.should_render_panels()) + + def test_should_render_panels_asgi(self): + """ + The toolbar not should render the panels on each request when wsgi.multiprocess + is True or missing in case of async context rather than multithreaded + wsgi. + """ + async_request = AsyncRequestFactory().get("/") + # by default ASGIRequest will have wsgi.multiprocess set to True + # but we are still assigning this to true cause this could change + # and we specifically need to check that method returns false even with + # wsgi.multiprocess set to true + async_request.META["wsgi.multiprocess"] = True + toolbar = DebugToolbar(async_request, self.get_response) + toolbar.config["RENDER_PANELS"] = None + self.assertFalse(toolbar.should_render_panels()) + def _resolve_stats(self, path): # takes stats from Request panel - self.request.path = path + request = rf.get(path) panel = self.toolbar.get_panel_by_id("RequestPanel") - response = panel.process_request(self.request) - panel.generate_stats(self.request, response) + response = panel.process_request(request) + panel.generate_stats(request, response) return panel.get_stats() def test_url_resolving_positional(self): @@ -95,11 +182,120 @@ def get_response(request): # check toolbar insertion before "" self.assertContains(response, "\n") + def test_middleware_no_injection_when_encoded(self): + def get_response(request): + response = HttpResponse("") + response["Content-Encoding"] = "something" + return response + + response = DebugToolbarMiddleware(get_response)(self.request) + self.assertEqual(response.content, b"") + def test_cache_page(self): - self.client.get("/cached_view/") - self.assertEqual(len(self.toolbar.get_panel_by_id("CachePanel").calls), 3) - self.client.get("/cached_view/") - self.assertEqual(len(self.toolbar.get_panel_by_id("CachePanel").calls), 5) + # Clear the cache before testing the views. Other tests that use cached_view + # may run earlier and cause fewer cache calls. + cache.clear() + response = self.client.get("/cached_view/") + self.assertEqual(len(response.toolbar.get_panel_by_id("CachePanel").calls), 3) + response = self.client.get("/cached_view/") + self.assertEqual(len(response.toolbar.get_panel_by_id("CachePanel").calls), 2) + + @override_settings(ROOT_URLCONF="tests.urls_use_package_urls") + def test_include_package_urls(self): + """Test urlsconf that uses the debug_toolbar.urls in the include call""" + # Clear the cache before testing the views. Other tests that use cached_view + # may run earlier and cause fewer cache calls. + cache.clear() + response = self.client.get("/cached_view/") + self.assertEqual(len(response.toolbar.get_panel_by_id("CachePanel").calls), 3) + response = self.client.get("/cached_view/") + self.assertEqual(len(response.toolbar.get_panel_by_id("CachePanel").calls), 2) + + def test_low_level_cache_view(self): + """Test cases when low level caching API is used within a request.""" + response = self.client.get("/cached_low_level_view/") + self.assertEqual(len(response.toolbar.get_panel_by_id("CachePanel").calls), 2) + response = self.client.get("/cached_low_level_view/") + self.assertEqual(len(response.toolbar.get_panel_by_id("CachePanel").calls), 1) + + def test_cache_disable_instrumentation(self): + """ + Verify that middleware cache usages before and after + DebugToolbarMiddleware are not counted. + """ + self.assertIsNone(cache.set("UseCacheAfterToolbar.before", None)) + self.assertIsNone(cache.set("UseCacheAfterToolbar.after", None)) + response = self.client.get("/execute_sql/") + self.assertEqual(cache.get("UseCacheAfterToolbar.before"), 1) + self.assertEqual(cache.get("UseCacheAfterToolbar.after"), 1) + self.assertEqual(len(response.toolbar.get_panel_by_id("CachePanel").calls), 0) + + def test_is_toolbar_request(self): + request = rf.get("/__debug__/render_panel/") + self.assertTrue(self.toolbar.is_toolbar_request(request)) + + request = rf.get("/invalid/__debug__/render_panel/") + self.assertFalse(self.toolbar.is_toolbar_request(request)) + + request = rf.get("/render_panel/") + self.assertFalse(self.toolbar.is_toolbar_request(request)) + + @override_settings(ROOT_URLCONF="tests.urls_invalid") + def test_is_toolbar_request_without_djdt_urls(self): + """Test cases when the toolbar urls aren't configured.""" + request = rf.get("/__debug__/render_panel/") + self.assertFalse(self.toolbar.is_toolbar_request(request)) + + request = rf.get("/render_panel/") + self.assertFalse(self.toolbar.is_toolbar_request(request)) + + @override_settings(ROOT_URLCONF="tests.urls_invalid") + def test_is_toolbar_request_override_request_urlconf(self): + """Test cases when the toolbar URL is configured on the request.""" + request = rf.get("/__debug__/render_panel/") + self.assertFalse(self.toolbar.is_toolbar_request(request)) + + # Verify overriding the urlconf on the request is valid. + request.urlconf = "tests.urls" + self.assertTrue(self.toolbar.is_toolbar_request(request)) + + def test_is_toolbar_request_with_script_prefix(self): + """ + Test cases when Django is running under a path prefix, such as via the + FORCE_SCRIPT_NAME setting. + """ + request = rf.get("/__debug__/render_panel/", SCRIPT_NAME="/path/") + self.assertTrue(self.toolbar.is_toolbar_request(request)) + + request = rf.get("/invalid/__debug__/render_panel/", SCRIPT_NAME="/path/") + self.assertFalse(self.toolbar.is_toolbar_request(request)) + + request = rf.get("/render_panel/", SCRIPT_NAME="/path/") + self.assertFalse(self.toolbar.is_toolbar_request(self.request)) + + def test_data_gone(self): + response = self.client.get( + "/__debug__/render_panel/?store_id=GONE&panel_id=RequestPanel" + ) + self.assertIn("Please reload the page and retry.", response.json()["content"]) + + def test_sql_page(self): + response = self.client.get("/execute_sql/") + self.assertEqual( + len(response.toolbar.get_panel_by_id("SQLPanel").get_stats()["queries"]), 1 + ) + + def test_async_sql_page(self): + response = self.client.get("/async_execute_sql/") + self.assertEqual( + len(response.toolbar.get_panel_by_id("SQLPanel").get_stats()["queries"]), 2 + ) + + def test_concurrent_async_sql_page(self): + response = self.client.get("/async_execute_sql_concurrently/") + self.assertEqual( + len(response.toolbar.get_panel_by_id("SQLPanel").get_stats()["queries"]), 2 + ) @override_settings(DEBUG=True) @@ -128,29 +324,26 @@ def test_html5_validation(self): default_msg = ["Content is invalid HTML:"] lines = content.split(b"\n") for position, errorcode, datavars in parser.errors: - default_msg.append(" %s" % html5lib.constants.E[errorcode] % datavars) - default_msg.append(" %r" % lines[position[0] - 1]) + default_msg.append(f" {html5lib.constants.E[errorcode]}" % datavars) + default_msg.append(f" {lines[position[0] - 1]!r}") msg = self._formatMessage(None, "\n".join(default_msg)) raise self.failureException(msg) def test_render_panel_checks_show_toolbar(self): - def get_response(request): - return HttpResponse() - - toolbar = DebugToolbar(rf.get("/"), get_response) - toolbar.store() url = "/__debug__/render_panel/" - data = {"store_id": toolbar.store_id, "panel_id": "VersionsPanel"} + data = {"store_id": toolbar_store_id(), "panel_id": "VersionsPanel"} response = self.client.get(url, data) self.assertEqual(response.status_code, 200) - response = self.client.get(url, data, HTTP_X_REQUESTED_WITH="XMLHttpRequest") + response = self.client.get( + url, data, headers={"x-requested-with": "XMLHttpRequest"} + ) self.assertEqual(response.status_code, 200) with self.settings(INTERNAL_IPS=[]): response = self.client.get(url, data) self.assertEqual(response.status_code, 404) response = self.client.get( - url, data, HTTP_X_REQUESTED_WITH="XMLHttpRequest" + url, data, headers={"x-requested-with": "XMLHttpRequest"} ) self.assertEqual(response.status_code, 404) @@ -180,62 +373,117 @@ def test_template_source_checks_show_toolbar(self): response = self.client.get(url, data) self.assertEqual(response.status_code, 200) - response = self.client.get(url, data, HTTP_X_REQUESTED_WITH="XMLHttpRequest") + response = self.client.get( + url, data, headers={"x-requested-with": "XMLHttpRequest"} + ) self.assertEqual(response.status_code, 200) with self.settings(INTERNAL_IPS=[]): response = self.client.get(url, data) self.assertEqual(response.status_code, 404) response = self.client.get( - url, data, HTTP_X_REQUESTED_WITH="XMLHttpRequest" + url, data, headers={"x-requested-with": "XMLHttpRequest"} ) self.assertEqual(response.status_code, 404) + def test_template_source_errors(self): + url = "/__debug__/template_source/" + + response = self.client.get(url, {}) + self.assertContains( + response, '"template_origin" key is required', status_code=400 + ) + + template = get_template("basic.html") + response = self.client.get( + url, + {"template_origin": signing.dumps(template.template.origin.name) + "xyz"}, + ) + self.assertContains(response, '"template_origin" is invalid', status_code=400) + + response = self.client.get( + url, {"template_origin": signing.dumps("does_not_exist.html")} + ) + self.assertContains(response, "Template Does Not Exist: does_not_exist.html") + def test_sql_select_checks_show_toolbar(self): url = "/__debug__/sql_select/" data = { - "sql": "SELECT * FROM auth_user", - "raw_sql": "SELECT * FROM auth_user", - "params": "{}", - "alias": "default", - "duration": "0", - "hash": "6e12daa636b8c9a8be993307135458f90a877606", + "signed": SignedDataForm.sign( + { + "sql": "SELECT * FROM auth_user", + "raw_sql": "SELECT * FROM auth_user", + "params": "{}", + "alias": "default", + "duration": "0", + } + ) } response = self.client.post(url, data) self.assertEqual(response.status_code, 200) - response = self.client.post(url, data, HTTP_X_REQUESTED_WITH="XMLHttpRequest") + response = self.client.post( + url, data, headers={"x-requested-with": "XMLHttpRequest"} + ) self.assertEqual(response.status_code, 200) with self.settings(INTERNAL_IPS=[]): response = self.client.post(url, data) self.assertEqual(response.status_code, 404) response = self.client.post( - url, data, HTTP_X_REQUESTED_WITH="XMLHttpRequest" + url, data, headers={"x-requested-with": "XMLHttpRequest"} ) self.assertEqual(response.status_code, 404) def test_sql_explain_checks_show_toolbar(self): url = "/__debug__/sql_explain/" data = { - "sql": "SELECT * FROM auth_user", - "raw_sql": "SELECT * FROM auth_user", - "params": "{}", - "alias": "default", - "duration": "0", - "hash": "6e12daa636b8c9a8be993307135458f90a877606", + "signed": SignedDataForm.sign( + { + "sql": "SELECT * FROM auth_user", + "raw_sql": "SELECT * FROM auth_user", + "params": "{}", + "alias": "default", + "duration": "0", + } + ) } response = self.client.post(url, data) self.assertEqual(response.status_code, 200) - response = self.client.post(url, data, HTTP_X_REQUESTED_WITH="XMLHttpRequest") + response = self.client.post( + url, data, headers={"x-requested-with": "XMLHttpRequest"} + ) self.assertEqual(response.status_code, 200) with self.settings(INTERNAL_IPS=[]): response = self.client.post(url, data) self.assertEqual(response.status_code, 404) response = self.client.post( - url, data, HTTP_X_REQUESTED_WITH="XMLHttpRequest" + url, data, headers={"x-requested-with": "XMLHttpRequest"} ) self.assertEqual(response.status_code, 404) + @unittest.skipUnless( + connection.vendor == "postgresql", "Test valid only on PostgreSQL" + ) + def test_sql_explain_postgres_union_query(self): + """ + Confirm select queries that start with a parenthesis can be explained. + """ + url = "/__debug__/sql_explain/" + data = { + "signed": SignedDataForm.sign( + { + "sql": "(SELECT * FROM auth_user) UNION (SELECT * from auth_user)", + "raw_sql": "(SELECT * FROM auth_user) UNION (SELECT * from auth_user)", + "params": "{}", + "alias": "default", + "duration": "0", + } + ) + } + + response = self.client.post(url, data) + self.assertEqual(response.status_code, 200) + @unittest.skipUnless( connection.vendor == "postgresql", "Test valid only on PostgreSQL" ) @@ -246,61 +494,104 @@ def test_sql_explain_postgres_json_field(self): ) query = base_query + """ '{"foo": "bar"}'""" data = { - "sql": query, - "raw_sql": base_query + " %s", - "params": '["{\\"foo\\": \\"bar\\"}"]', - "alias": "default", - "duration": "0", - "hash": "2b7172eb2ac8e2a8d6f742f8a28342046e0d00ba", + "signed": SignedDataForm.sign( + { + "sql": query, + "raw_sql": base_query + " %s", + "params": '["{\\"foo\\": \\"bar\\"}"]', + "alias": "default", + "duration": "0", + } + ) } response = self.client.post(url, data) self.assertEqual(response.status_code, 200) - response = self.client.post(url, data, HTTP_X_REQUESTED_WITH="XMLHttpRequest") + response = self.client.post( + url, data, headers={"x-requested-with": "XMLHttpRequest"} + ) self.assertEqual(response.status_code, 200) with self.settings(INTERNAL_IPS=[]): response = self.client.post(url, data) self.assertEqual(response.status_code, 404) response = self.client.post( - url, data, HTTP_X_REQUESTED_WITH="XMLHttpRequest" + url, data, headers={"x-requested-with": "XMLHttpRequest"} ) self.assertEqual(response.status_code, 404) def test_sql_profile_checks_show_toolbar(self): url = "/__debug__/sql_profile/" data = { - "sql": "SELECT * FROM auth_user", - "raw_sql": "SELECT * FROM auth_user", - "params": "{}", - "alias": "default", - "duration": "0", - "hash": "6e12daa636b8c9a8be993307135458f90a877606", + "signed": SignedDataForm.sign( + { + "sql": "SELECT * FROM auth_user", + "raw_sql": "SELECT * FROM auth_user", + "params": "{}", + "alias": "default", + "duration": "0", + } + ) } response = self.client.post(url, data) self.assertEqual(response.status_code, 200) - response = self.client.post(url, data, HTTP_X_REQUESTED_WITH="XMLHttpRequest") + response = self.client.post( + url, data, headers={"x-requested-with": "XMLHttpRequest"} + ) self.assertEqual(response.status_code, 200) with self.settings(INTERNAL_IPS=[]): response = self.client.post(url, data) self.assertEqual(response.status_code, 404) response = self.client.post( - url, data, HTTP_X_REQUESTED_WITH="XMLHttpRequest" + url, data, headers={"x-requested-with": "XMLHttpRequest"} ) self.assertEqual(response.status_code, 404) @override_settings(DEBUG_TOOLBAR_CONFIG={"RENDER_PANELS": True}) - def test_data_store_id_not_rendered_when_none(self): + def test_render_panels_in_request(self): + """ + Test that panels are are rendered during the request with + RENDER_PANELS=TRUE + """ url = "/regular/basic/" response = self.client.get(url) self.assertIn(b'id="djDebug"', response.content) + # Verify the store id is not included. self.assertNotIn(b"data-store-id", response.content) + # Verify the history panel was disabled + self.assertIn( + b'', + response.content, + ) + # Verify the a panel was rendered + self.assertIn(b"Response headers", response.content) + + @override_settings(DEBUG_TOOLBAR_CONFIG={"RENDER_PANELS": False}) + def test_load_panels(self): + """ + Test that panels are not rendered during the request with + RENDER_PANELS=False + """ + url = "/execute_sql/" + response = self.client.get(url) + self.assertIn(b'id="djDebug"', response.content) + # Verify the store id is included. + self.assertIn(b"data-store-id", response.content) + # Verify the history panel was not disabled + self.assertNotIn( + b'', + response.content, + ) + # Verify the a panel was not rendered + self.assertNotIn(b"Response headers", response.content) def test_view_returns_template_response(self): response = self.client.get("/template_response/basic/") self.assertEqual(response.status_code, 200) @override_settings(DEBUG_TOOLBAR_CONFIG={"DISABLE_PANELS": set()}) - def test_incercept_redirects(self): + def test_intercept_redirects(self): response = self.client.get("/redirect/") self.assertEqual(response.status_code, 200) # Link to LOCATION header. @@ -320,6 +611,15 @@ def test_server_timing_headers(self): for expected in expected_partials: self.assertTrue(re.compile(expected).search(server_timing)) + @override_settings(DEBUG_TOOLBAR_CONFIG={"RENDER_PANELS": True}) + def test_timer_panel(self): + response = self.client.get("/regular/basic/") + self.assertEqual(response.status_code, 200) + self.assertContains( + response, + '