* Updated documentation v0.4.0.beta2
authorBradley Ayers <bradley.ayers@gmail.com>
Sun, 3 Apr 2011 23:09:26 +0000 (09:09 +1000)
committerBradley Ayers <bradley.ayers@gmail.com>
Sun, 3 Apr 2011 23:09:26 +0000 (09:09 +1000)
* Renamed Columns to BoundColumns, and Rows to BoundRows
* Added some Accessor tests

django_tables/__init__.py
django_tables/columns.py
django_tables/rows.py
django_tables/tables.py
django_tables/templates/django_tables/basic_table.html
django_tables/templates/django_tables/table.html
django_tables/utils.py
docs/conf.py
docs/index.rst
setup.py
tests/utils.py

index 1b1fad541932273ebefd4f5f931d4eab3e0218a0..3437774d90a824ae240a75de800036e523efce65 100644 (file)
@@ -1,2 +1,2 @@
-from .tables import *
+from .tables import Table
 from .columns import *
index 05514bea57758a5fc56ff9772d12ac232010ecd4..24001b5d0ea5de1f730eaafbf0ed4b1c5e19bf4b 100644 (file)
@@ -14,83 +14,42 @@ class Column(object):
     :class:`Column` objects control the way a column (including the cells that
     fall within it) are rendered.
 
+    :param verbose_name: A pretty human readable version of the column name.
+        Typically this is used in the header cells in the HTML output.
+
+    :type accessor: :class:`basestring` or :class:`~.utils.Accessor`
+    :param accessor: An accessor that describes how to extract values for this
+        column from the :term:`table data`.
+
+    :param default: The default value for the column. This can be a value or a
+        callable object [1]_. If an object in the data provides :const:`None`
+        for a column, the default will be used instead.
+
+        The default value may affect ordering, depending on the type of
+        data the table is using. The only case where ordering is not
+        affected ing when a :class:`QuerySet` is used as the table data
+        (since sorting is performed by the database).
+
+        .. [1] The provided callable object must not expect to receive any
+           arguments.
+
+    :type visible: :class:`bool`
+    :param visible: If :const:`False`, this column will not be in HTML from
+        output generators (e.g. :meth:`as_html` or ``{% render_table %}``).
+
+        When a field is not visible, it is removed from the table's
+        :attr:`~Column.columns` iterable.
+
+    :type sortable: :class:`bool`
+    :param sortable: If :const:`False`, this column will not be allowed to
+        influence row ordering/sorting.
+
     """
     #: Tracks each time a Column instance is created. Used to retain order.
     creation_counter = 0
 
     def __init__(self, verbose_name=None, accessor=None, default=None,
                  visible=True, sortable=None):
-        """Initialise a :class:`Column` object.
-
-        :param verbose_name:
-            A pretty human readable version of the column name. Typically this
-            is used in the header cells in the HTML output.
-
-        :param accessor:
-            A string or callable that specifies the attribute to access when
-            retrieving the value for a cell in this column from the data-set.
-            Multiple lookups can be achieved by providing a dot separated list
-            of lookups, e.g. ``"user.first_name"``. The functionality is
-            identical to that of Django's template variable syntax, e.g. ``{{
-            user.first_name }}``
-
-            A callable should be used if the dot separated syntax is not
-            capable of describing the lookup properly. The callable will be
-            passed a single item from the data (if the table is using
-            :class:`QuerySet` data, this would be a :class:`Model` instance),
-            and is expected to return the correct value for the column.
-
-            Consider the following:
-
-            .. code-block:: python
-
-                >>> import django_tables as tables
-                >>> data = [
-                ...     {'dot.separated.key': 1},
-                ...     {'dot.separated.key': 2},
-                ... ]
-                ...
-                >>> class SlightlyComplexTable(tables.Table):
-                >>>     dot_seperated_key = tables.Column(accessor=lambda x: x['dot.separated.key'])
-                ...
-                >>> table = SlightlyComplexTable(data)
-                >>> for row in table.rows:
-                >>>     print row['dot_seperated_key']
-                ...
-                1
-                2
-
-            This would **not** have worked:
-
-            .. code-block:: python
-
-                dot_seperated_key = tables.Column(accessor='dot.separated.key')
-
-        :param default:
-            The default value for the column. This can be a value or a callable
-            object [1]_. If an object in the data provides :const:`None` for a
-            column, the default will be used instead.
-
-            The default value may affect ordering, depending on the type of
-            data the table is using. The only case where ordering is not
-            affected ing when a :class:`QuerySet` is used as the table data
-            (since sorting is performed by the database).
-
-            .. [1] The provided callable object must not expect to receive any
-               arguments.
-
-        :param visible:
-            If :const:`False`, this column will not be in HTML from output
-            generators (e.g. :meth:`as_html` or ``{% render_table %}``).
-
-            When a field is not visible, it is removed from the table's
-            :attr:`~Column.columns` iterable.
-
-        :param sortable:
-            If :const:`False`, this column will not be allowed to be used in
-            ordering the table.
-
-        """
         if not (accessor is None or isinstance(accessor, basestring) or
                 callable(accessor)):
             raise TypeError('accessor must be a string or callable, not %s' %
@@ -109,7 +68,8 @@ class Column(object):
 
     @property
     def default(self):
-        """The default value for cells in this column.
+        """
+        The default value for cells in this column.
 
         The default value passed into ``Column.default`` property may be a
         callable, this function handles access.
@@ -117,9 +77,30 @@ class Column(object):
         """
         return self._default() if callable(self._default) else self._default
 
+    @property
+    def header(self):
+        """
+        The value used for the column heading (e.g. inside the ``<th>`` tag).
+
+        By default this equivalent to the column's :attr:`verbose_name`.
+
+        .. note::
+
+            This property typically isn't accessed directly when a table is
+            rendered. Instead, :attr:`.BoundColumn.header` is accessed which
+            in turn accesses this property. This allows the header to fallback
+            to the column name (it's only available on a :class:`.BoundColumn`
+            object hence accessing that first) when this property doesn't
+            return something useful.
+
+        """
+        return self.verbose_name
+
     def render(self, value, **kwargs):
