14 Commits
0.7 ... 0.8.0

Author SHA1 Message Date
Matt Good
8d5d37fa0b Merge branch 'release/0.8.0' 2013-02-21 12:26:23 -08:00
Matt Good
1029b9131b Prepare 0.8.0 release 2013-02-21 12:25:45 -08:00
Matt Good
3bea63dc8a Fix werkzeug request logging with the log panel
Werkzeug will disable its default logging setup if another log handler
is already configured.  At some point the initialization order changed
and the logging panel's handler is getting added first now, so
werkzeug's request log will not be printed to the console by default. By
explicitly calling werkzeug's logger we now make sure it's initialized
before the logging panel's handler.

Fixes #33
2013-02-21 10:53:16 -08:00
Matt Good
856eb52a95 Nice error message for un-repr-able objects
The "printable" filter can throw an exception when trying to display a
user object whose __repr__ implementation returns unicode, which repr()
does not allow. Catch it and display a more informative message
instead.

See #31 and #39
2013-02-20 20:46:31 -08:00
Matt Good
50017f13a7 Switch to itsdangerous for SQL query signing
Fixing #31 where the query signing would break when the SECRET_KEY
contained non-ascii values. In the process switch to itsdangerous
instead of rolling our own signatures.
2013-02-20 20:02:00 -08:00
Matt Good
561889738f Merge pull request #41 from crosspop/ajaxfix
Intercept redirect only if it’s not XHR (Ajax)
2013-02-19 17:01:11 -08:00
Matt Good
b97e58c1f2 Merge pull request #42 from bollwyvl/share-jquery
adding reference to noConflict'ed jQuery
2013-02-19 11:10:48 -08:00
Nicholas Bollweg
0f923475c8 adding reference to noConflict'ed jQuery 2013-02-13 12:29:45 -05:00
Hong Minhee
43df69ab24 Intercept redirect only if it's not XHR (Ajax)
Intercept redirect can't really help debugging at all
if requests are XHR (Ajax), because browsers just don't render
their response by default.
2013-01-17 05:11:26 +09:00
Matt Good
3e4cd1ecfa Merge pull request #35 from rconradharris/patch-1
Profiler: Use update_wrapper on partial

If the profiler panel is applied before the request-vars panel, the wrapper added by the profiler causes an error since it's missing a `__module__` attribute.
2012-09-25 10:43:19 -07:00
Matt Good
cfa5984d3e Convert SQLAlchemy query duration to ms to match the label
The SQLAlchemy panel labels the query duration column "(ms)" but the
times from Flask-SQLAlchemy are given in seconds.

Fixes #36
2012-09-25 09:45:52 -07:00
Rick Harris
c9c0228a62 Profiler: Use update_wrapper on partial
The `update_wrapper` call is needed so that the new partial
func inherits the `__module__` of the original function.

The `__module__` is needed because the request-vars panel uses
`func.__module__` in building the qualified name.
2012-09-04 19:45:51 -05:00
Matt Good
d87e7fc347 Fix version number in docs 2012-05-19 18:22:18 -07:00
Matt Good
80eb747816 Release 0.7.1 to fix the in-place loading of the template editor 2012-05-18 18:20:28 -07:00
10 changed files with 110 additions and 88 deletions

View File

