view src/savane/middleware/debug.py @ 101:7e2908e2a8e6

Add debug footer with SQL stats
author Sylvain Beucler <beuc@beuc.net>
date Sat, 01 Aug 2009 11:00:18 +0200
parents
children 70abe9b6819e
line wrap: on
line source

# http://www.djangosnippets.org/snippets/766/
# http://www.djangosnippets.org/snippets/1033/
# Author: simon (May 21, 2008)
# Author: crucialfelix (September 7, 2008)
# Copyright (C) 2009  Sylvain Beucler
# 
# I hate legal-speak as much as anybody, but on a site which is geared
# toward sharing code there has to be at least a little bit of it, so
# here goes:
# 
# By creating an account here you agree to three things:
# 
#   1. That you will only post code which you wrote yourself and that
#   you have the legal right to release under these terms.
# 
#   2. That you grant any third party who sees the code you post a
#   royalty-free, non-exclusive license to copy and distribute that
#   code and to make and distribute derivative works based on that
#   code. You may include license terms in snippets you post, if you
#   wish to use a particular license (such as the BSD license or GNU
#   GPL), but that license must permit royalty-free copying,
#   distribution and modification of the code to which it is applied.
# 
#   3. That if you post code of which you are not the author or for
#   which you do not have the legal right to distribute according to
#   these terms, you will indemnify and hold harmless the operators of
#   this site and any third parties who are exposed to liability as a
#   result of your actions.
#
# If you can't legally agree to these terms, or don't want to, you
# cannot create an account here.

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

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" 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 %}
    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</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

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):
        # 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'] = reformat_sql(query['sql'])
            sql_total += float(query['time'])

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

        if hasattr(view_func,'view_func'):
            # it the view_func has a view_func then its a decorator
            co = view_func.view_func.func_code
        else:
            co = view_func.func_code
            
        view =  '%s.%s' % (view_func.__module__, view_func.__name__)
        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,
            'view': view
        }))

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

        return response

    def _storeRenderedTemplates(self, **kwargs):
        #signal=signal, sender=sender, template=template, context=context):
        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):
    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