-        """Returns a cell's content.
-        This method can be overridden by ``render_FOO`` methods on the table or
+        """
+        Returns the content for a specific cell.
+
+        This method can be overridden by :meth:`render_FOO` methods on the table or
         by subclassing :class:`Column`.
 
         """
@@ -127,18 +108,36 @@ class Column(object):
 
 
 class CheckBoxColumn(Column):
-    """A subclass of Column that renders its column data as a checkbox"""
-    def __init__(self, attrs=None, **extra):
-        """
-        :param attrs: a dict of HTML element attributes to be added to the
-            ``<input>``
+    """
+    A subclass of :class:`.Column` that renders as a checkbox form input.
 
-        """
+    This column allows a user to *select* a set of rows. The selection
+    information can then be used to apply some operation (e.g. "delete") onto
+    the set of objects that correspond to the selected rows.
+
+    The value that is extracted from the :term:`table data` for this column is
+    used as the value for the checkbox, i.e. ``<input type="checkbox"
+    value="..." />``
+
+    By default this column is not sortable.
+
+    .. note:: The "apply some operation onto the selection" functionality is
+        not implemented in this column, and requires manually implemention.
+
+    :param attrs:
+        a :class:`dict` of HTML attributes that are added to the rendered
+        ``<input type="checkbox" .../>`` tag
+
+    """
+    def __init__(self, attrs=None, **extra):
         params = {'sortable': False}
         params.update(extra)
         super(CheckBoxColumn, self).__init__(**params)
         self.attrs = attrs or {}
-        self.verbose_name = mark_safe('<input type="checkbox"/>')
+
+    @property
+    def header(self):
+        return mark_safe('<input type="checkbox"/>')
 
     def render(self, value, bound_column, **kwargs):
         attrs = AttributeDict({
@@ -147,19 +146,59 @@ class CheckBoxColumn(Column):
             'value': value
         })
         attrs.update(self.attrs)
-        return mark_safe('<input %s/>' % AttributeDict(attrs).as_html())
+        return mark_safe('<input %s/>' % attrs.as_html())
 
 
 
 class LinkColumn(Column):
+    """
+    A subclass of :class:`.Column` that renders the cell value as a hyperlink.
+
+    It's common to have the primary value in a row hyperlinked to page
+    dedicated to that record.
+
+    The first arguments are identical to that of
+    :func:`django.core.urlresolvers.reverse` and allow a URL to be
+    described. The last argument ``attrs`` allows custom HTML attributes to
+    be added to the ``<a>`` tag.
+
+    :param viewname: See :func:`django.core.urlresolvers.reverse`.
+    :param urlconf: See :func:`django.core.urlresolvers.reverse`.
+    :param args: See :func:`django.core.urlresolvers.reverse`. **
+    :param kwargs: See :func:`django.core.urlresolvers.reverse`. **
+    :param current_app: See :func:`django.core.urlresolvers.reverse`.
+
+    :param attrs:
+        a :class:`dict` of HTML attributes that are added to the rendered
+        ``<input type="checkbox" .../>`` tag
+
+    ** In order to create a link to a URL that relies on information in the
+    current row, :class:`.Accessor` objects can be used in the ``args`` or
+    ``kwargs`` arguments. The accessor will be resolved using the row's record
+    before ``reverse()`` is called.
+
+    Example:
+
+    .. code-block:: python
+
+        # models.py
+        class Person(models.Model):
+            name = models.CharField(max_length=200)
+
+        # urls.py
+        urlpatterns = patterns('',
+            url('people/(\d+)/', views.people_detail, name='people_detail')
+        )
+
+        # tables.py
+        from django_tables.utils import A  # alias for Accessor
+
+        class PeopleTable(tables.Table):
+            name = tables.LinkColumn('people_detail', args=[A('pk')])
+
+    """
     def __init__(self, viewname, urlconf=None, args=None, kwargs=None,
                  current_app=None, attrs=None, **extra):
-        """
-        The first arguments are identical to that of
-        :func:`django.core.urlresolvers.reverse` and allow a URL to be
-        described. The last argument ``attrs`` allows custom HTML attributes to
-        be added to the ``<a>`` tag.
-        """
         super(LinkColumn, self).__init__(**extra)
         self.viewname = viewname
         self.urlconf = urlconf
@@ -201,6 +240,28 @@ class LinkColumn(Column):
 
 
 class TemplateColumn(Column):
+    """
+    A subclass of :class:`.Column` that renders some template code to use as
+    the cell value.
+
+    :type template_code: :class:`basestring` object
+    :param template_code: the template code to render
+
+    A :class:`django.templates.Template` object is created from the
+    *template_code* and rendered with a context containing only a ``record``
+    variable. This variable is the record for the table row being rendered.
+
+    Example:
+
+    .. code-block:: python
+
+        class SimpleTable(tables.Table):
+            name1 = tables.TemplateColumn('{{ record.name }}')
+            name2 = tables.Column()
+
+    Both columns will have the same output.
+
+    """
     def __init__(self, template_code=None, **extra):
         super(TemplateColumn, self).__init__(**extra)
         self.template_code = template_code
@@ -220,16 +281,26 @@ class BoundColumn(object):
     For convenience, all :class:`Column` properties are available from this
     class.
 
-    """
-    def __init__(self, table, column, name):
-        """Initialise a :class:`BoundColumn` object where:
 
-        * *table* - a :class:`Table` object in which this column exists
-        * *column* - a :class:`Column` object
-        * *name* – the variable name used when the column was added to the
-                   :class:`Table` subclass
+    :type table: :class:`Table` object
+    :param table: the table in which this column exists
 
-        """
+    :type column: :class:`Column` object
+    :param column: the type of column
+
+    :type name: :class:`basestring` object
+    :param name: the variable name of the column used to when defining the
+        :class:`Table`. Example:
+
+        .. code-block:: python
+
+            class SimpleTable(tables.Table):
+                age = tables.Column()
+
+        `age` is the name.
+
+    """
+    def __init__(self, table, column, name):
         self._table = table
         self._column = column
         self._name = name
@@ -237,25 +308,16 @@ class BoundColumn(object):
     def __unicode__(self):
         return self.verbose_name
 
-    @property
-    def table(self):
-        """Returns the :class:`Table` object that this column is part of."""
-        return self._table
-
     @property
     def column(self):
         """Returns the :class:`Column` object for this column."""
         return self._column
 
-    @property
-    def name(self):
-        """Returns the string used to identify this column."""
-        return self._name
-
     @property
     def accessor(self):
-        """Returns the string used to access data for this column out of the
-        data source.
+        """
+        Returns the string used to access data for this column out of the data
+        source.
 
         """
         return self.column.accessor or A(self.name)
@@ -265,9 +327,39 @@ class BoundColumn(object):
         """Returns the default value for this column."""
         return self.column.default
 
+    @property
+    def header(self):
+        """
+        Return the value that should be used in the header cell for this
+        column.
+
+        """
+        return self.verbose_name
+
+    @property
+    def name(self):
+        """Returns the string used to identify this column."""
+        return self._name
+
+    @property
+    def order_by(self):
+        """
+        If this column is sorted, return the associated :class:`.OrderBy`
+        instance, otherwise :const:`None`.
+
+        """
+        try:
+            return self.table.order_by[self.name]
+        except IndexError:
+            return None
+
     @property
     def sortable(self):
-        """Returns a ``bool`` depending on whether this column is sortable."""
+        """
+        Return a :class:`bool` depending on whether this column is
+        sortable.
+
+        """
         if self.column.sortable is not None:
             return self.column.sortable
         elif self.table._meta.sortable is not None:
@@ -275,72 +367,78 @@ class BoundColumn(object):
         else:
             return True  # the default value
 
+    @property
+    def table(self):
+        """Return the :class:`Table` object that this column is part of."""
+        return self._table
+
     @property
     def verbose_name(self):
-        """Returns the verbose name for this column."""
+        """
+        Return the verbose name for this column, or fallback to prettified
+        column name.
+
+        """
         return (self.column.verbose_name
                 or capfirst(force_unicode(self.name.replace('_', ' '))))
 
     @property
     def visible(self):
