Django template counter in nested loops

15,960

Solution 1

I found a better solution with itertools. (Better than my previous answer) You can set current state of the loop to the itertools variable sent to the view context. This time i tried on a dummy Django project and it works like a charm.

views.py:

from django.shortcuts import render_to_response
import itertools

def home(request):
    iterator=itertools.count()
    base_parts = [
        {'important item': 43},
        {'lesser item1': 22, 'lesser item2': 3, 'lesser item3': 45},
        {'most important item': 55}
    ]
    return render_to_response('index.html', 
                             {'base_parts': base_parts, 'iterator':iterator})

index.html:

{% for base_part in base_parts %}
    {% for k, v in base_part.items %}
        {{ iterator.next }} - {{ v }}<br/>
    {% endfor %}
{% endfor %}

HTML Output:

0 - 43
1 - 22
2 - 45
3 - 3
4 - 55

Sorted values:

(This part is not an answer to the actual question. It's more like I'm playing around)

You can use Django's SortedDict instead of Python's built-in dictionary to keep items order.

views.py

from django.shortcuts import render_to_response
import itertools
from django.utils.datastructures import SortedDict

def home(request):
    iterator=itertools.count()
    base_parts = [
        SortedDict([('important item', 43)]),
        SortedDict([('lesser item1', 22), 
                    ('lesser item2', 3), 
                    ('lesser item3', 45)]),
        SortedDict([('most important item', 55)])
    ]
    print base_parts[1]
    return render_to_response('index.html', 
                             {'base_parts': base_parts, 'iterator':iterator})

HTML Output:

0 - 43
1 - 22
2 - 3
3 - 45
4 - 55

Edit 2014-May-25

You can also use collections.OrderedDict instead of Django's SortedDict.

Edit 2016-June-28

Calling iterator.next doesn't work in Python 3. You can create your own iterator class, inheriting from itertools.count:

import itertools
class TemplateIterator(itertools.count):
    def next(self):
        return next(self)

Solution 2

In an ideal world, you should avoid putting this kind of logic in the template. If you are not preserving the hierarchy in your output (eg displaying these items as a list of lists) flatten the list and use a simple for loop and the loop counter.

However, the ideal solution isn't always an option. In theory, I believe the following could/should work

{% for base_part in base_parts %}     
    {% with outerCount = forloop.parentloop.counter0 %}
    {% for k, v in base_part.items %}
        ...
        {% with innerCounter = forloop.counter %}
        {{ outerCounter|add:innerCounter }}
    {% endfor %}
{% endfor %}

Solution 3

UPDATE: This is not a correct answer. I am just keeping it here to display what doesn't work.

I have to admit that i haven't tried this one but you can use with and add statements.

{% with total=0 %}
    {% for base_part in base_parts %}
        {% for k, v in base_part.items %}

        {# ...do stuff #}

        {# I try to get a running total of items to use as an ID #}
        totalId: {{ total|add:"1" }} <br/>
        {% endfor %}
    {% endfor %}
{% endwith %}

This would probably work on template level but i think a better approach is calculating it on the view level and passing a dictionary to the template which includes calculated values.

Share:
15,960

Related videos on Youtube

Darwin Tech
Author by

Darwin Tech

Updated on September 15, 2022

Comments

  • Darwin Tech
    Darwin Tech over 1 year

    Hi I have a list of two dictionaries I am passing to a Django template:

    base_parts = [
        {'important item': 43},
        {'lesser item': 22, 'lesser item': 3, 'lesser item': 45}
    ]
    

    in my template I can do this:

    {% for base_part in base_parts %}
        {% for k, v in base_part.items %}
    
        {# ...do stuff #}
    
        {# I try to get a running total of items to use as an ID #}
        inner ID: {% forloop.counter0 %}< br/>
        outer ID: {% forloop.parentloop.counter0 %}< br/>
    
        {% endfor %}
    {% endfor %}
    

    As you can see, what I want is a running total of the total number of items I have iterated through, but both methods I have included return duplicates. I know I could concatenate the loops, but I am using a formset and really would like the ids to be indexed 0,1,2...etc.

    Is there a way to achieve this type of count in the template?

    Any help much appreciated.

    EDIT

    output at the moment looks like:

    outerID: 0<br />
    innerID: 0<br />
    outerID: 0<br />
    innerID: 1<br />
    outerID: 1<br />
    innerID: 0<br />
    outerID: 1<br />
    innerID: 1<br />
    outerID: 1<br />
    innerID: 2<br />
    

    I want:

    totalID: 0<br />
    totalID: 1<br />
    totalID: 2<br />
    totalID: 3<br />
    totalID: 4<br />
    totalID: 5<br />
    totalID: 6<br />
    totalID: 7<br />
    totalID: 8<br />
    totalID: 9<br />
    
    • George Cummins
      George Cummins over 11 years
      Will you post the output you receive and the output you expect to receive?
  • Darwin Tech
    Darwin Tech over 11 years
    But this never changes the value of total - it just adds 1.
  • username
    username over 11 years
    Then this is a bad answer. I thought it might work. But as Django template philosophy suggests: the template system is meant to express presentation, not program logic. We have to follow these rules. So counting the list items in the view and passing that object to the template would be a better approach.
  • Darwin Tech
    Darwin Tech over 11 years
    Yes, this seems like too much to do in the template. I think better approach is to compile the list of dictionaries to list of dictionaries where each element in the list is a single key value pair.
  • laffuste
    laffuste over 9 years
    Cool! works great with assignment tag! @register.assignment_tag \n def get_counter(start=1): \n return itertools.count(start)