Skip to content

Commit 4994938

Browse files
committed
Merge branch 'issue-duplicated-queries' of https://github.com/thinred/django-debug-toolbar into thinred-issue-duplicated-queries
2 parents 8278bb0 + 6539425 commit 4994938

File tree

3 files changed

+66
-3
lines changed

3 files changed

+66
-3
lines changed

debug_toolbar/panels/template.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
from django.template.context import get_standard_processors
77
from django.test.signals import template_rendered
88
from django.utils.translation import ugettext_lazy as _
9+
from django.db.models.query import QuerySet
910
from debug_toolbar.panels import DebugPanel
11+
from debug_toolbar.utils.tracking.db import recording, SQLQueryTriggered
1012

1113
# Code taken and adapted from Simon Willison and Django Snippets:
1214
# http://www.djangosnippets.org/snippets/766/
@@ -68,8 +70,20 @@ def _store_template_info(self, sender, **kwargs):
6870
# Replace LANGUAGES, which is available in i18n context processor
6971
elif key == 'LANGUAGES' and isinstance(value, tuple):
7072
temp_layer[key] = '<<languages>>'
73+
# QuerySet would trigger the database: user can run the query from SQL Panel
74+
elif isinstance(value, QuerySet):
75+
model_name = "%s.%s" % (value.model._meta.app_label, value.model.__name__)
76+
temp_layer[key] = '<<queryset of %s>>' % model_name
7177
else:
72-
temp_layer[key] = value
78+
try:
79+
recording(False)
80+
pformat(value) # this MAY trigger a db query
81+
except SQLQueryTriggered:
82+
temp_layer[key] = '<<triggers database query>>'
83+
else:
84+
temp_layer[key] = value
85+
finally:
86+
recording(True)
7387
try:
7488
context_list.append(pformat(temp_layer))
7589
except UnicodeEncodeError:

debug_toolbar/utils/tracking/db.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import sys
33

44
from datetime import datetime
5+
from threading import local
56

67
from django.conf import settings
78
from django.template import Node
@@ -16,7 +17,39 @@
1617
SQL_WARNING_THRESHOLD = getattr(settings, 'DEBUG_TOOLBAR_CONFIG', {}) \
1718
.get('SQL_WARNING_THRESHOLD', 500)
1819

19-
class CursorWrapper(object):
20+
class SQLQueryTriggered(Exception):
21+
"""Thrown when template panel triggers a query"""
22+
pass
23+
24+
class ThreadLocalState(local):
25+
def __init__(self):
26+
self.enabled = True
27+
28+
@property
29+
def Wrapper(self):
30+
return NormalCursorWrapper if self.enabled else ExceptionCursorWrapper
31+
32+
def recording(self, v):
33+
self.enabled = v
34+
35+
state = ThreadLocalState()
36+
recording = state.recording # export function
37+
38+
def CursorWrapper(*args, **kwds): # behave like a class
39+
return state.Wrapper(*args, **kwds)
40+
41+
class ExceptionCursorWrapper(object):
42+
"""
43+
Wraps a cursor and raises an exception on any operation.
44+
Used in Templates panel.
45+
"""
46+
def __init__(self, cursor, db, logger):
47+
pass
48+
49+
def __getattr__(self, attr):
50+
raise SQLQueryTriggered()
51+
52+
class NormalCursorWrapper(object):
2053
"""
2154
Wraps a cursor and logs queries.
2255
"""
@@ -103,4 +136,4 @@ def __getattr__(self, attr):
103136
return getattr(self.cursor, attr)
104137

105138
def __iter__(self):
106-
return iter(self.cursor)
139+
return iter(self.cursor)

tests/tests.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from debug_toolbar.middleware import DebugToolbarMiddleware
22
from debug_toolbar.panels.sql import SQLDebugPanel
33
from debug_toolbar.panels.request_vars import RequestVarsDebugPanel
4+
from debug_toolbar.panels.template import TemplateDebugPanel
45
from debug_toolbar.toolbar.loader import DebugToolbar
56
from debug_toolbar.utils import get_name_from_obj
67
from debug_toolbar.utils.tracking import pre_dispatch, post_dispatch, callbacks
@@ -9,6 +10,7 @@
910
from django.contrib.auth.models import User
1011
from django.http import HttpResponse
1112
from django.test import TestCase
13+
from django.template import Template, Context
1214

1315
from dingus import Dingus
1416
import thread
@@ -202,6 +204,20 @@ def test_recording(self):
202204
self.assertTrue('duration' in query[1])
203205
self.assertTrue('stacktrace' in query[1])
204206

207+
class TemplatePanelTestCase(BaseTestCase):
208+
def test_queryset_hook(self):
209+
template_panel = self.toolbar.get_panel(TemplateDebugPanel)
210+
sql_panel = self.toolbar.get_panel(SQLDebugPanel)
211+
t = Template("No context variables here!")
212+
c = Context({ 'queryset' : User.objects.all(), 'deep_queryset' : { 'queryset' : User.objects.all() } })
213+
t.render(c)
214+
# ensure the query was NOT logged
215+
self.assertEquals(len(sql_panel._queries), 0)
216+
ctx = template_panel.templates[0]['context'][0]
217+
ctx = eval(ctx) # convert back to Python
218+
self.assertEquals(ctx['queryset'], '<<queryset of auth.User>>')
219+
self.assertEquals(ctx['deep_queryset'], '<<triggers database query>>')
220+
205221
def module_func(*args, **kwargs):
206222
"""Used by dispatch tests"""
207223
return 'blah'

0 commit comments

Comments
 (0)