* Added pagination
[django-tables2.git] / django_tables / columns.py
1 # -*- coding: utf-8 -*-
2 from django.core.urlresolvers import reverse
3 from django.utils.encoding import force_unicode, StrAndUnicode
4 from django.utils.datastructures import SortedDict
5 from django.utils.text import capfirst
6 from django.utils.safestring import mark_safe
7 from django.template import RequestContext, Context, Template
8 from .utils import OrderBy, A, AttributeDict
9
10
11 class Column(object):
12     """Represents a single column of a table.
13
14     :class:`Column` objects control the way a column (including the cells that
15     fall within it) are rendered.
16
17     :param verbose_name: A pretty human readable version of the column name.
18         Typically this is used in the header cells in the HTML output.
19
20     :type accessor: :class:`basestring` or :class:`~.utils.Accessor`
21     :param accessor: An accessor that describes how to extract values for this
22         column from the :term:`table data`.
23
24     :param default: The default value for the column. This can be a value or a
25         callable object [1]_. If an object in the data provides :const:`None`
26         for a column, the default will be used instead.
27
28         The default value may affect ordering, depending on the type of
29         data the table is using. The only case where ordering is not
30         affected ing when a :class:`QuerySet` is used as the table data
31         (since sorting is performed by the database).
32
33         .. [1] The provided callable object must not expect to receive any
34            arguments.
35
36     :type visible: :class:`bool`
37     :param visible: If :const:`False`, this column will not be in HTML from
38         output generators (e.g. :meth:`as_html` or ``{% render_table %}``).
39
40         When a field is not visible, it is removed from the table's
41         :attr:`~Column.columns` iterable.
42
43     :type sortable: :class:`bool`
44     :param sortable: If :const:`False`, this column will not be allowed to
45         influence row ordering/sorting.
46
47     """
48     #: Tracks each time a Column instance is created. Used to retain order.
49     creation_counter = 0
50
51     def __init__(self, verbose_name=None, accessor=None, default=None,
52                  visible=True, sortable=None):
53         if not (accessor is None or isinstance(accessor, basestring) or
54                 callable(accessor)):
55             raise TypeError('accessor must be a string or callable, not %s' %
56                             accessor.__class__.__name__)
57         if callable(accessor) and default is not None:
58             raise TypeError('accessor must be string when default is used, not'
59                             ' callable')
60         self.accessor = A(accessor) if accessor else None
61         self._default = default
62         self.sortable = sortable
63         self.verbose_name = verbose_name
64         self.visible = visible
65
66         self.creation_counter = Column.creation_counter
67         Column.creation_counter += 1
68
69     @property
70     def default(self):
71         """
72         The default value for cells in this column.
73
74         The default value passed into ``Column.default`` property may be a
75         callable, this function handles access.
76
77         """
78         return self._default() if callable(self._default) else self._default
79
80     @property
81     def header(self):
82         """
83         The value used for the column heading (e.g. inside the ``<th>`` tag).
84
85         By default this equivalent to the column's :attr:`verbose_name`.
86
87         .. note::
88
89             This property typically isn't accessed directly when a table is
90             rendered. Instead, :attr:`.BoundColumn.header` is accessed which
91             in turn accesses this property. This allows the header to fallback
92             to the column name (it's only available on a :class:`.BoundColumn`
93             object hence accessing that first) when this property doesn't
94             return something useful.
95
96         """
97         return self.verbose_name
98
99     def render(self, value, **kwargs):
100         """
101         Returns the content for a specific cell.
102
103         This method can be overridden by :meth:`render_FOO` methods on the table or
104         by subclassing :class:`Column`.
105
106         """
107         return value
108
109
110 class CheckBoxColumn(Column):
111     """
112     A subclass of :class:`.Column` that renders as a checkbox form input.
113
114     This column allows a user to *select* a set of rows. The selection
115     information can then be used to apply some operation (e.g. "delete") onto
116     the set of objects that correspond to the selected rows.
117
118     The value that is extracted from the :term:`table data` for this column is
119     used as the value for the checkbox, i.e. ``<input type="checkbox"
120     value="..." />``
121
122     This class implements some sensible defaults:
123
124     - The ``name`` attribute of the input is the name of the :term:`column
125       name` (can be overriden via ``attrs`` argument).
126     - The ``sortable`` parameter defaults to :const:`False`.
127     - The ``type`` attribute of the input is ``checkbox`` (can be overriden via
128       ``attrs`` argument).
129     - The header checkbox is left bare, i.e. ``<input type="checkbox"/>`` (use
130       the ``header_attrs`` argument to customise).
131
132     .. note:: The "apply some operation onto the selection" functionality is
133         not implemented in this column, and requires manually implemention.
134
135     :param attrs:
136         a :class:`dict` of HTML attributes that are added to the rendered
137         ``<input type="checkbox" .../>`` tag
138     :param header_attrs:
139         same as *attrs*, but applied **only** to the header checkbox
140
141     """
142     def __init__(self, attrs=None, header_attrs=None, **extra):
143         params = {'sortable': False}
144         params.update(extra)
145         super(CheckBoxColumn, self).__init__(**params)
146         self.attrs = attrs or {}
147         self.header_attrs = header_attrs or {}
148
149     @property
150     def header(self):
151         attrs = AttributeDict({
152             'type': 'checkbox',
153         })
154         attrs.update(self.header_attrs)
155         return mark_safe('<input %s/>' % attrs.as_html())
156
157     def render(self, value, bound_column, **kwargs):
158         attrs = AttributeDict({
159             'type': 'checkbox',
160             'name': bound_column.name,
161             'value': value
162         })
163         attrs.update(self.attrs)
164         return mark_safe('<input %s/>' % attrs.as_html())
165
166
167
168 class LinkColumn(Column):
169     """
170     A subclass of :class:`.Column` that renders the cell value as a hyperlink.
171
172     It's common to have the primary value in a row hyperlinked to page
173     dedicated to that record.
174
175     The first arguments are identical to that of
176     :func:`django.core.urlresolvers.reverse` and allow a URL to be
177     described. The last argument ``attrs`` allows custom HTML attributes to
178     be added to the ``<a>`` tag.
179
180     :param viewname: See :func:`django.core.urlresolvers.reverse`.
181     :param urlconf: See :func:`django.core.urlresolvers.reverse`.
182     :param args: See :func:`django.core.urlresolvers.reverse`. **
183     :param kwargs: See :func:`django.core.urlresolvers.reverse`. **
184     :param current_app: See :func:`django.core.urlresolvers.reverse`.
185
186     :param attrs:
187         a :class:`dict` of HTML attributes that are added to the rendered
188         ``<input type="checkbox" .../>`` tag
189
190     ** In order to create a link to a URL that relies on information in the
191     current row, :class:`.Accessor` objects can be used in the ``args`` or
192     ``kwargs`` arguments. The accessor will be resolved using the row's record
193     before ``reverse()`` is called.
194
195     Example:
196
197     .. code-block:: python
198
199         # models.py
200         class Person(models.Model):
201             name = models.CharField(max_length=200)
202
203         # urls.py
204         urlpatterns = patterns('',
205             url('people/(\d+)/', views.people_detail, name='people_detail')
206         )
207
208         # tables.py
209         from django_tables.utils import A  # alias for Accessor
210
211         class PeopleTable(tables.Table):
212             name = tables.LinkColumn('people_detail', args=[A('pk')])
213
214     """
215     def __init__(self, viewname, urlconf=None, args=None, kwargs=None,
216                  current_app=None, attrs=None, **extra):
217         super(LinkColumn, self).__init__(**extra)
218         self.viewname = viewname
219         self.urlconf = urlconf
220         self.args = args
221         self.kwargs = kwargs
222         self.current_app = current_app
223         self.attrs = attrs or {}
224
225     def render(self, value, record, bound_column, **kwargs):
226         params = {}  # args for reverse()
227         if self.viewname:
228             params['viewname'] = (self.viewname.resolve(record)
229                                  if isinstance(self.viewname, A)
230                                  else self.viewname)
231         if self.urlconf:
232             params['urlconf'] = (self.urlconf.resolve(record)
233                                  if isinstance(self.urlconf, A)
234                                  else self.urlconf)
235         if self.args:
236             params['args'] = [a.resolve(record) if isinstance(a, A) else a
237                               for a in self.args]
238         if self.kwargs:
239             params['kwargs'] = self.kwargs
240             for key, value in self.kwargs:
241                 if isinstance(value, A):
242                     params['kwargs'][key] = value.resolve(record)
243         if self.current_app:
244             params['current_app'] = self.current_app
245             for key, value in self.current_app:
246                 if isinstance(value, A):
247                     params['current_app'][key] = value.resolve(record)
248         url = reverse(**params)
249         html = '<a href="{url}" {attrs}>{value}</a>'.format(
250             url=reverse(**params),
251             attrs=AttributeDict(self.attrs).as_html(),
252             value=value
253         )
254         return mark_safe(html)
255
256
257 class TemplateColumn(Column):
258     """
259     A subclass of :class:`.Column` that renders some template code to use as
260     the cell value.
261
262     :type template_code: :class:`basestring` object
263     :param template_code: the template code to render
264
265     A :class:`django.templates.Template` object is created from the
266     *template_code* and rendered with a context containing only a ``record``
267     variable. This variable is the record for the table row being rendered.
268
269     Example:
270
271     .. code-block:: python
272
273         class SimpleTable(tables.Table):
274             name1 = tables.TemplateColumn('{{ record.name }}')
275             name2 = tables.Column()
276
277     Both columns will have the same output.
278
279
280     .. important::
281         In order to use template tags or filters that require a
282         ``RequestContext``, the table **must** be rendered via
283         :ref:`{% render_table %} <template-tags.render_table>`.
284
285     """
286     def __init__(self, template_code=None, **extra):
287         super(TemplateColumn, self).__init__(**extra)
288         self.template_code = template_code
289
290     def render(self, record, table, **kwargs):
291         t = Template(self.template_code)
292         if hasattr(table, 'request'):
293             context = RequestContext(table.request, {'record': record})
294         else:
295             context = Context({'record': record})
296         return t.render(context)
297
298
299 class BoundColumn(object):
300     """A *runtime* version of :class:`Column`. The difference between
301     :class:`BoundColumn` and :class:`Column`, is that :class:`BoundColumn`
302     objects are of the relationship between a :class:`Column` and a
303     :class:`Table`. This means that it knows the *name* given to the
304     :class:`Column`.
305
306     For convenience, all :class:`Column` properties are available from this
307     class.
308
309
310     :type table: :class:`Table` object
311     :param table: the table in which this column exists
312
313     :type column: :class:`Column` object
314     :param column: the type of column
315
316     :type name: :class:`basestring` object
317     :param name: the variable name of the column used to when defining the
318         :class:`Table`. Example:
319
320         .. code-block:: python
321
322             class SimpleTable(tables.Table):
323                 age = tables.Column()
324
325         `age` is the name.
326
327     """
328     def __init__(self, table, column, name):
329         self._table = table
330         self._column = column
331         self._name = name
332
333     def __unicode__(self):
334         return self.verbose_name
335
336     @property
337     def column(self):
338         """Returns the :class:`Column` object for this column."""
339         return self._column
340
341     @property
342     def accessor(self):
343         """
344         Returns the string used to access data for this column out of the data
345         source.
346
347         """
348         return self.column.accessor or A(self.name)
349
350     @property
351     def default(self):
352         """Returns the default value for this column."""
353         return self.column.default
354
355     @property
356     def header(self):
357         """
358         Return the value that should be used in the header cell for this
359         column.
360
361         """
362         return self.column.header or self.verbose_name
363
364     @property
365     def name(self):
366         """Returns the string used to identify this column."""
367         return self._name
368
369     @property
370     def order_by(self):
371         """
372         If this column is sorted, return the associated :class:`.OrderBy`
373         instance, otherwise :const:`None`.
374
375         """
376         try:
377             return self.table.order_by[self.name]
378         except IndexError:
379             return None
380
381     @property
382     def sortable(self):
383         """
384         Return a :class:`bool` depending on whether this column is
385         sortable.
386
387         """
388         if self.column.sortable is not None:
389             return self.column.sortable
390         elif self.table._meta.sortable is not None:
391             return self.table._meta.sortable
392         else:
393             return True  # the default value
394
395     @property
396     def table(self):
397         """Return the :class:`Table` object that this column is part of."""
398         return self._table
399
400     @property
401     def verbose_name(self):
402         """
403         Return the verbose name for this column, or fallback to prettified
404         column name.
405
406         """
407         return (self.column.verbose_name
408                 or capfirst(force_unicode(self.name.replace('_', ' '))))
409
410     @property
411     def visible(self):
412         """
413         Returns a :class:`bool` depending on whether this column is visible.
414
415         """
416         return self.column.visible
417
418
419 class BoundColumns(object):
420     """
421     Container for spawning BoundColumns.
422
423     This is bound to a table and provides its :attr:`.Table.columns` property.
424     It provides access to those columns in different ways (iterator,
425     item-based, filtered and unfiltered etc), stuff that would not be possible
426     with a simple iterator in the table class.
427
428     A :class:`BoundColumns` object is a container for holding
429     :class:`BoundColumn` objects. It provides methods that make accessing
430     columns easier than if they were stored in a :class:`list` or
431     :class:`dict`. :class:`Columns` has a similar API to a :class:`dict` (it
432     actually uses a :class:`SortedDict` interally).
433
434     At the moment you'll only come across this class when you access a
435     :attr:`.Table.columns` property.
436
437     :type table: :class:`.Table` object
438     :param table: the table containing the columns
439
440     """
441     def __init__(self, table):
442         self.table = table
443         # ``self._columns`` attribute stores the bound columns (columns that
444         # have a real name, )
445         self._columns = SortedDict()
446
447     def _spawn_columns(self):
448         """
449         (re)build the "_bound_columns" cache of :class:`.BoundColumn` objects
450         (note that :attr:`.base_columns` might have changed since last time);
451         creating :class:`.BoundColumn` instances can be costly, so we reuse
452         existing ones.
453
454         """
455         columns = SortedDict()
456         for name, column in self.table.base_columns.items():
457             if name in self._columns:
458                 columns[name] = self._columns[name]
459             else:
460                 columns[name] = BoundColumn(self.table, column, name)
461         self._columns = columns
462
463     def all(self):
464         """
465         Return an iterator that exposes all :class:`.BoundColumn` objects,
466         regardless of visiblity or sortability.
467
468         """
469         self._spawn_columns()
470         for column in self._columns.values():
471             yield column
472
473     def items(self):
474         """
475         Return an iterator of ``(name, column)`` pairs (where ``column`` is a
476         :class:`.BoundColumn` object).
477
478         """
479         self._spawn_columns()
480         for r in self._columns.items():
481             yield r
482
483     def names(self):
484         """Return an iterator of column names."""
485         self._spawn_columns()
486         for r in self._columns.keys():
487             yield r
488
489     def sortable(self):
490         """
491         Same as :meth:`.BoundColumns.all` but only returns sortable :class:`BoundColumn`
492         objects.
493
494         This is useful in templates, where iterating over the full
495         set and checking ``{% if column.sortable %}`` can be problematic in
496         conjunction with e.g. ``{{ forloop.last }}`` (the last column might not
497         be the actual last that is rendered).
498
499         """
500         for column in self.all():
501             if column.sortable:
502                 yield column
503
504     def visible(self):
505         """
506         Same as :meth:`.sortable` but only returns visible
507         :class:`.BoundColumn` objects.
508
509         This is geared towards table rendering.
510
511         """
512         for column in self.all():
513             if column.visible:
514                 yield column
515
516     def __iter__(self):
517         """Convenience API with identical functionality to :meth:`visible`."""
518         return self.visible()
519
520     def __contains__(self, item):
521         """Check if a column is contained within a :class:`Columns` object.
522
523         *item* can either be a :class:`BoundColumn` object, or the name of a
524         column.
525
526         """
527         self._spawn_columns()
528         if isinstance(item, basestring):
529             return item in self.names()
530         else:
531             return item in self.all()
532
533     def __len__(self):
534         """Return how many :class:`BoundColumn` objects are contained."""
535         self._spawn_columns()
536         return len([1 for c in self._columns.values() if c.visible])
537
538     def __getitem__(self, index):
539         """Retrieve a specific :class:`BoundColumn` object.
540
541         *index* can either be 0-indexed or the name of a column
542
543         .. code-block:: python
544
545             columns['speed']  # returns a bound column with name 'speed'
546             columns[0]        # returns the first column
547
548         """
549         self._spawn_columns()
550         if isinstance(index, int):
551             return self._columns.value_for_index(index)
552         elif isinstance(index, basestring):
553             return self._columns[index]
554         else:
555             raise TypeError('row indices must be integers or str, not %s' %
556                             index.__class__.__name__)