36 Commits
0.7 ... 0.9.0

Author SHA1 Message Date
Matt Good
4901d68b01 Fix version number in docs 2014-01-03 15:14:49 -08:00
Matt Good
d671a849bb Bump version for new release 2014-01-03 15:07:48 -08:00
Matt Good
ffbdf0f563 Add SQLAlchemy to test app 2013-12-31 17:55:19 -05:00
Matt Good
b08fe477b6 Merge pull request #63 from defuz/pep8
PEP8 formatting
2013-12-02 16:20:27 -08:00
Ivan Ivaschenko
903ed85f00 remove coding:utf-8 and add some style fixes 2013-11-29 00:17:15 +02:00
Ivan Ivaschenko
5710314252 pep8 formatting + coding: utf8 2013-11-28 18:31:29 +02:00
Matt Good
277462d822 Fix build status image
RST example copied from Travis was a bit broken
2013-11-06 09:59:50 -08:00
Matt Good
26691d49cd Add Travis build statis to README 2013-11-06 09:58:49 -08:00
Matt Good
bd37db9dc4 Fix SQLAlchemy URL registration
The init_app() restructuring in 356e6c removed "load_panels()", but need
to add it back so panels like SQLAlchemy can register their URLs on the
Blueprint before it's activated.
2013-11-06 09:33:00 -08:00
Matt Good
091c4fa70c Compatibility fixes for Python 3
Fixes #54 (at least the basic stuff, this is still a bit experimental)
2013-11-06 09:06:05 -08:00
Matt Good
d5987410d1 Add basic sanity test 2013-11-06 09:00:05 -08:00
Matt Good
6d8249160d Rename "settings" panel to "config" 2013-11-01 14:04:10 -07:00
Matt Good
1e046d57f4 Remove some unneeded code from SettingsVars panel 2013-11-01 12:42:38 -07:00
Matt Good
9e3546b416 Merge commit 'refs/pull/51/head' of github.com:mgood/flask-debugtoolbar
Conflicts:
	flask_debugtoolbar/toolbar.py
2013-11-01 11:41:08 -07:00
Matt Good
26034475f2 Load panels with werkzeug's "import_string"
Also add back caching of the loaded panel classes.
This prevents repeated warnings if there was an import error loading the
class the first time.
2013-07-30 17:56:54 -07:00
Matt Good
356e6c8268 Restructure to support init_app
Fixes #38
2013-07-30 17:37:34 -07:00
Matt Good
5870fce178 Can check blueprint name instead of URL prefix 2013-07-30 09:25:02 -07:00
Alexey Diyan
855c7ab377 Port SettingsVarsDebugPanel from Django DebugToolbar 2013-05-10 19:17:32 +03:00
Matt Good
115e874a39 Reformat templates and static files with 2 space indentation
Fixes #45
2013-03-16 16:21:39 -07:00
Matt Good
b7c39113e7 Template editor should use Jinja loader's encoding
The template loader wouldn't handle non-ASCII template encodings.
Updated it to use the encoding parameter of the Jinja loader when
reading and writing the template files.

Fixes #46
2013-03-08 09:00:10 -08:00
Matt Good
05288daf17 Docs Makefile needs to ensure git submodules are initialized 2013-02-21 13:43:27 -08:00
Matt Good
79421df5cd Use Flask theme for the documentation 2013-02-21 13:37:12 -08:00
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
42 changed files with 1121 additions and 972 deletions

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@
build/
dist/
docs/_build
.tox

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "docs/_themes"]
path = docs/_themes
url = git://github.com/mitsuhiko/flask-sphinx-themes.git

9
.travis.yml Normal file
View File

@@ -0,0 +1,9 @@
language: python
python: "2.7"
env:
- TOXENV=py26
- TOXENV=py27
- TOXENV=py33
install:
- pip install tox --use-mirrors
script: tox

View File

