# 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 *
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:
@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
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, )
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()
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)
+# -*- 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
@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]
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]:
# -*- 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
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
# 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 -----------------------------------------------------
# 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']
# 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.
+.. default-domain:: py
+
=====================================================
django-tables - An app for creating HTML tables
=====================================================
... tz = tables.Column(verbose_name='Time Zone')
... visits = tables.Column()
-See :ref:`columns` for more information.
-
Providing data
--------------
{% load django_tables %}
{% render_table table %}
-See :ref:`template tags` for more information.
+See :ref:`template_tags` for more information.
Ordering
... 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
-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.
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
=============
{% 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.