From 5e98abdb271950b35e4aa786efdfcffbcd466e2b Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Thu, 6 Feb 2025 08:45:09 +0100 Subject: [PATCH 01/25] Refs #2068: Do not reinstantiate staticfiles storage classes --- debug_toolbar/panels/staticfiles.py | 61 +++++++++-------------------- 1 file changed, 18 insertions(+), 43 deletions(-) diff --git a/debug_toolbar/panels/staticfiles.py b/debug_toolbar/panels/staticfiles.py index 3dd29e979..9f1970ef6 100644 --- a/debug_toolbar/panels/staticfiles.py +++ b/debug_toolbar/panels/staticfiles.py @@ -3,10 +3,8 @@ from contextvars import ContextVar from os.path import join, normpath -from django.conf import settings from django.contrib.staticfiles import finders, storage from django.dispatch import Signal -from django.utils.functional import LazyObject from django.utils.translation import gettext_lazy as _, ngettext from debug_toolbar import panels @@ -37,46 +35,21 @@ def url(self): record_static_file_signal = Signal() -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): - try: - # From Django 4.2 use django.core.files.storage.storages in favor - # of the deprecated django.core.files.storage.get_storage_class - from django.core.files.storage import storages - - configured_storage_cls = storages["staticfiles"].__class__ - except ImportError: - # Backwards compatibility for Django versions prior to 4.2 - from django.core.files.storage import get_storage_class - - configured_storage_cls = get_storage_class(settings.STATICFILES_STORAGE) - - class DebugStaticFilesStorage(configured_storage_cls): - def url(self, path): - url = super().url(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 - - self._wrapped = DebugStaticFilesStorage() - - -_original_storage = storage.staticfiles_storage +class URLMixin: + def url(self, path): + url = super().url(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): @@ -103,7 +76,9 @@ def __init__(self, *args, **kwargs): @classmethod def ready(cls): - storage.staticfiles_storage = DebugConfiguredStorage() + 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 From f44ab85469ec24e6c16909a21fc80e6e35075980 Mon Sep 17 00:00:00 2001 From: dr-rompecabezas Date: Tue, 4 Mar 2025 21:23:25 -0500 Subject: [PATCH 02/25] Add URLMixin tests for staticfiles panel - Test storage state preservation to ensure URLMixin doesn't affect storage attributes - Test context variable lifecycle for static file tracking - Test multiple initialization safety to prevent URLMixin stacking --- tests/panels/test_staticfiles.py | 46 +++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/tests/panels/test_staticfiles.py b/tests/panels/test_staticfiles.py index 334b0b6a3..0549a0112 100644 --- a/tests/panels/test_staticfiles.py +++ b/tests/panels/test_staticfiles.py @@ -1,10 +1,11 @@ from pathlib import Path from django.conf import settings -from django.contrib.staticfiles import finders +from django.contrib.staticfiles import finders, storage from django.shortcuts import render from django.test import AsyncRequestFactory, RequestFactory +from debug_toolbar.panels.staticfiles import URLMixin from ..base import BaseTestCase @@ -76,3 +77,46 @@ def get_response(request): self.panel.generate_stats(self.request, response) 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__) + + # 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("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("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) From 8c09baaf2cc3529c212c5121952ccd6c912bd89c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Feb 2025 23:10:02 +0000 Subject: [PATCH 03/25] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/adamchainz/django-upgrade: 1.22.2 → 1.23.1](https://github.com/adamchainz/django-upgrade/compare/1.22.2...1.23.1) - [github.com/pre-commit/mirrors-eslint: v9.19.0 → v9.20.0](https://github.com/pre-commit/mirrors-eslint/compare/v9.19.0...v9.20.0) - [github.com/astral-sh/ruff-pre-commit: v0.9.4 → v0.9.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.4...v0.9.6) Signed-off-by: Matthias Kestenholz --- .pre-commit-config.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index df926db37..5bdd1cfa7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,7 +14,7 @@ repos: hooks: - id: doc8 - repo: https://github.com/adamchainz/django-upgrade - rev: 1.22.2 + rev: 1.23.1 hooks: - id: django-upgrade args: [--target-version, "4.2"] @@ -32,19 +32,19 @@ repos: args: - --trailing-comma=es5 - repo: https://github.com/pre-commit/mirrors-eslint - rev: v9.19.0 + rev: v9.20.0 hooks: - id: eslint additional_dependencies: - - "eslint@v9.18.0" - - "@eslint/js@v9.18.0" + - "eslint@v9.20.0" + - "@eslint/js@v9.20.0" - "globals" files: \.js?$ types: [file] args: - --fix - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 'v0.9.4' + rev: 'v0.9.6' hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] From 1991b88e631bb2bd6eb0dd0bf57c2866dfa4e211 Mon Sep 17 00:00:00 2001 From: Prashant Andoriya <121665385+andoriyaprashant@users.noreply.github.com> Date: Sat, 15 Feb 2025 04:11:44 +0530 Subject: [PATCH 04/25] Update package metadata to include well-known labels (#2078) * Update package metadata to include well-known labels --- docs/changes.rst | 1 + pyproject.toml | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/docs/changes.rst b/docs/changes.rst index 9010650ba..811c60225 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -5,6 +5,7 @@ Pending ------- * Added Django 5.2 to the tox matrix. +* Updated package metadata to include well-known labels. 5.0.1 (2025-01-13) ------------------ diff --git a/pyproject.toml b/pyproject.toml index 32c78c93a..d31fba500 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,8 +39,13 @@ 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 = [ From 004a581cbdc5b1f731a294a054ed5cbb672d6378 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Tue, 25 Feb 2025 08:52:21 +0100 Subject: [PATCH 05/25] Pinned django-csp's version used for our tests (#2084) The issue has been reported upstream, for now we just want passing tests. Refs #2082. --- requirements_dev.txt | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements_dev.txt b/requirements_dev.txt index d28391b7c..941e74a81 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -11,7 +11,7 @@ html5lib selenium tox black -django-csp # Used in tests/test_csp_rendering +django-csp<4 # Used in tests/test_csp_rendering # Integration support diff --git a/tox.ini b/tox.ini index c8f4a6815..691ba2670 100644 --- a/tox.ini +++ b/tox.ini @@ -25,7 +25,7 @@ deps = pygments selenium>=4.8.0 sqlparse - django-csp + django-csp<4 passenv= CI COVERAGE_ARGS From 6a076d68fb183faae40b1c0ad21c42a3f7d7416f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 25 Feb 2025 09:15:00 +0100 Subject: [PATCH 06/25] [pre-commit.ci] pre-commit autoupdate (#2080) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/pre-commit/mirrors-eslint: v9.20.0 → v9.21.0](https://github.com/pre-commit/mirrors-eslint/compare/v9.20.0...v9.21.0) - [github.com/astral-sh/ruff-pre-commit: v0.9.6 → v0.9.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.6...v0.9.7) * Update the ESLint dependency --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Matthias Kestenholz --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5bdd1cfa7..65f7e2d11 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,19 +32,19 @@ repos: args: - --trailing-comma=es5 - repo: https://github.com/pre-commit/mirrors-eslint - rev: v9.20.0 + rev: v9.21.0 hooks: - id: eslint additional_dependencies: - - "eslint@v9.20.0" - - "@eslint/js@v9.20.0" + - "eslint@v9.21.0" + - "@eslint/js@v9.21.0" - "globals" files: \.js?$ types: [file] args: - --fix - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 'v0.9.6' + rev: 'v0.9.7' hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] From 44182935ac3064e3ec6dbdcf512c690f06a4ce67 Mon Sep 17 00:00:00 2001 From: Felipe Villegas Date: Tue, 25 Feb 2025 09:24:45 -0500 Subject: [PATCH 07/25] Add resources section to the documentation (#2081) This adds a new section to the documentation, "Resources", which provides a curated list of tutorials and talks. - Favor conference site links and DjangoTV over YouTube - Make the invitation to contribute more inviting - Remove the criteria for inclusion and contributing sections --- docs/changes.rst | 1 + docs/index.rst | 1 + docs/resources.rst | 78 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 docs/resources.rst diff --git a/docs/changes.rst b/docs/changes.rst index 811c60225..9e09fd511 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -6,6 +6,7 @@ Pending * Added Django 5.2 to the tox matrix. * Updated package metadata to include well-known labels. +* Added resources section to the documentation. 5.0.1 (2025-01-13) ------------------ diff --git a/docs/index.rst b/docs/index.rst index e72037045..48c217b1a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,6 +10,7 @@ Django Debug Toolbar tips panels commands + resources changes contributing architecture diff --git a/docs/resources.rst b/docs/resources.rst new file mode 100644 index 000000000..cbb50a7c3 --- /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 Berderbos** 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. From b1e7a8c0f221d90143010a4463c5ffbe1d9d74e7 Mon Sep 17 00:00:00 2001 From: Tim Schilling Date: Tue, 25 Feb 2025 08:44:59 -0600 Subject: [PATCH 08/25] Make show toolbar callback function async/sync compatible. (#2066) This checks if the SHOW_TOOLBAR_CALLBACK is a coroutine if we're in async mode and the reverse if it's not. It will automatically wrap the function with sync_to_async or async_to_sync when necessary. * ASGI check approach with added test and docs * async compatible require_toolbar and tests * add docs for async require_toolbar --------- Co-authored-by: Aman Pandey --- debug_toolbar/decorators.py | 30 +++++++-- debug_toolbar/middleware.py | 39 +++++++++-- docs/changes.rst | 3 + docs/panels.rst | 2 +- tests/test_decorators.py | 46 ++++++++++++- tests/test_middleware.py | 93 ++++++++++++++++++++++++++ tests/test_middleware_compatibility.py | 46 ------------- 7 files changed, 197 insertions(+), 62 deletions(-) create mode 100644 tests/test_middleware.py delete mode 100644 tests/test_middleware_compatibility.py diff --git a/debug_toolbar/decorators.py b/debug_toolbar/decorators.py index 787282706..61e46490d 100644 --- a/debug_toolbar/decorators.py +++ b/debug_toolbar/decorators.py @@ -1,5 +1,6 @@ import functools +from asgiref.sync import iscoroutinefunction from django.http import Http404 from django.utils.translation import get_language, override as language_override @@ -7,15 +8,30 @@ 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): - show_toolbar = get_show_toolbar() - if not show_toolbar(request): - raise Http404 + @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 view(request, *args, **kwargs) + 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 + + return view(request, *args, **kwargs) return inner diff --git a/debug_toolbar/middleware.py b/debug_toolbar/middleware.py index 9986d9106..598ff3eef 100644 --- a/debug_toolbar/middleware.py +++ b/debug_toolbar/middleware.py @@ -6,7 +6,12 @@ import socket from functools import cache -from asgiref.sync import iscoroutinefunction, markcoroutinefunction +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 @@ -47,7 +52,12 @@ def show_toolbar(request): @cache -def get_show_toolbar(): +def show_toolbar_func_or_path(): + """ + Fetch the show toolbar callback from settings + + 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"] @@ -57,6 +67,23 @@ 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 @@ -82,7 +109,8 @@ def __call__(self, request): if self.async_mode: return self.__acall__(request) # Decide whether the toolbar is active for this request. - show_toolbar = get_show_toolbar() + 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) @@ -103,8 +131,9 @@ def __call__(self, request): async def __acall__(self, request): # Decide whether the toolbar is active for this request. - show_toolbar = get_show_toolbar() - if not show_toolbar(request) or DebugToolbar.is_toolbar_request(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 diff --git a/docs/changes.rst b/docs/changes.rst index 9e09fd511..341f6e0b8 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -7,6 +7,9 @@ Pending * 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. 5.0.1 (2025-01-13) ------------------ diff --git a/docs/panels.rst b/docs/panels.rst index 7892dcf94..be481fb6e 100644 --- a/docs/panels.rst +++ b/docs/panels.rst @@ -321,7 +321,7 @@ 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. + 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 diff --git a/tests/test_decorators.py b/tests/test_decorators.py index 5e7c8523b..9840a6390 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -1,10 +1,10 @@ from unittest.mock import patch -from django.http import HttpResponse -from django.test import RequestFactory, TestCase +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 +from debug_toolbar.decorators import render_with_toolbar_language, require_show_toolbar @render_with_toolbar_language @@ -12,6 +12,46 @@ 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"}) diff --git a/tests/test_middleware.py b/tests/test_middleware.py new file mode 100644 index 000000000..56081ce56 --- /dev/null +++ b/tests/test_middleware.py @@ -0,0 +1,93 @@ +import asyncio +from unittest.mock import patch + +from django.contrib.auth.models import User +from django.http import HttpResponse +from django.test import AsyncRequestFactory, RequestFactory, TestCase, override_settings + +from debug_toolbar.middleware import DebugToolbarMiddleware + + +def show_toolbar_if_staff(request): + # Hit the database, but always return True + return User.objects.exists() or True + + +async def ashow_toolbar_if_staff(request): + # Hit the database, but always return True + has_users = await User.objects.afirst() + return has_users or True + + +class MiddlewareSyncAsyncCompatibilityTestCase(TestCase): + def setUp(self): + self.factory = RequestFactory() + self.async_factory = AsyncRequestFactory() + + @override_settings(DEBUG=True) + def test_sync_mode(self): + """ + test middleware switches to sync (__call__) based on get_response type + """ + + request = self.factory.get("/") + middleware = DebugToolbarMiddleware( + lambda x: HttpResponse("Test app") + ) + + self.assertFalse(asyncio.iscoroutinefunction(middleware)) + + response = middleware(request) + self.assertEqual(response.status_code, 200) + self.assertIn(b"djdt", response.content) + + @override_settings(DEBUG=True) + async def test_async_mode(self): + """ + test middleware switches to async (__acall__) based on get_response type + and returns a coroutine + """ + + async def get_response(request): + return HttpResponse("Test app") + + middleware = DebugToolbarMiddleware(get_response) + request = self.async_factory.get("/") + + self.assertTrue(asyncio.iscoroutinefunction(middleware)) + + response = await middleware(request) + self.assertEqual(response.status_code, 200) + self.assertIn(b"djdt", response.content) + + @override_settings(DEBUG=True) + @patch( + "debug_toolbar.middleware.show_toolbar_func_or_path", + return_value=ashow_toolbar_if_staff, + ) + def test_async_show_toolbar_callback_sync_middleware(self, mocked_show): + def get_response(request): + return HttpResponse("Hello world") + + middleware = DebugToolbarMiddleware(get_response) + + request = self.factory.get("/") + response = middleware(request) + self.assertEqual(response.status_code, 200) + self.assertIn(b"djdt", response.content) + + @override_settings(DEBUG=True) + @patch( + "debug_toolbar.middleware.show_toolbar_func_or_path", + return_value=show_toolbar_if_staff, + ) + async def test_sync_show_toolbar_callback_async_middleware(self, mocked_show): + async def get_response(request): + return HttpResponse("Hello world") + + middleware = DebugToolbarMiddleware(get_response) + + request = self.async_factory.get("/") + response = await middleware(request) + self.assertEqual(response.status_code, 200) + self.assertIn(b"djdt", response.content) diff --git a/tests/test_middleware_compatibility.py b/tests/test_middleware_compatibility.py deleted file mode 100644 index 1337864b1..000000000 --- a/tests/test_middleware_compatibility.py +++ /dev/null @@ -1,46 +0,0 @@ -import asyncio - -from django.http import HttpResponse -from django.test import AsyncRequestFactory, RequestFactory, TestCase, override_settings - -from debug_toolbar.middleware import DebugToolbarMiddleware - - -class MiddlewareSyncAsyncCompatibilityTestCase(TestCase): - def setUp(self): - self.factory = RequestFactory() - self.async_factory = AsyncRequestFactory() - - @override_settings(DEBUG=True) - def test_sync_mode(self): - """ - test middleware switches to sync (__call__) based on get_response type - """ - - request = self.factory.get("/") - middleware = DebugToolbarMiddleware( - lambda x: HttpResponse("Django debug toolbar") - ) - - self.assertFalse(asyncio.iscoroutinefunction(middleware)) - - response = middleware(request) - self.assertEqual(response.status_code, 200) - - @override_settings(DEBUG=True) - async def test_async_mode(self): - """ - test middleware switches to async (__acall__) based on get_response type - and returns a coroutine - """ - - async def get_response(request): - return HttpResponse("Django debug toolbar") - - middleware = DebugToolbarMiddleware(get_response) - request = self.async_factory.get("/") - - self.assertTrue(asyncio.iscoroutinefunction(middleware)) - - response = await middleware(request) - self.assertEqual(response.status_code, 200) From d1362f5cb38971e17d89634958d0a1638cfaa291 Mon Sep 17 00:00:00 2001 From: Felipe Villegas Date: Tue, 25 Feb 2025 15:43:26 -0500 Subject: [PATCH 09/25] Fix typo in resources.rst (#2085) --- docs/resources.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/resources.rst b/docs/resources.rst index cbb50a7c3..d5974badb 100644 --- a/docs/resources.rst +++ b/docs/resources.rst @@ -23,7 +23,7 @@ along independently using the slides and GitHub repository. Mastering Django Debug Toolbar: Efficient Debugging and Optimization Techniques ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -This tutorial by **Bob Berderbos** provides an in-depth look at effectively +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. From 8a66da120db929a0301e744858c96df765634303 Mon Sep 17 00:00:00 2001 From: Luna <60090391+blingblin-g@users.noreply.github.com> Date: Thu, 27 Feb 2025 03:52:16 +0900 Subject: [PATCH 10/25] Add link to contributing documentation in CONTRIBUTING.md (#2086) --- CONTRIBUTING.md | 12 +++++++++--- docs/changes.rst | 1 + 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 470c5ccdf..efc91ec2a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,11 @@ +# Contributing to Django Debug Toolbar + 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 -[README](https://github.com/django-commons/membership/blob/main/README.md) -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/docs/changes.rst b/docs/changes.rst index 341f6e0b8..f982350c4 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -10,6 +10,7 @@ Pending * 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``. 5.0.1 (2025-01-13) ------------------ From f33c41615b6ed283b15d79b6d7a0d2b18c987ece Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Sat, 1 Mar 2025 07:21:09 +0100 Subject: [PATCH 11/25] Pull translations from transifex (#2089) --- debug_toolbar/locale/ru/LC_MESSAGES/django.mo | Bin 11468 -> 14277 bytes debug_toolbar/locale/ru/LC_MESSAGES/django.po | 55 +++++++++--------- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/debug_toolbar/locale/ru/LC_MESSAGES/django.mo b/debug_toolbar/locale/ru/LC_MESSAGES/django.mo index a1d9dca2b39d63d4af3bfd420258347722a9f42f..b388ed98d787415f8bcb4624f72efeeef2120fdb 100644 GIT binary patch literal 14277 zcmds-36LDsdB>l*B?HFXjstGX9J`=h?Mj@BRv;vSWJ_Yvu4IT~i{6>u-G|V|N8ap z%xZ-QhwVz2-v0aD-}~-t z=6j&V{S9~u_yh36;6H;Gf&T%X0bal#?b8lw{8g?#7hFz#A*lIJfI7!lz!vcL!H=*@!(HDwlMz+YTk(q5`CwGlEe968~6!O`z`~;pB13?{{q+v z-UY%c^9(3He+|?+W3K&GQ1f4N?cW8(&%bl+Z-Aob$FBchK=I`*Q0F-A?3C`0f?9V5 zsQqSwT4ye(b1Zc2t3aJ+0Mz^vsB?b_)IQrl$>|AD=hy)*1;6g<|K;$MbJG5EK_F0&o6=F;(z% z&zOGz?+2w9;U`nP7lcLT5m)~zcs=#KpzQSDT>qH}Np!Y?8hPYQ2|1{+ZYKBYwOE>U<|(me$V#d#GOsYW)(Zb=QNaka-Gx0DK8#3v&(355LEoW z2UPri5eKPJK#C>H8^w8d(~{#0&fE~{}qSd26eu_ z1g`~u1V-RRb5eSr0=3V6@M`dn!G8kH6)9ie0A-JFf;!JhSEl1m%E^>FD0fm$q-4*hc|mQ=b*`ZTuA}5#T@X@GS#t~JHp)s0BDOZ6 z^H^SidSIQofr6=-2Pu+|9?4mc^#4ngFH-KMtf9!p&Y|2&kv?BW>7``PW?nu=xsl?# zM(LMiB6-SI#pnAddNxoNP$CL0-R3}m_38J6+bMG>AE&IP=;@(+hB88tF6gl~07~XX zHMr+7u%Ck3Tm6=eoK3l#B0J2U>AXk}E}=*-S5Q3407cJ6N|}OM*nAjuws{g@bL_?7 zGRi{A!;~eI>^axHNMGkuuBRMFnN4|wqUTpqu(_pPw^FuHZc>HkcFJl>P8FVcDb)YU zMyGl{Pvgy#Iv%BdyldO!a49G|{w(EJDHl;>FWV@?lrt&QD0*W3xws|g=Z1pSIloYd zTJxUJ3ya>xEpa#;w6@P47>=eK6iqd%R4vA>kr#!-VZpC>cUOZ-7)0$XP^=Eqp@xyB z8r^AqbtT9J;hLZ#DzbO4G~n5}tD|tR=vxZi+uO{F-rY3K>V$S}bgb1UjjKjMp5qOK zg}~Fho8HhIW$k5uq)_tnt8-!%>i9pSTqJh1Mfl(k&XBt4B;-lZ)0Ovo)mx()TMPGrfyjLf04;>&>R6TnPrk zb@Zk$tx>CKsTS`lmev+q&D>(iTN4Ir=b5?HqQAyRf%*%K`juaWmu<)pOY3$Y6?D@r!;Zh}N_j(F}9|ahol$03(69xnNWt4Gcq}?vp&ls~X z=&ufXeWg;N->;a3VI+asjfx^&Q=)24nBc;Mm zEGqbA%*R&EqTzBp;zdDh78T#w(u*n;IJIb9kgIC>-dmTNn@W{oF9$dMu;>glES9VB zOs~Hhd-)P3QY^*ZkPnGEtslpgkgmWBin(~C9CXhZt`_33>{sGhFmbpo4?AbHrwhaQ zAur5(GwxN_>M;Ms86U`IH!te*w69ql_<3BUSsdmA`^SqN{}+c*T*A37$>UF!6urRm zf|t1B7b8Elq9r=I7Zd{73nCc^DjX9Yh2#nPovdRLYxWVWAM>z)IK;o|~h1 z_>OQeT{OZQ#a_$hg`hYX54D=*KGv|jG9(s(-Txo zPo+dikgvMc2Fn#loMq+I&}n0<)na3V3!8M619U`2SYZ-%YjGKRtNco+-l*O(s`pQHMHz8yWY<<4 zz#ZY=NTy65Y+iV$UmPrXO@Rhit*pj56HL>uI~0VR`tbe|-hAbXrE*$c-i9fq7P8=S zwNfr&knP^>r7FR@=;eq;cQwqz_NdB~izrq#NZq2Oo;fZL3W_*PztC=Mgw-FfMahHS zSO7lRd}AV2wkz_xqT&zNfQ{;H{sts zJYHOl;5LEFhyg{pyp?H=WmYN?s*7}$U#JEqa|&h^VMRXHVPsapF>e4@V_mPUg+)aH z6q#|2_F@$4=lcEq%Cq7EF|!`z>;BZoof5R*DF+gN1M)1d1Yav%;{FIu#l~%rXoX#g z*b5NSdY&7CD0<{6B-P|Zu+O+@uJf0htof;xFN_BHO9 zM!s?>7Rh95BVIlnuv>iw47d z+l|%1sI9Nm%NYRT>iC!XYbbgi% zAlG@hA-{sZ>|WV-Q`KxkpX~1J^se_hJcd)B*F8Jyn%jMaUhRyIxt+7U z`|eAp%!zE@AKb}! z@=|SmGFp2m*^?Yj#*#f=vM(9MN6Xlc@&P`1Qf=G2n3U{)A1CRH1OJ`8oQ&5tL*CwG zthU*%pS;ZR#%mjB+&OU*pfhGY@Fcw+XlzVIr*3AIV{|m#B#5BoU~N~CSMa} zTOp03by-6?YG-Or$LLnJK2pDgYP8bKgEZbodC8&L1`mpkJ}u=;>gJA^`g5Aua3`h% zKt;oySYu1WotSHGxD#_N?)uBRltbv%dh6T|*S6L+H(rk=J8N5$!)V^#WcS1f(_%g2 z9)vrV9DtWiH)gE4hxUERPS)LkPdl89bA6`f+Q#e(%?{cvw~I<|Ldh94&m_Mt!?Kfh zD$-2R@T@fmE$!kF=8!yN4aQPf+v*`aIn_yH9^;I=*oxC~qK&n!jkj^h1863b_@lu_zH~_a72zaLr=wy2ZdG?s-GdU2 zSGX2bTXq$!ScU&#z34kTk}E7go&Y)(L@=b#LR{Ro2x zbs*BDN3r{!s;8RCSEaSG6Xi4GFhRmmwz|>Ff@w;V6*3aB9!;**9gAh* z(?%9J#tm`PR$3@^pc~oCI(DsRiNS1}Zje7{!nprTnF9Y2s9d?#ksVn1dO{T1A%{?} zG#G+OG);(H0Ai*gH{5DmwPazMB zQdE{MZ%a>!1C{DcaM`N%Q)!5_^PKUD)=`#fj-Sa_;D7==F6%Lj6AEe@SX0f~e>XzX zL1kz5!^KvhM&B}{DG6{kO~+a;cTNSb1#_*!r>;Wwr)_Evx2BO^nMGZg&uBAyW9~$3 za~KU&yS>M5?%1A;Hw1Gf3-5vU__x>z-sE3U39=uI^*j4o~$x zozQq_F&X+KEPY6#-FA$<OvQELeSI*TS0gbifkdq$k z#{e6Fn!a|$@20O?)@c5jpxHe(>?X5MBbPN(u}e`#BPLW@0&NzcNq)<+ViUe(gN~2f zrMotGq@kL~XO$UtFh<9na#<;ivP^ufehsj{2rNo}d3al%Nh374Nh_4NVxJB9pxwDC z=fR!5n`z1ZpYb%SJE>V^Nz0LRH~(gm?65*X738zY6p+BEJduQB?cVwTxwHLgG$843 zZgTvKtV|VIC3orbr1-GIIv|5RK{8O41Dc#Cg-0*W~tC{i_ zP~~(kG-vs#EdGcb)#ig_mRasN)5Fa=zs-jDG~AP}pdfw^pRZ&jB#WuzK%H@ZQUN|_ zQzuVF){tiB2Wp#G?m>p>B+O$OeM%xgeayP3VABViLhWX%1cGCg#~CL_W(R%u>F795 z_bIOERDXp}+{B?puEqK@$k1o2t}5*Jv|W|&IjNVWg@x_imP_F|ae7T4*PMPF+m}Pq zB;A5E+!*NNMkm^p?Fc#PFR%5VP3a-<*u>MG+1{-%#oY+$)=W_YC!F49)mI#!_{p}> zDyIBCLVup;5HH)`aP5CvQA$Zm)2-j6A9Tsg#GC9sWRK!>Q~oB#!AmuA2Ac@fD`c(q zC*Ioq=*Z#pJB`t5vp@At5vL6fuj%*IpXHCfx~#D70^_p^x;6XR6(2Jp@omi%e$c-F O%L3dN1ED{o=zjqkQ&7SH delta 3006 zcmYk;drXye9LMqB;UX%a2#SK12asFb6vD_0N`{n)7bMLL!~~T9Q3(|-54L4)Z6rOZ zo6XEQ&DFB4;{`1*WVxJ955uID+0NvsHQ*j3M9Mt_AkXy_f zn1)+X1D-%l{2Zp@H>iI8({;u-!2<&wM4^8m)C?0)9i-TL9%`Th|6#tBxXs=bv zRXx%gkLouS)nATnFF<9W*j}GLi2N_3p`3ZA?CU&jb$+bUYN*-w4$G$*7EFp|)hayOiOcTf|F zB%i7eM@=Z#It9t1DMw{uDQcpta3p$ATel0f*I(ejcox;qX})62*<8Z@djEgtL>=5k zr9R|gV}@W1>eHBx8F&(v(p#vl=tWKB9v;AGM%R6vs0H<45%yvN=DP#V!%5Uz(Et7) z)d>w}P)~U`s^cD1`)^2g&0W+A2GN_^6HzP4!DK8!H#VT|Z^Lxljmp$H)Wk2_`gQco zqTvrt)NvtsQ-?EAZ^KgDgsrH|geCDUz*r7S?KsraT!eb)rlNi~6UnxzL``(PZQqO< zXFKv|_9u~ly+-G$>4#U4xtN=_9zH7Y$sC4c(M-l*tU|4%8r`@8m4S9tKS!)zq6X|j zW#l@t%jR!;Jto-`n6aC?uW%s;U&n|PzHTmK-o}&EFQ*13wk?gUQ13tuS6mH z_5R;RcFBZh2DT;*wIw;on`;VCZ&fL(-vzeaQ_o2R4I8a(s1D!75c~x7OdLcK;vZZq z&LgxX;e;~6j+s@2eygH1t0*h#M~hoR+%H9elm2EpCyR)=&h79R&wS3-69vR#B9zc{ zRM-Q5X|yUYDkTBT0-Q{|YU_e`(_czC>XBASAe3Vs0>f9zuowQq26@c$oU&AZ$>;bq zv4)uHZwVYWz>CB>f(L_fcXT@d`1E@IP)?NK_CHmN8VGQv>6hg*u_+ z5HI`BjCldeh*n|>F`iHTJeJN~lV`E)yO_S3Zljz)vS>#j>E_dz?&i57%Np^Wx##XtUt3%H^ zpU1Tn`nLE!^BwXX>)OcisN2`>JJHqZ>!5nf*Wo+vp3v01JbQIxZS~}(4U1~)vlllu z6lS_}M~}(N9^no4)+O|Dc|T0N7VI2M*%@3?T<)}I#yE9p>CT?CvEG*S uQ?AIJzC&HFx$n=dJtNP#n6b~@>b, 2014 # Ilya Baryshev , 2013 # Mikhail Korobov, 2009 -# Алексей Борискин , 2013,2015 +# Алексей Борискин , 2013,2015,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: Алексей Борискин , 2013,2015\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" @@ -30,24 +31,24 @@ msgstr "Панель отладки" msgid "" "Form with id \"{form_id}\" contains file input, but does not have the " "attribute enctype=\"multipart/form-data\"." -msgstr "" +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 "" +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 "" +msgstr "Элемент ввода ссылается на форму с id \"{form_id}\", но форма не имеет атрибута enctype=\"multipart/form-data\"." #: panels/alerts.py:77 msgid "Alerts" -msgstr "" +msgstr "Оповещения" #: panels/cache.py:168 msgid "Cache" @@ -77,7 +78,7 @@ msgstr "Заголовки" #: panels/history/panel.py:19 panels/history/panel.py:20 msgid "History" -msgstr "" +msgstr "История" #: panels/profiling.py:140 msgid "Profiling" @@ -106,7 +107,7 @@ msgstr "Настройки" #: panels/settings.py:20 #, python-format msgid "Settings from %s" -msgstr "" +msgstr "Настройки из %s" #: panels/signals.py:57 #, python-format @@ -178,19 +179,19 @@ msgstr "SQL" #, 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] "" +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] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" +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 @@ -221,7 +222,7 @@ msgstr "Шаблоны (обработано %(num_templates)s)" #: panels/templates/panel.py:195 msgid "No origin" -msgstr "" +msgstr "Без происхождения" #: panels/timer.py:27 #, python-format @@ -299,7 +300,7 @@ msgstr "Скрыть" #: templates/debug_toolbar/base.html:25 templates/debug_toolbar/base.html:26 msgid "Toggle Theme" -msgstr "" +msgstr "Переключатель темы" #: templates/debug_toolbar/base.html:35 msgid "Show toolbar" @@ -315,11 +316,11 @@ msgstr "Включить для последующих запросов" #: templates/debug_toolbar/panels/alerts.html:4 msgid "Alerts found" -msgstr "" +msgstr "Найдены оповещения" #: templates/debug_toolbar/panels/alerts.html:11 msgid "No alerts found" -msgstr "" +msgstr "Оповещения не найдены" #: templates/debug_toolbar/panels/cache.html:2 msgid "Summary" @@ -417,11 +418,11 @@ msgstr "Путь" #: templates/debug_toolbar/panels/history.html:12 msgid "Request Variables" -msgstr "" +msgstr "Запрос переменных" #: templates/debug_toolbar/panels/history.html:13 msgid "Status" -msgstr "" +msgstr "Статус" #: templates/debug_toolbar/panels/history.html:14 #: templates/debug_toolbar/panels/sql.html:37 @@ -524,14 +525,14 @@ msgstr[3] "%(num)s запросов" msgid "" "including %(count)s similar" -msgstr "" +msgstr "включая %(count)s похожий" #: templates/debug_toolbar/panels/sql.html:12 #, python-format msgid "" "and %(dupes)s duplicates" -msgstr "" +msgstr "и %(dupes)s дубликаты" #: templates/debug_toolbar/panels/sql.html:34 msgid "Query" @@ -545,12 +546,12 @@ msgstr "Временная диаграмма" #: templates/debug_toolbar/panels/sql.html:52 #, python-format msgid "%(count)s similar queries." -msgstr "" +msgstr "%(count)s похожих запросов." #: templates/debug_toolbar/panels/sql.html:58 #, python-format msgid "Duplicated %(dupes)s times." -msgstr "" +msgstr "Дублируется %(dupes)s раз." #: templates/debug_toolbar/panels/sql.html:95 msgid "Connection:" @@ -710,7 +711,7 @@ msgstr "С начала навигации в мс (+продолжительн #: templates/debug_toolbar/panels/versions.html:10 msgid "Package" -msgstr "" +msgstr "Пакет" #: templates/debug_toolbar/panels/versions.html:11 msgid "Name" From ec4837c07a8f1937dabf98cb769526e3625f2a11 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Sat, 1 Mar 2025 15:35:41 +0100 Subject: [PATCH 12/25] Replace ESLint and prettier with biome (#2090) biome is faster and nicer to configure with pre-commit. Closes #2065. --- .pre-commit-config.yaml | 24 +- biome.json | 36 ++ .../static/debug_toolbar/css/toolbar.css | 340 ++++++++++++++---- .../static/debug_toolbar/js/utils.js | 12 +- docs/changes.rst | 1 + eslint.config.js | 28 -- 6 files changed, 310 insertions(+), 131 deletions(-) create mode 100644 biome.json delete mode 100644 eslint.config.js diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 65f7e2d11..0811bb10b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,26 +23,10 @@ repos: hooks: - id: rst-backticks - id: rst-directive-colons -- repo: https://github.com/pre-commit/mirrors-prettier - rev: v4.0.0-alpha.8 - hooks: - - id: prettier - entry: env PRETTIER_LEGACY_CLI=1 prettier - types_or: [javascript, css] - args: - - --trailing-comma=es5 -- repo: https://github.com/pre-commit/mirrors-eslint - rev: v9.21.0 - hooks: - - id: eslint - additional_dependencies: - - "eslint@v9.21.0" - - "@eslint/js@v9.21.0" - - "globals" - files: \.js?$ - types: [file] - args: - - --fix +- repo: https://github.com/biomejs/pre-commit + rev: v1.9.4 + hooks: + - id: biome-check - repo: https://github.com/astral-sh/ruff-pre-commit rev: 'v0.9.7' hooks: diff --git a/biome.json b/biome.json new file mode 100644 index 000000000..139210ab2 --- /dev/null +++ b/biome.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "formatter": { + "enabled": true, + "useEditorconfig": true + }, + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "complexity": { + "useArrowFunction": "off", + "noForEach": "off" + }, + "style": { + "noArguments": "off", + "noParameterAssign": "off", + "noUselessElse": "off", + "useSingleVarDeclarator": "off", + "useTemplate": "off" + }, + "suspicious": { + "noAssignInExpressions": "off" + } + } + }, + "javascript": { + "formatter": { + "trailingCommas": "es5", + "quoteStyle": "double" + } + } +} diff --git a/debug_toolbar/static/debug_toolbar/css/toolbar.css b/debug_toolbar/static/debug_toolbar/css/toolbar.css index a8699a492..47f4abb2d 100644 --- a/debug_toolbar/static/debug_toolbar/css/toolbar.css +++ b/debug_toolbar/static/debug_toolbar/css/toolbar.css @@ -4,7 +4,8 @@ --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", + --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", @@ -190,9 +191,7 @@ #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 { @@ -570,80 +569,265 @@ To regenerate: from pygments.formatters import HtmlFormatter print(HtmlFormatter(wrapcode=True).get_style_defs()) */ -#djDebug .highlight pre { line-height: 125%; } -#djDebug .highlight td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } -#djDebug .highlight span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } -#djDebug .highlight td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } -#djDebug .highlight span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } -#djDebug .highlight .hll { background-color: #ffffcc } -#djDebug .highlight .c { color: #3D7B7B; font-style: italic } /* Comment */ -#djDebug .highlight .err { border: 1px solid #FF0000 } /* Error */ -#djDebug .highlight .k { color: #008000; font-weight: bold } /* Keyword */ -#djDebug .highlight .o { color: #666666 } /* Operator */ -#djDebug .highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ -#djDebug .highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ -#djDebug .highlight .cp { color: #9C6500 } /* Comment.Preproc */ -#djDebug .highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ -#djDebug .highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ -#djDebug .highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ -#djDebug .highlight .gd { color: #A00000 } /* Generic.Deleted */ -#djDebug .highlight .ge { font-style: italic } /* Generic.Emph */ -#djDebug .highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ -#djDebug .highlight .gr { color: #E40000 } /* Generic.Error */ -#djDebug .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ -#djDebug .highlight .gi { color: #008400 } /* Generic.Inserted */ -#djDebug .highlight .go { color: #717171 } /* Generic.Output */ -#djDebug .highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ -#djDebug .highlight .gs { font-weight: bold } /* Generic.Strong */ -#djDebug .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ -#djDebug .highlight .gt { color: #0044DD } /* Generic.Traceback */ -#djDebug .highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ -#djDebug .highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ -#djDebug .highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ -#djDebug .highlight .kp { color: #008000 } /* Keyword.Pseudo */ -#djDebug .highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ -#djDebug .highlight .kt { color: #B00040 } /* Keyword.Type */ -#djDebug .highlight .m { color: #666666 } /* Literal.Number */ -#djDebug .highlight .s { color: #BA2121 } /* Literal.String */ -#djDebug .highlight .na { color: #687822 } /* Name.Attribute */ -#djDebug .highlight .nb { color: #008000 } /* Name.Builtin */ -#djDebug .highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */ -#djDebug .highlight .no { color: #880000 } /* Name.Constant */ -#djDebug .highlight .nd { color: #AA22FF } /* Name.Decorator */ -#djDebug .highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */ -#djDebug .highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ -#djDebug .highlight .nf { color: #0000FF } /* Name.Function */ -#djDebug .highlight .nl { color: #767600 } /* Name.Label */ -#djDebug .highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ -#djDebug .highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ -#djDebug .highlight .nv { color: #19177C } /* Name.Variable */ -#djDebug .highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ -#djDebug .highlight .w { color: #bbbbbb; white-space: pre-wrap } /* Text.Whitespace */ -#djDebug .highlight .mb { color: #666666 } /* Literal.Number.Bin */ -#djDebug .highlight .mf { color: #666666 } /* Literal.Number.Float */ -#djDebug .highlight .mh { color: #666666 } /* Literal.Number.Hex */ -#djDebug .highlight .mi { color: #666666 } /* Literal.Number.Integer */ -#djDebug .highlight .mo { color: #666666 } /* Literal.Number.Oct */ -#djDebug .highlight .sa { color: #BA2121 } /* Literal.String.Affix */ -#djDebug .highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ -#djDebug .highlight .sc { color: #BA2121 } /* Literal.String.Char */ -#djDebug .highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ -#djDebug .highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ -#djDebug .highlight .s2 { color: #BA2121 } /* Literal.String.Double */ -#djDebug .highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ -#djDebug .highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ -#djDebug .highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ -#djDebug .highlight .sx { color: #008000 } /* Literal.String.Other */ -#djDebug .highlight .sr { color: #A45A77 } /* Literal.String.Regex */ -#djDebug .highlight .s1 { color: #BA2121 } /* Literal.String.Single */ -#djDebug .highlight .ss { color: #19177C } /* Literal.String.Symbol */ -#djDebug .highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ -#djDebug .highlight .fm { color: #0000FF } /* Name.Function.Magic */ -#djDebug .highlight .vc { color: #19177C } /* Name.Variable.Class */ -#djDebug .highlight .vg { color: #19177C } /* Name.Variable.Global */ -#djDebug .highlight .vi { color: #19177C } /* Name.Variable.Instance */ -#djDebug .highlight .vm { color: #19177C } /* Name.Variable.Magic */ -#djDebug .highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ +#djDebug .highlight pre { + line-height: 125%; +} +#djDebug .highlight td.linenos .normal { + color: inherit; + background-color: transparent; + padding-left: 5px; + padding-right: 5px; +} +#djDebug .highlight span.linenos { + color: inherit; + background-color: transparent; + padding-left: 5px; + padding-right: 5px; +} +#djDebug .highlight td.linenos .special { + color: #000000; + background-color: #ffffc0; + padding-left: 5px; + padding-right: 5px; +} +#djDebug .highlight span.linenos.special { + color: #000000; + background-color: #ffffc0; + padding-left: 5px; + padding-right: 5px; +} +#djDebug .highlight .hll { + background-color: #ffffcc; +} +#djDebug .highlight .c { + color: #3d7b7b; + font-style: italic; +} /* Comment */ +#djDebug .highlight .err { + border: 1px solid #ff0000; +} /* Error */ +#djDebug .highlight .k { + color: #008000; + font-weight: bold; +} /* Keyword */ +#djDebug .highlight .o { + color: #666666; +} /* Operator */ +#djDebug .highlight .ch { + color: #3d7b7b; + font-style: italic; +} /* Comment.Hashbang */ +#djDebug .highlight .cm { + color: #3d7b7b; + font-style: italic; +} /* Comment.Multiline */ +#djDebug .highlight .cp { + color: #9c6500; +} /* Comment.Preproc */ +#djDebug .highlight .cpf { + color: #3d7b7b; + font-style: italic; +} /* Comment.PreprocFile */ +#djDebug .highlight .c1 { + color: #3d7b7b; + font-style: italic; +} /* Comment.Single */ +#djDebug .highlight .cs { + color: #3d7b7b; + font-style: italic; +} /* Comment.Special */ +#djDebug .highlight .gd { + color: #a00000; +} /* Generic.Deleted */ +#djDebug .highlight .ge { + font-style: italic; +} /* Generic.Emph */ +#djDebug .highlight .ges { + font-weight: bold; + font-style: italic; +} /* Generic.EmphStrong */ +#djDebug .highlight .gr { + color: #e40000; +} /* Generic.Error */ +#djDebug .highlight .gh { + color: #000080; + font-weight: bold; +} /* Generic.Heading */ +#djDebug .highlight .gi { + color: #008400; +} /* Generic.Inserted */ +#djDebug .highlight .go { + color: #717171; +} /* Generic.Output */ +#djDebug .highlight .gp { + color: #000080; + font-weight: bold; +} /* Generic.Prompt */ +#djDebug .highlight .gs { + font-weight: bold; +} /* Generic.Strong */ +#djDebug .highlight .gu { + color: #800080; + font-weight: bold; +} /* Generic.Subheading */ +#djDebug .highlight .gt { + color: #0044dd; +} /* Generic.Traceback */ +#djDebug .highlight .kc { + color: #008000; + font-weight: bold; +} /* Keyword.Constant */ +#djDebug .highlight .kd { + color: #008000; + font-weight: bold; +} /* Keyword.Declaration */ +#djDebug .highlight .kn { + color: #008000; + font-weight: bold; +} /* Keyword.Namespace */ +#djDebug .highlight .kp { + color: #008000; +} /* Keyword.Pseudo */ +#djDebug .highlight .kr { + color: #008000; + font-weight: bold; +} /* Keyword.Reserved */ +#djDebug .highlight .kt { + color: #b00040; +} /* Keyword.Type */ +#djDebug .highlight .m { + color: #666666; +} /* Literal.Number */ +#djDebug .highlight .s { + color: #ba2121; +} /* Literal.String */ +#djDebug .highlight .na { + color: #687822; +} /* Name.Attribute */ +#djDebug .highlight .nb { + color: #008000; +} /* Name.Builtin */ +#djDebug .highlight .nc { + color: #0000ff; + font-weight: bold; +} /* Name.Class */ +#djDebug .highlight .no { + color: #880000; +} /* Name.Constant */ +#djDebug .highlight .nd { + color: #aa22ff; +} /* Name.Decorator */ +#djDebug .highlight .ni { + color: #717171; + font-weight: bold; +} /* Name.Entity */ +#djDebug .highlight .ne { + color: #cb3f38; + font-weight: bold; +} /* Name.Exception */ +#djDebug .highlight .nf { + color: #0000ff; +} /* Name.Function */ +#djDebug .highlight .nl { + color: #767600; +} /* Name.Label */ +#djDebug .highlight .nn { + color: #0000ff; + font-weight: bold; +} /* Name.Namespace */ +#djDebug .highlight .nt { + color: #008000; + font-weight: bold; +} /* Name.Tag */ +#djDebug .highlight .nv { + color: #19177c; +} /* Name.Variable */ +#djDebug .highlight .ow { + color: #aa22ff; + font-weight: bold; +} /* Operator.Word */ +#djDebug .highlight .w { + color: #bbbbbb; + white-space: pre-wrap; +} /* Text.Whitespace */ +#djDebug .highlight .mb { + color: #666666; +} /* Literal.Number.Bin */ +#djDebug .highlight .mf { + color: #666666; +} /* Literal.Number.Float */ +#djDebug .highlight .mh { + color: #666666; +} /* Literal.Number.Hex */ +#djDebug .highlight .mi { + color: #666666; +} /* Literal.Number.Integer */ +#djDebug .highlight .mo { + color: #666666; +} /* Literal.Number.Oct */ +#djDebug .highlight .sa { + color: #ba2121; +} /* Literal.String.Affix */ +#djDebug .highlight .sb { + color: #ba2121; +} /* Literal.String.Backtick */ +#djDebug .highlight .sc { + color: #ba2121; +} /* Literal.String.Char */ +#djDebug .highlight .dl { + color: #ba2121; +} /* Literal.String.Delimiter */ +#djDebug .highlight .sd { + color: #ba2121; + font-style: italic; +} /* Literal.String.Doc */ +#djDebug .highlight .s2 { + color: #ba2121; +} /* Literal.String.Double */ +#djDebug .highlight .se { + color: #aa5d1f; + font-weight: bold; +} /* Literal.String.Escape */ +#djDebug .highlight .sh { + color: #ba2121; +} /* Literal.String.Heredoc */ +#djDebug .highlight .si { + color: #a45a77; + font-weight: bold; +} /* Literal.String.Interpol */ +#djDebug .highlight .sx { + color: #008000; +} /* Literal.String.Other */ +#djDebug .highlight .sr { + color: #a45a77; +} /* Literal.String.Regex */ +#djDebug .highlight .s1 { + color: #ba2121; +} /* Literal.String.Single */ +#djDebug .highlight .ss { + color: #19177c; +} /* Literal.String.Symbol */ +#djDebug .highlight .bp { + color: #008000; +} /* Name.Builtin.Pseudo */ +#djDebug .highlight .fm { + color: #0000ff; +} /* Name.Function.Magic */ +#djDebug .highlight .vc { + color: #19177c; +} /* Name.Variable.Class */ +#djDebug .highlight .vg { + color: #19177c; +} /* Name.Variable.Global */ +#djDebug .highlight .vi { + color: #19177c; +} /* Name.Variable.Instance */ +#djDebug .highlight .vm { + color: #19177c; +} /* Name.Variable.Magic */ +#djDebug .highlight .il { + color: #666666; +} /* Literal.Number.Integer.Long */ #djDebug svg.djDebugLineChart { width: 100%; diff --git a/debug_toolbar/static/debug_toolbar/js/utils.js b/debug_toolbar/static/debug_toolbar/js/utils.js index c37525f13..3cefe58d3 100644 --- a/debug_toolbar/static/debug_toolbar/js/utils.js +++ b/debug_toolbar/static/debug_toolbar/js/utils.js @@ -75,11 +75,13 @@ function ajax(url, init) { return fetch(url, init) .then(function (response) { if (response.ok) { - return response.json().catch(function(error){ - return Promise.reject( - new Error("The response is a invalid Json object : " + error) - ); - }); + return response.json().catch(function (error) { + return Promise.reject( + new Error( + "The response is a invalid Json object : " + error + ) + ); + }); } return Promise.reject( new Error(response.status + ": " + response.statusText) diff --git a/docs/changes.rst b/docs/changes.rst index f982350c4..b75e0daf7 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -11,6 +11,7 @@ Pending 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. 5.0.1 (2025-01-13) ------------------ diff --git a/eslint.config.js b/eslint.config.js deleted file mode 100644 index 0b4d0e49e..000000000 --- a/eslint.config.js +++ /dev/null @@ -1,28 +0,0 @@ -const js = require("@eslint/js"); -const globals = require("globals"); - -module.exports = [ - js.configs.recommended, - { - files: ["**/*.js"], - languageOptions:{ - ecmaVersion: "latest", - sourceType: "module", - globals: { - ...globals.browser, - ...globals.node - } - } - }, - { - rules: { - "curly": ["error", "all"], - "dot-notation": "error", - "eqeqeq": "error", - "no-eval": "error", - "no-var": "error", - "prefer-const": "error", - "semi": "error" - } - } -]; From 50c1314a6874f80ab97079da3169c9c1693ddac8 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Sat, 1 Mar 2025 15:43:33 +0100 Subject: [PATCH 13/25] Enable the useSingleVarDeclarator style rule --- .pre-commit-config.yaml | 1 + biome.json | 1 - debug_toolbar/static/debug_toolbar/js/timer.js | 6 +++--- .../static/debug_toolbar/js/toolbar.js | 17 +++++++++-------- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0811bb10b..e9317b643 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,6 +27,7 @@ repos: rev: v1.9.4 hooks: - id: biome-check + verbose: true - repo: https://github.com/astral-sh/ruff-pre-commit rev: 'v0.9.7' hooks: diff --git a/biome.json b/biome.json index 139210ab2..f531c3900 100644 --- a/biome.json +++ b/biome.json @@ -19,7 +19,6 @@ "noArguments": "off", "noParameterAssign": "off", "noUselessElse": "off", - "useSingleVarDeclarator": "off", "useTemplate": "off" }, "suspicious": { diff --git a/debug_toolbar/static/debug_toolbar/js/timer.js b/debug_toolbar/static/debug_toolbar/js/timer.js index a88ab0d15..a50ae081a 100644 --- a/debug_toolbar/static/debug_toolbar/js/timer.js +++ b/debug_toolbar/static/debug_toolbar/js/timer.js @@ -1,9 +1,9 @@ import { $$ } from "./utils.js"; function insertBrowserTiming() { - const timingOffset = performance.timing.navigationStart, - timingEnd = performance.timing.loadEventEnd, - totalTime = timingEnd - timingOffset; + const timingOffset = performance.timing.navigationStart; + const timingEnd = performance.timing.loadEventEnd; + const totalTime = timingEnd - timingOffset; function getLeft(stat) { if (totalTime !== 0) { return ( diff --git a/debug_toolbar/static/debug_toolbar/js/toolbar.js b/debug_toolbar/static/debug_toolbar/js/toolbar.js index 067b5a312..7268999a0 100644 --- a/debug_toolbar/static/debug_toolbar/js/toolbar.js +++ b/debug_toolbar/static/debug_toolbar/js/toolbar.js @@ -37,9 +37,9 @@ const djdt = { this.parentElement.classList.add("djdt-active"); const inner = current.querySelector( - ".djDebugPanelContent .djdt-scroll" - ), - storeId = djDebug.dataset.storeId; + ".djDebugPanelContent .djdt-scroll" + ); + const storeId = djDebug.dataset.storeId; if (storeId && inner.children.length === 0) { const url = new URL( djDebug.dataset.renderPanelUrl, @@ -157,7 +157,8 @@ const djdt = { djdt.showToolbar(); } }); - let startPageY, baseY; + 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 @@ -341,8 +342,8 @@ const djdt = { return null; } - const cookieArray = document.cookie.split("; "), - cookies = {}; + const cookieArray = document.cookie.split("; "); + const cookies = {}; cookieArray.forEach(function (e) { const parts = e.split("="); @@ -355,8 +356,8 @@ const djdt = { options = options || {}; if (typeof options.expires === "number") { - const days = options.expires, - t = (options.expires = new Date()); + const days = options.expires; + const t = (options.expires = new Date()); t.setDate(t.getDate() + days); } From 25eb3f9f70085ea108aa265d8320dff67a3d5576 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Sat, 1 Mar 2025 15:44:03 +0100 Subject: [PATCH 14/25] Enable the noUselessElse rule --- biome.json | 1 - debug_toolbar/static/debug_toolbar/js/timer.js | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/biome.json b/biome.json index f531c3900..cd2e303b4 100644 --- a/biome.json +++ b/biome.json @@ -18,7 +18,6 @@ "style": { "noArguments": "off", "noParameterAssign": "off", - "noUselessElse": "off", "useTemplate": "off" }, "suspicious": { diff --git a/debug_toolbar/static/debug_toolbar/js/timer.js b/debug_toolbar/static/debug_toolbar/js/timer.js index a50ae081a..3205f551a 100644 --- a/debug_toolbar/static/debug_toolbar/js/timer.js +++ b/debug_toolbar/static/debug_toolbar/js/timer.js @@ -9,9 +9,8 @@ function insertBrowserTiming() { return ( ((performance.timing[stat] - timingOffset) / totalTime) * 100.0 ); - } else { - return 0; } + return 0; } function getCSSWidth(stat, endStat) { let width = 0; From 964b6d8eb1ecf7c7bf5e48a40981a7caa6e09911 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Sat, 1 Mar 2025 15:48:09 +0100 Subject: [PATCH 15/25] Enable the noParameterAssign rule --- biome.json | 1 - debug_toolbar/static/debug_toolbar/js/toolbar.js | 10 ++++------ debug_toolbar/static/debug_toolbar/js/utils.js | 3 +-- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/biome.json b/biome.json index cd2e303b4..3c65447f8 100644 --- a/biome.json +++ b/biome.json @@ -17,7 +17,6 @@ }, "style": { "noArguments": "off", - "noParameterAssign": "off", "useTemplate": "off" }, "suspicious": { diff --git a/debug_toolbar/static/debug_toolbar/js/toolbar.js b/debug_toolbar/static/debug_toolbar/js/toolbar.js index 7268999a0..e3167462d 100644 --- a/debug_toolbar/static/debug_toolbar/js/toolbar.js +++ b/debug_toolbar/static/debug_toolbar/js/toolbar.js @@ -297,11 +297,11 @@ const djdt = { const slowjax = debounce(ajax, 200); function handleAjaxResponse(storeId) { - storeId = encodeURIComponent(storeId); - const dest = `${sidebarUrl}?store_id=${storeId}`; + const encodedStoreId = encodeURIComponent(storeId); + const dest = `${sidebarUrl}?store_id=${encodedStoreId}`; slowjax(dest).then(function (data) { if (djdt.needUpdateOnFetch) { - replaceToolbarState(storeId, data); + replaceToolbarState(encodedStoreId, data); } }); } @@ -352,9 +352,7 @@ const djdt = { return cookies[key]; }, - set(key, value, options) { - options = options || {}; - + set(key, value, options = {}) { if (typeof options.expires === "number") { const days = options.expires; const t = (options.expires = new Date()); diff --git a/debug_toolbar/static/debug_toolbar/js/utils.js b/debug_toolbar/static/debug_toolbar/js/utils.js index 3cefe58d3..b5f00e2ac 100644 --- a/debug_toolbar/static/debug_toolbar/js/utils.js +++ b/debug_toolbar/static/debug_toolbar/js/utils.js @@ -71,8 +71,7 @@ const $$ = { }; function ajax(url, init) { - init = Object.assign({ credentials: "same-origin" }, init); - return fetch(url, init) + return fetch(url, Object.assign({ credentials: "same-origin" }, init)) .then(function (response) { if (response.ok) { return response.json().catch(function (error) { From 301f2cd768e99ac9ff74ae238c392f28822208be Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Sat, 1 Mar 2025 15:50:04 +0100 Subject: [PATCH 16/25] Enable the noArguments rule --- biome.json | 1 - debug_toolbar/static/debug_toolbar/js/toolbar.js | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/biome.json b/biome.json index 3c65447f8..554a2f5f4 100644 --- a/biome.json +++ b/biome.json @@ -16,7 +16,6 @@ "noForEach": "off" }, "style": { - "noArguments": "off", "useTemplate": "off" }, "suspicious": { diff --git a/debug_toolbar/static/debug_toolbar/js/toolbar.js b/debug_toolbar/static/debug_toolbar/js/toolbar.js index e3167462d..ee47025a1 100644 --- a/debug_toolbar/static/debug_toolbar/js/toolbar.js +++ b/debug_toolbar/static/debug_toolbar/js/toolbar.js @@ -308,7 +308,7 @@ const djdt = { // Patch XHR / traditional AJAX requests const origOpen = XMLHttpRequest.prototype.open; - XMLHttpRequest.prototype.open = function () { + 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 @@ -319,12 +319,12 @@ const djdt = { handleAjaxResponse(this.getResponseHeader("djdt-store-id")); } }); - origOpen.apply(this, arguments); + origOpen.apply(this, args); }; const origFetch = window.fetch; - window.fetch = function () { - const promise = origFetch.apply(this, arguments); + window.fetch = function (...args) { + const promise = origFetch.apply(this, args); promise.then(function (response) { if (response.headers.get("djdt-store-id") !== null) { handleAjaxResponse(response.headers.get("djdt-store-id")); From 9535aac2f094260f118bf8b6b0bb6ae764022575 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Sat, 1 Mar 2025 15:51:17 +0100 Subject: [PATCH 17/25] Prefer template literals to string concatenation --- biome.json | 3 -- .../static/debug_toolbar/js/history.js | 4 +-- .../static/debug_toolbar/js/timer.js | 33 ++++++++----------- .../static/debug_toolbar/js/toolbar.js | 18 +++++----- .../static/debug_toolbar/js/utils.js | 11 +++---- 5 files changed, 29 insertions(+), 40 deletions(-) diff --git a/biome.json b/biome.json index 554a2f5f4..bed293eb8 100644 --- a/biome.json +++ b/biome.json @@ -15,9 +15,6 @@ "useArrowFunction": "off", "noForEach": "off" }, - "style": { - "useTemplate": "off" - }, "suspicious": { "noAssignInExpressions": "off" } diff --git a/debug_toolbar/static/debug_toolbar/js/history.js b/debug_toolbar/static/debug_toolbar/js/history.js index 314ddb3ef..dc363d3ab 100644 --- a/debug_toolbar/static/debug_toolbar/js/history.js +++ b/debug_toolbar/static/debug_toolbar/js/history.js @@ -74,7 +74,7 @@ function refreshHistory() { function switchHistory(newStoreId) { const formTarget = djDebug.querySelector( - ".switchHistory[data-store-id='" + newStoreId + "']" + `.switchHistory[data-store-id='${newStoreId}']` ); const tbody = formTarget.closest("tbody"); @@ -88,7 +88,7 @@ function switchHistory(newStoreId) { if (Object.keys(data).length === 0) { const container = document.getElementById("djdtHistoryRequests"); container.querySelector( - 'button[data-store-id="' + newStoreId + '"]' + `button[data-store-id="${newStoreId}"]` ).innerHTML = "Switch [EXPIRED]"; } replaceToolbarState(newStoreId, data); diff --git a/debug_toolbar/static/debug_toolbar/js/timer.js b/debug_toolbar/static/debug_toolbar/js/timer.js index 3205f551a..a01dbb2d1 100644 --- a/debug_toolbar/static/debug_toolbar/js/timer.js +++ b/debug_toolbar/static/debug_toolbar/js/timer.js @@ -27,36 +27,31 @@ function insertBrowserTiming() { } else { width = 0; } - return width < 1 ? "2px" : width + "%"; + 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", "") + - "" + - '' + - "" + - (performance.timing[stat] - timingOffset) + - " (+" + - (performance.timing[endStat] - performance.timing[stat]) + - ")"; + row.innerHTML = ` +${stat.replace("Start", "")} + +${elapsed} (+${duration}) +`; row.querySelector("rect").setAttribute( "width", getCSSWidth(stat, endStat) ); } else { // Render a point in time - row.innerHTML = - "" + - stat + - "" + - '' + - "" + - (performance.timing[stat] - timingOffset) + - ""; + row.innerHTML = ` +${stat} + +${elapsed} +`; row.querySelector("rect").setAttribute("width", 2); } row.querySelector("rect").setAttribute("x", getLeft(stat)); diff --git a/debug_toolbar/static/debug_toolbar/js/toolbar.js b/debug_toolbar/static/debug_toolbar/js/toolbar.js index ee47025a1..79cfbdd58 100644 --- a/debug_toolbar/static/debug_toolbar/js/toolbar.js +++ b/debug_toolbar/static/debug_toolbar/js/toolbar.js @@ -116,7 +116,7 @@ const djdt = { const toggleClose = "-"; const openMe = this.textContent === toggleOpen; const name = this.dataset.toggleName; - const container = document.getElementById(name + "_" + id); + const container = document.getElementById(`${name}_${id}`); container .querySelectorAll(".djDebugCollapsed") .forEach(function (e) { @@ -129,7 +129,7 @@ const djdt = { }); const self = this; this.closest(".djDebugPanelContent") - .querySelectorAll(".djToggleDetails_" + id) + .querySelectorAll(`.djToggleDetails_${id}`) .forEach(function (e) { if (openMe) { e.classList.add("djSelected"); @@ -173,7 +173,7 @@ const djdt = { top = window.innerHeight - handle.offsetHeight; } - handle.style.top = top + "px"; + handle.style.top = `${top}px`; djdt.handleDragged = true; } } @@ -255,7 +255,7 @@ const djdt = { localStorage.getItem("djdt.top") || 265, window.innerHeight - handle.offsetWidth ); - handle.style.top = handleTop + "px"; + handle.style.top = `${handleTop}px`; }, hideToolbar() { djdt.hidePanels(); @@ -360,15 +360,15 @@ const djdt = { } 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=${options.samesite}` : "; samesite=lax", ].join(""); diff --git a/debug_toolbar/static/debug_toolbar/js/utils.js b/debug_toolbar/static/debug_toolbar/js/utils.js index b5f00e2ac..e2f03bb2b 100644 --- a/debug_toolbar/static/debug_toolbar/js/utils.js +++ b/debug_toolbar/static/debug_toolbar/js/utils.js @@ -77,21 +77,18 @@ function ajax(url, init) { return response.json().catch(function (error) { return Promise.reject( new Error( - "The response is a invalid Json object : " + 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) { const win = document.getElementById("djDebugWindow"); - win.innerHTML = - '

