based on Snippet 799 but added code inspection capabilities. Credit goes to django-logging for the actual inspection code.
Got the idea from the This Week in Django Podcast ;)
This adds the filename, lineno, functionname and the actual python-code line which caused a sql statement to be executed.
Note that i am adding keys to 'connection.queries' dict on request, which may be dangerous, so use with care!
The code inspection functionality can be toggled via FRAME_INSPECT.
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 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 | #!/usr/bin/env python
# -*- coding: utf-8 -*-
import time
from django.dispatch import dispatcher
from django.core.signals import request_started
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
# Frame inspection code credit goes to django-logging (http://code.google.com/p/django-logging/)
FRAME_INSPECT = True
if FRAME_INSPECT:
import inspect
import django
from django.contrib import admin
_django_path = django.__file__.split('__init__')[0]
_admin_path = admin.__file__.split('__init__')[0]
USE_PYGMENTS = False
try:
import textwrap
from pygments import highlight
from pygments.formatters import HtmlFormatter
from pygments.lexers import SqlLexer
from pygments.lexers import PythonLexer
from pygments.styles import get_style_by_name
USE_PYGMENTS = True
except ImportError:
pass
if USE_PYGMENTS:
WRAP = 120
SQL_STYLE = get_style_by_name('colorful')
PYTHON_STYLE = get_style_by_name('friendly')
HEIGHT = '240px' # or '100%' if full height is wished
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; {{ height }} margin: 12px; overflow: auto;"
id="debugbox">
<p>Server-time taken: {{ server_time|floatformat:"5" }} seconds</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><samp>{{ template }}</samp></li>
{% endfor %}
</ol>
{% else %}
None
{% endif %}
<p>SQL executed:</p>
{% if sql %}
<ol>
{% for query in sql %}
<li><pre>{{ query.sql|linebreaksbr }}</pre>
<p>took {{ query.time|floatformat:"3" }} seconds<br/>
{% if inspect %}module: <code>{{ query.module }}</code><br/>
file: <samp>{{ query.filename }}</samp><br/>
line: <code>{{ query.lineno }}</code><br/>
function: <code>{{ query.function }}</code>
<pre>{{ query.source|linebreaksbr }}</pre>{% endif %}</p>
</li>
{% endfor %}
</ol>
<p>Total SQL time: {{ sql_total }}</p>
{% else %}
None
{% 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
if FRAME_INSPECT:
class SqlLoggingList(list):
def append(self, object):
# Try to find the meaningful frame, rather than just using one from
# the innards of the Django DB code.
frame = inspect.currentframe().f_back
while frame.f_back and frame.f_code.co_filename.startswith(_django_path):
if frame.f_code.co_filename.startswith(_admin_path):
break
frame = frame.f_back
object['filename'] = frame.f_code.co_filename
object['lineno'] = frame.f_lineno
object['function'] = frame.f_code.co_name
try:
object['module'] = inspect.getmodule(frame).__name__
except:
object['module'] = "unknown"
try:
source_lines = inspect.getsourcelines(frame)
object['source'] = highlight_python(source_lines[0][object['lineno']-source_lines[1]])
except:
object['source'] = "N/A"
list.append(self, object)
class DebugFooter(object):
def process_request(self, request):
self.time_started = time.time()
self.templates_used = []
self.contexts_used = []
self.sql_offset_start = len(connection.queries)
if FRAME_INSPECT:
connection.queries = SqlLoggingList(connection.queries)
dispatcher.connect(
self._storeRenderedTemplates, signal=template_rendered
)
def process_response(self, request, 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 not settings.DEBUG:
return response
if response.status_code != 200:
return response
templates = [
(t.name, t.origin and t.origin.name or 'No origin')
for t in self.templates_used
]
sql_queries = connection.queries[self.sql_offset_start:]
# Reformat sql queries a bit
sql_total = 0.0
for query in sql_queries:
query['sql'] = highlight_sql(query['sql'])
sql_total += float(query['time'])
#import pdb; pdb.set_trace()
debug_content = Template(TEMPLATE).render(Context({
'server_time': time.time() - self.time_started,
'templates': templates,
'sql': sql_queries,
'sql_total': sql_total,
'template_dirs': settings.TEMPLATE_DIRS,
'height': 'height: %s;' % (HEIGHT),
'inspect': FRAME_INSPECT,
}))
content = response.content
response.content = force_unicode(content).replace('</body>', debug_content)
#import pdb; pdb.set_trace()
return response
def _storeRenderedTemplates(self, signal, sender, template, context):
self.templates_used.append(template)
self.contexts_used.append(context)
def highlight_sql(sql):
if USE_PYGMENTS:
sql = mark_safe(highlight(textwrap.fill(sql, WRAP),
SqlLexer(encoding='utf-8'),
HtmlFormatter(encoding='utf-8',
noclasses=True,
style=SQL_STYLE)
)
)
else:
sql = reformat_sql(sql)
return sql
def reformat_sql(sql):
sql = sql.replace('`,`', '`, `')
sql = sql.replace('` FROM `', '` \n FROM `')
sql = sql.replace('` WHERE ', '` \n WHERE ')
sql = sql.replace(' ORDER BY ', ' \n ORDER BY ')
return sql
def highlight_python(source):
if USE_PYGMENTS:
source = mark_safe(highlight(textwrap.fill(source, WRAP),
PythonLexer(encoding='utf-8'),
HtmlFormatter(encoding='utf-8',
noclasses=True,
style=PYTHON_STYLE)
)
)
return source
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 3 weeks ago
- Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
- Help text hyperlinks by sa2812 1 year, 6 months ago
Comments
Please login first before commenting.