Fixed #20435 -- Reordered the custom template tags docs.

Introduced the various shortcuts before explaining the more complex
parser/render functionality.

Also removed non-decorator syntax: it's been years since Django
supported a Python version without decorators.
This commit is contained in:
Daniel Roseman 2014-11-15 12:08:40 +00:00 committed by Julien Phalip
parent fc36437434
commit 524e71c9c2
1 changed files with 293 additions and 310 deletions

View File

@ -372,7 +372,291 @@ conversions in templates <time-zones-in-templates>`.
Writing custom template tags
----------------------------
Tags are more complex than filters, because tags can do anything.
Tags are more complex than filters, because tags can do anything. Django
provides a number of shortcuts that make writing most types of tags easier.
First we'll explore those shortcuts, then explain how to write a tag from
scratch for those cases when the shortcuts aren't powerful enough.
.. _howto-custom-template-tags-simple-tags:
Simple tags
~~~~~~~~~~~
.. method:: django.template.Library.simple_tag()
Many template tags take a number of arguments -- strings or template variables
-- and return a string after doing some processing based solely on
the input arguments and some external information. For example, a
``current_time`` tag might accept a format string and return the time as a
string formatted accordingly.
To ease the creation of these types of tags, Django provides a helper function,
``simple_tag``. This function, which is a method of
``django.template.Library``, takes a function that accepts any number of
arguments, wraps it in a ``render`` function and the other necessary bits
mentioned above and registers it with the template system.
Our ``current_time`` function could thus be written like this::
import datetime
from django import template
register = template.Library()
@register.simple_tag
def current_time(format_string):
return datetime.datetime.now().strftime(format_string)
A few things to note about the ``simple_tag`` helper function:
* Checking for the required number of arguments, etc., has already been
done by the time our function is called, so we don't need to do that.
* The quotes around the argument (if any) have already been stripped away,
so we just receive a plain string.
* If the argument was a template variable, our function is passed the
current value of the variable, not the variable itself.
If your template tag needs to access the current context, you can use the
``takes_context`` argument when registering your tag::
@register.simple_tag(takes_context=True)
def current_time(context, format_string):
timezone = context['timezone']
return your_get_current_time_method(timezone, format_string)
Note that the first argument *must* be called ``context``.
For more information on how the ``takes_context`` option works, see the section
on :ref:`inclusion tags<howto-custom-template-tags-inclusion-tags>`.
If you need to rename your tag, you can provide a custom name for it::
register.simple_tag(lambda x: x - 1, name='minusone')
@register.simple_tag(name='minustwo')
def some_function(value):
return value - 2
``simple_tag`` functions may accept any number of positional or keyword
arguments. For example::
@register.simple_tag
def my_tag(a, b, *args, **kwargs):
warning = kwargs['warning']
profile = kwargs['profile']
...
return ...
Then in the template any number of arguments, separated by spaces, may be
passed to the template tag. Like in Python, the values for keyword arguments
are set using the equal sign ("``=``") and must be provided after the
positional arguments. For example:
.. code-block:: html+django
{% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %}
.. _howto-custom-template-tags-inclusion-tags:
Inclusion tags
~~~~~~~~~~~~~~
.. method:: django.template.Library.inclusion_tag()
Another common type of template tag is the type that displays some data by
rendering *another* template. For example, Django's admin interface uses custom
template tags to display the buttons along the bottom of the "add/change" form
pages. Those buttons always look the same, but the link targets change
depending on the object being edited -- so they're a perfect case for using a
small template that is filled with details from the current object. (In the
admin's case, this is the ``submit_row`` tag.)
These sorts of tags are called "inclusion tags".
Writing inclusion tags is probably best demonstrated by example. Let's write a
tag that outputs a list of choices for a given ``Poll`` object, such as was
created in the :ref:`tutorials <creating-models>`. We'll use the tag like this:
.. code-block:: html+django
{% show_results poll %}
...and the output will be something like this:
.. code-block:: html
<ul>
<li>First choice</li>
<li>Second choice</li>
<li>Third choice</li>
</ul>
First, define the function that takes the argument and produces a dictionary of
data for the result. The important point here is we only need to return a
dictionary, not anything more complex. This will be used as a template context
for the template fragment. Example::
def show_results(poll):
choices = poll.choice_set.all()
return {'choices': choices}
Next, create the template used to render the tag's output. This template is a
fixed feature of the tag: the tag writer specifies it, not the template
designer. Following our example, the template is very simple:
.. code-block:: html+django
<ul>
{% for choice in choices %}
<li> {{ choice }} </li>
{% endfor %}
</ul>
Now, create and register the inclusion tag by calling the ``inclusion_tag()``
method on a ``Library`` object. Following our example, if the above template is
in a file called ``results.html`` in a directory that's searched by the
template loader, we'd register the tag like this::
# Here, register is a django.template.Library instance, as before
@register.inclusion_tag('results.html')
def show_results(poll):
...
Alternatively it is possible to register the inclusion tag using a
:class:`django.template.Template` instance::
from django.template.loader import get_template
t = get_template('results.html')
register.inclusion_tag(t)(show_results)
...when first creating the function.
Sometimes, your inclusion tags might require a large number of arguments,
making it a pain for template authors to pass in all the arguments and remember
their order. To solve this, Django provides a ``takes_context`` option for
inclusion tags. If you specify ``takes_context`` in creating a template tag,
the tag will have no required arguments, and the underlying Python function
will have one argument -- the template context as of when the tag was called.
For example, say you're writing an inclusion tag that will always be used in a
context that contains ``home_link`` and ``home_title`` variables that point
back to the main page. Here's what the Python function would look like::
@register.inclusion_tag('link.html', takes_context=True)
def jump_link(context):
return {
'link': context['home_link'],
'title': context['home_title'],
}
Note that the first parameter to the function *must* be called ``context``.
In that ``register.inclusion_tag()`` line, we specified ``takes_context=True``
and the name of the template. Here's what the template ``link.html`` might look
like:
.. code-block:: html+django
Jump directly to <a href="{{ link }}">{{ title }}</a>.
Then, any time you want to use that custom tag, load its library and call it
without any arguments, like so:
.. code-block:: html+django
{% jump_link %}
Note that when you're using ``takes_context=True``, there's no need to pass
arguments to the template tag. It automatically gets access to the context.
The ``takes_context`` parameter defaults to ``False``. When it's set to
``True``, the tag is passed the context object, as in this example. That's the
only difference between this case and the previous ``inclusion_tag`` example.
``inclusion_tag`` functions may accept any number of positional or keyword
arguments. For example::
@register.inclusion_tag('my_template.html')
def my_tag(a, b, *args, **kwargs):
warning = kwargs['warning']
profile = kwargs['profile']
...
return ...
Then in the template any number of arguments, separated by spaces, may be
passed to the template tag. Like in Python, the values for keyword arguments
are set using the equal sign ("``=``") and must be provided after the
positional arguments. For example:
.. code-block:: html+django
{% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %}
.. _howto-custom-template-tags-assignment-tags:
Assignment tags
~~~~~~~~~~~~~~~
.. method:: django.template.Library.assignment_tag()
To ease the creation of tags setting a variable in the context, Django provides
a helper function, ``assignment_tag``. This function works the same way as
:ref:`simple_tag<howto-custom-template-tags-simple-tags>`, except that it
stores the tag's result in a specified context variable instead of directly
outputting it.
Our earlier ``current_time`` function could thus be written like this::
@register.assignment_tag
def get_current_time(format_string):
return datetime.datetime.now().strftime(format_string)
You may then store the result in a template variable using the ``as`` argument
followed by the variable name, and output it yourself where you see fit:
.. code-block:: html+django
{% get_current_time "%Y-%m-%d %I:%M %p" as the_time %}
<p>The time is {{ the_time }}.</p>
If your template tag needs to access the current context, you can use the
``takes_context`` argument when registering your tag::
@register.assignment_tag(takes_context=True)
def get_current_time(context, format_string):
timezone = context['timezone']
return your_get_current_time_method(timezone, format_string)
Note that the first parameter to the function *must* be called ``context``.
For more information on how the ``takes_context`` option works, see the section
on :ref:`inclusion tags<howto-custom-template-tags-inclusion-tags>`.
``assignment_tag`` functions may accept any number of positional or keyword
arguments. For example::
@register.assignment_tag
def my_tag(a, b, *args, **kwargs):
warning = kwargs['warning']
profile = kwargs['profile']
...
return ...
Then in the template any number of arguments, separated by spaces, may be
passed to the template tag. Like in Python, the values for keyword arguments
are set using the equal sign ("``=``") and must be provided after the
positional arguments. For example:
.. code-block:: html+django
{% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile as the_result %}
Advanced custom template tags
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sometimes the basic features for custom template tag creation aren't enough.
Don't worry, Django gives you complete access to the internals required to build
a template tag from the ground up.
A quick overview
~~~~~~~~~~~~~~~~
@ -399,10 +683,11 @@ For each template tag the template parser encounters, it calls a Python
function with the tag contents and the parser object itself. This function is
responsible for returning a ``Node`` instance based on the contents of the tag.
For example, let's write a template tag, ``{% current_time %}``, that displays
the current date/time, formatted according to a parameter given in the tag, in
:func:`~time.strftime` syntax. It's a good idea to decide the tag syntax before
anything else. In our case, let's say the tag should be used like this:
For example, let's write a full implementation of our simple template tag,
``{% current_time %}``, that displays the current date/time, formatted according
to a parameter given in the tag, in :func:`~time.strftime` syntax. It's a good
idea to decide the tag syntax before anything else. In our case, let's say the
tag should be used like this:
.. code-block:: html+django
@ -708,239 +993,6 @@ for example::
Variable resolution will throw a ``VariableDoesNotExist`` exception if it
cannot resolve the string passed to it in the current context of the page.
.. _howto-custom-template-tags-simple-tags:
Simple tags
~~~~~~~~~~~
.. method:: django.template.Library.simple_tag()
Many template tags take a number of arguments -- strings or template variables
-- and return a string after doing some processing based solely on
the input arguments and some external information. For example, the
``current_time`` tag we wrote above is of this variety: we give it a format
string, it returns the time as a string.
To ease the creation of these types of tags, Django provides a helper function,
``simple_tag``. This function, which is a method of
``django.template.Library``, takes a function that accepts any number of
arguments, wraps it in a ``render`` function and the other necessary bits
mentioned above and registers it with the template system.
Our earlier ``current_time`` function could thus be written like this::
import datetime
from django import template
register = template.Library()
def current_time(format_string):
return datetime.datetime.now().strftime(format_string)
register.simple_tag(current_time)
The decorator syntax also works::
@register.simple_tag
def current_time(format_string):
...
A few things to note about the ``simple_tag`` helper function:
* Checking for the required number of arguments, etc., has already been
done by the time our function is called, so we don't need to do that.
* The quotes around the argument (if any) have already been stripped away,
so we just receive a plain string.
* If the argument was a template variable, our function is passed the
current value of the variable, not the variable itself.
If your template tag needs to access the current context, you can use the
``takes_context`` argument when registering your tag::
# The first argument *must* be called "context" here.
def current_time(context, format_string):
timezone = context['timezone']
return your_get_current_time_method(timezone, format_string)
register.simple_tag(takes_context=True)(current_time)
Or, using decorator syntax::
@register.simple_tag(takes_context=True)
def current_time(context, format_string):
timezone = context['timezone']
return your_get_current_time_method(timezone, format_string)
For more information on how the ``takes_context`` option works, see the section
on :ref:`inclusion tags<howto-custom-template-tags-inclusion-tags>`.
If you need to rename your tag, you can provide a custom name for it::
register.simple_tag(lambda x: x - 1, name='minusone')
@register.simple_tag(name='minustwo')
def some_function(value):
return value - 2
``simple_tag`` functions may accept any number of positional or keyword
arguments. For example::
@register.simple_tag
def my_tag(a, b, *args, **kwargs):
warning = kwargs['warning']
profile = kwargs['profile']
...
return ...
Then in the template any number of arguments, separated by spaces, may be
passed to the template tag. Like in Python, the values for keyword arguments
are set using the equal sign ("``=``") and must be provided after the
positional arguments. For example:
.. code-block:: html+django
{% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %}
.. _howto-custom-template-tags-inclusion-tags:
Inclusion tags
~~~~~~~~~~~~~~
Another common type of template tag is the type that displays some data by
rendering *another* template. For example, Django's admin interface uses custom
template tags to display the buttons along the bottom of the "add/change" form
pages. Those buttons always look the same, but the link targets change
depending on the object being edited -- so they're a perfect case for using a
small template that is filled with details from the current object. (In the
admin's case, this is the ``submit_row`` tag.)
These sorts of tags are called "inclusion tags".
Writing inclusion tags is probably best demonstrated by example. Let's write a
tag that outputs a list of choices for a given ``Poll`` object, such as was
created in the :ref:`tutorials <creating-models>`. We'll use the tag like this:
.. code-block:: html+django
{% show_results poll %}
...and the output will be something like this:
.. code-block:: html
<ul>
<li>First choice</li>
<li>Second choice</li>
<li>Third choice</li>
</ul>
First, define the function that takes the argument and produces a dictionary of
data for the result. The important point here is we only need to return a
dictionary, not anything more complex. This will be used as a template context
for the template fragment. Example::
def show_results(poll):
choices = poll.choice_set.all()
return {'choices': choices}
Next, create the template used to render the tag's output. This template is a
fixed feature of the tag: the tag writer specifies it, not the template
designer. Following our example, the template is very simple:
.. code-block:: html+django
<ul>
{% for choice in choices %}
<li> {{ choice }} </li>
{% endfor %}
</ul>
Now, create and register the inclusion tag by calling the ``inclusion_tag()``
method on a ``Library`` object. Following our example, if the above template is
in a file called ``results.html`` in a directory that's searched by the
template loader, we'd register the tag like this::
# Here, register is a django.template.Library instance, as before
register.inclusion_tag('results.html')(show_results)
Alternatively it is possible to register the inclusion tag using a
:class:`django.template.Template` instance::
from django.template.loader import get_template
t = get_template('results.html')
register.inclusion_tag(t)(show_results)
As always, decorator syntax works as well, so we could have written::
@register.inclusion_tag('results.html')
def show_results(poll):
...
...when first creating the function.
Sometimes, your inclusion tags might require a large number of arguments,
making it a pain for template authors to pass in all the arguments and remember
their order. To solve this, Django provides a ``takes_context`` option for
inclusion tags. If you specify ``takes_context`` in creating a template tag,
the tag will have no required arguments, and the underlying Python function
will have one argument -- the template context as of when the tag was called.
For example, say you're writing an inclusion tag that will always be used in a
context that contains ``home_link`` and ``home_title`` variables that point
back to the main page. Here's what the Python function would look like::
# The first argument *must* be called "context" here.
def jump_link(context):
return {
'link': context['home_link'],
'title': context['home_title'],
}
# Register the custom tag as an inclusion tag with takes_context=True.
register.inclusion_tag('link.html', takes_context=True)(jump_link)
(Note that the first parameter to the function *must* be called ``context``.)
In that ``register.inclusion_tag()`` line, we specified ``takes_context=True``
and the name of the template. Here's what the template ``link.html`` might look
like:
.. code-block:: html+django
Jump directly to <a href="{{ link }}">{{ title }}</a>.
Then, any time you want to use that custom tag, load its library and call it
without any arguments, like so:
.. code-block:: html+django
{% jump_link %}
Note that when you're using ``takes_context=True``, there's no need to pass
arguments to the template tag. It automatically gets access to the context.
The ``takes_context`` parameter defaults to ``False``. When it's set to
``True``, the tag is passed the context object, as in this example. That's the
only difference between this case and the previous ``inclusion_tag`` example.
``inclusion_tag`` functions may accept any number of positional or keyword
arguments. For example::
@register.inclusion_tag('my_template.html')
def my_tag(a, b, *args, **kwargs):
warning = kwargs['warning']
profile = kwargs['profile']
...
return ...
Then in the template any number of arguments, separated by spaces, may be
passed to the template tag. Like in Python, the values for keyword arguments
are set using the equal sign ("``=``") and must be provided after the
positional arguments. For example:
.. code-block:: html+django
{% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %}
Setting a variable in the context
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -1024,79 +1076,10 @@ The difference here is that ``do_current_time()`` grabs the format string and
the variable name, passing both to ``CurrentTimeNode3``.
Finally, if you only need to have a simple syntax for your custom
context-updating template tag, you might want to consider using an
:ref:`assignment tag <howto-custom-template-tags-assignment-tags>`.
context-updating template tag, you might want to consider using the
:ref:`assignment tag <howto-custom-template-tags-assignment-tags>` shortcut
we introduced above.
.. _howto-custom-template-tags-assignment-tags:
Assignment tags
~~~~~~~~~~~~~~~
To ease the creation of tags setting a variable in the context, Django provides
a helper function, ``assignment_tag``. This function works the same way as
:ref:`simple_tag<howto-custom-template-tags-simple-tags>`, except that it
stores the tag's result in a specified context variable instead of directly
outputting it.
Our earlier ``current_time`` function could thus be written like this::
def get_current_time(format_string):
return datetime.datetime.now().strftime(format_string)
register.assignment_tag(get_current_time)
The decorator syntax also works::
@register.assignment_tag
def get_current_time(format_string):
...
You may then store the result in a template variable using the ``as`` argument
followed by the variable name, and output it yourself where you see fit:
.. code-block:: html+django
{% get_current_time "%Y-%m-%d %I:%M %p" as the_time %}
<p>The time is {{ the_time }}.</p>
If your template tag needs to access the current context, you can use the
``takes_context`` argument when registering your tag::
# The first argument *must* be called "context" here.
def get_current_time(context, format_string):
timezone = context['timezone']
return your_get_current_time_method(timezone, format_string)
register.assignment_tag(takes_context=True)(get_current_time)
Or, using decorator syntax::
@register.assignment_tag(takes_context=True)
def get_current_time(context, format_string):
timezone = context['timezone']
return your_get_current_time_method(timezone, format_string)
For more information on how the ``takes_context`` option works, see the section
on :ref:`inclusion tags<howto-custom-template-tags-inclusion-tags>`.
``assignment_tag`` functions may accept any number of positional or keyword
arguments. For example::
@register.assignment_tag
def my_tag(a, b, *args, **kwargs):
warning = kwargs['warning']
profile = kwargs['profile']
...
return ...
Then in the template any number of arguments, separated by spaces, may be
passed to the template tag. Like in Python, the values for keyword arguments
are set using the equal sign ("``=``") and must be provided after the
positional arguments. For example:
.. code-block:: html+django
{% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile as the_result %}
Parsing until another block tag
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~