Reputation: 707
I'm building a django webpage but I seem to have hit a snag as I can't really figure out how to use the current iteration of a for loop (in the template) for multiple lists:
{% for num in loopRange %}
<tr>
{% for num2 in subRange %}
<td>{% cycle list1 list2 list3 list4 %}</td>
{% endfor %}
</tr>
{% endfor %}
I found a couple questions here stackoverflow that were similar and I attempted to use cycle, but alas this just resulted in all members of the list being printed each time--not exactly unexpected but I can't figure out how for the life of me.
What I have is multiple lists that are all similar in content, each list is a column in a row. So, if it was python and I was concatenating strings it would be like this:
for i in xrange(5):
string = list1[i] + list2[i] + list3[i] + list 4[i]
So basically that. I'm passing each list in as context in addition to two xranges (loopRange and Subrange in the first example), I need five rows (each list has five members) and four columns (four lists).
EDIT: I suppose in a nuthshell I want to refer to list indeces as foo[bar], done in django as foo.bar, however bar apparently can't be an integer an iterable range passed in as content
Thanks!
Upvotes: 3
Views: 2752
Reputation: 12829
If you would really like to do this in your templates themselves for some reason, you can also write a custom template tag that does it for you:
#app/templatetags/zip.py
from django import template
register = template.Library()
class Zipper(template.Node):
def __init__(self, format_string, var_name):
self.format_string = format_string
self.var_name = var_name
def render(self, context):
zippables = [context[tok] for tok in self.format_string.split()]
import pdb; pdb.set_trace()
context[self.var_name] = zip(*zippables)
return ''
import re
@register.tag(name="zip")
def do_zip(parser, token):
try:
# Splitting by None == splitting by spaces.
tag_name, arg = token.contents.split(None, 1)
except ValueError:
raise template.TemplateSyntaxError("%r tag requires arguments" % token.contents.split()[0])
m = re.search(r'(.*?) as (\w+)', arg)
if not m:
raise template.TemplateSyntaxError("%r tag had invalid arguments" % tag_name)
format_string, var_name = m.groups()
return Zipper(format_string, var_name)
Note: this syntax is about to get a lot simpler in Django 1.4, due to the addition of assignment_tag
.
Then, just use it like this:
{%load zip%}
{% zip list1 list2 list3 list4 as list_of_rows %}
You can then use list_of_rows
wherever you want in that block, including inside two nested for loops.
Upvotes: 0
Reputation: 239290
You can just create a simple filter to return an item from the list:
@register.filter
def get_item(row, i):
try:
return row[i]
except IndexError:
return ''
Then:
{{ list1|get_item:num2 }}
Upvotes: 1
Reputation: 40384
I'd say that the best approach would be to work on the data in the view before passing it to the template.
For example, the zip
builtin can be used to create the rows list based on the lists that contain the columns:
rows = zip(list1, list2, list3, list4)
After that, the template can iterate over the rows one by one and access the columns using index access if needed:
{% for row in rows %}
{{row.0}} {{row.1}} {{row.2}} {{row.3}}
{% endfor %}
Upvotes: 3