From bb68c8efe90a6f818c2c26f36ef0e02c91ed6158 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Marek=20St=C4=99pniowski?= Date: Tue, 9 Sep 2008 15:50:45 +0200 Subject: [PATCH 1/1] Added debug_toolbar app to project. --- apps/debug_toolbar/__init__.py | 0 apps/debug_toolbar/middleware.py | 61 ++++++ apps/debug_toolbar/models.py | 0 apps/debug_toolbar/panels/__init__.py | 33 +++ apps/debug_toolbar/panels/cache.py | 102 ++++++++++ apps/debug_toolbar/panels/headers.py | 42 ++++ apps/debug_toolbar/panels/http_vars.py | 24 +++ apps/debug_toolbar/panels/profiler.py | 53 +++++ apps/debug_toolbar/panels/sql.py | 79 ++++++++ apps/debug_toolbar/panels/templates.py | 60 ++++++ apps/debug_toolbar/panels/timer.py | 21 ++ apps/debug_toolbar/panels/version.py | 17 ++ apps/debug_toolbar/settings.py | 11 + .../templates/debug_toolbar/base.html | 191 ++++++++++++++++++ .../templates/debug_toolbar/panels/cache.html | 60 ++++++ .../debug_toolbar/panels/headers.html | 19 ++ .../debug_toolbar/panels/http_vars.html | 100 +++++++++ .../debug_toolbar/panels/profiler.html | 28 +++ .../templates/debug_toolbar/panels/sql.html | 19 ++ .../debug_toolbar/panels/sql_explain.html | 39 ++++ .../debug_toolbar/panels/templates.html | 18 ++ apps/debug_toolbar/toolbar/__init__.py | 0 apps/debug_toolbar/toolbar/loader.py | 49 +++++ wolnelektury/settings.py | 17 +- 24 files changed, 1038 insertions(+), 5 deletions(-) create mode 100644 apps/debug_toolbar/__init__.py create mode 100644 apps/debug_toolbar/middleware.py create mode 100644 apps/debug_toolbar/models.py create mode 100644 apps/debug_toolbar/panels/__init__.py create mode 100644 apps/debug_toolbar/panels/cache.py create mode 100644 apps/debug_toolbar/panels/headers.py create mode 100644 apps/debug_toolbar/panels/http_vars.py create mode 100644 apps/debug_toolbar/panels/profiler.py create mode 100644 apps/debug_toolbar/panels/sql.py create mode 100644 apps/debug_toolbar/panels/templates.py create mode 100644 apps/debug_toolbar/panels/timer.py create mode 100644 apps/debug_toolbar/panels/version.py create mode 100644 apps/debug_toolbar/settings.py create mode 100644 apps/debug_toolbar/templates/debug_toolbar/base.html create mode 100644 apps/debug_toolbar/templates/debug_toolbar/panels/cache.html create mode 100644 apps/debug_toolbar/templates/debug_toolbar/panels/headers.html create mode 100644 apps/debug_toolbar/templates/debug_toolbar/panels/http_vars.html create mode 100644 apps/debug_toolbar/templates/debug_toolbar/panels/profiler.html create mode 100644 apps/debug_toolbar/templates/debug_toolbar/panels/sql.html create mode 100644 apps/debug_toolbar/templates/debug_toolbar/panels/sql_explain.html create mode 100644 apps/debug_toolbar/templates/debug_toolbar/panels/templates.html create mode 100644 apps/debug_toolbar/toolbar/__init__.py create mode 100644 apps/debug_toolbar/toolbar/loader.py diff --git a/apps/debug_toolbar/__init__.py b/apps/debug_toolbar/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/debug_toolbar/middleware.py b/apps/debug_toolbar/middleware.py new file mode 100644 index 000000000..2ef7f1b72 --- /dev/null +++ b/apps/debug_toolbar/middleware.py @@ -0,0 +1,61 @@ +""" +Debug Toolbar middleware +""" +import re +from django.conf import settings +from django.utils.encoding import smart_str +from debug_toolbar.toolbar.loader import DebugToolbar + +_HTML_TYPES = ('text/html', 'application/xhtml+xml') +_END_HEAD_RE = re.compile(r'', re.IGNORECASE) +_END_BODY_RE = re.compile(r'', re.IGNORECASE) + +class DebugToolbarMiddleware(object): + """ + Middleware to set up Debug Toolbar on incoming request and render toolbar + on outgoing response. + """ + def __init__(self): + self.debug_toolbar = None + + def show_toolbar(self, request): + if not settings.DEBUG: + return False + if not request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS: + return False + return True + + def process_request(self, request): + if self.show_toolbar(request): + self.debug_toolbar = DebugToolbar(request) + self.debug_toolbar.load_panels() + debug = request.GET.get('djDebug') + # kinda ugly, needs changes to the loader to optimize + for panel in self.debug_toolbar.panels: + response = panel.process_request(request) + if not response: + if debug == panel.name: + response = panel.process_ajax(request) + if response: + response.skip_debug_response = True + return response + + def process_view(self, request, callback, callback_args, callback_kwargs): + if self.show_toolbar(request): + for panel in self.debug_toolbar.panels: + cb = panel.process_view(request, callback, callback_args, callback_kwargs) + if cb: + callback = cb + return callback + + def process_response(self, request, response): + if self.show_toolbar(request) and not getattr(response, 'skip_debug_response', False): + if response['Content-Type'].split(';')[0] in _HTML_TYPES and not request.is_ajax(): + # Saving this here in case we ever need to inject into + #response.content = _END_HEAD_RE.sub(smart_str(self.debug_toolbar.render_styles() + "%s" % match.group()), response.content) + for panel in self.debug_toolbar.panels: + nr = panel.process_response(request, response) + # Incase someone forgets `return response` + if nr: response = nr + response.content = _END_BODY_RE.sub(smart_str('' + self.debug_toolbar.render_toolbar()), response.content) + return response diff --git a/apps/debug_toolbar/models.py b/apps/debug_toolbar/models.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/debug_toolbar/panels/__init__.py b/apps/debug_toolbar/panels/__init__.py new file mode 100644 index 000000000..704ae1ed3 --- /dev/null +++ b/apps/debug_toolbar/panels/__init__.py @@ -0,0 +1,33 @@ +"""Base DebugPanel class""" + +class DebugPanel(object): + """ + Base class for debug panels. + """ + # name = Base + + def __init__(self, request): + self.request = request + + def process_request(self, request): + return None + + def process_response(self, request, response): + return response + + def process_view(self, request, callback, callback_args, callback_kwargs): + return None + + def dom_id(self): + return 'djDebug%sPanel' % (self.name.replace(' ', '')) + + def title(self): + raise NotImplementedError + + def url(self): + raise NotImplementedError + + def content(self): + # TODO: This is a bit flaky in that panel.content() returns a string + # that gets inserted into the toolbar HTML template. + raise NotImplementedError diff --git a/apps/debug_toolbar/panels/cache.py b/apps/debug_toolbar/panels/cache.py new file mode 100644 index 000000000..4fef6bf7b --- /dev/null +++ b/apps/debug_toolbar/panels/cache.py @@ -0,0 +1,102 @@ +from debug_toolbar.panels import DebugPanel +try: from cStringIO import StringIO +except ImportError: import StringIO +from django.core import cache +from django.core.cache.backends.base import BaseCache +import time +import inspect +import os.path +from django.template.loader import render_to_string + +class CacheStatTracker(BaseCache): + """A small class used to track cache calls.""" + def __init__(self, cache): + self.cache = cache + self.reset() + + def reset(self): + self.calls = [] + self.hits = 0 + self.misses = 0 + self.sets = 0 + self.gets = 0 + self.get_many = 0 + self.deletes = 0 + self.total_time = 0 + + def _get_func_info(self): + stack = inspect.stack()[2] + return (os.path.basename(stack[1]), stack[2], stack[3], stack[4]) + + def get(self, key, default=None): + t = time.time() + value = self.cache.get(key, default) + this_time = time.time()-t + self.total_time += this_time*1000 + if value is None: + self.misses += 1 + else: + self.hits += 1 + self.gets += 1 + self.calls.append((this_time, 'get', (key,), self._get_func_info())) + return value + + def set(self, key, value, timeout=None): + t = time.time() + self.cache.set(key, value, timeout) + this_time = time.time()-t + self.total_time += this_time*1000 + self.sets += 1 + self.calls.append((this_time, 'set', (key, value, timeout), self._get_func_info())) + + def delete(self, key): + t = time.time() + self.instance.delete(key, value) + this_time = time.time()-t + self.total_time += this_time*1000 + self.deletes += 1 + self.calls.append((this_time, 'delete', (key,), self._get_func_info())) + + def get_many(self, keys): + t = time.time() + results = self.cache.get_many(keys) + this_time = time.time()-t + self.total_time += this_time*1000 + self.get_many += 1 + for key, value in results.iteritems(): + if value is None: + self.misses += 1 + else: + self.hits += 1 + self.calls.append((this_time, 'get_many', (keys,), self._get_func_info())) + +class CacheDebugPanel(DebugPanel): + """ + Panel that displays the cache statistics. + """ + name = 'Cache' + + def __init__(self, request): + # This is hackish but to prevent threading issues + # is somewhat needed + if isinstance(cache.cache, CacheStatTracker): + cache.cache.reset() + self.cache = cache.cache + else: + self.cache = CacheStatTracker(cache.cache) + cache.cache = self.cache + super(CacheDebugPanel, self).__init__(request) + + def title(self): + return 'Cache: %.2fms' % self.cache.total_time + + def url(self): + return '' + + def content(self): + context = dict( + cache_calls = len(self.cache.calls), + cache_time = self.cache.total_time, + cache = self.cache, + ) + return render_to_string('debug_toolbar/panels/cache.html', context) \ No newline at end of file diff --git a/apps/debug_toolbar/panels/headers.py b/apps/debug_toolbar/panels/headers.py new file mode 100644 index 000000000..39126873a --- /dev/null +++ b/apps/debug_toolbar/panels/headers.py @@ -0,0 +1,42 @@ +from debug_toolbar.panels import DebugPanel +from django.template.loader import render_to_string + +class HeaderDebugPanel(DebugPanel): + """ + A panel to display HTTP headers. + """ + name = 'Header' + # List of headers we want to display + header_filter = [ + 'CONTENT_TYPE', + 'HTTP_ACCEPT', + 'HTTP_ACCEPT_CHARSET', + 'HTTP_ACCEPT_ENCODING', + 'HTTP_ACCEPT_LANGUAGE', + 'HTTP_CACHE_CONTROL', + 'HTTP_CONNECTION', + 'HTTP_HOST', + 'HTTP_KEEP_ALIVE', + 'HTTP_REFERER', + 'HTTP_USER_AGENT', + 'QUERY_STRING', + 'REMOTE_ADDR', + 'REMOTE_HOST', + 'REQUEST_METHOD', + 'SCRIPT_NAME', + 'SERVER_NAME', + 'SERVER_PORT', + 'SERVER_PROTOCOL', + 'SERVER_SOFTWARE', + ] + def title(self): + return 'HTTP Headers' + + def url(self): + return '' + + def content(self): + context = { + 'headers': dict([(k, self.request.META[k]) for k in self.header_filter if k in self.request.META]), + } + return render_to_string('debug_toolbar/panels/headers.html', context) \ No newline at end of file diff --git a/apps/debug_toolbar/panels/http_vars.py b/apps/debug_toolbar/panels/http_vars.py new file mode 100644 index 000000000..2bf1789c6 --- /dev/null +++ b/apps/debug_toolbar/panels/http_vars.py @@ -0,0 +1,24 @@ +from debug_toolbar.panels import DebugPanel +from django.template.loader import render_to_string + +class HttpVarsDebugPanel(DebugPanel): + """ + A panel to display HTTP variables (POST/GET). + """ + name = 'HttpVars' + # List of headers we want to display + + def title(self): + return 'HTTP Globals' + + def url(self): + return '' + + def content(self): + context = { + 'get': [(k, self.request.GET.getlist(k)) for k in self.request.GET.iterkeys()], + 'post': [(k, self.request.POST.getlist(k)) for k in self.request.POST.iterkeys()], + 'session': [(k, self.request.session.get(k)) for k in self.request.session.iterkeys()], + 'cookies': [(k, self.request.COOKIES.get(k)) for k in self.request.COOKIES.iterkeys()], + } + return render_to_string('debug_toolbar/panels/http_vars.html', context) \ No newline at end of file diff --git a/apps/debug_toolbar/panels/profiler.py b/apps/debug_toolbar/panels/profiler.py new file mode 100644 index 000000000..1c05c0816 --- /dev/null +++ b/apps/debug_toolbar/panels/profiler.py @@ -0,0 +1,53 @@ +from debug_toolbar.panels import DebugPanel +try: import cProfile as profile +except ImportError: import profile +import pstats +from django.template.loader import render_to_string + +class ProfilerDebugPanel(DebugPanel): + """ + Panel that displays the time a response took with cProfile output. + """ + name = 'Profiler' + + def __init__(self, request): + super(ProfilerDebugPanel, self).__init__(request) + + def process_response(self, request, response): + stats = pstats.Stats(self.profiler) + function_calls = [] + for func in stats.strip_dirs().sort_stats(1).fcn_list: + current = [] + if stats.stats[func][0] != stats.stats[func][1]: + current.append('%d/%d' % (stats.stats[func][1], stats.stats[func][0])) + else: + current.append(stats.stats[func][1]) + current.append(stats.stats[func][2]*1000) + current.append(stats.stats[func][2]*1000/stats.stats[func][1]) + current.append(stats.stats[func][3]*1000) + current.append(stats.stats[func][3]*1000/stats.stats[func][0]) + current.append(pstats.func_std_string(func)) + function_calls.append(current) + self.stats = stats + self.function_calls = function_calls + return response + + def process_view(self, request, callback, callback_args, callback_kwargs): + self.callback = callback + self.profiler = profile.Profile() + return self.profiler.runcall(callback, request, *callback_args, **callback_kwargs) + + def title(self): + return 'View: %.2fms' % (float(self.stats.total_tt)*1000,) + + def url(self): + return '' + + def content(self): + context = { + 'callback': self.callback.__name__, + 'module': self.callback.__module__, + 'stats': self.stats, + 'function_calls': self.function_calls, + } + return render_to_string('debug_toolbar/panels/profiler.html', context) \ No newline at end of file diff --git a/apps/debug_toolbar/panels/sql.py b/apps/debug_toolbar/panels/sql.py new file mode 100644 index 000000000..977e3155b --- /dev/null +++ b/apps/debug_toolbar/panels/sql.py @@ -0,0 +1,79 @@ +from debug_toolbar.panels import DebugPanel +from django.db import connection +from django.db.backends import util +from django.template.loader import render_to_string +from django.shortcuts import render_to_response +from django.http import HttpResponse +from django.utils import simplejson +from time import time + +class DatabaseStatTracker(util.CursorDebugWrapper): + """Replacement for CursorDebugWrapper which stores additional information + in `connection.queries`.""" + def execute(self, sql, params=()): + start = time() + try: + return self.cursor.execute(sql, params) + finally: + stop = time() + # We keep `sql` to maintain backwards compatibility + self.db.queries.append({ + 'sql': self.db.ops.last_executed_query(self.cursor, sql, params), + 'time': stop - start, + 'raw_sql': sql, + 'params': params, + }) + +util.CursorDebugWrapper = DatabaseStatTracker + +class SQLDebugPanel(DebugPanel): + """ + Panel that displays information about the SQL queries run while processing the request. + """ + name = 'SQL' + + def reformat_sql(self, sql): + sql = sql.replace('`,`', '`, `') + sql = sql.replace('` FROM `', '` \n FROM `') + sql = sql.replace('` WHERE ', '` \n WHERE ') + sql = sql.replace('`) WHERE ', '`) \n WHERE ') + sql = sql.replace(' ORDER BY ', ' \n ORDER BY ') + return sql + + def process_ajax(self, request): + action = request.GET.get('op') + if action == 'explain': + # XXX: loop through each sql statement to output explain? + sql = request.GET.get('sql', '').split(';')[0] + if sql.lower().startswith('select'): + if 'params' in request.GET: + params = simplejson.loads(request.GET['params']) + else: + params = [] + cursor = connection.cursor() + cursor.execute("EXPLAIN %s" % (sql,), params) + response = cursor.fetchall() + cursor.close() + context = {'explain': response, 'sql': self.reformat_sql(sql), 'params': params} + return render_to_response('debug_toolbar/panels/sql_explain.html', context) + else: + return HttpResponse('Invalid SQL', mimetype="text/plain", status_code=403) + + def process_request(self, request): + self.queries_offset = len(connection.queries) + + def process_response(self, request, response): + self.queries = connection.queries[self.queries_offset:] + self.total_time = sum(map(lambda q: float(q['time'])*1000, self.queries)) + return response + + def title(self): + return 'SQL: %.2fms (%d queries)' % (self.total_time, len(self.queries)) + + def url(self): + return '' + + def content(self): + queries = [(q['time'], q['sql'], q['raw_sql'], simplejson.dumps(q['params'])) for q in sorted(self.queries, key=lambda x: x['time'])[::-1]] + context = {'queries': queries} + return render_to_string('debug_toolbar/panels/sql.html', context) \ No newline at end of file diff --git a/apps/debug_toolbar/panels/templates.py b/apps/debug_toolbar/panels/templates.py new file mode 100644 index 000000000..ae773c688 --- /dev/null +++ b/apps/debug_toolbar/panels/templates.py @@ -0,0 +1,60 @@ +from debug_toolbar.panels import DebugPanel +from django.conf import settings +from django.dispatch import dispatcher +from django.core.signals import request_started +from django.test.signals import template_rendered +from django.template.loader import render_to_string + +# Based on http://www.djangosnippets.org/snippets/766/ +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=''): + old_template_init(self, template_string, origin, name) + self.origin = origin +Template.__init__ = new_template_init + +class TemplatesDebugPanel(DebugPanel): + """ + Panel that displays information about the SQL queries run while processing the request. + """ + name = 'Templates' + + def __init__(self, *args, **kwargs): + super(TemplatesDebugPanel, self).__init__(*args, **kwargs) + self.templates_used = [] + self.contexts_used = [] + template_rendered.connect(self._storeRenderedTemplates) + + def _storeRenderedTemplates(self, **kwargs): + template = kwargs.pop('template') + if template: + self.templates_used.append(template) + context = kwargs.pop('context') + if context: + self.contexts_used.append(context) + + def process_response(self, request, response): + self.templates = [ + (t.name, t.origin and t.origin.name or 'No origin') + for t in self.templates_used + ] + return response + + def title(self): + return 'Templates: %.2fms' + + def url(self): + return '' + + def content(self): + context = { + 'templates': self.templates, + 'template_dirs': settings.TEMPLATE_DIRS, + } + + return render_to_string('debug_toolbar/panels/templates.html', context) \ No newline at end of file diff --git a/apps/debug_toolbar/panels/timer.py b/apps/debug_toolbar/panels/timer.py new file mode 100644 index 000000000..d52019163 --- /dev/null +++ b/apps/debug_toolbar/panels/timer.py @@ -0,0 +1,21 @@ +import time +from debug_toolbar.panels import DebugPanel + +class TimerDebugPanel(DebugPanel): + """ + Panel that displays the time a response took. + """ + name = 'Timer' + + def __init__(self, request): + super(TimerDebugPanel, self).__init__(request) + self._start_time = time.time() + + def title(self): + return 'Time: %0.2fms' % ((time.time() - self._start_time) * 1000) + + def url(self): + return '' + + def content(self): + return '' diff --git a/apps/debug_toolbar/panels/version.py b/apps/debug_toolbar/panels/version.py new file mode 100644 index 000000000..7ea6543ed --- /dev/null +++ b/apps/debug_toolbar/panels/version.py @@ -0,0 +1,17 @@ +import django +from debug_toolbar.panels import DebugPanel + +class VersionDebugPanel(DebugPanel): + """ + Panel that displays the Django version. + """ + name = 'Version' + + def title(self): + return 'Version: %s' % (django.get_version()) + + def url(self): + return '' + + def content(self): + return '' diff --git a/apps/debug_toolbar/settings.py b/apps/debug_toolbar/settings.py new file mode 100644 index 000000000..4e8e5ba07 --- /dev/null +++ b/apps/debug_toolbar/settings.py @@ -0,0 +1,11 @@ +from django.conf import settings + +DEBUG_TOOLBAR_PANELS = getattr(settings, 'DEBUG_TOOLBAR_PANELS', ( + 'debug_toolbar.panels.version.VersionDebugPanel', + 'debug_toolbar.panels.profiler.ProfilerDebugPanel', + 'debug_toolbar.panels.sql.SQLDebugPanel', + 'debug_toolbar.panels.cache.CacheDebugPanel', + # 'debug_toolbar.panels.templates.TemplatesDebugPanel', + 'debug_toolbar.panels.headers.HeaderDebugPanel', + 'debug_toolbar.panels.http_vars.HttpVarsDebugPanel', +)) diff --git a/apps/debug_toolbar/templates/debug_toolbar/base.html b/apps/debug_toolbar/templates/debug_toolbar/base.html new file mode 100644 index 000000000..29ca16c50 --- /dev/null +++ b/apps/debug_toolbar/templates/debug_toolbar/base.html @@ -0,0 +1,191 @@ + + + +
+
+
    +
  • Django Debug
  • + {% for panel in panels %} +
  • + {% if panel.content %} + {{ panel.title }} + {% else %} + {{ panel.title }} + {% endif %} +
  • + {% endfor %} +
  • Hide Debug Toolbar
  • +
