Login

Easy Conditional Template Tags

Author:
fragsworth
Posted:
May 29, 2009
Language:
Python
Version:
1.0
Score:
5 (after 5 ratings)

This is a conditional templatetag decorator that makes it very easy to write template tags that can be used as conditions. This can help avoid template boilerplate code (e.g. setting a variable in your template to be used in a condition).

All you have to do is define a function with expected parameters that returns True or False. Examples are in the code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
from django import template
from django.template import NodeList
from functools import wraps
import inspect

def condition_tag(func):
    """ Generic conditional templatetag decorator.

    Example - how to define a conditional tag:
        @register.tag
        @condition_tag
        def if_can_foo(object, user='user'):
            return user.can_foo(object)

    Example - how to use it in the template:
        {% if_can_foo object user %}
        ...
        {% else %}
        ...
        {% endif_can_foo %}

    Or - we can leave out the second parameter, and it will default
    to 'user':
        {% if_can_foo object %}
        ...

    In python, the if_can_foo function's arguments are the expected
    arguments to the template tag. In this case, the first argument
    should be the object, the second argument should be the user. The
    return value must be either True or False, corresponding to the
    if/else sections of the condition node in the template.

    Default arguments for the function (e.g. user='user') will be
    processed by the context that the template tag resides in. In this
    case, it will resolve the global 'user' name, and in the function,
    we will be accessing the resultant object. If this value does not
    exist in the template's context, it will break.
    """

    class ConditionNode(template.Node):
        def __init__(self, arg_expressions, nodelist_true, nodelist_false):
            self.arg_expressions = arg_expressions
            self.nodelist_true = nodelist_true
            self.nodelist_false = nodelist_false

        def render(self, context):
            params = [ i.resolve(context) for i in self.arg_expressions ]
            if func(*params):
                return self.nodelist_true.render(context)
            else:
                return self.nodelist_false.render(context)

    @wraps(func)
    def wrapper(parser, token):
        bits = token.contents.split()

        # Get the parameters and default parameters for the decorated function
        argspec = inspect.getargspec(func)
        params = argspec[0]
        defaults = list(argspec[3])
        if defaults:
            default_params = dict(zip([i for i in reversed(params)],
                                      [i for i in reversed(defaults)] ))
        else:
            default_params = {}

        # Try to display nice template errors
        if len(bits) > len(params)+1 or len(bits) <= len(params)-len(defaults):
            error = (
                '"%s" accepts %d arguments: %s' %
                (bits[0], len(params), ', '.join(params),)
            )
            raise template.TemplateSyntaxError, error

        # Get the (string) arguments from the template
        arg_expressions = []
        for i in range(len(params)):
            try:
                # Try to use the parameter given by the template
                arg_expressions.append(parser.compile_filter(bits[i+1]))
            except IndexError:
                # If it doesn't exist, use the default value
                arg_expressions.append(
                    parser.compile_filter(default_params[params[i]])
                )

        # Parse out the true and false nodes
        nodelist_true = parser.parse(('else', 'end'+bits[0],))
        token = parser.next_token()
        if token.contents == 'else':
            nodelist_false = parser.parse(('end'+bits[0],))
            parser.delete_first_token()
        else:
            nodelist_false = NodeList()
        return ConditionNode(arg_expressions, nodelist_true, nodelist_false)
    return wrapper

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 3 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 7 months ago

Comments

HongFaeBu (on April 2, 2013):

If no defaults arguments are given line 60 fails because it is None and not an empty list.

Changing line 60 to this will fix it.

defaults = argspec[3] and list(argspec[3]) or []

Otherwise an excellent snippet.

#

Please login first before commenting.