4 A Django QuerySet renderer.
\r
9 Adding django-tables to your INSTALLED_APPS settings is optional, it'll get
\r
10 you the ability to load some template utilities via {% load tables %}, but
\r
11 apart from that, ``import django_tables as tables`` should get you going.
\r
13 Running the test suite
\r
14 ----------------------
\r
16 The test suite uses nose:
\r
17 http://somethingaboutorange.com/mrl/projects/nose/
\r
22 A table class looks very much like a form:
\r
24 import django_tables as tables
\r
25 class CountryTable(tables.MemoryTable):
\r
26 name = tables.Column(verbose_name="Country Name")
\r
27 population = tables.Column(sortable=False, visible=False)
\r
28 time_zone = tables.Column(name="tz", default="UTC+1")
\r
30 Instead of fields, you declare a column for every piece of data you want to
\r
33 To use the table, create an instance:
\r
35 countries = CountryTable([{'name': 'Germany', population: 80},
\r
36 {'name': 'France', population: 64}])
\r
38 Decide how the table should be sorted:
\r
40 countries.order_by = ('name',)
\r
41 assert [row.name for row in countries.row] == ['France', 'Germany']
\r
43 countries.order_by = ('-population',)
\r
44 assert [row.name for row in countries.row] == ['Germany', 'France']
\r
46 If you pass the table object along into a template, you can do:
\r
48 {% for column in countries.columns %}
\r
52 Which will give you:
\r
57 Note that ``population`` is skipped (as it has ``visible=False``), that the
\r
58 declared verbose name for the ``name`` column is used, and that ``time_zone``
\r
59 is converted into a more beautiful string for output automatically.
\r
61 There are few requirements for the source data of a table. It should be an
\r
62 iterable with dict-like objects. Values found in the source data that are
\r
63 not associated with a column are ignored, missing values are replaced by
\r
64 the column default or None.
\r
69 Usually, you are going to use a table like this. Assuming ``CountryTable``
\r
70 is defined as above, your view will create an instance and pass it to the
\r
73 def list_countries(request):
\r
75 countries = CountryTable(data, order_by=request.GET.get('sort'))
\r
76 return render_to_response('list.html', {'table': countries})
\r
78 Note that we are giving the incoming "sort" query string value directly to
\r
79 the table, asking for a sort. All invalid column names will (by default) be
\r
80 ignored. In this example, only "name" and "tz" are allowed, since:
\r
82 * "population" has sortable=False
\r
83 * "time_zone" has it's name overwritten with "tz".
\r
85 Then, in the "list.html" template, write:
\r
89 {% for column in table.columns %}
\r
90 <th><a href="?sort={{ column.name }}">{{ column }}</a></th>
\r
93 {% for row in table.rows %}
\r
95 {% for value in row %}
\r
96 <td>{{ value }}</td>
\r
102 This will output the data as an HTML table. Note how the table is now fully
\r
103 sortable, since our link passes along the column name via the querystring,
\r
104 which in turn will be used by the server for ordering. ``order_by`` accepts
\r
105 comma-separated strings as input, and "{{ table.order_by }}" will be rendered
\r
106 as a such a string.
\r
108 Instead of the iterator, you can use your knowledge of the table structure to
\r
109 access columns directly:
\r
111 {% if table.columns.tz.visible %}
\r
112 {{ table.columns.tz }}
\r
119 If any value in the source data is a callable, it will be passed it's own
\r
120 row instance and is expected to return the actual value for this particular
\r
123 Similarily, the colunn default value may also be callable that will takes
\r
124 the row instance as an argument (representing the row that the default is
\r
131 Table-specific options are implemented using the same inner ``Meta`` class
\r
132 concept as known from forms and models in Django:
\r
134 class MyTable(tables.MemoryTable):
\r
138 Currently, for non-model tables, the only supported option is ``sortable``.
\r
139 Per default, all columns are sortable, unless a column specifies otherwise.
\r
140 This meta option allows you to overwrite the global default for the table.
\r
146 Like forms, tables can also be used with models:
\r
148 class CountryTable(tables.ModelTable):
\r
149 id = tables.Column(sortable=False, visible=False)
\r
152 exclude = ['clicks']
\r
154 The resulting table will have one column for each model field, with the
\r
155 exception of "clicks", which is excluded. The column for "id" is overwritten
\r
156 to both hide it and deny it sort capability.
\r
158 When instantiating a ModelTable, you usually pass it a queryset to provide
\r
161 qs = Country.objects.filter(continent="europe")
\r
162 countries = CountryTable(qs)
\r
164 However, you can also just do:
\r
166 countries = CountryTable()
\r
168 and all rows exposed by the default manager of the model the table is based
\r
171 If you are using model inheritance, then the following also works:
\r
173 countries = CountryTable(CountrySubclass)
\r
175 Note that while you can pass any model, it really only makes sense if the
\r
176 model also provides fields for the columns you have defined.
\r
178 If you just want to use ModelTables, but without auto-generated columns,
\r
179 you do not have to list all model fields in the ``exclude`` Meta option.
\r
180 Instead, simply don't specify a model.
\r
186 You an add custom columns to your ModelTable that are not based on actual
\r
189 class CountryTable(tables.ModelTable):
\r
190 custom = tables.Column(default="foo")
\r
194 Just make sure your model objects do provide an attribute with that name.
\r
195 Functions are also supported, so ``Country.custom`` could be a callable.
\r
198 Spanning relationships
\r
199 ~~~~~~~~~~~~~~~~~~~~~~
\r
201 Let's assume you have a ``Country`` model, with a foreignkey ``capital``
\r
202 pointing to the ``City`` model. While displaying a list of countries,
\r
203 you might want want to link to the capital's geographic location, which is
\r
204 stored in ``City.geo`` as a ``(lat, long)`` tuple, on Google Maps.
\r
206 ModelTables support relationship spanning syntax of Django's database api:
\r
208 class CountryTable(tables.ModelTable):
\r
209 city__geo = tables.Column(name="geo")
\r
211 This will add a column named "geo", based on the field by the same name
\r
212 from the "city" relationship. Note that the name used to define the column
\r
213 is what will be used to access the data, while the name-overwrite passed to
\r
214 the column constructor just defines a prettier name for us to work with.
\r
215 This is to be consistent with auto-generated columns based on model fields,
\r
216 where the field/column name naturally equals the source name.
\r
218 However, to make table defintions more visually appealing and easier to
\r
219 read, an alternative syntax is supported: setting the column ``data``
\r
220 property to the appropriate string.
\r
222 class CountryTable(tables.ModelTable):
\r
223 geo = tables.Column(data='city__geo')
\r
225 Note that you don't need to define a relationship's fields as separate
\r
226 columns if you already have a column for the relationship itself, i.e.:
\r
228 class CountryTable(tables.ModelTable):
\r
229 city = tables.Column()
\r
231 for country in countries.rows:
\r
232 print country.city.id
\r
233 print country.city.geo
\r
234 print country.city.founder.name
\r
237 ModelTable Specialities
\r
238 ~~~~~~~~~~~~~~~~~~~~~~~
\r
240 ModelTables currently have some restrictions with respect to ordering:
\r
242 * Custom columns not based on a model field do not support ordering,
\r
243 regardless of the ``sortable`` property (it is ignored).
\r
245 * A ModelTable column's ``default`` or ``data`` value does not affect
\r
246 ordering. This differs from the non-model table behaviour.
\r
248 If a column is mapped to a method on the model, that method will be called
\r
249 without arguments. This behavior differs from non-model tables, where a
\r
250 row object will be passed.
\r
252 If you are using callables (e.g. for the ``default`` or ``data`` column
\r
253 options), they will generally be run when a row is accessed, and
\r
254 possible repeatetly when accessed more than once. This behavior differs from
\r
255 non-model tables, where they would be called once, when the table is
\r
261 Columns are what defines a table. Therefore, the way you configure your
\r
262 columns determines to a large extend how your table operates.
\r
264 ``django_tables.columns`` currently defines three classes, ``Column``,
\r
265 ``TextColumn`` and ``NumberColumn``. However, the two subclasses currently
\r
266 don't do anything special at all, so you can simply use the base class.
\r
267 While this will likely change in the future (e.g. when grouping is added),
\r
268 the base column class will continue to work by itself.
\r
270 There are no required arguments. The following is fine:
\r
272 class MyTable(tables.MemoryTable):
\r
273 c = tables.Column()
\r
275 It will result in a column named "c" in the table. You can specify the
\r
276 ``name`` to override this:
\r
278 c = tables.Column(name="count")
\r
280 The column is now called and accessed via "count", although the table will
\r
281 still use "c" to read it's values from the source. You can however modify
\r
282 that as well, by specifying ``data``:
\r
284 c = tables.Column(name="count", data="count")
\r
286 For most practicual purposes, "c" is now meaningless. While in most cases
\r
287 you will just define your column using the name you want it to have, the
\r
288 above is useful when working with columns automatically generated from
\r
291 class BookTable(tables.ModelTable):
\r
292 book_name = tables.Column(name="name")
\r
293 author = tables.Column(data="info__author__name")
\r
297 The overwritten ``book_name`` field/column will now be exposed as the
\r
298 cleaner "name", and the new "author" column retrieves it's values from
\r
299 ``Book.info.author.name``.
\r
301 Note: ``data`` may also be a callable which will be passed a row object.
\r
303 Apart from their internal name, you can define a string that will be used
\r
304 when for display via ``verbose_name``:
\r
306 pubdate = tables.Column(verbose_name="Published")
\r
308 The verbose name will be used, for example, if you put in a template:
\r
312 If you don't want a column to be sortable by the user:
\r
314 pubdate = tables.Column(sortable=False)
\r
316 Sorting is also affected by ``direction``, which can be used to change the
\r
317 *default* sort direction to descending. Note that this option only indirectly
\r
318 translates to the actual direction. Normal und reverse order, the terms
\r
319 django-tables exposes, now simply mean different things.
\r
321 pubdate = tables.Column(direction='desc')
\r
323 If you don't want to expose a column (but still require it to exist, for
\r
324 example because it should be sortable nonetheless):
\r
326 pubdate = tables.Column(visible=False)
\r
328 The column and it's values will now be skipped when iterating through the
\r
329 table, although it can still be accessed manually.
\r
331 Finally, you can specify default values for your columns:
\r
333 health_points = tables.Column(default=100)
\r
335 Note that how the default is used and when it is applied differs between
\r
336 static and ModelTables.
\r
339 The table.columns container
\r
340 ---------------------------
\r
342 While you can iterate through ``columns`` and get all the currently visible
\r
343 columns, it further provides features that go beyond a simple iterator.
\r
345 You can access all columns, regardless of their visibility, through
\r
348 ``columns.sortable`` is a handy shortcut that exposes all columns which's
\r
349 ``sortable`` attribute is True. This can be very useful in templates, when
\r
350 doing {% if column.sortable %} can conflict with {{ forloop.last }}.
\r
353 Tables and Pagination
\r
354 ---------------------
\r
356 If your table has a large number of rows, you probably want to paginate
\r
357 the output. There are two distinct approaches.
\r
359 First, you can just paginate over ``rows`` as you would do with any other
\r
362 table = MyTable(queryset)
\r
363 paginator = Paginator(table.rows, 10)
\r
364 page = paginator.page(1)
\r
366 You're not necessarily restricted to Django's own paginator (or subclasses) -
\r
367 any paginator should work with this approach, so long it only requires
\r
368 ``rows`` to implement ``len()``, slicing, and, in the case of ModelTables, a
\r
369 ``count()`` method. The latter means that the ``QuerySetPaginator`` also
\r
372 Alternatively, you may use the ``paginate`` feature:
\r
374 table = MyTable(queryset)
\r
375 table.paginate(Paginator, 10, page=1, orphans=2)
\r
376 for row in table.rows.page():
\r
378 table.paginator # new attributes
\r
381 The table will automatically create an instance of ``QuerySetPaginator``,
\r
382 passing it's own data as the first argument and additionally any arguments
\r
383 you have specified, except for ``page``. You may use any paginator, as long
\r
384 as it follows the Django protocol:
\r
386 * Take data as first argument.
\r
387 * Support a page() method returning an object with an ``object_list``
\r
388 attribute, exposing the paginated data.
\r
390 Note that due to the abstraction layer that django-tables represents, it is
\r
391 not necessary to use Django's QuerySetPaginator with ModelTables. Since the
\r
392 table knows that it holds a queryset, it will automatically choose to use
\r
393 count() to determine the data length (which is exactly what
\r
394 QuerySetPaginator would do).
\r
399 The syntax is similar to that of the Django database API. Order may be
\r
400 specified a list (or tuple) of column names. If prefixed with a hyphen, the
\r
401 ordering for that particular column will be in reverse order.
\r
403 Random ordering is currently not supported.
\r
405 Interacting with order
\r
406 ~~~~~~~~~~~~~~~~~~~~~~
\r
408 Letting the user change the order of a table is a common scenario. With
\r
409 respect to Django, this means adding links to your table output that will
\r
410 send off the appropriate arguments to the server. django-tables attempts
\r
411 to help with you that.
\r
413 A bound column, that is a colum accessed through a table instance, provides
\r
414 the following attributes:
\r
416 - ``name_reversed`` will simply return the column name prefixed with a
\r
417 hyphen; this is useful in templates, where string concatenation can
\r
418 at times be difficult.
\r
420 - ``name_toggled`` checks the tables current order, and will then
\r
421 return the column either prefixed with an hyphen (for reverse ordering)
\r
422 or without, giving you the exact opposite order. If the column is
\r
423 currently not ordered, it will start off in non-reversed order.
\r
425 It is easy to be confused about the difference between the ``reverse`` and
\r
426 ``toggle`` terminology. django-tables tries to put normal/reverse-order
\r
427 abstraction on top of "ascending/descending", where as normal order could
\r
428 potentially mean either ascending or descending, depending on the column.
\r
430 Commonly, you see tables that indicate what columns they are currently
\r
431 ordered by using little arrows. To implement this:
\r
433 - ``is_ordered``: Returns True if the column is in the current
\r
434 ``order_by``, regardless of the polarity.
\r
436 - ``is_ordered_reverse``, ``is_ordered_straight``: Returns True if the
\r
437 column is ordered in reverse or non-reverse, respectively, otherwise
\r
440 The above is usually enough for most simple cases, where tables are only
\r
441 ordered by a single column. For scenarios in which multi-column order is
\r
442 used, additional attributes are available:
\r
444 - ``order_by``: Return the current order, but with the current column
\r
445 set to normal ordering. If the current column is not already part of
\r
446 the order, it is appended. Any existing columns in the order are
\r
449 - ``order_by_reversed``, ``order_by_toggled``: Similarly, return the
\r
450 table's current ``order_by`` with the column set to reversed or toggled,
\r
451 respectively. Again, it is appended if not already ordered.
\r
453 Additionally, ``table.order_by.toggle()`` may also be useful in some cases:
\r
454 It will toggle all order columns and should thus give you the exact
\r
457 The following is a simple example of single-column ordering. It shows a list
\r
458 of sortable columns, each clickable, and an up/down arrow next to the one
\r
459 that is currently used to sort the table.
\r
462 {% for column in table.columns %}
\r
463 {% if column.sortable %}
\r
464 <a href="?sort={{ column.name_toggled }}">{{ column }}</a>
\r
465 {% if column.is_ordered_straight %}<img src="down.png" />{% endif %}
\r
466 {% if column.is_ordered_reverse %}<img src="up.png" />{% endif %}
\r
474 Passing incoming query string values from the request directly to the
\r
475 table constructor is a common thing to do. However, such data can easily
\r
476 be invalid, be it that a user manually modified it, or someone put up a
\r
477 broken link. In those cases, you usually would not want to raise an
\r
478 exception (nor be notified by Django's error notification mechanism) -
\r
479 there is nothing you could do anyway.
\r
481 Because of this, such errors will by default be silently ignored. For
\r
482 example, if one out of three columns in an "order_by" is invalid, the other
\r
483 two will still be applied:
\r
485 table.order_by = ('name', 'totallynotacolumn', '-date)
\r
486 assert table.order_by = ('name', '-date)
\r
488 This ensures that the following table will be created regardless of the
\r
491 table = MyTable(data, order_by=request.GET.get('sort'))
\r
493 However, if you want, you can disable this behaviour and have an exception
\r
494 raised instead, using:
\r
496 import django_tables
\r
497 django_tables.options.IGNORE_INVALID_OPTIONS = False
\r
503 If you want the give your users the ability to interact with your table (e.g.
\r
504 change the ordering), you will need to create urls with the appropriate
\r
505 queries. To simplify that process, django-tables comes with a helpful
\r
508 {% set_url_param sort="name" %} # ?sort=name
\r
509 {% set_url_param sort="" %} # delete "sort" param
\r
511 The template library can be found in 'django_modules.app.templates.tables'.
\r
512 If you add ''django_modules.app' to your INSTALLED_APPS setting, you will
\r
517 Note: The tag requires the current request to be available as ``request``
\r
518 in the context (usually, this means activating the Django request context
\r