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 "" % \ (', '.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``:: 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::