Skip to content
16 changes: 14 additions & 2 deletions debug_toolbar/panels/sql/tracking.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +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 import settings as dt_settings
from debug_toolbar.utils import get_stack_trace, get_template_info

try:
Expand All @@ -28,6 +30,11 @@
allow_sql = contextvars.ContextVar("debug-toolbar-allow-sql", default=True)


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"""

Expand Down Expand Up @@ -224,8 +231,13 @@ def _record(self, method, sql, params):
}
)

# We keep `sql` to maintain backwards compatibility
self.logger.record(**kwargs)
# 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
self.logger.record(**kwargs)

def callproc(self, procname, params=None):
return self._record(super().callproc, procname, params)
Expand Down
1 change: 1 addition & 0 deletions debug_toolbar/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
63 changes: 63 additions & 0 deletions tests/panels/test_sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from django.test.utils import override_settings

import debug_toolbar.panels.sql.tracking as sql_tracking
from debug_toolbar.models import HistoryEntry
from debug_toolbar.panels.sql import SQLPanel

try:
Expand All @@ -33,6 +34,20 @@ def sql_call(*, use_iterator=False):
return list(qs)


def sql_call_ddt(*, use_iterator=False):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add a doc string here to explain what this function is for please? I think that'll help us in the future when reviewing code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack, posted too soon. I think a doc string on these two helper functions and the four tests would be fantastic.

qs = HistoryEntry.objects.all()
if use_iterator:
qs = qs.iterator()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this isn't used, so we should be able to remove it and the one from async_sql_call_ddt

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:
Expand Down Expand Up @@ -104,6 +119,54 @@ 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):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def test_ddt_models_tracking(self):
def test_ddt_models_tracked(self):

nit: Small naming improvement (should be used on the other similar test too)

self.assertEqual(len(self.panel._queries), 0)

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we may be better off replacing this with a check on the models' table in the sql query rather than these assertions.


# ensure the stacktrace is populated
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)

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(query["stacktrace"]) > 0)

def test_ddt_models_untracking(self):
Copy link
Member

@tim-schilling tim-schilling Oct 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def test_ddt_models_untracking(self):
def test_ddt_models_not_tracked(self):

nit: Small naming improvement (should be used on the other similar test too)

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):
self.assertEqual(len(self.panel._queries), 0)

await async_sql_call_ddt()

self.assertEqual(len(self.panel._queries), 0)

@unittest.skipUnless(
connection.vendor == "postgresql", "Test valid only on PostgreSQL"
)
Expand Down