-        """Returns a ``bool`` depending on whether this column is visible."""
-        return self.column.visible
-
-    @property
-    def order_by(self):
-        """If this column is sorted, return the associated OrderBy instance.
-        Otherwise return a None.
+        """
+        Returns a :class:`bool` depending on whether this column is visible.
 
         """
-        try:
-            return self.table.order_by[self.name]
-        except IndexError:
-            return None
+        return self.column.visible
 
 
-class Columns(object):
-    """Container for spawning BoundColumns.
+class BoundColumns(object):
+    """
+    Container for spawning BoundColumns.
 
-    This is bound to a table and provides its ``columns`` property. It
-    provides access to those columns in different ways (iterator,
-    item-based, filtered and unfiltered etc), stuff that would not be
-    possible with a simple iterator in the table class.
+    This is bound to a table and provides its :attr:`.Table.columns` property.
+    It provides access to those columns in different ways (iterator,
+    item-based, filtered and unfiltered etc), stuff that would not be possible
+    with a simple iterator in the table class.
 
-    A :class:`Columns` object is a container for holding :class:`BoundColumn`
-    objects. It provides methods that make accessing columns easier than if
-    they were stored in a ``list`` or ``dict``. :class:`Columns` has a similar
-    API to a ``dict`` (it actually uses a :class:`SortedDict` interally).
+    A :class:`BoundColumns` object is a container for holding
+    :class:`BoundColumn` objects. It provides methods that make accessing
+    columns easier than if they were stored in a :class:`list` or
+    :class:`dict`. :class:`Columns` has a similar API to a :class:`dict` (it
+    actually uses a :class:`SortedDict` interally).
 
     At the moment you'll only come across this class when you access a
-    :attr:`Table.columns` property.
+    :attr:`.Table.columns` property.
+
+    :type table: :class:`.Table` object
+    :param table: the table containing the columns
 
     """
     def __init__(self, table):
-        """Initialise a :class:`Columns` object.
-
-        *table* must be a :class:`Table` object.
-
-        """
         self.table = table
         # ``self._columns`` attribute stores the bound columns (columns that
         # have a real name, )
         self._columns = SortedDict()
 
     def _spawn_columns(self):
-        # (re)build the "_columns" cache of BoundColumn objects (note that
-        # ``base_columns`` might have changed since last time); creating
-        # BoundColumn instances can be costly, so we reuse existing ones.
-        new_columns = SortedDict()
+        """
+        (re)build the "_bound_columns" cache of :class:`.BoundColumn` objects
+        (note that :attr:`.base_columns` might have changed since last time);
+        creating :class:`.BoundColumn` instances can be costly, so we reuse
+        existing ones.
+
+        """
+        columns = SortedDict()
         for name, column in self.table.base_columns.items():
             if name in self._columns:
-                new_columns[name] = self._columns[name]
+                columns[name] = self._columns[name]
             else:
-                new_columns[name] = BoundColumn(self.table, column, name)
-        self._columns = new_columns
+                columns[name] = BoundColumn(self.table, column, name)
+        self._columns = columns
 
     def all(self):
-        """Iterate through all :class:`BoundColumn` objects, regardless of
-        visiblity or sortability.
+        """
+        Return an iterator that exposes all :class:`.BoundColumn` objects,
+        regardless of visiblity or sortability.
 
         """
         self._spawn_columns()
@@ -348,8 +446,9 @@ class Columns(object):
             yield column
 
     def items(self):
-        """Return an iterator of ``(name, column)`` pairs (where *column* is a
-        :class:`BoundColumn` object).
+        """
+        Return an iterator of ``(name, column)`` pairs (where ``column`` is a
+        :class:`.BoundColumn` object).
 
         """
         self._spawn_columns()
@@ -363,7 +462,8 @@ class Columns(object):
             yield r
 
     def sortable(self):
-        """Same as :meth:`all` but only returns sortable :class:`BoundColumn`
+        """
+        Same as :meth:`.BoundColumns.all` but only returns sortable :class:`BoundColumn`
         objects.
 
         This is useful in templates, where iterating over the full
@@ -377,8 +477,9 @@ class Columns(object):
                 yield column
 
     def visible(self):
-        """Same as :meth:`sortable` but only returns visible
-        :class:`BoundColumn` objects.
+        """
+        Same as :meth:`.sortable` but only returns visible
+        :class:`.BoundColumn` objects.
 
         This is geared towards table rendering.
 
index d7e9e4ada388ca680d33367039730a5374cae934..6fab20264be04d7f23ca4302c44cb957f292113b 100644 (file)
@@ -133,30 +133,35 @@ class BoundRow(object):
             return item in self
 
 
-class Rows(object):
-    """Container for spawning BoundRows.
+class BoundRows(object):
+    """
+    Container for spawning :class:`.BoundRow` objects.
+
+    The :attr:`.tables.Table.rows` attribute is a :class:`.BoundRows` object.
+    It provides functionality that would not be possible with a simple iterator
+    in the table class.
 
-    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.
+        """
+        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, otherwise :const:`None`.
 
         """
         if not hasattr(self.table, 'page'):
@@ -164,7 +169,7 @@ class Rows(object):
         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):
index 3e8afdfc8ac5f99b55c4b7317441762f6803bd27..cba5d5f6dc900946e837263ee38043052bbf321d 100644 (file)
@@ -7,17 +7,19 @@ from django.template.loader import get_template
 from django.template import Context
 from django.utils.encoding import StrAndUnicode
 from .utils import OrderBy, OrderByTuple, Accessor, AttributeDict
-from .columns import Column
-from .rows import Rows, BoundRow
-from .columns import Columns
+from .rows import BoundRows, BoundRow
+from .columns import BoundColumns, Column
 
-__all__ = ('Table',)
 
 QUERYSET_ACCESSOR_SEPARATOR = '__'
 
 class TableData(object):
