6

I have two nested for loops inside a template. I need to get the total iterations made since the parent for loop started. The counter needs to be incremented only when the child for iterates.

For example:

Each loop goes from 1 to 3 (included)

Parent loop - 1st iteration

Child loop - 3rd iteration

Wanted result: 3

Parent loop - 2nd iteration

Child loop - 1st iteration

Wanted result: 4

Is there any way I can do this using the standard Django template tags? If not, what are my options?

Virgiliu
  • 3,068
  • 6
  • 32
  • 55
  • @jMyles Thanks. Your post is quite old but it helped me to solve my pb. Adding "divisibleby:" in the template allows to perform some specific action when number of loops reaches a given number (12 in example below) {% for basket in baskets %} {% for egg in basket.eggs.all %} {# if total number of egg is a multiple of 12, it means that a new dozen has been found #} {% if forloop.counter|add:forloop.parentloop.counter|divisibleby:12 %} Dozen of eggs ! {% endif %} {% endfor %} {% endfor %} – 06userit May 25 '17 at 13:29
  • 1
    One option would be to use a [memoizing tag](https://stackoverflow.com/a/74094611). – djvg Oct 17 '22 at 08:44

4 Answers4

2

Write a count template tag which will accumulate in a context variable.

{% for ... %}
  {% for ... %}
    {% count totalloops %}
  {% endfor %}
{% endfor %}
{{ totalloops }}
Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
  • 2
    I tried writing a custom tag that increments a variable on each iteration of the second loop but it gets reset when the parent loop iterates. – Virgiliu Dec 04 '10 at 01:19
2

Do you know, going in, how many loops there will be?

If so, an easy way is:

{{ forloop.counter |add: forloop.parentcounter.counter }} etc.

It's a bit stinky vis a vis logic separation (@Ignacio's suggestion is better on this front for sure), but I think it's acceptable if it's kept neat and orderly.

djvg
  • 11,722
  • 5
  • 72
  • 103
jMyles
  • 11,772
  • 6
  • 42
  • 56
2

Either you can use {{forloop.counter |add: forloop.parentcounter.counter }} but depend on the situation if you want to reset the counter then you need to write your own custom python method and later you can call it from django template.

Like in your views add-

class make_incrementor(object):
    count = 0

    def __init__(self, start):
        self.count = start

    def inc(self, jump=1):
        self.count += jump
        return self.count

    def res(self):
        self.count = 0
        return self.count

def EditSchemeDefinition(request, scheme_id):

    iterator_subtopic = make_incrementor(0)
    scheme_recs = scheme.objects.get(id=scheme_id)
    view_val = {
        'iterator_subtopic': iterator_subtopic,
        "scheme_recs": scheme_recs,
    }
    return render(request, "edit.html", view_val)

Later in your django template we can call "iterator_subtopic" methods to increment or reset its value like:-

<td id="subTopic" class="subTopic">
<p hidden value="{{ iterator_subtopic.res }}"></p>
{% for strand in  scheme_recs.stand_ids.all %}
    {{ iterator_subtopic.res }}
    {% for sub_strand in  strand.sub_strand_ids.all %}
        {% for topic in  sub_strand.topic_ids.all %}
            {% for subtopic in  topic.sub_topic_ids.all %}
                <input id="subTopic{{ iterator_subtopic.inc  }}" class="len"
                       value="{{ subtopic.name }}">
                <br>
            {% endfor %}
        {% endfor %}
    {% endfor %}
{% endfor %}

So It will keep incrementing the value and also we can reset it where we want.

hlongmore
  • 1,603
  • 24
  • 28
Javed
  • 1,613
  • 17
  • 16
1

With class-based views (specifically using Python 3 and Django 2.1), and using @Javed's answer as a starting point, in the view you can do:

class Accumulator:

    def __init__(self, start=0):
        self.reset(start)

    def increment(self, step=1):
        step = 1 if not isinstance(step, int) else step
        self.count += step
        return self.count

    def reset(self, start=0):
        start = 0 if not isinstance(start, int) else start
        self.count = start
        return self.count

class MyView(ParentView):

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['counter'] = Accumulator()  # use start=-1 for a zero-based index.
        return context

then in the template, you can do:

{% with counter.increment as count %}
  <input id="form-input-{{ count }}">
{% endwith %}
hlongmore
  • 1,603
  • 24
  • 28
  • Not sure why you spent the time answering a 9 year old question, but thanks :) – Virgiliu May 17 '19 at 19:41
  • 1
    @Virgiliu It was because I had a need to do the same thing for my current tech stack, so after finding this answer, I thought I'd give this update for Python 3 / Django 2. – hlongmore May 17 '19 at 23:09