' + - error.message + - "

"; + win.innerHTML = `

${error.message}

`; $$.show(win); throw error; }); @@ -118,7 +115,7 @@ function replaceToolbarState(newStoreId, data) { const panel = document.getElementById(panelId); if (panel) { panel.outerHTML = data[panelId].content; - document.getElementById("djdt-" + panelId).outerHTML = + document.getElementById(`djdt-${panelId}`).outerHTML = data[panelId].button; } }); From 9b20825e0c14a3720591bfab75ce25bbb0689d94 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Sat, 1 Mar 2025 15:54:08 +0100 Subject: [PATCH 18/25] Prefer arrow functions The way how they do not override the value of 'this' makes implementing handlers and callbacks so nuch nicer. --- biome.json | 1 - .../static/debug_toolbar/js/history.js | 24 ++++----- .../static/debug_toolbar/js/toolbar.js | 53 +++++++++---------- .../static/debug_toolbar/js/utils.js | 48 ++++++++--------- 4 files changed, 59 insertions(+), 67 deletions(-) diff --git a/biome.json b/biome.json index bed293eb8..7d0bdd912 100644 --- a/biome.json +++ b/biome.json @@ -12,7 +12,6 @@ "rules": { "recommended": true, "complexity": { - "useArrowFunction": "off", "noForEach": "off" }, "suspicious": { diff --git a/debug_toolbar/static/debug_toolbar/js/history.js b/debug_toolbar/static/debug_toolbar/js/history.js index dc363d3ab..a0d339f2b 100644 --- a/debug_toolbar/static/debug_toolbar/js/history.js +++ b/debug_toolbar/static/debug_toolbar/js/history.js @@ -15,7 +15,7 @@ function difference(setA, setB) { */ function pluckData(nodes, key) { const data = []; - nodes.forEach(function (obj) { + nodes.forEach((obj) => { data.push(obj.dataset[key]); }); return data; @@ -29,18 +29,16 @@ function refreshHistory() { ); ajaxForm(formTarget) - .then(function (data) { + .then((data) => { // Remove existing rows first then re-populate with new data - container - .querySelectorAll("tr[data-store-id]") - .forEach(function (node) { - node.remove(); - }); - data.requests.forEach(function (request) { + container.querySelectorAll("tr[data-store-id]").forEach((node) => { + node.remove(); + }); + data.requests.forEach((request) => { container.innerHTML = request.content + container.innerHTML; }); }) - .then(function () { + .then(() => { const allIds = new Set( pluckData( container.querySelectorAll("tr[data-store-id]"), @@ -55,8 +53,8 @@ function refreshHistory() { lastRequestId, }; }) - .then(function (refreshInfo) { - refreshInfo.newIds.forEach(function (newId) { + .then((refreshInfo) => { + refreshInfo.newIds.forEach((newId) => { const row = container.querySelector( `tr[data-store-id="${newId}"]` ); @@ -84,7 +82,7 @@ function switchHistory(newStoreId) { } formTarget.closest("tr").classList.add("djdt-highlighted"); - ajaxForm(formTarget).then(function (data) { + ajaxForm(formTarget).then((data) => { if (Object.keys(data).length === 0) { const container = document.getElementById("djdtHistoryRequests"); container.querySelector( @@ -100,7 +98,7 @@ $$.on(djDebug, "click", ".switchHistory", function (event) { switchHistory(this.dataset.storeId); }); -$$.on(djDebug, "click", ".refreshHistory", function (event) { +$$.on(djDebug, "click", ".refreshHistory", (event) => { event.preventDefault(); refreshHistory(); }); diff --git a/debug_toolbar/static/debug_toolbar/js/toolbar.js b/debug_toolbar/static/debug_toolbar/js/toolbar.js index 79cfbdd58..3e99e6ecb 100644 --- a/debug_toolbar/static/debug_toolbar/js/toolbar.js +++ b/debug_toolbar/static/debug_toolbar/js/toolbar.js @@ -47,7 +47,7 @@ const djdt = { ); url.searchParams.append("store_id", storeId); url.searchParams.append("panel_id", panelId); - ajax(url).then(function (data) { + ajax(url).then((data) => { inner.previousElementSibling.remove(); // Remove AJAX loader inner.innerHTML = data.content; $$.executeScripts(data.scripts); @@ -67,7 +67,7 @@ const djdt = { } } }); - $$.on(djDebug, "click", ".djDebugClose", function () { + $$.on(djDebug, "click", ".djDebugClose", () => { djdt.hideOneLevel(); }); $$.on( @@ -102,7 +102,7 @@ const djdt = { url = this.href; } - ajax(url, ajaxData).then(function (data) { + ajax(url, ajaxData).then((data) => { const win = document.getElementById("djDebugWindow"); win.innerHTML = data.content; $$.show(win); @@ -117,42 +117,37 @@ const djdt = { const openMe = this.textContent === toggleOpen; const name = this.dataset.toggleName; const container = document.getElementById(`${name}_${id}`); - container - .querySelectorAll(".djDebugCollapsed") - .forEach(function (e) { - $$.toggle(e, openMe); - }); - container - .querySelectorAll(".djDebugUncollapsed") - .forEach(function (e) { - $$.toggle(e, !openMe); - }); - const self = this; + container.querySelectorAll(".djDebugCollapsed").forEach((e) => { + $$.toggle(e, openMe); + }); + container.querySelectorAll(".djDebugUncollapsed").forEach((e) => { + $$.toggle(e, !openMe); + }); this.closest(".djDebugPanelContent") .querySelectorAll(`.djToggleDetails_${id}`) - .forEach(function (e) { + .forEach((e) => { if (openMe) { e.classList.add("djSelected"); e.classList.remove("djUnselected"); - self.textContent = toggleClose; + this.textContent = toggleClose; } else { e.classList.remove("djSelected"); e.classList.add("djUnselected"); - self.textContent = toggleOpen; + this.textContent = toggleOpen; } const switch_ = e.querySelector(".djToggleSwitch"); if (switch_) { - switch_.textContent = self.textContent; + switch_.textContent = this.textContent; } }); }); - $$.on(djDebug, "click", "#djHideToolBarButton", function (event) { + $$.on(djDebug, "click", "#djHideToolBarButton", (event) => { event.preventDefault(); djdt.hideToolbar(); }); - $$.on(djDebug, "click", "#djShowToolBarButton", function () { + $$.on(djDebug, "click", "#djShowToolBarButton", () => { if (!djdt.handleDragged) { djdt.showToolbar(); } @@ -177,7 +172,7 @@ const djdt = { djdt.handleDragged = true; } } - $$.on(djDebug, "mousedown", "#djShowToolBarButton", function (event) { + $$.on(djDebug, "mousedown", "#djShowToolBarButton", (event) => { event.preventDefault(); startPageY = event.pageY; baseY = handle.offsetTop - startPageY; @@ -185,12 +180,12 @@ const djdt = { document.addEventListener( "mouseup", - function (event) { + (event) => { document.removeEventListener("mousemove", onHandleMove); if (djdt.handleDragged) { event.preventDefault(); localStorage.setItem("djdt.top", handle.offsetTop); - requestAnimationFrame(function () { + requestAnimationFrame(() => { djdt.handleDragged = false; }); djdt.ensureHandleVisibility(); @@ -221,7 +216,7 @@ const djdt = { djDebug.setAttribute("data-theme", userTheme); } // Adds the listener to the Theme Toggle Button - $$.on(djDebug, "click", "#djToggleThemeButton", function () { + $$.on(djDebug, "click", "#djToggleThemeButton", () => { switch (djDebug.getAttribute("data-theme")) { case "auto": djDebug.setAttribute("data-theme", "light"); @@ -241,10 +236,10 @@ const djdt = { hidePanels() { const djDebug = getDebugElement(); $$.hide(document.getElementById("djDebugWindow")); - djDebug.querySelectorAll(".djdt-panelContent").forEach(function (e) { + djDebug.querySelectorAll(".djdt-panelContent").forEach((e) => { $$.hide(e); }); - document.querySelectorAll("#djDebugToolbar li").forEach(function (e) { + document.querySelectorAll("#djDebugToolbar li").forEach((e) => { e.classList.remove("djdt-active"); }); }, @@ -299,7 +294,7 @@ const djdt = { function handleAjaxResponse(storeId) { const encodedStoreId = encodeURIComponent(storeId); const dest = `${sidebarUrl}?store_id=${encodedStoreId}`; - slowjax(dest).then(function (data) { + slowjax(dest).then((data) => { if (djdt.needUpdateOnFetch) { replaceToolbarState(encodedStoreId, data); } @@ -325,7 +320,7 @@ const djdt = { const origFetch = window.fetch; window.fetch = function (...args) { const promise = origFetch.apply(this, args); - promise.then(function (response) { + promise.then((response) => { if (response.headers.get("djdt-store-id") !== null) { handleAjaxResponse(response.headers.get("djdt-store-id")); } @@ -345,7 +340,7 @@ const djdt = { const cookieArray = document.cookie.split("; "); const cookies = {}; - cookieArray.forEach(function (e) { + cookieArray.forEach((e) => { const parts = e.split("="); cookies[parts[0]] = parts[1]; }); diff --git a/debug_toolbar/static/debug_toolbar/js/utils.js b/debug_toolbar/static/debug_toolbar/js/utils.js index e2f03bb2b..0b46e6640 100644 --- a/debug_toolbar/static/debug_toolbar/js/utils.js +++ b/debug_toolbar/static/debug_toolbar/js/utils.js @@ -1,7 +1,7 @@ const $$ = { on(root, eventName, selector, fn) { root.removeEventListener(eventName, fn); - root.addEventListener(eventName, function (event) { + root.addEventListener(eventName, (event) => { const target = event.target.closest(selector); if (root.contains(target)) { fn.call(target, event); @@ -17,7 +17,7 @@ const $$ = { panelId: The Id of the panel. fn: A function to execute when the event is triggered. */ - root.addEventListener("djdt.panel.render", function (event) { + root.addEventListener("djdt.panel.render", (event) => { if (event.detail.panelId === panelId) { fn.call(event); } @@ -40,7 +40,7 @@ const $$ = { return !element.classList.contains("djdt-hidden"); }, executeScripts(scripts) { - scripts.forEach(function (script) { + scripts.forEach((script) => { const el = document.createElement("script"); el.type = "module"; el.src = script; @@ -54,39 +54,39 @@ const $$ = { * The format is data-djdt-styles="styleName1:value;styleName2:value2" * The style names should use the CSSStyleDeclaration camel cased names. */ - container - .querySelectorAll("[data-djdt-styles]") - .forEach(function (element) { - const styles = element.dataset.djdtStyles || ""; - styles.split(";").forEach(function (styleText) { - const styleKeyPair = styleText.split(":"); - if (styleKeyPair.length === 2) { - const name = styleKeyPair[0].trim(); - const value = styleKeyPair[1].trim(); - element.style[name] = value; - } - }); + container.querySelectorAll("[data-djdt-styles]").forEach((element) => { + const styles = element.dataset.djdtStyles || ""; + styles.split(";").forEach((styleText) => { + 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) { return fetch(url, Object.assign({ credentials: "same-origin" }, init)) - .then(function (response) { + .then((response) => { if (response.ok) { - return response.json().catch(function (error) { - return Promise.reject( - new Error( - `The response is a invalid Json object : ${error}` + 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}`) ); }) - .catch(function (error) { + .catch((error) => { const win = document.getElementById("djDebugWindow"); win.innerHTML = `

${error.message}

`; $$.show(win); @@ -111,7 +111,7 @@ 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. - Object.keys(data).forEach(function (panelId) { + Object.keys(data).forEach((panelId) => { const panel = document.getElementById(panelId); if (panel) { panel.outerHTML = data[panelId].content; @@ -125,7 +125,7 @@ function debounce(func, delay) { let timer = null; let resolves = []; - return function (...args) { + return (...args) => { clearTimeout(timer); timer = setTimeout(() => { const result = func(...args); From 4b05d1f6780081b2214e5f247d37c81f6a677886 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Sat, 1 Mar 2025 16:20:24 +0100 Subject: [PATCH 19/25] Enable the noAssignInExpressions rule --- biome.json | 3 --- debug_toolbar/static/debug_toolbar/js/toolbar.js | 5 +++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/biome.json b/biome.json index 7d0bdd912..b905286b3 100644 --- a/biome.json +++ b/biome.json @@ -13,9 +13,6 @@ "recommended": true, "complexity": { "noForEach": "off" - }, - "suspicious": { - "noAssignInExpressions": "off" } } }, diff --git a/debug_toolbar/static/debug_toolbar/js/toolbar.js b/debug_toolbar/static/debug_toolbar/js/toolbar.js index 3e99e6ecb..08f4fc75c 100644 --- a/debug_toolbar/static/debug_toolbar/js/toolbar.js +++ b/debug_toolbar/static/debug_toolbar/js/toolbar.js @@ -350,8 +350,9 @@ const djdt = { set(key, value, options = {}) { if (typeof options.expires === "number") { const days = options.expires; - const t = (options.expires = new Date()); - t.setDate(t.getDate() + days); + const expires = new Date(); + expires.setDate(expires.setDate() + days); + options.expires = expires; } document.cookie = [ From 01b9261d671ddce66db808390116945156faae02 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Sat, 1 Mar 2025 16:28:16 +0100 Subject: [PATCH 20/25] Replace forEach loops with for...of loops --- biome.json | 5 +- .../static/debug_toolbar/js/history.js | 30 ++++----- .../static/debug_toolbar/js/toolbar.js | 64 ++++++++++--------- .../static/debug_toolbar/js/utils.js | 22 ++++--- 4 files changed, 61 insertions(+), 60 deletions(-) diff --git a/biome.json b/biome.json index b905286b3..625e4ebe7 100644 --- a/biome.json +++ b/biome.json @@ -10,10 +10,7 @@ "linter": { "enabled": true, "rules": { - "recommended": true, - "complexity": { - "noForEach": "off" - } + "recommended": true } }, "javascript": { diff --git a/debug_toolbar/static/debug_toolbar/js/history.js b/debug_toolbar/static/debug_toolbar/js/history.js index a0d339f2b..d10156660 100644 --- a/debug_toolbar/static/debug_toolbar/js/history.js +++ b/debug_toolbar/static/debug_toolbar/js/history.js @@ -14,11 +14,7 @@ function difference(setA, setB) { * Create an array of dataset properties from a NodeList. */ function pluckData(nodes, key) { - const data = []; - nodes.forEach((obj) => { - data.push(obj.dataset[key]); - }); - return data; + return [...nodes].map((obj) => obj.dataset[key]); } function refreshHistory() { @@ -31,12 +27,14 @@ function refreshHistory() { ajaxForm(formTarget) .then((data) => { // Remove existing rows first then re-populate with new data - container.querySelectorAll("tr[data-store-id]").forEach((node) => { + for (const node of container.querySelectorAll( + "tr[data-store-id]" + )) { node.remove(); - }); - data.requests.forEach((request) => { + } + for (const request of data.requests) { container.innerHTML = request.content + container.innerHTML; - }); + } }) .then(() => { const allIds = new Set( @@ -54,18 +52,18 @@ function refreshHistory() { }; }) .then((refreshInfo) => { - refreshInfo.newIds.forEach((newId) => { + for (const newId of refreshInfo.newIds) { const row = container.querySelector( `tr[data-store-id="${newId}"]` ); row.classList.add("flash-new"); - }); + } setTimeout(() => { - container - .querySelectorAll("tr[data-store-id]") - .forEach((row) => { - row.classList.remove("flash-new"); - }); + for (const row of container.querySelectorAll( + "tr[data-store-id]" + )) { + row.classList.remove("flash-new"); + } }, 2000); }); } diff --git a/debug_toolbar/static/debug_toolbar/js/toolbar.js b/debug_toolbar/static/debug_toolbar/js/toolbar.js index 08f4fc75c..329bce669 100644 --- a/debug_toolbar/static/debug_toolbar/js/toolbar.js +++ b/debug_toolbar/static/debug_toolbar/js/toolbar.js @@ -117,29 +117,31 @@ const djdt = { const openMe = this.textContent === toggleOpen; const name = this.dataset.toggleName; const container = document.getElementById(`${name}_${id}`); - container.querySelectorAll(".djDebugCollapsed").forEach((e) => { - $$.toggle(e, openMe); - }); - container.querySelectorAll(".djDebugUncollapsed").forEach((e) => { - $$.toggle(e, !openMe); - }); - this.closest(".djDebugPanelContent") - .querySelectorAll(`.djToggleDetails_${id}`) - .forEach((e) => { - if (openMe) { - e.classList.add("djSelected"); - e.classList.remove("djUnselected"); - this.textContent = toggleClose; - } else { - e.classList.remove("djSelected"); - e.classList.add("djUnselected"); - this.textContent = toggleOpen; - } - const switch_ = e.querySelector(".djToggleSwitch"); - if (switch_) { - switch_.textContent = this.textContent; - } - }); + 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; + } + } }); $$.on(djDebug, "click", "#djHideToolBarButton", (event) => { @@ -236,12 +238,12 @@ const djdt = { hidePanels() { const djDebug = getDebugElement(); $$.hide(document.getElementById("djDebugWindow")); - djDebug.querySelectorAll(".djdt-panelContent").forEach((e) => { - $$.hide(e); - }); - document.querySelectorAll("#djDebugToolbar li").forEach((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"); + } }, ensureHandleVisibility() { const handle = document.getElementById("djDebugToolbarHandle"); @@ -340,10 +342,10 @@ const djdt = { const cookieArray = document.cookie.split("; "); const cookies = {}; - cookieArray.forEach((e) => { + for (const e of cookieArray) { const parts = e.split("="); cookies[parts[0]] = parts[1]; - }); + } return cookies[key]; }, diff --git a/debug_toolbar/static/debug_toolbar/js/utils.js b/debug_toolbar/static/debug_toolbar/js/utils.js index 0b46e6640..c42963fe3 100644 --- a/debug_toolbar/static/debug_toolbar/js/utils.js +++ b/debug_toolbar/static/debug_toolbar/js/utils.js @@ -40,13 +40,13 @@ const $$ = { return !element.classList.contains("djdt-hidden"); }, executeScripts(scripts) { - scripts.forEach((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) { /* @@ -54,17 +54,19 @@ const $$ = { * The format is data-djdt-styles="styleName1:value;styleName2:value2" * The style names should use the CSSStyleDeclaration camel cased names. */ - container.querySelectorAll("[data-djdt-styles]").forEach((element) => { + for (const element of container.querySelectorAll( + "[data-djdt-styles]" + )) { const styles = element.dataset.djdtStyles || ""; - styles.split(";").forEach((styleText) => { + 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; } - }); - }); + } + } }, }; @@ -111,14 +113,14 @@ 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. - Object.keys(data).forEach((panelId) => { + 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) { @@ -129,7 +131,9 @@ function debounce(func, delay) { clearTimeout(timer); timer = setTimeout(() => { const result = func(...args); - resolves.forEach((r) => r(result)); + for (const r of resolves) { + r(result); + } resolves = []; }, delay); From f8f9cdd4caa5e0ab4e503474d7d6b42f5f1f67a3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Mar 2025 08:17:05 +0100 Subject: [PATCH 21/25] [pre-commit.ci] pre-commit autoupdate (#2095) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.9.7 → v0.9.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.7...v0.9.9) - [github.com/tox-dev/pyproject-fmt: v2.5.0 → v2.5.1](https://github.com/tox-dev/pyproject-fmt/compare/v2.5.0...v2.5.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e9317b643..adf0aed43 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,13 +29,13 @@ repos: - id: biome-check verbose: true - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 'v0.9.7' + rev: 'v0.9.9' hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] - id: ruff-format - repo: https://github.com/tox-dev/pyproject-fmt - rev: v2.5.0 + rev: v2.5.1 hooks: - id: pyproject-fmt - repo: https://github.com/abravalheri/validate-pyproject From 2b34fcb07cf492dc81d5759475ea07a2ffcced45 Mon Sep 17 00:00:00 2001 From: Abdulwasiu Apalowo <64538336+mrbazzan@users.noreply.github.com> Date: Tue, 4 Mar 2025 19:35:43 +0100 Subject: [PATCH 22/25] Add help command to the Makefile (#2094) Closes #2092 --- Makefile | 23 ++++++++++++++--------- docs/changes.rst | 2 ++ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 24b59ab95..4d2db27af 100644 --- a/Makefile +++ b/Makefile @@ -1,23 +1,24 @@ -.PHONY: example test coverage translatable_strings update_translations +.PHONY: example test coverage translatable_strings update_translations help +.DEFAULT_GOAL := help -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 -example_test: +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 DJANGO_SETTINGS_MODULE=tests.settings \ python -b -W always -m coverage run -m django test -v2 $${TEST_ARGS:-tests} @@ -25,15 +26,19 @@ coverage: 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/docs/changes.rst b/docs/changes.rst index b75e0daf7..8ca86692d 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -12,6 +12,8 @@ Pending * 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. 5.0.1 (2025-01-13) ------------------ From 5a3b80539cdf111a04d67868576263c2cd853b67 Mon Sep 17 00:00:00 2001 From: dr-rompecabezas Date: Wed, 5 Mar 2025 10:25:27 -0500 Subject: [PATCH 23/25] Update changelog: added URLMixin --- docs/changes.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changes.rst b/docs/changes.rst index 8ca86692d..bbf1bdeee 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -14,6 +14,7 @@ Pending * 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. +* Replaced DebugConfiguredStorage with URLMixin in staticfiles panel. 5.0.1 (2025-01-13) ------------------ From 2a45f804013c7286d3c648fafedb7636db74a331 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 15:46:49 +0000 Subject: [PATCH 24/25] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/panels/test_staticfiles.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/tests/panels/test_staticfiles.py b/tests/panels/test_staticfiles.py index 0549a0112..2306c8365 100644 --- a/tests/panels/test_staticfiles.py +++ b/tests/panels/test_staticfiles.py @@ -6,6 +6,7 @@ from django.test import AsyncRequestFactory, RequestFactory from debug_toolbar.panels.staticfiles import URLMixin + from ..base import BaseTestCase @@ -82,21 +83,21 @@ 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__) - + # 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("test.css") self.assertTrue(url.startswith("/static/")) - + # Should track when context is set token = request_id_context_var.set("test-request-id") try: @@ -105,18 +106,16 @@ def test_context_variable_lifecycle(self): # Verify file was tracked self.assertIn("test.css", [f.path for f in self.panel.used_paths]) finally: - request_id_context_var.reset(token) - + 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 - ) + mixin_count = sum(1 for base in storage_class.__bases__ if base == URLMixin) self.assertEqual(mixin_count, 1) From b6b21b03eebd13efda62bcd4f34b6ef3560562b3 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Wed, 5 Mar 2025 16:49:55 +0100 Subject: [PATCH 25/25] Add words to the spelling wordlist --- docs/spelling_wordlist.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index 662e6df4f..8db8072b7 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -13,6 +13,7 @@ async backend backends backported +biome checkbox contrib dicts @@ -50,6 +51,7 @@ pylibmc pyupgrade querysets refactoring +reinitializing resizing runserver spellchecking @@ -57,6 +59,7 @@ spooler stacktrace stacktraces startup +staticfiles theming timeline tox