1. ==========================================
    
  2. How to implement a custom template backend
    
  3. ==========================================
    
  4. 
    
  5. Custom backends
    
  6. ---------------
    
  7. 
    
  8. Here's how to implement a custom template backend in order to use another
    
  9. template system. A template backend is a class that inherits
    
  10. ``django.template.backends.base.BaseEngine``. It must implement
    
  11. ``get_template()`` and optionally ``from_string()``. Here's an example for a
    
  12. fictional ``foobar`` template library::
    
  13. 
    
  14.     from django.template import TemplateDoesNotExist, TemplateSyntaxError
    
  15.     from django.template.backends.base import BaseEngine
    
  16.     from django.template.backends.utils import csrf_input_lazy, csrf_token_lazy
    
  17. 
    
  18.     import foobar
    
  19. 
    
  20. 
    
  21.     class FooBar(BaseEngine):
    
  22. 
    
  23.         # Name of the subdirectory containing the templates for this engine
    
  24.         # inside an installed application.
    
  25.         app_dirname = 'foobar'
    
  26. 
    
  27.         def __init__(self, params):
    
  28.             params = params.copy()
    
  29.             options = params.pop('OPTIONS').copy()
    
  30.             super().__init__(params)
    
  31. 
    
  32.             self.engine = foobar.Engine(**options)
    
  33. 
    
  34.         def from_string(self, template_code):
    
  35.             try:
    
  36.                 return Template(self.engine.from_string(template_code))
    
  37.             except foobar.TemplateCompilationFailed as exc:
    
  38.                 raise TemplateSyntaxError(exc.args)
    
  39. 
    
  40.         def get_template(self, template_name):
    
  41.             try:
    
  42.                 return Template(self.engine.get_template(template_name))
    
  43.             except foobar.TemplateNotFound as exc:
    
  44.                 raise TemplateDoesNotExist(exc.args, backend=self)
    
  45.             except foobar.TemplateCompilationFailed as exc:
    
  46.                 raise TemplateSyntaxError(exc.args)
    
  47. 
    
  48. 
    
  49.     class Template:
    
  50. 
    
  51.         def __init__(self, template):
    
  52.             self.template = template
    
  53. 
    
  54.         def render(self, context=None, request=None):
    
  55.             if context is None:
    
  56.                 context = {}
    
  57.             if request is not None:
    
  58.                 context['request'] = request
    
  59.                 context['csrf_input'] = csrf_input_lazy(request)
    
  60.                 context['csrf_token'] = csrf_token_lazy(request)
    
  61.             return self.template.render(context)
    
  62. 
    
  63. See `DEP 182`_ for more information.
    
  64. 
    
  65. .. _template-debug-integration:
    
  66. 
    
  67. Debug integration for custom engines
    
  68. ------------------------------------
    
  69. 
    
  70. The Django debug page has hooks to provide detailed information when a template
    
  71. error arises. Custom template engines can use these hooks to enhance the
    
  72. traceback information that appears to users. The following hooks are available:
    
  73. 
    
  74. .. _template-postmortem:
    
  75. 
    
  76. Template postmortem
    
  77. ~~~~~~~~~~~~~~~~~~~
    
  78. 
    
  79. The postmortem appears when :exc:`~django.template.TemplateDoesNotExist` is
    
  80. raised. It lists the template engines and loaders that were used when trying to
    
  81. find a given template. For example, if two Django engines are configured, the
    
  82. postmortem will appear like:
    
  83. 
    
  84. .. image:: _images/postmortem.png
    
  85. 
    
  86. Custom engines can populate the postmortem by passing the ``backend`` and
    
  87. ``tried`` arguments when raising :exc:`~django.template.TemplateDoesNotExist`.
    
  88. Backends that use the postmortem :ref:`should specify an origin
    
  89. <template-origin-api>` on the template object.
    
  90. 
    
  91. Contextual line information
    
  92. ~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
  93. 
    
  94. If an error happens during template parsing or rendering, Django can display
    
  95. the line the error happened on. For example:
    
  96. 
    
  97. .. image:: _images/template-lines.png
    
  98. 
    
  99. Custom engines can populate this information by setting a ``template_debug``
    
  100. attribute on exceptions raised during parsing and rendering. This attribute is
    
  101. a :class:`dict` with the following values:
    
  102. 
    
  103. * ``'name'``: The name of the template in which the exception occurred.
    
  104. 
    
  105. * ``'message'``: The exception message.
    
  106. 
    
  107. * ``'source_lines'``: The lines before, after, and including the line the
    
  108.   exception occurred on. This is for context, so it shouldn't contain more than
    
  109.   20 lines or so.
    
  110. 
    
  111. * ``'line'``: The line number on which the exception occurred.
    
  112. 
    
  113. * ``'before'``: The content on the error line before the token that raised the
    
  114.   error.
    
  115. 
    
  116. * ``'during'``: The token that raised the error.
    
  117. 
    
  118. * ``'after'``: The content on the error line after the token that raised the
    
  119.   error.
    
  120. 
    
  121. * ``'total'``: The number of lines in ``source_lines``.
    
  122. 
    
  123. * ``'top'``: The line number where ``source_lines`` starts.
    
  124. 
    
  125. * ``'bottom'``: The line number where ``source_lines`` ends.
    
  126. 
    
  127. Given the above template error, ``template_debug`` would look like::
    
  128. 
    
  129.     {
    
  130.         'name': '/path/to/template.html',
    
  131.         'message': "Invalid block tag: 'syntax'",
    
  132.         'source_lines': [
    
  133.             (1, 'some\n'),
    
  134.             (2, 'lines\n'),
    
  135.             (3, 'before\n'),
    
  136.             (4, 'Hello {% syntax error %} {{ world }}\n'),
    
  137.             (5, 'some\n'),
    
  138.             (6, 'lines\n'),
    
  139.             (7, 'after\n'),
    
  140.             (8, ''),
    
  141.         ],
    
  142.         'line': 4,
    
  143.         'before': 'Hello ',
    
  144.         'during': '{% syntax error %}',
    
  145.         'after': ' {{ world }}\n',
    
  146.         'total': 9,
    
  147.         'bottom': 9,
    
  148.         'top': 1,
    
  149.     }
    
  150. 
    
  151. .. _template-origin-api:
    
  152. 
    
  153. Origin API and 3rd-party integration
    
  154. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
  155. 
    
  156. Django templates have an :class:`~django.template.base.Origin` object available
    
  157. through the ``template.origin`` attribute. This enables debug information to be
    
  158. displayed in the :ref:`template postmortem <template-postmortem>`, as well as
    
  159. in 3rd-party libraries, like the `Django Debug Toolbar`_.
    
  160. 
    
  161. Custom engines can provide their own ``template.origin`` information by
    
  162. creating an object that specifies the following attributes:
    
  163. 
    
  164. * ``'name'``: The full path to the template.
    
  165. 
    
  166. * ``'template_name'``: The relative path to the template as passed into the
    
  167.   template loading methods.
    
  168. 
    
  169. * ``'loader_name'``: An optional string identifying the function or class used
    
  170.   to load the template, e.g. ``django.template.loaders.filesystem.Loader``.
    
  171. 
    
  172. .. _DEP 182: https://github.com/django/deps/blob/main/final/0182-multiple-template-engines.rst
    
  173. .. _Django Debug Toolbar: https://github.com/jazzband/django-debug-toolbar/