+
+ {% for panel in panels %} + {% with panel.content as content %} + {% if content %} +
+
+ Close + +
+
+ {{ content|safe }} +
+
+ {% endif %} + {% endwith %} + {% endfor %} +
+
+ Close + Back +
+
+
+
+
diff --git a/apps/debug_toolbar/templates/debug_toolbar/panels/cache.html b/apps/debug_toolbar/templates/debug_toolbar/panels/cache.html new file mode 100644 index 000000000..3d630b1ba --- /dev/null +++ b/apps/debug_toolbar/templates/debug_toolbar/panels/cache.html @@ -0,0 +1,60 @@ +
+ Cache Usage +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Total Calls{{ cache_calls }}Total Time{{ cache_time }}msHits{{ cache.hits }}Misses{{ cache.misses }}
gets{{ cache.gets }}sets{{ cache.sets }}deletes{{ cache.deletes }}get_many{{ cache.get_many }}
+{% if cache.calls %} +
+ Breakdown +
+ + + + + + + + + + + {% for query in cache.calls %} + + + + + + + {% endfor %} + +
Time (ms)TypeParametersFunction
{{ query.0|floatformat:"4" }}{{ query.1|escape }}{{ query.2|escape }}{{ query.3.0 }}:{{ query.3.1 }}({{ query.3.2|escape }}); {{ query.3.3.0|escape }}
+{% endif %} \ No newline at end of file diff --git a/apps/debug_toolbar/templates/debug_toolbar/panels/headers.html b/apps/debug_toolbar/templates/debug_toolbar/panels/headers.html new file mode 100644 index 000000000..fb253d6f1 --- /dev/null +++ b/apps/debug_toolbar/templates/debug_toolbar/panels/headers.html @@ -0,0 +1,19 @@ +
+ Request Headers +
+ + + + + + + + + {% for key, value in headers.iteritems %} + + + + + {% endfor %} + +
KeyValue
{{ key|escape }}{{ value|escape }}
\ No newline at end of file diff --git a/apps/debug_toolbar/templates/debug_toolbar/panels/http_vars.html b/apps/debug_toolbar/templates/debug_toolbar/panels/http_vars.html new file mode 100644 index 000000000..f7013dcd8 --- /dev/null +++ b/apps/debug_toolbar/templates/debug_toolbar/panels/http_vars.html @@ -0,0 +1,100 @@ +
request.GET
+{% if get %} + + + + + + + + + + + + + {% for key, value in get %} + + + + + {% endfor %} + +
KeyValue
{{ key|escape }}{{ value|join:", "|escape }}
+{% else %} +

