From 524e71c9c20da57722b22b978d87690379ca4ade Mon Sep 17 00:00:00 2001 From: Daniel Roseman Date: Sat, 15 Nov 2014 12:08:40 +0000 Subject: 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. --- docs/howto/custom-template-tags.txt | 819 ++++++++++++++++++------------------ 1 file changed, 401 insertions(+), 418 deletions(-) (limited to 'docs') diff --git a/docs/howto/custom-template-tags.txt b/docs/howto/custom-template-tags.txt index 321185407b..d69cedad35 100644 --- a/docs/howto/custom-template-tags.txt +++ b/docs/howto/custom-template-tags.txt @@ -372,7 +372,291 @@ conversions 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`. + +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 `. 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 + +
    +
  • First choice
  • +
  • Second choice
  • +
  • Third choice
  • +
+ +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 + +
    + {% for choice in choices %} +
  • {{ choice }}
  • + {% endfor %} +
+ +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 {{ title }}. + +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`, 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 %} +

The time is {{ the_time }}.

+ +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`. + +``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 @@ -563,383 +848,150 @@ A naive implementation of ``CycleNode`` might look something like this:: class CycleNode(template.Node): def __init__(self, cyclevars): - self.cycle_iter = itertools.cycle(cyclevars) - - def render(self, context): - return next(self.cycle_iter) - -But, suppose we have two templates rendering the template snippet from above at -the same time: - -1. Thread 1 performs its first loop iteration, ``CycleNode.render()`` - returns 'row1' -2. Thread 2 performs its first loop iteration, ``CycleNode.render()`` - returns 'row2' -3. Thread 1 performs its second loop iteration, ``CycleNode.render()`` - returns 'row1' -4. Thread 2 performs its second loop iteration, ``CycleNode.render()`` - returns 'row2' - -The CycleNode is iterating, but it's iterating globally. As far as Thread 1 -and Thread 2 are concerned, it's always returning the same value. This is -obviously not what we want! - -To address this problem, Django provides a ``render_context`` that's associated -with the ``context`` of the template that is currently being rendered. The -``render_context`` behaves like a Python dictionary, and should be used to -store ``Node`` state between invocations of the ``render`` method. - -Let's refactor our ``CycleNode`` implementation to use the ``render_context``:: - - class CycleNode(template.Node): - def __init__(self, cyclevars): - self.cyclevars = cyclevars - - def render(self, context): - if self not in context.render_context: - context.render_context[self] = itertools.cycle(self.cyclevars) - cycle_iter = context.render_context[self] - return next(cycle_iter) - -Note that it's perfectly safe to store global information that will not change -throughout the life of the ``Node`` as an attribute. In the case of -``CycleNode``, the ``cyclevars`` argument doesn't change after the ``Node`` is -instantiated, so we don't need to put it in the ``render_context``. But state -information that is specific to the template that is currently being rendered, -like the current iteration of the ``CycleNode``, should be stored in the -``render_context``. - -.. note:: - Notice how we used ``self`` to scope the ``CycleNode`` specific information - within the ``render_context``. There may be multiple ``CycleNodes`` in a - given template, so we need to be careful not to clobber another node's - state information. The easiest way to do this is to always use ``self`` as - the key into ``render_context``. If you're keeping track of several state - variables, make ``render_context[self]`` a dictionary. - -Registering the tag -~~~~~~~~~~~~~~~~~~~ - -Finally, register the tag with your module's ``Library`` instance, as explained -in "Writing custom template filters" above. Example:: - - register.tag('current_time', do_current_time) - -The ``tag()`` method takes two arguments: - -1. The name of the template tag -- a string. If this is left out, the - name of the compilation function will be used. -2. The compilation function -- a Python function (not the name of the - function as a string). - -As with filter registration, it is also possible to use this as a decorator:: - - @register.tag(name="current_time") - def do_current_time(parser, token): - ... - - @register.tag - def shout(parser, token): - ... - -If you leave off the ``name`` argument, as in the second example above, Django -will use the function's name as the tag name. - -Passing template variables to the tag -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Although you can pass any number of arguments to a template tag using -``token.split_contents()``, the arguments are all unpacked as -string literals. A little more work is required in order to pass dynamic -content (a template variable) to a template tag as an argument. - -While the previous examples have formatted the current time into a string and -returned the string, suppose you wanted to pass in a -:class:`~django.db.models.DateTimeField` from an object and have the template -tag format that date-time: - -.. code-block:: html+django - -

