views:

4038

answers:

4

Hi,

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.

+1  A: 

The easiest answer might be: "give up and use jQuery." If that's acceptable it's probably easier than fighting with Django's templates over something so simple.

Steve Losh
This is what I do, though since I was using jQuery anyway, it was pretty trivial.
Jeff Bauer
Cool, thanks. Am learning new uses for jQuery daily.
DGGenuine
+1  A: 

Give up and use Jinja2 Template System

I gave up on django template language, it's very restricted in what you can do with it. Jinja2 uses the same syntax that the django template uses, but adds many enhancements over it.

EDIT/NOTE ( I know it sounds like a big switch for just a minor issue, but in reality I bet you always find yourself fighting the default template system in django, so it really is worthwhile and I believe it will make you more productive in the long run. )

You can read this article written by its author, although it's technical, he mentions the problem of the {% cycle %} tag in django.

Jinja doesn't have a cycle tag, it has a cycle method on the loop:

{% for user in users %}
    <li class="{{ loop.cycle('odd', 'even') }}">{{ user }}</li>
{% endfor %}

A major advantage of Jinja2 is that it allows you to use logic for the presentation, so if you have a list of pictures, you can put them in a table, because you can start a new row inside a table every N elements, see, you can do for example:

{% if loop.index is divisibleby(5) %}   
     </tr>
     {% if not loop.last %}
     <tr>
     {% endif %}
{% endif %}

you can also use mathematical expressions:

{% if x > 10 %}

and you can access your python functions directly (but some setup is required to specify which functions should be exposed for the template)

{% for item in normal_python_function_that_returns_a_query_or_a_list() %}

even set variables ..

{% set variable_name = function_that_returns_an_object_or_something() %}
hasen j
That's a big switch to make (with some major complications, like third-party or contrib apps) for such a minor issue. -1
Carl Meyer
Good point to bring up. The thing is, I bet that people always find themselves fighting the django template system, it's not just this minor issue or that one, it's a whole pile of annoyances.
hasen j
Thank you for your suggestion and article; I have felt limited by Django's approach to templating and annoyed by its bugs. But Django's sweet overall.
DGGenuine
I know, actually I love Django. It's not like I'm saying to throw django, just plug Jinja2 into its environment. :)
hasen j
A: 

There's a way to do it server-side with an iterator that doesn't keep a simultaneous copy of all the entries:

import itertools
return render_to_response('template.html',
  {
    "flattened_entries": itertools.chain(*(blog.entries for blog in blogs)),
  })
orip
How does this solve the OP's problem? Seems like this is just another way to provide the wrong behavior (alternating row colors that don't reset between blog posts). -1
Carl Meyer
You're right, I understood his problem in reverse
orip
+30  A: 

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.

Carl Meyer
that seems like the ticket. Thanks.
DGGenuine