Commit cd428281 authored by Preston Timmons's avatar Preston Timmons Committed by Tim Graham
Browse files

Fixed #18651 -- Enabled optional assignments for simple_tag().

parent 8adc5903
Loading
Loading
Loading
Loading
+30 −49
Original line number Diff line number Diff line
@@ -61,7 +61,8 @@ from django.apps import apps
from django.template.context import (BaseContext, Context, RequestContext,  # NOQA: imported for backwards compatibility
    ContextPopException)
from django.utils import lru_cache
from django.utils.deprecation import RemovedInDjango20Warning
from django.utils.deprecation import (RemovedInDjango20Warning,
    RemovedInDjango21Warning)
from django.utils.itercompat import is_iterable
from django.utils.text import (smart_split, unescape_string_literal,
    get_text_list)
@@ -1021,9 +1022,9 @@ def token_kwargs(bits, parser, support_legacy=False):
def parse_bits(parser, bits, params, varargs, varkw, defaults,
               takes_context, name):
    """
    Parses bits for template tag helpers (simple_tag, include_tag and
    assignment_tag), in particular by detecting syntax errors and by
    extracting positional and keyword arguments.
    Parses bits for template tag helpers simple_tag and inclusion_tag, in
    particular by detecting syntax errors and by extracting positional and
    keyword arguments.
    """
    if takes_context:
        if params[0] == 'context':