This post was last updated at {% format_time blog_entry.date_updated "%Y-%m-%d %I:%M %p" %}.

- -Initially, ``token.split_contents()`` will return three values: - -1. The tag name ``format_time``. -2. The string ``"blog_entry.date_updated"`` (without the surrounding - quotes). -3. The formatting string ``"%Y-%m-%d %I:%M %p"``. The return value from - ``split_contents()`` will include the leading and trailing quotes for - string literals like this. - -Now your tag should begin to look like this:: - - from django import template - - def do_format_time(parser, token): - try: - # split_contents() knows not to split quoted strings. - tag_name, date_to_be_formatted, format_string = token.split_contents() - except ValueError: - raise template.TemplateSyntaxError("%r tag requires exactly two arguments" % token.contents.split()[0]) - if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")): - raise template.TemplateSyntaxError("%r tag's argument should be in quotes" % tag_name) - return FormatTimeNode(date_to_be_formatted, format_string[1:-1]) - -You also have to change the renderer to retrieve the actual contents of the -``date_updated`` property of the ``blog_entry`` object. This can be -accomplished by using the ``Variable()`` class in ``django.template``. - -To use the ``Variable`` class, simply instantiate it with the name of the -variable to be resolved, and then call ``variable.resolve(context)``. So, -for example:: - - class FormatTimeNode(template.Node): - def __init__(self, date_to_be_formatted, format_string): - self.date_to_be_formatted = template.Variable(date_to_be_formatted) - self.format_string = format_string - - def render(self, context): - try: - actual_date = self.date_to_be_formatted.resolve(context) - return actual_date.strftime(self.format_string) - except template.VariableDoesNotExist: - return '' - -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`. - -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". + self.cycle_iter = itertools.cycle(cyclevars) -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 `. We'll use the tag like this: + def render(self, context): + return next(self.cycle_iter) -.. code-block:: html+django +But, suppose we have two templates rendering the template snippet from above at +the same time: - {% show_results poll %} +1. Thread 1 performs its first loop iteration, ``CycleNode.render()`` + returns 'row1' +2. Thread 2 performs its first loop iteration, ``CycleNode.render()`` + returns 'row2' +3. Thread 1 performs its second loop iteration, ``CycleNode.render()`` + returns 'row1' +4. Thread 2 performs its second loop iteration, ``CycleNode.render()`` + returns 'row2' -...and the output will be something like this: +The CycleNode is iterating, but it's iterating globally. As far as Thread 1 +and Thread 2 are concerned, it's always returning the same value. This is +obviously not what we want! -.. code-block:: html +To address this problem, Django provides a ``render_context`` that's associated +with the ``context`` of the template that is currently being rendered. The +``render_context`` behaves like a Python dictionary, and should be used to +store ``Node`` state between invocations of the ``render`` method. -
    -
  • First choice
  • -
  • Second choice
  • -
  • Third choice
  • -
+Let's refactor our ``CycleNode`` implementation to use the ``render_context``:: -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:: + class CycleNode(template.Node): + def __init__(self, cyclevars): + self.cyclevars = cyclevars - def show_results(poll): - choices = poll.choice_set.all() - return {'choices': choices} + def render(self, context): + if self not in context.render_context: + context.render_context[self] = itertools.cycle(self.cyclevars) + cycle_iter = context.render_context[self] + return next(cycle_iter) -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: +Note that it's perfectly safe to store global information that will not change +throughout the life of the ``Node`` as an attribute. In the case of +``CycleNode``, the ``cyclevars`` argument doesn't change after the ``Node`` is +instantiated, so we don't need to put it in the ``render_context``. But state +information that is specific to the template that is currently being rendered, +like the current iteration of the ``CycleNode``, should be stored in the +``render_context``. -.. code-block:: html+django +.. note:: + Notice how we used ``self`` to scope the ``CycleNode`` specific information + within the ``render_context``. There may be multiple ``CycleNodes`` in a + given template, so we need to be careful not to clobber another node's + state information. The easiest way to do this is to always use ``self`` as + the key into ``render_context``. If you're keeping track of several state + variables, make ``render_context[self]`` a dictionary. -
    - {% for choice in choices %} -
  • {{ choice }}
  • - {% endfor %} -
