aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile4
-rw-r--r--README.rst2
-rw-r--r--debug_toolbar/urls.py5
-rw-r--r--debug_toolbar/utils/__init__.py10
-rw-r--r--debug_toolbar/utils/tracking/db.py47
-rw-r--r--debug_toolbar/views.py26
-rw-r--r--example/urls.py10
-rw-r--r--runtests.py5
-rw-r--r--test_pgsql.py28
-rw-r--r--tests/tests.py16
-rw-r--r--tests/urls.py7
11 files changed, 130 insertions, 30 deletions
diff --git a/Makefile b/Makefile
index f140dd0..96eddf8 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,10 @@
# Make file to compress and join all JS files
all: compress_js compress_css
+test:
+ pip install Django
+ python runtests.py
+
compress_js:
java -jar ~/bin/yuicompressor.jar debug_toolbar/static/debug_toolbar/js/jquery.js > debug_toolbar/static/debug_toolbar/js/toolbar.min.js
java -jar ~/bin/yuicompressor.jar debug_toolbar/static/debug_toolbar/js/toolbar.js >> debug_toolbar/static/debug_toolbar/js/toolbar.min.js
diff --git a/README.rst b/README.rst
index 17659f1..6cbac7d 100644
--- a/README.rst
+++ b/README.rst
@@ -120,7 +120,7 @@ The debug toolbar has two settings that can be set in ``settings.py``:
If not set or set to None, the debug_toolbar
middleware will use its built-in show_toolbar method for determining whether
the toolbar should show or not. The default checks are that DEBUG must be
- set to True or the IP of the request must be in INTERNAL_IPS. You can
+ set to True and the IP of the request must be in INTERNAL_IPS. You can
provide your own method for displaying the toolbar which contains your
custom logic. This method should return True or False.
diff --git a/debug_toolbar/urls.py b/debug_toolbar/urls.py
index 1446d99..0053ae7 100644
--- a/debug_toolbar/urls.py
+++ b/debug_toolbar/urls.py
@@ -4,7 +4,10 @@ URLpatterns for the debug toolbar.
These should not be loaded explicitly; the debug toolbar middleware will patch
this into the urlconf for the request.
"""
-from django.conf.urls.defaults import *
+try:
+ from django.conf.urls import patterns, url
+except ImportError: # django < 1.4
+ from django.conf.urls.defaults import patterns, url
_PREFIX = '__debug__'
diff --git a/debug_toolbar/utils/__init__.py b/debug_toolbar/utils/__init__.py
index bfb485c..2d2ff10 100644
--- a/debug_toolbar/utils/__init__.py
+++ b/debug_toolbar/utils/__init__.py
@@ -56,9 +56,15 @@ def render_stacktrace(trace):
stacktrace = []
for frame in trace:
params = map(escape, frame[0].rsplit(os.path.sep, 1) + list(frame[1:]))
+ params_dict = dict((unicode(idx), v) for idx, v in enumerate(params))
try:
- stacktrace.append(u'<span class="path">{0}/</span><span class="file">{1}</span> in <span class="func">{3}</span>(<span class="lineno">{2}</span>)\n <span class="code">{4}</span>'.format(*params))
- except IndexError:
+ stacktrace.append(u'<span class="path">%(0)s/</span>'
+ u'<span class="file">%(1)s</span>'
+ u' in <span class="func">%(3)s</span>'
+ u'(<span class="lineno">%(2)s</span>)\n'
+ u' <span class="code">%(4)s</span>'
+ % params_dict)
+ except KeyError:
# This frame doesn't have the expected format, so skip it and move on to the next one
continue
return mark_safe('\n'.join(stacktrace))
diff --git a/debug_toolbar/utils/tracking/db.py b/debug_toolbar/utils/tracking/db.py
index 4d87090..0ff3359 100644
--- a/debug_toolbar/utils/tracking/db.py
+++ b/debug_toolbar/utils/tracking/db.py
@@ -5,13 +5,22 @@ from threading import local
from django.conf import settings
from django.template import Node
-from django.utils import simplejson
from django.utils.encoding import force_unicode, smart_str
-from django.utils.hashcompat import sha_constructor
from debug_toolbar.utils import ms_from_timedelta, tidy_stacktrace, \
get_template_info, get_stack
from debug_toolbar.utils.compat.db import connections
+
+try:
+ import json
+except ImportError: # python < 2.6
+ from django.utils import simplejson as json
+
+try:
+ from hashlib import sha1
+except ImportError: # python < 2.5
+ from django.utils.hashcompat import sha_constructor as sha1
+
# TODO:This should be set in the toolbar loader as a default and panels should
# get a copy of the toolbar object with access to its config dictionary
SQL_WARNING_THRESHOLD = getattr(settings, 'DEBUG_TOOLBAR_CONFIG', {}) \
@@ -82,12 +91,25 @@ class NormalCursorWrapper(object):
for key, value in params.iteritems())
return map(self._quote_expr, params)
+ def _decode(self, param):
+ try:
+ return force_unicode(param, strings_only=True)
+ except UnicodeDecodeError:
+ return '(encoded string)'
+
def execute(self, sql, params=()):
- __traceback_hide__ = True
start = datetime.now()
try:
return self.cursor.execute(sql, params)
finally:
+ # FIXME: Sometimes connections which are not in the connections
+ # dict are used (for example in test database destroying).
+ # The code below (at least get_transaction_id(alias) needs to have
+ # the connection in the connections dict. It would be good to
+ # not have this requirement at all, but for now lets just skip
+ # these connections.
+ if self.db.alias not in connections:
+ return
stop = datetime.now()
duration = ms_from_timedelta(stop - start)
enable_stacktraces = getattr(settings,
@@ -98,10 +120,8 @@ class NormalCursorWrapper(object):
stacktrace = []
_params = ''
try:
- _params = simplejson.dumps(
- [force_unicode(x, strings_only=True) for x in params]
- )
- except TypeError:
+ _params = json.dumps(map(self._decode, params))
+ except Exception:
pass # object not JSON serializable
template_info = None
@@ -119,7 +139,7 @@ class NormalCursorWrapper(object):
del cur_frame
alias = getattr(self.db, 'alias', 'default')
- conn = connections[alias].connection
+ conn = self.db.connection
# HACK: avoid imports
if conn:
engine = conn.__class__.__module__.split('.', 1)[0]
@@ -134,7 +154,7 @@ class NormalCursorWrapper(object):
'duration': duration,
'raw_sql': sql,
'params': _params,
- 'hash': sha_constructor(settings.SECRET_KEY \
+ 'hash': sha1(settings.SECRET_KEY \
+ smart_str(sql) \
+ _params).hexdigest(),
'stacktrace': stacktrace,
@@ -146,10 +166,17 @@ class NormalCursorWrapper(object):
}
if engine == 'psycopg2':
+ # If an erroneous query was ran on the connection, it might
+ # be in a state where checking isolation_level raises an
+ # exception.
+ try:
+ iso_level = conn.isolation_level
+ except conn.InternalError:
+ iso_level = 'unknown'
params.update({
'trans_id': self.logger.get_transaction_id(alias),
'trans_status': conn.get_transaction_status(),
- 'iso_level': conn.isolation_level,
+ 'iso_level': iso_level,
'encoding': conn.encoding,
})
diff --git a/debug_toolbar/views.py b/debug_toolbar/views.py
index be917ff..4b4ebc9 100644
--- a/debug_toolbar/views.py
+++ b/debug_toolbar/views.py
@@ -9,11 +9,19 @@ import django.views.static
from django.conf import settings
from django.http import HttpResponseBadRequest
from django.shortcuts import render_to_response
-from django.utils import simplejson
-from django.utils.hashcompat import sha_constructor
from debug_toolbar.utils.compat.db import connections
+try:
+ import json
+except ImportError: # python < 2.6
+ from django.utils import simplejson as json
+
+try:
+ from hashlib import sha1
+except ImportError: # python < 2.5
+ from django.utils.hashcompat import sha_constructor as sha1
+
class InvalidSQLError(Exception):
def __init__(self, value):
@@ -37,11 +45,11 @@ def sql_select(request):
sql = request.GET.get('sql', '')
params = request.GET.get('params', '')
alias = request.GET.get('alias', 'default')
- hash = sha_constructor(settings.SECRET_KEY + sql + params).hexdigest()
+ hash = sha1(settings.SECRET_KEY + sql + params).hexdigest()
if hash != request.GET.get('hash', ''):
return HttpResponseBadRequest('Tamper alert') # SQL Tampering alert
if sql.lower().strip().startswith('select'):
- params = simplejson.loads(params)
+ params = json.loads(params)
cursor = connections[alias].cursor()
cursor.execute(sql, params)
headers = [d[0] for d in cursor.description]
@@ -72,11 +80,11 @@ def sql_explain(request):
sql = request.GET.get('sql', '')
params = request.GET.get('params', '')
alias = request.GET.get('alias', 'default')
- hash = sha_constructor(settings.SECRET_KEY + sql + params).hexdigest()
+ hash = sha1(settings.SECRET_KEY + sql + params).hexdigest()
if hash != request.GET.get('hash', ''):
return HttpResponseBadRequest('Tamper alert') # SQL Tampering alert
if sql.lower().strip().startswith('select'):
- params = simplejson.loads(params)
+ params = json.loads(params)
cursor = connections[alias].cursor()
conn = connections[alias].connection
@@ -87,6 +95,8 @@ def sql_explain(request):
# EXPLAIN QUERY PLAN dumps a more human-readable summary
# See http://www.sqlite.org/lang_explain.html for details
cursor.execute("EXPLAIN QUERY PLAN %s" % (sql,), params)
+ elif engine == "psycopg2":
+ cursor.execute("EXPLAIN ANALYZE %s" % (sql,), params)
else:
cursor.execute("EXPLAIN %s" % (sql,), params)
@@ -118,11 +128,11 @@ def sql_profile(request):
sql = request.GET.get('sql', '')
params = request.GET.get('params', '')
alias = request.GET.get('alias', 'default')
- hash = sha_constructor(settings.SECRET_KEY + sql + params).hexdigest()
+ hash = sha1(settings.SECRET_KEY + sql + params).hexdigest()
if hash != request.GET.get('hash', ''):
return HttpResponseBadRequest('Tamper alert') # SQL Tampering alert
if sql.lower().strip().startswith('select'):
- params = simplejson.loads(params)
+ params = json.loads(params)
cursor = connections[alias].cursor()
result = None
headers = None
diff --git a/example/urls.py b/example/urls.py
index 25acc31..d8bed27 100644
--- a/example/urls.py
+++ b/example/urls.py
@@ -1,15 +1,15 @@
from django.conf import settings
from django.conf.urls.defaults import *
from django.contrib import admin
-from django.views.generic.simple import direct_to_template
+from django.views.generic import TemplateView
admin.autodiscover()
urlpatterns = patterns('',
- (r'^$', direct_to_template, {'template': 'index.html'}),
- (r'^jquery/index/$', direct_to_template, {'template': 'jquery/index.html'}),
- (r'^mootools/index/$', direct_to_template, {'template': 'mootools/index.html'}),
- (r'^prototype/index/$', direct_to_template, {'template': 'prototype/index.html'}),
+ (r'^$', TemplateView.as_view(template_name='index.html')),
+ (r'^jquery/index/$', TemplateView.as_view(template_name='jquery/index.html')),
+ (r'^mootools/index/$', TemplateView.as_view(template_name='mootools/index.html')),
+ (r'^prototype/index/$', TemplateView.as_view(template_name='prototype/index.html')),
(r'^admin/', include(admin.site.urls)),
)
diff --git a/runtests.py b/runtests.py
index 3432f8b..ed49824 100644
--- a/runtests.py
+++ b/runtests.py
@@ -1,11 +1,14 @@
#!/usr/bin/env python
import sys
+import os
from os.path import dirname, abspath
from optparse import OptionParser
from django.conf import settings, global_settings
-if not settings.configured:
+# For convenience configure settings if they are not pre-configured or if we
+# haven't been provided settings to use by environment variable.
+if not settings.configured and not os.environ.get('DJANGO_SETTINGS_MODULE'):
settings.configure(
DATABASES={
'default': {
diff --git a/test_pgsql.py b/test_pgsql.py
new file mode 100644
index 0000000..28c0178
--- /dev/null
+++ b/test_pgsql.py
@@ -0,0 +1,28 @@
+from django.conf import global_settings
+DATABASES={
+ 'default': {
+ 'ENGINE': 'django.db.backends.postgresql_psycopg2',
+ # Edit the below settings before use...
+ 'USER': '',
+ 'NAME': '',
+ 'HOST': '',
+ 'PASSWORD': '',
+ }
+}
+INSTALLED_APPS=[
+ 'django.contrib.auth',
+ 'django.contrib.admin',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+
+ 'debug_toolbar',
+
+ 'tests',
+]
+MIDDLEWARE_CLASSES=global_settings.MIDDLEWARE_CLASSES + (
+ 'debug_toolbar.middleware.DebugToolbarMiddleware',
+)
+ROOT_URLCONF=''
+DEBUG=False
+SITE_ID=1
diff --git a/tests/tests.py b/tests/tests.py
index ea2938d..f76f1ab 100644
--- a/tests/tests.py
+++ b/tests/tests.py
@@ -1,10 +1,13 @@
+from __future__ import with_statement
import thread
from django.conf import settings
from django.contrib.auth.models import User
+from django.db import connection
from django.http import HttpResponse
from django.test import TestCase, RequestFactory
from django.template import Template, Context
+from django.utils import unittest
from debug_toolbar.middleware import DebugToolbarMiddleware
from debug_toolbar.panels.sql import SQLDebugPanel
@@ -214,6 +217,19 @@ class SQLPanelTestCase(BaseTestCase):
# ensure the stacktrace is populated
self.assertTrue(len(query[1]['stacktrace']) > 0)
+ @unittest.skipUnless(connection.vendor=='postgresql',
+ 'Test valid only on PostgreSQL')
+ def test_erroneous_query(self):
+ """
+ Test that an error in the query isn't swallowed by the middleware.
+ """
+ from django.db import connection
+ from django.db.utils import DatabaseError
+ try:
+ connection.cursor().execute("erroneous query")
+ except DatabaseError as e:
+ self.assertTrue('erroneous query' in str(e))
+
def test_disable_stacktraces(self):
panel = self.toolbar.get_panel(SQLDebugPanel)
self.assertEquals(len(panel._queries), 0)
diff --git a/tests/urls.py b/tests/urls.py
index a556703..778f417 100644
--- a/tests/urls.py
+++ b/tests/urls.py
@@ -4,14 +4,17 @@ URLpatterns for the debug toolbar.
These should not be loaded explicitly; the debug toolbar middleware will patch
this into the urlconf for the request.
"""
-from django.conf.urls.defaults import *
from django.contrib import admin
+try:
+ from django.conf.urls import patterns, url
+except ImportError: # django < 1.4
+ from django.conf.urls.defaults import patterns, url
admin.autodiscover()
urlpatterns = patterns('',
# This pattern should be last to ensure tests still work
- url(r'^resolving1/(.+)/(.+)/$', 'tests.views.resolving_view', name = 'positional-resolving'),
+ url(r'^resolving1/(.+)/(.+)/$', 'tests.views.resolving_view', name='positional-resolving'),
url(r'^resolving2/(?P<arg1>.+)/(?P<arg2>.+)/$', 'tests.views.resolving_view'),
url(r'^resolving3/(.+)/$', 'tests.views.resolving_view', { 'arg2' : 'default' }),
url(r'^execute_sql/$', 'tests.views.execute_sql'),