@@ -1099,9 +1100,9 @@ def generic_tag_compiler(parser, token, params, varargs, varkw, defaults,

class TagHelperNode(Node):
    """
    Base class for tag helper nodes such as SimpleNode, InclusionNode and
    AssignmentNode. Manages the positional and keyword arguments to be passed
    to the decorated function.
    Base class for tag helper nodes such as SimpleNode and InclusionNode.
    Manages the positional and keyword arguments to be passed to the decorated
    function.
    """

    def __init__(self, takes_context, args, kwargs):
@@ -1191,71 +1192,51 @@ class Library(object):
            params, varargs, varkw, defaults = getargspec(func)

            class SimpleNode(TagHelperNode):

                def render(self, context):
                    resolved_args, resolved_kwargs = self.get_resolved_arguments(context)
                    return func(*resolved_args, **resolved_kwargs)

            function_name = (name or
                getattr(func, '_decorated_function', func).__name__)
            compile_func = partial(generic_tag_compiler,
                params=params, varargs=varargs, varkw=varkw,
                defaults=defaults, name=function_name,
                takes_context=takes_context, node_class=SimpleNode)
            compile_func.__doc__ = func.__doc__
            self.tag(function_name, compile_func)
            return func

        if func is None:
            # @register.simple_tag(...)
            return dec
        elif callable(func):
            # @register.simple_tag
            return dec(func)
        else:
            raise TemplateSyntaxError("Invalid arguments provided to simple_tag")

    def assignment_tag(self, func=None, takes_context=None, name=None):
        def dec(func):
            params, varargs, varkw, defaults = getargspec(func)

            class AssignmentNode(TagHelperNode):
                def __init__(self, takes_context, args, kwargs, target_var):
                    super(AssignmentNode, self).__init__(takes_context, args, kwargs)
                    super(SimpleNode, self).__init__(takes_context, args, kwargs)
                    self.target_var = target_var

                def render(self, context):
                    resolved_args, resolved_kwargs = self.get_resolved_arguments(context)
                    context[self.target_var] = func(*resolved_args, **resolved_kwargs)
                    output = func(*resolved_args, **resolved_kwargs)
                    if self.target_var is not None:
                        context[self.target_var] = output
                        return ''
                    return output

            function_name = (name or
                getattr(func, '_decorated_function', func).__name__)

            def compile_func(parser, token):
                bits = token.split_contents()[1:]
                if len(bits) < 2 or bits[-2] != 'as':
                    raise TemplateSyntaxError(
                        "'%s' tag takes at least 2 arguments and the "
                        "second last argument must be 'as'" % function_name)
                target_var = None
                if len(bits) >= 2 and bits[-2] == 'as':
                    target_var = bits[-1]
                    bits = bits[:-2]
                args, kwargs = parse_bits(parser, bits, params,
                    varargs, varkw, defaults, takes_context, function_name)
                return AssignmentNode(takes_context, args, kwargs, target_var)
                return SimpleNode(takes_context, args, kwargs, target_var)

            compile_func.__doc__ = func.__doc__
            self.tag(function_name, compile_func)
            return func

        if func is None:
            # @register.assignment_tag(...)
            # @register.simple_tag(...)
            return dec
        elif callable(func):
            # @register.assignment_tag
            # @register.simple_tag
            return dec(func)
        else:
            raise TemplateSyntaxError("Invalid arguments provided to assignment_tag")
            raise TemplateSyntaxError("Invalid arguments provided to simple_tag")

    def assignment_tag(self, func=None, takes_context=None, name=None):
        warnings.warn(
            "assignment_tag() is deprecated. Use simple_tag() instead",
            RemovedInDjango21Warning,
            stacklevel=2,
        )
        return self.simple_tag(func, takes_context, name)

    def inclusion_tag(self, file_name, takes_context=False, name=None):
        def dec(func):
+20 −36
Original line number Diff line number Diff line
@@ -388,7 +388,7 @@ Simple tags
.. method:: django.template.Library.simple_tag()

Many template tags take a number of arguments -- strings or template variables
-- and return a result 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.
@@ -459,6 +459,18 @@ positional arguments. For example:

    {% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %}

.. versionadded:: 1.9

It's possible to store the tag results in a template variable rather than
directly outputting it. This is done by using the ``as`` argument followed by
the variable name. Doing so enables you to output the content 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>

.. _howto-custom-template-tags-inclusion-tags:

Inclusion tags
@@ -602,11 +614,15 @@ Assignment tags

.. method:: django.template.Library.assignment_tag()

.. deprecated:: 1.9

    ``simple_tag`` can now store results in a template variable and should
    be used instead.

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.
:meth:`~django.template.Library.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::

@@ -622,38 +638,6 @@ followed by the variable name, and output it yourself where you see fit:
    {% 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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

+2 −0
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@ details on these changes.

* The ``django.forms.extras`` package will be removed.

* The ``assignment_tag`` helper will be removed.

.. _deprecation-removed-in-2.0:

2.0
+12 −1
Original line number Diff line number Diff line
@@ -137,7 +137,9 @@ Signals
Templates
^^^^^^^^^

* ...
* Template tags created with the :meth:`~django.template.Library.simple_tag`
  helper can now store results in a template variable by using the ``as``
  argument.

Requests and Responses
^^^^^^^^^^^^^^^^^^^^^^
@@ -194,6 +196,15 @@ Miscellaneous
Features deprecated in 1.9
==========================

``assignment_tag()``
~~~~~~~~~~~~~~~~~~~~

Django 1.4 added the ``assignment_tag`` helper to ease the creation of
template tags that store results in a template variable. The
:meth:`~django.template.Library.simple_tag` helper has gained this same
ability, making the ``assignment_tag`` obsolete. Tags that use
``assignment_tag`` should be updated to use ``simple_tag``.

Miscellaneous
~~~~~~~~~~~~~

+13 −78
Original line number Diff line number Diff line
import operator
import warnings

from django import template
from django.template.defaultfilters import stringfilter
@@ -125,81 +126,15 @@ def minustwo_overridden_name(value):
register.simple_tag(lambda x: x - 1, name='minusone')


with warnings.catch_warnings():
    warnings.simplefilter('ignore')

    @register.assignment_tag
    def assignment_no_params():
        """Expected assignment_no_params __doc__"""
        return "assignment_no_params - Expected result"
    assignment_no_params.anything = "Expected assignment_no_params __dict__"


@register.assignment_tag
def assignment_one_param(arg):
    """Expected assignment_one_param __doc__"""
    return "assignment_one_param - Expected result: %s" % arg
assignment_one_param.anything = "Expected assignment_one_param __dict__"


@register.assignment_tag(takes_context=False)
def assignment_explicit_no_context(arg):
    """Expected assignment_explicit_no_context __doc__"""
    return "assignment_explicit_no_context - Expected result: %s" % arg
assignment_explicit_no_context.anything = "Expected assignment_explicit_no_context __dict__"


@register.assignment_tag(takes_context=True)
def assignment_no_params_with_context(context):
    """Expected assignment_no_params_with_context __doc__"""
    return "assignment_no_params_with_context - Expected result (context value: %s)" % context['value']
assignment_no_params_with_context.anything = "Expected assignment_no_params_with_context __dict__"


@register.assignment_tag(takes_context=True)
def assignment_params_and_context(context, arg):
    """Expected assignment_params_and_context __doc__"""
    return "assignment_params_and_context - Expected result (context value: %s): %s" % (context['value'], arg)
assignment_params_and_context.anything = "Expected assignment_params_and_context __dict__"


@register.assignment_tag
def assignment_two_params(one, two):
    """Expected assignment_two_params __doc__"""
    return "assignment_two_params - Expected result: %s, %s" % (one, two)
assignment_two_params.anything = "Expected assignment_two_params __dict__"


@register.assignment_tag
def assignment_one_default(one, two='hi'):
    """Expected assignment_one_default __doc__"""
    return "assignment_one_default - Expected result: %s, %s" % (one, two)
assignment_one_default.anything = "Expected assignment_one_default __dict__"


@register.assignment_tag
def assignment_unlimited_args(one, two='hi', *args):
    """Expected assignment_unlimited_args __doc__"""
    return "assignment_unlimited_args - Expected result: %s" % (', '.join(six.text_type(arg) for arg in [one, two] + list(args)))
assignment_unlimited_args.anything = "Expected assignment_unlimited_args __dict__"


@register.assignment_tag
def assignment_only_unlimited_args(*args):
    """Expected assignment_only_unlimited_args __doc__"""
    return "assignment_only_unlimited_args - Expected result: %s" % ', '.join(six.text_type(arg) for arg in args)
assignment_only_unlimited_args.anything = "Expected assignment_only_unlimited_args __dict__"


@register.assignment_tag
def assignment_unlimited_args_kwargs(one, two='hi', *args, **kwargs):
    """Expected assignment_unlimited_args_kwargs __doc__"""
    # Sort the dictionary by key to guarantee the order for testing.
    sorted_kwarg = sorted(six.iteritems(kwargs), key=operator.itemgetter(0))
    return "assignment_unlimited_args_kwargs - Expected result: %s / %s" % (
        ', '.join(six.text_type(arg) for arg in [one, two] + list(args)),
        ', '.join('%s=%s' % (k, v) for (k, v) in sorted_kwarg)
    )
assignment_unlimited_args_kwargs.anything = "Expected assignment_unlimited_args_kwargs __dict__"


    @register.assignment_tag(takes_context=True)
    def assignment_tag_without_context_parameter(arg):
        """Expected assignment_tag_without_context_parameter __doc__"""
Loading