Ticket #1105: 1105.simple_tag.diff

File 1105.simple_tag.diff, 11.8 KB (added by julien, 6 years ago)

Updated patch to r8984

  • 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)
     73 No newline at end of file
  • django/docs/howto/custom-template-tags.txt

     
    615615    * If the argument was a template variable, our function is passed the
    616616      current value of the variable, not the variable itself.
    617617
    618 When your template tag does not need access to the current context, writing a
    619 function to work with the input values and using the ``simple_tag`` helper is
    620 the easiest way to create a new tag.
     618If your template tag needs to access the current context, you can use the
     619``takes_context`` option as follows::
    621620
     621    # The first argument *must* be called "context" here.
     622    def current_time(context, format_string):
     623        timezone = context['timezone']
     624        ...
     625
     626    register.simple_tag(takes_context=True)(current_time)
     627
     628You can also use the decorator syntax if running in Python 2.4::
     629
     630    @register.simple_tag(takes_context=True)
     631    def current_time(context, format_string):
     632        ...
     633
     634For more information on how the ``takes_context`` option works, see the section
     635on `inclusion tags`_.
     636
     637To create a simple block tag, you can use the ``takes_nodelist`` option as
     638follows::
     639
     640    # The first argument *must* be called "nodelist" here.
     641    def my_bock_tag(nodelist, an_argument):
     642        ...
     643    register.simple_tag(takes_nodelist=True)(my_bock_tag)
     644
     645In the above example, ``nodelist`` is a list of all nodes between
     646``{% my_bock_tag %}`` and ``{% endmy_bock_tag %}``, not counting
     647``{% my_bock_tag %}`` and ``{% endmy_bock_tag %}`` themselves.
     648
     649It is also possible to use both ``takes_context`` and ``takes_nodelist`` at the
     650same time. For example::
     651
     652    # The first argument *must* be called "context" and the second one "nodelist".
     653    def my_bock_tag(context, nodelist, an_argument):
     654        timezone = context['timezone']
     655        content = nodelist.render(context)
     656        ...
     657    register.simple_tag(takes_context=True, takes_nodelist=True)(my_bock_tag)
     658
     659If you need to create a more complex block tag, refer to the section on
     660`parsing until another block tag`_.
     661
     662_inclusion tags: #inclusion-tags
     663_parsing until another block tag: #parsing-until-another-block-tag
     664
    622665Inclusion tags
    623666~~~~~~~~~~~~~~
    624667
Back to Top