@@ -1,6 +1,47 @@
Changes
=======
0.9.0 (2014-01-03)
--------
Enhancements:
- Python 3 compatibility (#54, thanks justinmayer and jmagnusson)
- Support .init_app() (#38)
- New "Config" panel displaying Flask config values (#51, thanks Alexey Diyan)
- Better PEP8-style formatting (#63, thanks Ivan Ivaschenko)
Fixes:
- Fix template editor with non-ASCII templates (#46)
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 +53,7 @@ Enhancements:
0.6.3.1 (2012-04-16)
------------------
--------------------
New release to add missing changelog for 0.6.3

View File

@@ -4,6 +4,9 @@ Flask Debug-toolbar
This is a port of the excellent `django-debug-toolbar <https://github.com/django-debug-toolbar/django-debug-toolbar>`_
for Flask applications.
.. image:: https://travis-ci.org/mgood/flask-debugtoolbar.png?branch=master
:target: https://travis-ci.org/mgood/flask-debugtoolbar
Installation
------------

View File

@@ -41,7 +41,7 @@ help:
clean:
-rm -rf $(BUILDDIR)/*
html:
html: _themes
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
@@ -151,3 +151,7 @@ doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
_themes:
git clone git://github.com/Pylons/pylons_sphinx_theme.git _themes
cd ..; git submodule update --init; cd docs

1
docs/_themes Submodule

Submodule docs/_themes added at 0269f3d188

View File

@@ -48,9 +48,9 @@ copyright = u'2012, Matt Good'
# built documents.
#
# The short X.Y version.
version = '0.6'
version = '0.9'
# The full version, including alpha/beta/rc tags.
release = '0.6.1'
release = '0.9.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@@ -91,15 +91,19 @@ pygments_style = 'sphinx'
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'default'
html_theme = 'flask_small'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
html_theme_options = {
'github_fork': 'mgood/flask-debugtoolbar',
'index_logo': None,
}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
sys.path.append(os.path.abspath('_themes'))
html_theme_path = ['_themes']
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".

View File

@@ -3,8 +3,8 @@
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to Flask-DebugToolbar's documentation!
==============================================
Flask-DebugToolbar
==================
This is a port of the excellent `django-debug-toolbar <https://github.com/django-debug-toolbar/django-debug-toolbar>`_
for Flask applications.

View File

@@ -22,16 +22,19 @@ db = SQLAlchemy(app)
toolbar = DebugToolbarExtension(app)
class ExampleModel(db.Model):
__tablename__ = 'examples'
value = db.Column(db.String(100), primary_key=True)
@app.route('/')
def index():
app.logger.info("Hello there")
ExampleModel.query.get(1)
return render_template('index.html')
@app.route('/redirect')
def redirect_example():
@@ -44,4 +47,3 @@ if __name__ == "__main__":
manager = Manager(app)
manager.run()

View File

@@ -8,30 +8,32 @@ from werkzeug.exceptions import HTTPException
from werkzeug.urls import url_quote_plus
from flask_debugtoolbar.toolbar import DebugToolbar
from flask_debugtoolbar.compat import iteritems
from flask import Blueprint
module = Blueprint('debugtoolbar', __name__)
def replace_insensitive(string, target, replacement):
"""Similar to string.replace() but is case insensitive
Code borrowed from: http://forums.devshed.com/python-programming-11/case-insensitive-string-replace-490921.html
Code borrowed from:
http://forums.devshed.com/python-programming-11/case-insensitive-string-replace-490921.html
"""
no_case = string.lower()
index = no_case.rfind(target.lower())
if index >= 0:
return string[:index] + replacement + string[index + len(target):]
else: # no results so return the original string
else: # no results so return the original string
return string
def _printable(value):
if isinstance(value, unicode):
return value.encode('unicode_escape')
elif isinstance(value, str):
return value.encode('string_escape')
else:
try:
return repr(value)
except Exception as e:
return '<repr(%s) raised %s: %s>' % (
object.__repr__(value), type(e).__name__, e)
class DebugToolbarExtension(object):
@@ -40,29 +42,9 @@ class DebugToolbarExtension(object):
_redirect_codes = [301, 302, 303, 304]
def __init__(self, app):
def __init__(self, app=None):
self.app = app
self.debug_toolbars = {}
self.hosts = ()
if not app.config.get('DEBUG_TB_ENABLED', app.debug):
return
if not app.config.get('SECRET_KEY'):
raise RuntimeError(
"The Flask-DebugToolbar requires the 'SECRET_KEY' config "
"var to be set")
DebugToolbar.load_panels(app)
self.hosts = app.config.get('DEBUG_TB_HOSTS', ())
self.app.before_request(self.process_request)
self.app.after_request(self.process_response)
self.app.teardown_request(self.teardown_request)
# Monkey-patch the Flask.dispatch_request method
app.dispatch_request = self.dispatch_request
# Configure jinja for the internal templates and add url rules
# for static data
@@ -73,11 +55,53 @@ class DebugToolbarExtension(object):
self.jinja_env.filters['urlencode'] = url_quote_plus
self.jinja_env.filters['printable'] = _printable
if app is not None:
self.init_app(app)
def init_app(self, app):
for k, v in iteritems(self._default_config(app)):
app.config.setdefault(k, v)
if not app.config['DEBUG_TB_ENABLED']:
return
if not app.config.get('SECRET_KEY'):
raise RuntimeError(
"The Flask-DebugToolbar requires the 'SECRET_KEY' config "
"var to be set")
DebugToolbar.load_panels(app)
app.before_request(self.process_request)
app.after_request(self.process_response)
app.teardown_request(self.teardown_request)
# Monkey-patch the Flask.dispatch_request method
app.dispatch_request = self.dispatch_request
app.add_url_rule('/_debug_toolbar/static/<path:filename>',
'_debug_toolbar.static', self.send_static_file)
'_debug_toolbar.static', self.send_static_file)
app.register_blueprint(module, url_prefix='/_debug_toolbar/views')
def _default_config(self, app):
return {
'DEBUG_TB_ENABLED': app.debug,
'DEBUG_TB_HOSTS': (),
'DEBUG_TB_INTERCEPT_REDIRECTS': True,
'DEBUG_TB_PANELS': (
'flask_debugtoolbar.panels.versions.VersionDebugPanel',
'flask_debugtoolbar.panels.timer.TimerDebugPanel',
'flask_debugtoolbar.panels.headers.HeaderDebugPanel',
'flask_debugtoolbar.panels.request_vars.RequestVarsDebugPanel',
'flask_debugtoolbar.panels.config_vars.ConfigVarsDebugPanel',
'flask_debugtoolbar.panels.template.TemplateDebugPanel',
'flask_debugtoolbar.panels.sqlalchemy.SQLAlchemyDebugPanel',
'flask_debugtoolbar.panels.logger.LoggingPanel',
'flask_debugtoolbar.panels.profiler.ProfilerDebugPanel',
),
}
def dispatch_request(self):
"""Modified version of Flask.dispatch_request to call process_view."""
req = _request_ctx_stack.top.request
@@ -102,10 +126,11 @@ class DebugToolbarExtension(object):
def _show_toolbar(self):
"""Return a boolean to indicate if we need to show the toolbar."""
if request.path.startswith('/_debug_toolbar/'):
if request.blueprint == 'debugtoolbar':
return False
if self.hosts and request.remote_addr not in self.hosts:
hosts = current_app.config['DEBUG_TB_HOSTS']
if hosts and request.remote_addr not in hosts:
return False
return True
@@ -145,8 +170,9 @@ 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 current_app.config['DEBUG_TB_INTERCEPT_REDIRECTS']:
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:
@@ -161,8 +187,8 @@ class DebugToolbarExtension(object):
# If the http response code is 200 then we process to add the
# toolbar to the returned html response.
if (response.status_code == 200
and response.headers['content-type'].startswith('text/html')):
if (response.status_code == 200 and
response.headers['content-type'].startswith('text/html')):
for panel in self.debug_toolbars[real_request].panels:
panel.process_response(real_request, response)

View File

@@ -0,0 +1,9 @@
import sys
PY2 = sys.version_info[0] == 2
if PY2:
iteritems = lambda d: d.iteritems()
else:
iteritems = lambda d: iter(d.items())

View File

@@ -1,16 +1,16 @@
"""Base DebugPanel class"""
class DebugPanel(object):
"""
Base class for debug panels.
"""
# name = Base
has_content = False # If content returns something, set to true in subclass
has_content = False # If content returns something, set to true in subclass
# If the client is able to activate/de-activate the panel
user_enable = False
# We'll maintain a local context instance so we can expose our template
# context variables to panels which need them:
context = {}
@@ -57,5 +57,3 @@ class DebugPanel(object):
def process_response(self, request, response):
pass

View File

@@ -0,0 +1,29 @@
from flask import current_app
from flask_debugtoolbar.panels import DebugPanel
_ = lambda x: x
class ConfigVarsDebugPanel(DebugPanel):
"""
A panel to display all variables from Flask configuration
"""
name = 'ConfigVars'
has_content = True
def nav_title(self):
return _('Config')
def title(self):
return _('Config')
def url(self):
return ''
def content(self):
context = self.context.copy()
context.update({
'config': current_app.config,
})
return self.render('panels/config_vars.html', context)

View File

@@ -12,13 +12,14 @@ from flask_debugtoolbar.utils import format_fname
_ = lambda x: x
class ThreadTrackingHandler(logging.Handler):
def __init__(self):
if threading is None:
raise NotImplementedError("threading module is not available, \
the logging panel cannot be used without it")
logging.Handler.__init__(self)
self.records = {} # a dictionary that maps threads to log records
self.records = {} # a dictionary that maps threads to log records
def emit(self, record):
self.get_records().append(record)
@@ -46,18 +47,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):
@@ -78,7 +88,8 @@ class LoggingPanel(DebugPanel):
def nav_subtitle(self):
# FIXME l10n: use ngettext
return "%s message%s" % (len(handler.get_records()), (len(handler.get_records()) == 1) and '' or 's')
return "%s message%s" % \
(len(handler.get_records()), (len(handler.get_records()) == 1) and '' or 's')
def title(self):
return _('Log Messages')
@@ -102,5 +113,3 @@ class LoggingPanel(DebugPanel):
context.update({'records': records})
return self.render('panels/logger.html', context)

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:
@@ -116,6 +118,3 @@ class ProfilerDebugPanel(DebugPanel):
'function_calls': self.function_calls,
}
return self.render('panels/profiler.html', context)

View File

@@ -4,6 +4,7 @@ from flask_debugtoolbar.panels import DebugPanel
_ = lambda x: x
class RequestVarsDebugPanel(DebugPanel):
"""
A panel to display request variables (POST/GET, session, cookies).
@@ -37,11 +38,11 @@ class RequestVarsDebugPanel(DebugPanel):
'get': [(k, self.request.args.getlist(k)) for k in self.request.args],
'post': [(k, self.request.form.getlist(k)) for k in self.request.form],
'cookies': [(k, self.request.cookies.get(k)) for k in self.request.cookies],
'view_func': '%s.%s' % (self.view_func.__module__, self.view_func.__name__) if self.view_func else '[unknown]',
'view_func': ('%s.%s' % (self.view_func.__module__, self.view_func.__name__)
if self.view_func else '[unknown]'),
'view_args': self.view_args,
'view_kwargs': self.view_kwargs or {},
'session': self.session.items(),
})
return self.render('panels/request_vars.html', context)

View File

@@ -1,5 +1,3 @@
import hashlib
try:
from flask.ext.sqlalchemy import get_debug_queries, SQLAlchemy
except ImportError:
@@ -9,25 +7,53 @@ 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.
"""
name = 'SQLAlchemy'
@property
def has_content(self):
if not json_available or not sqlalchemy_available:
return True # will display an error message
return True # will display an error message
return bool(get_debug_queries())
def process_request(self, request):
@@ -66,49 +92,21 @@ 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)
})
return self.render('panels/sqlalchemy.html', { 'queries': data})
return self.render('panels/sqlalchemy.html', {'queries': data})
# Panel views
@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)
@@ -119,23 +117,10 @@ def sql_select():
'duration': float(request.args['duration']),
})
@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

@@ -5,7 +5,10 @@ import traceback
import uuid
from jinja2.exceptions import TemplateSyntaxError
from flask import template_rendered, request, g, render_template_string, Response, current_app, abort, url_for
from flask import (
template_rendered, request, g, render_template_string,
Response, current_app, abort, url_for
)
from flask_debugtoolbar import module
from flask_debugtoolbar.panels import DebugPanel
@@ -77,6 +80,16 @@ def require_enabled():
abort(403)
def _get_source(template):
with open(template.filename, 'rb') as fp:
source = fp.read()
return source.decode(_template_encoding())
def _template_encoding():
return getattr(current_app.jinja_loader, 'encoding', 'utf-8')
@module.route('/template/<key>')
def template_editor(key):
require_enabled()
@@ -87,8 +100,7 @@ def template_editor(key):
'static_path': url_for('_debug_toolbar.static', filename=''),
'request': request,
'templates': [
{'name': t.name,
'source': open(t.filename).read()}
{'name': t.name, 'source': _get_source(t)}
for t in templates
]
})
@@ -98,8 +110,8 @@ def template_editor(key):
def save_template(key):
require_enabled()
template = TemplateDebugPanel.get_cache_for_key(key)[0]['template']
content = request.form['content']
with open(template.filename, 'w') as fp:
content = request.form['content'].encode(_template_encoding())
with open(template.filename, 'wb') as fp:
fp.write(content)
return 'ok'

View File

@@ -1,18 +1,19 @@
try:
import resource
except ImportError:
pass # Will fail on Win32 systems
pass # Will fail on Win32 systems
import time
from flask_debugtoolbar.panels import DebugPanel
_ = lambda x: x
class TimerDebugPanel(DebugPanel):
"""
Panel that displays the time a response took in milliseconds.
"""
name = 'Timer'
try: # if resource module not available, don't show content panel
try: # if resource module not available, don't show content panel
resource
except NameError:
has_content = False
@@ -91,4 +92,3 @@ class TimerDebugPanel(DebugPanel):
})
return self.render('panels/timer.html', context)

View File

@@ -3,6 +3,7 @@ from flask_debugtoolbar.panels import DebugPanel
_ = lambda x: x
class VersionDebugPanel(DebugPanel):
"""
Panel that displays the Flask version.
@@ -24,5 +25,3 @@ class VersionDebugPanel(DebugPanel):
def content(self):
return None

View File

@@ -1,16 +1,16 @@
/* Debug Toolbar CSS Reset, adapted from Eric Meyer's CSS Reset */
#flDebug, #flDebug * {
margin:0;
padding:0;
border:0;
outline:0;
font-size:12px;
line-height:1.5em;
color:#000;
vertical-align:baseline;
background: none;
font-family: inherit;
text-align:left;
margin:0;
padding:0;
border:0;
outline:0;
font-size:12px;
line-height:1.5em;
color:#000;
vertical-align:baseline;
background: none;
font-family: inherit;
text-align:left;
}
#flDebug { font-family: sans-serif; color: #000; background: #fff; }
@@ -20,376 +20,324 @@
}
#flDebug #flDebugToolbar {
background:#111;
width:200px;
z-index:100000000;
position:fixed;
top:0;
bottom:0;
right:0;
opacity:0.9;
background:#111;
width:200px;
z-index:100000000;
position:fixed;
top:0;
bottom:0;
right:0;
opacity:0.9;
}
#flDebug #flDebugToolbar small {
color:#999;
color:#999;
}
#flDebug #flDebugToolbar ul {
margin:0;
padding:0;
list-style:none;
margin:0;
padding:0;
list-style:none;
}
#flDebug #flDebugToolbar li {
border-bottom:1px solid #222;
color:#fff;
display:block;
font-weight:bold;
float:none;
margin:0;
padding:0;
position:relative;
width:auto;
border-bottom:1px solid #222;
color:#fff;
display:block;
font-weight:bold;
float:none;
margin:0;
padding:0;
position:relative;
width:auto;
}
#flDebug #flDebugToolbar li>a,
#flDebug #flDebugToolbar li>div.contentless {
font-weight:normal;
font-style:normal;
text-decoration:none;
display:block;
font-size:16px;
padding:10px 10px 5px 25px;
color:#fff;
font-weight:normal;
font-style:normal;
text-decoration:none;
display:block;
font-size:16px;
padding:10px 10px 5px 25px;
color:#fff;
}
#flDebug #flDebugToolbar li a:hover {
color:#111;
background-color:#ffc;
color:#111;
background-color:#ffc;
}
#flDebug #flDebugToolbar li.active {
background-image:url(../img/indicator.png);
background-repeat:no-repeat;
background-position:left center;
background-color:#333;
padding-left:10px;
background-image:url(../img/indicator.png);
background-repeat:no-repeat;
background-position:left center;
background-color:#333;
padding-left:10px;
}
#flDebug #flDebugToolbar li.active a:hover {
color:#b36a60;
background-color:transparent;
color:#b36a60;
background-color:transparent;
}
#flDebug #flDebugToolbar li small {
font-size:12px;
color:#999;
font-style:normal;
text-decoration:none;
font-variant:small-caps;
font-size:12px;
color:#999;
font-style:normal;
text-decoration:none;
font-variant:small-caps;
}
#flDebug #flDebugToolbar li .switch {
font-size: 10px;
position: absolute;
display: block;
color: white;
height: 16px;
width: 16px;
cursor: pointer;
top: 15px;
right: 2px;
font-size: 10px;
position: absolute;
display: block;
color: white;
height: 16px;
width: 16px;
cursor: pointer;
top: 15px;
right: 2px;
}
#flDebug #flDebugToolbar li .switch.active {
background-image: url(../img/tick.png);
background-image: url(../img/tick.png);
}
#flDebug #flDebugToolbar li .switch.inactive {
background-image: url(../img/tick-red.png);
background-image: url(../img/tick-red.png);
}
#flDebug #flDebugToolbarHandle {
position:fixed;
background:#fff;
border:1px solid #111;
top:30px;
right:0;
z-index:100000000;
opacity:0.75;
position:fixed;
background:#fff;
border:1px solid #111;
top:30px;
right:0;
z-index:100000000;
opacity:0.75;
}
#flDebug a#flShowToolBarButton {
display:block;
height:75px;
width:30px;
border-right:none;
border-bottom:4px solid #fff;
border-top:4px solid #fff;
border-left:4px solid #fff;
color:#fff;
font-size:10px;
font-weight:bold;
text-decoration:none;
text-align:center;
text-indent:-999999px;
background:#000 url(../img/djdt_vertical.png) no-repeat left center;
opacity:0.5;
display:block;
height:75px;
width:30px;
border-right:none;
border-bottom:4px solid #fff;
border-top:4px solid #fff;
border-left:4px solid #fff;
color:#fff;
font-size:10px;
font-weight:bold;
text-decoration:none;
text-align:center;
text-indent:-999999px;
background:#000 url(../img/djdt_vertical.png) no-repeat left center;
opacity:0.5;
}
#flDebug a#flShowToolBarButton:hover {
background-color:#111;
padding-right:6px;
border-top-color:#FFE761;
border-left-color:#FFE761;
border-bottom-color:#FFE761;
opacity:1.0;
background-color:#111;
padding-right:6px;
border-top-color:#FFE761;
border-left-color:#FFE761;
border-bottom-color:#FFE761;
opacity:1.0;
}
#flDebug code {
display:block;
white-space:pre;
overflow:auto;
display:block;
white-space:pre;
overflow:auto;
}
#flDebug tr.flDebugOdd {
background-color:#f5f5f5;
background-color:#f5f5f5;
}
#flDebug .panelContent {
display:none;
position:fixed;
margin:0;
top:0;
right:200px;
bottom:0;
left:0px;
background-color:#eee;
color:#666;
z-index:100000000;
display:none;
position:fixed;
margin:0;
top:0;
right:200px;
bottom:0;
left:0px;
background-color:#eee;
color:#666;
z-index:100000000;
}
#flDebug .panelContent > div {
border-bottom:1px solid #ddd;
border-bottom:1px solid #ddd;
}
#flDebug .flDebugPanelTitle {
position:absolute;
background-color:#ffc;
color:#666;
padding-left:20px;
top:0;
right:0;
left:0;
height:50px;
position:absolute;
background-color:#ffc;
color:#666;
padding-left:20px;
top:0;
right:0;
left:0;
height:50px;
}
#flDebug .flDebugPanelTitle code {
display:inline;
font-size:inherit;
display:inline;
font-size:inherit;
}
#flDebug .flDebugPanelContent {
position:absolute;
top:50px;
right:0;
bottom:0;
left:0;
height:auto;
padding:0 0 0 20px;
position:absolute;
top:50px;
right:0;
bottom:0;
left:0;
height:auto;
padding:0 0 0 20px;
}
#flDebug .flDebugPanelContent .scroll {
height:100%;
overflow:auto;
display:block;
padding:0 10px 0 0;
height:100%;
overflow:auto;
display:block;
padding:0 10px 0 0;
}
#flDebug h3 {
font-size:24px;
font-weight:normal;
line-height:50px;
font-size:24px;
font-weight:normal;
line-height:50px;
}
#flDebug h4 {
font-size:20px;
font-weight:bold;
margin-top:0.8em;
font-size:20px;
font-weight:bold;
margin-top:0.8em;
}
#flDebug .panelContent table {
border:1px solid #ccc;
border-collapse:collapse;
width:100%;
background-color:#fff;
display:block;
margin-top:0.8em;
overflow: auto;
border:1px solid #ccc;
border-collapse:collapse;
width:100%;
background-color:#fff;
display:block;
margin-top:0.8em;
overflow: auto;
}
#flDebug .panelContent tbody td,
#flDebug .panelContent tbody th {
vertical-align:top;
padding:2px 3px;
vertical-align:top;
padding:2px 3px;
}
#flDebug .panelContent thead th {
padding:1px 6px 1px 3px;
text-align:left;
font-weight:bold;
font-size:14px;
padding:1px 6px 1px 3px;
text-align:left;
font-weight:bold;
font-size:14px;
}
#flDebug .panelContent tbody th {
width:12em;
text-align:right;
color:#666;
padding-right:.5em;
width:12em;
text-align:right;
color:#666;
padding-right:.5em;
}
#flDebug .flTemplateHideContextDiv {
background-color:#fff;
background-color:#fff;
}
/*
#flDebug .panelContent p a:hover, #flDebug .panelContent dd a:hover {
color:#111;
background-color:#ffc;
}
#flDebug .panelContent p {
padding:0 5px;
}
#flDebug .panelContent p, #flDebug .panelContent table, #flDebug .panelContent ol, #flDebug .panelContent ul, #flDebug .panelContent dl {
margin:5px 0 15px;
background-color:#fff;
}
#flDebug .panelContent table {
clear:both;
border:0;
padding:0;
margin:0;
border-collapse:collapse;
border-spacing:0;
}
#flDebug .panelContent table a {
color:#000;
padding:2px 4px;
}
#flDebug .panelContent table a:hover {
background-color:#ffc;
}
#flDebug .panelContent table th {
background-color:#333;
font-weight:bold;
color:#fff;
padding:3px 7px 3px;
text-align:left;
cursor:pointer;
}
#flDebug .panelContent table td {
padding:5px 10px;
font-size:14px;
background:#fff;
color:#000;
vertical-align:top;
border:0;
}
#flDebug .panelContent table tr.flDebugOdd td {
background:#eee;
}
*/
#flDebug .panelContent .flDebugClose {
text-indent:-9999999px;
display:block;
position:absolute;
top:4px;
right:15px;
height:40px;
width:40px;
background:url(../img/close.png) no-repeat center center;
text-indent:-9999999px;
display:block;
position:absolute;
top:4px;
right:15px;
height:40px;
width:40px;
background:url(../img/close.png) no-repeat center center;
}
#flDebug .panelContent .flDebugClose:hover {
background-image:url(../img/close_hover.png);
background-image:url(../img/close_hover.png);
}
#flDebug .panelContent .flDebugClose.flDebugBack {
background-image:url(../img/back.png);
background-image:url(../img/back.png);
}
#flDebug .panelContent .flDebugClose.flDebugBack:hover {
background-image:url(../img/back_hover.png);
background-image:url(../img/back_hover.png);
}
#flDebug .panelContent dt, #flDebug .panelContent dd {
display:block;
display:block;
}
#flDebug .panelContent dt {
margin-top:0.75em;
margin-top:0.75em;
}
#flDebug .panelContent dd {
margin-left:10px;
margin-left:10px;
}
#flDebug a.toggleTemplate {
padding:4px;
background-color:#bbb;
-moz-border-radius:3px;
-webkit-border-radius:3px;
padding:4px;
background-color:#bbb;
-moz-border-radius:3px;
-webkit-border-radius:3px;
}
#flDebug a.toggleTemplate:hover {
padding:4px;
background-color:#444;
color:#ffe761;
-moz-border-radius:3px;
-webkit-border-radius:3px;
padding:4px;
background-color:#444;
color:#ffe761;
-moz-border-radius:3px;
-webkit-border-radius:3px;
}
#flDebug a.flTemplateShowContext, #flDebug a.flTemplateShowContext span.toggleArrow {
color:#999;
color:#999;
}
#flDebug a.flTemplateShowContext:hover, #flDebug a.flTemplateShowContext:hover span.toggleArrow {
color:#000;
cursor:pointer;
color:#000;
cursor:pointer;
}
#flDebug .flDebugSqlWrap {
position:relative;
position:relative;
}
#flDebug .flDebugSql {
z-index:100000002;
z-index:100000002;
}
#flDebug .flSQLHideStacktraceDiv tbody th {
text-align: left;
text-align: left;
}
#flDebug .flSqlExplain td {
white-space: pre;
white-space: pre;
}
#flDebug span.flDebugLineChart {
background-color:#777;
height:3px;
position:absolute;
bottom:0;
top:0;
left:0;
display:block;
z-index:1000000001;
background-color:#777;
height:3px;
position:absolute;
bottom:0;
top:0;
left:0;
display:block;
z-index:1000000001;
}
#flDebug span.flDebugLineChartWarning {
background-color:#900;
background-color:#900;
}
#flDebug .highlight { color:#000; }
@@ -414,24 +362,24 @@
/* tablesorted */
#flDebug table.tablesorter {
width: 100%;
width: 100%;
}
#flDebug table.tablesorter thead th, table.tablesorter tfoot th {
padding-right: 20px;
padding-right: 20px;
}
#flDebug table.tablesorter thead th {
background: url(../img/bg.gif) center right no-repeat;
cursor: pointer;
background: url(../img/bg.gif) center right no-repeat;
cursor: pointer;
}
#flDebug table.tablesorter tbody tr.odd td {
background-color: #F0F0F6;
background-color: #F0F0F6;
}
#flDebug table.tablesorter thead .headerSortUp {
background-image: url(../img/asc.gif);
background-image: url(../img/asc.gif);
}
#flDebug table.tablesorter thead .headerSortDown {
background-image: url(../img/desc.gif);
background-image: url(../img/desc.gif);
}
#flDebug table.tablesorter thead .headerSortDown, #flDebug table.tablesorter thead .headerSortUp {
background-color: #8dbdd8;
background-color: #8dbdd8;
}

