Django

Code

Ticket #1105: improved_simple_tag.r7818.diff

File improved_simple_tag.r7818.diff, 11.7 kB (added by julien, 6 months ago)

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

  • django/django/template/__init__.py

    old new  
    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

    old new  
    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

    old new  
     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

    old new  
    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