Skip to content

Request Line is too large (400) sometimes #347

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Apr 24, 2013
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ August 2008.

The following is a list of much appreciated contributors:

Vladislav Polukhin <nuklea@gmail.com>
Reto Aebersold <aeby@atizo.com>
Andi Albrecht <albrecht.andi@gmail.com>
Chris Beaven <smileychris@gmail.com>
Expand Down
93 changes: 93 additions & 0 deletions debug_toolbar/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
from django import forms
from django.conf import settings
from django.core.exceptions import ValidationError

from debug_toolbar.utils.functional import cached_property
from debug_toolbar.utils.sql import reformat_sql

try:
import json
except ImportError:
from django.utils import simplejson as json

try:
from hashlib import sha1
except ImportError:
from django.utils.hashcompat import sha_constructor as sha1

from debug_toolbar.utils.compat.db import connections


class SQLSelectForm(forms.Form):
"""
Validate params

sql: urlencoded sql with positional arguments
params: JSON encoded parameter values
duration: time for SQL to execute passed in from toolbar just for redisplay
hash: the hash of (secret + sql + params) for tamper checking
"""
sql = forms.CharField()
params = forms.CharField()
alias = forms.CharField(required=False, initial='default')
duration = forms.FloatField()
hash = forms.CharField()

def __init__(self, *args, **kwargs):
initial = kwargs.get('initial', None)

if initial is not None:
initial['hash'] = self.make_hash(initial)

super(SQLSelectForm, self).__init__(*args, **kwargs)

for name in self.fields:
self.fields[name].widget = forms.HiddenInput()

def clean_sql(self):
value = self.cleaned_data['sql']

if not value.lower().strip().startswith('select'):
raise ValidationError("Only 'select' queries are allowed.")

return value

def clean_params(self):
value = self.cleaned_data['params']

try:
return json.loads(value)
except ValueError:
raise ValidationError('Is not valid JSON')

def clean_alias(self):
value = self.cleaned_data['alias']

if value not in connections:
raise ValidationError("Database alias '%s' not found" % value)

return value

def clean_hash(self):
hash = self.cleaned_data['hash']

if hash != self.make_hash(self.data):
raise ValidationError('Tamper alert')

return hash

def reformat_sql(self):
sql, params = self.cleaned_data['sql'], self.cleaned_data['params']
return reformat_sql(self.cursor.db.ops.last_executed_query(self.cursor, sql, params))

def make_hash(self, data):
params = settings.SECRET_KEY + data['sql'] + data['params']
return sha1(params).hexdigest()

@property
def connection(self):
return connections[self.cleaned_data['alias']]

@cached_property
def cursor(self):
return self.connection.cursor()
35 changes: 7 additions & 28 deletions debug_toolbar/panels/sql.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import re
import uuid
from copy import copy

from django.db.backends import BaseDatabaseWrapper
from django.utils.html import escape
from django.utils.translation import ugettext_lazy as _, ungettext_lazy as __

from debug_toolbar.forms import SQLSelectForm
from debug_toolbar.utils.compat.db import connections
from debug_toolbar.middleware import DebugToolbarMiddleware
from debug_toolbar.panels import DebugPanel
from debug_toolbar.utils import sqlparse, render_stacktrace
from debug_toolbar.utils import render_stacktrace
from debug_toolbar.utils.sql import reformat_sql
from debug_toolbar.utils.tracking.db import CursorWrapper
from debug_toolbar.utils.tracking import replace_call

Expand Down Expand Up @@ -170,6 +171,9 @@ def process_response(self, request, response):
query['iso_level'] = get_isolation_level_display(query['engine'], query['iso_level'])
if 'trans_status' in query:
query['trans_status'] = get_transaction_status_display(query['engine'], query['trans_status'])

query['form'] = SQLSelectForm(auto_id=None, initial=copy(query))

if query['sql']:
query['sql'] = reformat_sql(query['sql'])
query['rgb_color'] = self._databases[alias]['rgb_color']
Expand All @@ -193,28 +197,3 @@ def process_response(self, request, response):
'queries': [q for a, q in self._queries],
'sql_time': self._sql_time,
})


class BoldKeywordFilter(sqlparse.filters.Filter):
"""sqlparse filter to bold SQL keywords"""
def process(self, stack, stream):
"""Process the token stream"""
for token_type, value in stream:
is_keyword = token_type in sqlparse.tokens.Keyword
if is_keyword:
yield sqlparse.tokens.Text, '<strong>'
yield token_type, escape(value)
if is_keyword:
yield sqlparse.tokens.Text, '</strong>'


