ColumnizeTag
I wanted to take a long list of objects and display them, but not in an unorder list (way too long) or in a popup/drop-down menu (again, way too long).
This tag will create <tr></tr> and <td></td> tags around everything between the {% columnize %} and {% endcolumnize %} tags. It allows for optional row classes and cell classes.
Tag Parameters
{% columnize num_cols row_tags cell_tags %}{% endcolumnize %}
num_cols: should be greater than 1 (otherwise you aren't creating columns). If it is left out, it defaults to 1
row_tags: is optional. The class name you want for the <tr> tags goes here, otherwise. If you list several (e.g. row1,row2 ) it will loop through them like {% cycle %}. If you do not want row classes, but do want cell classes, pass two quote marks ().
cell_tags: is optional. The class name you want for the <td> tags goes here, otherwise. If you list several (e.g. cell1,cell2 ) it will loop through them within the row like {% cycle %}.
Typical Usage
Simple
<table border="0" cellspacing="5" cellpadding="5">
{% for o in some_list %}
{% columnize 3 %}
<a href="{{ o.get_absolute_url }}">{{ o.name }}</a>
{% endcolumnize %}
{% endfor %}
</table>
With row and cell classes
<table border="0" cellspacing="5" cellpadding="5">
{% for o in some_list %}
{% columnize 3 myrow mycell %}
<a href="{{ o.get_absolute_url }}">{{ o.name }}</a>
{% endcolumnize %}
{% endfor %}
</table>
With a cell class, but no row class
<table border="0" cellspacing="5" cellpadding="5">
{% for o in some_list %}
{% columnize 3 '' mycell %}
<a href="{{ o.get_absolute_url }}">{{ o.name }}</a>
{% endcolumnize %}
{% endfor %}
</table>
Alternating rows, and each cell/column is different
<table border="0" cellspacing="5" cellpadding="5">
{% for o in some_list %}
{% columnize 3 row1,row2 cell1,cell2,cell3 %}
<a href="{{ o.get_absolute_url }}">{{ o.name }}</a>
{% endcolumnize %}
{% endfor %}
</table>
Code
from django import template
register = template.Library()
def columnize(parser, token):
"""
Put stuff into columns. Can also define class tags for rows and cells
Usage: {% columnize num_cols [row_class[,row_class2...]|'' [cell_class[,cell_class2]]] %}
num_cols: the number of columns to format.
row_class: can use a comma (no spaces, please) separated list that cycles
(utilizing the cycle code) can also put in '' for nothing,
if you want no row_class, but want a cell_class.
cell_class: same format as row_class, but the cells only loop within a row.
Every row resets the cell counter.
Typical usage:
<table border="0" cellspacing="5" cellpadding="5">
{% for o in some_list %}
{% columnize 3 %}
<a href="{{ o.get_absolute_url }}">{{ o.name }}</a>
{% endcolumnize %}
{% endfor %}
</table>
"""
nodelist = parser.parse(('endcolumnize',))
parser.delete_first_token()
#Get the number of columns, default 1
columns = 1
row_class = ''
cell_class = ''
args = token.contents.split(None, 3)
num_args = len(args)
if num_args >= 2:
#{% columnize columns %}
if args[1].isdigit():
columns = int(args[1])
else:
raise template.TemplateSyntaxError('The number of columns must be a number. "%s" is not a number.') % args[2]
if num_args >= 3:
#{% columnize columns row_class %}
if "," in args[2]:
#{% columnize columns row1,row2,row3 %}
row_class = [v for v in args[2].split(",") if v] # split and kill blanks
else:
row_class = [args[2]]
if row_class == "''":
# Allow the designer to pass an empty string (two quotes) to skip the row_class and
# only have a cell_class
row_class = []
if num_args == 4:
#{% columnize columns row_class cell_class %}
if "," in args[3]:
#{% columnize row_class cell1,cell2,cell3 %}
cell_class = [v for v in args[3].split(",") if v] # split and kill blanks
else:
cell_class = [args[3]]
if cell_class == "''":
# This shouldn't be necessary, but might as well test for it
cell_class = []
return ColumnizeNode(nodelist, columns, row_class, cell_class)
class ColumnizeNode(template.Node):
def __init__(self, nodelist, columns = 1, row_class = '', cell_class = ''):
self.nodelist = nodelist
self.columns = int(columns)
self.counter = 0
self.rowcounter = -1
self.cellcounter = -1
self.row_class_len = len(row_class)
self.row_class = row_class
self.cell_class_len = len(cell_class)
self.cell_class = cell_class
def render(self, context):
output = ''
self.counter += 1
if (self.counter > self.columns):
self.counter = 1
self.cellcounter = -1
if (self.counter == 1):
output = '<tr'
if self.row_class:
self.rowcounter += 1
output += ' class="%s">' % self.row_class[self.rowcounter % self.row_class_len]
else:
output += '>'
output += '<td'
if self.cell_class:
self.cellcounter += 1
output += ' class="%s">' % self.cell_class[self.cellcounter % self.cell_class_len]
else:
output += '>'
output += self.nodelist.render(context) + '</td>'
if (self.counter == self.columns):
output += '</tr>'
return output
register.tag('columnize', columnize)
A small comment / correction…
Very nice and useful, but produces broken HTML (missing </tr> on last row) if the last row is unfilled (number of cells % columns != 0).
Fix that seems to work for me - replace two lines before the last 'return' with:
forloop = context['forloop']
if self.counter == self.columns or forloop['last'] and forloop['counter0'] % self.columns != (self.columns - 1):
output += '</tr>'
