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