Alternate Row Coloring in Django Template with More Than One Set of Rows

Carl G picture Carl G · Jan 19, 2009 · Viewed 31.1k times · Source

Django templates offer the builtin tag cycle for alternating between several values at different points in a template (or for loop in a template) but this tag does not reset when it is accessed in a scope outside of the cycles definition. I.e., if you have two or more lists in your template, the rows of all of which you'd like to use some css definitions odd and even, the first row of a list will pick up where the last left off, not with a fresh iteration from the choices (odd and even)

E.g., in the following code, if the first blog has an odd number of entries, then the first entry in a second blog will start as even, when I want it to start at odd.

{% for blog in blogs %}
  {% for entry in blog.entries %}
    <div class="{% cycle 'odd' 'even' %}" id="{{entry.id}}">
      {{entry.text}}
    </div>
  {% endfor %}
{% endfor %}

I've tried obviating this by patching with the resetcycle tag offered here:

Django ticket: Cycle tag should reset after it steps out of scope

to no avail. (The code didn't work for me.)

I've also tried moving my inner loop into a custom tag, but this also did not work, perhaps because the compile/render cycle moves the loop back into the outer loop? (Regardless of why, it didn't work for me.)

How can I accomplish this simple task!? I'd prefer not to create a data structure in my view with this information pre-compiled; that seems unnecessary. Thanks in advance.

Answer

Carl Meyer picture Carl Meyer · Jan 20, 2009

The easiest workaround (until the resetcycle patch gets fixed up and applied) is to use the built-in "divisibleby" filter with forloop.counter:

{% for entry in blog.entries %}
  <div class="{% if forloop.counter|divisibleby:2 %}even{% else %}odd{% endif %}" id="{{ entry.id }}">
    {{ entry.text }}
  </div>
{% endfor %}

A little more verbose, but not hard to understand and it works great.