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
12 """Represents a single column of a table.
14 :class:`Column` objects control the way a column (including the cells that
15 fall within it) are rendered.
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.
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`.
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.
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).
33 .. [1] The provided callable object must not expect to receive any
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 %}``).
40 When a field is not visible, it is removed from the table's
41 :attr:`~Column.columns` iterable.
43 :type sortable: :class:`bool`
44 :param sortable: If :const:`False`, this column will not be allowed to
45 influence row ordering/sorting.
48 #: Tracks each time a Column instance is created. Used to retain order.
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
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'
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
66 self.creation_counter = Column.creation_counter
67 Column.creation_counter += 1
72 The default value for cells in this column.
74 The default value passed into ``Column.default`` property may be a
75 callable, this function handles access.
78 return self._default() if callable(self._default) else self._default
83 The value used for the column heading (e.g. inside the ``<th>`` tag).
85 By default this equivalent to the column's :attr:`verbose_name`.
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.
97 return self.verbose_name
99 def render(self, value, **kwargs):
101 Returns the content for a specific cell.
103 This method can be overridden by :meth:`render_FOO` methods on the table or
104 by subclassing :class:`Column`.
110 class CheckBoxColumn(Column):
112 A subclass of :class:`.Column` that renders as a checkbox form input.
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.
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"
122 This class implements some sensible defaults:
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
129 - The header checkbox is left bare, i.e. ``<input type="checkbox"/>`` (use
130 the ``header_attrs`` argument to customise).
132 .. note:: The "apply some operation onto the selection" functionality is
133 not implemented in this column, and requires manually implemention.
136 a :class:`dict` of HTML attributes that are added to the rendered
137 ``<input type="checkbox" .../>`` tag
139 same as *attrs*, but applied **only** to the header checkbox
142 def __init__(self, attrs=None, header_attrs=None, **extra):
143 params = {'sortable': False}
145 super(CheckBoxColumn, self).__init__(**params)
146 self.attrs = attrs or {}
147 self.header_attrs = header_attrs or {}
151 attrs = AttributeDict({
154 attrs.update(self.header_attrs)
155 return mark_safe('<input %s/>' % attrs.as_html())
157 def render(self, value, bound_column, **kwargs):
158 attrs = AttributeDict({
160 'name': bound_column.name,
163 attrs.update(self.attrs)
164 return mark_safe('<input %s/>' % attrs.as_html())
168 class LinkColumn(Column):
170 A subclass of :class:`.Column` that renders the cell value as a hyperlink.
172 It's common to have the primary value in a row hyperlinked to page
173 dedicated to that record.
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.
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`.
187 a :class:`dict` of HTML attributes that are added to the rendered
188 ``<input type="checkbox" .../>`` tag
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.
197 .. code-block:: python
200 class Person(models.Model):
201 name = models.CharField(max_length=200)
204 urlpatterns = patterns('',
205 url('people/(\d+)/', views.people_detail, name='people_detail')
209 from django_tables.utils import A # alias for Accessor
211 class PeopleTable(tables.Table):
212 name = tables.LinkColumn('people_detail', args=[A('pk')])
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
222 self.current_app = current_app
223 self.attrs = attrs or {}
225 def render(self, value, record, bound_column, **kwargs):
226 params = {} # args for reverse()
228 params['viewname'] = (self.viewname.resolve(record)
229 if isinstance(self.viewname, A)
232 params['urlconf'] = (self.urlconf.resolve(record)
233 if isinstance(self.urlconf, A)
236 params['args'] = [a.resolve(record) if isinstance(a, A) else a
239 params['kwargs'] = self.kwargs
240 for key, value in self.kwargs:
241 if isinstance(value, A):
242 params['kwargs'][key] = value.resolve(record)
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(),
254 return mark_safe(html)
257 class TemplateColumn(Column):
259 A subclass of :class:`.Column` that renders some template code to use as
262 :type template_code: :class:`basestring` object
263 :param template_code: the template code to render
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.
271 .. code-block:: python
273 class SimpleTable(tables.Table):
274 name1 = tables.TemplateColumn('{{ record.name }}')
275 name2 = tables.Column()
277 Both columns will have the same output.
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>`.
286 def __init__(self, template_code=None, **extra):
287 super(TemplateColumn, self).__init__(**extra)
288 self.template_code = template_code
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})
295 context = Context({'record': record})
296 return t.render(context)
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
306 For convenience, all :class:`Column` properties are available from this
310 :type table: :class:`Table` object
311 :param table: the table in which this column exists
313 :type column: :class:`Column` object
314 :param column: the type of column
316 :type name: :class:`basestring` object
317 :param name: the variable name of the column used to when defining the
318 :class:`Table`. Example:
320 .. code-block:: python
322 class SimpleTable(tables.Table):
323 age = tables.Column()
328 def __init__(self, table, column, name):
330 self._column = column
333 def __unicode__(self):
334 return self.verbose_name
338 """Returns the :class:`Column` object for this column."""
344 Returns the string used to access data for this column out of the data
348 return self.column.accessor or A(self.name)
352 """Returns the default value for this column."""
353 return self.column.default
358 Return the value that should be used in the header cell for this
362 return self.column.header or self.verbose_name
366 """Returns the string used to identify this column."""
372 If this column is sorted, return the associated :class:`.OrderBy`
373 instance, otherwise :const:`None`.
377 return self.table.order_by[self.name]
384 Return a :class:`bool` depending on whether this column is
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
393 return True # the default value
397 """Return the :class:`Table` object that this column is part of."""
401 def verbose_name(self):
403 Return the verbose name for this column, or fallback to prettified
407 return (self.column.verbose_name
408 or capfirst(force_unicode(self.name.replace('_', ' '))))
413 Returns a :class:`bool` depending on whether this column is visible.
416 return self.column.visible
419 class BoundColumns(object):
421 Container for spawning BoundColumns.
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.
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).
434 At the moment you'll only come across this class when you access a
435 :attr:`.Table.columns` property.
437 :type table: :class:`.Table` object
438 :param table: the table containing the columns
441 def __init__(self, table):
443 # ``self._columns`` attribute stores the bound columns (columns that
444 # have a real name, )
445 self._columns = SortedDict()
447 def _spawn_columns(self):
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
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]
460 columns[name] = BoundColumn(self.table, column, name)
461 self._columns = columns
465 Return an iterator that exposes all :class:`.BoundColumn` objects,
466 regardless of visiblity or sortability.
469 self._spawn_columns()
470 for column in self._columns.values():
475 Return an iterator of ``(name, column)`` pairs (where ``column`` is a
476 :class:`.BoundColumn` object).
479 self._spawn_columns()
480 for r in self._columns.items():
484 """Return an iterator of column names."""
485 self._spawn_columns()
486 for r in self._columns.keys():
491 Same as :meth:`.BoundColumns.all` but only returns sortable :class:`BoundColumn`
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).
500 for column in self.all():
506 Same as :meth:`.sortable` but only returns visible
507 :class:`.BoundColumn` objects.
509 This is geared towards table rendering.
512 for column in self.all():
517 """Convenience API with identical functionality to :meth:`visible`."""
518 return self.visible()
520 def __contains__(self, item):
521 """Check if a column is contained within a :class:`Columns` object.
523 *item* can either be a :class:`BoundColumn` object, or the name of a
527 self._spawn_columns()
528 if isinstance(item, basestring):
529 return item in self.names()
531 return item in self.all()
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])
538 def __getitem__(self, index):
539 """Retrieve a specific :class:`BoundColumn` object.
541 *index* can either be 0-indexed or the name of a column
543 .. code-block:: python
545 columns['speed'] # returns a bound column with name 'speed'
546 columns[0] # returns the first column
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]
555 raise TypeError('row indices must be integers or str, not %s' %
556 index.__class__.__name__)