-    """Exposes a consistent API for a table data. It currently supports a
-    :class:`QuerySet` or a ``list`` of ``dict``s.
+    """
+    Exposes a consistent API for :term:`table data`. It currently supports a
+    :class:`QuerySet`, or a :class:`list` of :class:`dict` objects.
+
+    This class is used by :class:.Table` to wrap any
+    input table data.
 
     """
     def __init__(self, data, table):
@@ -45,7 +47,13 @@ class TableData(object):
                                       else len(self.list))
 
     def order_by(self, order_by):
-        """Order the data based on column names in the table."""
+        """
+        Order the data based on column names in the table.
+
+        :param order_by: the ordering to apply
+        :type order_by: an :class:`~.utils.OrderByTuple` object
+
+        """
         # translate order_by to something suitable for this data
         order_by = self._translate_order_by(order_by)
         if hasattr(self, 'queryset'):
@@ -70,7 +78,8 @@ class TableData(object):
         return OrderByTuple(translated)
 
     def _populate_missing_values(self, data):
-        """Populates self._data with missing values based on the default value
+        """
+        Populates self._data with missing values based on the default value
         for each column. It will create new items in the dataset (not modify
         existing ones).
 
@@ -108,7 +117,8 @@ class TableData(object):
 
 
 class DeclarativeColumnsMetaclass(type):
-    """Metaclass that converts Column attributes on the class to a dictionary
+    """
+    Metaclass that converts Column attributes on the class to a dictionary
     called ``base_columns``, taking into account parent class ``base_columns``
     as well.
 
@@ -147,20 +157,17 @@ class DeclarativeColumnsMetaclass(type):
 
 
 class TableOptions(object):
-    """Options for a :term:`table`.
-
-    The following parameters are extracted via attribute access from the
-    *object* parameter.
-
-    :param sortable:
-        bool determining if the table supports sorting.
-    :param order_by:
-        tuple describing the fields used to order the contents.
-    :param attrs:
-        HTML attributes added to the ``<table>`` tag.
-
+    """
+    Extracts and exposes options for a :class:`.Table` from a ``class Meta``
+    when the table is defined.
     """
     def __init__(self, options=None):
+        """
+
+        :param options: options for a table
+        :type options: :class:`Meta` on a :class:`.Table`
+
+        """
         super(TableOptions, self).__init__()
         self.sortable = getattr(options, 'sortable', None)
         order_by = getattr(options, 'order_by', ())
@@ -171,7 +178,21 @@ class TableOptions(object):
 
 
 class Table(StrAndUnicode):
-    """A collection of columns, plus their associated data rows."""
+    """A collection of columns, plus their associated data rows.
+
+    :type data: :class:`list` or :class:`QuerySet`
+    :param data:
+        The :term:`table data`.
+
+    :param: :class:`tuple`-like or :class:`basestring`
+    :param order_by:
+        The description of how the table should be ordered. This allows the
+        :attr:`.Table.Meta.order_by` option to be overridden.
+
+    .. note::
+        Unlike a :class:`Form`, tables are always bound to data.
+
+    """
     __metaclass__ = DeclarativeColumnsMetaclass
 
     # this value is not the same as None. it means 'use the default sort
@@ -181,29 +202,8 @@ class Table(StrAndUnicode):
     TableDataClass = TableData
 
     def __init__(self, data, order_by=DefaultOrder):
-        """Create a new table instance with the iterable ``data``.
-
-        :param order_by:
-            If specified, it must be a sequence containing the names of columns
-            in the order that they should be ordered (much the same as
-            :method:`QuerySet.order_by`)
-
-            If not specified, the table will fall back to the
-            :attr:`Meta.order_by` setting.
-
-        Note that unlike a ``Form``, tables are always bound to data. Also
-        unlike a form, the ``columns`` attribute is read-only and returns
-        ``BoundColumn`` wrappers, similar to the ``BoundField``s you get
-        when iterating over a form. This is because the table iterator
-        already yields rows, and we need an attribute via which to expose
-        the (visible) set of (bound) columns - ``Table.columns`` is simply
-        the perfect fit for this. Instead, ``base_colums`` is copied to
-        table instances, so modifying that will not touch the class-wide
-        column list.
-
-        """
-        self._rows = Rows(self)  # bound rows
-        self._columns = Columns(self)  # bound columns
+        self._rows = BoundRows(self)  # bound rows
+        self._columns = BoundColumns(self)  # bound columns
         self._data = self.TableDataClass(data=data, table=self)
 
         # None is a valid order, so we must use DefaultOrder as a flag
@@ -271,9 +271,7 @@ class Table(StrAndUnicode):
         """The attributes that should be applied to the ``<table>`` tag when
         rendering HTML.
 
-        ``attrs`` is an :class:`AttributeDict` object which allows the
-        attributes to be rendered to HTML element style syntax via the
-        :meth:`~AttributeDict.as_html` method.
+        :returns: :class:`~.utils.AttributeDict` object.
 
         """
         return self._meta.attrs
index 323ce3ef4b46dc1a677b0fdfff873b805fd28c5e..a564e7bc396e29212cdfa7510eff0ffc81b5cf3c 100644 (file)
@@ -3,7 +3,7 @@
     <thead>
         <tr class="{% cycle "odd" "even" %}">
         {% for column in table.columns %}
-            <th>{{ column }}</th>
+            <th>{{ column.header }}</th>
         {% endfor %}
         </tr>
     </thead>
index 72128761ed3d06cac30c6fa4c2128841941f7576..9ecf79ac4ede8dc74e42f77370f73c60ff2ad181 100644 (file)
@@ -1,12 +1,12 @@
-{% load django_tables %}
 {% spaceless %}
+{% load django_tables %}
 <table{% if table.attrs %} {{ table.attrs.as_html }}{% endif %}>
     <thead>
         <tr class="{% cycle "odd" "even" %}">
         {% for column in table.columns %}
         {% if column.sortable %}
             {% with column.order_by as ob %}
-            <th class="{% spaceless %}{% if column.sortable %}sortable {% endif %}{% if ob %}{% if ob.is_descending %}desc{% else %}asc{% endif %}{% endif %}{% endspaceless %}"><a href="{% if ob %}{% set_url_param sort=ob.opposite %}{% else %}{% set_url_param sort=column.name %}{% endif %}">{{ column.verbose_name }}</a></th>
+            <th class="{% spaceless %}{% if column.sortable %}sortable {% endif %}{% if ob %}{% if ob.is_descending %}desc{% else %}asc{% endif %}{% endif %}{% endspaceless %}"><a href="{% if ob %}{% set_url_param sort=ob.opposite %}{% else %}{% set_url_param sort=column.name %}{% endif %}">{{ column.header }}</a></th>
             {% endwith %}
         {% else %}
             <th>{{ column.verbose_name }}</th>
index f327df25d7c9f938b087acee81aa2fedc3c9e9ab..405f4471274b0ca33eae65c00b525dfdd1d7822a 100644 (file)
@@ -10,22 +10,26 @@ __all__ = ('BaseTable', 'options')
 
 
 class OrderBy(str):
-    """A single element in an :class:`OrderByTuple`. This class is essentially
-    just a :class:`str` with some extra properties.
+    """A single item in an :class:`.OrderByTuple` object. This class is
+    essentially just a :class:`str` with some extra properties.
 
     """
     @property
     def bare(self):
-        """Return the bare or naked version. That is, remove a ``-`` prefix if
-        it exists and return the result.
+        """
+        Return the :term:`bare <bare orderby>` form.
+
+        :rtype: :class:`.OrderBy` object
 
         """
         return OrderBy(self[1:]) if self[:1] == '-' else self
 
     @property
     def opposite(self):
-        """Return the an :class:`OrderBy` object with the opposite sort
-        influence. e.g.
+        """
+        Return an :class:`.OrderBy` object with an opposite sort influence.
+
+        Example:
 
         .. code-block:: python
 
@@ -33,40 +37,49 @@ class OrderBy(str):
             >>> order_by.opposite
             '-name'
 
+        :rtype: :class:`.OrderBy` object
+
         """
         return OrderBy(self[1:]) if self.is_descending else OrderBy('-' + self)
 
     @property
     def is_descending(self):
-        """Return :const:`True` if this object induces *descending* ordering."""
+        """
+        Return :const:`True` if this object induces *descending* ordering
+
+        :rtype: :class:`bool`
+
+        """
         return self.startswith('-')
 
     @property
     def is_ascending(self):
-        """Return :const:`True` if this object induces *ascending* ordering."""
+        """
+        Return :const:`True` if this object induces *ascending* ordering.
+
+        :returns: :class:`bool`
+
+        """
         return not self.is_descending
 
 
 class OrderByTuple(tuple, StrAndUnicode):
-    """Stores ordering instructions (as :class:`OrderBy` objects). The
-    :attr:`Table.order_by` property is always converted into an
-    :class:`OrderByTuplw` objectUsed to render output in a format we understand
-    as input (see :meth:`~OrderByTuple.__unicode__`) - especially useful in
-    templates.
+    """Stores ordering as (as :class:`.OrderBy` objects). The
+    :attr:`django_tables.tables.Table.order_by` property is always converted
+    to an :class:`.OrderByTuple` object.
+
+    This class is essentially just a :class:`tuple` with some useful extras.
 
-    It's quite easy to create one of these. Pass in an iterable, and it will
-    automatically convert each element into an :class:`OrderBy` object. e.g.
+    Example:
 
     .. code-block:: python
 
-        >>> ordering = ('name', '-age')
-        >>> order_by_tuple = OrderByTuple(ordering)
-        >>> age = order_by_tuple['age']
-        >>> age
+        >>> x = OrderByTuple(('name', '-age'))
+        >>> x['age']
         '-age'
-        >>> age.is_descending
+        >>> x['age'].is_descending
         True
-        >>> age.opposite
+        >>> x['age'].opposite
         'age'
 
     """
@@ -83,18 +96,23 @@ class OrderByTuple(tuple, StrAndUnicode):
         return ','.join(self)
 
     def __contains__(self, name):
-        """Determine whether a column is part of this order (i.e. descending
-        prefix agnostic). e.g.
+        """
+        Determine if a column has an influence on ordering.
+
+        Example:
 
         .. code-block:: python
 
-            >>> ordering = ('name', '-age')
-            >>> order_by_tuple = OrderByTuple(ordering)
-            >>> 'age' in  order_by_tuple
+            >>> ordering =
+            >>> x = OrderByTuple(('name', '-age'))
+            >>> 'age' in  x
             True
-            >>> '-age' in order_by_tuple
+            >>> '-age' in x
             True
 
+        :param name: The name of a column. (optionally prefixed)
+        :returns: :class:`bool`
+
         """
         for o in self:
             if o == name or o.bare == name:
@@ -102,20 +120,24 @@ class OrderByTuple(tuple, StrAndUnicode):
         return False
 
     def __getitem__(self, index):
-        """Allows an :class:`OrderBy` object to be extracted using
-        :class:`dict`-style indexing in addition to standard 0-based integer
-        indexing. The :class:`dict`-style is prefix agnostic in the same way as
-        :meth:`~OrderByTuple.__contains__`.
+        """
+        Allows an :class:`.OrderBy` object to be extracted via named or integer
+        based indexing.
+
+        When using named based indexing, it's fine to used a prefixed named.
 
         .. code-block:: python
 
-            >>> ordering = ('name', '-age')
-            >>> order_by_tuple = OrderByTuple(ordering)
-            >>> order_by_tuple['age']
+            >>> x = OrderByTuple(('name', '-age'))
+            >>> x[0]
+            'name'
+            >>> x['age']
             '-age'
-            >>> order_by_tuple['-age']
+            >>> x['-age']
             '-age'
 
+        :rtype: :class:`.OrderBy` object
+
         """
         if isinstance(index, basestring):
             for ob in self:
@@ -126,8 +148,12 @@ class OrderByTuple(tuple, StrAndUnicode):
 
     @property
     def cmp(self):
-        """Return a function suitable for sorting a list. This is used for
-        non-:class:`QuerySet` data sources.
+        """
+        Return a function for use with :meth:`list.sort()` that implements this
+        object's ordering. This is used to sort non-:class:`QuerySet` based
+        :term:`table data`.
+
+        :rtype: function
 
         """
         def _cmp(a, b):
@@ -146,12 +172,46 @@ class OrderByTuple(tuple, StrAndUnicode):
 
 
 class Accessor(str):
+    """
+    A string describing a path from one object to another via attribute/index
+    accesses. For convenience, the class has an alias ``A`` to allow for more concise code.
+
+    Relations are separated by a ``.`` character.
+
+    """
     SEPARATOR = '.'
 
     def resolve(self, context):
-        # Try to resolve relationships spanning attributes. This is
-        # basically a copy/paste from django/template/base.py in
-        # Variable._resolve_lookup()
+        """
+        Return an object described by the accessor by traversing the attributes
+        of *context*.
+
+        Example:
+
+        .. code-block:: python
+
+            >>> x = Accessor('__len__`')
+            >>> x.resolve('brad')
+            4
+            >>> x = Accessor('0.upper')
+            >>> x.resolve('brad')
+            'B'
+
+        :type context: :class:`object`
+        :param context: The root/first object to traverse.
+        :returns: target object
+        :raises: TypeError, AttributeError, KeyError, ValueError
+
+        :meth:`~.Accessor.resolve` attempts lookups in the following order:
+
+        - dictionary (e.g. ``obj[related]``)
+        - attribute (e.g. ``obj.related``)
+        - list-index lookup (e.g. ``obj[int(related)]``)
+
+        Callable objects are called, and their result is used, before
+        proceeding with the resolving.
+
+        """
         current = context
         for bit in self.bits:
             try:  # dictionary lookup
@@ -190,8 +250,25 @@ class AttributeDict(dict):
     """A wrapper around :class:`dict` that knows how to render itself as HTML
     style tag attributes.
 
