forked from plumdog/flask_table
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathREADME
397 lines (283 loc) · 12.1 KB
/
README
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
Flask Table
===========
Because writing HTML is fiddly and all of your tables are basically the
same.
Quick Start
===========
.. code:: python
# import things
from flask_table import Table, Col
# Declare your table
class ItemTable(Table):
name = Col('Name')
description = Col('Description')
# Get some objects
class Item(object):
def __init__(self, name, description):
self.name = name
self.description = description
items = [Item('Name1', 'Description1'),
Item('Name2', 'Description2'),
Item('Name3', 'Description3')]
# Or, equivalently, some dicts
items = [dict(name='Name1', description='Description1'),
dict(name='Name2', description='Description2'),
dict(name='Name3', description='Description3')]
# Or, more likely, load items from your database with something like
items = ItemModel.query.all()
# Populate the table
table = ItemTable(items)
# Print the html
print(table.__html__())
# or just {{ table }} from within a Jinja template
Which gives something like:
.. code:: html
<table>
<thead><tr><th>Name</th><th>Description</th></tr></thead>
<tbody>
<tr><td>Name1</td><td>Description1</td></tr>
<tr><td>Name2</td><td>Description2</td></tr>
<tr><td>Name3</td><td>Description3</td></tr>
</tbody>
</table>
Extra things:
-------------
- The attribute used for each column in the declaration of the column
is used as the default thing to lookup in each item.
- The thing that you pass when you populate the table must:
- be iterable
- contain dicts or objects - there's nothing saying it can't contain
some of each
- There are also LinkCol and ButtonCol that allow links and buttons,
which is where the Flask-specific-ness comes in.
- There are also DateCol and DatetimeCol that format dates and
datetimes.
- Oh, and BoolCol, which does Yes/No.
- But most importantly, Col is easy to subclass.
Included Col Types
==================
- ```OptCol`` <#more-about-optcol>`__ - converts values according to a
dictionary of choices. Eg for turning stored codes into human
readable text.
- ```BoolCol`` <#more-about-boolcol>`__ (subclass of OptCol) - converts
values to yes/no.
- ```DateCol`` <#more-about-datecol>`__ - for dates (uses
``format_date`` from ``babel.dates``).
- ```DatetimeCol`` <#more-about-datetimecol>`__ - for date-times (uses
``format_datetime`` from ``babel.dates``).
- ```LinkCol`` <#more-about-linkcol>`__ - creates a link by specifying
an endpoint and url\_kwargs.
- ```ButtonCol`` <#more-about-buttoncol>`__ (subclass of LinkCol)
creates a button that posts the the given address.
More about ``OptCol``
---------------------
When creating the column, you pass some ``choices``. This should be a
dict with the keys being the values that will be found on the item's
attribute, and the values will be the text to be displayed.
You can also set a ``default_key``, or a ``default_value``. The default
value will be used if the value found from the item isn't in the choices
dict. The default key works in much the same way, but means that if your
default is already in your choices, you can just point to it rather than
repeat it.
And you can use ``coerce_fn`` if you need to alter the value from the
item before looking it up in the dict.
More about ``BoolCol``
----------------------
A subclass of ``OptCol`` where the ``choices`` are:
.. code:: python
{True: 'Yes', False: 'No'}
and the ``coerce_fn`` is ``bool``. So the value from the item is coerced
to a ``bool`` and then looked up in the choices to get the text to
display.
[[Possible future work: mark 'Yes' and 'No' for translation.]]
[[Possible future work: make it easier to override 'Yes' and 'No'.]]
[[Possible future work: add a ``BoolNaCol`` or similar that has a
separate option for ``None``]]
More about ``DateCol``
----------------------
Formats a date from the item. Can specify a ``date_format`` to use,
which defaults to ``'short'``, which is passed to
``babel.dates.format_date``.
More about ``DatetimeCol``
--------------------------
Formats a datetime from the item. Can specify a ``datetime_format`` to
use, which defaults to ``'short'``, which is passed to
``babel.dates.format_datetime``.
More about ``LinkCol``
----------------------
Gives a way of putting a link into a ``td``. You must specify an
``endpoint`` for the url. You should also specify some ``url_kwargs``.
This should be a dict which will be passed as the second argument of
``url_for``, except the values will be treated as attributes to be
looked up on the item. These keys obey the same rules as elsewhere, so
can be things like ``'category.name'`` or ``('category', 'name')``.
[[Possible future work: make it so some constants can be passed as part
of the ``url_kwargs``]]
The text for the link is acquired in *almost* the same way as with other
columns. However, other columns can be given no ``attr`` or
``attr_list`` and will use the attribute that the column was given in
the table class, but ``LinkCol`` does not, and instead falls back to the
heading of the column. This make more sense for things like an "Edit"
link.
[[Possible future work: make it so you can specify a specific fallback
for the ``td`` that is different to the ``th``]]
[[Possible future work: make it so you can specify attributes for the
HTML anchor element.]]
More about ``ButtonCol``
------------------------
Has all the same options as ``LinkCol`` but instead adds a form and a
button that gets posted to the url.
[[Possible future work: make it so you can specify hidden fields to be
added into the form.]]
[[Possible future work: make it so you can specify attributes for the
HTML form or button elements.]]
Subclassing Col
===============
(Look in examples/subclassing.py for a more concrete example)
Suppose our item has an attribute, but we don't want to output the value
directly, we need to alter it first. If the value that we get from the
item gives us all the information we need, then we can just override the
td\_format method:
.. code:: python
class LangCol(Col):
def td_format(self, content):
if content == 'en_GB':
return 'British English'
elif content == 'de_DE':
return 'German'
elif content == 'fr_FR':
return 'French'
else:
return 'Not Specified'
If you need access to all of information in the item, then we can go a
stage earlier in the process and override the td\_contents method:
.. code:: python
from flask import Markup
def td_contents(self, i, attr_list):
# by default this does
# return self.td_format(self.from_attr_list(i, attr_list))
return Markup.escape(self.from_attr_list(i, attr_list) + ' for ' + item.name)
At present, you do still need to be careful about escaping things as you
override these methods. Also, because of the way that the Markup class
works, you need to be careful about how you concatenate these with other
strings.
Setting a class on the ``<table>`` element
==========================================
If you set a classes attribute on the Table class, this gets added as a
class on the ``<table>`` element. The classes attribute should be an
iterable of strings, all of which will be added.
For example, if:
.. code:: python
class MyTable(Table):
classes = ['class1', 'class2']
...
Then the table created would be:
.. code:: html
<table class="class1 class2">
...
</table>
Manipulating ``<tr>``\ s
========================
(Look in examples/rows.py for a more concrete example)
Suppose you want to change something about the tr element for some or
all items. You can do this by overriding your table's ``tr_format``
method. By default, this method returns:
.. code:: python
'<tr>{}</tr>'
which betrays the fact that it has ``.format()`` called on it, to put in
the tds. If you override the method, keep that in mind.
So, we might want to use something like:
.. code:: python
class ItemTable(Table):
name = Col('Name')
description = Col('Description')
def tr_format(self, item):
if item.important():
return '<tr class="important">{}</tr>'
else:
return '<tr>{}</tr>'
which would give all trs for items that returned a true value for the
``important()`` method, a class of "important".
Dynamically Creating Tables
===========================
(Look in examples/dynamic.py for a more concrete example)
You can define a table dynamically too.
.. code:: python
TableCls = create_table('TableCls')\
.add_column('name', Col('Name'))\
.add_column('description', Col('Description'))
which is equivalent to
.. code:: python
class TableCls(Table):
name = Col('Name')
description = Col('Description')
but makes it easier to add columns dynamically.
For example, you may wish to only add a column based on a condition.
.. code:: python
TableCls = create_table('TableCls')\
.add_column('name', Col('Name'))
if condition:
TableCls.add_column('description', Col('Description'))
which is equivalent to
.. code:: python
class TableCls(Table):
name = Col('Name')
description = Col('Description', show=condition)
thanks to the ``show`` option. Use whichever you think makes your code
more readable. Though you may still need the dynamic option for
something like
.. code:: python
TableCls = create_table('TableCls')
for i in range(num):
TableCls.add_column(str(i), Col(str(i)))
Sortable Tables
===============
(Look in examples/sortable.py for a more concrete example)
Define a table and set its allow\_sort attribute to True. Now all
columns will be default try to turn their header into a link for
sorting, unless you set allow\_sort to False for a column.
You also must declare a sort\_url method for that table. Given a
col\_key, this determines the url for link in the header. If reverse is
True, then that means that the table has just been sorted by that column
and the url can adjust accordingly, ie to now give the address for the
table sorted in the reverse direction. It is, however, entirely up to
your flask view method to interpret the values given to it from this url
and to order the results before giving the to the table. The table
itself will not do any reordering of the items it is given.
.. code:: python
class SortableTable(Table):
name = Col('Name')
allow_sort = True
def sort_url(self, col_key, reverse=False):
if reverse:
direction = 'desc'
else:
direction = 'asc'
return url_for('index', sort=col_key, direction=direction)
The Examples
============
The ``examples`` directory contains a few pieces of sample code to show
some of the concepts and features. They are all intended to be runnable.
Some of them just output the code they generate, but some (just one,
``sortable.py``, at present) actually creates a Flask app that you can
access.
You should be able to just run them directly with ``python``, but if you
have cloned the repository for the sake of dev, and created a
virtualenv, you may find that they generate an import error for
``flask_table``. This is because ``flask_table`` hasn't been installed,
and can be rectified by running something like
``PYTHONPATH=.:./lib/python3.3/site-packages python examples/simple.py``,
which will use the local version of ``flask_table`` including any
changes.
Also, if there is anything that you think is not clear and would be
helped by an example, please just ask and I'll happily write one. Only
you can help me realise which bits are tricky or non-obvious and help me
to work on explaining the bits that need explaining.
Other Things
============
At the time of first writing, I was not aware of the work of
Django-Tables. However, I have now found it and started adapting ideas
from it, where appropriate. For example, allowing items to be dicts as
well as objects.
.. |Build Status| image:: https://travis-ci.org/plumdog/flask_table.svg?branch=master
:target: https://travis-ci.org/plumdog/flask_table