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)