mirror of
https://github.com/pallets-eco/flask-debugtoolbar.git
synced 2025-12-31 02:29:33 -06:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d5d37fa0b | ||
|
|
1029b9131b | ||
|
|
3bea63dc8a | ||
|
|
856eb52a95 | ||
|
|
50017f13a7 | ||
|
|
561889738f | ||
|
|
b97e58c1f2 | ||
|
|
0f923475c8 | ||
|
|
43df69ab24 | ||
|
|
3e4cd1ecfa | ||
|
|
cfa5984d3e | ||
|
|
c9c0228a62 | ||
|
|
d87e7fc347 | ||
|
|
80eb747816 |
28
CHANGES.rst
28
CHANGES.rst
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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 }}&params={{ query.params|urlencode }}&duration={{ query.duration|urlencode }}&hash={{ query.hash }}">SELECT</a><br />
|
||||
<a class="remoteCall" href="/_debug_toolbar/views/sqlalchemy/sql_explain?sql={{ query.raw_sql|urlencode }}&params={{ query.params|urlencode }}&duration={{ query.duration|urlencode }}&hash={{ query.hash }}">EXPLAIN</a><br />
|
||||
{% if is_mysql %}
|
||||
<a class="remoteCall" href="/__debug__/sql_profile/?sql={{ query.raw_sql|urlencode }}&params={{ query.params|urlencode }}&duration={{ query.duration|urlencode }}&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 }}&duration={{ query.duration|urlencode }}">SELECT</a><br />
|
||||
<a class="remoteCall" href="/_debug_toolbar/views/sqlalchemy/sql_explain?query={{ query.signed_query }}&duration={{ query.duration|urlencode }}">EXPLAIN</a><br />
|
||||
{% endif %}
|
||||
</td>
|
||||
<td title="{{ query.context_long }}">
|
||||
|
||||
@@ -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>
|
||||
|
||||
5
setup.py
5
setup.py
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user