+    The returned string is marked safe, so it can be used safely in a template.
+    See :meth:`.as_html` for a usage example.
+
     """
     def as_html(self):
-        """Render as HTML style tag attributes."""
+        """
+        Render to HTML tag attributes.
+
+        Example:
+
+        .. code-block:: python
+
+            >>> from django_tables.utils import AttributeDict
+            >>> attrs = AttributeDict({'class': 'mytable', 'id': 'someid'})
+            >>> attrs.as_html()
+            'class="mytable" id="someid"'
+
+        :rtype: :class:`~django.utils.safestring.SafeUnicode` object
+
+        """
         return mark_safe(' '.join(['%s="%s"' % (k, escape(v))
                                    for k, v in self.iteritems()]))
index 37726b76b6fee57e3e69ad4d29490c986eb2892a..36f0aabe393bdc51f67fd4eed4b6f722853561fa 100644 (file)
@@ -50,9 +50,9 @@ project = u'django-tables'
 # built documents.
 #
 # The short X.Y version.
-version = '0.4.0.beta'
+version = '0.4.0'
 # The full version, including alpha/beta/rc tags.
-release = '0.4.0.beta'
+release = '0.4.0.beta2'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
index 25a4401dcd8fb11e390486c1f0fd179d976025bf..37f2c061407f3a02f9116f52b9a47758d885ff96 100644 (file)
@@ -1,8 +1,8 @@
 .. default-domain:: py
 
-=====================================================
+===============================================
 django-tables - An app for creating HTML tables
-=====================================================
+===============================================
 
 django-tables simplifies the task of turning sets of datainto HTML tables. It
 has native support for pagination and sorting. It does for HTML tables what
@@ -11,243 +11,240 @@ has native support for pagination and sorting. It does for HTML tables what
 Quick start guide
 =================
 
-1. Download and install the package.
-2. Install the tables framework by adding ``'django_tables'`` to your
+1. Download and install from https://github.com/bradleyayers/django-tables.
+   Grab a ``.tar.gz`` of the latest tag, and run ``pip install <tar.gz>``.
+2. Hook the app into your Django project by adding ``'django_tables'`` to your
    ``INSTALLED_APPS`` setting.
-3. Ensure that ``'django.core.context_processors.request'`` is in your
-   ``TEMPLATE_CONTEXT_PROCESSORS`` setting.
-4. Write table classes for the types of tables you want to display.
-5. Create an instance of a table in a view, provide it your data, and pass it
-   to a template for display.
-6. Use ``{{ table.as_html }}``, the
-   :ref:`template tag <template_tags.render_table>`, or your own
-   custom template code to display the table.
+3. Write a subclass of :class:`~django_tables.tables.Table` that describes the
+   structure of your table.
+4. Create an instance of your table in a :term:`view`, provide it with
+   :term:`table data`, and pass it to a :term:`template` for display.
+5. Use ``{{ table.as_html }}``, the
+   :ref:`template tag <template-tags.render_table>`, or your own
+   :ref:`custom template <custom-template>` to display the table.
 
 
-Tables
-======
+Slow start guide
+================
 
-For each type of table you want to display, you will need to create a subclass
-of ``django_tables.Table`` that describes the structure of the table.
-
-In this example we are going to take some data describing three countries and
-turn it into a HTML table. We start by creating our data:
+We're going to take some data that describes three countries and
+turn it into an HTML table. This is the data we'll be using:
 
 .. code-block:: python
 
-    >>> countries = [
-    ...     {'name': 'Australia', 'population': 21, 'tz': 'UTC +10', 'visits': 1},
-    ...     {'name': 'Germany', 'population', 81, 'tz': 'UTC +1', 'visits': 2},
-    ...     {'name': 'Mexico', 'population': 107, 'tz': 'UTC -6', 'visits': 0},
-    ... ]
+    countries = [
+        {'name': 'Australia', 'population': 21, 'tz': 'UTC +10', 'visits': 1},
+        {'name': 'Germany', 'population', 81, 'tz': 'UTC +1', 'visits': 2},
+        {'name': 'Mexico', 'population': 107, 'tz': 'UTC -6', 'visits': 0},
+    ]
+
 
-Next we subclass ``django_tables.Table`` to create a table that describes our
-data. The API should look very familiar since it's based on Django's
-database model API:
+The first step is to subclass :class:`~django_tables.tables.Table` and describe
+the table structure. This is done by creating a column for each attribute in
+the :term:`table data`.
 
 .. code-block:: python
 
-    >>> import django_tables as tables
-    >>> class CountryTable(tables.Table):
-    ...     name = tables.Column()
-    ...     population = tables.Column()
-    ...     tz = tables.Column(verbose_name='Time Zone')
-    ...     visits = tables.Column()
+    import django_tables as tables
 
+    class CountryTable(tables.Table):
+        name = tables.Column()
+        population = tables.Column()
+        tz = tables.Column(verbose_name='Time Zone')
+        visits = tables.Column()
 
-Providing data
---------------
 
-To use the table, simply create an instance of the table class and pass in your
-data. e.g. following on from the above example:
+Now that we've defined our table, it's ready for use. We simply create an
+instance of it, and pass in our table data.
 
 .. code-block:: python
 
-    >>> table = CountryTable(countries)
+    table = CountryTable(countries)
 
-Tables have support for any iterable data that contains objects with
-attributes that can be accessed as property or dictionary syntax:
+Now we add it to our template context and render it to HTML. Typically you'd
+write a view that would look something like:
 
 .. code-block:: python
 
-    >>> table = SomeTable([{'a': 1, 'b': 2}, {'a': 4, 'b': 8}])  # valid
-    >>> table = SomeTable(SomeModel.objects.all())  # also valid
-
-Each item in the data corresponds to one row in the table. By default, the
-table uses column names as the keys (or attributes) for extracting cell values
-from the data. This can be changed by using the :attr:`~Column.accessor`
-argument.
+    def home(request):
+        table = CountryTable(countries)
+        return render_to_response('home.html', {'table': table},
+                                  context_instances=RequestContext(request))
 
-
-Displaying a table
-------------------
-
-There are two ways to display a table, the easiest way is to use the table's
-own ``as_html`` method:
+In your template, the easiest way to :term:`render` the table is via the
+:meth:`~django_tables.tables.Table.as_html` method:
 
 .. code-block:: django
 
     {{ table.as_html }}
 
-Which will render something like:
+…which will render something like:
 
-+--------------+------------+---------+
-| Country Name | Population | Tz      |
-+==============+============+=========+
-| Australia    | 21         | UTC +10 |
-+--------------+------------+---------+
-| Germany      | 81         | UTC +1  |
-+--------------+------------+---------+
-| Mexico       | 107        | UTC -6  |
-+--------------+------------+---------+
++--------------+------------+---------+--------+
+| Country Name | Population | Tz      | Visit  |
++==============+============+=========+========+
+| Australia    | 21         | UTC +10 | 1      |
++--------------+------------+---------+--------+
+| Germany      | 81         | UTC +1  | 2      |
++--------------+------------+---------+--------+
+| Mexico       | 107        | UTC -6  | 0      |
++--------------+------------+---------+--------+
 
-The downside of this approach is that pagination and sorting will not be
-available. These features require the use of the ``{% render_table %}``
-template tag:
+This approach is easy, but it's not fully featured. For slightly more effort,
+you can render a table with sortable columns. For this, you must use the
+template tag.
 
 .. code-block:: django
 
     {% load django_tables %}
     {% render_table table %}
 
-See :ref:`template_tags` for more information.
+See :ref:`template-tags.render_table` for more information.
 
+The table will be rendered, but chances are it will still look quite ugly. An
+easy way to make it pretty is to use the built-in *paleblue* theme. For this to
+work, you must add a CSS class to the ``<table>`` tag. This can be achieved by
+adding a ``class Meta:`` to the table class and defining a ``attrs`` variable.
 
-Ordering
---------
+.. code-block:: python
 
-Controlling the order that the rows are displayed (sorting) is simple, just use
-the :attr:`~Table.order_by` property or pass it in when initialising the
-instance:
+    import django_tables as tables
 
-.. code-block:: python
+    class CountryTable(tables.Table):
+        name = tables.Column()
+        population = tables.Column()
+        tz = tables.Column(verbose_name='Time Zone')
+        visits = tables.Column()
 
-    >>> # order_by argument when creating table instances
-    >>> table = CountryTable(countries, order_by='name, -population')
-    >>> table = CountryTable(countries, order_by=('name', '-population'))
-    >>> # order_by property on table instances
-    >>> table = CountryTable(countries)
-    >>> table.order_by = 'name, -population'
-    >>> table.order_by = ('name', '-population')
+        class Meta:
+            attrs = {'class': 'paleblue'}
 
+The last thing to do is to include the stylesheet in the template.
 
-Customising the output
-======================
+.. code-block:: html
 
-There are a number of options available for changing the way the table is
-rendered. Each approach provides balance of ease-of-use and control (the more
-control you want, the less easy it is to use).
+    <link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}django_tables/themes/paleblue/css/screen.css" />
 
-CSS
----
+Save your template and reload the page in your browser.
+
+
+.. _table-data:
+
+Table data
+==========
 
-If you want to affect the appearance of the table using CSS, you probably want
-to add a ``class`` or ``id`` attribute to the ``<table>`` element. This can be
-achieved by specifying an ``attrs`` variable in the table's ``Meta`` class.
+The data used to populate a table is called :term:`table data`. To provide a
+table with data, pass it in as the first argument when instantiating a table.
 
 .. code-block:: python
 
-    >>> import django_tables as tables
-    >>> class SimpleTable(tables.Table):
-    ...     id = tables.Column()
-    ...     age = tables.Column()
-    ...
-    ...     class Meta:
-    ...         attrs = {'class': 'mytable'}
-    ...
-    >>> table = SimpleTable()
-    >>> table.as_html()
-    '<table class="mytable">...'
+    table = CountryTable(countries)              # valid
+    table = CountryTable(Country.objects.all())  # also valid
+
+Each item in the :term:`table data` is called a :term:`record` and is used to
+populate a single row in the table. By default, the table uses column names
+as :term:`accessors <accessor>` to retrieve individual cell values. This can
+be changed via the :attr:`~django_tables.columns.Column.accessor` argument.
+
+Any iterable can be used as table data, and there's builtin support for
+:class:`QuerySet` objects (to ensure they're handled effeciently).
 
-The :attr:`Table.attrs` property actually returns an :class:`AttributeDict`
-object. These objects are identical to :class:`dict`, but have an
-:meth:`AttributeDict.as_html` method that returns a HTML tag attribute string.
+
+.. _ordering:
+
+Ordering
+========
+
+Changing the table ordering is easy. When creating a
+:class:`~django_tables.tables.Table` object include an `order_by` parameter
+with a tuple that describes the way the ordering should be applied.
 
 .. code-block:: python
 
-    >>> from django_tables.utils import AttributeDict
-    >>> attrs = AttributeDict({'class': 'mytable', 'id': 'someid'})
-    >>> attrs.as_html()
-    'class="mytable" id="someid"'
+    table = CountryTable(countries, order_by=('name', '-population'))
+    table = CountryTable(countries, order_by='name,-population')  # equivalant
+
+Alternatively, the :attr:`~django_tables.tables.Table.order_by` attribute can
+by modified.
+
+    table = CountryTable(countries)
+    table.order_by = ('name', '-population')
+    table.order_by = 'name,-population'  # equivalant
 
-The returned string is marked safe, so it can be used safely in a template.
 
-Column formatter
-----------------
+.. _custom-rendering:
 
-Using a formatter is a quick way to adjust the way values are displayed in a
-column. A limitation of this approach is that you *only* have access to a
-single attribute of the data source.
+Custom rendering
+================
 
-To use a formatter, simply provide the :attr:`~Column.formatter` argument to a
-:class:`Column` when you define the :class:`Table`:
+Various options are available for changing the way the table is :term:`rendered
+<render>`. Each approach has a different balance of ease-of-use and
+flexibility.
+
+CSS
+---
+
+In order to use CSS to style a table, you'll probably want to add a
+``class`` or ``id`` attribute to the ``<table>`` element. ``django-tables`` has
+a hook that allows abitrary attributes to be added to the ``<table>`` tag.
 
 .. code-block:: python
 
     >>> import django_tables as tables
     >>> class SimpleTable(tables.Table):
-    ...     id = tables.Column(formatter=lambda x: '#%d' % x)
-    ...     age = tables.Column(formatter=lambda x: '%d years old' % x)
+    ...     id = tables.Column()
+    ...     age = tables.Column()
     ...
-    >>> table = SimpleTable([{'age': 31, 'id': 10}, {'age': 34, 'id': 11}])
-    >>> row = table.rows[0]
-    >>> for cell in row:
-    ...     print cell
+    ...     class Meta:
+    ...         attrs = {'class': 'mytable'}
     ...
-    #10
-    31 years old
-
-As you can see, the only the value of the column is available to the formatter.
-This means that **it's impossible create a formatter that incorporates other
-values of the record**, e.g. a column with an ``<a href="...">`` that uses
-:func:`reverse` with the record's ``pk``.
+    >>> table = SimpleTable()
+    >>> table.as_html()
+    '<table class="mytable">...'
 
-If formatters aren't powerful enough, you'll need to either :ref:`create a
-Column subclass <subclassing-column>`, or to use the
-:ref:`Table.render_FOO method <table.render_foo>`.
+Inspired by Django's ORM, the ``class Meta:`` allows you to define extra
+characteristics of a table. See :class:`Table.Meta` for details.
 
 
 .. _table.render_foo:
 
-:meth:`Table.render_FOO` Method
--------------------------------
+:meth:`Table.render_FOO` Methods
+--------------------------------
+
+If you want to adjust the way table cells in a particular column are rendered,
+you can implement a ``render_FOO`` method. ``FOO`` is replaced with the
+:term:`name <column name>` of the column.
 
 This approach provides a lot of control, but is only suitable if you intend to
 customise the rendering for a single table (otherwise you'll end up having to
 copy & paste the method to every table you want to modify – which violates
 DRY).
 
-The example below has a number of different techniques in use:
-
-* :meth:`Column.render` (accessible via :attr:`BoundColumn.column`) applies the
-  *formatter* if it's been provided. The effect of this behaviour can be seen
-  below in the output for the ``id`` column. Square brackets (from the
-  *formatter*) have been applied *after* the angled brackets (from the
-  :meth:`~Table.render_FOO`).
-* Completely abitrary values can be returned by :meth:`render_FOO` methods, as
-  shown in :meth:`~SimpleTable.render_row_number` (a :attr:`_counter` attribute
-  is added to the :class:`SimpleTable` object to keep track of the row number).
+For convenience, a bunch of commonly used/useful values are passed to
+``render_FOO`` functions, when writing the signature, accept the arguments
+you're interested in, and collect the rest in a ``**kwargs`` argument.
 
-  This is possible because :meth:`render_FOO` methods override the default
-  behaviour of retrieving a value from the data-source.
+:param value: the value for the cell retrieved from the :term:`table data`
+:param record: the entire record for the row from :term:`table data`
+:param column: the :class:`.Column` object
+:param bound_column: the :class:`.BoundColumn` object
+:param bound_row: the :class:`.BoundRow` object
+:param table: alias for ``self``
 
 .. code-block:: python
 
     >>> import django_tables as tables
     >>> class SimpleTable(tables.Table):
     ...     row_number = tables.Column()
-    ...     id = tables.Column(formatter=lambda x: '[%s]' % x)
-    ...     age = tables.Column(formatter=lambda x: '%d years old' % x)
+    ...     id = tables.Column()
+    ...     age = tables.Column()
     ...
-    ...     def render_row_number(self, bound_column, bound_row):
+    ...     def render_row_number(self, **kwargs):
     ...         value = getattr(self, '_counter', 0)
     ...         self._counter = value + 1
     ...         return 'Row %d' % value
     ...
-    ...     def render_id(self, bound_column, bound_row):
-    ...         value = bound_column.column.render(table=self,
-    ...                                            bound_column=bound_column,
-    ...                                            bound_row=bound_row)
+    ...     def render_id(self, value, **kwargs):
     ...         return '<%s>' % value
     ...
     >>> table = SimpleTable([{'age': 31, 'id': 10}, {'age': 34, 'id': 11}])
@@ -255,14 +252,11 @@ The example below has a number of different techniques in use:
     ...     print cell
     ...
     Row 0
-    <[10]>
-    31 years old
+    <10>
+    31
 
-The :meth:`Column.render` method is what actually performs the lookup into a
-record to retrieve the column value. In the example above, the
-:meth:`render_row_number` never called :meth:`Column.render` and as a result
-there was not attempt to access the data source to retrieve a value.
 
+.. _custom-template:
 
 Custom Template
 ---------------
@@ -362,15 +356,15 @@ rendered). This can be achieved by using the :func:`mark_safe` function.
 Template tags
 =============
 
-.. _template_tags.render_table:
+.. _template-tags.render_table:
 
 render_table
 ------------
 
-If you want to render a table that provides support for sorting and pagination,
-you must use the ``{% render_table %}`` template tag. In this example ``table``
-is an instance of a :class:`django_tables.Table` that has been put into the
-template context:
+Renders a :class:`~django_tables.tables.Table` object to HTML and includes as
+many features as possible.
+
+Sample usage:
 
 .. code-block:: django
 
@@ -378,7 +372,7 @@ template context:
     {% render_table table %}
 
 
-.. _template_tags.set_url_param:
+.. _template-tags.set_url_param:
 
 set_url_param
 -------------
@@ -392,9 +386,8 @@ This is very useful if you want the give your users the ability to interact
 with your table (e.g. change the ordering), because you will need to create
 urls with the appropriate queries.
 
-Let's assume we have the query-string
-``?search=pirates&sort=name&page=5`` and we want to update the ``sort``
-parameter:
+Let's assume we have the querystring ``?search=pirates&sort=name&page=5`` and
+we want to update the ``sort`` parameter:
 
 .. code-block:: django
 
@@ -432,11 +425,57 @@ which can be iterated over:
 API Reference
 =============
 
+:class:`Accessor` Objects:
+--------------------------
+
+.. autoclass:: django_tables.utils.Accessor
+    :members:
+
+
 :class:`Table` Objects:
-------------------------
+-----------------------
 
 .. autoclass:: django_tables.tables.Table
-    :members:
+
+
+:class:`Table.Meta` Objects:
+----------------------------
+
+.. class:: Table.Meta
+
+    .. attribute:: attrs
+
+        Allows custom HTML attributes to be specified which will be added to
+        the ``<table>`` tag of any table rendered via
+        :meth:`~django_tables.tables.Table.as_html` or the
+        :ref:`template-tags.render_table` template tag.
+
+        Default: ``{}``
+
+        :type: :class:`dict`
+
+    .. attribute:: sortable
+
+        Does the table support ordering?
+
+        Default: :const:`True`
+
+        :type: :class:`bool`
+
+    .. attribute:: order_by
+
+        The default ordering. e.g. ``('name', '-age')``
+
+        Default: ``()``
+
+        :type: :class:`tuple`
+
+
+:class:`TableData` Objects:
+------------------------------
+
+.. autoclass:: django_tables.tables.TableData
+    :members: __init__, order_by, __getitem__, __len__
 
 
 :class:`TableOptions` Objects:
@@ -450,14 +489,34 @@ API Reference
 ------------------------
 
 .. autoclass:: django_tables.columns.Column
-    :members: __init__, default, render
 
 
-:class:`Columns` Objects
-------------------------
+:class:`CheckBoxColumn` Objects:
+--------------------------------
 
-.. autoclass:: django_tables.columns.Columns
-    :members: __init__, all, items, names, sortable, visible, __iter__,
+.. autoclass:: django_tables.columns.CheckBoxColumn
+    :members:
+
+
+:class:`LinkColumn` Objects:
+----------------------------
+
+.. autoclass:: django_tables.columns.LinkColumn
+    :members:
+
+
+:class:`TemplateColumn` Objects:
+--------------------------------
+
+.. autoclass:: django_tables.columns.TemplateColumn
+    :members:
+
+
+:class:`BoundColumns` Objects
+-----------------------------
+
+.. autoclass:: django_tables.columns.BoundColumns
+    :members: all, items, names, sortable, visible, __iter__,
               __contains__, __len__, __getitem__
 
 
@@ -465,15 +524,14 @@ API Reference
 ----------------------------
 
 .. autoclass:: django_tables.columns.BoundColumn
-    :members: __init__, table, column, name, accessor, default, formatter,
-              sortable, verbose_name, visible
+    :members:
 
 
-:class:`Rows` Objects
----------------------
+:class:`BoundRows` Objects
+--------------------------
 
-.. autoclass:: django_tables.rows.Rows
-    :members: __init__, all, page, __iter__, __len__, count, __getitem__
+.. autoclass:: django_tables.rows.BoundRows
+    :members: __init__, all, page, __iter__, __len__, count
 
 
 :class:`BoundRow` Objects
@@ -501,7 +559,7 @@ API Reference
 -----------------------------
 
 .. autoclass:: django_tables.utils.OrderByTuple
-    :members: __contains__, __getitem__, __unicode__
+    :members: __unicode__, __contains__, __getitem__, cmp
 
 
 Glossary
@@ -509,6 +567,41 @@ Glossary
 
 .. glossary::
 
+    accessor
+        Refers to an :class:`~django_tables.utils.Accessor` object
+
+    bare orderby
+        The non-prefixed form of an :class:`~django_tables.utils.OrderBy`
+        object. Typically the bare form is just the ascending form.
+
+        Example: ``age`` is the bare form of ``-age``
+
+    column name
+        The name given to a column. In the follow example, the *column name* is
+        ``age``.
+
+        .. code-block:: python
+
+            class SimpleTable(tables.Table):
+                age = tables.Column()
+
     table
         The traditional concept of a table. i.e. a grid of rows and columns
         containing data.
+
+    view
+        A Django view.
+
+    record
+        A single Python object used as the data for a single row.
+
+    render
+        The act of serialising a :class:`~django_tables.tables.Table` into
+        HTML.
+
+    template
+        A Django template.
+
+    table data
+        An interable of :term:`records <record>` that
+        :class:`~django_tables.tables.Table` uses to populate its rows.
index f69c18bd1c0748bc0f7c8536c3f38be36c0c63f3..0f96d61d659bc4bb85cae9390b661f349fe73c12 100755 (executable)
--- a/setup.py
+++ b/setup.py
@@ -4,7 +4,7 @@ from setuptools import setup, find_packages
 
 setup(
     name='django-tables',
-    version='0.4.0.beta',
+    version='0.4.0.beta2',
     description='Table framework for Django',
 
     author='Bradley Ayers',
index f2d7f3b833f343d507ea91b68eabc14d9bf5608f..48b6fe98b7d0eb6e6e127845b3fa25f46fea6cff 100644 (file)
@@ -1,5 +1,5 @@
 # -*- coding: utf8 -*-
-from django_tables.utils import OrderByTuple, OrderBy
+from django_tables.utils import OrderByTuple, OrderBy, Accessor
 from attest import Tests, Assert
 
 
@@ -33,3 +33,18 @@ def orderby():
     Assert('b') == b.opposite
     Assert(True) == b.is_descending
     Assert(False) == b.is_ascending
+
+
+@utils.test
+def accessor():
+    x = Accessor('0')
+    Assert('B') == x.resolve('Brad')
+    
+    x = Accessor('1')
+    Assert('r') == x.resolve('Brad')
+
+    x = Accessor('2.upper')
+    Assert('A') == x.resolve('Brad')
+
+    x = Accessor('2.upper.__len__')
+    Assert(1) == x.resolve('Brad')