Ticket #1105: improved_simple_tag.r7818.diff

File improved_simple_tag.r7818.diff, 11.7 KB (added by julien, 7 years ago)

Replaced "takes_block" by "takes_nodelist", and "block_nodelist" by "nodelist". Amended the doc and tests as well.

  • django/django/template/__init__.py

     
    792792        else:
    793793            return force_unicode(output)
    794794
    795 def generic_tag_compiler(params, defaults, name, node_class, parser, token):
     795def generic_tag_compiler(params, defaults, name, node_class, parser, token, takes_context=False, takes_nodelist=False):
    796796    "Returns a template.Node subclass."
    797797    bits = token.split_contents()[1:]
    798798    bmax = len(params)
     
    804804        else:
    805805            message = "%s takes between %s and %s arguments" % (name, bmin, bmax)
    806806        raise TemplateSyntaxError(message)
     807    if takes_context:
     808        node_class = curry(node_class, takes_context=takes_context)
     809    if takes_nodelist:
     810        nodelist = parser.parse(('end%s' % name,))
     811        parser.delete_first_token()
     812        node_class = curry(node_class, nodelist=nodelist)
    807813    return node_class(bits)
    808814
    809815class Library(object):
     
    859865        self.filters[getattr(func, "_decorated_function", func).__name__] = func
    860866        return func
    861867
    862     def simple_tag(self,func):
    863         params, xx, xxx, defaults = getargspec(func)
     868    def simple_tag(self, compile_function=None, takes_context=None, takes_nodelist=None):
     869        def dec(func):
     870            params, xx, xxx, defaults = getargspec(func)
     871            if takes_context and takes_nodelist:
     872                if params[0] == 'context' and params[1] == 'nodelist':
     873                    params = params[2:]
     874                else:
     875                    raise TemplateSyntaxError("Any tag function decorated both with takes_context=True and with takes_nodelist=True must have a first argument of 'context', and a second argument of 'nodelist'")
     876            elif takes_nodelist:
     877                if params[0] == 'nodelist':
     878                    params = params[1:]
     879                else:
     880                    raise TemplateSyntaxError("Any tag function decorated with takes_nodelist=True must have a first argument of 'nodelist'")
     881            elif takes_context:
     882                if params[0] == 'context':
     883                    params = params[1:]
     884                else:
     885                    raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'")
    864886
    865         class SimpleNode(Node):
    866             def __init__(self, vars_to_resolve):
    867                 self.vars_to_resolve = map(Variable, vars_to_resolve)
     887            class SimpleNode(Node):
     888                def __init__(self, vars_to_resolve, takes_context=False, nodelist=None):
     889                    self.vars_to_resolve = map(Variable, vars_to_resolve)
     890                    self.takes_context = takes_context
     891                    if nodelist is not None:
     892                        # Only save the 'nodelist' attribute if it's not None, so that it is picked by the Node.get_nodes_by_type() method.
     893                        self.nodelist = nodelist
    868894
    869             def render(self, context):
    870                 resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]
    871                 return func(*resolved_vars)
     895                def render(self, context):
     896                    resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]
     897                    if self.takes_context and hasattr(self, 'nodelist'):
     898                        func_args = [context, self.nodelist] + resolved_vars
     899                    elif hasattr(self, 'nodelist'):
     900                        func_args = [self.nodelist] + resolved_vars
     901                    elif self.takes_context:
     902                        func_args = [context] + resolved_vars
     903                    else:
     904                        func_args = resolved_vars
     905                    return func(*func_args)
    872906
    873         compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode)
    874         compile_func.__doc__ = func.__doc__
    875         self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
    876         return func
     907            compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode, takes_nodelist=takes_nodelist, takes_context=takes_context)
     908            compile_func.__doc__ = func.__doc__
     909            self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
     910            return func
     911       
     912        if takes_context is not None or takes_nodelist is not None:
     913            # Examples: @register.simple_tag(takes_context=True) or @register.simple_tag(takes_context=True, takes_nodelist=True)
     914            return dec
     915        elif compile_function is None:
     916            # @register.simple_tag()
     917            return dec
     918        elif callable(compile_function):
     919            # @register.simple_tag
     920            return dec(compile_function)
     921        else:
     922            raise TemplateSyntaxError("Incorrect parameters for the simple_tag decorator.")
    877923
    878924    def inclusion_tag(self, file_name, context_class=Context, takes_context=False):
    879925        def dec(func):
  • django/tests/regressiontests/templates/tests.py

     
    1717from django.utils.safestring import mark_safe
    1818from django.utils.tzinfo import LocalTimezone
    1919
     20from decorators import DecoratorsTest
    2021from unicode import unicode_tests
    2122from context import context_tests
    2223
  • django/tests/regressiontests/templates/decorators.py

     
     1from unittest import TestCase
     2from unittest import TestCase
     3from sys import version_info
     4
     5from django import template
     6
     7register = template.Library()
     8
     9# Very simple tag, with no parameters.
     10def a_simple_tag_without_parameters(arg):
     11    """Expected __doc__"""
     12    return "Expected result"
     13a_simple_tag_without_parameters.anything = "Expected __dict__"
     14
     15# Tag that takes the context.
     16def a_simple_tag_with_context(context, arg):
     17    """Expected __doc__"""
     18    return "Expected result"
     19a_simple_tag_with_context.anything = "Expected __dict__"
     20
     21# Tag that takes the inner block's node list.
     22def a_simple_tag_with_nodelist(nodelist, arg):
     23    """Expected __doc__"""
     24    return "Expected result"
     25a_simple_tag_with_nodelist.anything = "Expected __dict__"
     26
     27# Tag that takes both the context and the inner block's node list.
     28def a_simple_tag_with_context_and_nodelist(context, nodelist, arg):
     29    """Expected __doc__"""
     30    return "Expected result"
     31a_simple_tag_with_context_and_nodelist.anything = "Expected __dict__"
     32
     33# Tag that *wants* to take both the context and the inner block's node list, but that has arguments in wrong order.
     34def a_simple_tag_with_context_and_nodelist_wrong_order(nodelist, context, arg):
     35    """Expected __doc__"""
     36    return "Expected result"
     37a_simple_tag_with_context_and_nodelist_wrong_order.anything = "Expected __dict__"
     38
     39
     40
     41
     42class DecoratorsTest(TestCase):
     43    def verify_decorator(self, decorator, func_name):
     44        # Only check __name__ on Python 2.4 or later since __name__ can't be
     45        # assigned to in earlier Python versions.
     46        if version_info[0] >= 2 and version_info[1] >= 4:
     47            self.assertEquals(decorator.__name__, func_name)
     48        self.assertEquals(decorator.__doc__, 'Expected __doc__')
     49        self.assertEquals(decorator.__dict__['anything'], 'Expected __dict__')
     50
     51    def test_simple_tag(self):
     52        # Test that the decorators preserve the decorated function's docstring, name and attributes.
     53        decorator = register.simple_tag(a_simple_tag_without_parameters)
     54        self.verify_decorator(decorator, 'a_simple_tag_without_parameters')
     55       
     56        decorator = register.simple_tag(takes_context=True)(a_simple_tag_with_context)
     57        self.verify_decorator(decorator, 'a_simple_tag_with_context')
     58       
     59        decorator = register.simple_tag(takes_nodelist=True)(a_simple_tag_with_nodelist)
     60        self.verify_decorator(decorator, 'a_simple_tag_with_nodelist')
     61       
     62        decorator = register.simple_tag(takes_context=True, takes_nodelist=True)(a_simple_tag_with_context_and_nodelist)
     63        self.verify_decorator(decorator, 'a_simple_tag_with_context_and_nodelist')
     64       
     65        # Now test that 'context' and 'nodelist' arguments and their order are correct.
     66        decorator = register.simple_tag(takes_context=True)
     67        self.assertRaises(template.TemplateSyntaxError, decorator, a_simple_tag_without_parameters)
     68       
     69        decorator = register.simple_tag(takes_nodelist=True)
     70        self.assertRaises(template.TemplateSyntaxError, decorator, a_simple_tag_without_parameters)
     71       
     72        decorator = register.simple_tag(takes_nodelist=True, takes_context=True)
     73        self.assertRaises(template.TemplateSyntaxError, decorator, a_simple_tag_with_context_and_nodelist_wrong_order)
  • django/docs/templates_python.txt

     
    11811181    * If the argument was a template variable, our function is passed the
    11821182      current value of the variable, not the variable itself.
    11831183
    1184 When your template tag does not need access to the current context, writing a
    1185 function to work with the input values and using the ``simple_tag`` helper is
    1186 the easiest way to create a new tag.
     1184If your template tag needs to access the current context, you can use the
     1185``takes_context`` option as follows::
    11871186
     1187    # The first argument *must* be called "context" here.
     1188    def current_time(context, format_string):
     1189        timezone = context['timezone']
     1190        ...
     1191
     1192    register.simple_tag(takes_context=True)(current_time)
     1193
     1194You can also use the decorator syntax if running in Python 2.4::
     1195
     1196    @register.simple_tag(takes_context=True)
     1197    def current_time(context, format_string):
     1198        ...
     1199
     1200For more information on how the ``takes_context`` option works, see the section
     1201on `inclusion tags`_.
     1202
     1203If your template is a simple block tag, you can use the ``takes_nodelist`` option as
     1204follows::
     1205
     1206    # The first argument *must* be called "nodelist".
     1207    def my_bock_tag(nodelist, an_argument):
     1208        ...
     1209    register.simple_tag(takes_nodelist=True)(my_bock_tag)
     1210
     1211In the above example, ``nodelist`` is a list of all nodes between
     1212``{% my_bock_tag %}`` and ``{% endmy_bock_tag %}``, not counting
     1213``{% my_bock_tag %}`` and ``{% endmy_bock_tag %}`` themselves.
     1214
     1215It is also possible to use both ``takes_context`` and ``takes_nodelist`` at the
     1216same time. For example::
     1217
     1218    # The first argument *must* be called "context" and the second one "nodelist".
     1219    def my_bock_tag(context, nodelist, an_argument):
     1220        timezone = context['timezone']
     1221        content = nodelist.render(context)
     1222        ...
     1223    register.simple_tag(takes_context=True, takes_nodelist=True)(my_bock_tag)
     1224
     1225If you need to create a more complex block tag, refer to the section on
     1226`parsing until another block tag`_.
     1227
     1228_inclusion tags: #inclusion-tags
     1229_block tags: #parsing-until-another-block-tag
     1230
    11881231Inclusion tags
    11891232~~~~~~~~~~~~~~
    11901233
Back to Top