2021-07-13 09:25:37 +02:00
|
|
|
==========================================
|
|
|
|
How to implement a custom template backend
|
|
|
|
==========================================
|
2020-04-22 22:16:29 +02:00
|
|
|
|
|
|
|
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``.
|
|
|
|
|
2021-02-25 17:20:14 +01:00
|
|
|
.. _DEP 182: https://github.com/django/deps/blob/main/final/0182-multiple-template-engines.rst
|
2021-04-27 13:09:00 +02:00
|
|
|
.. _Django Debug Toolbar: https://github.com/jazzband/django-debug-toolbar/
|