tgext.debugbar 0.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tgext/debugbar/__init__.py +17 -0
- tgext/debugbar/controller.py +117 -0
- tgext/debugbar/initialize.py +151 -0
- tgext/debugbar/sections/__init__.py +17 -0
- tgext/debugbar/sections/base.py +20 -0
- tgext/debugbar/sections/controllers.py +49 -0
- tgext/debugbar/sections/inventing.py +198 -0
- tgext/debugbar/sections/logmessages.py +69 -0
- tgext/debugbar/sections/mingorm.py +124 -0
- tgext/debugbar/sections/request_vars.py +64 -0
- tgext/debugbar/sections/sqla.py +126 -0
- tgext/debugbar/sections/templates/__init__.py +2 -0
- tgext/debugbar/sections/templates/controllers.html +18 -0
- tgext/debugbar/sections/templates/inventing.html +26 -0
- tgext/debugbar/sections/templates/logging.html +21 -0
- tgext/debugbar/sections/templates/ming.html +31 -0
- tgext/debugbar/sections/templates/request.html +19 -0
- tgext/debugbar/sections/templates/sqla.html +32 -0
- tgext/debugbar/sections/templates/timing.html +68 -0
- tgext/debugbar/sections/timing.py +163 -0
- tgext/debugbar/statics/TgGear.png +0 -0
- tgext/debugbar/statics/__init__.py +1 -0
- tgext/debugbar/statics/jquery.js +5 -0
- tgext/debugbar/statics/style.css +170 -0
- tgext/debugbar/templates/__init__.py +1 -0
- tgext/debugbar/templates/debugbar.html +57 -0
- tgext/debugbar/templates/perform_ming.html +30 -0
- tgext/debugbar/templates/perform_sql.html +30 -0
- tgext/debugbar/utils.py +98 -0
- tgext.debugbar-0.6.0-py3.8-nspkg.pth +1 -0
- tgext_debugbar-0.6.0.dist-info/METADATA +98 -0
- tgext_debugbar-0.6.0.dist-info/RECORD +35 -0
- tgext_debugbar-0.6.0.dist-info/WHEEL +5 -0
- tgext_debugbar-0.6.0.dist-info/namespace_packages.txt +1 -0
- tgext_debugbar-0.6.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from .initialize import DebugBar, enable_debugbar
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def plugme(app_config, options):
|
|
5
|
+
try:
|
|
6
|
+
app_config.update_blueprint({
|
|
7
|
+
'debugbar.inventing': options.get('inventing', False),
|
|
8
|
+
'debugbar.inventing_css': options.get('inventing_css', True),
|
|
9
|
+
'debugbar.enable_logs': options.get('enable_logs', False)
|
|
10
|
+
})
|
|
11
|
+
except AttributeError:
|
|
12
|
+
app_config['debugbar.inventing'] = options.get('inventing', False)
|
|
13
|
+
app_config['debugbar.inventing_css'] = options.get('inventing_css', True)
|
|
14
|
+
app_config['debugbar.enable_logs'] = options.get('enable_logs', False)
|
|
15
|
+
|
|
16
|
+
enable_debugbar(app_config)
|
|
17
|
+
return dict(appid='tgext.debugbar')
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import os
|
|
3
|
+
import pprint
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
from tg import app_globals, config, expose, request
|
|
7
|
+
from tg.controllers import TGController, WSGIAppController
|
|
8
|
+
from webob.exc import HTTPBadRequest
|
|
9
|
+
from .utils import format_sql, format_json
|
|
10
|
+
from .sections import __sections__
|
|
11
|
+
|
|
12
|
+
STATICS_PATH = os.path.join(os.path.split(sys.modules['tgext.debugbar'].__file__)[0], 'statics')
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
from webob.static import DirectoryApp
|
|
17
|
+
except ImportError:
|
|
18
|
+
from paste.urlparser import StaticURLParser as DirectoryApp
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
from pymongo import json_util
|
|
22
|
+
except ImportError:
|
|
23
|
+
try:
|
|
24
|
+
from bson import json_util
|
|
25
|
+
except ImportError:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class StaticsController(TGController):
|
|
30
|
+
_directory_app = DirectoryApp(STATICS_PATH)
|
|
31
|
+
|
|
32
|
+
@expose()
|
|
33
|
+
def _default(self, *args):
|
|
34
|
+
new_req = request.copy()
|
|
35
|
+
to_pop = len(new_req.path_info.strip('/').split('/')) - len(args)
|
|
36
|
+
for i in range(to_pop):
|
|
37
|
+
new_req.path_info_pop()
|
|
38
|
+
return new_req.get_response(self._directory_app)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class DebugBarController(TGController):
|
|
42
|
+
statics = StaticsController()
|
|
43
|
+
|
|
44
|
+
@expose('genshi:tgext.debugbar.templates.perform_sql!html')
|
|
45
|
+
@expose('kajiki:tgext.debugbar.templates.perform_sql!html')
|
|
46
|
+
def perform_sql(self, stmt, params, engine_id, duration, modify=None):
|
|
47
|
+
# Make sure it is a select statement
|
|
48
|
+
if not stmt.lower().lstrip().startswith('select'):
|
|
49
|
+
raise HTTPBadRequest('Not a SELECT SQL statement')
|
|
50
|
+
try:
|
|
51
|
+
if not engine_id:
|
|
52
|
+
raise ValueError
|
|
53
|
+
engine_id = int(engine_id)
|
|
54
|
+
engine = getattr(app_globals, 'tgdb_sqla_engines')[engine_id]()
|
|
55
|
+
except (AttributeError, IndexError, ValueError):
|
|
56
|
+
raise HTTPBadRequest('No valid database engine')
|
|
57
|
+
|
|
58
|
+
if modify and modify.lower() == 'explain':
|
|
59
|
+
if engine.name.startswith('sqlite'):
|
|
60
|
+
stmt = 'EXPLAIN QUERY PLAN %s' % stmt
|
|
61
|
+
else:
|
|
62
|
+
stmt = 'EXPLAIN %s' % stmt
|
|
63
|
+
title = 'Execution Plan'
|
|
64
|
+
else:
|
|
65
|
+
title = 'Query Results'
|
|
66
|
+
|
|
67
|
+
sql_params = json.loads(params)
|
|
68
|
+
if isinstance(sql_params, list):
|
|
69
|
+
sql_params = tuple(sql_params)
|
|
70
|
+
|
|
71
|
+
with engine.connect() as connection:
|
|
72
|
+
result = connection.exec_driver_sql(stmt, sql_params)
|
|
73
|
+
rows = result.fetchall()
|
|
74
|
+
headers = result.keys()
|
|
75
|
+
|
|
76
|
+
return dict(
|
|
77
|
+
sql=format_sql(stmt),
|
|
78
|
+
params=params,
|
|
79
|
+
result=rows,
|
|
80
|
+
headers=headers,
|
|
81
|
+
duration=float(duration),
|
|
82
|
+
title=title)
|
|
83
|
+
|
|
84
|
+
@expose('genshi:tgext.debugbar.templates.perform_ming!html')
|
|
85
|
+
@expose('kajiki:tgext.debugbar.templates.perform_ming!html')
|
|
86
|
+
def perform_ming(self, collection, command, params, duration, modify=None):
|
|
87
|
+
if not command.startswith('find'):
|
|
88
|
+
raise HTTPBadRequest('Not a find statement')
|
|
89
|
+
|
|
90
|
+
query_params = json.loads(params, object_hook=json_util.object_hook)
|
|
91
|
+
session = config['package'].model.DBSession
|
|
92
|
+
|
|
93
|
+
cursor = []
|
|
94
|
+
for i, step in enumerate(command.split('.')):
|
|
95
|
+
if step == 'find':
|
|
96
|
+
args = query_params[i]
|
|
97
|
+
query = args[0]
|
|
98
|
+
options = args[1]
|
|
99
|
+
cursor = session.find(collection, query, **options)
|
|
100
|
+
else:
|
|
101
|
+
args = query_params[i]
|
|
102
|
+
cmd = getattr(cursor, step)
|
|
103
|
+
cursor = cmd(*args)
|
|
104
|
+
|
|
105
|
+
isexplain = False
|
|
106
|
+
if modify and modify.lower() == 'explain':
|
|
107
|
+
cursor = cursor.ming_cursor.cursor.explain()
|
|
108
|
+
isexplain = True
|
|
109
|
+
|
|
110
|
+
return dict(
|
|
111
|
+
pformat=pprint.pformat,
|
|
112
|
+
action=command,
|
|
113
|
+
params=format_json(params),
|
|
114
|
+
result=cursor,
|
|
115
|
+
collection=collection,
|
|
116
|
+
duration=float(duration),
|
|
117
|
+
isexplain=isexplain)
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from markupsafe import Markup
|
|
4
|
+
|
|
5
|
+
import tg
|
|
6
|
+
from tg import request, url
|
|
7
|
+
from tg.render import render
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
# Verify that we have hooks with disconnect feature,
|
|
11
|
+
# which is only available since TG2.3.5, otherwise
|
|
12
|
+
# use app_config to register/disconnect hooks.
|
|
13
|
+
from tg import hooks as tg_hooks
|
|
14
|
+
if not hasattr(tg_hooks, 'disconnect'):
|
|
15
|
+
tg_hooks = None
|
|
16
|
+
except ImportError:
|
|
17
|
+
tg_hooks = None
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
# TG >= 2.4
|
|
21
|
+
from tg import ApplicationConfigurator
|
|
22
|
+
except ImportError:
|
|
23
|
+
# TG < 2.4
|
|
24
|
+
class ApplicationConfigurator: pass
|
|
25
|
+
|
|
26
|
+
from tgext.debugbar.sections import __sections__
|
|
27
|
+
from tgext.debugbar.utils import get_root_controller
|
|
28
|
+
|
|
29
|
+
log = logging.getLogger('tgext.debugbar')
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class DebugBar():
|
|
33
|
+
css_link = '<link rel="stylesheet" type="text/css" href="%s" />'
|
|
34
|
+
css_path = '/_debugbar/statics/style.css'
|
|
35
|
+
template = 'tgext.debugbar.templates.debugbar!html'
|
|
36
|
+
|
|
37
|
+
def __init__(self, app_config):
|
|
38
|
+
self.app_config = app_config
|
|
39
|
+
self.available_engines = None
|
|
40
|
+
|
|
41
|
+
def _register_hook(self, hook_name, handler):
|
|
42
|
+
if tg_hooks is None:
|
|
43
|
+
# 2.1+
|
|
44
|
+
self.app_config.register_hook(hook_name, handler)
|
|
45
|
+
elif hasattr(tg_hooks, 'wrap_controller'):
|
|
46
|
+
# 2.3+
|
|
47
|
+
if hook_name == 'controller_wrapper':
|
|
48
|
+
def _accept_decoration(decoration, controller):
|
|
49
|
+
return handler(controller)
|
|
50
|
+
tg_hooks.wrap_controller(_accept_decoration)
|
|
51
|
+
else:
|
|
52
|
+
tg_hooks.register(hook_name, handler)
|
|
53
|
+
else:
|
|
54
|
+
# 2.4+
|
|
55
|
+
if hook_name == 'controller_wrapper':
|
|
56
|
+
from tg import ApplicationConfigurator
|
|
57
|
+
dispatch = ApplicationConfigurator.current().get_component('dispatch')
|
|
58
|
+
if dispatch is None:
|
|
59
|
+
raise RuntimeError('TurboGears application configured without dispatching')
|
|
60
|
+
dispatch.register_controller_wrapper(handler)
|
|
61
|
+
else:
|
|
62
|
+
tg_hooks.register(hook_name, handler)
|
|
63
|
+
|
|
64
|
+
def _disconnect_hook(self, hook_name, handler):
|
|
65
|
+
if tg_hooks is None:
|
|
66
|
+
self.app_config.hooks[hook_name].remove(handler)
|
|
67
|
+
else:
|
|
68
|
+
tg_hooks.disconnect(hook_name, handler)
|
|
69
|
+
|
|
70
|
+
def __call__(self, configurator=None, conf=None):
|
|
71
|
+
if conf is None:
|
|
72
|
+
conf = tg.config
|
|
73
|
+
|
|
74
|
+
if not conf.get('debug', False):
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
conf['debugbar.engine'] = next(
|
|
78
|
+
iter(sorted(set(('genshi', 'kajiki')) & set(conf['renderers']),
|
|
79
|
+
key=lambda x: x == conf['default_renderer'],
|
|
80
|
+
reverse=True)),
|
|
81
|
+
None
|
|
82
|
+
)
|
|
83
|
+
if not conf['debugbar.engine']:
|
|
84
|
+
log.error("Genshi or Kajiki rendering engines unavailable. Please install kajiki "
|
|
85
|
+
"and add base_config.renderers.append('kajiki') to your app_cfg.py")
|
|
86
|
+
raise RuntimeError('Debugbar requires Genshi or Kajiki rendering engines')
|
|
87
|
+
|
|
88
|
+
log.log(logging.INFO, 'Enabling Debug Toolbar')
|
|
89
|
+
for sec in __sections__:
|
|
90
|
+
if not sec.is_active:
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
log.log(logging.DEBUG, 'Enabling Section: %s' % sec.name)
|
|
94
|
+
for hook_name, hooks in sec.hooks.items():
|
|
95
|
+
for handler in hooks:
|
|
96
|
+
if hook_name == 'startup':
|
|
97
|
+
handler()
|
|
98
|
+
else:
|
|
99
|
+
try:
|
|
100
|
+
self._register_hook(hook_name, handler)
|
|
101
|
+
except:
|
|
102
|
+
log.exception('Unable to register hook: %s', hook_name)
|
|
103
|
+
|
|
104
|
+
self._register_hook('after_render', self.render_first)
|
|
105
|
+
|
|
106
|
+
def render_first(self, response):
|
|
107
|
+
try:
|
|
108
|
+
self._disconnect_hook('after_render', self.render_first)
|
|
109
|
+
except ValueError:
|
|
110
|
+
pass # pre-emptied by another request
|
|
111
|
+
else:
|
|
112
|
+
from tgext.debugbar.controller import DebugBarController
|
|
113
|
+
get_root_controller()._debugbar = DebugBarController()
|
|
114
|
+
self._register_hook('after_render', self.render_bars)
|
|
115
|
+
self.render_bars(response)
|
|
116
|
+
|
|
117
|
+
def render_bars(self, response):
|
|
118
|
+
page = response.get('response')
|
|
119
|
+
if (not page or not isinstance(page, str)
|
|
120
|
+
or 'text/html' not in response['content_type']
|
|
121
|
+
or request.headers.get(
|
|
122
|
+
'X-Requested-With') == 'XMLHttpRequest'):
|
|
123
|
+
|
|
124
|
+
if tg.config.get('debugbar.enable_logs', False):
|
|
125
|
+
for section in __sections__:
|
|
126
|
+
if hasattr(section, 'log_content'):
|
|
127
|
+
section.log_content()
|
|
128
|
+
|
|
129
|
+
return
|
|
130
|
+
|
|
131
|
+
pos_head = page.find('</head>')
|
|
132
|
+
if pos_head > 0:
|
|
133
|
+
pos_body = page.find('</body>', pos_head + 7)
|
|
134
|
+
if pos_body > 0:
|
|
135
|
+
response['response'] = ''.join(
|
|
136
|
+
[page[:pos_head],
|
|
137
|
+
Markup(self.css_link % url(self.css_path)),
|
|
138
|
+
page[pos_head:pos_body],
|
|
139
|
+
Markup(render(dict(sections=__sections__),
|
|
140
|
+
tg.config['debugbar.engine'], self.template,).split('\n', 1)[-1]),
|
|
141
|
+
page[pos_body:]])
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def enable_debugbar(app_config):
|
|
145
|
+
if isinstance(app_config, ApplicationConfigurator):
|
|
146
|
+
tg_hooks.register('initialized_config', DebugBar(app_config))
|
|
147
|
+
else:
|
|
148
|
+
if tg_hooks is None:
|
|
149
|
+
app_config.register_hook('startup', DebugBar(app_config))
|
|
150
|
+
else:
|
|
151
|
+
tg_hooks.register('startup', DebugBar(app_config))
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from .sqla import SQLADebugSection
|
|
2
|
+
from .timing import TimingDebugSection
|
|
3
|
+
from .request_vars import RequestDebugSection
|
|
4
|
+
from .controllers import ControllersDebugSection
|
|
5
|
+
from .logmessages import LoggingDebugSection
|
|
6
|
+
from .mingorm import MingDebugSection
|
|
7
|
+
from .inventing import InventingDebugSection
|
|
8
|
+
|
|
9
|
+
__sections__ = [
|
|
10
|
+
TimingDebugSection(),
|
|
11
|
+
RequestDebugSection(),
|
|
12
|
+
SQLADebugSection(),
|
|
13
|
+
MingDebugSection(),
|
|
14
|
+
ControllersDebugSection(),
|
|
15
|
+
LoggingDebugSection(),
|
|
16
|
+
InventingDebugSection()
|
|
17
|
+
]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""The base debug section class."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class DebugSection(object):
|
|
5
|
+
|
|
6
|
+
name = "Unnamed"
|
|
7
|
+
is_active = False
|
|
8
|
+
hooks = dict(
|
|
9
|
+
startup=[],
|
|
10
|
+
shutdown=[],
|
|
11
|
+
before_validate=[],
|
|
12
|
+
before_call=[],
|
|
13
|
+
before_render=[],
|
|
14
|
+
after_render=[])
|
|
15
|
+
|
|
16
|
+
def title(self):
|
|
17
|
+
raise NotImplementedError
|
|
18
|
+
|
|
19
|
+
def content(self):
|
|
20
|
+
raise NotImplementedError
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
|
|
3
|
+
import tg
|
|
4
|
+
from tg.controllers.decoratedcontroller import DecoratedController
|
|
5
|
+
from tg.i18n import ugettext as _
|
|
6
|
+
from tg.render import render
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
from tg.util import odict
|
|
10
|
+
except ImportError:
|
|
11
|
+
from collections import OrderedDict as odict
|
|
12
|
+
|
|
13
|
+
from tgext.debugbar.sections.base import DebugSection
|
|
14
|
+
from tgext.debugbar.utils import get_root_controller
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def map_controllers(path, controller, output):
|
|
18
|
+
if inspect.isclass(controller):
|
|
19
|
+
if not issubclass(controller, DecoratedController):
|
|
20
|
+
return
|
|
21
|
+
else:
|
|
22
|
+
if isinstance(controller, DecoratedController):
|
|
23
|
+
controller = controller.__class__
|
|
24
|
+
else:
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
exposed_methods = {}
|
|
28
|
+
output[path and path or '/'] = dict(
|
|
29
|
+
controller=controller, exposed_methods=exposed_methods)
|
|
30
|
+
for name, cont in controller.__dict__.items():
|
|
31
|
+
if hasattr(cont, 'decoration') and cont.decoration.exposed:
|
|
32
|
+
exposed_methods[name] = cont
|
|
33
|
+
map_controllers(path + '/' + name, cont, output)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ControllersDebugSection(DebugSection):
|
|
37
|
+
name = 'Controllers'
|
|
38
|
+
is_active = True
|
|
39
|
+
|
|
40
|
+
def title(self):
|
|
41
|
+
return _('Controllers')
|
|
42
|
+
|
|
43
|
+
def content(self):
|
|
44
|
+
controllers = odict()
|
|
45
|
+
map_controllers('', get_root_controller(), controllers)
|
|
46
|
+
return str(render(
|
|
47
|
+
dict(controllers=controllers),
|
|
48
|
+
tg.config['debugbar.engine'], 'tgext.debugbar.sections.templates.controllers!html'
|
|
49
|
+
).split('\n', 1)[-1])
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import hashlib, re, logging, os, time
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from tg import config, tmpl_context
|
|
4
|
+
from tg.render import render
|
|
5
|
+
from tg.i18n import ugettext as _
|
|
6
|
+
|
|
7
|
+
from paste.deploy.converters import asbool
|
|
8
|
+
from tgext.debugbar.sections.base import DebugSection
|
|
9
|
+
|
|
10
|
+
from markupsafe import Markup
|
|
11
|
+
|
|
12
|
+
log = logging.getLogger('tgext.debugbar')
|
|
13
|
+
|
|
14
|
+
_reload_datetime = datetime.now().strftime('%Y%m%d%H%M%S')
|
|
15
|
+
_href_re = re.compile(r'''href=["\']([^\'\"]+)[\'"]''', re.UNICODE|re.IGNORECASE)
|
|
16
|
+
|
|
17
|
+
def detect_stylesheets(page):
|
|
18
|
+
try:
|
|
19
|
+
base_dir = config.get('paths', config.get('pylons.paths'))['static_files']
|
|
20
|
+
except:
|
|
21
|
+
log.warn('Unable to detect static files path, skipping inventing mode on stylesheets')
|
|
22
|
+
return []
|
|
23
|
+
|
|
24
|
+
files = []
|
|
25
|
+
|
|
26
|
+
line = page.find('stylesheet')
|
|
27
|
+
while line >= 0:
|
|
28
|
+
css_link = _href_re.search(page[line:])
|
|
29
|
+
if css_link:
|
|
30
|
+
css_file = css_link.group(1)
|
|
31
|
+
css_file = css_file.replace('/', os.sep)
|
|
32
|
+
if css_file.startswith(os.sep):
|
|
33
|
+
css_file = css_file[1:]
|
|
34
|
+
|
|
35
|
+
css_file = os.path.join(base_dir, css_file)
|
|
36
|
+
if os.path.exists(css_file):
|
|
37
|
+
files.append(css_file)
|
|
38
|
+
line = page.find('stylesheet', line+1)
|
|
39
|
+
|
|
40
|
+
return files
|
|
41
|
+
|
|
42
|
+
def on_after_render(response, *args, **kw):
|
|
43
|
+
content_type = response.get('content_type', '')
|
|
44
|
+
template = response.get('template_name')
|
|
45
|
+
page = response.get('response')
|
|
46
|
+
|
|
47
|
+
if content_type and 'text/html' in content_type and template and isinstance(page, str):
|
|
48
|
+
m = hashlib.md5()
|
|
49
|
+
m.update(page.encode('utf-8'))
|
|
50
|
+
m = m.hexdigest() + _reload_datetime
|
|
51
|
+
|
|
52
|
+
if config.get('debugbar.inventing_css', False):
|
|
53
|
+
newest_modified_time = 0
|
|
54
|
+
|
|
55
|
+
for f in detect_stylesheets(page):
|
|
56
|
+
modified_time = os.path.getmtime(f)
|
|
57
|
+
if modified_time > newest_modified_time:
|
|
58
|
+
newest_modified_time = modified_time
|
|
59
|
+
|
|
60
|
+
if newest_modified_time:
|
|
61
|
+
m += str(newest_modified_time)
|
|
62
|
+
|
|
63
|
+
pos_head = page.find('</head>')
|
|
64
|
+
if pos_head > 0:
|
|
65
|
+
pos_body = page.find('</body>', pos_head + 7)
|
|
66
|
+
if pos_body > 0:
|
|
67
|
+
response['response'] = ''.join([page[:pos_head],
|
|
68
|
+
Markup('<script>if(typeof tgext_debugbar_page_hash === "undefined") window.tgext_debugbar_page_hash="%s";</script>' % m),
|
|
69
|
+
page[pos_head:pos_body],
|
|
70
|
+
page[pos_body:]])
|
|
71
|
+
|
|
72
|
+
class InventingDebugSection(DebugSection):
|
|
73
|
+
name = 'Inventing'
|
|
74
|
+
is_active = True
|
|
75
|
+
hooks = dict(after_render=[on_after_render])
|
|
76
|
+
|
|
77
|
+
js_reloadscript = '''
|
|
78
|
+
<script>
|
|
79
|
+
; (function(win, undefined) {
|
|
80
|
+
var tgext_debugbar_inventing = win['tgext_debugbar_inventing'] = (win['tgext_debugbar_inventing'] || {});
|
|
81
|
+
|
|
82
|
+
tgext_debugbar_inventing.start = function() {
|
|
83
|
+
if (!supportsLocalStorage()) {
|
|
84
|
+
alert("Inventing Mode requires local storage support");
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
localStorage["tgext.debugbar.enabled"] = "true";
|
|
89
|
+
DebugBarJQuery('#tgdb_debugbar_inventing_toggle').text('Turn Off');
|
|
90
|
+
tgext_debugbar_inventing.loop();
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
tgext_debugbar_inventing.loop = function() {
|
|
94
|
+
if (!tgext_debugbar_inventing.is_enabled())
|
|
95
|
+
return;
|
|
96
|
+
|
|
97
|
+
setTimeout(tgext_debugbar_check_changed, 1000);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
tgext_debugbar_inventing.is_enabled = function() {
|
|
101
|
+
if (!supportsLocalStorage())
|
|
102
|
+
return false;
|
|
103
|
+
|
|
104
|
+
var enabled = (localStorage["tgext.debugbar.enabled"] == "true");
|
|
105
|
+
return enabled;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
tgext_debugbar_inventing.init = function(force) {
|
|
109
|
+
var enabled = (tgext_debugbar_inventing.is_enabled() || force);
|
|
110
|
+
if (!enabled)
|
|
111
|
+
return false;
|
|
112
|
+
|
|
113
|
+
tgext_debugbar_inventing.start();
|
|
114
|
+
return true;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
tgext_debugbar_inventing.toggle = function(button) {
|
|
118
|
+
if (!supportsLocalStorage()) {
|
|
119
|
+
alert("Inventing Mode requires local storage support");
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
button = DebugBarJQuery(button);
|
|
124
|
+
|
|
125
|
+
var debugbar_enabled = tgext_debugbar_inventing.is_enabled();
|
|
126
|
+
if (!debugbar_enabled) {
|
|
127
|
+
tgext_debugbar_inventing.start();
|
|
128
|
+
button.text('Turn Off');
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
localStorage["tgext.debugbar.enabled"] = "false";
|
|
132
|
+
button.text('Turn On');
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
function tgext_debugbar_check_changed() {
|
|
137
|
+
DebugBarJQuery.ajax(window.location.href, {
|
|
138
|
+
'success': function(data, textStatus, jqXHR) {
|
|
139
|
+
var page_hash_re = /tgext_debugbar_page_hash="(.*)";/;
|
|
140
|
+
var page_hash = page_hash_re.exec(data);
|
|
141
|
+
if (page_hash && page_hash.length) {
|
|
142
|
+
var page_hash = page_hash[1];
|
|
143
|
+
if(page_hash != tgext_debugbar_page_hash) {
|
|
144
|
+
window.location.reload();
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
DebugBarJQuery('#tgdb_debugbar #tgdb_barcontent').removeClass('tgdb_barcontent_error');
|
|
149
|
+
DebugBarJQuery('#tgdb_debugbar #tgdb_barcontent').removeClass('tgdb_barcontent_warning');
|
|
150
|
+
tgext_debugbar_inventing.loop();
|
|
151
|
+
},
|
|
152
|
+
'error':function(jqXHR, textStatus, errorThrown) {
|
|
153
|
+
DebugBarJQuery('#tgdb_debugbar #tgdb_barcontent').removeClass('tgdb_barcontent_error');
|
|
154
|
+
DebugBarJQuery('#tgdb_debugbar #tgdb_barcontent').removeClass('tgdb_barcontent_warning');
|
|
155
|
+
|
|
156
|
+
if (jqXHR.status && jqXHR.status >= 500) {
|
|
157
|
+
DebugBarJQuery('#tgdb_debugbar #tgdb_barcontent').addClass('tgdb_barcontent_error');
|
|
158
|
+
}
|
|
159
|
+
else if (jqXHR.status && jqXHR.status < 500) {
|
|
160
|
+
//Probably not an error in this case, yet to decide if to alert it somehow
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
DebugBarJQuery('#tgdb_debugbar #tgdb_barcontent').addClass('tgdb_barcontent_warning');
|
|
164
|
+
}
|
|
165
|
+
tgext_debugbar_inventing.loop();
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function supportsLocalStorage() {
|
|
171
|
+
try {
|
|
172
|
+
return 'localStorage' in window && window['localStorage'] !== null;
|
|
173
|
+
} catch(e){
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
})(window);
|
|
178
|
+
</script>'''
|
|
179
|
+
|
|
180
|
+
def title(self):
|
|
181
|
+
return _('Inventing')
|
|
182
|
+
|
|
183
|
+
def content(self):
|
|
184
|
+
inventing_enabled = asbool(config.get('debugbar.inventing', 'false'))
|
|
185
|
+
inventing_enabled = getattr(tmpl_context, 'debugbar_inventing', inventing_enabled)
|
|
186
|
+
|
|
187
|
+
result = ''
|
|
188
|
+
result += render(dict(), config['debugbar.engine'],
|
|
189
|
+
'tgext.debugbar.sections.templates.inventing!html')
|
|
190
|
+
|
|
191
|
+
result += Markup(self.js_reloadscript)
|
|
192
|
+
if inventing_enabled:
|
|
193
|
+
result += Markup('<script>tgext_debugbar_inventing.init(true)</script>')
|
|
194
|
+
else:
|
|
195
|
+
result += Markup('<script>tgext_debugbar_inventing.init(false)</script>')
|
|
196
|
+
|
|
197
|
+
return result
|
|
198
|
+
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import logging
|
|
3
|
+
import threading
|
|
4
|
+
|
|
5
|
+
import tg
|
|
6
|
+
from tg.i18n import ugettext as _
|
|
7
|
+
from tg.render import render
|
|
8
|
+
|
|
9
|
+
from tgext.debugbar.sections.base import DebugSection
|
|
10
|
+
from tgext.debugbar.utils import format_fname
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ThreadTrackingHandler(logging.Handler):
|
|
14
|
+
def __init__(self):
|
|
15
|
+
logging.Handler.__init__(self)
|
|
16
|
+
self.records = {}
|
|
17
|
+
|
|
18
|
+
def emit(self, record):
|
|
19
|
+
self.get_records().append(record)
|
|
20
|
+
|
|
21
|
+
def get_records(self, thread=None):
|
|
22
|
+
if thread is None:
|
|
23
|
+
thread = threading.current_thread()
|
|
24
|
+
if thread not in self.records:
|
|
25
|
+
self.records[thread] = []
|
|
26
|
+
return self.records[thread]
|
|
27
|
+
|
|
28
|
+
def clear_records(self, thread=None):
|
|
29
|
+
if thread is None:
|
|
30
|
+
thread = threading.current_thread()
|
|
31
|
+
if thread in self.records:
|
|
32
|
+
del self.records[thread]
|
|
33
|
+
|
|
34
|
+
handler = ThreadTrackingHandler()
|
|
35
|
+
logging.root.addHandler(handler)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class LoggingDebugSection(DebugSection):
|
|
39
|
+
name = 'Logging'
|
|
40
|
+
is_active = True
|
|
41
|
+
|
|
42
|
+
def get_and_clear(self):
|
|
43
|
+
records = handler.get_records()
|
|
44
|
+
handler.clear_records()
|
|
45
|
+
return records
|
|
46
|
+
|
|
47
|
+
def title(self):
|
|
48
|
+
return _('Logging')
|
|
49
|
+
|
|
50
|
+
def content(self):
|
|
51
|
+
records = []
|
|
52
|
+
for record in self.get_and_clear():
|
|
53
|
+
msg = record.getMessage()
|
|
54
|
+
if isinstance(msg, bytes):
|
|
55
|
+
msg = msg.decode('utf-8', 'ignore')
|
|
56
|
+
records.append({
|
|
57
|
+
'message': msg,
|
|
58
|
+
'time': datetime.datetime.fromtimestamp(record.created),
|
|
59
|
+
'level': record.levelname,
|
|
60
|
+
'file': format_fname(record.pathname),
|
|
61
|
+
'file_long': record.pathname,
|
|
62
|
+
'line': record.lineno,
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
records = reversed(records)
|
|
66
|
+
return str(render(
|
|
67
|
+
dict(records=records),
|
|
68
|
+
tg.config['debugbar.engine'], 'tgext.debugbar.sections.templates.logging!html'
|
|
69
|
+
).split('\n', 1)[-1])
|