I have an odd problem with Django. I have a set of objects that Im looping through in a template as you would normally. However, I need to group the items in threes. The lay
You can use a simple filter for this:
import itertools
from django import template
register = template.Library()
@register.filter
def chunks(value, chunk_length):
"""
Breaks a list up into a list of lists of size <chunk_length>
"""
clen = int(chunk_length)
i = iter(value)
while True:
chunk = list(itertools.islice(i, clen))
if chunk:
yield chunk
else:
break
Then inside your template:
{% for chunk in paintings|chunks:3 %}
<div class="row">
{% for painting in chunk %}
<div>{{ painting }}</div>
{% endfor %}
</div>
{% endfor %}
{% for painting in painting_list %}
<div class="painting">{{ painting }}</div>
{% if forloop.counter|divisibleby:"3" %}
<br>
{{ description_list.forloop.counter0-2 }}
{{ description_list.forloop.counter0-1 }}
{{ description_list.forloop.counter0 }}
{% endif %}
{% endfor %}
just so you know this code won't work, but something like this is what you want.
Maybe you could come up with your own templatetag to access the nth object in an object list.
Yuval is on the right lines, but it looks as though you're wanting to loop through the list of paintings twice: once to display the names (three per line), and once for the descriptions. This isn't at all easy to achieve within the template language.
I would suggest seeing if you can do something with some CSS. It should be possible to assign a class to the name and/or description divs so that the names all float left together, and the descriptions display as blocks underneath. Still tricky, though.
Alternatively, you could pre-process the list in your view, so that you separate out the names and descriptions into groups of three. You want to end up with this structure:
[
[name1, name2, name3],
[description1, description2, description3],
[name4, name5, name6],
[description4, description5, description6],
...
]
So you could try doing this:
painting_list = []
counter = 0
while counter < len(paintings):
painting_list.append([p.name for p in paintings[counter:counter+3])
painting_list.append([p.description for p in paintings[counter:counter+3])
counter += 3
and then in your template:
{% for group in painting_list %}
<div class="names">{% for name in group.0 %}{{ name }} {% endfor %}</div>
<ul class="descriptions">
{% for description in group.1 %}
<li>{{ description }}</li>
{% endfor %}
</ul>
{% endfor %}
I definitely vote for creating the structure in your view and passing it on to your template in the correct form.
If you find yourself struggling with template logic then it's a sign you should be doing more of the work in your view. (this also taught me that quite a bit of my view logic needed to be pushed back into my models...)
Whenever you find yourself trying out complex code inside templates, its usually a good indication that it should be moved elsewhere. One of the alternative solutions have already been suggested, which is to move the code into your view function.
The other solution would be to expose the functionality via a new template tag. One of the reasons you would choose this solution over the view solution, is that you'll be able to easily re-use the code for pages that are served with different views.
class GroupPaintingsNode(template.Node):
def __init__(self, num, varname):
self.num, self.varname = int(num), varname
def render(self, context):
paintings = Painting.objects.all # do your fetching/filtering here..
l = [[] for i in range(len(paintings))]
i = 0
while i < len(paintings):
l[i].append([p.title for p in paintings[i:i+self.num]])
l[i].append([p.desc for p in paintings[i:i+self.num]])
i += self.num
context[self.varname] = l
return ''
def group_paintings(parser, token):
tokens = token.contents.split()
if len(tokens) != 4:
raise template.TemplateSyntaxError, "'%s' tag requires three arguments" % tokens[0]
if tokens[2] != 'as':
raise template.TemplateSyntaxError, "Second argument to '%s' tag must be 'as'" % tokens[0]
return GroupPaintingsNode(tokens[1], tokens[3])
group_paintings = register.tag(group_paintings)
In template code you would use it like this:
{% group_paintings 3 as paintings %}
{% for p in paintings %}
{% for title in p.0 %} {{ title }} {% endfor %}<br>
{% for desc in p.1 %} {{ desc }} {% endfor %}
{% endfor %}
How's about something around these lines:
{% for p in paintingss %}
<div class="painting">whatever</div>
{% if forloop.counter|divisibleby:"3" %}
<br>
{% endif %}
{% endfor %}
Will this do you good?