mirror of
https://github.com/pallets-eco/flask-debugtoolbar.git
synced 2025-12-31 10:39:33 -06:00
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bebe884615 | ||
|
|
10c03880c7 | ||
|
|
3fcdfc8f83 | ||
|
|
914553ddf5 | ||
|
|
b4fe0b954c | ||
|
|
7557ee6794 | ||
|
|
382c5e7da9 | ||
|
|
6286dadc27 | ||
|
|
82295aa4aa | ||
|
|
70488fc14a | ||
|
|
4901d68b01 | ||
|
|
d671a849bb | ||
|
|
ffbdf0f563 | ||
|
|
b08fe477b6 | ||
|
|
903ed85f00 | ||
|
|
5710314252 | ||
|
|
277462d822 | ||
|
|
26691d49cd | ||
|
|
bd37db9dc4 | ||
|
|
091c4fa70c | ||
|
|
d5987410d1 | ||
|
|
6d8249160d | ||
|
|
1e046d57f4 | ||
|
|
9e3546b416 | ||
|
|
26034475f2 | ||
|
|
356e6c8268 | ||
|
|
5870fce178 | ||
|
|
855c7ab377 | ||
|
|
115e874a39 | ||
|
|
b7c39113e7 | ||
|
|
05288daf17 | ||
|
|
79421df5cd | ||
|
|
8d5d37fa0b | ||
|
|
1029b9131b | ||
|
|
3bea63dc8a | ||
|
|
856eb52a95 | ||
|
|
50017f13a7 | ||
|
|
561889738f | ||
|
|
b97e58c1f2 | ||
|
|
0f923475c8 | ||
|
|
43df69ab24 | ||
|
|
3e4cd1ecfa | ||
|
|
cfa5984d3e | ||
|
|
c9c0228a62 | ||
|
|
d87e7fc347 | ||
|
|
80eb747816 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,3 +3,4 @@
|
||||
build/
|
||||
dist/
|
||||
docs/_build
|
||||
.tox
|
||||
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal 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
9
.travis.yml
Normal 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
|
||||
61
CHANGES.rst
61
CHANGES.rst
@@ -1,6 +1,65 @@
|
||||
Changes
|
||||
=======
|
||||
|
||||
0.9.2 (2014-12-05)
|
||||
------------------
|
||||
|
||||
Fixes:
|
||||
|
||||
- HTML escape SQL queries when syntax highlighting is not available
|
||||
- Use case-insensitive comparison to normalize filenames on Windows
|
||||
- Fix exception when SQL query contained non-ASCII characters
|
||||
|
||||
0.9.1 (2014-11-24)
|
||||
------------------
|
||||
|
||||
Fixes:
|
||||
|
||||
- Fix SQL queries with byte strings on Python 3
|
||||
- Fix displaying values whose `repr()` contains unprintable characters
|
||||
|
||||
|
||||
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 +71,7 @@ Enhancements:
|
||||
|
||||
|
||||
0.6.3.1 (2012-04-16)
|
||||
------------------
|
||||
--------------------
|
||||
|
||||
New release to add missing changelog for 0.6.3
|
||||
|
||||
|
||||
@@ -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
|
||||
------------
|
||||
|
||||
@@ -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
1
docs/_themes
Submodule
Submodule docs/_themes added at 0269f3d188
14
docs/conf.py
14
docs/conf.py
@@ -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.2'
|
||||
|
||||
# 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".
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -1,37 +1,38 @@
|
||||
import os
|
||||
|
||||
from flask import current_app, request, g
|
||||
from flask import Blueprint, current_app, request, g, send_from_directory
|
||||
from flask.globals import _request_ctx_stack
|
||||
from flask import send_from_directory
|
||||
from jinja2 import Environment, PackageLoader
|
||||
from werkzeug.exceptions import HTTPException
|
||||
from werkzeug.urls import url_quote_plus
|
||||
|
||||
from flask_debugtoolbar.compat import iteritems
|
||||
from flask_debugtoolbar.toolbar import DebugToolbar
|
||||
from flask import Blueprint
|
||||
from flask_debugtoolbar.utils import decode_text
|
||||
|
||||
|
||||
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:
|
||||
return repr(value)
|
||||
try:
|
||||
return decode_text(repr(value))
|
||||
except Exception as e:
|
||||
return '<repr(%s) raised %s: %s>' % (
|
||||
object.__repr__(value), type(e).__name__, e)
|
||||
|
||||
|
||||
class DebugToolbarExtension(object):
|
||||
@@ -40,29 +41,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 +54,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 +125,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 +169,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 +186,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)
|
||||
|
||||
|
||||
9
flask_debugtoolbar/compat.py
Normal file
9
flask_debugtoolbar/compat.py
Normal 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())
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
29
flask_debugtoolbar/panels/config_vars.py
Normal file
29
flask_debugtoolbar/panels/config_vars.py
Normal 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)
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import hashlib
|
||||
|
||||
try:
|
||||
from flask.ext.sqlalchemy import get_debug_queries, SQLAlchemy
|
||||
except ImportError:
|
||||
@@ -9,25 +7,58 @@ 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 is_select(statement):
|
||||
prefix = b'select' if isinstance(statement, bytes) else 'select'
|
||||
return statement.lower().strip().startswith(prefix)
|
||||
|
||||
|
||||
def dump_query(statement, params):
|
||||
if not params or not is_select(statement):
|
||||
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 is_select(statement):
|
||||
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,87 +97,35 @@ 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)
|
||||
|
||||
@module.route('/sqlalchemy/sql_explain', methods=['GET', 'POST'],
|
||||
defaults=dict(explain=True))
|
||||
def sql_select(explain=False):
|
||||
statement, params = load_query(request.args['query'])
|
||||
engine = SQLAlchemy().get_engine(current_app)
|
||||
|
||||
if explain:
|
||||
if engine.driver == 'pysqlite':
|
||||
statement = 'EXPLAIN QUERY PLAN\n%s' % statement
|
||||
else:
|
||||
statement = 'EXPLAIN\n%s' % statement
|
||||
|
||||
result = engine.execute(statement, params)
|
||||
return g.debug_toolbar.render('panels/sqlalchemy_select.html', {
|
||||
'result': result.fetchall(),
|
||||
'headers': result.keys(),
|
||||
'sql': format_sql(statement, params),
|
||||
'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)
|
||||
|
||||
engine = SQLAlchemy().get_engine(current_app)
|
||||
|
||||
if engine.driver == 'pysqlite':
|
||||
query = 'EXPLAIN QUERY PLAN %s' % statement
|
||||
else:
|
||||
query = 'EXPLAIN %s' % statement
|
||||
|
||||
result = engine.execute(query, params)
|
||||
return g.debug_toolbar.render('panels/sqlalchemy_explain.html', {
|
||||
'result': result.fetchall(),
|
||||
'headers': result.keys(),
|
||||
'sql': format_sql(statement, params),
|
||||
'duration': float(request.args['duration']),
|
||||
})
|
||||
})
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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,320 @@
|
||||
}
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
#flDebug .flSqlExplain td {
|
||||
white-space: pre;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#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 +358,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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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 »</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 »</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="#">«</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="#">«</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>
|
||||
|
||||
16
flask_debugtoolbar/templates/panels/config_vars.html
Normal file
16
flask_debugtoolbar/templates/panels/config_vars.html
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -1,68 +1,31 @@
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> (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 }}&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 %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td title="{{ query.context_long }}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th> (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 }}&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 }}">
|
||||
{{ 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 %}
|
||||
<span class="djDebugLineChart{% if query.is_slow %} djDebugLineChartWarning{% endif %}" style="width:{{ query.width_ratio }}%; left:{{ query.start_offset }}%;"></span>
|
||||
#}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</td>
|
||||
<td class="syntax">
|
||||
<div class="flDebugSqlWrap">
|
||||
<div class="flDebugSql">{{ query.sql }}</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
<div class="flDebugPanelTitle">
|
||||
<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>
|
||||
|
||||
@@ -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 Details</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 }}</dd>
|
||||
<dt>Original query duration</dt>
|
||||
<dd>{{ '%.4f'|format(duration * 1000) }} 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>
|
||||
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import itertools
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
@@ -12,45 +13,65 @@ except ImportError:
|
||||
HAVE_PYGMENTS = False
|
||||
|
||||
|
||||
from flask import current_app
|
||||
from flask import current_app, Markup
|
||||
|
||||
|
||||
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).
|
||||
# If the value has a builtin prefix, return it unchanged
|
||||
if value.startswith(('{', '<')):
|
||||
return value
|
||||
|
||||
value = os.path.normpath(value)
|
||||
|
||||
# If the file is absolute, try normalizing it relative to the project root
|
||||
# to handle it as a project file
|
||||
if os.path.isabs(value):
|
||||
value = _shortest_relative_path(
|
||||
value, [current_app.root_path], os.path)
|
||||
|
||||
# If the value is a relative path, it is a project file
|
||||
if not os.path.isabs(value):
|
||||
if value.startswith(('{', '<')):
|
||||
return value
|
||||
if value.startswith('.' + os.path.sep):
|
||||
return value
|
||||
return '.' + os.path.sep + value
|
||||
return os.path.join('.', value)
|
||||
|
||||
# If the file is absolute and within the project root handle it as
|
||||
# a project file
|
||||
if value.startswith(current_app.root_path):
|
||||
return "." + value[len(current_app.root_path):]
|
||||
# Otherwise, normalize other paths relative to sys.path
|
||||
return '<%s>' % _shortest_relative_path(value, sys.path, os.path)
|
||||
|
||||
# Loop through sys.path to find the longest match and return
|
||||
# the relative path from there.
|
||||
paths = sys.path
|
||||
prefix = None
|
||||
prefix_len = 0
|
||||
for path in sys.path:
|
||||
new_prefix = os.path.commonprefix([path, value])
|
||||
if len(new_prefix) > prefix_len:
|
||||
prefix = new_prefix
|
||||
prefix_len = len(prefix)
|
||||
|
||||
if not prefix.endswith(os.path.sep):
|
||||
prefix_len -= 1
|
||||
path = value[prefix_len:]
|
||||
return '<%s>' % path
|
||||
def _shortest_relative_path(value, paths, path_module):
|
||||
relpaths = _relative_paths(value, paths, path_module)
|
||||
return min(itertools.chain(relpaths, [value]), key=len)
|
||||
|
||||
|
||||
def _relative_paths(value, paths, path_module):
|
||||
for path in paths:
|
||||
try:
|
||||
relval = path_module.relpath(value, path)
|
||||
except ValueError:
|
||||
# on Windows, relpath throws a ValueError for
|
||||
# paths with different drives
|
||||
continue
|
||||
if not relval.startswith(path_module.pardir):
|
||||
yield relval
|
||||
|
||||
|
||||
def decode_text(value):
|
||||
"""
|
||||
Decode a text-like value for display.
|
||||
|
||||
Unicode values are returned unchanged. Byte strings will be decoded
|
||||
with a text-safe replacement for unrecognized characters.
|
||||
"""
|
||||
if isinstance(value, bytes):
|
||||
return value.decode('ascii', 'replace')
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
def format_sql(query, args):
|
||||
if not HAVE_PYGMENTS:
|
||||
return query
|
||||
return decode_text(query)
|
||||
|
||||
return highlight(
|
||||
return Markup(highlight(
|
||||
query,
|
||||
SqlLexer(encoding='utf-8'),
|
||||
HtmlFormatter(encoding='utf-8', noclasses=True, style=PYGMENT_STYLE))
|
||||
|
||||
SqlLexer(),
|
||||
HtmlFormatter(noclasses=True, style=PYGMENT_STYLE)))
|
||||
|
||||
6
setup.py
6
setup.py
@@ -14,8 +14,8 @@ except:
|
||||
|
||||
setup(
|
||||
name='Flask-DebugToolbar',
|
||||
version='0.7',
|
||||
url='http://github.com/mgood/flask-debugtoolbar',
|
||||
version='0.9.2',
|
||||
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
30
test/basic_app.py
Normal 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()
|
||||
4
test/templates/basic_app.html
Normal file
4
test/templates/basic_app.html
Normal file
@@ -0,0 +1,4 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<body>Hello world</body>
|
||||
</html>
|
||||
41
test/test_toolbar.py
Normal file
41
test/test_toolbar.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
from flask_debugtoolbar import _printable
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info >= (3,),
|
||||
reason='test only applies to Python 2')
|
||||
def test_printable_unicode():
|
||||
class UnicodeRepr(object):
|
||||
def __repr__(self):
|
||||
return u'\uffff'
|
||||
|
||||
printable = _printable(UnicodeRepr())
|
||||
assert "raised UnicodeEncodeError: 'ascii' codec" in printable
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info >= (3,),
|
||||
reason='test only applies to Python 2')
|
||||
def test_printable_non_ascii():
|
||||
class NonAsciiRepr(object):
|
||||
def __repr__(self):
|
||||
return 'a\xffb'
|
||||
|
||||
printable = u'%s' % _printable(NonAsciiRepr())
|
||||
# should replace \xff with the unicode ? character
|
||||
assert printable == u'a\ufffdb'
|
||||
120
test/test_utils.py
Normal file
120
test/test_utils.py
Normal file
@@ -0,0 +1,120 @@
|
||||
import pytest
|
||||
|
||||
import posixpath
|
||||
import ntpath
|
||||
|
||||
from flask import Markup
|
||||
|
||||
from flask_debugtoolbar.utils import (_relative_paths, _shortest_relative_path,
|
||||
format_sql, decode_text, HAVE_PYGMENTS)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('value,paths,expected,path_module', [
|
||||
# should yield relative path to the parent directory
|
||||
('/foo/bar', ['/foo'], ['bar'], posixpath),
|
||||
('c:\\foo\\bar', ['c:\\foo'], ['bar'], ntpath),
|
||||
|
||||
# should not yield result if no path is a parent directory
|
||||
('/foo/bar', ['/baz'], [], posixpath),
|
||||
('c:\\foo\\bar', ['c:\\baz'], [], ntpath),
|
||||
|
||||
# should only yield relative paths for parent directories
|
||||
('/foo/bar', ['/foo', '/baz'], ['bar'], posixpath),
|
||||
('c:\\foo\\bar', ['c:\\foo', 'c:\\baz'], ['bar'], ntpath),
|
||||
|
||||
# should yield all results when multiple parents match
|
||||
('/foo/bar/baz', ['/foo', '/foo/bar'], ['bar/baz', 'baz'], posixpath),
|
||||
('c:\\foo\\bar\\baz', ['c:\\foo', 'c:\\foo\\bar'], ['bar\\baz', 'baz'], ntpath),
|
||||
|
||||
# should ignore case differences on windows
|
||||
('c:\\Foo\\bar', ['c:\\foo'], ['bar'], ntpath),
|
||||
|
||||
# should preserve original case
|
||||
('/Foo/Bar', ['/Foo'], ['Bar'], posixpath),
|
||||
('c:\\Foo\\Bar', ['c:\\foo'], ['Bar'], ntpath),
|
||||
])
|
||||
def test_relative_paths(value, paths, expected, path_module):
|
||||
assert list(_relative_paths(value, paths, path_module)) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('value,paths,expected,path_module', [
|
||||
# should yield relative path to the parent directory
|
||||
('/foo/bar', ['/foo'], 'bar', posixpath),
|
||||
('c:\\foo\\bar', ['c:\\foo'], 'bar', ntpath),
|
||||
|
||||
# should return the original value if no path is a parent directory
|
||||
('/foo/bar', ['/baz'], '/foo/bar', posixpath),
|
||||
('c:\\foo\\bar', ['c:\\baz'], 'c:\\foo\\bar', ntpath),
|
||||
|
||||
# should yield shortest result when multiple parents match
|
||||
('/foo/bar/baz', ['/foo', '/foo/bar'], 'baz', posixpath),
|
||||
('c:\\foo\\bar\\baz', ['c:\\foo', 'c:\\foo\\bar'], 'baz', ntpath),
|
||||
])
|
||||
def test_shortest_relative_path(value, paths, expected, path_module):
|
||||
assert _shortest_relative_path(value, paths, path_module) == expected
|
||||
|
||||
|
||||
def test_decode_text_unicode():
|
||||
value = u'\uffff'
|
||||
decoded = decode_text(value)
|
||||
assert decoded == value
|
||||
|
||||
|
||||
def test_decode_text_ascii():
|
||||
value = 'abc'
|
||||
assert decode_text(value.encode('ascii')) == value
|
||||
|
||||
|
||||
def test_decode_text_non_ascii():
|
||||
value = b'abc \xff xyz'
|
||||
assert isinstance(value, bytes)
|
||||
|
||||
decoded = decode_text(value)
|
||||
assert not isinstance(decoded, bytes)
|
||||
|
||||
assert decoded.startswith('abc')
|
||||
assert decoded.endswith('xyz')
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def no_pygments(monkeypatch):
|
||||
monkeypatch.setattr('flask_debugtoolbar.utils.HAVE_PYGMENTS', False)
|
||||
|
||||
|
||||
def test_format_sql_no_pygments(no_pygments):
|
||||
sql = 'select 1'
|
||||
assert format_sql(sql, {}) == sql
|
||||
|
||||
|
||||
def test_format_sql_no_pygments_non_ascii(no_pygments):
|
||||
sql = b"select '\xff'"
|
||||
formatted = format_sql(sql, {})
|
||||
assert formatted.startswith(u"select '")
|
||||
|
||||
|
||||
def test_format_sql_no_pygments_escape_html(no_pygments):
|
||||
sql = 'select x < 1'
|
||||
formatted = format_sql(sql, {})
|
||||
assert not isinstance(formatted, Markup)
|
||||
assert Markup('%s') % formatted == 'select x < 1'
|
||||
|
||||
|
||||
@pytest.mark.skipif(not HAVE_PYGMENTS, reason='test requires the "Pygments" library')
|
||||
def test_format_sql_pygments():
|
||||
sql = 'select 1'
|
||||
html = format_sql(sql, {})
|
||||
assert isinstance(html, Markup)
|
||||
assert html.startswith('<div')
|
||||
assert 'select' in html
|
||||
assert '1' in html
|
||||
|
||||
|
||||
@pytest.mark.skipif(not HAVE_PYGMENTS, reason='test requires the "Pygments" library')
|
||||
def test_format_sql_pygments_non_ascii():
|
||||
sql = b"select 'abc \xff xyz'"
|
||||
html = format_sql(sql, {})
|
||||
assert isinstance(html, Markup)
|
||||
assert html.startswith('<div')
|
||||
assert 'select' in html
|
||||
assert 'abc' in html
|
||||
assert 'xyz' in html
|
||||
Reference in New Issue
Block a user