While trying to add a simple sorting feature to the critify game listing, I went off on a strange train of thought involving a bunch of future functionality I only have a very vague picture of, and decided that it would be best to choose the most complex approach possible and create a separate, overarchitected abstraction layer for that very purpose.
Well, something like that, though hopefully not quite as bad. Basically, the idea is:
- to making rendering tabular data a little easier by encapsulating some repetitive parts.
- make working with tabular data a lot easier when it comes to the user interacting with the table.
You would define a table, which is sort of a cross between a model and a form, define it’s columns (i.e. fields), and then tell it how you want to sort, filter and group the data.
Some snippets of actual code should probably explain it best.
First, let’s define a table:
import django_tables as tables
class BookTable(tables.ModelTable):
id = tables.Column(sortable=False, visible=False)
book_name = tables.Column(name='title')
author = tables.Column(data='author__name')
class Meta:
model = Book
The table is based on the Book model; thus, it will have a column for each model field, and in addition the locally defined columns will override the default ones, or add to them, very much like newforms works (you’ll find that even the internals are at times very similar to the newforms code).
So, now that we have defined the table, let’s create an instance:
initial_queryset = Book.objects.all()
books = BookTable(
initial_queryset,
order_by=request.GET.get('sort', 'title'))
We tell the table to operate on the full book data set, and to order it by whatever the user sends along via the query string, or fall back to the default sort order based on the title (book_name) column.
Finally, you would send the table to the template:
return render_to_response('table.html', {'table': books})
Where it is easy to print it out:
<table>
<tr>
{% for column in table.columns %}
<th>
{% if column.sortable %}
<a href="?sort={{ column.name_toggled }}">
{{ column }}
</a>
{% if column.is_ordered_reverse %}
<img src="up.png" />
{% else %}
<img src="down.png" />
{% endif %}
{% else %}
{{ column }}
{% endif %}
</th>
{% endfor %}
</tr>
{% for row in table.rows %}
<tr>
{% for value in row %}
<td>{{ value }}<td>
{% endfor %}
</tr>
{% endfor %}
</table>
The above template code generically renders any table you give it, restricted to it’s visible columns, and allows each column to be sorted in ascending or descending order (so long sorting is not disabled for a column). It gives you those nice arrow icons too.
At this point you will probably wonder if it’s not a lot simpler to just say:
Books.objects.all().order_by(request.GET.get('sort'))
And you’d be right, it is, and I indicated as much at the beginning of the post. There are however a couple of nice things about the table abstraction:
- While Django’s ORM already protects you against SQL injections, you still might like to play it safe and limit the possible values of the sort parameter (which will also ensure users won’t be able to guess your database schema by trying different values). Using django-tables, this is built in.
- It easily allows you to expose your fields under a different name, e.g. date instead of published_at. True, it’s just cosmetics, but personally I am (probably unhealthily) particular about stuff like that.
- Both previous points are especially relevant when you want to order via a relationship, e.g. author__birthdate. The double underscore doesn’t look all that nice, gives a rather clear insight into your database layout and also exposes that you are using Django, which may be undesirable.
- It easily allows you to move control over what fields to expose from your templates into python. Your templates will need to deal to a lesser extend with what to render, but rather on how to render it.
- Boilerplate code required to let the user sort the table, particularly when it comes to allowing toggling between descending/ascending, or even multiple sort fields, is reduced.
Additionally, it might be worth noting that there is a non-model implementation as well (use Table instead of ModelTable), that bascially does the same thing with static python data, e.g. a list of dicts.
To be perfectly honest, at this point I have no idea myself if much of this makes any sense or how useful it actually is. The fact that I haven’t really thought that much beyond the (already implemented) sorting functionality is not helping either (i.e. grouping, filtering…). I am going to toy with it for a bit though, and you are invited to do so too.
The project is maintained in bazaar and can be retrieved via Launchpad:
bzr branch lp:django-tables
There is also a source code browser, where you’ll also find the readme with a lot more information.
For questions/discussions/support use the django-apps Google Group and prefix your message with (tables).