Ticket #1105: 1105.simple_tag.r9050.diff

File 1105.simple_tag.r9050.diff, 11.8 KB (added by Julien Phalip, 16 years ago)

Updated patch to r9050

  • django/django/template/__init__.py

     
    809809        else:
    810810            return force_unicode(output)
    811811
    812 def generic_tag_compiler(params, defaults, name, node_class, parser, token):
     812def generic_tag_compiler(params, defaults, name, node_class, parser, token, takes_context=False, takes_nodelist=False):
    813813    "Returns a template.Node subclass."
    814814    bits = token.split_contents()[1:]
    815815    bmax = len(params)
     
    821821        else:
    822822            message = "%s takes between %s and %s arguments" % (name, bmin, bmax)
    823823        raise TemplateSyntaxError(message)
     824    if takes_context:
     825        node_class = curry(node_class, takes_context=takes_context) 
     826    if takes_nodelist:
     827        nodelist = parser.parse(('end%s' % name,)) 
     828        parser.delete_first_token() 
     829        node_class = curry(node_class, nodelist=nodelist)
    824830    return node_class(bits)
    825831
    826832class Library(object):
     
    876882        self.filters[getattr(func, "_decorated_function", func).__name__] = func
    877883        return func
    878884
    879     def simple_tag(self,func):
    880         params, xx, xxx, defaults = getargspec(func)
     885    def simple_tag(self, compile_function=None, takes_context=None, takes_nodelist=None):
     886        def dec(func):
     887            params, xx, xxx, defaults = getargspec(func)
     888            if takes_context and takes_nodelist:
     889                if params[0] == 'context' and params[1] == 'nodelist':
     890                    params = params[2:]
     891                else:
     892                    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'")
     893            elif takes_nodelist:
     894                if params[0] == 'nodelist':
     895                    params = params[1:]
     896                else:
     897                    raise TemplateSyntaxError("Any tag function decorated with takes_nodelist=True must have a first argument of 'nodelist'")
     898            elif takes_context:
     899                if params[0] == 'context':
     900                    params = params[1:]
     901                else:
     902                    raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'")
     903            class SimpleNode(Node):
     904                def __init__(self, vars_to_resolve, takes_context=False, nodelist=None):
     905                    self.vars_to_resolve = map(Variable, vars_to_resolve)
     906                    self.takes_context = takes_context
     907                    if nodelist is not None:
     908                        # Only save the 'nodelist' attribute if it's not None, so that it is picked by the Node.get_nodes_by_type() method.
     909                        self.nodelist = nodelist
    881910
    882         class SimpleNode(Node):
    883             def __init__(self, vars_to_resolve):
    884                 self.vars_to_resolve = map(Variable, vars_to_resolve)
     911                def render(self, context):
     912                    resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]
     913                    if self.takes_context and hasattr(self, 'nodelist'):
     914                        func_args = [context, self.nodelist] + resolved_vars
     915                    elif hasattr(self, 'nodelist'):
     916                        func_args = [self.nodelist] + resolved_vars
     917                    elif self.takes_context:
     918                        func_args = [context] + resolved_vars
     919                    else:
     920                        func_args = resolved_vars
     921                    return func(*func_args)
     922            compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode, takes_nodelist=takes_nodelist, takes_context=takes_context)
     923            compile_func.__doc__ = func.__doc__
     924            self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
     925            return func
     926       
     927        if takes_context is not None or takes_nodelist is not None:
     928            # Examples: @register.simple_tag(takes_context=True) or @register.simple_tag(takes_context=True, takes_nodelist=True)
     929            return dec
     930        elif compile_function is None:
     931            # @register.simple_tag()
     932            return dec
     933        elif callable(compile_function):
     934            # @register.simple_tag
     935            return dec(compile_function)
     936        else:
     937            raise TemplateSyntaxError("Incorrect parameters for the simple_tag decorator.")
    885938
    886             def render(self, context):
    887                 resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]
    888                 return func(*resolved_vars)
    889 
    890         compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode)
    891         compile_func.__doc__ = func.__doc__
    892         self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
    893         return func
    894 
    895939    def inclusion_tag(self, file_name, context_class=Context, takes_context=False):
    896940        def dec(func):
    897941            params, xx, xxx, defaults = getargspec(func)
  • django/tests/regressiontests/templates/tests.py

     
    1818from django.utils.safestring import mark_safe
    1919from django.utils.tzinfo import LocalTimezone
    2020
     21from decorators import DecoratorsTest
    2122from unicode import unicode_tests
    2223from context import context_tests
    2324
  • 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's node list.
     21def a_simple_tag_with_nodelist(nodelist, arg):
     22    """Expected __doc__"""
     23    return "Expected result"
     24a_simple_tag_with_nodelist.anything = "Expected __dict__"
     25
     26# Tag that takes both the context and the inner block's node list.
     27def a_simple_tag_with_context_and_nodelist(context, nodelist, arg):
     28    """Expected __doc__"""
     29    return "Expected result"
     30a_simple_tag_with_context_and_nodelist.anything = "Expected __dict__"
     31
     32# Tag that *wants* to take both the context and the inner block's node list, but that has arguments in wrong order.
     33def a_simple_tag_with_context_and_nodelist_wrong_order(nodelist, context, arg):
     34    """Expected __doc__"""
     35    return "Expected result"
     36a_simple_tag_with_context_and_nodelist_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_nodelist)
     59        self.verify_decorator(decorator, 'a_simple_tag_with_nodelist')
     60       
     61        decorator = register.simple_tag(takes_context=True, takes_nodelist=True)(a_simple_tag_with_context_and_nodelist)
     62        self.verify_decorator(decorator, 'a_simple_tag_with_context_and_nodelist')
     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_nodelist_wrong_order)
  • django/docs/howto/custom-template-tags.txt

     
    587587    * If the argument was a template variable, our function is passed the
    588588      current value of the variable, not the variable itself.
    589589
    590 When your template tag does not need access to the current context, writing a
    591 function to work with the input values and using the ``simple_tag`` helper is
    592 the easiest way to create a new tag.
     590If your template tag needs to access the current context, you can use the
     591``takes_context`` option as follows::
    593592
     593    # The first argument *must* be called "context" here.
     594    def current_time(context, format_string):
     595        timezone = context['timezone']
     596        ...
     597
     598    register.simple_tag(takes_context=True)(current_time)
     599
     600You can also use the decorator syntax if running in Python 2.4::
     601
     602    @register.simple_tag(takes_context=True)
     603    def current_time(context, format_string):
     604        ...
     605
     606For more information on how the ``takes_context`` option works, see the section
     607on `inclusion tags`_.
     608
     609To create a simple block tag, you can use the ``takes_nodelist`` option as
     610follows::
     611
     612    # The first argument *must* be called "nodelist" here.
     613    def my_bock_tag(nodelist, an_argument):
     614        ...
     615    register.simple_tag(takes_nodelist=True)(my_bock_tag)
     616
     617In the above example, ``nodelist`` is a list of all nodes between
     618``{% my_bock_tag %}`` and ``{% endmy_bock_tag %}``, not counting
     619``{% my_bock_tag %}`` and ``{% endmy_bock_tag %}`` themselves.
     620
     621It is also possible to use both ``takes_context`` and ``takes_nodelist`` at the
     622same time. For example::
     623
     624    # The first argument *must* be called "context" and the second one "nodelist".
     625    def my_bock_tag(context, nodelist, an_argument):
     626        timezone = context['timezone']
     627        content = nodelist.render(context)
     628        ...
     629    register.simple_tag(takes_context=True, takes_nodelist=True)(my_bock_tag)
     630
     631If you need to create a more complex block tag, refer to the section on
     632`parsing until another block tag`_.
     633
     634_inclusion tags: #inclusion-tags
     635_parsing until another block tag: #parsing-until-another-block-tag
     636
    594637.. _howto-custom-template-tags-inclusion-tags:
    595638
    596639Inclusion tags
Back to Top