@@ -1,6 +1,32 @@
Changes
=======
0.8 (2013-02-21)
----------------
Enhancements:
- Use `itsdangerous <http://pythonhosted.org/itsdangerous/>`_ to sign SQL queries
- Expose the jQuery object as ``fldt.$`` so extensions can use the toolbar's
copy of jQuery (#42)
Fixes:
- Don't intercept redirects on XHR requests (#41)
- Fix SQL query time display as milliseconds (#36)
- Fix ``functools.partial`` error (#35)
- Fix werkzeug request logging with logging panel (#33)
- Fix SQL panel unicode encoding error (#31)
0.7.1 (2012-05-18)
------------------
Fixes:
- loading template editor in-place over current page
0.7 (2012-05-18)
----------------
@@ -12,7 +38,7 @@ Enhancements:
0.6.3.1 (2012-04-16)
------------------
--------------------
New release to add missing changelog for 0.6.3

View File

@@ -48,9 +48,9 @@ copyright = u'2012, Matt Good'
# built documents.
#
# The short X.Y version.
version = '0.6'
version = '0.7'
# The full version, including alpha/beta/rc tags.
release = '0.6.1'
release = '0.7.1'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

View File

@@ -30,8 +30,11 @@ def _printable(value):
return value.encode('unicode_escape')
elif isinstance(value, str):
return value.encode('string_escape')
else:
try:
return repr(value)
except Exception, e:
return '<repr(%s) raised %s: %s>' % (
object.__repr__(value), type(e).__name__, e)
class DebugToolbarExtension(object):
@@ -146,7 +149,8 @@ class DebugToolbarExtension(object):
# Intercept http redirect codes and display an html page with a
# link to the target.
if self.debug_toolbars[real_request].config['DEBUG_TB_INTERCEPT_REDIRECTS']:
if response.status_code in self._redirect_codes:
if (response.status_code in self._redirect_codes and
not real_request.is_xhr):
redirect_to = response.location
redirect_code = response.status_code
if redirect_to:

View File

@@ -46,18 +46,27 @@ _init_lock = threading.Lock()
def _init_once():
# Initialize the logging handler once, but after werkzeug has set up its
# default logger. Otherwise, if this sets up the logging first, werkzeug
# will not create a default logger, so the development server's output will
# not get printed.
global handler
if handler is not None:
return
with _init_lock:
global handler
if handler is not None:
return
handler = ThreadTrackingHandler()
logging.root.addHandler(handler)
return
with _init_lock:
if handler is not None:
return
# Call werkzeug's internal logging to make sure it gets configured
# before we add our handler. Otherwise werkzeug will see our handler
# and not configure console logging for the request log.
# Werkzeug's default log level is INFO so this message probably won't be
# seen.
try:
from werkzeug._internal import _log
except ImportError:
pass
else:
_log('debug', 'Initializing Flask-DebugToolbar log handler')
handler = ThreadTrackingHandler()
logging.root.addHandler(handler)
class LoggingPanel(DebugPanel):

View File

@@ -37,7 +37,9 @@ class ProfilerDebugPanel(DebugPanel):
def process_view(self, request, view_func, view_kwargs):
if self.is_active:
return functools.partial(self.profiler.runcall, view_func)
func = functools.partial(self.profiler.runcall, view_func)
functools.update_wrapper(func, view_func)
return func
def process_response(self, request, response):
if not self.is_active:

View File

@@ -1,4 +1,3 @@
import hashlib
try:
from flask.ext.sqlalchemy import get_debug_queries, SQLAlchemy
@@ -9,14 +8,43 @@ else:
sqlalchemy_available = True
from flask import request, current_app, abort, json_available, g
from flask.helpers import json
from flask_debugtoolbar import module
from flask_debugtoolbar.panels import DebugPanel
from flask_debugtoolbar.utils import format_fname, format_sql
import itsdangerous
_ = lambda x: x
def query_signer():
return itsdangerous.URLSafeSerializer(current_app.config['SECRET_KEY'],
salt='fdt-sql-query')
def dump_query(statement, params):
if not params or not statement.lower().strip().startswith('select'):
return None
try:
return query_signer().dumps([statement, params])
except TypeError:
return None
def load_query(data):
try:
statement, params = query_signer().loads(request.args['query'])
except (itsdangerous.BadSignature, TypeError):
abort(406)
# Make sure it is a select statement
if not statement.lower().strip().startswith('select'):
abort(406)
return statement, params
class SQLAlchemyDebugPanel(DebugPanel):
"""
Panel that displays the time a response took in milliseconds.
@@ -66,25 +94,10 @@ class SQLAlchemyDebugPanel(DebugPanel):
queries = get_debug_queries()
data = []
for query in queries:
is_select = query.statement.strip().lower().startswith('select')
_params = ''
try:
_params = json.dumps(query.parameters)
except TypeError:
pass # object not JSON serializable
hash = hashlib.sha1(
current_app.config['SECRET_KEY'] +
query.statement + _params).hexdigest()
data.append({
'duration': query.duration,
'sql': format_sql(query.statement, query.parameters),
'raw_sql': query.statement,
'hash': hash,
'params': _params,
'is_select': is_select,
'signed_query': dump_query(query.statement, query.parameters),
'context_long': query.context,
'context': format_fname(query.context)
})
@@ -94,21 +107,7 @@ class SQLAlchemyDebugPanel(DebugPanel):
@module.route('/sqlalchemy/sql_select', methods=['GET', 'POST'])
def sql_select():
statement = request.args['sql']
params = request.args['params']
# Validate hash
hash = hashlib.sha1(
current_app.config['SECRET_KEY'] + statement + params).hexdigest()
if hash != request.args['hash']:
return abort(406)
# Make sure it is a select statement
if not statement.lower().strip().startswith('select'):
return abort(406)
params = json.loads(params)
statement, params = load_query(request.args['query'])
engine = SQLAlchemy().get_engine(current_app)
result = engine.execute(statement, params)
@@ -121,21 +120,7 @@ def sql_select():
@module.route('/sqlalchemy/sql_explain', methods=['GET', 'POST'])
def sql_explain():
statement = request.args['sql']
params = request.args['params']
# Validate hash
hash = hashlib.sha1(
current_app.config['SECRET_KEY'] + statement + params).hexdigest()
if hash != request.args['hash']:
return abort(406)
# Make sure it is a select statement
if not statement.lower().strip().startswith('select'):
return abort(406)
params = json.loads(params)
statement, params = load_query(request.args['query'])
engine = SQLAlchemy().get_engine(current_app)
if engine.driver == 'pysqlite':

View File

@@ -175,10 +175,20 @@
var uarr = String.fromCharCode(0x25b6);
var darr = String.fromCharCode(0x25bc);
elem.html(elem.html() == uarr ? darr : uarr);
}
},
load_href: function(href) {
$.get(href, function(data, status, xhr) {
document.open();
document.write(xhr.responseText);
document.close();
});
return false;
},
$: $
};
$(document).ready(function() {
fldt.init();
});
window.fldt = fldt;
})(jQuery.noConflict(true));

View File

@@ -10,16 +10,11 @@
<tbody>
{% for query in queries %}
<tr class="{{ loop.cycle('flDebugOdd', 'flDebugEven') }}">
<td>{{ '%.4f'|format(query.duration) }}</td>
<td>{{ '%.4f'|format(query.duration * 1000) }}</td>
<td>
{% if query.params %}
{% if query.is_select %}
<a class="remoteCall" href="/_debug_toolbar/views/sqlalchemy/sql_select?sql={{ query.raw_sql|urlencode }}&amp;params={{ query.params|urlencode }}&amp;duration={{ query.duration|urlencode }}&amp;hash={{ query.hash }}">SELECT</a><br />
<a class="remoteCall" href="/_debug_toolbar/views/sqlalchemy/sql_explain?sql={{ query.raw_sql|urlencode }}&amp;params={{ query.params|urlencode }}&amp;duration={{ query.duration|urlencode }}&amp;hash={{ query.hash }}">EXPLAIN</a><br />
{% if is_mysql %}
<a class="remoteCall" href="/__debug__/sql_profile/?sql={{ query.raw_sql|urlencode }}&amp;params={{ query.params|urlencode }}&amp;duration={{ query.duration|urlencode }}&amp;hash={{ query.hash }}">PROFILE</a><br />
{% endif %}
{% endif %}
{% if query.signed_query %}
<a class="remoteCall" href="/_debug_toolbar/views/sqlalchemy/sql_select?query={{ query.signed_query }}&amp;duration={{ query.duration|urlencode }}">SELECT</a><br />
<a class="remoteCall" href="/_debug_toolbar/views/sqlalchemy/sql_explain?query={{ query.signed_query }}&amp;duration={{ query.duration|urlencode }}">EXPLAIN</a><br />
{% endif %}
</td>
<td title="{{ query.context_long }}">

View File

@@ -1,16 +1,6 @@
{% if templates %}
{% if editable %}
<script>
function loadHref() {
$.get(this.href, function(data, status, xhr) {
document.open();
document.write(xhr.responseText);
document.close();
});
return false;
}
</script>
<a href="/_debug_toolbar/views/template/{{ key }}" onclick="return loadHref.apply(this);">Edit templates</a>
<a href="/_debug_toolbar/views/template/{{ key }}" onclick="return fldt.load_href(this.href);">Edit templates</a>
{% endif %}
{% for template in templates %}
<h4>{{ template.template.name }}</h4>

View File

@@ -14,8 +14,8 @@ except:
setup(
name='Flask-DebugToolbar',
version='0.7',
url='http://github.com/mgood/flask-debugtoolbar',
version='0.8.0',
url='http://flask-debugtoolbar.rtfd.org/',
license='BSD',
author='Michael van Tellingen',
author_email='michaelvantellingen@gmail.com',
@@ -32,6 +32,7 @@ setup(
install_requires=[
'Flask>=0.8',
'Blinker',
'itsdangerous',
],
classifiers=[
'Development Status :: 4 - Beta',