views:

399

answers:

5

Hi here guys. 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 layout of the page goes like this:

Painting 1 - Painting 2 - Painting 3

D E S C R I P T I O N 1
D E S C R I P T I O N 2
D E S C R I P T I O N 3

Painting 4 - Painting - 5 Painting 6

D E S C R I P T I O N 4
D E S C R I P T I O N 5
D E S C R I P T I O N 6

etc etc

I cant quite figure out the best set of Django tags to do this really. It seems somewhat tricky. the {% cycle %} statement didnt quite help.

Unless of course, I do some java script hacking of some kind and leave Django out of this? There must be someway of saying "Put all the description divs after one another" or similar. Not sure how best to play with this ordering. Any thoughts? Cheers.

+1  A: 

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?

Yuval A
+3  A: 

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 %}
Daniel Roseman
+2  A: 

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...)

andybak
+3  A: 

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 %}
Mikael Garde Nielsen
I think this seems best as a template tag is the nicest place for this functionality to go as oppose to a view which is more awkward. Lot of lists there but I get it :) Somewhat elegant solution there! Thanks :)
Oni
A: 
{% 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.

soniiic