View File

@@ -1,184 +1,192 @@
(function($) {
$.cookie = function(name, value, options) { if (typeof value != 'undefined') { options = options || {}; if (value === null) { value = ''; options.expires = -1; } var expires = ''; if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) { var date; if (typeof options.expires == 'number') { date = new Date(); date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000)); } else { date = options.expires; } expires = '; expires=' + date.toUTCString(); } var path = options.path ? '; path=' + (options.path) : ''; var domain = options.domain ? '; domain=' + (options.domain) : ''; var secure = options.secure ? '; secure' : ''; document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join(''); } else { var cookieValue = null; if (document.cookie && document.cookie != '') { var cookies = document.cookie.split(';'); for (var i = 0; i < cookies.length; i++) { var cookie = $.trim(cookies[i]); if (cookie.substring(0, name.length + 1) == (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } };
$('head').append('<link rel="stylesheet" href="'+DEBUG_TOOLBAR_STATIC_PATH+'css/toolbar.css?'+ Math.random() +'" type="text/css" />');
var COOKIE_NAME = 'fldt';
var COOKIE_NAME_ACTIVE = COOKIE_NAME +'_active';
var fldt = {
init: function() {
$('#flDebug').show();
var current = null;
$('#flDebugPanelList li a').click(function() {
if (!this.className) {
return false;
}
current = $('#flDebug #' + this.className + '-content');
if (current.is(':visible')) {
$(document).trigger('close.flDebug');
$(this).parent().removeClass('active');
} else {
$('.panelContent').hide(); // Hide any that are already open
current.show();
$('#flDebugToolbar li').removeClass('active');
$(this).parent().addClass('active');
}
return false;
});
$('#flDebugPanelList li .switch').click(function() {
var $panel = $(this).parent();
var $this = $(this);
var dom_id = $panel.attr('id');
// Turn cookie content into an array of active panels
var active_str = $.cookie(COOKIE_NAME_ACTIVE);
var active = (active_str) ? active_str.split(';') : [];
active = $.grep(active, function(n,i) { return n != dom_id; });
if ($this.hasClass('active')) {
$this.removeClass('active');
$this.addClass('inactive');
}
else {
active.push(dom_id);
$this.removeClass('inactive');
$this.addClass('active');
}
if (active.length > 0) {
$.cookie(COOKIE_NAME_ACTIVE, active.join(';'), {
path: '/', expires: 10
});
}
else {
$.cookie(COOKIE_NAME_ACTIVE, null, {
path: '/', expires: -1
});
}
});
$('#flDebug a.flDebugClose').click(function() {
$(document).trigger('close.flDebug');
$('#flDebugToolbar li').removeClass('active');
return false;
});
$('#flDebug a.remoteCall').click(function() {
$('#flDebugWindow').load(this.href, {}, function() {
$('#flDebugWindow a.flDebugBack').click(function() {
$(this).parent().parent().hide();
return false;
});
});
$('#flDebugWindow').show();
return false;
});
$('#flDebugTemplatePanel a.flTemplateShowContext').click(function() {
fldt.toggle_arrow($(this).children('.toggleArrow'))
fldt.toggle_content($(this).parent().next());
return false;
});
$('#flDebugSQLPanel a.flSQLShowStacktrace').click(function() {
fldt.toggle_content($('.flSQLHideStacktraceDiv', $(this).parents('tr')));
return false;
});
$('#flHideToolBarButton').click(function() {
fldt.hide_toolbar(true);
return false;
});
$('#flShowToolBarButton').click(function() {
fldt.show_toolbar();
return false;
});
$(document).bind('close.flDebug', function() {
// If a sub-panel is open, close that
if ($('#flDebugWindow').is(':visible')) {
$('#flDebugWindow').hide();
return;
}
// If a panel is open, close that
if ($('.panelContent').is(':visible')) {
$('.panelContent').hide();
return;
}
// Otherwise, just minimize the toolbar
if ($('#flDebugToolbar').is(':visible')) {
fldt.hide_toolbar(true);
return;
}
});
if ($.cookie(COOKIE_NAME)) {
fldt.hide_toolbar(false);
} else {
fldt.show_toolbar(false);
}
$('#flDebug table.tablesorter')
.tablesorter()
.bind('sortStart', function() {
$(this).find('tbody tr')
.removeClass('flDebugEven')
.removeClass('flDebugOdd');
})
.bind('sortEnd', function() {
$(this).find('tbody tr').each(function(idx, elem) {
var even = idx % 2 == 0;
$(elem)
.toggleClass('flDebugEven', even)
.toggleClass('flDebugOdd', !even);
});
});
},
toggle_content: function(elem) {
if (elem.is(':visible')) {
elem.hide();
} else {
elem.show();
}
},
close: function() {
$(document).trigger('close.flDebug');
return false;
},
hide_toolbar: function(setCookie) {
// close any sub panels
$('#flDebugWindow').hide();
// close all panels
$('.panelContent').hide();
$('#flDebugToolbar li').removeClass('active');
// finally close toolbar
$('#flDebugToolbar').hide('fast');
$('#flDebugToolbarHandle').show();
// Unbind keydown
$(document).unbind('keydown.flDebug');
if (setCookie) {
$.cookie(COOKIE_NAME, 'hide', {
path: '/',
expires: 10
});
}
},
show_toolbar: function(animate) {
// Set up keybindings
$(document).bind('keydown.flDebug', function(e) {
if (e.keyCode == 27) {
fldt.close();
}
});
$('#flDebugToolbarHandle').hide();
if (animate) {
$('#flDebugToolbar').show('fast');
} else {
$('#flDebugToolbar').show();
}
$.cookie(COOKIE_NAME, null, {
path: '/',
expires: -1
});
},
toggle_arrow: function(elem) {
var uarr = String.fromCharCode(0x25b6);
var darr = String.fromCharCode(0x25bc);
elem.html(elem.html() == uarr ? darr : uarr);
$.cookie = function(name, value, options) { if (typeof value != 'undefined') { options = options || {}; if (value === null) { value = ''; options.expires = -1; } var expires = ''; if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) { var date; if (typeof options.expires == 'number') { date = new Date(); date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000)); } else { date = options.expires; } expires = '; expires=' + date.toUTCString(); } var path = options.path ? '; path=' + (options.path) : ''; var domain = options.domain ? '; domain=' + (options.domain) : ''; var secure = options.secure ? '; secure' : ''; document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join(''); } else { var cookieValue = null; if (document.cookie && document.cookie != '') { var cookies = document.cookie.split(';'); for (var i = 0; i < cookies.length; i++) { var cookie = $.trim(cookies[i]); if (cookie.substring(0, name.length + 1) == (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } };
$('head').append('<link rel="stylesheet" href="'+DEBUG_TOOLBAR_STATIC_PATH+'css/toolbar.css?'+ Math.random() +'" type="text/css" />');
var COOKIE_NAME = 'fldt';
var COOKIE_NAME_ACTIVE = COOKIE_NAME +'_active';
var fldt = {
init: function() {
$('#flDebug').show();
var current = null;
$('#flDebugPanelList li a').click(function() {
if (!this.className) {
return false;
}
};
$(document).ready(function() {
fldt.init();
});
current = $('#flDebug #' + this.className + '-content');
if (current.is(':visible')) {
$(document).trigger('close.flDebug');
$(this).parent().removeClass('active');
} else {
$('.panelContent').hide(); // Hide any that are already open
current.show();
$('#flDebugToolbar li').removeClass('active');
$(this).parent().addClass('active');
}
return false;
});
$('#flDebugPanelList li .switch').click(function() {
var $panel = $(this).parent();
var $this = $(this);
var dom_id = $panel.attr('id');
// Turn cookie content into an array of active panels
var active_str = $.cookie(COOKIE_NAME_ACTIVE);
var active = (active_str) ? active_str.split(';') : [];
active = $.grep(active, function(n,i) { return n != dom_id; });
if ($this.hasClass('active')) {
$this.removeClass('active');
$this.addClass('inactive');
} else {
active.push(dom_id);
$this.removeClass('inactive');
$this.addClass('active');
}
if (active.length > 0) {
$.cookie(COOKIE_NAME_ACTIVE, active.join(';'), {
path: '/', expires: 10
});
} else {
$.cookie(COOKIE_NAME_ACTIVE, null, {
path: '/', expires: -1
});
}
});
$('#flDebug a.flDebugClose').click(function() {
$(document).trigger('close.flDebug');
$('#flDebugToolbar li').removeClass('active');
return false;
});
$('#flDebug a.remoteCall').click(function() {
$('#flDebugWindow').load(this.href, {}, function() {
$('#flDebugWindow a.flDebugBack').click(function() {
$(this).parent().parent().hide();
return false;
});
});
$('#flDebugWindow').show();
return false;
});
$('#flDebugTemplatePanel a.flTemplateShowContext').click(function() {
fldt.toggle_arrow($(this).children('.toggleArrow'))
fldt.toggle_content($(this).parent().next());
return false;
});
$('#flDebugSQLPanel a.flSQLShowStacktrace').click(function() {
fldt.toggle_content($('.flSQLHideStacktraceDiv', $(this).parents('tr')));
return false;
});
$('#flHideToolBarButton').click(function() {
fldt.hide_toolbar(true);
return false;
});
$('#flShowToolBarButton').click(function() {
fldt.show_toolbar();
return false;
});
$(document).bind('close.flDebug', function() {
// If a sub-panel is open, close that
if ($('#flDebugWindow').is(':visible')) {
$('#flDebugWindow').hide();
return;
}
// If a panel is open, close that
if ($('.panelContent').is(':visible')) {
$('.panelContent').hide();
return;
}
// Otherwise, just minimize the toolbar
if ($('#flDebugToolbar').is(':visible')) {
fldt.hide_toolbar(true);
return;
}
});
if ($.cookie(COOKIE_NAME)) {
fldt.hide_toolbar(false);
} else {
fldt.show_toolbar(false);
}
$('#flDebug table.tablesorter')
.tablesorter()
.bind('sortStart', function() {
$(this).find('tbody tr')
.removeClass('flDebugEven')
.removeClass('flDebugOdd');
})
.bind('sortEnd', function() {
$(this).find('tbody tr').each(function(idx, elem) {
var even = idx % 2 == 0;
$(elem)
.toggleClass('flDebugEven', even)
.toggleClass('flDebugOdd', !even);
});
});
},
toggle_content: function(elem) {
if (elem.is(':visible')) {
elem.hide();
} else {
elem.show();
}
},
close: function() {
$(document).trigger('close.flDebug');
return false;
},
hide_toolbar: function(setCookie) {
// close any sub panels
$('#flDebugWindow').hide();
// close all panels
$('.panelContent').hide();
$('#flDebugToolbar li').removeClass('active');
// finally close toolbar
$('#flDebugToolbar').hide('fast');
$('#flDebugToolbarHandle').show();
// Unbind keydown
$(document).unbind('keydown.flDebug');
if (setCookie) {
$.cookie(COOKIE_NAME, 'hide', {
path: '/',
expires: 10
});
}
},
show_toolbar: function(animate) {
// Set up keybindings
$(document).bind('keydown.flDebug', function(e) {
if (e.keyCode == 27) {
fldt.close();
}
});
$('#flDebugToolbarHandle').hide();
if (animate) {
$('#flDebugToolbar').show('fast');
} else {
$('#flDebugToolbar').show();
}
$.cookie(COOKIE_NAME, null, {
path: '/',
expires: -1
});
},
toggle_arrow: function(elem) {
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

@@ -1,56 +1,57 @@
<div id="flDebug" style="display:none;">
<script type="text/javascript">var DEBUG_TOOLBAR_STATIC_PATH = '{{ static_path }}'</script>
<script type="text/javascript" src="{{ static_path }}js/jquery.js"></script>
<script type="text/javascript" src="{{ static_path }}js/jquery.tablesorter.js"></script>
<script type="text/javascript" src="{{ static_path }}js/toolbar.js"></script>
<script type="text/javascript">var DEBUG_TOOLBAR_STATIC_PATH = '{{ static_path }}'</script>
<script type="text/javascript" src="{{ static_path }}js/jquery.js"></script>
<script type="text/javascript" src="{{ static_path }}js/jquery.tablesorter.js"></script>
<script type="text/javascript" src="{{ static_path }}js/toolbar.js"></script>
<div style="display: none;" id="flDebugToolbar">
<ol id="flDebugPanelList">
{% if panels %}
<li><a id="flHideToolBarButton" href="#" title="Hide Toolbar">Hide &raquo;</a></li>
{% else %}
<li id="flDebugButton">DEBUG</li>
{% endif %}
{% for panel in panels %}
<li id="{{ panel.dom_id() }}">
{% if panel.has_content %}
<a href="{{ panel.url()|default("#") }}" title="{{ panel.title() }}" class="{{ panel.dom_id() }}">
{% else %}
<div class="contentless">
{% endif %}
<div style="display: none;" id="flDebugToolbar">
<ol id="flDebugPanelList">
{% if panels %}
<li><a id="flHideToolBarButton" href="#" title="Hide Toolbar">Hide &raquo;</a></li>
{% else %}
<li id="flDebugButton">DEBUG</li>
{% endif %}
{{ panel.nav_title() }}
{% if panel.nav_subtitle() %}<br /><small>{{ panel.nav_subtitle() }}</small>{% endif %}
{% for panel in panels %}
<li id="{{ panel.dom_id() }}">
{% if panel.has_content %}
<a href="{{ panel.url()|default("#") }}" title="{{ panel.title() }}" class="{{ panel.dom_id() }}">
{% else %}
<div class="contentless">
{% endif %}
{% if panel.has_content %}
</a>
{% else %}
</div>
{% endif %}
{% if panel.user_activate %}
<span class="switch {{ 'active' if panel.is_active else 'inactive' }}" title="Enable or disable the panel"></span>
{% endif %}
</li>
{% endfor %}
</ol>
{{ panel.nav_title() }}
{% if panel.nav_subtitle() %}<br /><small>{{ panel.nav_subtitle() }}</small>{% endif %}
{% if panel.has_content %}
</a>
{% else %}
</div>
{% endif %}
{% if panel.user_activate %}
<span class="switch {{ 'active' if panel.is_active else 'inactive' }}" title="Enable or disable the panel"></span>
{% endif %}
</li>
{% endfor %}
</ol>
</div>
<div style="display:none;" id="flDebugToolbarHandle">
<a title="Show Toolbar" id="flShowToolBarButton" href="#">&laquo;</a>
</div>
{% for panel in panels %}
{% if panel.has_content %}
<div id="{{ panel.dom_id() }}-content" class="panelContent">
<div class="flDebugPanelTitle">
<a href="" class="flDebugClose">Close</a>
<h3>{{ panel.title()|safe }}</h3>
</div>
<div class="flDebugPanelContent">
<div class="scroll">
{{ panel.content()|safe }}
</div>
</div>
</div>
<div style="display:none;" id="flDebugToolbarHandle">
<a title="Show Toolbar" id="flShowToolBarButton" href="#">&laquo;</a>
</div>
{% for panel in panels %}
{% if panel.has_content %}
<div id="{{ panel.dom_id() }}-content" class="panelContent">
<div class="flDebugPanelTitle">
<a href="" class="flDebugClose">Close</a>
<h3>{{ panel.title()|safe }}</h3>
</div>
<div class="flDebugPanelContent">
<div class="scroll">
{{ panel.content()|safe }}
</div>
</div>
</div>
{% endif %}
{% endfor %}
<div id="flDebugWindow" class="panelContent"></div>
{% endif %}
{% endfor %}
<div id="flDebugWindow" class="panelContent"></div>
</div>

View File

@@ -0,0 +1,16 @@
<table>
<thead>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{% for key, value in config|dictsort %}
<tr class="{{ loop.cycle('flDebugOdd', 'flDebugEven') }}">
<td>{{ key }}</td>
<td><code>{{ value|printable }}</code></td>
</tr>
{% endfor %}
</tbody>
</table>

View File

@@ -1,16 +1,16 @@
<table>
<thead>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{% for key, value in headers.iteritems() %}
<tr class="{{ loop.cycle('flDebugOdd', 'flDebugEven') }}">
<td>{{ key|escape }}</td>
<td>{{ value|escape }}</td>
</tr>
{% endfor %}
</tbody>
<thead>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{% for key, value in headers|dictsort %}
<tr class="{{ loop.cycle('flDebugOdd', 'flDebugEven') }}">
<td>{{ key|escape }}</td>
<td>{{ value|escape }}</td>
</tr>
{% endfor %}
</tbody>
</table>

View File

@@ -1,26 +1,26 @@
{% if records %}
<table>
<thead>
<tr>
<th>Level</th>
<th>Time</th>
<th>Message</th>
<th>Location</th>
</tr>
</thead>
<tbody>
{% for record in records %}
<tr class="{{ loop.cycle('flDebugOdd', 'flDebugEven') }}">
<td>{{ record.level }}</td>
<td>{{ record.time }}</td>
<td>{{ record.message }}</td>
<td title="{{ record.file_long }}:{{ record.line }}">{{ record.file }}:{{ record.line }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<table>
<thead>
<tr>
<th>Level</th>
<th>Time</th>
<th>Message</th>
<th>Location</th>
</tr>
</thead>
<tbody>
{% for record in records %}
<tr class="{{ loop.cycle('flDebugOdd', 'flDebugEven') }}">
<td>{{ record.level }}</td>
<td>{{ record.time }}</td>
<td>{{ record.message }}</td>
<td title="{{ record.file_long }}:{{ record.line }}">{{ record.file }}:{{ record.line }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>No messages logged.</p>
<p>No messages logged.</p>
{% endif %}

View File

@@ -1,25 +1,25 @@
<table id="debug_toolbar_profiler_table" class="tablesorter">
<thead>
<tr>
<th>Calls</th>
<th>Total Time (ms)</th>
<th>Per Call (ms)</th>
<th>Cumulative Time (ms)</th>
<th>Per Call (ms)</th>
<th>Function</th>
</tr>
</thead>
<tbody>
{% for row in function_calls %}
<tr class="{{ loop.cycle('flDebugOdd', 'flDebugEven') }}">
<td>{{ row.ncalls }}</td>
<td>{{ row.tottime }}</td>
<td>{{ '%.4f'|format(row.percall) }}</td>
<td>{{ row.cumtime }}</td>
<td>{{ '%.4f'|format(row.percall_cum) }}</td>
<td title="{{ row.filename_long }}">{{ row.filename|escape }}</td>
</tr>
{% endfor %}
</tbody>
<thead>
<tr>
<th>Calls</th>
<th>Total Time (ms)</th>
<th>Per Call (ms)</th>
<th>Cumulative Time (ms)</th>
<th>Per Call (ms)</th>
<th>Function</th>
</tr>
</thead>
<tbody>
{% for row in function_calls %}
<tr class="{{ loop.cycle('flDebugOdd', 'flDebugEven') }}">
<td>{{ row.ncalls }}</td>
<td>{{ row.tottime }}</td>
<td>{{ '%.4f'|format(row.percall) }}</td>
<td>{{ row.cumtime }}</td>
<td>{{ '%.4f'|format(row.percall_cum) }}</td>
<td title="{{ row.filename_long }}">{{ row.filename|escape }}</td>
</tr>
{% endfor %}
</tbody>
</table>

View File

@@ -1,104 +1,104 @@
<h4>View information</h4>
<table>
<thead>
<tr>
<th>View Function</th>
<th>args</th>
<th>kwargs</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ view_func }}</td>
<td>{{ view_args|default("None") }}</td>
<td>
{% if view_kwargs.items() %}
{% for k, v in view_kwargs.items() %}
{{ k }}={{ v }}{% if not loop.last %}, {% endif %}
{% endfor %}
{% else %}
None
{% endif %}
</td>
</tr>
</tbody>
<thead>
<tr>
<th>View Function</th>
<th>args</th>
<th>kwargs</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ view_func }}</td>
<td>{{ view_args|default("None") }}</td>
<td>
{% if view_kwargs.items() %}
{% for k, v in view_kwargs.items() %}
{{ k }}={{ v }}{% if not loop.last %}, {% endif %}
{% endfor %}
{% else %}
None
{% endif %}
</td>
</tr>
</tbody>
</table>
{% macro show_map(map) %}
<table>
<colgroup>
<col style="width:20%"/>
<col/>
</colgroup>
<thead>
<tr>
<th>Variable</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{% for key, value in map %}
<tr class="{{ loop.cycle('flDebugOdd', 'flDebugEven') }}">
<td>{{ key|printable }}</td>
<td>{{ value|printable }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<table>
<colgroup>
<col style="width:20%"/>
<col/>
</colgroup>
<thead>
<tr>
<th>Variable</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{% for key, value in map %}
<tr class="{{ loop.cycle('flDebugOdd', 'flDebugEven') }}">
<td>{{ key|printable }}</td>
<td>{{ value|printable }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endmacro %}
<h4>COOKIES Variables</h4>
{% if cookies %}
{{ show_map(cookies) }}
{{ show_map(cookies) }}
{% else %}
<p>No COOKIE data</p>
<p>No COOKIE data</p>
{% endif %}
<h4>SESSION Variables</h4>
{% if session %}
{{ show_map(session) }}
{{ show_map(session) }}
{% else %}
<p>No SESSION data</p>
<p>No SESSION data</p>
{% endif %}
{% macro show_multi_map(map) %}
<table>
<thead>
<tr>
<th>Variable</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{% for key, value in map %}
<tr class="{{ loop.cycle('flDebugOdd', 'flDebugEven') }}">
<td>{{ key|printable }}</td>
<td>
{%- set sep = joiner() -%}
{%- for v in value -%}
{{ sep() }}{{ v|printable }}
{%- endfor -%}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{% for key, value in map %}
<tr class="{{ loop.cycle('flDebugOdd', 'flDebugEven') }}">
<td>{{ key|printable }}</td>
<td>
{%- set sep = joiner() -%}
{%- for v in value -%}
{{ sep() }}{{ v|printable }}
{%- endfor -%}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endmacro %}
<h4>GET Variables</h4>
{% if get %}
{{ show_multi_map(get) }}
{{ show_multi_map(get) }}
{% else %}
<p>No GET data</p>
<p>No GET data</p>
{% endif %}
<h4>POST Variables</h4>
{% if post %}
{{ show_multi_map(post) }}
{{ show_multi_map(post) }}
{% else %}
<p>No POST data</p>
<p>No POST data</p>
{% endif %}

View File

@@ -1,68 +1,63 @@
<table>
<thead>
<tr>
<th>&nbsp;(ms)</th>
<th>Action</th>
<th>Context</th>
<th>Query</th>
</tr>
</thead>
<tbody>
{% for query in queries %}
<tr class="{{ loop.cycle('flDebugOdd', 'flDebugEven') }}">
<td>{{ '%.4f'|format(query.duration) }}</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 %}
{% endif %}
</td>
<td title="{{ query.context_long }}">
<thead>
<tr>
<th>&nbsp;(ms)</th>
<th>Action</th>
<th>Context</th>
<th>Query</th>
</tr>
</thead>
<tbody>
{% for query in queries %}
<tr class="{{ loop.cycle('flDebugOdd', 'flDebugEven') }}">
<td>{{ '%.4f'|format(query.duration * 1000) }}</td>
<td>
{% 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 }}">
{{ query.context }}
</td>
<td class="syntax">
<div class="flDebugSqlWrap">
<div class="flDebugSql">{{ query.sql|safe }}</div>
{#
{% if query.stacktrace %}
<div class="djSQLHideStacktraceDiv" style="display:none;">
<table>
<tr>
<th>{% trans "Line" %}</th>
<th>{% trans "Method" %}</th>
<th>{% trans "File" %}</th>
</tr>
{% for file, line, method in query.stacktrace %}
<tr>
<td>{{ line }}</td>
<td><code>{{ method|escape }}</code></td>
<td><code>{{ file|escape }}</code></td>
</tr>
{% endfor %}
</table>
{% if query.template_info %}
<table>
{% for line in query.template_info.context %}
<tr>
<td>{{ line.num }}</td>
<td><code style="font-family: monospace;{% if line.highlight %}background-color: lightgrey{% endif %}">{{ line.content }}</code></td>
</tr>
{% endfor %}
</table>
<p><strong>{{ query.template_info.name|default:"(unknown)" }}</strong></p>
{% endif %}
</div>
{% endif %}
</td>
<td class="syntax">
<div class="flDebugSqlWrap">
<div class="flDebugSql">{{ query.sql|safe }}</div>
{#
{% if query.stacktrace %}
<div class="djSQLHideStacktraceDiv" style="display:none;">
<table>
<tr>
<th>{% trans "Line" %}</th>
<th>{% trans "Method" %}</th>
<th>{% trans "File" %}</th>
</tr>
{% for file, line, method in query.stacktrace %}
<tr>
<td>{{ line }}</td>
<td><code>{{ method|escape }}</code></td>
<td><code>{{ file|escape }}</code></td>
</tr>
{% endfor %}
</table>
{% if query.template_info %}
<table>
{% for line in query.template_info.context %}
<tr>
<td>{{ line.num }}</td>
<td><code style="font-family: monospace;{% if line.highlight %}background-color: lightgrey{% endif %}">{{ line.content }}</code></td>
</tr>
{% endfor %}
</table>
<p><strong>{{ query.template_info.name|default:"(unknown)" }}</strong></p>
{% endif %}
</div>
{% endif %}
<span class="djDebugLineChart{% if query.is_slow %} djDebugLineChartWarning{% endif %}" style="width:{{ query.width_ratio }}%; left:{{ query.start_offset }}%;"></span>
#}
</div>
</td>
</tr>
{% endfor %}
</tbody>
#}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>

View File

@@ -1,33 +1,33 @@
<div class="flDebugPanelTitle">
<a class="flDebugClose flDebugBack" href="">Back</a>
<h3>SQL Explained</h3>
<a class="flDebugClose flDebugBack" href="">Back</a>
<h3>SQL Explained</h3>
</div>
<div class="flDebugPanelContent">
<div class="scroll">
<dl>
<dt>Executed SQL</dt>
<dd>{{ sql|safe }}</dd>
<dt>Time</dt>
<dd>{{ '%.4f'|format(duration) }} ms</dd>
</dl>
<table class="flSqlExplain">
<thead>
<tr>
{% for h in headers %}
<th>{{ h|upper }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in result %}
<tr class="{{ loop.cycle('fjDebugOdd', 'fjDebugEven') }}">
{% for column in row %}
<td>{{ column }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="scroll">
<dl>
<dt>Executed SQL</dt>
<dd>{{ sql|safe }}</dd>
<dt>Time</dt>
<dd>{{ '%.4f'|format(duration) }} ms</dd>
</dl>
<table class="flSqlExplain">
<thead>
<tr>
{% for h in headers %}
<th>{{ h|upper }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in result %}
<tr class="{{ loop.cycle('fjDebugOdd', 'fjDebugEven') }}">
{% for column in row %}
<td>{{ column }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>

View File

@@ -1,37 +1,36 @@
<div class="flDebugPanelTitle">
<a class="flDebugClose flDebugBack" href="">Back</a>
<h3>SQL Explained</h3>
<a class="flDebugClose flDebugBack" href="">Back</a>
<h3>SQL Explained</h3>
</div>
<div class="flDebugPanelContent">
<div class="scroll">
<dl>
<dt>Executed SQL</dt>
<dd>{{ sql|safe }}</dd>
<dt>Time</dt>
<dd>{{ '%.4f'|format(duration) }} ms</dd>
</dl>
{% if result %}
<table class="flSqlSelect">
<thead>
<tr>
{% for h in headers %}
<th>{{ h|upper }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in result %}
<tr class="{{ loop.cycle('flDebugOdd', 'flDebugEven') }}">
{% for column in row %}
<td>{{ column }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>Empty set</p>
{% endif %}
</div>
<div class="scroll">
<dl>
<dt>Executed SQL</dt>
<dd>{{ sql|safe }}</dd>
<dt>Time</dt>
<dd>{{ '%.4f'|format(duration) }} ms</dd>
</dl>
{% if result %}
<table class="flSqlSelect">
<thead>
<tr>
{% for h in headers %}
<th>{{ h|upper }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in result %}
<tr class="{{ loop.cycle('flDebugOdd', 'flDebugEven') }}">
{% for column in row %}
<td>{{ column }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>Empty set</p>
{% endif %}
</div>
</div>

View File

@@ -1,36 +1,26 @@
{% 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>
{% endif %}
{% for template in templates %}
<h4>{{ template.template.name }}</h4>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{% for k, v in template.context|dictsort %}
<tr class="{{ loop.cycle('flDebugOdd', 'flDebugEven') }}">
<td>{{ k }}</td>
<td>{{ v|printable }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endfor %}
{% if editable %}
<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>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{% for k, v in template.context|dictsort %}
<tr class="{{ loop.cycle('flDebugOdd', 'flDebugEven') }}">
<td>{{ k }}</td>
<td>{{ v|printable }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endfor %}
{% else %}
<p>No template rendered</p>
<p>No template rendered</p>
{% endif %}

View File

@@ -1,21 +1,21 @@
<table>
<colgroup>
<col style="width:20%"/>
<col/>
</colgroup>
<thead>
<tr>
<th>Resource</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{% for key, value in rows %}
<tr class="{{ loop.cycle('flDebugOdd', 'flDebugEven') }}">
<td>{{ key|escape }}</td>
<td>{{ value|escape }}</td>
</tr>
{% endfor %}
</tbody>
<colgroup>
<col style="width:20%"/>
<col/>
</colgroup>
<thead>
<tr>
<th>Resource</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{% for key, value in rows %}
<tr class="{{ loop.cycle('flDebugOdd', 'flDebugEven') }}">
<td>{{ key|escape }}</td>
<td>{{ value|escape }}</td>
</tr>
{% endfor %}
</tbody>
</table>

View File

@@ -1,15 +1,15 @@
<html>
<head>
<title>Redirect intercepted</title>
</head>
<body>
<h1>Redirect ({{ redirect_code }})</h1>
<p>Location: <a href="{{ redirect_to }}">{{ redirect_to }}</a></p>
<p class="notice">
The Flask Debug Toolbar has intercepted a redirect to the above URL for
debug viewing purposes. You can click the above link to continue with the
redirect as normal. If you'd like to disable this feature, you can set the
config variable <code>DEBUG_TB_INTERCEPT_REDIRECTS</code> to <code>False</code>.
</p>
</body>
<head>
<title>Redirect intercepted</title>
</head>
<body>
<h1>Redirect ({{ redirect_code }})</h1>
<p>Location: <a href="{{ redirect_to }}">{{ redirect_to }}</a></p>
<p class="notice">
The Flask Debug Toolbar has intercepted a redirect to the above URL for
debug viewing purposes. You can click the above link to continue with the
redirect as normal. If you'd like to disable this feature, you can set the
config variable <code>DEBUG_TB_INTERCEPT_REDIRECTS</code> to <code>False</code>.
</p>
</body>
</html>

View File

@@ -1,27 +1,15 @@
import urllib
try:
from urllib.parse import unquote
except ImportError:
from urllib import unquote
from flask import url_for, current_app
from werkzeug.utils import import_string
class DebugToolbar(object):
# default config settings
config = {
'DEBUG_TB_INTERCEPT_REDIRECTS': True,
'DEBUG_TB_PANELS': (
'flask_debugtoolbar.panels.versions.VersionDebugPanel',
'flask_debugtoolbar.panels.timer.TimerDebugPanel',
'flask_debugtoolbar.panels.headers.HeaderDebugPanel',
'flask_debugtoolbar.panels.request_vars.RequestVarsDebugPanel',
'flask_debugtoolbar.panels.template.TemplateDebugPanel',
'flask_debugtoolbar.panels.sqlalchemy.SQLAlchemyDebugPanel',
'flask_debugtoolbar.panels.logger.LoggingPanel',
'flask_debugtoolbar.panels.profiler.ProfilerDebugPanel',
)
}
panel_classes = []
_cached_panel_classes = {}
def __init__(self, request, jinja_env):
self.jinja_env = jinja_env
@@ -34,36 +22,19 @@ class DebugToolbar(object):
self.create_panels()
@classmethod
def load_panels(cls, app):
cls.config.update(app.config)
for panel_path in cls.config['DEBUG_TB_PANELS']:
dot = panel_path.rindex('.')
panel_module, panel_classname = panel_path[:dot], panel_path[dot+1:]
try:
mod = __import__(panel_module, {}, {}, [''])
except ImportError, e:
app.logger.warning('Disabled %s due to ImportError: %s', panel_classname, e)
continue
panel_class = getattr(mod, panel_classname)
cls.panel_classes.append(panel_class)
def create_panels(self):
"""
Populate debug panels
"""
activated = self.request.cookies.get('fldt_active', '')
activated = urllib.unquote(activated).split(';')
activated = unquote(activated).split(';')
for panel_class in self.panel_classes:
panel_instance = panel_class(
context=self.template_context,
jinja_env=self.jinja_env)
for panel_class in self._iter_panels(current_app):
panel_instance = panel_class(jinja_env=self.jinja_env, context=self.template_context)
if panel_instance.dom_id() in activated:
panel_instance.is_active = True
self.panels.append(panel_instance)
def render_toolbar(self):
@@ -73,4 +44,33 @@ class DebugToolbar(object):
template = self.jinja_env.get_template('base.html')
return template.render(**context)
@classmethod
def load_panels(cls, app):
for panel_class in cls._iter_panels(app):
# just loop to make sure they've been loaded
pass
@classmethod
def _iter_panels(cls, app):
for panel_path in app.config['DEBUG_TB_PANELS']:
panel_class = cls._import_panel(app, panel_path)
if panel_class is not None:
yield panel_class
@classmethod
def _import_panel(cls, app, path):
cache = cls._cached_panel_classes
try:
return cache[path]
except KeyError:
pass
try:
panel_class = import_string(path)
except ImportError as e:
app.logger.warning('Disabled %s due to ImportError: %s', path, e)
panel_class = None
cache[path] = panel_class
return panel_class

View File

@@ -14,6 +14,7 @@ except ImportError:
from flask import current_app
def format_fname(value):
# If the value is not an absolute path, the it is a builtin or
# a relative file (thus a project file).
@@ -45,6 +46,7 @@ def format_fname(value):
path = value[prefix_len:]
return '<%s>' % path
def format_sql(query, args):
if not HAVE_PYGMENTS:
return query
@@ -53,4 +55,3 @@ def format_sql(query, args):
query,
SqlLexer(encoding='utf-8'),
HtmlFormatter(encoding='utf-8', noclasses=True, style=PYGMENT_STYLE))

View File

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

30
test/basic_app.py Normal file
View File

@@ -0,0 +1,30 @@
from flask import Flask, render_template
from flask_debugtoolbar import DebugToolbarExtension
from flask_sqlalchemy import SQLAlchemy
app = Flask('basic_app')
app.debug = True
app.config['SECRET_KEY'] = 'abc123'
# make sure these are printable in the config panel
app.config['BYTES_VALUE'] = b'\x00'
app.config['UNICODE_VALUE'] = u'\uffff'
toolbar = DebugToolbarExtension(app)
db = SQLAlchemy(app)
class Foo(db.Model):
__tablename__ = 'foo'
id = db.Column(db.Integer, primary_key=True)
@app.route('/')
def index():
db.create_all()
Foo.query.filter_by(id=1).all()
return render_template('basic_app.html')
if __name__ == '__main__':
app.run()

View File

@@ -0,0 +1,4 @@
<!doctype html>
<html>
<body>Hello world</body>
</html>

11
test/test_toolbar.py Normal file
View File

@@ -0,0 +1,11 @@
def load_app(name):
app = __import__(name).app
app.config['TESTING'] = True
return app.test_client()
def test_basic_app():
app = load_app('basic_app')
index = app.get('/')
assert index.status_code == 200
assert b'<div id="flDebug"' in index.data

9
tox.ini Normal file
View File

@@ -0,0 +1,9 @@
[tox]
envlist = py26,py27,py33
[testenv]
deps =
pytest
Flask-SQLAlchemy
commands =
py.test