195 lines
6.1 KiB
Python
195 lines
6.1 KiB
Python
# coding: utf-8
|
||
from .utils import A, getargspec
|
||
from django.db import models
|
||
from django.db.models.fields import FieldDoesNotExist
|
||
import six
|
||
|
||
|
||
class BoundRow(object):
|
||
"""
|
||
Represents a *specific* row in a table.
|
||
|
||
`.BoundRow` objects are a container that make it easy to access the
|
||
final 'rendered' values for cells in a row. You can simply iterate over a
|
||
`.BoundRow` object and it will take care to return values rendered
|
||
using the correct method (e.g. :ref:`table.render_FOO`)
|
||
|
||
To access the rendered value of each cell in a row, just iterate over it:
|
||
|
||
.. code-block:: python
|
||
|
||
>>> import django_tables2 as tables
|
||
>>> class SimpleTable(tables.Table):
|
||
... a = tables.Column()
|
||
... b = tables.CheckBoxColumn(attrs={'name': 'my_chkbox'})
|
||
...
|
||
>>> table = SimpleTable([{'a': 1, 'b': 2}])
|
||
>>> row = table.rows[0] # we only have one row, so let's use it
|
||
>>> for cell in row:
|
||
... print cell
|
||
...
|
||
1
|
||
<input type="checkbox" name="my_chkbox" value="2" />
|
||
|
||
Alternatively you can treat it like a list and use indexing to retrieve a
|
||
specific cell. It should be noted that this will raise an IndexError on
|
||
failure.
|
||
|
||
.. code-block:: python
|
||
|
||
>>> row[0]
|
||
1
|
||
>>> row[1]
|
||
u'<input type="checkbox" name="my_chkbox" value="2" />'
|
||
>>> row[2]
|
||
...
|
||
IndexError: list index out of range
|
||
|
||
Finally you can also treat it like a dictionary and use column names as the
|
||
keys. This will raise KeyError on failure (unlike the above indexing using
|
||
integers).
|
||
|
||
.. code-block:: python
|
||
|
||
>>> row['a']
|
||
1
|
||
>>> row['b']
|
||
u'<input type="checkbox" name="my_chkbox" value="2" />'
|
||
>>> row['c']
|
||
...
|
||
KeyError: 'c'
|
||
|
||
:param table: is the `.Table` in which this row exists.
|
||
:param record: a single record from the :term:`table data` that is used to
|
||
populate the row. A record could be a `~django.db.Model`
|
||
object, a `dict`, or something else.
|
||
|
||
"""
|
||
def __init__(self, record, table):
|
||
self._record = record
|
||
self._table = table
|
||
|
||
@property
|
||
def table(self):
|
||
"""The associated `.Table` object."""
|
||
return self._table
|
||
|
||
@property
|
||
def record(self):
|
||
"""
|
||
The data record from the data source which is used to populate this row
|
||
with data.
|
||
"""
|
||
return self._record
|
||
|
||
def __iter__(self):
|
||
"""
|
||
Iterate over the rendered values for cells in the row.
|
||
|
||
Under the hood this method just makes a call to
|
||
`.BoundRow.__getitem__` for each cell.
|
||
"""
|
||
for column, value in self.items():
|
||
# this uses __getitem__, using the name (rather than the accessor)
|
||
# is correct – it's what __getitem__ expects.
|
||
yield value
|
||
|
||
def __getitem__(self, name):
|
||
"""
|
||
Returns the final rendered value for a cell in the row, given the name
|
||
of a column.
|
||
"""
|
||
bound_column = self.table.columns[name]
|
||
|
||
value = None
|
||
# We need to take special care here to allow get_FOO_display()
|
||
# methods on a model to be used if available. See issue #30.
|
||
path, _, remainder = bound_column.accessor.rpartition('.')
|
||
penultimate = A(path).resolve(self.record, quiet=True)
|
||
# If the penultimate is a model and the remainder is a field
|
||
# using choices, use get_FOO_display().
|
||
if isinstance(penultimate, models.Model):
|
||
try:
|
||
field = penultimate._meta.get_field(remainder)
|
||
display = getattr(penultimate, 'get_%s_display' % remainder, None)
|
||
if getattr(field, "choices", ()) and display:
|
||
value = display()
|
||
remainder = None
|
||
except FieldDoesNotExist:
|
||
pass
|
||
# Fall back to just using the original accessor (we just need
|
||
# to follow the remainder).
|
||
if remainder:
|
||
value = A(remainder).resolve(penultimate, quiet=True)
|
||
|
||
if value in bound_column.column.empty_values:
|
||
return bound_column.default
|
||
|
||
available = {
|
||
'value': value,
|
||
'record': self.record,
|
||
'column': bound_column.column,
|
||
'bound_column': bound_column,
|
||
'bound_row': self,
|
||
'table': self._table,
|
||
}
|
||
expected = {}
|
||
|
||
# provide only the arguments expected by `render`
|
||
argspec = getargspec(bound_column.render)
|
||
args, varkw = argspec[0], argspec[2]
|
||
if varkw:
|
||
expected = available
|
||
else:
|
||
for key, value in available.items():
|
||
if key in args[1:]:
|
||
expected[key] = value
|
||
|
||
return bound_column.render(**expected)
|
||
|
||
def __contains__(self, item):
|
||
"""Check by both row object and column name."""
|
||
if isinstance(item, six.string_types):
|
||
return item in self.table._columns
|
||
else:
|
||
return item in self
|
||
|
||
def items(self):
|
||
"""
|
||
Returns iterator yielding ``(bound_column, cell)`` pairs.
|
||
|
||
*cell* is ``row[name]`` -- the rendered unicode value that should be
|
||
``rendered within ``<td>``.
|
||
"""
|
||
for column in self.table.columns:
|
||
yield (column, self[column.name])
|
||
|
||
|
||
class BoundRows(object):
|
||
"""
|
||
Container for spawning `.BoundRow` objects.
|
||
|
||
:param data: iterable of records
|
||
:param table: the table in which the rows exist
|
||
|
||
This is used for `.Table.rows`.
|
||
"""
|
||
def __init__(self, data, table):
|
||
self.data = data
|
||
self.table = table
|
||
|
||
def __iter__(self):
|
||
for record in self.data:
|
||
yield BoundRow(record, table=self.table)
|
||
|
||
def __len__(self):
|
||
return len(self.data)
|
||
|
||
def __getitem__(self, key):
|
||
"""
|
||
Slicing returns a new `.BoundRows` instance, indexing returns a single
|
||
`.BoundRow` instance.
|
||
"""
|
||
container = BoundRows if isinstance(key, slice) else BoundRow
|
||
return container(self.data[key], table=self.table)
|