From efe955a1a86977cf9f392ce60ad2bd5ad43f4784 Mon Sep 17 00:00:00 2001 From: Bradley Ayers Date: Wed, 23 Feb 2011 17:33:22 +1000 Subject: [PATCH] updated docs --- django_tables/__init__.py | 30 +++++---- django_tables/columns.py | 126 ++++++++++++++++++++++++++++-------- django_tables/rows.py | 92 +++++++++++++++++++++++--- django_tables/tables.py | 2 +- docs/conf.py | 10 +-- docs/index.rst | 132 ++++++++------------------------------ 6 files changed, 232 insertions(+), 160 deletions(-) diff --git a/django_tables/__init__.py b/django_tables/__init__.py index e44cdfa..7e93bcc 100644 --- a/django_tables/__init__.py +++ b/django_tables/__init__.py @@ -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 * diff --git a/django_tables/columns.py b/django_tables/columns.py index 532f9b0..e721b0b 100644 --- a/django_tables/columns.py +++ b/django_tables/columns.py @@ -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) diff --git a/django_tables/rows.py b/django_tables/rows.py index 9491023..1883baa 100644 --- a/django_tables/rows.py +++ b/django_tables/rows.py @@ -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 + + + 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'' + >>> 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'' + >>> 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]: diff --git a/django_tables/tables.py b/django_tables/tables.py index 686bd7b..aabc1a7 100644 --- a/django_tables/tables.py +++ b/django_tables/tables.py @@ -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 diff --git a/docs/conf.py b/docs/conf.py index 2463f23..eed41bd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -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. diff --git a/docs/index.rst b/docs/index.rst index 1e3d091..735f57f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -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 `). +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 - - -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'' - >>> 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'' - >>> 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 %} - - -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 '' - else: - return '' % 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. -- 2.26.2