Ticket #14262: 14262.assignment_tag.2.diff

File 14262.assignment_tag.2.diff, 13.3 KB (added by julien, 4 years ago)

Updated patch to current trunk

  • django/template/base.py

    diff --git a/django/template/base.py b/django/template/base.py
    index b6470f3..37b6fab 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 e86e070..41da2b5 100644
    a b override the provided templates to change the doctype. 
    4343A lazily evaluated version of :func:`django.core.urlresolvers.reverse` was
    4444added to allow using URL reversals before the project's URLConf gets loaded.
    4545
     46Assignment template tags
     47~~~~~~~~~~~~~~~~~~~~~~~~
     48
     49A new helper function,
     50:ref:`assignment_tag<howto-custom-template-tags-assignment-tags>`, was added to
     51``template.Library`` to ease the creation of template tags that store some
     52data in a specified context variable.
     53
    4654.. _backwards-incompatible-changes-1.4:
    4755
    4856Backwards 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 7ca359d..5453657 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): 
    102104
    103105        c.use_l10n = True
    104106        self.assertEquals(t.render(c).strip(), u'True')
     107
     108    def test_assignment_tags(self):
     109        c = template.Context({'value': 42})
     110
     111        t = template.Template('{% load custom %}{% assignment_no_params as var %}The result is: {{ var }}')
     112        self.assertEqual(t.render(c), u'The result is: assignment_no_params - Expected result')
     113
     114        t = template.Template('{% load custom %}{% assignment_one_param 37 as var %}The result is: {{ var }}')
     115        self.assertEqual(t.render(c), u'The result is: assignment_one_param - Expected result: 37')
     116
     117        t = template.Template('{% load custom %}{% assignment_explicit_no_context 37 as var %}The result is: {{ var }}')
     118        self.assertEqual(t.render(c), u'The result is: assignment_explicit_no_context - Expected result: 37')
     119
     120        t = template.Template('{% load custom %}{% assignment_no_params_with_context as var %}The result is: {{ var }}')
     121        self.assertEqual(t.render(c), u'The result is: assignment_no_params_with_context - Expected result (context value: 42)')
     122
     123        t = template.Template('{% load custom %}{% assignment_params_and_context 37 as var %}The result is: {{ var }}')
     124        self.assertEqual(t.render(c), u'The result is: assignment_params_and_context - Expected result (context value: 42): 37')
     125
     126        with self.assertRaises(template.TemplateSyntaxError) as context_manager:
     127            template.Template('{% load custom %}{% assignment_one_param 37 %}The result is: {{ var }}')
     128        self.assertEqual(context_manager.exception.message, "'assignment_one_param' tag takes at least 2 arguments and the second last argument must be 'as'")
     129       
     130        with self.assertRaises(template.TemplateSyntaxError) as context_manager:
     131            template.Template('{% load custom %}{% assignment_one_param 37 as %}The result is: {{ var }}')
     132        self.assertEqual(context_manager.exception.message, "'assignment_one_param' tag takes at least 2 arguments and the second last argument must be 'as'")
     133       
     134        with self.assertRaises(template.TemplateSyntaxError) as context_manager:
     135            template.Template('{% load custom %}{% assignment_one_param 37 ass var %}The result is: {{ var }}')
     136        self.assertEqual(context_manager.exception.message, "'assignment_one_param' tag takes at least 2 arguments and the second last argument must be 'as'")
     137       
     138    def test_assignment_tag_registration(self):
     139        # Test that the decorators preserve the decorated function's docstring, name and attributes.
     140        self.verify_tag(custom.assignment_no_params, 'assignment_no_params')
     141        self.verify_tag(custom.assignment_one_param, 'assignment_one_param')
     142        self.verify_tag(custom.assignment_explicit_no_context, 'assignment_explicit_no_context')
     143        self.verify_tag(custom.assignment_no_params_with_context, 'assignment_no_params_with_context')
     144        self.verify_tag(custom.assignment_params_and_context, 'assignment_params_and_context')
     145
     146    def test_assignment_tag_missing_context(self):
     147        # That the 'context' parameter must be present when takes_context is True
     148        def an_assignment_tag_without_parameters(arg):
     149            """Expected __doc__"""
     150            return "Expected result"
     151
     152        register = template.Library()
     153        decorator = register.assignment_tag(takes_context=True)
     154       
     155        with self.assertRaises(template.TemplateSyntaxError) as context_manager:
     156            decorator(an_assignment_tag_without_parameters)
     157        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 8db113a..2e281f7 100644
    a b def use_l10n(context): 
    8484@register.inclusion_tag('test_incl_tag_use_l10n.html', takes_context=True)
    8585def inclusion_tag_use_l10n(context):
    8686    return {}
     87
     88@register.assignment_tag
     89def assignment_no_params():
     90    """Expected assignment_no_params __doc__"""
     91    return "assignment_no_params - Expected result"
     92assignment_no_params.anything = "Expected assignment_no_params __dict__"
     93
     94@register.assignment_tag
     95def assignment_one_param(arg):
     96    """Expected assignment_one_param __doc__"""
     97    return "assignment_one_param - Expected result: %s" % arg
     98assignment_one_param.anything = "Expected assignment_one_param __dict__"
     99
     100@register.assignment_tag(takes_context=False)
     101def assignment_explicit_no_context(arg):
     102    """Expected assignment_explicit_no_context __doc__"""
     103    return "assignment_explicit_no_context - Expected result: %s" % arg
     104assignment_explicit_no_context.anything = "Expected assignment_explicit_no_context __dict__"
     105
     106@register.assignment_tag(takes_context=True)
     107def assignment_no_params_with_context(context):
     108    """Expected assignment_no_params_with_context __doc__"""
     109    return "assignment_no_params_with_context - Expected result (context value: %s)" % context['value']
     110assignment_no_params_with_context.anything = "Expected assignment_no_params_with_context __dict__"
     111
     112@register.assignment_tag(takes_context=True)
     113def assignment_params_and_context(context, arg):
     114    """Expected assignment_params_and_context __doc__"""
     115    return "assignment_params_and_context - Expected result (context value: %s): %s" % (context['value'], arg)
     116assignment_params_and_context.anything = "Expected assignment_params_and_context __dict__"
Back to Top