Ticket #1105: super_simple_tag.diff

File super_simple_tag.diff, 11.7 KB (added by Julien Phalip, 16 years ago)

Patch + tests + doc (created from revision 7774)

  • 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_block=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_block:
     810        nodelist = parser.parse(('end%s' % name,))
     811        parser.delete_first_token()
     812        node_class = curry(node_class, block_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_block=None, takes_context=None):
     869        def dec(func):
     870            params, xx, xxx, defaults = getargspec(func)
     871            if takes_context and takes_block:
     872                if params[0] == 'context' and params[1] == 'block_nodelist':
     873                    params = params[2:]
     874                else:
     875                    raise TemplateSyntaxError("Any tag function decorated both with takes_context=True and with takes_block=True must have a first argument of 'context', and a second argument of 'block_nodelist'")
     876            elif takes_block:
     877                if params[0] == 'block_nodelist':
     878                    params = params[1:]
     879                else:
     880                    raise TemplateSyntaxError("Any tag function decorated with takes_block=True must have a first argument of 'block_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, block_nodelist=None):
     889                    self.vars_to_resolve = map(Variable, vars_to_resolve)
     890                    self.takes_context = takes_context
     891                    self.block_nodelist = block_nodelist
     892                    if block_nodelist is not None:
     893                        # Save a nodelist attribute so it can be used in the Node.get_nodes_by_type method
     894                        self.nodelist = block_nodelist
     895   
     896                def render(self, context):
     897                    resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]
     898                    if self.takes_context and self.block_nodelist:
     899                        func_args = [context, self.block_nodelist] + resolved_vars
     900                    elif self.block_nodelist:
     901                        func_args = [self.block_nodelist] + resolved_vars
     902                    elif self.takes_context:
     903                        func_args = [context] + resolved_vars
     904                    else:
     905                        func_args = resolved_vars
     906                    return func(*func_args)
    868907
    869             def render(self, context):
    870                 resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]
    871                 return func(*resolved_vars)
     908            compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode, takes_block=takes_block, takes_context=takes_context)
     909            compile_func.__doc__ = func.__doc__
     910            self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
     911            return func
     912       
     913        if takes_context is not None or takes_block is not None:
     914            # Examples: @register.simple_tag(takes_context=True) or @register.simple_tag(takes_context=True, takes_block=True)
     915            return dec
     916        elif compile_function is None:
     917            # @register.simple_tag()
     918            return dec
     919        elif callable(compile_function):
     920            # @register.simple_tag
     921            return dec(compile_function)
     922        else:
     923            raise TemplateSyntaxError("Incorrect parameters for simple_tag. Accepted parameters are: takes_context and/or takes_block.")
    872924
    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 
    878925    def inclusion_tag(self, file_name, context_class=Context, takes_context=False):
    879926        def dec(func):
    880927            params, xx, xxx, defaults = getargspec(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 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 the 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 the inner block.
     21def a_simple_tag_with_block(block_nodelist, arg):
     22    """Expected __doc__"""
     23    return "Expected result"
     24a_simple_tag_with_block.anything = "Expected __dict__"
     25
     26# Tag that takes both the context and the inner block.
     27def a_simple_tag_with_context_and_block(context, block_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 the context and the inner block, but that has arguments in wrong order.
     33def a_simple_tag_with_context_and_block_wrong_order(block_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_block=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_block=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 'block_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_block=True)
     69        self.assertRaises(template.TemplateSyntaxError, decorator, a_simple_tag_without_parameters)
     70       
     71        decorator = register.simple_tag(takes_block=True, takes_context=True)
     72        self.assertRaises(template.TemplateSyntaxError, decorator, a_simple_tag_with_context_and_block_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_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