updated docs
[django-tables2.git] / django_tables / rows.py
1 # -*- coding: utf-8 -*-
2
3 class BoundRow(object):
4     """Represents a *specific* row in a table.
5
6     :class:`BoundRow` objects expose rendered versions of raw table data. This
7     means that formatting (via :attr:`Column.formatter` or an overridden
8     :meth:`Column.render` method) is applied to the values from the table's
9     data.
10
11     To access the rendered value of each cell in a row, just iterate over it:
12
13     .. code-block:: python
14
15         >>> import django_tables as tables
16         >>> class SimpleTable(tables.Table):
17         ...     a = tables.Column()
18         ...     b = tables.CheckBoxColumn(attrs={'name': 'my_chkbox'})
19         ...
20         >>> table = SimpleTable([{'a': 1, 'b': 2}])
21         >>> row = table.rows[0]  # we only have one row, so let's use it
22         >>> for cell in row:
23         ...     print cell
24         ...
25         1
26         <input type="checkbox" name="my_chkbox" value="2" />
27
28     Alternatively you can treat it like a list and use indexing to retrieve a
29     specific cell. It should be noted that this will raise an IndexError on
30     failure.
31
32     .. code-block:: python
33
34         >>> row[0]
35         1
36         >>> row[1]
37         u'<input type="checkbox" name="my_chkbox" value="2" />'
38         >>> row[2]
39         ...
40         IndexError: list index out of range
41
42     Finally you can also treat it like a dictionary and use column names as the
43     keys. This will raise KeyError on failure (unlike the above indexing using
44     integers).
45
46     .. code-block:: python
47
48         >>> row['a']
49         1
50         >>> row['b']
51         u'<input type="checkbox" name="my_chkbox" value="2" />'
52         >>> row['c']
53         ...
54         KeyError: 'c'
55
56     """
57     def __init__(self, table, data):
58         """Initialise a new :class:`BoundRow` object where:
59
60         * *table* is the :class:`Table` in which this row exists.
61         * *data* is a chunk of data that describes the information for this
62           row. A "chunk" of data might be a :class:`Model` object, a ``dict``,
63           or perhaps something else.
64
65         """
66         self.table = table
67         self.data = data
68
69     def __iter__(self):
70         """Iterate over the rendered values for cells in the row.
71
72         Under the hood this method just makes a call to :meth:`__getitem__` for
73         each cell.
74
75         """
76         for value in self.values:
77             yield value
78
79     def __getitem__(self, name):
80         """Returns the final rendered value for a cell in the row, given the
81         name of a column.
82         """
83         bound_column = self.table.columns[name]
84         # use custom render_FOO methods on the table
85         custom = getattr(self.table, 'render_%s' % name, None)
86         if custom:
87             return custom(bound_column, self)
88         return bound_column.column.render(self.table, bound_column, self)
89
90     def __contains__(self, item):
91         """Check by both row object and column name."""
92         if isinstance(item, basestring):
93             return item in self.table._columns
94         else:
95             return item in self
96
97     @property
98     def values(self):
99         for column in self.table.columns:
100             # this uses __getitem__, using the name (rather than the accessor)
101             # is correct – it's what __getitem__ expects.
102             yield self[column.name]
103
104
105 class Rows(object):
106     """Container for spawning BoundRows.
107
108     This is bound to a table and provides it's ``rows`` property. It
109     provides functionality that would not be possible with a simple
110     iterator in the table class.
111     """
112     def __init__(self, table):
113         """Initialise a :class:`Rows` object. *table* is the :class:`Table`
114         object in which the rows exist.
115
116         """
117         self.table = table
118
119     def all(self):
120         """Return an iterable for all :class:`BoundRow` objects in the table.
121
122         """
123         for row in self.table.data:
124             yield BoundRow(self.table, row)
125
126     def page(self):
127         """If the table is paginated, return an iterable of :class:`BoundRow`
128         objects that appear on the current page, otherwise return None.
129
130         """
131         if not hasattr(self.table, 'page'):
132             return None
133         return iter(self.table.page.object_list)
134
135     def __iter__(self):
136         """Convience method for all()"""
137         return self.all()
138
139     def __len__(self):
140         """Returns the number of rows in the table."""
141         return len(self.table.data)
142
143     # for compatibility with QuerySetPaginator
144     count = __len__
145
146     def __getitem__(self, key):
147         """Allows normal list slicing syntax to be used."""
148         if isinstance(key, slice):
149             result = list()
150             for row in self.table.data[key]:
151                 result.append(BoundRow(self.table, row))
152             return result
153         elif isinstance(key, int):
154             return BoundRow(self.table, self.table.data[key])
155         else:
156             raise TypeError('Key must be a slice or integer.')