No GET variables.

+{% endif %} +
request.POST
+{% if post %} + + + + + + + + + + + + + {% for key, value in post %} + + + + + {% endfor %} + +
KeyValue
{{ key|escape }}{{ value|join:", "|escape }}
+{% else %} +

No POST variables.

+{% endif %} +
request.session
+{% if session %} + + + + + + + + + + + + + {% for key, value in session %} + + + + + {% endfor %} + +
KeyValue
{{ key|escape }}{{ value|escape }}
+{% else %} +

No SESSION variables.

+{% endif %} +
request.COOKIES
+{% if cookies %} + + + + + + + + + + + + + {% for key, value in cookies %} + + + + + {% endfor %} + +
KeyValue
{{ key|escape }}{{ value|escape }}
+{% else %} +

No COOKIES variables.

+{% endif %} \ No newline at end of file diff --git a/apps/debug_toolbar/templates/debug_toolbar/panels/profiler.html b/apps/debug_toolbar/templates/debug_toolbar/panels/profiler.html new file mode 100644 index 000000000..41f692017 --- /dev/null +++ b/apps/debug_toolbar/templates/debug_toolbar/panels/profiler.html @@ -0,0 +1,28 @@ +
+ Profile Output +
+

`profile` module output for the view `{{ module|escape }}.{{ callback|escape }}`