def swap_fields(sql):
return re.sub('SELECT</strong> (.*?) <strong>FROM', 'SELECT</strong> <a class="djDebugUncollapsed djDebugToggle" href="#">&bull;&bull;&bull;</a> ' +
'<a class="djDebugCollapsed djDebugToggle" href="#">\g<1></a> <strong>FROM', sql)


def reformat_sql(sql):
stack = sqlparse.engine.FilterStack()
stack.preprocess.append(BoldKeywordFilter()) # add our custom filter
stack.postprocess.append(sqlparse.filters.SerializerUnicode()) # tokens -> strings
return swap_fields(''.join(stack.run(sql)))
8 changes: 7 additions & 1 deletion debug_toolbar/static/debug_toolbar/css/toolbar.css
Original file line number Diff line number Diff line change
Expand Up @@ -229,11 +229,16 @@
vertical-align:top;
padding:2px 3px;
}
#djDebug .panelContent tbody td.time {
text-align: center;
}

#djDebug .panelContent thead th {
padding:1px 6px 1px 3px;
text-align:left;
font-weight:bold;
font-size:14px;
white-space: nowrap;
}
#djDebug .panelContent tbody th {
width:12em;
Expand Down Expand Up @@ -507,8 +512,9 @@
width: 14px;
padding-top: 3px;
}
#djdebug .panelcontent table .actions {
#djDebug .panelContent table .actions {
min-width: 70px;
white-space: nowrap;
}
#djdebug .panelcontent table .color {
width: 3px;
Expand Down
2 changes: 1 addition & 1 deletion debug_toolbar/static/debug_toolbar/css/toolbar.min.css

Large diffs are not rendered by default.

