From bb13711451157d5081c2d2a297820f6bc131ac27 Mon Sep 17 00:00:00 2001 From: David Smith Date: Wed, 22 Apr 2020 21:16:29 +0100 Subject: [PATCH] Fixed #25712 -- Reorganized templates docs. --- docs/{topics => howto}/_images/postmortem.png | Bin .../_images/template-lines.png | Bin docs/howto/custom-template-backend.txt | 173 ++++++ docs/howto/index.txt | 1 + docs/index.txt | 3 +- docs/topics/templates.txt | 521 ++++++------------ 6 files changed, 351 insertions(+), 347 deletions(-) rename docs/{topics => howto}/_images/postmortem.png (100%) rename docs/{topics => howto}/_images/template-lines.png (100%) create mode 100644 docs/howto/custom-template-backend.txt diff --git a/docs/topics/_images/postmortem.png b/docs/howto/_images/postmortem.png similarity index 100% rename from docs/topics/_images/postmortem.png rename to docs/howto/_images/postmortem.png diff --git a/docs/topics/_images/template-lines.png b/docs/howto/_images/template-lines.png similarity index 100% rename from docs/topics/_images/template-lines.png rename to docs/howto/_images/template-lines.png diff --git a/docs/howto/custom-template-backend.txt b/docs/howto/custom-template-backend.txt new file mode 100644 index 0000000000..3e9c87a07a --- /dev/null +++ b/docs/howto/custom-template-backend.txt @@ -0,0 +1,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 +` 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 `, 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/master/final/0182-multiple-template-engines.rst +.. _Django Debug Toolbar: https://github.com/jazzband/django-debug-toolbar diff --git a/docs/howto/index.txt b/docs/howto/index.txt index 433ab248e7..ffe4c5519e 100644 --- a/docs/howto/index.txt +++ b/docs/howto/index.txt @@ -14,6 +14,7 @@ you quickly accomplish common tasks. custom-management-commands custom-model-fields custom-lookups + custom-template-backend custom-template-tags custom-file-storage deployment/index diff --git a/docs/index.txt b/docs/index.txt index e55d28b22f..9e8a3fdfd6 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -173,7 +173,8 @@ designers and how it can be extended by programmers: * **For programmers:** :doc:`Template API ` | - :doc:`Custom tags and filters ` + :doc:`Custom tags and filters ` | + :doc:`Custom template backend ` Forms ===== diff --git a/docs/topics/templates.txt b/docs/topics/templates.txt index 63ae8b72f4..4b00028e45 100644 --- a/docs/topics/templates.txt +++ b/docs/topics/templates.txt @@ -15,7 +15,8 @@ A Django project can be configured with one or several template engines (or even zero if you don't use templates). Django ships built-in backends for its own template system, creatively called the Django template language (DTL), and for the popular alternative Jinja2_. Backends for other template languages may -be available from third-parties. +be available from third-parties. You can also write your own custom backend, +see :doc:`Custom template backend ` Django defines a standard API for loading and rendering templates regardless of the backend. Loading consists of finding the template for a given identifier @@ -43,11 +44,183 @@ namespace. since template authors can do things like perform XSS attacks and access properties of template variables that may contain sensitive information. +.. _template-language-intro: + +The Django template language +============================ + +.. highlight:: html+django + +Syntax +------ + +.. admonition:: About this section + + This is an overview of the Django template language's syntax. For details + see the :doc:`language syntax reference `. + +A Django template is a text document or a Python string marked-up using the +Django template language. Some constructs are recognized and interpreted by the +template engine. The main ones are variables and tags. + +A template is rendered with a context. Rendering replaces variables with their +values, which are looked up in the context, and executes tags. Everything else +is output as is. + +The syntax of the Django template language involves four constructs. + +Variables +~~~~~~~~~ + +A variable outputs a value from the context, which is a dict-like object +mapping keys to values. + +Variables are surrounded by ``{{`` and ``}}`` like this:: + + My first name is {{ first_name }}. My last name is {{ last_name }}. + +With a context of ``{'first_name': 'John', 'last_name': 'Doe'}``, this template +renders to:: + + My first name is John. My last name is Doe. + +Dictionary lookup, attribute lookup and list-index lookups are implemented with +a dot notation:: + + {{ my_dict.key }} + {{ my_object.attribute }} + {{ my_list.0 }} + +If a variable resolves to a callable, the template system will call it with no +arguments and use its result instead of the callable. + +Tags +~~~~ + +Tags provide arbitrary logic in the rendering process. + +This definition is deliberately vague. For example, a tag can output content, +serve as a control structure e.g. an "if" statement or a "for" loop, grab +content from a database, or even enable access to other template tags. + +Tags are surrounded by ``{%`` and ``%}`` like this:: + + {% csrf_token %} + +Most tags accept arguments:: + + {% cycle 'odd' 'even' %} + +Some tags require beginning and ending tags:: + + {% if user.is_authenticated %}Hello, {{ user.username }}.{% endif %} + +A :ref:`reference of built-in tags ` is +available as well as :ref:`instructions for writing custom tags +`. + +Filters +~~~~~~~ + +Filters transform the values of variables and tag arguments. + +They look like this:: + + {{ django|title }} + +With a context of ``{'django': 'the web framework for perfectionists with +deadlines'}``, this template renders to:: + + The Web Framework For Perfectionists With Deadlines + +Some filters take an argument:: + + {{ my_date|date:"Y-m-d" }} + +A :ref:`reference of built-in filters ` is +available as well as :ref:`instructions for writing custom filters +`. + +Comments +~~~~~~~~ + +Comments look like this:: + + {# this won't be rendered #} + +A :ttag:`{% comment %} ` tag provides multi-line comments. + +Components +---------- + +.. admonition:: About this section + + This is an overview of the Django template language's APIs. For details + see the :doc:`API reference `. + +Engine +~~~~~~ + +:class:`django.template.Engine` encapsulates an instance of the Django +template system. The main reason for instantiating an +:class:`~django.template.Engine` directly is to use the Django template +language outside of a Django project. + +:class:`django.template.backends.django.DjangoTemplates` is a thin wrapper +adapting :class:`django.template.Engine` to Django's template backend API. + +Template +~~~~~~~~ + +:class:`django.template.Template` represents a compiled template. Templates are +obtained with :meth:`.Engine.get_template` or :meth:`.Engine.from_string`. + +Likewise ``django.template.backends.django.Template`` is a thin wrapper +adapting :class:`django.template.Template` to the common template API. + +Context +~~~~~~~ + +:class:`django.template.Context` holds some metadata in addition to the context +data. It is passed to :meth:`.Template.render` for rendering a template. + +:class:`django.template.RequestContext` is a subclass of +:class:`~django.template.Context` that stores the current +:class:`~django.http.HttpRequest` and runs template context processors. + +The common API doesn't have an equivalent concept. Context data is passed in a +plain :class:`dict` and the current :class:`~django.http.HttpRequest` is passed +separately if needed. + +Loaders +~~~~~~~ + +Template loaders are responsible for locating templates, loading them, and +returning :class:`~django.template.Template` objects. + +Django provides several :ref:`built-in template loaders ` +and supports :ref:`custom template loaders `. + +Context processors +~~~~~~~~~~~~~~~~~~ + +Context processors are functions that receive the current +:class:`~django.http.HttpRequest` as an argument and return a :class:`dict` of +data to be added to the rendering context. + +Their main use is to add common data shared by all templates to the context +without repeating code in every view. + +Django provides many :ref:`built-in context processors `, +and you can implement your own additional context processors, too. + .. _template-engines: Support for template engines ============================ +.. highlight:: python + Configuration ------------- @@ -483,348 +656,4 @@ templates, as shown in the example above. Jinja2's global namespace removes the need for template context processors. The Django template language doesn't have an equivalent of Jinja2 tests. -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 -` 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 `, 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``. - -.. currentmodule:: django.template - -.. _template-language-intro: - -The Django template language -============================ - -.. highlight:: html+django - -Syntax ------- - -.. admonition:: About this section - - This is an overview of the Django template language's syntax. For details - see the :doc:`language syntax reference `. - -A Django template is a text document or a Python string marked-up using the -Django template language. Some constructs are recognized and interpreted by the -template engine. The main ones are variables and tags. - -A template is rendered with a context. Rendering replaces variables with their -values, which are looked up in the context, and executes tags. Everything else -is output as is. - -The syntax of the Django template language involves four constructs. - -Variables -~~~~~~~~~ - -A variable outputs a value from the context, which is a dict-like object -mapping keys to values. - -Variables are surrounded by ``{{`` and ``}}`` like this:: - - My first name is {{ first_name }}. My last name is {{ last_name }}. - -With a context of ``{'first_name': 'John', 'last_name': 'Doe'}``, this -template renders to:: - - My first name is John. My last name is Doe. - -Dictionary lookup, attribute lookup and list-index lookups are implemented -with a dot notation:: - - {{ my_dict.key }} - {{ my_object.attribute }} - {{ my_list.0 }} - -If a variable resolves to a callable, the template system will call it with no -arguments and use its result instead of the callable. - -Tags -~~~~ - -Tags provide arbitrary logic in the rendering process. - -This definition is deliberately vague. For example, a tag can output content, -serve as a control structure e.g. an "if" statement or a "for" loop, grab -content from a database, or even enable access to other template tags. - -Tags are surrounded by ``{%`` and ``%}`` like this:: - - {% csrf_token %} - -Most tags accept arguments:: - - {% cycle 'odd' 'even' %} - -Some tags require beginning and ending tags:: - - {% if user.is_authenticated %}Hello, {{ user.username }}.{% endif %} - -A :ref:`reference of built-in tags ` is -available as well as :ref:`instructions for writing custom tags -`. - -Filters -~~~~~~~ - -Filters transform the values of variables and tag arguments. - -They look like this:: - - {{ django|title }} - -With a context of ``{'django': 'the web framework for perfectionists with -deadlines'}``, this template renders to:: - - The Web Framework For Perfectionists With Deadlines - -Some filters take an argument:: - - {{ my_date|date:"Y-m-d" }} - -A :ref:`reference of built-in filters ` is -available as well as :ref:`instructions for writing custom filters -`. - -Comments -~~~~~~~~ - -Comments look like this:: - - {# this won't be rendered #} - -A :ttag:`{% comment %} ` tag provides multi-line comments. - -Components ----------- - -.. admonition:: About this section - - This is an overview of the Django template language's APIs. For details - see the :doc:`API reference `. - -Engine -~~~~~~ - -:class:`django.template.Engine` encapsulates an instance of the Django -template system. The main reason for instantiating an -:class:`~django.template.Engine` directly is to use the Django template -language outside of a Django project. - -:class:`django.template.backends.django.DjangoTemplates` is a thin wrapper -adapting :class:`django.template.Engine` to Django's template backend API. - -Template -~~~~~~~~ - -:class:`django.template.Template` represents a compiled template. -Templates are obtained with :meth:`Engine.get_template() -` or :meth:`Engine.from_string() -` - -Likewise ``django.template.backends.django.Template`` is a thin wrapper -adapting :class:`django.template.Template` to the common template API. - -Context -~~~~~~~ - -:class:`django.template.Context` holds some metadata in addition to the -context data. It is passed to :meth:`Template.render() -` for rendering a template. - -:class:`django.template.RequestContext` is a subclass of -:class:`~django.template.Context` that stores the current -:class:`~django.http.HttpRequest` and runs template context processors. - -The common API doesn't have an equivalent concept. Context data is passed in a -plain :class:`dict` and the current :class:`~django.http.HttpRequest` is passed -separately if needed. - -Loaders -~~~~~~~ - -Template loaders are responsible for locating templates, loading them, and -returning :class:`~django.template.Template` objects. - -Django provides several :ref:`built-in template loaders ` -and supports :ref:`custom template loaders `. - -Context processors -~~~~~~~~~~~~~~~~~~ - -Context processors are functions that receive the current -:class:`~django.http.HttpRequest` as an argument and return a :class:`dict` of -data to be added to the rendering context. - -Their main use is to add common data shared by all templates to the context -without repeating code in every view. - -Django provides many :ref:`built-in context processors `, -and you can implement your own additional context processors, too. - -.. _Jinja2: http://jinja.pocoo.org/ -.. _DEP 182: https://github.com/django/deps/blob/master/final/0182-multiple-template-engines.rst -.. _Django Debug Toolbar: https://github.com/jazzband/django-debug-toolbar +.. _Jinja2: https://jinja.palletsprojects.com/