Using forloop.counter value as list index in a Django template

49,943

Solution 1

You can't use variables for attribute names, dictionary keys or list indices.

Also range(0,len(data)-1] is not valid python. It should be range(len(data)).

You probably don't need cycle. Maybe what you want is this:

{% for itemlist in data %}
    ...
    <table>
        {% for item in itemlist %}
        <tr>
          <td>{{ item.a }}</td>
          <td>{{ item.b }} ... </td>
        </tr>
        ...
        {% endfor %}
    </table>
    {% if not forloop.last %}
        <div class="page_break_div">
    {% endif %}
{% endfor %}

Solution 2

I solved this in a rather inefficient way. Please don't throw up on your computer when you read this code. Given two lists of identical length, it will iterate through the first and print the corresponding item from the second.

If you must use this, only use it for rarely-accessed templates where the length of both lists will be small. Ideally, refactor your template's data to avoid this problem altogether.

{% for list1item in list1 %}
   {% for list2item in list2 %}
      {% if forloop.counter == forloop.parentloop.counter %}
          {{ list1item }} {{ list2item }}
      {% endif %}
   {% endfor %}
{% endfor %}

Solution 3

I wanted to have alternating colours in my table using a style sheet, by passing a list of toggling True/False values. I found this really frustrating. In the end I created a list of dictionary items with the same keys as the fields in the table, plus one more with the toggling true/false value.

def jobListView(request):
    # django does not allow you to append stuff to the job identity, neither
    # will it allow forloop.counter to index another list. The only solution
    # is to have the toggle embedded in a dictionary along with
    # every field from the job
    j                   = job.objects.order_by('-priority')
    # have a toggling true/false list for alternating colours in the table
    theTog              = True
    jobList             = [] 
    for i in j:
        myJob           = {}
        myJob['id']     = i.id
        myJob['duty']   = i.duty
        myJob['updated'] = i.updated
        myJob['priority'] = i.priority
        myJob['description'] = i.description
        myJob['toggle'] = theTog
        jobList.append(myJob)
        theTog          = not(theTog)
    # next i

    return render_to_response('index.html', locals())
# end jobDetaiView

and my template

{% if jobList %}
    <table border="1"><tr>
    <th>Job ID</th><th>Duty</th><th>Updated</th><th>Priority</th><th>Description</th>
    </tr>

    {% for myJob in jobList %}

        <!-- only show jobs that are not closed and have a positive priority. -->
        {% if myJob.priority and not myJob.closeDate %}
            <!-- alternate colours with the classes defined in the style sheet -->
            {% if myJob.toggle %}
                <tr class=d1> 
            {% else %}
                <tr class=d0>
            {% endif %}

            <td><a href="/jobs/{{ myJob.id }}/">{{ myJob.id }}</td><td>{{ myJob.duty }}</td> 
            <td>{{ myJob.updated }}</td><td>{{ myJob.priority }}</td>
            <td class=middle>{{ myJob.description }}</td>
            </tr>
        {% endif %}
    {% endfor %}
    </ul>
{% else %}
    <p>No jobs are in the system.</p>
{% endif %}

Solution 4

Use forloop.last - True if this is the last time through the loop:

{% if forloop.last %}
{% endif %}

From Built-in template tags and filters

Share:
49,943
Alex
Author by

Alex

Updated on August 23, 2020

Comments

  • Alex
    Alex over 3 years

    in my Django 1.1.1 application I've got a function in the view that returns to his template a range of numbers and a list of lists of items, for example:

    ...  
    data=[[item1 , item2, item3], [item4, item5, item6], [item7, item8, item9]]  
    return render_to_response('page.html', {'data':data, 'cycle':range(0,len(data)-1])
    

    Inside the template I've got an external for loop, that contains also another for cycle to display in output the contains of the inner lists of data in this way

    ...  
    {% for page in cycle %}   
    ...   
    < table >   
    {% for item in data.forloop.counter0 %}  
    < tr >< td >{{item.a}} < /td > < td > {{item.b}} ... < /td > < /tr >  
    ...  
    < /table >  
    {% endfor %}  
    {% if not forloop.last %}  
    < div class="page_break_div" >  
    {% endif %}  
    {% endfor %}  
    ... 
    

    But Django template engine doesn't work with the forloop.counter0 value as index for the list (instead it does if I manually put a numeric value as index). Is there a way to let the list loop works with the external forloop.counter0 value? Thanks in advance for the help :)

  • Alex
    Alex about 14 years
    Thanks Stefanw, it's exactly what I was trying to do, I didn't think about iterate over the list because in the case of len(data)==1 (yes, the one I wrote before wasn't a good python statement) I had to show the list output in a different way. Anyway now seems all is working, thanks again for your help!
  • Dave
    Dave almost 12 years
    This is definitely the right way to do this, but here is how I work around the "no variables as attribute names, dictionary keys, or list indexes" problem. It's inelegant to be sure, but it does so while only using built-in tags and filters.
  • acjay
    acjay over 11 years
    I really can't imagine a situation where this would be the best possible solution.
  • Alkindus
    Alkindus about 4 years
    it was the best answer for my case. I wish I have double up button
  • Abpostman1
    Abpostman1 about 2 years
    same for me. Small list. Just added an alert if lists' lengths are different. But they shouldn't be.