* Added pagination
[django-tables2.git] / django_tables / rows.py
index 1883baa2be4a4025cedc41837349a4f1daa233f5..1b662b99d80a94b0512bbc6d36a1c0d47fe38b4a 100644 (file)
@@ -1,12 +1,17 @@
 # -*- coding: utf-8 -*-
+from django.utils.safestring import EscapeUnicode, SafeData
+from .proxies import TemplateSafeLazyProxy
+import itertools
+
 
 class BoundRow(object):
-    """Represents a *specific* row in a table.
+    """
+    Represents a *specific* row in a table.
 
-    :class:`BoundRow` objects expose rendered versions of raw table data. This
-    means that formatting (via :attr:`Column.formatter` or an overridden
-    :meth:`Column.render` method) is applied to the values from the table's
-    data.
+    :class:`.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
+    :class:`.BoundRow` object and it will take care to return values rendered
+    using the correct method (e.g. :meth:`.Column.render_FOO`)
 
     To access the rendered value of each cell in a row, just iterate over it:
 
@@ -53,39 +58,82 @@ class BoundRow(object):
         ...
         KeyError: 'c'
 
+    :param table: is the :class:`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 :class:`Model` object, a
+        :class:`dict`, or something else.
+
     """
-    def __init__(self, table, data):
-        """Initialise a new :class:`BoundRow` object where:
+    def __init__(self, table, record):
+        self._table = table
+        self._record = record
 
-        * *table* is the :class:`Table` in which this row exists.
-        * *data* is a chunk of data that describes the information for this
-          row. A "chunk" of data might be a :class:`Model` object, a ``dict``,
-          or perhaps something else.
+    @property
+    def table(self):
+        """The associated :class:`.Table` object."""
+        return self._table
 
+    @property
+    def record(self):
         """
-        self.table = table
-        self.data = data
+        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.
+        """
+        Iterate over the rendered values for cells in the row.
 
-        Under the hood this method just makes a call to :meth:`__getitem__` for
-        each cell.
+        Under the hood this method just makes a call to
+        :meth:`.BoundRow.__getitem__` for each cell.
 
         """
-        for value in self.values:
-            yield value
+        for column in self.table.columns:
+            # this uses __getitem__, using the name (rather than the accessor)
+            # is correct – it's what __getitem__ expects.
+            yield self[column.name]
 
     def __getitem__(self, name):
-        """Returns the final rendered value for a cell in the row, given the
-        name of a column.
+        """
+        Returns the final rendered value for a cell in the row, given the name
+        of a column.
+
         """
         bound_column = self.table.columns[name]
-        # use custom render_FOO methods on the table
-        custom = getattr(self.table, 'render_%s' % name, None)
-        if custom:
-            return custom(bound_column, self)
-        return bound_column.column.render(self.table, bound_column, self)
+
+        def value():
+            try:
+                raw = bound_column.accessor.resolve(self.record)
+            except (TypeError, AttributeError, KeyError, ValueError) as e:
+                raw = None
+            return raw if raw is not None else bound_column.default
+
+        kwargs = {
+            'value': TemplateSafeLazyProxy(value),
+            'record': self.record,
+            'column': bound_column.column,
+            'bound_column': bound_column,
+            'bound_row': self,
+            'table': self._table,
+        }
+        render_FOO = 'render_' + bound_column.name
+        render = getattr(self.table, render_FOO, bound_column.column.render)
+        try:
+            return render(**kwargs)
+        except TypeError as e:
+            # Let's be helpful and provide a decent error message, since
+            # render() underwent backwards incompatible changes.
+            if e.message.startswith('render() got an unexpected keyword'):
+                if hasattr(self.table, render_FOO):
+                    cls = self.table.__class__.__name__
+                    meth = render_FOO
+                else:
+                    cls = kwargs['column'].__class__.__name__
+                    meth = 'render'
+                msg = 'Did you forget to add **kwargs to %s.%s() ?' % (cls, meth)
+                raise TypeError(e.message + '. ' + msg)
 
     def __contains__(self, item):
         """Check by both row object and column name."""
@@ -94,46 +142,43 @@ class BoundRow(object):
         else:
             return item in self
 
-    @property
-    def values(self):
-        for column in self.table.columns:
-            # this uses __getitem__, using the name (rather than the accessor)
-            # is correct – it's what __getitem__ expects.
-            yield self[column.name]
 
+class BoundRows(object):
+    """
+    Container for spawning :class:`.BoundRow` objects.
+
+    The :attr:`.Table.rows` attribute is a :class:`.BoundRows` object.
+    It provides functionality that would not be possible with a simple iterator
+    in the table class.
 
-class Rows(object):
-    """Container for spawning BoundRows.
+    :type table: :class:`.Table` object
+    :param table: the table in which the rows exist.
 
-    This is bound to a table and provides it's ``rows`` property. It
-    provides functionality that would not be possible with a simple
-    iterator in the table class.
     """
     def __init__(self, table):
-        """Initialise a :class:`Rows` object. *table* is the :class:`Table`
-        object in which the rows exist.
-
-        """
         self.table = table
 
     def all(self):
-        """Return an iterable for all :class:`BoundRow` objects in the table.
+        """
+        Return an iterable for all :class:`.BoundRow` objects in the table.
 
         """
-        for row in self.table.data:
-            yield BoundRow(self.table, row)
+        for record in self.table.data:
+            yield BoundRow(self.table, record)
 
     def page(self):
-        """If the table is paginated, return an iterable of :class:`BoundRow`
-        objects that appear on the current page, otherwise return None.
+        """
+        If the table is paginated, return an iterable of :class:`.BoundRow`
+        objects that appear on the current page.
 
+        :rtype: iterable of :class:`.BoundRow` objects, or :const:`None`.
         """
         if not hasattr(self.table, 'page'):
             return None
         return iter(self.table.page.object_list)
 
     def __iter__(self):
-        """Convience method for all()"""
+        """Convience method for :meth:`.BoundRows.all`"""
         return self.all()
 
     def __len__(self):
@@ -146,10 +191,8 @@ class Rows(object):
     def __getitem__(self, key):
         """Allows normal list slicing syntax to be used."""
         if isinstance(key, slice):
-            result = list()
-            for row in self.table.data[key]:
-                result.append(BoundRow(self.table, row))
-            return result
+            return itertools.imap(lambda record: BoundRow(self.table, record),
+                                  self.table.data[key])
         elif isinstance(key, int):
             return BoundRow(self.table, self.table.data[key])
         else: