Ticket #14262: 14262.assignment_tag.diff

File 14262.assignment_tag.diff, 13.6 KB (added by julien, 4 years ago)
  • django/template/base.py

    diff --git a/django/template/base.py b/django/template/base.py
    index 08ff5c6..ed1ae86 100644
    a b class Library(object): 
    901901        else:
    902902            raise TemplateSyntaxError("Invalid arguments provided to simple_tag")
    903903
     904    def assignment_tag(self, func=None, takes_context=None):
     905        def dec(func):
     906            params, xx, xxx, defaults = getargspec(func)
     907            if takes_context:
     908                if params[0] == 'context':
     909                    params = params[1:]
     910                else:
     911                    raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'")
     912
     913            class AssignmentNode(Node):
     914                def __init__(self, params_vars, target_var):
     915                    self.params_vars = map(Variable, params_vars)
     916                    self.target_var = target_var
     917
     918                def render(self, context):
     919                    resolved_vars = [var.resolve(context) for var in self.params_vars]
     920                    if takes_context:
     921                        func_args = [context] + resolved_vars
     922                    else:
     923                        func_args = resolved_vars
     924                    context[self.target_var] = func(*func_args)
     925                    return ''
     926
     927            def compile_func(parser, token):
     928                bits = token.split_contents()
     929                tag_name = bits[0]
     930                bits = bits[1:]
     931                params_max = len(params)
     932                defaults_length = defaults and len(defaults) or 0
     933                params_min = params_max - defaults_length
     934                if (len(bits) < 2 or bits[-2] != 'as'):
     935                    raise TemplateSyntaxError("'%s' tag takes at least 2 arguments and the second last argument must be 'as'" % tag_name)
     936                params_vars = bits[:-2]
     937                target_var = bits[-1]
     938                if (len(params_vars) < params_min or len(params_vars) > params_max):
     939                    if params_min == params_max:
     940                        raise TemplateSyntaxError("%s takes %s arguments" % (tag_name, params_min))
     941                    else:
     942                        raise TemplateSyntaxError("%s takes between %s and %s arguments" % (tag_name, params_min, params_max))
     943                return AssignmentNode(params_vars, target_var)
     944           
     945            compile_func.__doc__ = func.__doc__
     946            self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
     947            return func
     948
     949        if func is None:
     950            # @register.assignment_tag(...)
     951            return dec
     952        elif callable(func):
     953            # @register.assignment_tag
     954            return dec(func)
     955        else:
     956            raise TemplateSyntaxError("Invalid arguments provided to assignment_tag")
     957       
    904958    def inclusion_tag(self, file_name, context_class=Context, takes_context=False):
    905959        def dec(func):
    906960            params, xx, xxx, defaults = getargspec(func)
  • docs/howto/custom-template-tags.txt

    diff --git a/docs/howto/custom-template-tags.txt b/docs/howto/custom-template-tags.txt
    index 7dc6cce..770e823 100644
    a b for example:: 
    624624Variable resolution will throw a ``VariableDoesNotExist`` exception if it cannot
    625625resolve the string passed to it in the current context of the page.
    626626
     627.. _howto-custom-template-tags-simple-tags:
     628
    627629Shortcut for simple tags
    628630~~~~~~~~~~~~~~~~~~~~~~~~
    629631
    630 Many template tags take a number of arguments -- strings or a template variables
     632Many template tags take a number of arguments -- strings or template variables
    631633-- and return a string after doing some processing based solely on
    632634the input argument and some external information. For example, the
    633635``current_time`` tag we wrote above is of this variety: we give it a format
    634636string, it returns the time as a string.
    635637
    636 To ease the creation of the types of tags, Django provides a helper function,
     638To ease the creation of this type of tags, Django provides a helper function,
    637639``simple_tag``. This function, which is a method of
    638640``django.template.Library``, takes a function that accepts any number of
    639641arguments, wraps it in a ``render`` function and the other necessary bits
    Or, using decorator syntax:: 
    681683        return your_get_current_time_method(timezone, format_string)
    682684
    683685For more information on how the ``takes_context`` option works, see the section
    684 on `inclusion tags`_.
     686on :ref:`inclusion tags<howto-custom-template-tags-inclusion-tags>`.
     687
     688.. _howto-custom-template-tags-assignment-tags:
     689
     690Assignment tags
     691~~~~~~~~~~~~~~~
     692
     693.. versionadded:: 1.4
     694
     695Another common type of template tag is the type that fetches some data and
     696stores it in a context variable. To ease the creation of this type of tags,
     697Django provides a helper function, ``assignment_tag``. This function works
     698the same way as :ref:`simple_tag<howto-custom-template-tags-simple-tags>`,
     699except that it stores the tag's result in a specified context variable instead
     700of directly outputting it.
     701
     702Our earlier ``current_time`` function could thus be written like this::
     703
     704.. code-block:: python
     705
     706    def get_current_time(format_string):
     707        return datetime.datetime.now().strftime(format_string)
     708
     709    register.assignment_tag(get_current_time)
     710
     711The decorator syntax also works::
     712
     713.. code-block:: python
     714
     715    @register.assignment_tag
     716    def get_current_time(format_string):
     717        ...
     718
     719You may then store the result in a template variable using the ``as`` argument
     720followed by the variable name, and output it yourself where you see fit::
     721
     722.. code-block:: html+django
     723
     724    {% get_current_time "%Y-%m-%d %I:%M %p" as the_time %}
     725    <p>The time is {{ the_time }}.</p>
     726
     727If your template tag needs to access the current context, you can use the
     728``takes_context`` argument when registering your tag::
     729
     730    # The first argument *must* be called "context" here.
     731    def get_current_time(context, format_string):
     732        timezone = context['timezone']
     733        return your_get_current_time_method(timezone, format_string)
     734
     735    register.assignment_tag(takes_context=True)(get_current_time)
     736
     737Or, using decorator syntax::
     738
     739    @register.assignment_tag(takes_context=True)
     740    def get_current_time(context, format_string):
     741        timezone = context['timezone']
     742        return your_get_current_time_method(timezone, format_string)
     743
     744For more information on how the ``takes_context`` option works, see the section
     745on :ref:`inclusion tags<howto-custom-template-tags-inclusion-tags>`.
    685746
    686747.. _howto-custom-template-tags-inclusion-tags:
    687748
  • docs/releases/1.4.txt

    diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt
    index a63fff7..162b493 100644
    a b compatibility with old browsers, this change means that you can use any HTML5 
    3737features you need in admin pages without having to lose HTML validity or
    3838override the provided templates to change the doctype.
    3939
     40Assignment template tags
     41~~~~~~~~~~~~~~~~~~~~~~~~
     42
     43A new helper function,
     44:ref:`assignment_tag<howto-custom-template-tags-assignment-tags>`, was added to
     45``template.Library`` to ease the creation of template tags that store some
     46data in a specified context variable.
     47
    4048.. _backwards-incompatible-changes-1.4:
    4149
    4250Backwards incompatible changes in 1.4
  • tests/regressiontests/templates/custom.py

    diff --git a/tests/regressiontests/templates/custom.py b/tests/regressiontests/templates/custom.py
    index fe5b095..6e7f842 100644
    a b  
     1from __future__ import with_statement
     2
    13from django import template
    24from django.utils.unittest import TestCase
    35from templatetags import custom
    class CustomTagTests(TestCase): 
    7880        self.verify_tag(custom.inclusion_explicit_no_context, 'inclusion_explicit_no_context')
    7981        self.verify_tag(custom.inclusion_no_params_with_context, 'inclusion_no_params_with_context')
    8082        self.verify_tag(custom.inclusion_params_and_context, 'inclusion_params_and_context')
     83       
     84    def test_assignment_tags(self):
     85        c = template.Context({'value': 42})
     86
     87        t = template.Template('{% load custom %}{% assignment_no_params as var %}The result is: {{ var }}')
     88        self.assertEqual(t.render(c), u'The result is: assignment_no_params - Expected result')
     89
     90        t = template.Template('{% load custom %}{% assignment_one_param 37 as var %}The result is: {{ var }}')
     91        self.assertEqual(t.render(c), u'The result is: assignment_one_param - Expected result: 37')
     92
     93        t = template.Template('{% load custom %}{% assignment_explicit_no_context 37 as var %}The result is: {{ var }}')
     94        self.assertEqual(t.render(c), u'The result is: assignment_explicit_no_context - Expected result: 37')
     95
     96        t = template.Template('{% load custom %}{% assignment_no_params_with_context as var %}The result is: {{ var }}')
     97        self.assertEqual(t.render(c), u'The result is: assignment_no_params_with_context - Expected result (context value: 42)')
     98
     99        t = template.Template('{% load custom %}{% assignment_params_and_context 37 as var %}The result is: {{ var }}')
     100        self.assertEqual(t.render(c), u'The result is: assignment_params_and_context - Expected result (context value: 42): 37')
     101
     102        with self.assertRaises(template.TemplateSyntaxError) as context_manager:
     103            template.Template('{% load custom %}{% assignment_one_param 37 %}The result is: {{ var }}')
     104        self.assertEqual(context_manager.exception.message, "'assignment_one_param' tag takes at least 2 arguments and the second last argument must be 'as'")
     105       
     106        with self.assertRaises(template.TemplateSyntaxError) as context_manager:
     107            template.Template('{% load custom %}{% assignment_one_param 37 as %}The result is: {{ var }}')
     108        self.assertEqual(context_manager.exception.message, "'assignment_one_param' tag takes at least 2 arguments and the second last argument must be 'as'")
     109       
     110        with self.assertRaises(template.TemplateSyntaxError) as context_manager:
     111            template.Template('{% load custom %}{% assignment_one_param 37 ass var %}The result is: {{ var }}')
     112        self.assertEqual(context_manager.exception.message, "'assignment_one_param' tag takes at least 2 arguments and the second last argument must be 'as'")
     113       
     114    def test_assignment_tag_registration(self):
     115        # Test that the decorators preserve the decorated function's docstring, name and attributes.
     116        self.verify_tag(custom.assignment_no_params, 'assignment_no_params')
     117        self.verify_tag(custom.assignment_one_param, 'assignment_one_param')
     118        self.verify_tag(custom.assignment_explicit_no_context, 'assignment_explicit_no_context')
     119        self.verify_tag(custom.assignment_no_params_with_context, 'assignment_no_params_with_context')
     120        self.verify_tag(custom.assignment_params_and_context, 'assignment_params_and_context')
     121
     122    def test_assignment_tag_missing_context(self):
     123        # That the 'context' parameter must be present when takes_context is True
     124        def an_assignment_tag_without_parameters(arg):
     125            """Expected __doc__"""
     126            return "Expected result"
     127
     128        register = template.Library()
     129        decorator = register.assignment_tag(takes_context=True)
     130       
     131        with self.assertRaises(template.TemplateSyntaxError) as context_manager:
     132            decorator(an_assignment_tag_without_parameters)
     133        self.assertEqual(context_manager.exception.message, "Any tag function decorated with takes_context=True must have a first argument of 'context'")
  • tests/regressiontests/templates/templatetags/custom.py

    diff --git a/tests/regressiontests/templates/templatetags/custom.py b/tests/regressiontests/templates/templatetags/custom.py
    index b2e8a16..f27890e 100644
    a b def inclusion_params_and_context(context, arg): 
    6969    return {"result" : "inclusion_params_and_context - Expected result (context value: %s): %s" % (context['value'], arg)}
    7070inclusion_params_and_context.anything = "Expected inclusion_params_and_context __dict__"
    7171
     72@register.assignment_tag
     73def assignment_no_params():
     74    """Expected assignment_no_params __doc__"""
     75    return "assignment_no_params - Expected result"
     76assignment_no_params.anything = "Expected assignment_no_params __dict__"
     77
     78@register.assignment_tag
     79def assignment_one_param(arg):
     80    """Expected assignment_one_param __doc__"""
     81    return "assignment_one_param - Expected result: %s" % arg
     82assignment_one_param.anything = "Expected assignment_one_param __dict__"
     83
     84@register.assignment_tag(takes_context=False)
     85def assignment_explicit_no_context(arg):
     86    """Expected assignment_explicit_no_context __doc__"""
     87    return "assignment_explicit_no_context - Expected result: %s" % arg
     88assignment_explicit_no_context.anything = "Expected assignment_explicit_no_context __dict__"
     89
     90@register.assignment_tag(takes_context=True)
     91def assignment_no_params_with_context(context):
     92    """Expected assignment_no_params_with_context __doc__"""
     93    return "assignment_no_params_with_context - Expected result (context value: %s)" % context['value']
     94assignment_no_params_with_context.anything = "Expected assignment_no_params_with_context __dict__"
     95
     96@register.assignment_tag(takes_context=True)
     97def assignment_params_and_context(context, arg):
     98    """Expected assignment_params_and_context __doc__"""
     99    return "assignment_params_and_context - Expected result (context value: %s): %s" % (context['value'], arg)
     100assignment_params_and_context.anything = "Expected assignment_params_and_context __dict__"
Back to Top