0.4.0 alpha 1 0.4.0-alpha1
authorBradley Ayers <bradley.ayers@gmail.com>
Thu, 24 Feb 2011 17:07:40 +0000 (03:07 +1000)
committerBradley Ayers <bradley.ayers@gmail.com>
Thu, 24 Feb 2011 17:07:40 +0000 (03:07 +1000)
.gitignore
django_tables/__init__.py
django_tables/columns.py
django_tables/rows.py
django_tables/tables.py
docs/conf.py
docs/index.rst
setup.py

index e1f1a7b3034057b6e86ca09614069d53cfb9bc89..53b958f3ca989d384ed5c537c74f44a4265b1591 100644 (file)
@@ -2,5 +2,6 @@
 /*.komodoproject
 /MANIFEST
 /dist/
+/build/
 /docs/_build/
 /django_tables.egg-info/
index afe96dd54485744894074b0658f86285e2d35ab8..38774d17960fcefcb591a5b8656783c7f25c9902 100644 (file)
@@ -1,25 +1,43 @@
-# -*- coding: utf-8 -*-
-__version__ = (0, 2, 0, 'dev')
+# -*- coding: utf8 -*-
+# (major, minor, bugfix, "pre-alpha" | "alpha" | "beta" | "final", release | 0)
+VERSION = (0, 4, 0, 'alpha', 1)
 
 
 def get_version():
-    version = '%s.%s' % (__version__[0], __version__[1])
-    if __version__[2]:
-        version = '%s.%s' % (version, __version__[2])
-    if __version__[3] != '':
-        version = '%s %s' % (version, __version__[3])
+    version = '%s.%s' % (VERSION[0], VERSION[1])
+    if VERSION[2]:
+        version = '%s.%s' % (version, VERSION[2])
+    if VERSION[3:] == ('alpha', 0):
+        version = '%s pre-alpha' % version
+    else:
+        if VERSION[3] != 'final':
+            version = '%s %s %s' % (version, VERSION[3], VERSION[4])
     return version
 
+
 # We want to make get_version() available to setup.py even if Django is not
-# available or we are not inside a Django project (so we do distutils stuff).
+# available or we are not inside a Django project.
 try:
-    # this fails if project settings module isn't configured
-    from django.contrib import admin
+    import django
 except ImportError:
     import warnings
-    warnings.warn('django-tables requires Django to be configured (settings) '
-        'prior to use, however this has not been done. Version information '
-        'will still be available.')
+    warnings.warn('django-tables requires Django, however it is not installed.'
+        ' Version information will still be available.')
 else:
+    try:
+        # http://docs.djangoproject.com/en/dev/topics/settings/ says::
+        #
+        #   If you don't set DJANGO_SETTINGS_MODULE and don't call configure(),
+        #   Django will raise an ImportError exception the first time a setting is
+        #   accessed.
+        #
+        from django.conf import settings
+        settings.DEBUG  # will raise ImportError if Django isn't configured
+    except ImportError:
+        # allow get_version() to remain available
+        import warnings
+        warnings.warn('django-tables requires Django to be configured... but '
+            "it isn't! A bunch of stuff won't work :(")
+
     from tables import *
     from columns import *
index 532f9b015988b90b5bb172c74200b29885ff102e..d3582621e7a059aa53ac13bd9de1e07c57addb55 100644 (file)
@@ -7,27 +7,99 @@ from django.utils.text import capfirst
 class Column(object):
     """Represents a single column of a table.
 
-    ``verbose_name`` defines a display name for this column used for output.
+    :class:`Column` objects control the way a column (including the cells that
+    fall within it) are rendered.
 
-    You can use ``visible`` to flag the column as hidden by default.
-    However, this can be overridden by the ``visibility`` argument to the
-    table constructor. If you want to make the column completely unavailable
-    to the user, set ``inaccessible`` to True.
-
-    Setting ``sortable`` to False will result in this column being unusable
-    in ordering. You can further change the *default* sort direction to
-    descending using ``direction``. Note that this option changes the actual
-    direction only indirectly. Normal und reverse order, the terms
-    django-tables exposes, now simply mean different things.
-
-    Data can be formatted by using ``formatter``, which accepts a callable as
-    an argument (e.g. lambda x: x.upper())
     """
-    # Tracks each time a Column instance is created. Used to retain order.
+    #: 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, formatter=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.
+
+        :param formatter:
+            A callable object that is used as a final step in formatting the
+            value for a cell. The callable will be passed the string that would
+            have otherwise been displayed in the cell.
+
+            In the following table, cells in the *name* column have upper-case
+            values.
+
+            .. code-block:: python
+
+                class Example(tables.Table):
+                    name = tables.Column(formatter=lambda x: x.upper())
+
+        """
         if not (accessor is None or isinstance(accessor, basestring) or
                 callable(accessor)):
             raise TypeError('accessor must be a string or callable, not %s' %
@@ -47,26 +119,32 @@ class Column(object):
 
     @property
     def default(self):
-        """Since ``Column.default`` property may be a callable, this function
-        handles access.
+        """The default value for cells in this column.
+
+        The default value passed into ``Column.default`` property may be a
+        callable, this function handles access.
+
         """
         return self._default() if callable(self._default) else self._default
 
     def render(self, table, bound_column, bound_row):
         """Returns a cell's content.
         This method can be overridden by ``render_FOO`` methods on the table or
-        by subclassing ``Column``.
+        by subclassing :class:`Column`.
+        
         """
         return table.data.data_for_cell(bound_column=bound_column,
                                         bound_row=bound_row)
 
 
 class CheckBoxColumn(Column):
-    """A subclass of Column that renders its column data as a checkbox
-
-    ``name`` is the html name of the checkbox.
-    """
+    """A subclass of Column that renders its column data as a checkbox"""
     def __init__(self, attrs=None, *args, **kwargs):
+        """
+        :param attrs: a dict of HTML element attributes to be added to the
+            ``<input>``
+
+        """
         super(CheckBoxColumn, self).__init__(*args, **kwargs)
         self.attrs = attrs or {}
 
@@ -86,41 +164,72 @@ class CheckBoxColumn(Column):
 
 
 class BoundColumn(StrAndUnicode):
-    """'Runtime' version of ``Column`` that is bound to a table instance,
-    and thus knows about the table's data. The difference between BoundColumn
-    and Column, is a BoundColumn is aware of actual values (e.g. its name)
-    where-as Column is not.
+    """A *runtime* version of :class:`Column`. The difference between
+    :class:`BoundColumn` and :class:`Column`, is that :class:`BoundColumn`
+    objects are of the relationship between a :class:`Column` and a
+    :class:`Table`. This means that it knows the *name* given to the
+    :class:`Column`.
+
+    For convenience, all :class:`Column` properties are available from this
+    class.
 
-    For convenience, all Column properties are available from this class.
     """
     def __init__(self, table, column, name):
-        """*table* - the table in which this column exists
-        *column* - the column class
-        *name* – the variable name used when the column was defined in the
-                 table class
+        """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
+
         """
-        self.table = table
-        self.column = column
-        self.name = name
+        self._table = table
+        self._column = column
+        self._name = name
 
     def __unicode__(self):
         s = self.column.verbose_name or self.name.replace('_', ' ')
         return capfirst(force_unicode(s))
 
+    @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.
+
+        """
         return self.column.accessor or self.name
 
     @property
     def default(self):
+        """Returns the default value for this column."""
         return self.column.default
 
     @property
     def formatter(self):
+        """Returns a function or ``None`` that represents the formatter for
+        this column.
+
+        """
         return self.column.formatter
 
     @property
     def sortable(self):
+        """Returns a ``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:
@@ -130,10 +239,12 @@ class BoundColumn(StrAndUnicode):
 
     @property
     def verbose_name(self):
+        """Returns the verbose name for this column."""
         return self.column.verbose_name
 
     @property
     def visible(self):
+        """Returns a ``bool`` depending on whether this column is visible."""
         return self.column.visible
 
 
@@ -144,8 +255,22 @@ class Columns(object):
     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).
+
+    At the moment you'll only come across this class when you access a
+    :attr:`Table.columns` property.
+
     """
     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, )
@@ -164,52 +289,65 @@ class Columns(object):
         self._columns = new_columns
 
     def all(self):
-        """Iterate through all columns, regardless of visiblity (as
-        opposed to ``__iter__``.
+        """Iterate through all :class:`BoundColumn` objects, regardless of
+        visiblity or sortability.
 
-        This is used internally a lot.
         """
         self._spawn_columns()
         for column in self._columns.values():
             yield column
 
     def items(self):
+        """Return an iterator of ``(name, column)`` pairs (where *column* is a
+        :class:`BoundColumn` object).
+
+        """
         self._spawn_columns()
         for r in self._columns.items():
             yield r
 
     def names(self):
+        """Return an iterator of column names."""
         self._spawn_columns()
         for r in self._columns.keys():
             yield r
 
-    def index(self, name):
-        self._spawn_columns()
-        return self._columns.keyOrder.index(name)
-
     def sortable(self):
-        """Iterate through all sortable columns.
+        """Same as :meth:`all` but only returns sortable :class:`BoundColumn`
+        objects.
 
-        This is primarily useful in templates, where iterating over the full
-        set and checking {% if column.sortable %} can be problematic in
-        conjunction with e.g. {{ forloop.last }} (the last column might not
+        This is useful in templates, where iterating over the full
+        set and checking ``{% if column.sortable %}`` can be problematic in
+        conjunction with e.g. ``{{ forloop.last }}`` (the last column might not
         be the actual last that is rendered).
+
         """
         for column in self.all():
             if column.sortable:
                 yield column
 
-    def __iter__(self):
-        """Iterate through all *visible* bound columns.
+    def visible(self):
+        """Same as :meth:`sortable` but only returns visible
+        :class:`BoundColumn` objects.
+
+        This is geared towards table rendering.
 
-        This is primarily geared towards table rendering.
         """
         for column in self.all():
             if column.visible:
                 yield column
 
+    def __iter__(self):
+        """Convenience API with identical functionality to :meth:`visible`."""
+        return self.visible()
+
     def __contains__(self, item):
-        """Check by both column object and column name."""
+        """Check if a column is contained within a :class:`Columns` object.
+
+        *item* can either be a :class:`BoundColumn` object, or the name of a
+        column.
+
+        """
         self._spawn_columns()
         if isinstance(item, basestring):
             return item in self.names()
@@ -217,11 +355,21 @@ class Columns(object):
             return item in self.all()
 
     def __len__(self):
+        """Return how many :class:`BoundColumn` objects are contained."""
         self._spawn_columns()
         return len([1 for c in self._columns.values() if c.visible])
 
     def __getitem__(self, index):
-        """Return a column by name or index."""
+        """Retrieve a specific :class:`BoundColumn` object.
+
+        *index* can either be 0-indexed or the name of a column
+
+        .. code-block:: python
+
+            columns['speed']  # returns a bound column with name 'speed'
+            columns[0]        # returns the first column
+
+        """
         self._spawn_columns()
         if isinstance(index, int):
             return self._columns.value_for_index(index)
index 9491023152daa740a5d29c9e5457b1a57ee4311e..9544bd31f73e0675195305244dfb61fea54ceacc 100644 (file)
@@ -1,16 +1,78 @@
+# -*- coding: utf-8 -*-
+
 class BoundRow(object):
-    """Represents a single row of 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.
+
+    To access the rendered value of each cell in a row, just iterate over it:
+
+    .. code-block:: python
+
+        >>> import django_tables 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'
 
-    BoundRow provides a layer on top of the table data that exposes final
-    rendered cell values for the table. This means that formatting (via
-    Column.formatter or overridden Column.render in subclasses) applied to the
-    values from the table's data.
     """
     def __init__(self, table, data):
+        """Initialise a new :class:`BoundRow` object where:
+
+        * *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.
+
+        """
         self.table = table
         self.data = data
 
     def __iter__(self):
+        """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.
+
+        """
         for value in self.values:
             yield value
 
@@ -23,7 +85,9 @@ class BoundRow(object):
         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)
+        return bound_column.column.render(table=self.table,
+                                          bound_column=bound_column,
+                                          bound_row=self)
 
     def __contains__(self, item):
         """Check by both row object and column name."""
@@ -35,6 +99,8 @@ class BoundRow(object):
     @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]
 
 
@@ -46,29 +112,41 @@ class Rows(object):
     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 all rows."""
+        """Return an iterable for all :class:`BoundRow` objects in the table.
+
+        """
         for row in self.table.data:
             yield BoundRow(self.table, row)
 
     def page(self):
-        """Return rows on current page (if paginated)."""
+        """If the table is paginated, return an iterable of :class:`BoundRow`
+        objects that appear on the current page, otherwise return None.
+
+        """
         if not hasattr(self.table, 'page'):
             return None
         return iter(self.table.page.object_list)
 
     def __iter__(self):
-        return iter(self.all())
+        """Convience method for all()"""
+        return self.all()
 
     def __len__(self):
+        """Returns the number of rows in the table."""
         return len(self.table.data)
 
     # for compatibility with QuerySetPaginator
     count = __len__
 
     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]:
index db87cc2a65cab990b3d8771d5309f573431c0c95..df26d8f747b623807f6396561b4d229c70df4c7a 100644 (file)
@@ -1,14 +1,13 @@
 # -*- coding: utf8 -*-
 import copy
-from django.db.models.query import QuerySet
 from django.core.paginator import Paginator
 from django.utils.datastructures import SortedDict
 from django.http import Http404
 from django.template.loader import get_template
 from django.template import Context
+from django.utils.encoding import StrAndUnicode
 from .utils import rmprefix, toggleprefix, OrderByTuple, Accessor
 from .columns import Column
-from .memory import sort_table
 from .rows import Rows, BoundRow
 from .columns import Columns
 
@@ -21,6 +20,7 @@ class TableData(object):
     set and a list of dicts.
     """
     def __init__(self, data, table):
+        from django.db.models.query import QuerySet
         self._data = data if not isinstance(data, QuerySet) else None
         self._queryset = data if isinstance(data, QuerySet) else None
         self._table = table
@@ -175,7 +175,7 @@ class TableOptions(object):
         self.order_by = getattr(options, 'order_by', ())
 
 
-class Table(object):
+class Table(StrAndUnicode):
     """A collection of columns, plus their associated data rows."""
     __metaclass__ = DeclarativeColumnsMetaclass
 
index 2463f2300a5da5719dcc97156898e35a56e2c27e..9fcd66ff7f140388a25007acb3c8aeb04b38151e 100644 (file)
@@ -16,7 +16,9 @@ import sys, os
 # If extensions (or modules to document with autodoc) are in another directory,
 # add these directories to sys.path here. If the directory is relative to the
 # documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.insert(0, os.path.abspath('.'))
+sys.path.insert(0, os.path.join(os.path.abspath('.'), os.pardir))
+import django_tables as tables
+sys.path.pop(0)
 
 # -- General configuration -----------------------------------------------------
 
@@ -25,7 +27,7 @@ import sys, os
 
 # Add any Sphinx extension module names here, as strings. They can be extensions
 # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = []
+extensions = ['sphinx.ext.autodoc']
 
 # Add any paths that contain templates here, relative to this directory.
 templates_path = ['_templates']
@@ -48,9 +50,9 @@ project = u'django-tables'
 # built documents.
 #
 # The short X.Y version.
-version = '0.2'
+version = '.'.join(map(str, tables.VERSION[0:2]))
 # The full version, including alpha/beta/rc tags.
-release = '0.2-dev'
+release = tables.get_version()
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
@@ -153,7 +155,7 @@ html_static_path = ['_static']
 #html_show_sphinx = True
 
 # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = True
+html_show_copyright = False
 
 # If true, an OpenSearch description file will be output, and all pages will
 # contain a <link> tag referring to it.  The value of this option must be the
index 1e3d091fd15212a656cd38a2b2a01bd864fe84b2..40cdf4a9b69a467749a961185d69c4af6807c99f 100644 (file)
@@ -1,3 +1,5 @@
+.. default-domain:: py
+
 =====================================================
 django-tables - An app for creating HTML tables
 =====================================================
@@ -52,8 +54,6 @@ database model API:
     ...     tz = tables.Column(verbose_name='Time Zone')
     ...     visits = tables.Column()
 
-See :ref:`columns` for more information.
-
 
 Providing data
 --------------
@@ -110,7 +110,7 @@ template tag:
     {% load django_tables %}
     {% render_table table %}
 
-See :ref:`template tags` for more information.
+See :ref:`template_tags` for more information.
 
 
 Ordering
@@ -166,36 +166,60 @@ run-time information of the table into the formatter. For example it would not
 be possible to incorporate the row number into the cell's value.
 
 
-Column render method
---------------------
+:meth:`Table.render_FOO` Method
+-------------------------------
 
 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* function if it's been provided. This is evident in the order that
+  the square and angled brackets have been applied for the ``id`` column.
+* 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).
+
+  This is possible because :meth:`render_FOO` methods override the default
+  behaviour of retrieving a value from the data-source.
+
+.. code-block:: python
+
     >>> import django_tables as tables
     >>> class SimpleTable(tables.Table):
     ...     row_number = tables.Column()
-    ...     id = tables.Column(formatter=lambda x: '#%d' % x)
+    ...     id = tables.Column(formatter=lambda x: '[%s]' % x)
     ...     age = tables.Column(formatter=lambda x: '%d years old' % x)
     ...
     ...     def render_row_number(self, bound_column, bound_row):
-    ...         value =
+    ...         value = getattr(self, '_counter', 0)
+    ...         self._counter = value + 1
+    ...         return 'Row %d' % value
     ...
     ...     def render_id(self, bound_column, bound_row):
-    ...         value = self.column.
+    ...         value = bound_column.column.render(table=self,
+    ...                                            bound_column=bound_column,
+    ...                                            bound_row=bound_row)
+    ...         return '<%s>' % value
     ...
     >>> table = SimpleTable([{'age': 31, 'id': 10}, {'age': 34, 'id': 11}])
-    >>> row = table.rows[0]
-    >>> for cell in row:
+    >>> for cell in table.rows[0]:
     ...     print cell
     ...
-    #10
+    Row 0
+    <[10]>
     31 years old
 
-If you want full control over the way the table is rendered, create
-and render the template yourself:
+
+Custom Template
+---------------
+
+And of course if you want full control over the way the table is rendered,
+ignore the built-in generation tools, and instead pass an instance of your
+:class:`Table` subclass into your own template, and render it yourself:
 
 .. code-block:: django
 
@@ -220,161 +244,69 @@ and render the template yourself:
     </table>
 
 
+Subclassing :class:`Column`
+---------------------------
 
-Columns
-=======
-
-The :class:`Columns` class provides an container for :class:`BoundColumn`
-instances. The simplest way to access the contained columns is to iterate over
-the instance:
-
-Each :class:`Table` instance has an instance as its :attr:`~Table.columns`
-property. Iterating over the instance yields only the visible columns. To
-access all columns (including those that are hidden), use the
-:func:`~Columns.all` method.
-
-Additionally, the :func:`~Columns.sortable` method provides access to all the
-sortable columns.
-
-
-Column options
---------------
-
-Each column takes a certain set of column-specific arguments (documented in the
-:ref:`column reference <columns.types>`).
-
-There's also a set of common arguments available to all column types. All are
-optional. Here's a summary of them.
-
-    :attr:`~Column.verbose_name`
-        A pretty human readable version of the column name. Typically this is
-        used in the header cells in the HTML output.
-
-    :attr:`~Column.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')
-
-    :attr:`~Column.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.
+If you want to have a column behave the same way in many tables, it's best to
+create a subclass of :class:`Column` and use that when defining the table.
 
-        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.
-
-    :attr:`~Column.visible`
-        If :const:`False`, this column will not be in the HTML output.
-
-        When a field is not visible, it is removed from the table's
-        :attr:`~Column.columns` iterable.
-
-    :attr:`~Column.sortable`
-        If :const:`False`, this column will not be allowed to be used in
-        ordering the table.
-
-    :attr:`~Column.formatter`
-        A callable object that is used as a final step in formatting the value
-        for a cell. The callable will be passed the string that would have
-        otherwise been displayed in the cell.
-
-
-Rows
-====
-
-Row objects
------------
-
-A row object represents a single row in a table.
-
-To access the rendered value of each cell in a row, you can iterate over the
-row:
+To change the way cells are rendered, simply override the
+:meth:`~Column.render` method.
 
 .. code-block:: python
 
     >>> import django_tables as tables
-    >>> class SimpleTable(tables.Table):
-    ...     a = tables.Column()
-    ...     b = tables.CheckBoxColumn(attrs={'name': 'my_chkbox'})
+    >>>
+    >>> class AngryColumn(tables.Column):
+    ...     def render(self, *args, **kwargs):
+    ...         raw = super(AngryColumn, self).render(*args, **kwargs)
+    ...         return raw.upper()
     ...
-    >>> 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
+    >>> class Example(tables.Table):
+    ...     normal = tables.Column()
+    ...     angry = AngryColumn()
+    ...
+    >>> data = [{
+    ...     'normal': 'May I have some food?',
+    ...     'angry': 'Give me the food now!',
+    ... }, {
+    ...     'normal': 'Hello!',
+    ...     'angry': 'What are you looking at?',
+    ... }]
     ...
-    1
-    <input type="checkbox" name="my_chkbox" value="2" />
+    >>> table = Example(data)
+    >>> table.as_html()
+    u'<table><thead><tr><th>Normal</th><th>Angry</th></tr></thead><tbody><tr><td>May I have some food?</td><td>GIVE ME THE FOOD NOW!</td></tr><tr><td>Hello!</td><td>WHAT ARE YOU LOOKING AT?</td></tr></tbody></table>\n'
 
-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.
+Which, when displayed in a browser, would look something like this:
 
-.. code-block:: python
++-----------------------+--------------------------+
+| Normal                | Angry                    |
++=======================+==========================+
+| May I have some food? | GIVE ME THE FOOD NOW!    |
++-----------------------+--------------------------+
+| Hello!                | WHAT ARE YOU LOOKING AT? |
++-----------------------+--------------------------+
 
-    >>> 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).
+If you plan on returning HTML from a :meth:`~Column.render` method, you must
+remember to mark it as safe (otherwise it will be escaped when the table is
+rendered). This can be achieved by using the :func:`mark_safe` function.
 
 .. code-block:: python
 
-    >>> row['a']
-    1
-    >>> row['b']
-    u'<input type="checkbox" name="my_chkbox" value="2" />'
-    >>> row['c']
+    >>> from django.utils.safestring import mark_safe
+    >>>
+    >>> class ImageColumn(tables.Column):
+    ...     def render(self, **kwargs):
+    ...         raw = super(AngryColumn, self).render(**kwargs)
+    ...         return mark_safe('<img src="/media/img/%s.jpg" />' % raw)
     ...
-    KeyError: 'c'
 
 
 
+.. _template_tags:
+
 Template tags
 =============
 
@@ -445,44 +377,42 @@ which can be iterated over:
     </table>
 
 
-Custom render methods
----------------------
+API Reference
+=============
 
-Often, displaying a raw value of a table cell is not good enough. For
-example, if your table has a ``rating`` column, you might want to show
-an image showing the given number of **stars**, rather than the plain
-numeric value.
+:class:`Column` Objects:
+------------------------
 
-While you can always write your templates so that the column in question
-is treated separately, either by conditionally checking for a column name,
-or by explicitely rendering each column manually (as opposed to simply
-looping over the ``rows`` and ``columns`` attributes), this is often
-tedious to do.
 
-Instead, you can opt to move certain formatting responsibilites into
-your Python code:
+.. autoclass:: django_tables.columns.Column
+    :members: __init__, default, render
 
-.. code-block:: python
 
-    class BookTable(tables.ModelTable):
-        name = tables.Column()
-        rating = tables.Column(accessor='rating_int')
-
-        def render_rating(self, bound_table):
-            if bound_table.rating_count == 0:
-                return '<img src="no-rating.png"/>'
-            else:
-                return '<img src="rating-%s.png"/>' % bound_table.rating_int
-
-When accessing ``table.rows[i].rating``, the ``render_rating`` method
-will be called. Note the following:
-
-- What is passed is underlying raw data object, in this case, the model
-  instance. This gives you access to data values that may not have been defined
-  as a column.
-- For the method name, the public name of the column must be used, not the
-  internal field name. That is, it's ``render_rating``, not
-  ``render_rating_int``.
-- The method is called whenever the cell value is retrieved by you, whether from
-  Python code or within templates. However, operations by ``django-tables``,
-  like sorting, always work with the raw data.
+:class:`Columns` Objects
+------------------------
+
+.. autoclass:: django_tables.columns.Columns
+    :members: __init__, all, items, names, sortable, visible, __iter__,
+              __contains__, __len__, __getitem__
+
+
+:class:`BoundColumn` Objects
+----------------------------
+
+.. autoclass:: django_tables.columns.BoundColumn
+    :members: __init__, table, column, name, accessor, default, formatter,
+              sortable, verbose_name, visible
+
+
+:class:`Rows` Objects
+---------------------
+
+.. autoclass:: django_tables.rows.Rows
+    :members: __init__, all, page, __iter__, __len__, count, __getitem__
+
+
+:class:`BoundRow` Objects
+-------------------------
+
+.. autoclass:: django_tables.rows.BoundRow
+    :members: __init__, values, __getitem__, __contains__, __iter__
index 2dfeb428f02e0e1673aa4618f086c1b4f8670e4c..ba5d8f7dc597ba64a32eb1d0d75e4d6279cef19c 100755 (executable)
--- a/setup.py
+++ b/setup.py
@@ -1,5 +1,8 @@
 # -*- coding: utf8 -*-
-from distutils.core import setup
+try:
+    from setuptools import setup
+except ImportError:
+    from distutils.core import setup
 from distutils.command.install_data import install_data
 from distutils.command.install import INSTALL_SCHEMES
 import os
@@ -86,6 +89,6 @@ setup(
     packages = packages,
     data_files = data_files,
     cmdclass = cmdclasses,
-    requires = ['django(>=1.1)'],
-    install_requires = ['django>=1.1']
+    requires = ['Django(>=1.1)'],
+    install_requires = ['Django>=1.1']
 )