/flask_debugtoolbar/panels/template.py

https://bitbucket.org/jonasteuwen/flask-debugtoolbar · Python · 124 lines · 91 code · 25 blank · 8 comment · 13 complexity · 88b02aa1d4ba76d720ac5900dc9d52c1 MD5 · raw file

  1. import collections
  2. import json
  3. import sys
  4. import traceback
  5. import uuid
  6. from jinja2.exceptions import TemplateSyntaxError
  7. from flask import template_rendered, request, g, render_template_string, Response, current_app, abort, url_for
  8. from flask_debugtoolbar import module
  9. from flask_debugtoolbar.panels import DebugPanel
  10. _ = lambda x: x
  11. class TemplateDebugPanel(DebugPanel):
  12. """
  13. Panel that displays the time a response took in milliseconds.
  14. """
  15. name = 'Template'
  16. has_content = True
  17. # save the context for the 5 most recent requests
  18. template_cache = collections.deque(maxlen=5)
  19. @classmethod
  20. def get_cache_for_key(self, key):
  21. for cache_key, value in self.template_cache:
  22. if key == cache_key:
  23. return value
  24. raise KeyError(key)
  25. def __init__(self, *args, **kwargs):
  26. super(self.__class__, self).__init__(*args, **kwargs)
  27. self.key = str(uuid.uuid4())
  28. self.templates = []
  29. template_rendered.connect(self._store_template_info)
  30. def _store_template_info(self, sender, **kwargs):
  31. # only record in the cache if the editor is enabled and there is
  32. # actually a template for this request
  33. if not self.templates and is_editor_enabled():
  34. self.template_cache.append((self.key, self.templates))
  35. self.templates.append(kwargs)
  36. def process_request(self, request):
  37. pass
  38. def process_response(self, request, response):
  39. pass
  40. def nav_title(self):
  41. return _('Templates')
  42. def nav_subtitle(self):
  43. return "%d rendered" % len(self.templates)
  44. def title(self):
  45. return _('Templates')
  46. def url(self):
  47. return ''
  48. def content(self):
  49. return self.render('panels/template.html', {
  50. 'key': self.key,
  51. 'templates': self.templates,
  52. 'editable': is_editor_enabled(),
  53. })
  54. def is_editor_enabled():
  55. return current_app.config.get('DEBUG_TB_TEMPLATE_EDITOR_ENABLED')
  56. def require_enabled():
  57. if not is_editor_enabled():
  58. abort(403)
  59. @module.route('/template/<key>')
  60. def template_editor(key):
  61. require_enabled()
  62. # TODO set up special loader that caches templates it loads
  63. # and can override template contents
  64. templates = [t['template'] for t in TemplateDebugPanel.get_cache_for_key(key)]
  65. return g.debug_toolbar.render('panels/template_editor.html', {
  66. 'static_path': url_for('_debug_toolbar.static', filename=''),
  67. 'request': request,
  68. 'templates': [
  69. {'name': t.name,
  70. 'source': open(t.filename).read()}
  71. for t in templates
  72. ]
  73. })
  74. @module.route('/template/<key>/save', methods=['POST'])
  75. def save_template(key):
  76. require_enabled()
  77. template = TemplateDebugPanel.get_cache_for_key(key)[0]['template']
  78. content = request.form['content']
  79. with open(template.filename, 'w') as fp:
  80. fp.write(content)
  81. return 'ok'
  82. @module.route('/template/<key>', methods=['POST'])
  83. def template_preview(key):
  84. require_enabled()
  85. context = TemplateDebugPanel.get_cache_for_key(key)[0]['context']
  86. content = request.form['content']
  87. env = current_app.jinja_env.overlay(autoescape=True)
  88. try:
  89. template = env.from_string(content)
  90. return template.render(context)
  91. except Exception as e:
  92. tb = sys.exc_info()[2]
  93. try:
  94. while tb.tb_next:
  95. tb = tb.tb_next
  96. msg = {'lineno': tb.tb_lineno, 'error': str(e)}
  97. return Response(json.dumps(msg), status=400, mimetype='application/json')
  98. finally:
  99. del tb