Login

Debug middleware for displaying sql queries and template loading info when ?debug=true

Author:
SEJeff
Posted:
April 8, 2011
Language:
Python
Version:
1.2
Score:
1 (after 1 ratings)

Originally based on: http://djangosnippets.org/snippets/1872/

The way the original snippet formatted sql didn't work for mysql properly so I taught it to use the sqlparse python module. Now it looks like this when settings.DEBUG=True:

SQL executed:
SELECT "django_session"."session_key",
       "django_session"."session_data",
       "django_session"."expire_date"
FROM "django_session"
WHERE ("django_session"."session_key" = d326108d313a2e5c5fb417364b005ab9
       AND "django_session"."expire_date" > 2011-04-08 14:54:13.969881)
took 0.001 seconds

SELECT "auth_user"."id",
       "auth_user"."username",
       "auth_user"."first_name",
       "auth_user"."last_name",
       "auth_user"."email",
       "auth_user"."password",
       "auth_user"."is_staff",
       "auth_user"."is_active",
       "auth_user"."is_superuser",
       "auth_user"."last_login",
       "auth_user"."date_joined"
FROM "auth_user"
WHERE "auth_user"."id" = 2
took 0.000 seconds

Additionally, this middlware is enabled conditionally based upon the url query string "debug". You can enable it for a single request by appending: ?debug=true to the url.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# Originally based on: http://djangosnippets.org/snippets/1872/
# Requires sqlparse: http://pypi.python.org/pypi/sqlparse
import time
from django.test.signals import template_rendered
from django.conf import settings
from django.db import connection
from django.utils.encoding import force_unicode
from django.utils.safestring import mark_safe

TEMPLATE = """
<div id="debug" style="clear:both;">
<a href="#debugbox"
    onclick="this.style.display = 'none';
        document.getElementById('debugbox').style.display = 'block';
        return false;"
    style="font-size: small; color: red; text-decoration: none; display: block; margin: 12px;"
>+</a>

<div style="display: none;clear: both; border: 1px solid red; padding: 12px; margin: 12px; overflow: scroll; white-space: wrap;" id="debugbox">

<p>Server-time taken: {{ server_time|floatformat:"5" }} seconds</p>
<p>View: <strong>{{view}}</strong></p>
<p>Templates used:</p>
{% if templates %}
<ol>
    {% for template in templates %}
        <li><strong>{{ template.0 }}</strong> loaded from <samp>{{ template.1 }}</samp></li>
    {% endfor %}
</ol>
{% else %}
    None
{% endif %}
<p>Template path:</p>
{% if template_dirs %}
    <ol>
    {% for template in template_dirs %}
        <li>{{ template }}</li>
    {% endfor %}
    </ol>
{% else %}
    <ul><li>None</li></ul>
{% endif %}
<p>SQL executed:</p>
{% if sql %}
<pre style="margin-left: 2em;">{% for query in sql %}{{ query.sql }}
<strong>took {{ query.time|floatformat:"3" }} seconds</p>{{ query.count }}</strong>
{% endfor %}</pre>
<p>Total SQL time: {{ sql_total }} in {{num_queries}} queries</p>
{% else %}
    {% if not debug %}
    <ol><li>Showing full queries is disabled when settings.DEBUG = False.</li></ol>
    {% else %}
    None
    {% endif %}
{% endif %}
</div>
</div>
</body>
"""

# Monkeypatch instrumented test renderer from django.test.utils - we could use
# django.test.utils.setup_test_environment for this but that would also set up
# e-mail interception, which we don't want
from django.test.utils import instrumented_test_render
from django.template import Template, Context
if Template.render != instrumented_test_render:
    Template.original_render = Template.render
    Template.render = instrumented_test_render
# MONSTER monkey-patch
old_template_init = Template.__init__
def new_template_init(self, template_string, origin=None, name='<Unknown Template>'):
    old_template_init(self, template_string, origin, name)
    self.origin = origin
Template.__init__ = new_template_init

class DebugFooter:
    def process_request(self, request):
        self.time_started = time.time()
        self.templates_used = []
        self.contexts_used = []
        self.sql_offset_start = len(connection.queries)
        template_rendered.connect(self._storeRenderedTemplates)

    def process_response(self, request, response):

        # Don't bother if the url doesn't have the "debug"  query  string
        # Added by Jeff Schroeder for dynamically enabling/disabling this
        if not request.GET.has_key("debug"):
            return response

        # Only include debug info for text/html pages not accessed via Ajax
        if 'text/html' not in response['Content-Type']:
            return response
        if request.is_ajax():
            return response
        if response.status_code != 200:
            return response

        templates = []
        for t in self.templates_used:
            if t.origin and t.origin.name:
                templates.append( (t.name, t.origin.name) )
            else:
                templates.append( (t.name, "no origin") )

        sql_queries = connection.queries[self.sql_offset_start:]
        # Reformat sql queries a bit
        sql_total = 0.0
        sql_counts = {}
        for query in sql_queries:
            raw_sql = query['sql']
            query['sql'] = reformat_sql(query['sql'])
            sql_total += float(query['time'])
            count = sql_counts.get(raw_sql,0) + 1
            sql_counts[raw_sql] = count
            if count > 1:
                query['count'] = mark_safe('<p>duplicate query count=%s</p>' % count)
            else:
                query['count'] = ''

        from django.core.urlresolvers import resolve
        view_func = resolve(request.META['PATH_INFO'])[0]

        view =  '%s.%s' % (view_func.__module__, view_func.__name__)

        vf = view_func
        breaker = 10
        while not hasattr(vf,'func_code'):
            if hasattr(vf,'view_func'):
                vf = vf.view_func
            else:
                break # somethings wrong about the assumptions of the decorator
            breaker = breaker - 1
            if breaker < 0:
                break
        if hasattr(vf,'func_code'):
            co = vf.func_code
            filename = co.co_filename
            lineno = co.co_firstlineno
            view = '- '.join([view, ':'.join([co.co_filename, str(co.co_firstlineno)])])

        debug_content = Template(TEMPLATE).render(Context({
            'debug': settings.DEBUG,
            'server_time': time.time() - self.time_started,
            'templates': templates,
            'sql': sql_queries,
            'sql_total': sql_total,
            'num_queries' : len(sql_queries),
            'template_dirs': settings.TEMPLATE_DIRS,
            'view': view
        }))

        content = response.content
        response.content = force_unicode(content).replace('</body>', debug_content)

        return response

    def _storeRenderedTemplates(self, **kwargs):
        template = kwargs.get('template')
        if(template):
            self.templates_used.append(template)
        context = kwargs.get('context')
        if(context):
            self.contexts_used.append(context)

def reformat_sql(sql):
    if sql:
        import sqlparse
        sql = sqlparse.format(sql, reindent=True, keyword_case='upper')
    return sql

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 2 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 6 months ago

Comments

sleepycal (on July 13, 2011):

Have you considered using DDT (Django Debug Toolbar) ??

#

Please login first before commenting.