41 changes: 31 additions & 10 deletions debug_toolbar/static/debug_toolbar/js/toolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,41 @@ window.djdt = (function(window, document, jQuery) {
$('#djDebugToolbar li').removeClass('active');
return false;
});
$('#djDebug a.remoteCall').live('click', function() {
$('#djDebugWindow').load(this.href, function(response, status, xhr) {
if (status == "error") {
var message = '<div class="djDebugPanelTitle"><a class="djDebugClose djDebugBack" href="">Back</a><h3>'+xhr.status+': '+xhr.statusText+'</h3></div>';
$('#djDebugWindow').html(message);

$('#djDebug .remoteCall').live('click', function() {
var self = $(this);
var name = self[0].tagName.toLowerCase();
var ajax_data = {};

if (name == 'button') {
var form = self.parents('form:eq(0)');
ajax_data['url'] = self.attr('formaction');

if (form.length) {
ajax_data['data'] = form.serialize();
ajax_data['type'] = form.attr('method') || 'POST';
}
$('#djDebugWindow a.djDebugBack').live('click', function() {
$(this).parent().parent().hide();
return false;
});
}

if (name == 'a') {
ajax_data['url'] = self.attr('href');
}

$.ajax(ajax_data).done(function(data){
$('#djDebugWindow').html(data).show();
}).fail(function(xhr){
var message = '<div class="djDebugPanelTitle"><a class="djDebugClose djDebugBack" href="">Back</a><h3>'+xhr.status+': '+xhr.statusText+'</h3></div>';
$('#djDebugWindow').html(message).show();
});

$('#djDebugWindow a.djDebugBack').live('click', function() {
$(this).parent().parent().hide();
return false;
});
$('#djDebugWindow').show();

return false;
});

$('#djDebugTemplatePanel a.djTemplateShowContext').live('click', function() {
djdt.toggle_arrow($(this).children('.toggleArrow'));
djdt.toggle_content($(this).parent().next());
Expand Down
2 changes: 1 addition & 1 deletion debug_toolbar/static/debug_toolbar/js/toolbar.min.js

Large diffs are not rendered by default.

16 changes: 11 additions & 5 deletions debug_toolbar/templates/debug_toolbar/panels/sql.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,19 @@
{{ query.duration|floatformat:"2" }}
</td>
<td class="actions">

{% if query.params %}
{% if query.is_select %}
<a class="remoteCall" href="/__debug__/sql_select/?sql={{ query.raw_sql|urlencode }}&amp;params={{ query.params|urlencode }}&amp;duration={{ query.duration|floatformat:"2"|urlencode }}&amp;hash={{ query.hash }}&amp;alias={{ query.alias|urlencode }}">Sel</a>
<a class="remoteCall" href="/__debug__/sql_explain/?sql={{ query.raw_sql|urlencode }}&amp;params={{ query.params|urlencode }}&amp;duration={{ query.duration|floatformat:"2"|urlencode }}&amp;hash={{ query.hash }}&amp;alias={{ query.alias|urlencode }}">Expl</a>
{% ifequal query.engine 'mysql' %}
<a class="remoteCall" href="/__debug__/sql_profile/?sql={{ query.raw_sql|urlencode }}&amp;params={{ query.params|urlencode }}&amp;duration={{ query.duration|floatformat:"2"|urlencode }}&amp;hash={{ query.hash }}&amp;alias={{ query.alias|urlencode }}">Prof</a>
{% endifequal %}
<form method="post">
{{ query.form }}

<button formaction="/__debug__/sql_select/" class="remoteCall">Sel</button>
<button formaction="/__debug__/sql_explain/" class="remoteCall">Expl</button>

{% ifequal query.engine 'mysql' %}
<button formaction="/__debug__/sql_profile/" class="remoteCall">Prof</button>
{% endifequal %}
</form>
{% endif %}
{% endif %}
</td>
Expand Down
10 changes: 5 additions & 5 deletions debug_toolbar/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@

_PREFIX = '__debug__'

urlpatterns = patterns('',
url(r'^%s/sql_select/$' % _PREFIX, 'debug_toolbar.views.sql_select', name='sql_select'),
url(r'^%s/sql_explain/$' % _PREFIX, 'debug_toolbar.views.sql_explain', name='sql_explain'),
url(r'^%s/sql_profile/$' % _PREFIX, 'debug_toolbar.views.sql_profile', name='sql_profile'),
url(r'^%s/template_source/$' % _PREFIX, 'debug_toolbar.views.template_source', name='template_source'),
urlpatterns = patterns('debug_toolbar.views',
url(r'^%s/sql_select/$' % _PREFIX, 'sql_select', name='sql_select'),
url(r'^%s/sql_explain/$' % _PREFIX, 'sql_explain', name='sql_explain'),
url(r'^%s/sql_profile/$' % _PREFIX, 'sql_profile', name='sql_profile'),
url(r'^%s/template_source/$' % _PREFIX, 'template_source', name='template_source'),
)
14 changes: 14 additions & 0 deletions debug_toolbar/utils/functional.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
try:
from django.utils.functional import cached_property
except ImportError: # Django < 1.4
class cached_property(object):
"""
Decorator that creates converts a method with a single
self argument into a property cached on the instance.
"""
def __init__(self, func):
self.func = func

def __get__(self, instance, type):
res = instance.__dict__[self.func.__name__] = self.func(instance)
return res
15 changes: 15 additions & 0 deletions debug_toolbar/utils/sql.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import re
from debug_toolbar.utils import sqlparse
from debug_toolbar.utils.sqlparse.filters import BoldKeywordFilter


def reformat_sql(sql):
stack = sqlparse.engine.FilterStack()
stack.preprocess.append(BoldKeywordFilter()) # add our custom filter
stack.postprocess.append(sqlparse.filters.SerializerUnicode()) # tokens -> strings
return swap_fields(''.join(stack.run(sql)))


def swap_fields(sql):
return re.sub('SELECT</strong> (.*?) <strong>FROM', 'SELECT</strong> <a class="djDebugUncollapsed djDebugToggle" href="#">&bull;&bull;&bull;</a> ' +
'<a class="djDebugCollapsed djDebugToggle" href="#">\g<1></a> <strong>FROM', sql)
14 changes: 14 additions & 0 deletions debug_toolbar/utils/sqlparse/filters.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-

import re
from django.utils.html import escape

from debug_toolbar.utils.sqlparse import tokens as T
from debug_toolbar.utils.sqlparse import sql
Expand Down Expand Up @@ -423,3 +424,16 @@ def process(self, stack, stmt):
varname = self.varname
stmt.tokens = tuple(self._process(stmt.tokens, varname))
return stmt


class BoldKeywordFilter(Filter):
"""sqlparse filter to bold SQL keywords"""
def process(self, stack, stream):
"""Process the token stream"""
for token_type, value in stream:
is_keyword = token_type in T.Keyword
if is_keyword:
yield T.Text, '<strong>'
yield token_type, escape(value)
if is_keyword:
yield T.Text, '</strong>'
3 changes: 0 additions & 3 deletions debug_toolbar/utils/tracking/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,6 @@ def execute(self, sql, params=()):
'duration': duration,
'raw_sql': sql,
'params': _params,
'hash': sha1(settings.SECRET_KEY \
+ smart_str(sql) \
+ _params).hexdigest(),
'stacktrace': stacktrace,
'start_time': start,
'stop_time': stop,
Expand Down
Loading