From c63ee8171389b5638eed0dfcbd64ddedaf019f48 Mon Sep 17 00:00:00 2001 From: theShinigami Date: Thu, 25 Sep 2025 15:58:33 +0300 Subject: [PATCH 1/8] refactor: add contextvar to enable/disable tracking of DDT models --- debug_toolbar/panels/sql/tracking.py | 18 +++++++ tests/panels/test_sql.py | 73 ++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/debug_toolbar/panels/sql/tracking.py b/debug_toolbar/panels/sql/tracking.py index 45e0c0c17..a25cecf10 100644 --- a/debug_toolbar/panels/sql/tracking.py +++ b/debug_toolbar/panels/sql/tracking.py @@ -8,6 +8,7 @@ from django.utils.encoding import force_str from debug_toolbar.utils import get_stack_trace, get_template_info +from django.apps import apps try: import psycopg @@ -27,6 +28,17 @@ # additional queries. allow_sql = contextvars.ContextVar("debug-toolbar-allow-sql", default=True) +# Prevents tracking of DDT models +allow_ddt_models_tracking = contextvars.ContextVar( + "debug-toolbar-allow-ddt-models", default=False +) + +DDT_MODELS = { + m._meta.db_table + for m in apps.get_app_config("debug_toolbar").get_models() + if m._meta.app_label == "debug_toolbar" +} + class SQLQueryTriggered(Exception): """Thrown when template panel triggers a query""" @@ -224,6 +236,12 @@ def _record(self, method, sql, params): } ) + # Skip recording if query includes DDT models. + if not allow_ddt_models_tracking.get() and any( + table in sql for table in DDT_MODELS + ): + return + # We keep `sql` to maintain backwards compatibility self.logger.record(**kwargs) diff --git a/tests/panels/test_sql.py b/tests/panels/test_sql.py index e238bd0d8..5a167dda7 100644 --- a/tests/panels/test_sql.py +++ b/tests/panels/test_sql.py @@ -16,6 +16,7 @@ import debug_toolbar.panels.sql.tracking as sql_tracking from debug_toolbar.panels.sql import SQLPanel +from debug_toolbar.models import HistoryEntry try: import psycopg @@ -33,6 +34,20 @@ def sql_call(*, use_iterator=False): return list(qs) +def sql_call_ddt(*, use_iterator=False): + qs = HistoryEntry.objects.all() + if use_iterator: + qs = qs.iterator() + return list(qs) + + +async def async_sql_call_ddt(*, use_iterator=False): + qs = HistoryEntry.objects.all() + if use_iterator: + qs = qs.iterator() + return await sync_to_async(list)(qs) + + async def async_sql_call(*, use_iterator=False): qs = User.objects.all() if use_iterator: @@ -104,6 +119,64 @@ async def test_recording_concurrent_async(self): # ensure the stacktrace is populated self.assertTrue(len(query["stacktrace"]) > 0) + def test_ddt_models_tracking(self): + self.assertEqual(len(self.panel._queries), 0) + + # enable ddt tracking + sql_tracking.allow_ddt_models_tracking.set(True) + + sql_call_ddt() + + # ensure query was logged + self.assertEqual(len(self.panel._queries), 1) + query = self.panel._queries[0] + self.assertEqual(query["alias"], "default") + self.assertTrue("sql" in query) + self.assertTrue("duration" in query) + self.assertTrue("stacktrace" in query) + + # ensure the stacktrace is populated + self.assertTrue(len(self.panel._queries), 0) + + async def test_ddt_models_tracking_async(self): + self.assertEqual(len(self.panel._queries), 0) + + # enable ddt tracking + sql_tracking.allow_ddt_models_tracking.set(True) + + await async_sql_call_ddt() + + # ensure query was logged + self.assertEqual(len(self.panel._queries), 1) + query = self.panel._queries[0] + self.assertEqual(query["alias"], "default") + self.assertTrue("sql" in query) + self.assertTrue("duration" in query) + self.assertTrue("stacktrace" in query) + + # ensure the stacktrace is populated + self.assertTrue(len(self.panel._queries), 0) + + def test_ddt_models_untracking(self): + self.assertEqual(len(self.panel._queries), 0) + + # disable ddt tracking + sql_tracking.allow_ddt_models_tracking.set(False) + + sql_call_ddt() + + self.assertEqual(len(self.panel._queries), 0) + + async def test_ddt_models_untracking_async(self): + self.assertEqual(len(self.panel._queries), 0) + + # enable ddt tracking + sql_tracking.allow_ddt_models_tracking.set(False) + + await async_sql_call_ddt() + + self.assertEqual(len(self.panel._queries), 0) + @unittest.skipUnless( connection.vendor == "postgresql", "Test valid only on PostgreSQL" ) From efbff9a8b7e2a0d77214e22608e2ca8dd7b52888 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 25 Sep 2025 13:09:40 +0000 Subject: [PATCH 2/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- debug_toolbar/panels/sql/tracking.py | 2 +- tests/panels/test_sql.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/debug_toolbar/panels/sql/tracking.py b/debug_toolbar/panels/sql/tracking.py index a25cecf10..18b4b0237 100644 --- a/debug_toolbar/panels/sql/tracking.py +++ b/debug_toolbar/panels/sql/tracking.py @@ -5,10 +5,10 @@ from time import perf_counter import django.test.testcases +from django.apps import apps from django.utils.encoding import force_str from debug_toolbar.utils import get_stack_trace, get_template_info -from django.apps import apps try: import psycopg diff --git a/tests/panels/test_sql.py b/tests/panels/test_sql.py index 5a167dda7..33a4bd05f 100644 --- a/tests/panels/test_sql.py +++ b/tests/panels/test_sql.py @@ -15,8 +15,8 @@ from django.test.utils import override_settings import debug_toolbar.panels.sql.tracking as sql_tracking -from debug_toolbar.panels.sql import SQLPanel from debug_toolbar.models import HistoryEntry +from debug_toolbar.panels.sql import SQLPanel try: import psycopg From 9f125bdde83d98e2238b70b6ee6f2a27ce0e9632 Mon Sep 17 00:00:00 2001 From: theShinigami Date: Thu, 25 Sep 2025 16:47:51 +0300 Subject: [PATCH 3/8] chore: ruff B012 --- debug_toolbar/panels/sql/tracking.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/debug_toolbar/panels/sql/tracking.py b/debug_toolbar/panels/sql/tracking.py index 18b4b0237..a720d2278 100644 --- a/debug_toolbar/panels/sql/tracking.py +++ b/debug_toolbar/panels/sql/tracking.py @@ -237,13 +237,11 @@ def _record(self, method, sql, params): ) # Skip recording if query includes DDT models. - if not allow_ddt_models_tracking.get() and any( + if allow_ddt_models_tracking.get() or not any( table in sql for table in DDT_MODELS ): - return - - # We keep `sql` to maintain backwards compatibility - self.logger.record(**kwargs) + # We keep `sql` to maintain backwards compatibility + self.logger.record(**kwargs) def callproc(self, procname, params=None): return self._record(super().callproc, procname, params) From 115f3a09eb54eb1aa515a2000a85eadb6fa379ac Mon Sep 17 00:00:00 2001 From: theShinigami Date: Fri, 3 Oct 2025 11:29:34 +0300 Subject: [PATCH 4/8] refactor: add `TRACK_DDT_MODELS` to override tracking DDT models --- debug_toolbar/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/debug_toolbar/settings.py b/debug_toolbar/settings.py index d6b9003b6..737202be0 100644 --- a/debug_toolbar/settings.py +++ b/debug_toolbar/settings.py @@ -28,6 +28,7 @@ def _is_running_tests(): "ROOT_TAG_EXTRA_ATTRS": "", "SHOW_COLLAPSED": False, "SHOW_TOOLBAR_CALLBACK": "debug_toolbar.middleware.show_toolbar", + "TRACK_DDT_MODELS": False, # Panel options "EXTRA_SIGNALS": [], "ENABLE_STACKTRACES": True, From 2f61273e3496eb51d084c1d9d1336bb9e7156ef8 Mon Sep 17 00:00:00 2001 From: theShinigami Date: Fri, 3 Oct 2025 11:30:00 +0300 Subject: [PATCH 5/8] refactor: add `TRACK_DDT_MODELS` to override tracking DDT models --- debug_toolbar/panels/sql/tracking.py | 11 +++++++++-- tests/panels/test_sql.py | 18 ++++-------------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/debug_toolbar/panels/sql/tracking.py b/debug_toolbar/panels/sql/tracking.py index a720d2278..167f0a9ac 100644 --- a/debug_toolbar/panels/sql/tracking.py +++ b/debug_toolbar/panels/sql/tracking.py @@ -8,6 +8,7 @@ from django.apps import apps from django.utils.encoding import force_str +from debug_toolbar import settings as dt_settings from debug_toolbar.utils import get_stack_trace, get_template_info try: @@ -40,6 +41,11 @@ } +DDT_MODELS = { + m._meta.db_table for m in apps.get_app_config("debug_toolbar").get_models() +} + + class SQLQueryTriggered(Exception): """Thrown when template panel triggers a query""" @@ -236,8 +242,9 @@ def _record(self, method, sql, params): } ) - # Skip recording if query includes DDT models. - if allow_ddt_models_tracking.get() or not any( + # Skip tracking for DDT models by default. + # This can be overridden by setting TRACK_DDT_MODELS = True + if dt_settings.get_config()["TRACK_DDT_MODELS"] or not any( table in sql for table in DDT_MODELS ): # We keep `sql` to maintain backwards compatibility diff --git a/tests/panels/test_sql.py b/tests/panels/test_sql.py index 33a4bd05f..813739b9c 100644 --- a/tests/panels/test_sql.py +++ b/tests/panels/test_sql.py @@ -119,12 +119,10 @@ async def test_recording_concurrent_async(self): # ensure the stacktrace is populated self.assertTrue(len(query["stacktrace"]) > 0) + @override_settings(DEBUG_TOOLBAR_CONFIG={"TRACK_DDT_MODELS": True}) def test_ddt_models_tracking(self): self.assertEqual(len(self.panel._queries), 0) - # enable ddt tracking - sql_tracking.allow_ddt_models_tracking.set(True) - sql_call_ddt() # ensure query was logged @@ -136,14 +134,12 @@ def test_ddt_models_tracking(self): self.assertTrue("stacktrace" in query) # ensure the stacktrace is populated - self.assertTrue(len(self.panel._queries), 0) + self.assertTrue(len(query["stacktrace"]) > 0) + @override_settings(DEBUG_TOOLBAR_CONFIG={"TRACK_DDT_MODELS": True}) async def test_ddt_models_tracking_async(self): self.assertEqual(len(self.panel._queries), 0) - # enable ddt tracking - sql_tracking.allow_ddt_models_tracking.set(True) - await async_sql_call_ddt() # ensure query was logged @@ -155,14 +151,11 @@ async def test_ddt_models_tracking_async(self): self.assertTrue("stacktrace" in query) # ensure the stacktrace is populated - self.assertTrue(len(self.panel._queries), 0) + self.assertTrue(len(query["stacktrace"]) > 0) def test_ddt_models_untracking(self): self.assertEqual(len(self.panel._queries), 0) - # disable ddt tracking - sql_tracking.allow_ddt_models_tracking.set(False) - sql_call_ddt() self.assertEqual(len(self.panel._queries), 0) @@ -170,9 +163,6 @@ def test_ddt_models_untracking(self): async def test_ddt_models_untracking_async(self): self.assertEqual(len(self.panel._queries), 0) - # enable ddt tracking - sql_tracking.allow_ddt_models_tracking.set(False) - await async_sql_call_ddt() self.assertEqual(len(self.panel._queries), 0) From 1fe70b6f2b7b17c4453f60093f93fe70ed3d07d1 Mon Sep 17 00:00:00 2001 From: theShinigami Date: Fri, 3 Oct 2025 11:40:05 +0300 Subject: [PATCH 6/8] chore: remove leftover code from the previous change --- debug_toolbar/panels/sql/tracking.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/debug_toolbar/panels/sql/tracking.py b/debug_toolbar/panels/sql/tracking.py index 167f0a9ac..9a06aead4 100644 --- a/debug_toolbar/panels/sql/tracking.py +++ b/debug_toolbar/panels/sql/tracking.py @@ -29,17 +29,6 @@ # additional queries. allow_sql = contextvars.ContextVar("debug-toolbar-allow-sql", default=True) -# Prevents tracking of DDT models -allow_ddt_models_tracking = contextvars.ContextVar( - "debug-toolbar-allow-ddt-models", default=False -) - -DDT_MODELS = { - m._meta.db_table - for m in apps.get_app_config("debug_toolbar").get_models() - if m._meta.app_label == "debug_toolbar" -} - DDT_MODELS = { m._meta.db_table for m in apps.get_app_config("debug_toolbar").get_models() From cb772834292c1bcb3c7565f42bdcb27699468636 Mon Sep 17 00:00:00 2001 From: theShinigami Date: Fri, 3 Oct 2025 17:54:28 +0300 Subject: [PATCH 7/8] refactor: small improvements --- tests/panels/test_sql.py | 33 +++++++++------------------------ 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/tests/panels/test_sql.py b/tests/panels/test_sql.py index 813739b9c..b6cf02e63 100644 --- a/tests/panels/test_sql.py +++ b/tests/panels/test_sql.py @@ -34,17 +34,14 @@ def sql_call(*, use_iterator=False): return list(qs) -def sql_call_ddt(*, use_iterator=False): +def sql_call_ddt(): + """Test query to fetch DDT related entries from one of the DDT models""" qs = HistoryEntry.objects.all() - if use_iterator: - qs = qs.iterator() return list(qs) -async def async_sql_call_ddt(*, use_iterator=False): +async def async_sql_call_ddt(): qs = HistoryEntry.objects.all() - if use_iterator: - qs = qs.iterator() return await sync_to_async(list)(qs) @@ -120,7 +117,7 @@ async def test_recording_concurrent_async(self): self.assertTrue(len(query["stacktrace"]) > 0) @override_settings(DEBUG_TOOLBAR_CONFIG={"TRACK_DDT_MODELS": True}) - def test_ddt_models_tracking(self): + def test_ddt_models_tracked(self): self.assertEqual(len(self.panel._queries), 0) sql_call_ddt() @@ -128,16 +125,10 @@ def test_ddt_models_tracking(self): # ensure query was logged self.assertEqual(len(self.panel._queries), 1) query = self.panel._queries[0] - self.assertEqual(query["alias"], "default") - self.assertTrue("sql" in query) - self.assertTrue("duration" in query) - self.assertTrue("stacktrace" in query) - - # ensure the stacktrace is populated - self.assertTrue(len(query["stacktrace"]) > 0) + self.assertTrue(HistoryEntry._meta.db_table in query["sql"]) @override_settings(DEBUG_TOOLBAR_CONFIG={"TRACK_DDT_MODELS": True}) - async def test_ddt_models_tracking_async(self): + async def test_ddt_models_tracked_async(self): self.assertEqual(len(self.panel._queries), 0) await async_sql_call_ddt() @@ -145,22 +136,16 @@ async def test_ddt_models_tracking_async(self): # ensure query was logged self.assertEqual(len(self.panel._queries), 1) query = self.panel._queries[0] - self.assertEqual(query["alias"], "default") - self.assertTrue("sql" in query) - self.assertTrue("duration" in query) - self.assertTrue("stacktrace" in query) - - # ensure the stacktrace is populated - self.assertTrue(len(query["stacktrace"]) > 0) + self.assertTrue(HistoryEntry._meta.db_table in query["sql"]) - def test_ddt_models_untracking(self): + def test_ddt_models_not_tracked(self): self.assertEqual(len(self.panel._queries), 0) sql_call_ddt() self.assertEqual(len(self.panel._queries), 0) - async def test_ddt_models_untracking_async(self): + async def test_ddt_models_not_tracked_async(self): self.assertEqual(len(self.panel._queries), 0) await async_sql_call_ddt() From 52d9f14d3cb6328a975061c11e3c07ffa94b2f4b Mon Sep 17 00:00:00 2001 From: theShinigami Date: Fri, 3 Oct 2025 18:02:20 +0300 Subject: [PATCH 8/8] refactor: add doc string to helper function and tests --- tests/panels/test_sql.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/panels/test_sql.py b/tests/panels/test_sql.py index b6cf02e63..add9f3546 100644 --- a/tests/panels/test_sql.py +++ b/tests/panels/test_sql.py @@ -35,12 +35,13 @@ def sql_call(*, use_iterator=False): def sql_call_ddt(): - """Test query to fetch DDT related entries from one of the DDT models""" + """Helper function to query one of the DDT models to test tracking of DDT entries.""" qs = HistoryEntry.objects.all() return list(qs) async def async_sql_call_ddt(): + """(async) Helper function to query one of the DDT models to test tracking of DDT entries.""" qs = HistoryEntry.objects.all() return await sync_to_async(list)(qs) @@ -118,6 +119,7 @@ async def test_recording_concurrent_async(self): @override_settings(DEBUG_TOOLBAR_CONFIG={"TRACK_DDT_MODELS": True}) def test_ddt_models_tracked(self): + """test if DDT models are being tracked when the `TRACK_DDT_MODELS` is set to True""" self.assertEqual(len(self.panel._queries), 0) sql_call_ddt() @@ -129,6 +131,7 @@ def test_ddt_models_tracked(self): @override_settings(DEBUG_TOOLBAR_CONFIG={"TRACK_DDT_MODELS": True}) async def test_ddt_models_tracked_async(self): + """(async) test if DDT models are being tracked when the `TRACK_DDT_MODELS` is set to True""" self.assertEqual(len(self.panel._queries), 0) await async_sql_call_ddt() @@ -139,6 +142,7 @@ async def test_ddt_models_tracked_async(self): self.assertTrue(HistoryEntry._meta.db_table in query["sql"]) def test_ddt_models_not_tracked(self): + """Tests whether DDt models are not being tracked when the `TRACK_DDT_MODELS` is set to False""" self.assertEqual(len(self.panel._queries), 0) sql_call_ddt() @@ -146,6 +150,7 @@ def test_ddt_models_not_tracked(self): self.assertEqual(len(self.panel._queries), 0) async def test_ddt_models_not_tracked_async(self): + """(async) Tests whether DDt models are not being tracked when the `TRACK_DDT_MODELS` is set to False""" self.assertEqual(len(self.panel._queries), 0) await async_sql_call_ddt()