making two-column table from one-column data

108 views
Skip to first unread message

andrej kesely

unread,
Dec 2, 2007, 6:05:37 AM12/2/07
to Django users
hi,
i have small question:
suppose i have data in QuerySet - ['A', 'B', 'C', 'D', 'E', 'F',
'G'].
I want make from this set two-column table in my template:

A B
C D
E F
G

What is the fastest way to do it? I don't want split the QuerySet in
my views to something like this: [(A, B), (C, D)...] etc.

Thanks,
Andrej Kesely

alex.v...@gmail.com

unread,
Dec 2, 2007, 7:16:19 AM12/2/07
to Django users
Multi-column data is not yet supported by newforms. Until we needed
it, I never noticed. But we have recently
got an order to build in-house sales order processing database, with a
lot of table-like data (rows, columns)
to be edited online. So we decided to extend newforms, to allow us to
do it.

There are no documentation, no examples, and not yet final version.
But code is available here:
svn co http://svn.halogen-dg.com/svn/repos/DjHDGutils/trunk DjHDGutils

there are file DjHDGutils/newforms.py where we store our company
extensions to newforms. And a class BaseTable which makes it possible
to edit table data. It is already (successfully) working in our
application, and I will try to provide some documentation & examples
on that soon.

Alex

J. Clifford Dyer

unread,
Dec 2, 2007, 10:56:36 AM12/2/07
to django...@googlegroups.com

Can you just pass in QuerySet[::2] and QuerySet[1::2]?

Cheers,
Cliff


Ned Batchelder

unread,
Dec 2, 2007, 12:59:37 PM12/2/07
to django...@googlegroups.com
If your data is in the 'data' context variable, then try this:

<table>
{% for d in data %}
{% if forloop.counter0|divisibleby:"2" %}<tr>{% endif %}
<td>{{d}}</td>
{% if forloop.counter|divisibleby:"2" %}</tr>{% endif %}
{% endfor %}
{% if data|length|divisibleby:"2" %}{% else
%}<td>&nbsp;</td></tr>{% endif %}
</table>

It's a little awkward, and doesn't generalize to more than two columns,
but it does produce properly structured HTML.

If you're willing to add some code to your view, you can get more columns:

In the view:

context['data'] = data
ncols = 3
for i, d in enumerate(data):
d.firstcol = (i % ncols == 0)
d.lastcol = (i % ncols == ncols-1)
context['data_blanks'] = [None]*((ncols-len(data)%ncols)%ncols)

In the template:

<table>
{% for d in data %}
{% if d.firstcol %}<tr>{% endif %}
<td>{{d}}</td>
{% if d.lastcol %}</tr>{% endif %}
{% endfor %}
{% for blank in data_blanks %}<td>&nbsp;</td>{% endfor %}
{% if data_blanks %}</tr>{% endif %}
</table>

--Ned.
http://nedbatchelder.com

--
Ned Batchelder, http://nedbatchelder.com

Tim Chase

unread,
Dec 2, 2007, 5:09:53 PM12/2/07
to django...@googlegroups.com
> If your data is in the 'data' context variable, then try this:
>
> <table>
> {% for d in data %}
> {% if forloop.counter0|divisibleby:"2" %}<tr>{% endif %}
> <td>{{d}}</td>
> {% if forloop.counter|divisibleby:"2" %}</tr>{% endif %}
> {% endfor %}
> {% if data|length|divisibleby:"2" %}{% else
> %}<td>&nbsp;</td></tr>{% endif %}
> </table>
>
> It's a little awkward, and doesn't generalize to more than two columns,
> but it does produce properly structured HTML.

It can be generalized, as I've done it in a pinch, and it looks
something like this (untested, as I don't have my actual code
right in front of me):

<table>
<tr>


{% for d in data %}

<td>{{d}}</td>
{% if forloop.counter|divisibleby:"4" %}</tr><tr>{% endif %}
{% endfor %}
</tr>
</table>

HTML is forgiving if you don't have the right number of columns
in your final row (don't know about XHTML's definition). And it
adds a bogus row at the bottom if your dataset is divisible by N.

A better way would be to make a custom template filter that
breaks the data-set into groups.

#####################################
from itertools import izip, islice

def pad(iterable, length, padding=None):
i = 0
for thing in iterable:
i += 1
yield thing
for i in xrange(length - i):
yield padding

def columnize(dataset, args="2"):
if "," in str(args):
columns, padding = args.split(",",1)
else:
columns = args
padding = ""
columns = int(columns)
row_count, leftovers = divmod(len(dataset), columns)
if leftovers: row_count += 1
return izip(*(
pad(islice(dataset, i, None, columns), row_count, padding)
for i in xrange(columns)
))
#####################################

and then register columnize your filter. It should presume 2
columns, but be extensible to more than two columns with an
optional parameter. It also takes optional padding as a
parameter, so it can be called like

<table>
{% for thing in data|columnize:"3,&nbsp;" %}
<tr>
{% for value in thing %}
<td>{{ value }}</td>
{% endfor %}
</tr>
{% endfor %}
</table>

or

<table>
{% for thing in my_queryset|columnize %}
<tr>
{% for value in thing %}
<td>
{% if value %}
<b>{{ value.name }}:</b>
{{ value.other_field }}
{% else %}
&nbsp;
{% endif %}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>

which is about as clean a solution as it gets, as it guarantees
"square" result-sets that have been padded out.

-tim

andrej kesely

unread,
Dec 3, 2007, 2:08:22 AM12/3/07
to Django users
Thanks for your reply, I really like the general iterator approach.
This recipe will definitely go to my personal "standard" library.

A.
Reply all
Reply to author
Forward
0 new messages