updated docs
authorBradley Ayers <bradley.ayers@enigmainteractive.com>
Wed, 23 Feb 2011 07:33:22 +0000 (17:33 +1000)
committerBradley Ayers <bradley.ayers@enigmainteractive.com>
Wed, 23 Feb 2011 07:33:22 +0000 (17:33 +1000)
django_tables/__init__.py
django_tables/columns.py
django_tables/rows.py
django_tables/tables.py
docs/conf.py
docs/index.rst

index e44cdfa06f0542a0e5a7d5aaf8c84d5e7a0bb992..7e93bcca71e6a61d64c97e76989b8629ce90e7ff 100644 (file)
@@ -18,20 +18,26 @@ def get_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.
 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
+    import django
 except ImportError:
-    # allow get_version() to remain available
     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..e721b0b4ca3438775127cb838bf32118d3f1000c 100644 (file)
@@ -86,41 +86,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 +161,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 +177,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 +211,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 +277,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..1883baa2be4a4025cedc41837349a4f1daa233f5 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
 
@@ -35,6 +97,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 +110,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 686bd7bf378a5c2d13cf779c133be9fc836bd0c5..aabc1a71c2f60a10b018535b05215c8692f947b9 100644 (file)
@@ -1,6 +1,5 @@
 # -*- 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
@@ -20,6 +19,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
index 2463f2300a5da5719dcc97156898e35a56e2c27e..eed41bd73b1eda58cb9cbcfa73b5bfcb68e46375 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.
index 1e3d091fd15212a656cd38a2b2a01bd864fe84b2..735f57f977a16b837ac9ec767597feadfd309bba 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
@@ -187,8 +187,7 @@ DRY).
     ...         value = self.column.
     ...
     >>> 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
@@ -221,27 +220,26 @@ and render the template yourself:
 
 
 
-Columns
-=======
+:class:`Columns` Objects
+========================
 
-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:
+.. autoclass:: django_tables.columns.Columns
+    :members: __init__, all, items, names, sortable, visible, __iter__,
+              __contains__, __len__, __getitem__
 
-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.
+:class:`BoundColumn` Objects
+============================
+
+.. autoclass:: django_tables.columns.BoundColumn
+    :members: __init__, table, column, name, accessor, default, formatter,
+              sortable, verbose_name, visible
 
 
 Column options
 --------------
 
-Each column takes a certain set of column-specific arguments (documented in the
-:ref:`column reference <columns.types>`).
+Each column takes a certain set of column-specific arguments.
 
 There's also a set of common arguments available to all column types. All are
 optional. Here's a summary of them.
@@ -322,58 +320,21 @@ optional. Here's a summary of them.
 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:
-
-.. 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
+:class:`Rows` Objects
+=====================
 
-    >>> row[0]
-    1
-    >>> row[1]
-    u'<input type="checkbox" name="my_chkbox" value="2" />'
-    >>> row[2]
-    ...
-    IndexError: list index out of range
+.. autoclass:: django_tables.rows.Rows
+    :members: __init__, all, page, __iter__, __len__, count, __getitem__
 
-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
+:class:`BoundRow` Objects
+=========================
 
-    >>> row['a']
-    1
-    >>> row['b']
-    u'<input type="checkbox" name="my_chkbox" value="2" />'
-    >>> row['c']
-    ...
-    KeyError: 'c'
+.. autoclass:: django_tables.rows.BoundRow
+    :members: __init__, values, __getitem__, __contains__, __iter__
 
 
+.. _template_tags:
 
 Template tags
 =============
@@ -443,46 +404,3 @@ which can be iterated over:
         {% endfor %}
         </tbody>
     </table>
-
-
-Custom render methods
----------------------
-
-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.
-
-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:
-
-.. 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.