PageRenderTime 49ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/flaskext/debugtoolbar/__init__.py

https://github.com/plaes/flask-debugtoolbar
Python | 166 lines | 108 code | 32 blank | 26 comment | 23 complexity | d083e4893ecf1579ed2a9c7fe65338da MD5 | raw file
  1. import os
  2. from flask import current_app, request
  3. from flask.globals import _request_ctx_stack
  4. from flask.helpers import send_from_directory
  5. from jinja2 import Environment, PackageLoader
  6. from werkzeug.exceptions import HTTPException
  7. from werkzeug.urls import url_quote_plus
  8. from flaskext.debugtoolbar.toolbar import DebugToolbar
  9. from flask import Blueprint
  10. module = Blueprint('debugtoolbar', __name__)
  11. def replace_insensitive(string, target, replacement):
  12. """Similar to string.replace() but is case insensitive
  13. Code borrowed from: http://forums.devshed.com/python-programming-11/case-insensitive-string-replace-490921.html
  14. """
  15. no_case = string.lower()
  16. index = no_case.rfind(target.lower())
  17. if index >= 0:
  18. return string[:index] + replacement + string[index + len(target):]
  19. else: # no results so return the original string
  20. return string
  21. class DebugToolbarExtension(object):
  22. _static_dir = os.path.realpath(
  23. os.path.join(os.path.dirname(__file__), 'static'))
  24. _redirect_codes = [301, 302, 303, 304]
  25. def __init__(self, app):
  26. self.app = app
  27. self.debug_toolbars = {}
  28. if not app.debug:
  29. return
  30. if not app.config.get('SECRET_KEY'):
  31. raise RuntimeError(
  32. "The Flask-DebugToolbar requires the 'SECRET_KEY' config "
  33. "var to be set")
  34. app.register_blueprint(module, url_prefix='/_debug_toolbar/views')
  35. app.add_url_rule('/_debug_toolbar/static/<path:filename>',
  36. '_debug_toolbar.static', self.send_static_file)
  37. app.before_request(self.process_request)
  38. app.after_request(self.process_response)
  39. # Monkey-patch the Flask.dispatch_request method
  40. app.dispatch_request = self.dispatch_request
  41. # Configure jinja for the internal templates and add url rules
  42. # for static data
  43. self.jinja_env = Environment(
  44. autoescape=True,
  45. extensions=['jinja2.ext.i18n'],
  46. loader=PackageLoader(__name__, 'templates'))
  47. self.jinja_env.filters['urlencode'] = url_quote_plus
  48. def dispatch_request(self):
  49. """Does the request dispatching. Matches the URL and returns the
  50. return value of the view or error handler. This does not have to
  51. be a response object. In order to convert the return value to a
  52. proper response object, call :func:`make_response`.
  53. This is a modified version of the default Flask.dispatch_request
  54. """
  55. req = _request_ctx_stack.top.request
  56. app = current_app
  57. try:
  58. if req.routing_exception is not None:
  59. raise req.routing_exception
  60. rule = req.url_rule
  61. # if we provide automatic options for this URL and the
  62. # request came with the OPTIONS method, reply automatically
  63. if getattr(rule, 'provide_automatic_options', False) \
  64. and req.method == 'OPTIONS':
  65. return app.make_default_options_response()
  66. # otherwise dispatch to the handler for that endpoint, give the
  67. # panels the ability to wrap the view_func
  68. view_func = app.view_functions[rule.endpoint]
  69. view_func = self.process_view(app, view_func, req.view_args)
  70. if req.path.startswith('/_debug_toolbar/views'):
  71. req.view_args['render'] = self.render
  72. return view_func(**req.view_args)
  73. except HTTPException, e:
  74. return app.handle_http_exception(e)
  75. def _show_toolbar(self):
  76. """Return a boolean to indicate if we need to show the toolbar."""
  77. if request.path.startswith('/_debug_toolbar/'):
  78. return False
  79. return True
  80. def send_static_file(self, filename):
  81. """Send a static file from the flask-debugtoolbar static directory."""
  82. return send_from_directory(self._static_dir, filename)
  83. def process_request(self):
  84. if not self._show_toolbar():
  85. return
  86. self.debug_toolbars[request] = DebugToolbar(request, self.jinja_env)
  87. for panel in self.debug_toolbars[request].panels:
  88. panel.process_request(request)
  89. def process_view(self, app, view_func, view_kwargs):
  90. """This method is called just before the flask view is called.
  91. This is done by the dispatch_request method.
  92. """
  93. if request in self.debug_toolbars:
  94. for panel in self.debug_toolbars[request].panels:
  95. new_view = panel.process_view(request, view_func, view_kwargs)
  96. if new_view:
  97. view_func = new_view
  98. return view_func
  99. def process_response(self, response):
  100. if request not in self.debug_toolbars:
  101. return response
  102. # Intercept http redirect codes and display an html page with a
  103. # link to the target.
  104. if self.debug_toolbars[request].config['DEBUG_TB_INTERCEPT_REDIRECTS']:
  105. if response.status_code in self._redirect_codes:
  106. redirect_to = response.location
  107. redirect_code = response.status_code
  108. if redirect_to:
  109. content = self.render('redirect.html', {
  110. 'redirect_to': redirect_to,
  111. 'redirect_code': redirect_code
  112. })
  113. response.content_length = len(content)
  114. response.location = None
  115. response.response = [content]
  116. response.status_code = 200
  117. # If the http response code is 200 then we process to add the
  118. # toolbar to the returned html response.
  119. if response.status_code == 200:
  120. for panel in self.debug_toolbars[request].panels:
  121. panel.process_response(request, response)
  122. if response.is_sequence:
  123. response_html = response.data.decode(response.charset)
  124. toolbar_html = self.debug_toolbars[request].render_toolbar()
  125. response.response = [
  126. replace_insensitive(
  127. response_html,
  128. '</body>',
  129. toolbar_html + '</body>')]
  130. return response
  131. def render(self, template_name, context):
  132. template = self.jinja_env.get_template(template_name)
  133. return template.render(**context)