+Registering the tag +~~~~~~~~~~~~~~~~~~~ -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:: +Finally, register the tag with your module's ``Library`` instance, as explained +in "Writing custom template filters" above. Example:: - # Here, register is a django.template.Library instance, as before - register.inclusion_tag('results.html')(show_results) + register.tag('current_time', do_current_time) -Alternatively it is possible to register the inclusion tag using a -:class:`django.template.Template` instance:: +The ``tag()`` method takes two arguments: - from django.template.loader import get_template - t = get_template('results.html') - register.inclusion_tag(t)(show_results) +1. The name of the template tag -- a string. If this is left out, the + name of the compilation function will be used. +2. The compilation function -- a Python function (not the name of the + function as a string). -As always, decorator syntax works as well, so we could have written:: +As with filter registration, it is also possible to use this as a decorator:: - @register.inclusion_tag('results.html') - def show_results(poll): + @register.tag(name="current_time") + def do_current_time(parser, token): ... -...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. + @register.tag + def shout(parser, token): + ... -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:: +If you leave off the ``name`` argument, as in the second example above, Django +will use the function's name as the tag name. - # 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) +Passing template variables to the tag +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -(Note that the first parameter to the function *must* be called ``context``.) +Although you can pass any number of arguments to a template tag using +``token.split_contents()``, the arguments are all unpacked as +string literals. A little more work is required in order to pass dynamic +content (a template variable) to a template tag as an argument. -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: +While the previous examples have formatted the current time into a string and +returned the string, suppose you wanted to pass in a +:class:`~django.db.models.DateTimeField` from an object and have the template +tag format that date-time: .. code-block:: html+django - Jump directly to {{ title }}. +

This post was last updated at {% format_time blog_entry.date_updated "%Y-%m-%d %I:%M %p" %}.

-Then, any time you want to use that custom tag, load its library and call it -without any arguments, like so: +Initially, ``token.split_contents()`` will return three values: -.. code-block:: html+django +1. The tag name ``format_time``. +2. The string ``"blog_entry.date_updated"`` (without the surrounding + quotes). +3. The formatting string ``"%Y-%m-%d %I:%M %p"``. The return value from + ``split_contents()`` will include the leading and trailing quotes for + string literals like this. - {% jump_link %} +Now your tag should begin to look like this:: -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. + from django import template -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. + def do_format_time(parser, token): + try: + # split_contents() knows not to split quoted strings. + tag_name, date_to_be_formatted, format_string = token.split_contents() + except ValueError: + raise template.TemplateSyntaxError("%r tag requires exactly two arguments" % token.contents.split()[0]) + if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")): + raise template.TemplateSyntaxError("%r tag's argument should be in quotes" % tag_name) + return FormatTimeNode(date_to_be_formatted, format_string[1:-1]) -``inclusion_tag`` functions may accept any number of positional or keyword -arguments. For example:: +You also have to change the renderer to retrieve the actual contents of the +``date_updated`` property of the ``blog_entry`` object. This can be +accomplished by using the ``Variable()`` class in ``django.template``. - @register.inclusion_tag('my_template.html') - def my_tag(a, b, *args, **kwargs): - warning = kwargs['warning'] - profile = kwargs['profile'] - ... - return ... +To use the ``Variable`` class, simply instantiate it with the name of the +variable to be resolved, and then call ``variable.resolve(context)``. So, +for example:: -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: + class FormatTimeNode(template.Node): + def __init__(self, date_to_be_formatted, format_string): + self.date_to_be_formatted = template.Variable(date_to_be_formatted) + self.format_string = format_string -.. code-block:: html+django + def render(self, context): + try: + actual_date = self.date_to_be_formatted.resolve(context) + return actual_date.strftime(self.format_string) + except template.VariableDoesNotExist: + return '' - {% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %} +Variable resolution will throw a ``VariableDoesNotExist`` exception if it +cannot resolve the string passed to it in the current context of the page. 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: - -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`, 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 %} -

The time is {{ the_time }}.

- -If your template tag needs to access the current context, you can use the -``takes_context`` argument when registering your tag:: +context-updating template tag, you might want to consider using the +:ref:`assignment tag ` shortcut +we introduced above. - # 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`. - -``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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- cgit v1.3