71ae0965df509240e040a991c34329b190a3d1c3
[django-tables2.git] / README
1 django-tables\r
2 =============\r
3 \r
4 A Django QuerySet renderer.\r
5 \r
6 Installation\r
7 ------------\r
8 \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
12 \r
13 Running the test suite\r
14 ----------------------\r
15 \r
16 The test suite uses py.test (from http://codespeak.net/py/dist/test.html).\r
17 \r
18 Working with Tables\r
19 -------------------\r
20 \r
21 A table class looks very much like a form:\r
22 \r
23     import django_tables as tables\r
24     class CountryTable(tables.Table):\r
25         name = tables.Column(verbose_name="Country Name")\r
26         population = tables.Column(sortable=False, visible=False)\r
27         time_zone = tables.Column(name="tz", default="UTC+1")\r
28 \r
29 Instead of fields, you declare a column for every piece of data you want to\r
30 expose to the user.\r
31 \r
32 To use the table, create an instance:\r
33 \r
34     countries = CountryTable([{'name': 'Germany', population: 80},\r
35                               {'name': 'France', population: 64}])\r
36 \r
37 Decide how the table should be sorted:\r
38 \r
39     countries.order_by = ('name',)\r
40     assert [row.name for row in countries.row] == ['France', 'Germany']\r
41 \r
42     countries.order_by = ('-population',)\r
43     assert [row.name for row in countries.row] == ['Germany', 'France']\r
44 \r
45 If you pass the table object along into a template, you can do:\r
46 \r
47     {% for column in countries.columns %}\r
48         {{column}}\r
49     {% endfor %}\r
50 \r
51 Which will give you:\r
52 \r
53     Country Name\r
54     Timezone\r
55 \r
56 Note that ``population`` is skipped (as it has ``visible=False``), that the\r
57 declared verbose name for the ``name`` column is used, and that ``time_zone``\r
58 is converted into a more beautiful string for output automatically.\r
59 \r
60 There are few requirements for the source data of a table. It should be an\r
61 iterable with dict-like objects. Values found in the source data that are\r
62 not associated with a column are ignored, missing values are replaced by\r
63 the column default or None.\r
64 \r
65 Common Workflow\r
66 ~~~~~~~~~~~~~~~\r
67 \r
68 Usually, you are going to use a table like this. Assuming ``CountryTable``\r
69 is defined as above, your view will create an instance and pass it to the\r
70 template:\r
71 \r
72     def list_countries(request):\r
73         data = ...\r
74         countries = CountryTable(data, order_by=request.GET.get('sort'))\r
75         return render_to_response('list.html', {'table': countries})\r
76 \r
77 Note that we are giving the incoming "sort" query string value directly to\r
78 the table, asking for a sort. All invalid column names will (by default) be\r
79 ignored. In this example, only "name" and "tz" are allowed, since:\r
80 \r
81     * "population" has sortable=False\r
82     * "time_zone" has it's name overwritten with "tz".\r
83 \r
84 Then, in the "list.html" template, write:\r
85 \r
86     <table>\r
87     <tr>\r
88         {% for column in table.columns %}\r
89         <th><a href="?sort={{ column.name }}">{{ column }}</a></th>\r
90         {% endfor %}\r
91     </tr>\r
92     {% for row in table.rows %}\r
93         {% for value in row %}\r
94             <td>{{ value }}<td>\r
95         {% endfor %}\r
96     {% endfor %}\r
97     </table>\r
98 \r
99 This will output the data as an HTML table. Note how the table is now fully\r
100 sortable, since our link passes along the column name via the querystring,\r
101 which in turn will be used by the server for ordering. ``order_by`` accepts\r
102 comma-separated strings as input, and "{{ table.order_by }}" will be rendered\r
103 as a such a string.\r
104 \r
105 Instead of the iterator, you can use your knowledge of the table structure to\r
106 access columns directly:\r
107 \r
108         {% if table.columns.tz.visible %}\r
109             {{ table.columns.tz }}\r
110         {% endfor %}\r
111 \r
112 \r
113 Dynamic Data\r
114 ~~~~~~~~~~~~\r
115 \r
116 If any value in the source data is a callable, it will be passed it's own\r
117 row instance and is expected to return the actual value for this particular\r
118 table cell.\r
119 \r
120 Similarily, the colunn default value may also be callable that will takes\r
121 the row instance as an argument (representing the row that the default is\r
122 needed for).\r
123 \r
124 \r
125 Table Options\r
126 -------------\r
127 \r
128 Table-specific options are implemented using the same inner ``Meta`` class\r
129 concept as known from forms and models in Django:\r
130 \r
131     class MyTable(tables.Table):\r
132         class Meta:\r
133             sortable = True\r
134 \r
135 Currently, for non-model tables, the only supported option is ``sortable``.\r
136 Per default, all columns are sortable, unless a column specifies otherwise.\r
137 This meta option allows you to overwrite the global default for the table.\r
138 \r
139 \r
140 ModelTables\r
141 -----------\r
142 \r
143 Like forms, tables can also be used with models:\r
144 \r
145     class CountryTable(tables.ModelTable):\r
146         id = tables.Column(sortable=False, visible=False)\r
147         class Meta:\r
148             model = Country\r
149             exclude = ['clicks']\r
150 \r
151 The resulting table will have one column for each model field, with the\r
152 exception of "clicks", which is excluded. The column for "id" is overwritten\r
153 to both hide it and deny it sort capability.\r
154 \r
155 When instantiating a ModelTable, you usually pass it a queryset to provide\r
156 the table data:\r
157 \r
158     qs = Country.objects.filter(continent="europe")\r
159     countries = CountryTable(qs)\r
160 \r
161 However, you can also just do:\r
162 \r
163     countries = CountryTable()\r
164 \r
165 and all rows exposed by the default manager of the model the table is based\r
166 on will be used.\r
167 \r
168 If you are using model inheritance, then the following also works:\r
169 \r
170     countries = CountryTable(CountrySubclass)\r
171 \r
172 Note that while you can pass any model, it really only makes sense if the\r
173 model also provides fields for the columns you have defined.\r
174 \r
175 If you just want to use ModelTables, but without auto-generated columns,\r
176 you do not have to list all model fields in the ``exclude`` Meta option.\r
177 Instead, simply don't specify a model.\r
178 \r
179 \r
180 Custom Columns\r
181 ~~~~~~~~~~~~~~\r
182 \r
183 You an add custom columns to your ModelTable that are not based on actual\r
184 model fields:\r
185 \r
186     class CountryTable(tables.ModelTable):\r
187         custom = tables.Column(default="foo")\r
188         class Meta:\r
189             model = Country\r
190 \r
191 Just make sure your model objects do provide an attribute with that name.\r
192 Functions are also supported, so ``Country.custom`` could be a callable.\r
193 \r
194 \r
195 Spanning relationships\r
196 ~~~~~~~~~~~~~~~~~~~~~~\r
197 \r
198 Let's assume you have a ``Country`` model, with a foreignkey ``capital``\r
199 pointing to the ``City`` model. While displaying a list of countries,\r
200 you might want want to link to the capital's geographic location, which is\r
201 stored in ``City.geo`` as a ``(lat, long)`` tuple, on Google Maps.\r
202 \r
203 ModelTables support relationship spanning syntax of Django's database api:\r
204 \r
205     class CountryTable(tables.ModelTable):\r
206         city__geo = tables.Column(name="geo")\r
207 \r
208 This will add a column named "geo", based on the field by the same name\r
209 from the "city" relationship. Note that the name used to define the column\r
210 is what will be used to access the data, while the name-overwrite passed to\r
211 the column constructor just defines a prettier name for us to work with.\r
212 This is to be consistent with auto-generated columns based on model fields,\r
213 where the field/column name naturally equals the source name.\r
214 \r
215 However, to make table defintions more visually appealing and easier to\r
216 read, an alternative syntax is supported: setting the column ``data``\r
217 property to the appropriate string.\r
218 \r
219     class CountryTable(tables.ModelTable):\r
220         geo = tables.Column(data='city__geo')\r
221 \r
222 Note that you don't need to define a relationship's fields as columns\r
223 simple to access them:\r
224 \r
225     for country in countries.rows:\r
226         print country.city.id\r
227         print country.city.geo\r
228         print country.city.founder.name\r
229 \r
230 \r
231 ModelTable Specialities\r
232 ~~~~~~~~~~~~~~~~~~~~~~~\r
233 \r
234 ModelTables currently have some restrictions with respect to ordering:\r
235 \r
236     * Custom columns not based on a model field do not support ordering,\r
237       regardless of the ``sortable`` property (it is ignored).\r
238 \r
239     * A ModelTable column's ``default`` or ``data`` value does not affect\r
240       ordering. This differs from the non-model table behaviour.\r
241 \r
242 If a column is mapped to a method on the model, that method will be called\r
243 without arguments. This behavior differs from non-model tables, where a\r
244 row object will be passed.\r
245 \r
246 If you are using callables (e.g. for the ``default`` or ``data`` column\r
247 options), they will generally be run when a row is accessed, and\r
248 possible repeatetly when accessed more than once. This behavior differs from\r
249 non-model tables, where they would be called once, when the table is\r
250 generated.\r
251 \r
252 Columns\r
253 -------\r
254 \r
255 Columns are what defines a table. Therefore, the way you configure your\r
256 columns determines to a large extend how your table operates.\r
257 \r
258 ``django_tables.columns`` currently defines three classes, ``Column``,\r
259 ``TextColumn`` and ``NumberColumn``. However, the two subclasses currently\r
260 don't do anything special at all, so you can simply use the base class.\r
261 While this will likely change in the future (e.g. when grouping is added),\r
262 the base column class will continue to work by itself.\r
263 \r
264 There are no required arguments. The following is fine:\r
265 \r
266     class MyTable(tables.Table):\r
267         c = tables.Column()\r
268 \r
269 It will result in a column named "c" in the table. You can specify the\r
270 ``name`` to override this:\r
271 \r
272     c = tables.Column(name="count")\r
273 \r
274 The column is now called and accessed via "count", although the table will\r
275 still use "c" to read it's values from the source. You can however modify\r
276 that as well, by specifying ``data``:\r
277 \r
278     c = tables.Column(name="count", data="count")\r
279 \r
280 For most practicual purposes, "c" is now meaningless. While in most cases\r
281 you will just define your column using the name you want it to have, the\r
282 above is useful when working with columns automatically generated from\r
283 models:\r
284 \r
285     class BookTable(tables.ModelTable):\r
286         book_name = tables.Column(name="name")\r
287         author = tables.Column(data="info__author__name")\r
288         class Meta:\r
289             model = Book\r
290 \r
291 The overwritten ``book_name`` field/column will now be exposed as the\r
292 cleaner "name", and the new "author" column retrieves it's values from\r
293 ``Book.info.author.name``.\r
294 \r
295 Note: ``data`` may also be a callable which will be passed a row object.\r
296 \r
297 Apart from their internal name, you can define a string that will be used\r
298 when for display via ``verbose_name``:\r
299 \r
300     pubdate = tables.Column(verbose_name="Published")\r
301 \r
302 The verbose name will be used, for example, if you put in a template:\r
303 \r
304     {{ column }}\r
305 \r
306 If you don't want a column to be sortable by the user:\r
307 \r
308     pubdate = tables.Column(sortable=False)\r
309 \r
310 If you don't want to expose a column (but still require it to exist, for\r
311 example because it should be sortable nonetheless):\r
312 \r
313     pubdate = tables.Column(visible=False)\r
314 \r
315 The column and it's values will now be skipped when iterating through the\r
316 table, although it can still be accessed manually.\r
317 \r
318 Finally, you can specify default values for your columns:\r
319 \r
320     health_points = tables.Column(default=100)\r
321 \r
322 Note that how the default is used and when it is applied differs between\r
323 static and ModelTables.\r
324 \r
325 \r
326 The table.columns container\r
327 ---------------------------\r
328 \r
329 While you can iterate through ``columns`` and get all the currently visible\r
330 columns, it further provides features that go beyond a simple iterator.\r
331 \r
332 You can access all columns, regardless of their visibility, through\r
333 ``columns.all``.\r
334 \r
335 ``columns.sortable`` is a handy shortcut that exposes all columns which's\r
336 ``sortable`` attribute is True. This can be very useful in templates, when\r
337 doing {% if column.sortable %} can conflict with {{ forloop.last }}.\r
338 \r
339 \r
340 Tables and Pagination\r
341 ---------------------\r
342 \r
343 If your table has a large number of rows, you probably want to paginate\r
344 the output. There are two distinct approaches.\r
345 \r
346 First, you can just paginate over ``rows`` as you would do with any other\r
347 data:\r
348 \r
349     table = MyTable(queryset)\r
350     paginator = Paginator(table.rows, 10)\r
351     page = paginator.page(1)\r
352 \r
353 You're not necessarily restricted to Django's own paginator (or subclasses) -\r
354 any paginator should work with this approach, so long it only requires\r
355 ``rows`` to implement ``len()``, slicing, and, in the case of ModelTables, a\r
356 ``count()`` method. The latter means that the ``QuerySetPaginator`` also\r
357 works as expected.\r
358 \r
359 Alternatively, you may use the ``paginate`` feature:\r
360 \r
361     table = MyTable(queryset)\r
362     table.paginate(QuerySetPaginator, page=1, 10, padding=2)\r
363     for row in table.rows.page():\r
364         pass\r
365     table.paginator                # new attributes\r
366     table.page\r
367 \r
368 The table will automatically create an instance of ``QuerySetPaginator``,\r
369 passing it's own data as the first argument and additionally any arguments\r
370 you have specified, except for ``page``. You may use any paginator, as long\r
371 as it follows the Django protocol:\r
372 \r
373     * Take data as first argument.\r
374     * Support a page() method returning an object with an ``object_list``\r
375       attribute, exposing the paginated data.\r
376 \r
377 Note that due to the abstraction layer that django-tables represents, it is\r
378 not necessary to use Django's QuerySetPaginator with ModelTables. Since the\r
379 table knows that it holds a queryset, it will automatically choose to use\r
380 count() to determine the data length (which is exactly what\r
381 QuerySetPaginator would do).\r
382 \r
383 Ordering\r
384 --------\r
385 \r
386 The syntax is similar to that of the Django database API. Order may be\r
387 specified a list (or tuple) of column names. If prefixed with a hyphen, the\r
388 ordering for that particular column will be in reverse order.\r
389 \r
390 Random ordering is currently not supported.\r
391 \r
392 Interacting with order\r
393 ~~~~~~~~~~~~~~~~~~~~~~\r
394 \r
395 Letting the user change the order of a table is a common scenario. With\r
396 respect to Django, this means adding links to your table output that will\r
397 send off the appropriate arguments to the server. django-tables attempts\r
398 to help with you that.\r
399 \r
400 A bound column, that is a colum accessed through a table instance, provides\r
401 the following attributes:\r
402 \r
403     - ``name_reversed`` will simply return the column name prefixed with a\r
404       hyphen; this is useful in templates, where string concatenation can\r
405       at times be difficult.\r
406 \r
407     - ``name_toggled`` checks the tables current order, and will then\r
408     return the column either prefixed with an hyphen (for reverse ordering)\r
409     or without, giving you the exact opposite order. If the column is\r
410     currently not ordered, it will start off in non-reversed order.\r
411 \r
412 It is easy to be confused about the difference between the ``reverse`` and\r
413 ``toggle`` terminology. django-tables tries to put normal/reverse-order\r
414 abstraction on top of "ascending/descending", where as normal order could\r
415 potentially mean either ascending or descending, depending on the column.\r
416 \r
417 Commonly, you see tables that indicate what columns they are currently\r
418 ordered by using little arrows. To implement this:\r
419 \r
420     - ``is_ordered``: Returns True if the column is in the current\r
421     ``order_by``, regardless of the polarity.\r
422 \r
423     - ``is_ordered_reverse``, ``is_ordered_straight``: Returns True if the\r
424     column is ordered in reverse or non-reverse, respectively, otherwise\r
425     False.\r
426 \r
427 The above is usually enough for most simple cases, where tables are only\r
428 ordered by a single column. For scenarios in which multi-column order is\r
429 used, additional attributes are available:\r
430 \r
431     - ``order_by``: Return the current order, but with the current column\r
432     set to normal ordering. If the current column is not already part of\r
433     the order, it is appended. Any existing columns in the order are\r
434     maintained as-is.\r
435 \r
436     - ``order_by_reversed``, ``order_by_toggled``: Similarly, return the\r
437     table's current ``order_by`` with the column set to reversed or toggled,\r
438     respectively. Again, it is appended if not already ordered.\r
439 \r
440 Additionally, ``table.order_by.toggle()`` may also be useful in some cases:\r
441 It will toggle all order columns and should thus give you the exact\r
442 opposite order.\r
443 \r
444 The following is a simple example of single-column ordering. It shows a list\r
445 of sortable columns, each clickable, and an up/down arrow next to the one\r
446 that is currently used to sort the table.\r
447 \r
448     Sort by:\r
449     {% for column in table.columns %}\r
450         {% if column.sortable %}\r
451             <a href="?sort={{ column.name_toggled }}">{{ column }}</a>\r
452             {% if column.is_ordered_straight %}<img src="down.png" />{% endif %}\r
453             {% if column.is_ordered_reverse %}<img src="up.png" />{% endif %}\r
454         {% endif %}\r
455     {% endfor %}\r
456 \r
457 \r
458 Error handling\r
459 --------------\r
460 \r
461 Passing incoming query string values from the request directly to the\r
462 table constructor is a common thing to do. However, such data can easily\r
463 be invalid, be it that a user manually modified it, or someone put up a\r
464 broken link. In those cases, you usually would not want to raise an\r
465 exception (nor be notified by Django's error notification mechanism) -\r
466 there is nothing you could do anyway.\r
467 \r
468 Because of this, such errors will by default be silently ignored. For\r
469 example, if one out of three columns in an "order_by" is invalid, the other\r
470 two will still be applied:\r
471 \r
472     table.order_by = ('name', 'totallynotacolumn', '-date)\r
473     assert table.order_by = ('name', '-date)\r
474 \r
475 This ensures that the following table will be created regardless of the\r
476 string in "sort.\r
477 \r
478     table = MyTable(data, order_by=request.GET.get('sort'))\r
479 \r
480 However, if you want, you can disable this behaviour and have an exception\r
481 raised instead, using:\r
482 \r
483     import django_tables\r
484     django_tables.options.IGNORE_INVALID_OPTIONS = False\r
485 \r
486 \r
487 Template Utilities\r
488 ------------------\r
489 \r
490 If you want the give your users the ability to interact with your table (e.g.\r
491 change the ordering), you will need to create urls with the appropriate\r
492 queries. To simplify that process, django-tables comes with helpful\r
493 templatetag:\r
494 \r
495     {% set_url_param "sort" "name" %}       # ?sort=name\r
496     {% set_url_param "sort" %}              # delete "sort" param\r
497 \r
498 The template library can be found in 'django_modules.app.templates.tables'.\r
499 If you add ''django_modules.app' to your INSTALLED_APPS setting, you will\r
500 be able to do:\r
501 \r
502     {% load tables %}\r
503 \r
504 \r
505 TODO\r
506 ----\r
507     - as_html methods are all empty right now\r
508     - table.column[].values is a stub\r
509     - let columns change their default ordering (ascending/descending)\r
510     - filters\r
511     - grouping\r
512     - choices support for columns (internal column value will be looked up\r
513       for output)\r
514     - for columns that span model relationships, automatically generate\r
515       select_related(); this is important, since right now such an e.g.\r
516       order_by would cause rows to be dropped (inner join).\r
517     - initialize auto-generated columns with the relevant properties of the\r
518       model fields (verbose_name, editable=visible?, ...)\r
519     - remove support for callable fields? this has become obsolete since we\r
520       Column.data property; also, it's easy to make the call manually, or let\r
521       the template engine handle it\r
522     - tests could use some refactoring, they are currently all over the place\r
523     - what happens if duplicate column names are used? we currently don't\r
524       check for that at all