Login

"for" template tag with support for "else" if array is empty

Author:
jezdez
Posted:
January 15, 2008
Language:
Python
Version:
.96
Tags:
template tags template-tag templatetags forloop
Score:
11 (after 13 ratings)

This is a customized version of the default for template tag which takes an optional {% else %} clause that will be displayed if the given array is empty.

from django.template import *
>>> t1 = Template("""
    {% load mytags %}
    {% for athlete in athlete_list %}
        {{ athlete }} 
    {% else %}
        No athlete in list!
    {% endfor %}
    """)
>>> c1 = Context(
    {'athlete_list':
        ['me', 'myself', 'I']
    })
>>> t1.render(c1)
u'me myself I '
>>> c2 = Context({})
>>> t1.render(c2)
u'No athlete in list!'

If you want to automatically override the builtin for template-tag add it to the builtins:

from django.template import add_to_builtins
add_to_builtins('python.path.to.mytags')
  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
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
import re
try:
    reversed
except NameError:
    from django.utils.itercompat import reversed     # Python 2.3 fallback

from django.template import Node, NodeList, Library
from django.template import TemplateSyntaxError, VariableDoesNotExist
from django.conf import settings

register = Library()

class ForNode(Node):
    def __init__(self, loopvars, sequence, is_reversed, nodelist_loop, nodelist_false):
        self.loopvars, self.sequence = loopvars, sequence
        self.is_reversed = is_reversed
        self.nodelist_loop, self.nodelist_false = nodelist_loop, nodelist_false

    def __repr__(self):
        reversed_text = self.is_reversed and ' reversed' or ''
        return "<For Node: for %s in %s, tail_len: %d%s>" % \
            (', '.join(self.loopvars), self.sequence, len(self.nodelist_loop),
             reversed_text)

    def __iter__(self):
        for node in self.nodelist_loop:
            yield node
        for node in self.nodelist_false:
            yield node        

    def get_nodes_by_type(self, nodetype):
        nodes = []
        if isinstance(self, nodetype):
            nodes.append(self)
        nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype))
        nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
        return nodes

    def render(self, context):
        nodelist = NodeList()
        if 'forloop' in context:
            parentloop = context['forloop']
        else:
            parentloop = {}
        context.push()
        try:
            values = self.sequence.resolve(context, True)
        except VariableDoesNotExist:
            values = []
        if values is None:
            values = []
        if not hasattr(values, '__len__'):
            values = list(values)
        len_values = len(values)
        if len_values < 1:
            return self.nodelist_false.render(context)
        if self.is_reversed:
            values = reversed(values)
        unpack = len(self.loopvars) > 1
        # Create a forloop value in the context.  We'll update counters on each
        # iteration just below.
        loop_dict = context['forloop'] = {'parentloop': parentloop}
        for i, item in enumerate(values):
            # Shortcuts for current loop iteration number.
            loop_dict['counter0'] = i
            loop_dict['counter'] = i+1
            # Reverse counter iteration numbers.
            loop_dict['revcounter'] = len_values - i
            loop_dict['revcounter0'] = len_values - i - 1
            # Boolean values designating first and last times through loop.
            loop_dict['first'] = (i == 0)
            loop_dict['last'] = (i == len_values - 1)

            if unpack:
                # If there are multiple loop variables, unpack the item into
                # them.
                context.update(dict(zip(self.loopvars, item)))
            else:
                context[self.loopvars[0]] = item
            for node in self.nodelist_loop:
                nodelist.append(node.render(context))
            if unpack:
                # The loop variables were pushed on to the context so pop them
                # off again. This is necessary because the tag lets the length
                # of loopvars differ to the length of each set of items and we
                # don't want to leave any vars from the previous loop on the
                # context.
                context.pop()
        context.pop()
        return nodelist.render(context)

def do_for(parser, token):
    """
    Loops over each item in an array, with support of ``else``.

    For example, to display a list of athletes given ``athlete_list``::

        <ul>
        {% for athlete in athlete_list %}
            <li>{{ athlete.name }}</li>
        {% endfor %}
        </ul>

    You can loop over a list in reverse by using
    ``{% for obj in list reversed %}``.

    You can also unpack multiple values from a two-dimensional array::

        {% for key,value in dict.items %}
            {{ key }}: {{ value }}
        {% endfor %}

    As you can see, this customized ``for`` tag can take an optional
    ``{% else %}`` clause that will be displayed if the given array is empty::
    
        <ul>
        {% for athlete in athlete_list %}
            <li>{{ athlete.name }}</li>
        {% else %}
            <li>Sorry, no athlete in this list!</li>
        {% endfor %}
        <ul>

    The for loop sets a number of variables available within the loop:

        ==========================  ================================================
        Variable                    Description
        ==========================  ================================================
        ``forloop.counter``         The current iteration of the loop (1-indexed)
        ``forloop.counter0``        The current iteration of the loop (0-indexed)
        ``forloop.revcounter``      The number of iterations from the end of the
                                    loop (1-indexed)
        ``forloop.revcounter0``     The number of iterations from the end of the
                                    loop (0-indexed)
        ``forloop.first``           True if this is the first time through the loop
        ``forloop.last``            True if this is the last time through the loop
        ``forloop.parentloop``      For nested loops, this is the loop "above" the
                                    current one
        ==========================  ================================================

    """
    bits = token.contents.split()
    if len(bits) < 4:
        raise TemplateSyntaxError("'for' statements should have at least four"
                                  " words: %s" % token.contents)

    is_reversed = bits[-1] == 'reversed'
    in_index = is_reversed and -3 or -2
    if bits[in_index] != 'in':
        raise TemplateSyntaxError("'for' statements should use the format"
                                  " 'for x in y': %s" % token.contents)

    loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',')
    for var in loopvars:
        if not var or ' ' in var:
            raise TemplateSyntaxError("'for' tag received an invalid argument:"
                                      " %s" % token.contents)

    sequence = parser.compile_filter(bits[in_index+1])
    nodelist_loop = parser.parse(('else', 'endfor',))
    token = parser.next_token()
    if token.contents == 'else':
        nodelist_false = parser.parse(('endfor',))
        parser.delete_first_token()
    else:
        nodelist_false = NodeList()
    #parser.delete_first_token()
    return ForNode(loopvars, sequence, is_reversed, nodelist_loop, nodelist_false)
do_for = register.tag("for", do_for)

More like this

  1. Render specific blocks from templates (useful for AJAX) by sciyoshi 7 years, 1 month ago
  2. Safe template decorator by eternicode 3 years, 1 month ago
  3. really spaceless (trim spaces at line start) by wolfram 7 years, 5 months ago
  4. custom template filter to check if list contains any non-empty element by doniyor 10 months ago
  5. Render specific blocks from templates (useful for AJAX, alternative) by zbyte64 6 years, 11 months ago

Comments

Please login first before commenting.