/__init__.py

https://github.com/235/django-template-introspection · Python · 91 lines · 67 code · 8 blank · 16 comment · 16 complexity · 1dd7d3c51add7d706b20b044d3cae707 MD5 · raw file

  1. from settings import DTI_DEBUG, DTI_TRACE_TEMPLATE
  2. from django.conf import settings
  3. from django.template import Template, StringOrigin
  4. from BeautifulSoup import BeautifulSoup, Tag
  5. from hashlib import md5
  6. import inspect
  7. import os
  8. try:
  9. from threading import local
  10. except ImportError:
  11. from django.utils._threading_local import local
  12. if DTI_DEBUG:
  13. #Create a global variable that will be visible in a modified template render
  14. #here we store all provanence traces of the produced output
  15. GLOBALS = local()
  16. #Collect paths to modules that have to be excluded from a trace tree:
  17. #exclude django & the current module
  18. import django
  19. EXCLUDE_PATHS = (django.__file__.split('__init__')[0],
  20. __file__.split('__init__')[0])
  21. ###Django template engine monkey-patching
  22. #we enable provanence collection in its runtime
  23. def enhanced_init(old_init):
  24. """ Decorator over the original init in order to store the template name """
  25. def new_init(self, template_string, origin=None, name='<Unknown Template>'):
  26. if origin is None:
  27. self.origin = StringOrigin(template_string)
  28. else:
  29. self.origin = origin
  30. return old_init(self, template_string, origin, name)
  31. return new_init
  32. def render(self, context):
  33. """ New render that saves metadata and annotate produces tags """
  34. def nice_inspect():
  35. """ Inspect the trace tree, collect usuful data """
  36. tree = []
  37. frame = inspect.currentframe().f_back
  38. #skip excluded and only then gather few hops
  39. while frame.f_back and frame.f_code.co_filename.startswith(EXCLUDE_PATHS):
  40. frame = frame.f_back
  41. for i in range(1, 3):
  42. if frame.f_code.co_filename.startswith(EXCLUDE_PATHS):
  43. frame = frame.f_back
  44. continue
  45. trace = inspect.getframeinfo(frame)
  46. filename = os.path.relpath(trace.filename, settings.SITE_BASEDIR)
  47. formatted = DTI_TRACE_TEMPLATE % {'file': filename,
  48. 'line': trace.lineno,
  49. 'func': trace.function,
  50. 'code': "<br/>".join(trace.code_context)}
  51. tree.append( formatted.replace("'", '"') )
  52. frame = frame.f_back
  53. return tree
  54. def insert_metadata(tree, output):
  55. """ Add current rendering event to the global metadata, annotate output """
  56. info = {'origin': os.path.relpath(str(self.origin), settings.SITE_BASEDIR),
  57. #'name': str(self.name),
  58. 'tree': "<br/>".join(tree)}
  59. dhash = md5(str(info['origin'])).hexdigest()
  60. GLOBALS.tdebug[dhash] = info
  61. #add an attribute to each HTML-tag with a given hash or update existing
  62. #WARNING: if the produced HTML is invalid, BeautifulSoup will try to fix it
  63. soup = BeautifulSoup(output)
  64. tags_gen = soup.recursiveChildGenerator()
  65. while True:
  66. try:
  67. tag = tags_gen.next()
  68. if not isinstance(tag, Tag):
  69. continue
  70. if ('dhash' in tag) and (dhash not in tag['dhash'].split(' ')):
  71. tag['dhash'] = tag['dhash'] + ' ' + dhash
  72. else:
  73. tag['dhash'] = dhash
  74. except StopIteration:
  75. break
  76. return unicode(soup)
  77. tree = nice_inspect()
  78. #The only line from the original method - the rendering itself
  79. output = self.nodelist.render(context)
  80. return insert_metadata(tree, output)
  81. #Monkey-patching itself
  82. Template.__init__ = enhanced_init(Template.__init__)
  83. Template.render = render