Ticket #1105: 1105.simple_tag.r7813.diff

File 1105.simple_tag.r7813.diff, 13.3 KB (added by Johannes Dollinger, 16 years ago)

takes_nodelist instead of takes_block

  • 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_nodelist=False, takes_context=False):
     869        def dec(func):
     870            params, xx, xxx, defaults = getargspec(func)
     871            offset = 0
     872            if takes_context:
     873                if params[offset] != 'context':
     874                    raise TemplateSyntaxError("Any tag function decorated with takes_context=True must take positional 'context' argument")
     875                offset += 1
     876            if takes_nodelist:
     877                if params[offset] != 'nodelist':
     878                    raise TemplateSyntaxError("Any tag function decorated with takes_nodelist=True must take positional 'nodelist' argument")
     879                offset += 1
     880            params = params[offset:]
     881           
     882            class SimpleNode(Node):
     883                def __init__(self, vars_to_resolve, takes_context=False, nodelist=None):
     884                    self.vars_to_resolve = map(Variable, vars_to_resolve)
     885                    self.takes_context = takes_context
     886                    self.nodelist = nodelist
     887   
     888                def render(self, context):
     889                    func_args = []
     890                    if self.takes_context:
     891                        func_args.append(context)
     892                    if self.nodelist:
     893                        func_args.append(self.nodelist)
     894                    func_args += [var.resolve(context) for var in self.vars_to_resolve]
     895                    return func(*func_args)
    864896
    865         class SimpleNode(Node):
    866             def __init__(self, vars_to_resolve):
    867                 self.vars_to_resolve = map(Variable, vars_to_resolve)
     897            compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode, takes_nodelist=takes_nodelist, takes_context=takes_context)
     898            compile_func.__doc__ = func.__doc__
     899            self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
     900            return func
     901       
     902        if not callable(compile_function):
     903            return dec
     904        return dec(compile_function)
    868905
    869             def render(self, context):
    870                 resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]
    871                 return func(*resolved_vars)
    872 
    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
    877 
    878906    def inclusion_tag(self, file_name, context_class=Context, takes_context=False):
    879907        def dec(func):
    880908            params, xx, xxx, defaults = getargspec(func)
  • 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
  • tests/regressiontests/templates/decorators.py

     
     1from unittest import TestCase
     2from sys import version_info
     3
     4from django import template
     5
     6register = template.Library()
     7
     8# Very simple tag, with no parameters.
     9def a_simple_tag_without_parameters(arg):
     10    """Expected __doc__"""
     11    return "Expected result"
     12a_simple_tag_without_parameters.anything = "Expected __dict__"
     13
     14# Tag that takes a context.
     15def a_simple_tag_with_context(context, arg):
     16    """Expected __doc__"""
     17    return "Expected result"
     18a_simple_tag_with_context.anything = "Expected __dict__"
     19
     20# Tag that takes a nodelist.
     21def a_simple_tag_with_block(nodelist, arg):
     22    """Expected __doc__"""
     23    return "Expected result"
     24a_simple_tag_with_block.anything = "Expected __dict__"
     25
     26# Tag that takes both a context and a nodlist.
     27def a_simple_tag_with_context_and_block(context, nodelist, arg):
     28    """Expected __doc__"""
     29    return "Expected result"
     30a_simple_tag_with_context_and_block.anything = "Expected __dict__"
     31
     32# Tag that *wants* to take both a context and a nodelist, but that has arguments in wrong order.
     33def a_simple_tag_with_context_and_block_wrong_order(nodelist, context, arg):
     34    """Expected __doc__"""
     35    return "Expected result"
     36a_simple_tag_with_context_and_block_wrong_order.anything = "Expected __dict__"
     37
     38
     39
     40
     41class DecoratorsTest(TestCase):
     42    def verify_decorator(self, decorator, func_name):
     43        # Only check __name__ on Python 2.4 or later since __name__ can't be
     44        # assigned to in earlier Python versions.
     45        if version_info[0] >= 2 and version_info[1] >= 4:
     46            self.assertEquals(decorator.__name__, func_name)
     47        self.assertEquals(decorator.__doc__, 'Expected __doc__')
     48        self.assertEquals(decorator.__dict__['anything'], 'Expected __dict__')
     49
     50    def test_simple_tag(self):
     51        # Test that the decorators preserve the decorated function's docstring, name and attributes.
     52        decorator = register.simple_tag(a_simple_tag_without_parameters)
     53        self.verify_decorator(decorator, 'a_simple_tag_without_parameters')
     54       
     55        decorator = register.simple_tag(takes_context=True)(a_simple_tag_with_context)
     56        self.verify_decorator(decorator, 'a_simple_tag_with_context')
     57       
     58        decorator = register.simple_tag(takes_nodelist=True)(a_simple_tag_with_block)
     59        self.verify_decorator(decorator, 'a_simple_tag_with_block')
     60       
     61        decorator = register.simple_tag(takes_context=True, takes_nodelist=True)(a_simple_tag_with_context_and_block)
     62        self.verify_decorator(decorator, 'a_simple_tag_with_context_and_block')
     63       
     64        # Now test that 'context' and 'nodelist' arguments and their order are correct.
     65        decorator = register.simple_tag(takes_context=True)
     66        self.assertRaises(template.TemplateSyntaxError, decorator, a_simple_tag_without_parameters)
     67       
     68        decorator = register.simple_tag(takes_nodelist=True)
     69        self.assertRaises(template.TemplateSyntaxError, decorator, a_simple_tag_without_parameters)
     70       
     71        decorator = register.simple_tag(takes_nodelist=True, takes_context=True)
     72        self.assertRaises(template.TemplateSyntaxError, decorator, a_simple_tag_with_context_and_block_wrong_order)
     73from unittest import TestCase
     74from sys import version_info
     75
     76from django import template
     77
     78register = template.Library()
     79
     80# Very simple tag, with no parameters.
     81def a_simple_tag_without_parameters(arg):
     82    """Expected __doc__"""
     83    return "Expected result"
     84a_simple_tag_without_parameters.anything = "Expected __dict__"
     85
     86# Tag that takes the context.
     87def a_simple_tag_with_context(context, arg):
     88    """Expected __doc__"""
     89    return "Expected result"
     90a_simple_tag_with_context.anything = "Expected __dict__"
     91
     92# Tag that takes the inner block.
     93def a_simple_tag_with_block(nodelist, arg):
     94    """Expected __doc__"""
     95    return "Expected result"
     96a_simple_tag_with_block.anything = "Expected __dict__"
     97
     98# Tag that takes both the context and the inner block.
     99def a_simple_tag_with_context_and_block(context, nodelist, arg):
     100    """Expected __doc__"""
     101    return "Expected result"
     102a_simple_tag_with_context_and_block.anything = "Expected __dict__"
     103
     104# Tag that *wants* to take both the context and the inner block, but that has arguments in wrong order.
     105def a_simple_tag_with_context_and_block_wrong_order(nodelist, context, arg):
     106    """Expected __doc__"""
     107    return "Expected result"
     108a_simple_tag_with_context_and_block_wrong_order.anything = "Expected __dict__"
     109
     110
     111
     112
     113class DecoratorsTest(TestCase):
     114    def verify_decorator(self, decorator, func_name):
     115        # Only check __name__ on Python 2.4 or later since __name__ can't be
     116        # assigned to in earlier Python versions.
     117        if version_info[0] >= 2 and version_info[1] >= 4:
     118            self.assertEquals(decorator.__name__, func_name)
     119        self.assertEquals(decorator.__doc__, 'Expected __doc__')
     120        self.assertEquals(decorator.__dict__['anything'], 'Expected __dict__')
     121
     122    def test_simple_tag(self):
     123        # Test that the decorators preserve the decorated function's docstring, name and attributes.
     124        decorator = register.simple_tag(a_simple_tag_without_parameters)
     125        self.verify_decorator(decorator, 'a_simple_tag_without_parameters')
     126       
     127        decorator = register.simple_tag(takes_context=True)(a_simple_tag_with_context)
     128        self.verify_decorator(decorator, 'a_simple_tag_with_context')
     129       
     130        decorator = register.simple_tag(takes_nodelist=True)(a_simple_tag_with_block)
     131        self.verify_decorator(decorator, 'a_simple_tag_with_block')
     132       
     133        decorator = register.simple_tag(takes_context=True, takes_nodelist=True)(a_simple_tag_with_context_and_block)
     134        self.verify_decorator(decorator, 'a_simple_tag_with_context_and_block')
     135       
     136        # Now test that 'context' and 'nodelist' arguments and their order are correct.
     137        decorator = register.simple_tag(takes_context=True)
     138        self.assertRaises(template.TemplateSyntaxError, decorator, a_simple_tag_without_parameters)
     139       
     140        decorator = register.simple_tag(takes_nodelist=True)
     141        self.assertRaises(template.TemplateSyntaxError, decorator, a_simple_tag_without_parameters)
     142       
     143        decorator = register.simple_tag(takes_nodelist=True, takes_context=True)
     144        self.assertRaises(template.TemplateSyntaxError, decorator, a_simple_tag_with_context_and_block_wrong_order)
  • 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_block`` option as
     1204follows::
     1205
     1206    # The first argument *must* be called "block_nodelist".
     1207    def my_bock_tag(block_nodelist, an_argument):
     1208        ...
     1209    register.simple_tag(takes_block=True)(my_bock_tag)
     1210
     1211In the above example, ``block_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_block`` at the
     1216same time. For example::
     1217
     1218    # The first argument *must* be called "context" and the second one "block_nodelist".
     1219    def my_bock_tag(context, block_nodelist, an_argument):
     1220        timezone = context['timezone']
     1221        content = block_nodelist.render(context)
     1222        ...
     1223    register.simple_tag(takes_context=True, takes_block=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