jinja2.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. from pathlib import Path
  2. import jinja2
  3. from django.conf import settings
  4. from django.template import TemplateDoesNotExist, TemplateSyntaxError
  5. from django.utils.functional import cached_property
  6. from django.utils.module_loading import import_string
  7. from .base import BaseEngine
  8. class Jinja2(BaseEngine):
  9. app_dirname = 'jinja2'
  10. def __init__(self, params):
  11. params = params.copy()
  12. options = params.pop('OPTIONS').copy()
  13. super().__init__(params)
  14. self.context_processors = options.pop('context_processors', [])
  15. environment = options.pop('environment', 'jinja2.Environment')
  16. environment_cls = import_string(environment)
  17. if 'loader' not in options:
  18. options['loader'] = jinja2.FileSystemLoader(self.template_dirs)
  19. options.setdefault('autoescape', True)
  20. options.setdefault('auto_reload', settings.DEBUG)
  21. options.setdefault('undefined',
  22. jinja2.DebugUndefined if settings.DEBUG else jinja2.Undefined)
  23. self.env = environment_cls(**options)
  24. def from_string(self, template_code):
  25. return Template(self.env.from_string(template_code), self)
  26. def get_template(self, template_name):
  27. try:
  28. return Template(self.env.get_template(template_name), self)
  29. except jinja2.TemplateNotFound as exc:
  30. raise TemplateDoesNotExist(exc.name, backend=self) from exc
  31. except jinja2.TemplateSyntaxError as exc:
  32. new = TemplateSyntaxError(exc.args)
  33. new.template_debug = get_exception_info(exc)
  34. raise new from exc
  35. @cached_property
  36. def template_context_processors(self):
  37. return [import_string(path) for path in self.context_processors]
  38. class Template:
  39. def __init__(self, template, backend):
  40. self.template = template
  41. self.backend = backend
  42. self.origin = Origin(
  43. name=template.filename, template_name=template.name,
  44. )
  45. def render(self, context=None, request=None):
  46. from .utils import csrf_input_lazy, csrf_token_lazy
  47. if context is None:
  48. context = {}
  49. if request is not None:
  50. context['request'] = request
  51. context['csrf_input'] = csrf_input_lazy(request)
  52. context['csrf_token'] = csrf_token_lazy(request)
  53. for context_processor in self.backend.template_context_processors:
  54. context.update(context_processor(request))
  55. try:
  56. return self.template.render(context)
  57. except jinja2.TemplateSyntaxError as exc:
  58. new = TemplateSyntaxError(exc.args)
  59. new.template_debug = get_exception_info(exc)
  60. raise new from exc
  61. class Origin:
  62. """
  63. A container to hold debug information as described in the template API
  64. documentation.
  65. """
  66. def __init__(self, name, template_name):
  67. self.name = name
  68. self.template_name = template_name
  69. def get_exception_info(exception):
  70. """
  71. Format exception information for display on the debug page using the
  72. structure described in the template API documentation.
  73. """
  74. context_lines = 10
  75. lineno = exception.lineno
  76. source = exception.source
  77. if source is None:
  78. exception_file = Path(exception.filename)
  79. if exception_file.exists():
  80. with open(exception_file, 'r') as fp:
  81. source = fp.read()
  82. if source is not None:
  83. lines = list(enumerate(source.strip().split('\n'), start=1))
  84. during = lines[lineno - 1][1]
  85. total = len(lines)
  86. top = max(0, lineno - context_lines - 1)
  87. bottom = min(total, lineno + context_lines)
  88. else:
  89. during = ''
  90. lines = []
  91. total = top = bottom = 0
  92. return {
  93. 'name': exception.filename,
  94. 'message': exception.message,
  95. 'source_lines': lines[top:bottom],
  96. 'line': lineno,
  97. 'before': '',
  98. 'during': during,
  99. 'after': '',
  100. 'total': total,
  101. 'top': top,
  102. 'bottom': bottom,
  103. }