+ + + + + + + + + + + + + {% for row in function_calls %} + + + + + + + + + {% endfor %} + +
CallsTotal Time (ms)Per Call (ms)Cumulative Time (ms)Per Call (ms)Function
{{ row.0 }}{{ row.1|floatformat:"4" }}{{ row.2|floatformat:"4" }}{{ row.3|floatformat:"4" }}{{ row.4|floatformat:"4" }}{{ row.5|escape }}
\ No newline at end of file diff --git a/apps/debug_toolbar/templates/debug_toolbar/panels/sql.html b/apps/debug_toolbar/templates/debug_toolbar/panels/sql.html new file mode 100644 index 000000000..28008e245 --- /dev/null +++ b/apps/debug_toolbar/templates/debug_toolbar/panels/sql.html @@ -0,0 +1,19 @@ +
+ SQL Queries +
+ + + + + + + + + {% for query in queries %} + + + + + {% endfor %} + +
Time (ms)Query
{{ query.0|floatformat:"4" }}{{ query.1|escape }}
\ No newline at end of file diff --git a/apps/debug_toolbar/templates/debug_toolbar/panels/sql_explain.html b/apps/debug_toolbar/templates/debug_toolbar/panels/sql_explain.html new file mode 100644 index 000000000..31dd10d61 --- /dev/null +++ b/apps/debug_toolbar/templates/debug_toolbar/panels/sql_explain.html @@ -0,0 +1,39 @@ +
SQL Explain
+
+
Query:
+
{{ sql|escape|linebreaksbr }}
+
Parameters:
+
{{ params|join:", "|escape }}
+
+ + + + + + + + + + + + + + + + + {% for row in explain %} + + + + + + + + + + + + + {% endfor %} + +
IDSelect TypeTableTypePossible KeysKeyKen LengthRefRowsExtra
{{ row.0|escape }}{{ row.1|escape }}{{ row.2|escape }}{{ row.3|escape }}{{ row.4|escape }}{{ row.5|escape }}{{ row.6|escape }}{{ row.7|escape }}{{ row.8|escape }}{{ row.9|escape }}
\ No newline at end of file diff --git a/apps/debug_toolbar/templates/debug_toolbar/panels/templates.html b/apps/debug_toolbar/templates/debug_toolbar/panels/templates.html new file mode 100644 index 000000000..09dd3ef47 --- /dev/null +++ b/apps/debug_toolbar/templates/debug_toolbar/panels/templates.html @@ -0,0 +1,18 @@ +
+ Templates +
+ + + + + + + + + {% for template in templates %} + + + + {% endfor %} + +
Time (ms)Template
{{ template }}
\ No newline at end of file diff --git a/apps/debug_toolbar/toolbar/__init__.py b/apps/debug_toolbar/toolbar/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/debug_toolbar/toolbar/loader.py b/apps/debug_toolbar/toolbar/loader.py new file mode 100644 index 000000000..416432470 --- /dev/null +++ b/apps/debug_toolbar/toolbar/loader.py @@ -0,0 +1,49 @@ +""" +The main DebugToolbar class that loads and renders the Toolbar. +""" +from django.template.loader import render_to_string +from debug_toolbar.settings import DEBUG_TOOLBAR_PANELS + +class DebugToolbar(object): + + def __init__(self, request): + self.request = request + self.panels = [] + self.panel_list = [] + self.content_list = [] + + def load_panels(self): + """ + Populate debug panel lists from settings.DEBUG_TOOLBAR_PANELS. + """ + from django.conf import settings + from django.core import exceptions + + for panel_path in DEBUG_TOOLBAR_PANELS: + try: + dot = panel_path.rindex('.') + except ValueError: + raise exceptions.ImproperlyConfigured, '%s isn\'t a debug panel module' % panel_path + panel_module, panel_classname = panel_path[:dot], panel_path[dot+1:] + try: + mod = __import__(panel_module, {}, {}, ['']) + except ImportError, e: + raise exceptions.ImproperlyConfigured, 'Error importing debug panel %s: "%s"' % (panel_module, e) + try: + panel_class = getattr(mod, panel_classname) + except AttributeError: + raise exceptions.ImproperlyConfigured, 'Toolbar Panel module "%s" does not define a "%s" class' % (panel_module, panel_classname) + + try: + panel_instance = panel_class(self.request) + except: + raise + continue # Some problem loading panel + + self.panels.append(panel_instance) + + def render_toolbar(self): + """ + Renders the overall Toolbar with panels inside. + """ + return render_to_string('debug_toolbar/base.html', {'panels': self.panels}) diff --git a/wolnelektury/settings.py b/wolnelektury/settings.py index c5b8d6797..294869355 100644 --- a/wolnelektury/settings.py +++ b/wolnelektury/settings.py @@ -68,17 +68,23 @@ TEMPLATE_CONTEXT_PROCESSORS = ( 'django.core.context_processors.request', ) -MIDDLEWARE_CLASSES = [ +MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.middleware.doc.XViewMiddleware', + 'debug_toolbar.middleware.DebugToolbarMiddleware', 'pagination.middleware.PaginationMiddleware', -] +) + +DEBUG_TOOLBAR_PANELS = ( + 'debug_toolbar.panels.version.VersionDebugPanel', + 'debug_toolbar.panels.sql.SQLDebugPanel', + 'debug_toolbar.panels.timer.TimerDebugPanel', + 'debug_toolbar.panels.headers.HeaderDebugPanel', +) -# If DEBUG is enabled add query log to bottom of every template -if DEBUG: - MIDDLEWARE_CLASSES.append('middleware.ProfileMiddleware') +INTERNAL_IPS = ('127.0.0.1', ) ROOT_URLCONF = 'urls' @@ -118,6 +124,7 @@ INSTALLED_APPS = ( 'pagination', 'chunks', 'compress', + 'debug_toolbar', 'catalogue', ) -- 2.20.1