Django, templates, for loops, and cycles

djangodjango-templates

(tl;dr at the bottom)

Let me try to explain what I'm trying to accomplish: I have a two dimensional array, and I would like to display its contents a certain way. I want "rows", and each row can display no more than three "objects", for lack of a better word. So I want to iterate over the array and create my HTML in the process. My idea is this: every "first of three" elements in the array should open up the "row". Every "third of three" elements should close the "row". However, if the last element in the [inner] array does not happen to be the "third of three", it should still close the row. So, for example, if we had something like L=[ [0,1,2,3,4], [5,6,7] ], I would want to display it like so:

0  1  2
3  4

5  6  7

Which might be marked up like:

<div>0 1 2</div>
<div>3 4</div>
<div>5 6 7</div>

My first thought was to simply use the modulo operator and see if each iteration was the "first", "second", or "third" of a row, but Django templates don't directly support modulo (more on that later).

So I came up with template code like this:

{% for x in L %}
 {% for y in x %}
  {% cycle '<div>' '' '' %}
   {{ y }}
  {% cycle '' '' '</div>' %}
 {% endfor %}<br/>
{% endfor %}

And this was a good start. Taking the above example, on 0 we would open a div, on 1 do nothing, on 2 close the div, on 3 open it, and on 4… well, it wouldn't close, because 4 was not the "third in a series of 3". So, while Django templates don't have modulo, they do have a divisibleby check, so I came up with additional logic to say, if we hit the last element of the [inner] for loop, and it also is not divisible by 3 (so we don't have a duplicate close), then close the div:

{% for x in z %}
 {% for y in x %}
  {% cycle '<div>' '' '' %}
   {{ y }}
  {% cycle '' '' '</div>' %}

  {% if forloop.last %}
  {% if forloop.counter|divisibleby:"3" %}
    <!-- Row Already Closed -->
  {% else %}
    </div>
  {% endif %}
  {% endif %}

 {% endfor %}<br/>
{% endfor %}

This worked better! Now, I got through the whole first inner array with proper markup. My issue is this: apparently, Django's cycle functionality does not reset when you go out of scope of the inner for loop. What this means is, my number 5 from the above example is not opening a div like it should, it is not being recognized as the first in a cycle. In fact, it's actually being recognized as a third, and so it is closing a div!

So I'm not sure where to go from here. This appears to be a known and unfixed issues in Django. Can anyone assist, please?

tl;dr I want to take a 2d array, e.g. L=[ [0,1,2,3,4], [5,6,7] ] and properly mark it up grouping no more than 3 at a time and without grouping any elements from multiple arrays together, like so:

0  1  2
3  4

5  6  7

What would be the template code for that and why?

Best Answer

You should be able to use {% if forloop.counter0|divisibleby:"3" %} to determine when to open the <div> tag, and {% if forloop.last or forloop.counter|divisibleby:"3" %} to determine when to close the </div> tag.

{% for x in z %}
 {% for y in x %}
  {% if forloop.counter0|divisibleby:"3" %}<div>{% endif %}
   {{ y }}
  {% if forloop.last or forloop.counter|divisibleby:"3" %}</div>{% endif %}
 {% endfor %}<br/>
{% endfor %}