Converted the readme file to a Sphinx documentation; some minor refactoring along...
authorMichael Elsdoerfer <michael@elsdoerfer.com>
Fri, 26 Mar 2010 11:55:07 +0000 (12:55 +0100)
committerMichael Elsdoerfer <michael@elsdoerfer.com>
Fri, 26 Mar 2010 11:55:07 +0000 (12:55 +0100)
17 files changed:
.gitignore
README
docs/Makefile [new file with mode: 0644]
docs/columns.rst [new file with mode: 0644]
docs/conf.py [new file with mode: 0644]
docs/features/index.rst [new file with mode: 0644]
docs/features/ordering.rst [new file with mode: 0644]
docs/features/pagination.rst [new file with mode: 0644]
docs/index.rst [new file with mode: 0644]
docs/installation.rst [new file with mode: 0644]
docs/make.bat [new file with mode: 0644]
docs/templates.rst [new file with mode: 0644]
docs/types/index.rst [new file with mode: 0644]
docs/types/memory.rst [new file with mode: 0644]
docs/types/models.rst [new file with mode: 0644]
docs/types/sql.rst [new file with mode: 0644]
requirements-dev.pip

index 4a5d74c65b8e5e996e0202f7bbb4319c5afa193f..74e43af5911d4c10d066aabd85633c519a15171d 100644 (file)
@@ -2,9 +2,11 @@
 
 /MANIFEST
 /dist
+/docs/_build/*
 
 /BRANCH_TODO
 
+# Project files
 /.project
 /.pydevproject
 /*.wpr
diff --git a/README b/README
index a3ca337d1566a8dc785d202b2643021a96cc959f..2bc8b8161c8c6253d70499aba7eb0766991d8197 100644 (file)
--- a/README
+++ b/README
@@ -1,520 +1,4 @@
-django-tables\r
-=============\r
-\r
-A Django QuerySet renderer.\r
-\r
-Installation\r
-------------\r
-\r
-Adding django-tables to your INSTALLED_APPS settings is optional, it'll get\r
-you the ability to load some template utilities via {% load tables %}, but\r
-apart from that, ``import django_tables as tables`` should get you going.\r
-\r
-Running the test suite\r
-----------------------\r
-\r
-The test suite uses nose:\r
-    http://somethingaboutorange.com/mrl/projects/nose/\r
-\r
-Working with Tables\r
--------------------\r
-\r
-A table class looks very much like a form:\r
-\r
-    import django_tables as tables\r
-    class CountryTable(tables.MemoryTable):\r
-        name = tables.Column(verbose_name="Country Name")\r
-        population = tables.Column(sortable=False, visible=False)\r
-        time_zone = tables.Column(name="tz", default="UTC+1")\r
-\r
-Instead of fields, you declare a column for every piece of data you want to\r
-expose to the user.\r
-\r
-To use the table, create an instance:\r
-\r
-    countries = CountryTable([{'name': 'Germany', population: 80},\r
-                              {'name': 'France', population: 64}])\r
-\r
-Decide how the table should be sorted:\r
-\r
-    countries.order_by = ('name',)\r
-    assert [row.name for row in countries.row] == ['France', 'Germany']\r
-\r
-    countries.order_by = ('-population',)\r
-    assert [row.name for row in countries.row] == ['Germany', 'France']\r
-\r
-If you pass the table object along into a template, you can do:\r
-\r
-    {% for column in countries.columns %}\r
-        {{column}}\r
-    {% endfor %}\r
-\r
-Which will give you:\r
-\r
-    Country Name\r
-    Timezone\r
-\r
-Note that ``population`` is skipped (as it has ``visible=False``), that the\r
-declared verbose name for the ``name`` column is used, and that ``time_zone``\r
-is converted into a more beautiful string for output automatically.\r
-\r
-There are few requirements for the source data of a table. It should be an\r
-iterable with dict-like objects. Values found in the source data that are\r
-not associated with a column are ignored, missing values are replaced by\r
-the column default or None.\r
-\r
-Common Workflow\r
-~~~~~~~~~~~~~~~\r
-\r
-Usually, you are going to use a table like this. Assuming ``CountryTable``\r
-is defined as above, your view will create an instance and pass it to the\r
-template:\r
-\r
-    def list_countries(request):\r
-        data = ...\r
-        countries = CountryTable(data, order_by=request.GET.get('sort'))\r
-        return render_to_response('list.html', {'table': countries})\r
-\r
-Note that we are giving the incoming "sort" query string value directly to\r
-the table, asking for a sort. All invalid column names will (by default) be\r
-ignored. In this example, only "name" and "tz" are allowed, since:\r
-\r
-    * "population" has sortable=False\r
-    * "time_zone" has it's name overwritten with "tz".\r
-\r
-Then, in the "list.html" template, write:\r
-\r
-    <table>\r
-    <tr>\r
-        {% for column in table.columns %}\r
-        <th><a href="?sort={{ column.name }}">{{ column }}</a></th>\r
-        {% endfor %}\r
-    </tr>\r
-    {% for row in table.rows %}\r
-        <tr>\r
-        {% for value in row %}\r
-            <td>{{ value }}</td>\r
-        {% endfor %}\r
-        </tr>\r
-    {% endfor %}\r
-    </table>\r
-\r
-This will output the data as an HTML table. Note how the table is now fully\r
-sortable, since our link passes along the column name via the querystring,\r
-which in turn will be used by the server for ordering. ``order_by`` accepts\r
-comma-separated strings as input, and "{{ table.order_by }}" will be rendered\r
-as a such a string.\r
-\r
-Instead of the iterator, you can use your knowledge of the table structure to\r
-access columns directly:\r
-\r
-    {% if table.columns.tz.visible %}\r
-        {{ table.columns.tz }}\r
-    {% endfor %}\r
-\r
-\r
-Dynamic Data\r
-~~~~~~~~~~~~\r
-\r
-If any value in the source data is a callable, it will be passed it's own\r
-row instance and is expected to return the actual value for this particular\r
-table cell.\r
-\r
-Similarily, the colunn default value may also be callable that will takes\r
-the row instance as an argument (representing the row that the default is\r
-needed for).\r
-\r
-\r
-Table Options\r
--------------\r
-\r
-Table-specific options are implemented using the same inner ``Meta`` class\r
-concept as known from forms and models in Django:\r
-\r
-    class MyTable(tables.MemoryTable):\r
-        class Meta:\r
-            sortable = True\r
-\r
-Currently, for non-model tables, the only supported option is ``sortable``.\r
-Per default, all columns are sortable, unless a column specifies otherwise.\r
-This meta option allows you to overwrite the global default for the table.\r
-\r
-\r
-ModelTables\r
------------\r
-\r
-Like forms, tables can also be used with models:\r
-\r
-    class CountryTable(tables.ModelTable):\r
-        id = tables.Column(sortable=False, visible=False)\r
-        class Meta:\r
-            model = Country\r
-            exclude = ['clicks']\r
-\r
-The resulting table will have one column for each model field, with the\r
-exception of "clicks", which is excluded. The column for "id" is overwritten\r
-to both hide it and deny it sort capability.\r
-\r
-When instantiating a ModelTable, you usually pass it a queryset to provide\r
-the table data:\r
-\r
-    qs = Country.objects.filter(continent="europe")\r
-    countries = CountryTable(qs)\r
-\r
-However, you can also just do:\r
-\r
-    countries = CountryTable()\r
-\r
-and all rows exposed by the default manager of the model the table is based\r
-on will be used.\r
-\r
-If you are using model inheritance, then the following also works:\r
-\r
-    countries = CountryTable(CountrySubclass)\r
-\r
-Note that while you can pass any model, it really only makes sense if the\r
-model also provides fields for the columns you have defined.\r
-\r
-If you just want to use ModelTables, but without auto-generated columns,\r
-you do not have to list all model fields in the ``exclude`` Meta option.\r
-Instead, simply don't specify a model.\r
-\r
-\r
-Custom Columns\r
-~~~~~~~~~~~~~~\r
-\r
-You an add custom columns to your ModelTable that are not based on actual\r
-model fields:\r
-\r
-    class CountryTable(tables.ModelTable):\r
-        custom = tables.Column(default="foo")\r
-        class Meta:\r
-            model = Country\r
-\r
-Just make sure your model objects do provide an attribute with that name.\r
-Functions are also supported, so ``Country.custom`` could be a callable.\r
-\r
-\r
-Spanning relationships\r
-~~~~~~~~~~~~~~~~~~~~~~\r
-\r
-Let's assume you have a ``Country`` model, with a foreignkey ``capital``\r
-pointing to the ``City`` model. While displaying a list of countries,\r
-you might want want to link to the capital's geographic location, which is\r
-stored in ``City.geo`` as a ``(lat, long)`` tuple, on Google Maps.\r
-\r
-ModelTables support relationship spanning syntax of Django's database api:\r
-\r
-    class CountryTable(tables.ModelTable):\r
-        city__geo = tables.Column(name="geo")\r
-\r
-This will add a column named "geo", based on the field by the same name\r
-from the "city" relationship. Note that the name used to define the column\r
-is what will be used to access the data, while the name-overwrite passed to\r
-the column constructor just defines a prettier name for us to work with.\r
-This is to be consistent with auto-generated columns based on model fields,\r
-where the field/column name naturally equals the source name.\r
-\r
-However, to make table defintions more visually appealing and easier to\r
-read, an alternative syntax is supported: setting the column ``data``\r
-property to the appropriate string.\r
-\r
-    class CountryTable(tables.ModelTable):\r
-        geo = tables.Column(data='city__geo')\r
-\r
-Note that you don't need to define a relationship's fields as separate\r
-columns if you already have a column for the relationship itself, i.e.:\r
-\r
-    class CountryTable(tables.ModelTable):\r
-        city = tables.Column()\r
-\r
-    for country in countries.rows:\r
-        print country.city.id\r
-        print country.city.geo\r
-        print country.city.founder.name\r
-\r
-\r
-ModelTable Specialities\r
-~~~~~~~~~~~~~~~~~~~~~~~\r
-\r
-ModelTables currently have some restrictions with respect to ordering:\r
-\r
-    * Custom columns not based on a model field do not support ordering,\r
-      regardless of the ``sortable`` property (it is ignored).\r
-\r
-    * A ModelTable column's ``default`` or ``data`` value does not affect\r
-      ordering. This differs from the non-model table behaviour.\r
-\r
-If a column is mapped to a method on the model, that method will be called\r
-without arguments. This behavior differs from non-model tables, where a\r
-row object will be passed.\r
-\r
-If you are using callables (e.g. for the ``default`` or ``data`` column\r
-options), they will generally be run when a row is accessed, and\r
-possible repeatetly when accessed more than once. This behavior differs from\r
-non-model tables, where they would be called once, when the table is\r
-generated.\r
-\r
-Columns\r
--------\r
-\r
-Columns are what defines a table. Therefore, the way you configure your\r
-columns determines to a large extend how your table operates.\r
-\r
-``django_tables.columns`` currently defines three classes, ``Column``,\r
-``TextColumn`` and ``NumberColumn``. However, the two subclasses currently\r
-don't do anything special at all, so you can simply use the base class.\r
-While this will likely change in the future (e.g. when grouping is added),\r
-the base column class will continue to work by itself.\r
-\r
-There are no required arguments. The following is fine:\r
-\r
-    class MyTable(tables.MemoryTable):\r
-        c = tables.Column()\r
-\r
-It will result in a column named "c" in the table. You can specify the\r
-``name`` to override this:\r
-\r
-    c = tables.Column(name="count")\r
-\r
-The column is now called and accessed via "count", although the table will\r
-still use "c" to read it's values from the source. You can however modify\r
-that as well, by specifying ``data``:\r
-\r
-    c = tables.Column(name="count", data="count")\r
-\r
-For most practicual purposes, "c" is now meaningless. While in most cases\r
-you will just define your column using the name you want it to have, the\r
-above is useful when working with columns automatically generated from\r
-models:\r
-\r
-    class BookTable(tables.ModelTable):\r
-        book_name = tables.Column(name="name")\r
-        author = tables.Column(data="info__author__name")\r
-        class Meta:\r
-            model = Book\r
-\r
-The overwritten ``book_name`` field/column will now be exposed as the\r
-cleaner "name", and the new "author" column retrieves it's values from\r
-``Book.info.author.name``.\r
-\r
-Note: ``data`` may also be a callable which will be passed a row object.\r
-\r
-Apart from their internal name, you can define a string that will be used\r
-when for display via ``verbose_name``:\r
-\r
-    pubdate = tables.Column(verbose_name="Published")\r
-\r
-The verbose name will be used, for example, if you put in a template:\r
-\r
-    {{ column }}\r
-\r
-If you don't want a column to be sortable by the user:\r
-\r
-    pubdate = tables.Column(sortable=False)\r
-\r
-Sorting is also affected by ``direction``, which can be used to change the\r
-*default* sort direction to descending. Note that this option only indirectly\r
-translates to the actual direction. Normal und reverse order, the terms\r
-django-tables exposes, now simply mean different things.\r
-\r
-    pubdate = tables.Column(direction='desc')\r
-\r
-If you don't want to expose a column (but still require it to exist, for\r
-example because it should be sortable nonetheless):\r
-\r
-    pubdate = tables.Column(visible=False)\r
-\r
-The column and it's values will now be skipped when iterating through the\r
-table, although it can still be accessed manually.\r
-\r
-Finally, you can specify default values for your columns:\r
-\r
-    health_points = tables.Column(default=100)\r
-\r
-Note that how the default is used and when it is applied differs between\r
-static and ModelTables.\r
-\r
-\r
-The table.columns container\r
----------------------------\r
-\r
-While you can iterate through ``columns`` and get all the currently visible\r
-columns, it further provides features that go beyond a simple iterator.\r
-\r
-You can access all columns, regardless of their visibility, through\r
-``columns.all``.\r
-\r
-``columns.sortable`` is a handy shortcut that exposes all columns which's\r
-``sortable`` attribute is True. This can be very useful in templates, when\r
-doing {% if column.sortable %} can conflict with {{ forloop.last }}.\r
-\r
-\r
-Tables and Pagination\r
----------------------\r
-\r
-If your table has a large number of rows, you probably want to paginate\r
-the output. There are two distinct approaches.\r
-\r
-First, you can just paginate over ``rows`` as you would do with any other\r
-data:\r
-\r
-    table = MyTable(queryset)\r
-    paginator = Paginator(table.rows, 10)\r
-    page = paginator.page(1)\r
-\r
-You're not necessarily restricted to Django's own paginator (or subclasses) -\r
-any paginator should work with this approach, so long it only requires\r
-``rows`` to implement ``len()``, slicing, and, in the case of ModelTables, a\r
-``count()`` method. The latter means that the ``QuerySetPaginator`` also\r
-works as expected.\r
-\r
-Alternatively, you may use the ``paginate`` feature:\r
-\r
-    table = MyTable(queryset)\r
-    table.paginate(Paginator, 10, page=1, orphans=2)\r
-    for row in table.rows.page():\r
-        pass\r
-    table.paginator                # new attributes\r
-    table.page\r
-\r
-The table will automatically create an instance of ``QuerySetPaginator``,\r
-passing it's own data as the first argument and additionally any arguments\r
-you have specified, except for ``page``. You may use any paginator, as long\r
-as it follows the Django protocol:\r
-\r
-    * Take data as first argument.\r
-    * Support a page() method returning an object with an ``object_list``\r
-      attribute, exposing the paginated data.\r
-\r
-Note that due to the abstraction layer that django-tables represents, it is\r
-not necessary to use Django's QuerySetPaginator with ModelTables. Since the\r
-table knows that it holds a queryset, it will automatically choose to use\r
-count() to determine the data length (which is exactly what\r
-QuerySetPaginator would do).\r
-\r
-Ordering\r
---------\r
-\r
-The syntax is similar to that of the Django database API. Order may be\r
-specified a list (or tuple) of column names. If prefixed with a hyphen, the\r
-ordering for that particular column will be in reverse order.\r
-\r
-Random ordering is currently not supported.\r
-\r
-Interacting with order\r
-~~~~~~~~~~~~~~~~~~~~~~\r
-\r
-Letting the user change the order of a table is a common scenario. With\r
-respect to Django, this means adding links to your table output that will\r
-send off the appropriate arguments to the server. django-tables attempts\r
-to help with you that.\r
-\r
-A bound column, that is a colum accessed through a table instance, provides\r
-the following attributes:\r
-\r
-    - ``name_reversed`` will simply return the column name prefixed with a\r
-      hyphen; this is useful in templates, where string concatenation can\r
-      at times be difficult.\r
-\r
-    - ``name_toggled`` checks the tables current order, and will then\r
-    return the column either prefixed with an hyphen (for reverse ordering)\r
-    or without, giving you the exact opposite order. If the column is\r
-    currently not ordered, it will start off in non-reversed order.\r
-\r
-It is easy to be confused about the difference between the ``reverse`` and\r
-``toggle`` terminology. django-tables tries to put normal/reverse-order\r
-abstraction on top of "ascending/descending", where as normal order could\r
-potentially mean either ascending or descending, depending on the column.\r
-\r
-Commonly, you see tables that indicate what columns they are currently\r
-ordered by using little arrows. To implement this:\r
-\r
-    - ``is_ordered``: Returns True if the column is in the current\r
-    ``order_by``, regardless of the polarity.\r
-\r
-    - ``is_ordered_reverse``, ``is_ordered_straight``: Returns True if the\r
-    column is ordered in reverse or non-reverse, respectively, otherwise\r
-    False.\r
-\r
-The above is usually enough for most simple cases, where tables are only\r
-ordered by a single column. For scenarios in which multi-column order is\r
-used, additional attributes are available:\r
-\r
-    - ``order_by``: Return the current order, but with the current column\r
-    set to normal ordering. If the current column is not already part of\r
-    the order, it is appended. Any existing columns in the order are\r
-    maintained as-is.\r
-\r
-    - ``order_by_reversed``, ``order_by_toggled``: Similarly, return the\r
-    table's current ``order_by`` with the column set to reversed or toggled,\r
-    respectively. Again, it is appended if not already ordered.\r
-\r
-Additionally, ``table.order_by.toggle()`` may also be useful in some cases:\r
-It will toggle all order columns and should thus give you the exact\r
-opposite order.\r
-\r
-The following is a simple example of single-column ordering. It shows a list\r
-of sortable columns, each clickable, and an up/down arrow next to the one\r
-that is currently used to sort the table.\r
-\r
-    Sort by:\r
-    {% for column in table.columns %}\r
-        {% if column.sortable %}\r
-            <a href="?sort={{ column.name_toggled }}">{{ column }}</a>\r
-            {% if column.is_ordered_straight %}<img src="down.png" />{% endif %}\r
-            {% if column.is_ordered_reverse %}<img src="up.png" />{% endif %}\r
-        {% endif %}\r
-    {% endfor %}\r
-\r
-\r
-Error handling\r
---------------\r
-\r
-Passing incoming query string values from the request directly to the\r
-table constructor is a common thing to do. However, such data can easily\r
-be invalid, be it that a user manually modified it, or someone put up a\r
-broken link. In those cases, you usually would not want to raise an\r
-exception (nor be notified by Django's error notification mechanism) -\r
-there is nothing you could do anyway.\r
-\r
-Because of this, such errors will by default be silently ignored. For\r
-example, if one out of three columns in an "order_by" is invalid, the other\r
-two will still be applied:\r
-\r
-    table.order_by = ('name', 'totallynotacolumn', '-date)\r
-    assert table.order_by = ('name', '-date)\r
-\r
-This ensures that the following table will be created regardless of the\r
-string in "sort.\r
-\r
-    table = MyTable(data, order_by=request.GET.get('sort'))\r
-\r
-However, if you want, you can disable this behaviour and have an exception\r
-raised instead, using:\r
-\r
-    import django_tables\r
-    django_tables.options.IGNORE_INVALID_OPTIONS = False\r
-\r
-\r
-Template Utilities\r
-------------------\r
-\r
-If you want the give your users the ability to interact with your table (e.g.\r
-change the ordering), you will need to create urls with the appropriate\r
-queries. To simplify that process, django-tables comes with a helpful\r
-templatetag:\r
-\r
-    {% set_url_param sort="name" %}       # ?sort=name\r
-    {% set_url_param sort="" %}           # delete "sort" param\r
-\r
-The template library can be found in 'django_modules.app.templates.tables'.\r
-If you add ''django_modules.app' to your INSTALLED_APPS setting, you will\r
-be able to do:\r
-\r
-    {% load tables %}\r
-\r
-Note: The tag requires the current request to be available as ``request``\r
-in the context (usually, this means activating the Django request context\r
-processor).\r
+django-tables - a Django QuerySet renderer.\r
 \r
+Documentation:\r
+    http://elsdoerfer.name/docs/django-tables/\r
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644 (file)
index 0000000..05ce818
--- /dev/null
@@ -0,0 +1,89 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = _build
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest
+
+help:
+       @echo "Please use \`make <target>' where <target> is one of"
+       @echo "  html      to make standalone HTML files"
+       @echo "  dirhtml   to make HTML files named index.html in directories"
+       @echo "  pickle    to make pickle files"
+       @echo "  json      to make JSON files"
+       @echo "  htmlhelp  to make HTML files and a HTML help project"
+       @echo "  qthelp    to make HTML files and a qthelp project"
+       @echo "  latex     to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+       @echo "  changes   to make an overview of all changed/added/deprecated items"
+       @echo "  linkcheck to check all external links for integrity"
+       @echo "  doctest   to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+       -rm -rf $(BUILDDIR)/*
+
+html:
+       $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+       @echo
+       @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+       $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+       @echo
+       @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+pickle:
+       $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+       @echo
+       @echo "Build finished; now you can process the pickle files."
+
+json:
+       $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+       @echo
+       @echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+       $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+       @echo
+       @echo "Build finished; now you can run HTML Help Workshop with the" \
+             ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+       $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+       @echo
+       @echo "Build finished; now you can run "qcollectiongenerator" with the" \
+             ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+       @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-tables.qhcp"
+       @echo "To view the help file:"
+       @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-tables.qhc"
+
+latex:
+       $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+       @echo
+       @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+       @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
+             "run these through (pdf)latex."
+
+changes:
+       $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+       @echo
+       @echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+       $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+       @echo
+       @echo "Link check complete; look for any errors in the above output " \
+             "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+       $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+       @echo "Testing of doctests in the sources finished, look at the " \
+             "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/docs/columns.rst b/docs/columns.rst
new file mode 100644 (file)
index 0000000..d064918
--- /dev/null
@@ -0,0 +1,100 @@
+=================
+All about Columns
+=================
+
+Columns are what defines a table. Therefore, the way you configure your
+columns determines to a large extend how your table operates.
+
+``django_tables.columns`` currently defines three classes, ``Column``,
+``TextColumn`` and ``NumberColumn``. However, the two subclasses currently
+don't do anything special at all, so you can simply use the base class.
+While this will likely change in the future (e.g. when grouping is added),
+the base column class will continue to work by itself.
+
+There are no required arguments. The following is fine:
+
+.. code-block:: python
+
+    class MyTable(tables.MemoryTable):
+        c = tables.Column()
+
+It will result in a column named ``c`` in the table. You can specify the
+``name`` to override this:
+
+.. code-block:: python
+
+    c = tables.Column(name="count")
+
+The column is now called and accessed via "count", although the table will
+still use ``c`` to read it's values from the source. You can however modify
+that as well, by specifying ``data``:
+
+.. code-block:: python
+
+    c = tables.Column(name="count", data="count")
+
+For practicual purposes, ``c`` is now meaningless. While in most cases
+you will just define your column using the name you want it to have, the
+above is useful when working with columns automatically generated from
+models:
+
+.. code-block:: python
+
+    class BookTable(tables.ModelTable):
+        book_name = tables.Column(name="name")
+        author = tables.Column(data="info__author__name")
+        class Meta:
+            model = Book
+
+The overwritten ``book_name`` field/column will now be exposed as the
+cleaner ``name``, and the new ``author`` column retrieves it's values from
+``Book.info.author.name``.
+
+Note: ``data`` may also be a callable which will be passed a row object.
+
+Apart from their internal name, you can define a string that will be used
+when for display via ``verbose_name``:
+
+.. code-block:: python
+
+    pubdate = tables.Column(verbose_name="Published")
+
+The verbose name will be used, for example, if you put in a template:
+
+.. code-block:: django
+
+    {{ column }}
+
+If you don't want a column to be sortable by the user:
+
+.. code-block:: python
+
+    pubdate = tables.Column(sortable=False)
+
+Sorting is also affected by ``direction``, which can be used to change the
+*default* sort direction to descending. Note that this option only indirectly
+translates to the actual direction. Normal und reverse order, the terms
+django-tables exposes, now simply mean different things.
+
+.. code-block:: python
+
+    pubdate = tables.Column(direction='desc')
+
+If you don't want to expose a column (but still require it to exist, for
+example because it should be sortable nonetheless):
+
+.. code-block:: python
+
+    pubdate = tables.Column(visible=False)
+
+The column and it's values will now be skipped when iterating through the
+table, although it can still be accessed manually.
+
+Finally, you can specify default values for your columns:
+
+.. code-block:: python
+
+    health_points = tables.Column(default=100)
+
+Note that how the default is used and when it is applied differs between
+table types.
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644 (file)
index 0000000..4a407dd
--- /dev/null
@@ -0,0 +1,194 @@
+# -*- coding: utf-8 -*-
+#
+# django-tables documentation build configuration file, created by
+# sphinx-quickstart on Fri Mar 26 08:40:14 2010.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+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.append(os.path.abspath('.'))
+
+# -- 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 = ['sphinx.ext.autodoc']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'django-tables'
+copyright = u'2010, Michael Elsdörfer'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '0.1'
+# The full version, including alpha/beta/rc tags.
+release = '0.1'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of documents that shouldn't be included in the build.
+#unused_docs = []
+
+# List of directories, relative to source directory, that shouldn't be searched
+# for source files.
+exclude_trees = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  Major themes that come with
+# Sphinx are currently 'default' and 'sphinxdoc'.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_use_modindex = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# 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
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = ''
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'django-tablesdoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index.rst', 'django-tables.tex', u'django-tables Documentation',
+   u'Michael Elsdörfer', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_use_modindex = True
diff --git a/docs/features/index.rst b/docs/features/index.rst
new file mode 100644 (file)
index 0000000..abada5a
--- /dev/null
@@ -0,0 +1,11 @@
+===============
+How to do stuff
+===============
+
+This section will explain some specific features in more detail.
+
+.. toctree::
+   :maxdepth: 1
+
+   ordering
+   pagination
\ No newline at end of file
diff --git a/docs/features/ordering.rst b/docs/features/ordering.rst
new file mode 100644 (file)
index 0000000..4f81f1d
--- /dev/null
@@ -0,0 +1,170 @@
+=================
+Sorting the table
+=================
+
+``django-tables`` allows you to specify which column the user can sort,
+and will validate and resolve an incoming query string value the the
+correct ordering.
+
+It will also help you rendering the correct links to change the sort
+order in your template.
+
+
+Specify which columns are sortable
+----------------------------------
+
+Tables can take a ``sortable`` option through an inner ``Meta``, the same
+concept as known from forms and models in Django:
+
+.. code-block:: python
+
+    class MyTable(tables.MemoryTable):
+        class Meta:
+            sortable = True
+
+This will be the default value for all columns, and it defaults to ``True``.
+You can override the table default for each individual column:
+
+.. code-block:: python
+
+    class MyTable(tables.MemoryTable):
+        foo = tables.Column(sortable=False)
+        class Meta:
+            sortable = True
+
+
+Setting the table ordering
+--------------------------
+
+Your table both takes a ``order_by`` argument in it's constructor, and you
+can change the order by assigning to the respective attribute:
+
+.. code-block:: python
+
+    table = MyTable(order_by='-foo')
+    table.order_by = 'foo'
+
+You can see that the value expected is pretty much what is used by the
+Django database API: An iterable of column names, optionally using a hyphen
+as a prefix to indicate reverse order. However, you may also pass a
+comma-separated string:
+
+.. code-block:: python
+
+    table = MyTable(order_by='column1,-column2')
+
+When you set ``order_by``, the value is parsed right away, and subsequent
+reads will give you the normalized value:
+
+.. code-block:: python
+
+    >>> table.order_by = ='column1,-column2'
+    >>> table.order_by
+    ('column1', '-column2')
+
+Note: Random ordering is currently not supported.
+
+
+Error handling
+~~~~~~~~~~~~~~
+
+Passing incoming query string values from the request directly to the
+table constructor is a common thing to do. However, such data can easily
+contain invalid column names, be it that a user manually modified it,
+or someone put up a broken link. In those cases, you usually would not want
+to raise an exception (nor be notified by Django's error notification
+mechanism) - there is nothing you could do anyway.
+
+Because of this, such errors will by default be silently ignored. For
+example, if one out of three columns in an "order_by" is invalid, the other
+two will still be applied:
+
+.. code-block:: python
+
+    >>> table.order_by = ('name', 'totallynotacolumn', '-date)
+    >>> table.order_by
+    ('name', '-date)
+
+This ensures that the following table will be created regardless of the
+value in ``sort``:
+
+.. code-block:: python
+
+    table = MyTable(data, order_by=request.GET.get('sort'))
+
+However, if you want, you can disable this behaviour and have an exception
+raised instead, using:
+
+.. code-block:: python
+
+    import django_tables
+    django_tables.options.IGNORE_INVALID_OPTIONS = False
+
+
+Interacting with order
+----------------------
+
+Letting the user change the order of a table is a common scenario. With
+respect to Django, this means adding links to your table output that will
+send off the appropriate arguments to the server. ``django-tables``
+attempts to help with you that.
+
+A bound column, that is a column accessed through a table instance, provides
+the following attributes:
+
+- ``name_reversed`` will simply return the column name prefixed with a
+  hyphen; this is useful in templates, where string concatenation can
+  at times be difficult.
+
+- ``name_toggled`` checks the tables current order, and will then
+  return the column either prefixed with an hyphen (for reverse ordering)
+  or without, giving you the exact opposite order. If the column is
+  currently not ordered, it will start off in non-reversed order.
+
+It is easy to be confused about the difference between the ``reverse`` and
+``toggle`` terminology. ``django-tables`` tries to put a normal/reverse-order
+abstraction on top of "ascending/descending", where as normal order could
+potentially mean either ascending or descending, depending on the column.
+
+Something you commonly see is a table that indicates which column it is
+currently ordered by through little arrows. To implement this, you will
+find useful:
+
+- ``is_ordered``: Returns ``True`` if the column is in the current
+  ``order_by``, regardless of the polarity.
+
+- ``is_ordered_reverse``, ``is_ordered_straight``: Returns ``True`` if the
+  column is ordered in reverse or non-reverse, respectively, otherwise
+  ``False``.
+
+The above is usually enough for most simple cases, where tables are only
+ordered by a single column. For scenarios in which multi-column order is
+used, additional attributes are available:
+
+- ``order_by``: Return the current order, but with the current column
+  set to normal ordering. If the current column is not already part of
+  the order, it is appended. Any existing columns in the order are
+  maintained as-is.
+
+- ``order_by_reversed``, ``order_by_toggled``: Similarly, return the
+  table's current ``order_by`` with the column set to reversed or toggled,
+  respectively. Again, it is appended if not already ordered.
+
+Additionally, ``table.order_by.toggle()`` may also be useful in some cases:
+It will toggle all order columns and should thus give you the exact
+opposite order.
+
+The following is a simple example of single-column ordering. It shows a list
+of sortable columns, each clickable, and an up/down arrow next to the one
+that is currently used to sort the table.
+
+.. code-block:: django
+
+    Sort by:
+    {% for column in table.columns %}
+        {% if column.sortable %}
+            <a href="?sort={{ column.name_toggled }}">{{ column }}</a>
+            {% if column.is_ordered_straight %}<img src="down.png" />{% endif %}
+            {% if column.is_ordered_reverse %}<img src="up.png" />{% endif %}
+        {% endif %}
+    {% endfor %}
diff --git a/docs/features/pagination.rst b/docs/features/pagination.rst
new file mode 100644 (file)
index 0000000..30217c1
--- /dev/null
@@ -0,0 +1,47 @@
+----------
+Pagination
+----------
+
+If your table has a large number of rows, you probably want to paginate
+the output. There are two distinct approaches.
+
+First, you can just paginate over ``rows`` as you would do with any other
+data:
+
+.. code-block:: python
+
+    table = MyTable(queryset)
+    paginator = Paginator(table.rows, 10)
+    page = paginator.page(1)
+
+You're not necessarily restricted to Django's own paginator (or subclasses) -
+any paginator should work with this approach, so long it only requires
+``rows`` to implement ``len()``, slicing, and, in the case of a
+``ModelTable``, a ``count()`` method. The latter means that the
+``QuerySetPaginator`` also works as expected.
+
+Alternatively, you may use the ``paginate`` feature:
+
+.. code-block:: python
+
+    table = MyTable(queryset)
+    table.paginate(Paginator, 10, page=1, orphans=2)
+    for row in table.rows.page():
+        pass
+    table.paginator                # new attributes
+    table.page
+
+The table will automatically create an instance of ``Paginator``,
+passing it's own data as the first argument and additionally any arguments
+you have specified, except for ``page``. You may use any paginator, as long
+as it follows the Django protocol:
+
+* Take data as first argument.
+* Support a page() method returning an object with an ``object_list``
+  attribute, exposing the paginated data.
+
+Note that due to the abstraction layer that ``django-tables`` represents, it
+is not necessary to use Django's ``QuerySetPaginator`` with model tables.
+Since the table knows that it holds a queryset, it will automatically choose
+to use count() to determine the data length (which is exactly what
+``QuerySetPaginator`` would do).
\ No newline at end of file
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644 (file)
index 0000000..95ae89b
--- /dev/null
@@ -0,0 +1,145 @@
+==========================================
+django-tables - A Django Queryset renderer
+==========================================
+
+
+``django-tables`` wants to help you present data while allowing your user
+to apply common tabular transformations on it.
+
+Currently, this mostly mostly means "sorting", i.e. parsing a query string
+coming from the browser (while supporting multiple sort fields, restricting
+the fields that may be sorted, exposing fields under different names) and
+generating the proper links to allow the user to change the sort order.
+
+In the future, filtering and grouping will hopefully be added.
+
+
+A simple example
+----------------
+
+The API looks similar to that of Django's ``ModelForms``:
+
+.. code-block:: python
+
+    import django_tables as tables
+
+    class CountryTable(tables.MemoryTable):
+        name = tables.Column(verbose_name="Country Name")
+        population = tables.Column(sortable=False, visible=False)
+        time_zone = tables.Column(name="tz", default="UTC+1")
+
+Instead of fields, you declare a column for every piece of data you want
+to expose to the user.
+
+To use the table, create an instance:
+
+.. code-block:: python
+
+    countries = CountryTable([{'name': 'Germany', population: 80},
+                              {'name': 'France', population: 64}])
+
+Decide how the table should be sorted:
+
+.. code-block:: python
+
+    countries.order_by = ('name',)
+    assert [row.name for row in countries.row] == ['France', 'Germany']
+
+    countries.order_by = ('-population',)
+    assert [row.name for row in countries.row] == ['Germany', 'France']
+
+If you pass the table object along into a template, you can do:
+
+.. code-block:: django
+
+    {% for column in countries.columns %}
+        {{ column }}
+    {% endfor %}
+
+Which will give you:
+
+.. code-block:: django
+
+    Country Name
+    Timezone
+
+Note that ``population`` is skipped (as it has ``visible=False``), that the
+declared verbose name for the ``name`` column is used, and that ``time_zone``
+is converted into a more beautiful string for output automatically.
+
+
+Common Workflow
+~~~~~~~~~~~~~~~
+
+Usually, you are going to use a table like this. Assuming ``CountryTable``
+is defined as above, your view will create an instance and pass it to the
+template:
+
+.. code-block:: python
+
+    def list_countries(request):
+        data = ...
+        countries = CountryTable(data, order_by=request.GET.get('sort'))
+        return render_to_response('list.html', {'table': countries})
+
+Note that we are giving the incoming ``sort`` query string value directly to
+the table, asking for a sort. All invalid column names will (by default) be
+ignored. In this example, only ``name`` and ``tz`` are allowed, since:
+
+ * ``population`` has ``sortable=False``
+ * ``time_zone`` has it's name overwritten with ``tz``.
+
+Then, in the ``list.html`` template, write:
+
+.. code-block:: django
+
+    <table>
+    <tr>
+        {% for column in table.columns %}
+        <th><a href="?sort={{ column.name_toggled }}">{{ column }}</a></th>
+        {% endfor %}
+    </tr>
+    {% for row in table.rows %}
+        <tr>
+        {% for value in row %}
+            <td>{{ value }}</td>
+        {% endfor %}
+        </tr>
+    {% endfor %}
+    </table>
+
+This will output the data as an HTML table. Note how the table is now fully
+sortable, since our link passes along the column name via the querystring,
+which in turn will be used by the server for ordering. ``order_by`` accepts
+comma-separated strings as input, and "{{ column.name_toggled }}" will be
+rendered as a such a string.
+
+Instead of the iterator, you can alos use your knowledge of the table
+structure to access columns directly:
+
+.. code-block:: django
+
+    {% if table.columns.tz.visible %}
+        {{ table.columns.tz }}
+    {% endfor %}
+
+
+In Detail
+=========
+
+.. toctree::
+   :maxdepth: 2
+
+   installation
+   types/index
+   features/index
+   columns
+   templates
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
diff --git a/docs/installation.rst b/docs/installation.rst
new file mode 100644 (file)
index 0000000..2df888b
--- /dev/null
@@ -0,0 +1,15 @@
+------------
+Installation
+------------
+
+Adding ``django-tables`` to your ``INSTALLED_APPS`` setting is optional.
+It'll get you the ability to load some template utilities via
+``{% load tables %}``, but apart from that,
+``import django_tables as tables`` should get you going.
+
+
+Running the test suite
+----------------------
+
+The test suite uses nose:
+    http://somethingaboutorange.com/mrl/projects/nose/
\ No newline at end of file
diff --git a/docs/make.bat b/docs/make.bat
new file mode 100644 (file)
index 0000000..28bda72
--- /dev/null
@@ -0,0 +1,113 @@
+@ECHO OFF
+
+REM Command file for Sphinx documentation
+
+set SPHINXBUILD=sphinx-build
+set BUILDDIR=_build
+set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
+if NOT "%PAPER%" == "" (
+       set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
+)
+
+if "%1" == "" goto help
+
+if "%1" == "help" (
+       :help
+       echo.Please use `make ^<target^>` where ^<target^> is one of
+       echo.  html      to make standalone HTML files
+       echo.  dirhtml   to make HTML files named index.html in directories
+       echo.  pickle    to make pickle files
+       echo.  json      to make JSON files
+       echo.  htmlhelp  to make HTML files and a HTML help project
+       echo.  qthelp    to make HTML files and a qthelp project
+       echo.  latex     to make LaTeX files, you can set PAPER=a4 or PAPER=letter
+       echo.  changes   to make an overview over all changed/added/deprecated items
+       echo.  linkcheck to check all external links for integrity
+       echo.  doctest   to run all doctests embedded in the documentation if enabled
+       goto end
+)
+
+if "%1" == "clean" (
+       for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
+       del /q /s %BUILDDIR%\*
+       goto end
+)
+
+if "%1" == "html" (
+       %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
+       echo.
+       echo.Build finished. The HTML pages are in %BUILDDIR%/html.
+       goto end
+)
+
+if "%1" == "dirhtml" (
+       %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
+       echo.
+       echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
+       goto end
+)
+
+if "%1" == "pickle" (
+       %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
+       echo.
+       echo.Build finished; now you can process the pickle files.
+       goto end
+)
+
+if "%1" == "json" (
+       %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
+       echo.
+       echo.Build finished; now you can process the JSON files.
+       goto end
+)
+
+if "%1" == "htmlhelp" (
+       %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
+       echo.
+       echo.Build finished; now you can run HTML Help Workshop with the ^
+.hhp project file in %BUILDDIR%/htmlhelp.
+       goto end
+)
+
+if "%1" == "qthelp" (
+       %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
+       echo.
+       echo.Build finished; now you can run "qcollectiongenerator" with the ^
+.qhcp project file in %BUILDDIR%/qthelp, like this:
+       echo.^> qcollectiongenerator %BUILDDIR%\qthelp\django-tables.qhcp
+       echo.To view the help file:
+       echo.^> assistant -collectionFile %BUILDDIR%\qthelp\django-tables.ghc
+       goto end
+)
+
+if "%1" == "latex" (
+       %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+       echo.
+       echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
+       goto end
+)
+
+if "%1" == "changes" (
+       %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
+       echo.
+       echo.The overview file is in %BUILDDIR%/changes.
+       goto end
+)
+
+if "%1" == "linkcheck" (
+       %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
+       echo.
+       echo.Link check complete; look for any errors in the above output ^
+or in %BUILDDIR%/linkcheck/output.txt.
+       goto end
+)
+
+if "%1" == "doctest" (
+       %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
+       echo.
+       echo.Testing of doctests in the sources finished, look at the ^
+results in %BUILDDIR%/doctest/output.txt.
+       goto end
+)
+
+:end
diff --git a/docs/templates.rst b/docs/templates.rst
new file mode 100644 (file)
index 0000000..36a2acc
--- /dev/null
@@ -0,0 +1,67 @@
+================================
+Rendering the table in templates
+================================
+
+
+A table instance bound to data has two attributes ``columns`` and ``rows``,
+which can be iterate over:
+
+.. code-block:: django
+
+    <table>
+    <tr>
+        {% for column in table.columns %}
+        <th><a href="?sort={{ column.name_toggled }}">{{ column }}</a></th>
+        {% endfor %}
+    </tr>
+    {% for row in table.rows %}
+        <tr>
+        {% for value in row %}
+            <td>{{ value }}</td>
+        {% endfor %}
+        </tr>
+    {% endfor %}
+    </table>
+
+For the attributes available on a bound column, see :doc:`features/index`,
+depending on what you want to accomplish.
+
+
+The table.columns container
+---------------------------
+
+While you can iterate through ``columns`` and get all the currently visible
+columns, it further provides features that go beyond a simple iterator.
+
+You can access all columns, regardless of their visibility, through
+``columns.all``.
+
+``columns.sortable`` is a handy shortcut that exposes all columns which's
+``sortable`` attribute is True. This can be very useful in templates, when
+doing {% if column.sortable %} can conflict with {{ forloop.last }}.
+
+
+Template Utilities
+------------------
+
+If you want the give your users the ability to interact with your table (e.g.
+change the ordering), you will need to create urls with the appropriate
+queries. To simplify that process, django-tables comes with a helpful
+templatetag:
+
+.. code-block:: django
+
+    {% set_url_param sort="name" %}       # ?sort=name
+    {% set_url_param sort="" %}           # delete "sort" param
+
+The template library can be found in 'django_modules.app.templates.tables'.
+If you add ''django_modules.app' to your ``INSTALLED_APPS`` setting, you
+will be able to do:
+
+.. code-block:: django
+
+    {% load tables %}
+
+Note: The tag requires the current request to be available as ``request``
+in the context (usually, this means activating the Django request context
+processor).
diff --git a/docs/types/index.rst b/docs/types/index.rst
new file mode 100644 (file)
index 0000000..02b20e0
--- /dev/null
@@ -0,0 +1,12 @@
+===========
+Table types
+===========
+
+Different types of tables are available:
+
+.. toctree::
+   :maxdepth: 1
+
+   MemoryTable - uses dicts as the data source <memory>
+   ModelTable - wraps around a Django Model <models>
+   SqlTable - is based on a raw SQL query <sql>
\ No newline at end of file
diff --git a/docs/types/memory.rst b/docs/types/memory.rst
new file mode 100644 (file)
index 0000000..d43d7a1
--- /dev/null
@@ -0,0 +1,20 @@
+-----------
+MemoryTable
+-----------
+
+This table expects an iterable of ``dict`` (or compatible) objects as the
+data source. Values found in the data that are not associated with a column
+are ignored, missing values are replaced by the column default or ``None``.
+
+Sorting is done in memory, in pure Python.
+
+Dynamic Data
+~~~~~~~~~~~~
+
+If any value in the source data is a callable, it will be passed it's own
+row instance and is expected to return the actual value for this particular
+table cell.
+
+Similarily, the colunn default value may also be callable that will take
+the row instance as an argument (representing the row that the default is
+needed for).
\ No newline at end of file
diff --git a/docs/types/models.rst b/docs/types/models.rst
new file mode 100644 (file)
index 0000000..b4e782d
--- /dev/null
@@ -0,0 +1,134 @@
+----------
+ModelTable
+----------
+
+This table type is based on a Django model. It will use the Model's data,
+and, like a ``ModelForm``, can automatically generate it's columns based
+on the mode fields.
+
+.. code-block:: python
+
+    class CountryTable(tables.ModelTable):
+        id = tables.Column(sortable=False, visible=False)
+        class Meta:
+            model = Country
+            exclude = ['clicks']
+
+In this example, the table will have one column for each model field,
+with the exception of ``clicks``, which is excluded. The column for ``id``
+is overwritten to both hide it by default and deny it sort capability.
+
+When instantiating a ``ModelTable``, you usually pass it a queryset to
+provide the table data:
+
+.. code-block:: python
+
+    qs = Country.objects.filter(continent="europe")
+    countries = CountryTable(qs)
+
+However, you can also just do:
+
+.. code-block:: python
+
+    countries = CountryTable()
+
+and all rows exposed by the default manager of the model the table is based
+on will be used.
+
+If you are using model inheritance, then the following also works:
+
+.. code-block:: python
+
+    countries = CountryTable(CountrySubclass)
+
+Note that while you can pass any model, it really only makes sense if the
+model also provides fields for the columns you have defined.
+
+If you just want to use a ``ModelTable``, but without auto-generated
+columns, you do not have to list all model fields in the ``exclude``
+``Meta`` option. Instead, simply don't specify a model.
+
+
+Custom Columns
+~~~~~~~~~~~~~~
+
+You an add custom columns to your ModelTable that are not based on actual
+model fields:
+
+.. code-block:: python
+
+    class CountryTable(tables.ModelTable):
+        custom = tables.Column(default="foo")
+        class Meta:
+            model = Country
+
+Just make sure your model objects do provide an attribute with that name.
+Functions are also supported, so ``Country.custom`` could be a callable.
+
+
+Spanning relationships
+~~~~~~~~~~~~~~~~~~~~~~
+
+Let's assume you have a ``Country`` model, with a ``ForeignKey`` ``capital``
+pointing to the ``City`` model. While displaying a list of countries,
+you might want want to link to the capital's geographic location, which is
+stored in ``City.geo`` as a ``(lat, long)`` tuple, on, say, a Google Map.
+
+``ModelTable`` supports the relationship spanning syntax of Django's
+database API:
+
+.. code-block:: python
+
+    class CountryTable(tables.ModelTable):
+        city__geo = tables.Column(name="geo")
+
+This will add a column named "geo", based on the field by the same name
+from the "city" relationship. Note that the name used to define the column
+is what will be used to access the data, while the name-overwrite passed to
+the column constructor just defines a prettier name for us to work with.
+This is to be consistent with auto-generated columns based on model fields,
+where the field/column name naturally equals the source name.
+
+However, to make table defintions more visually appealing and easier to
+read, an alternative syntax is supported: setting the column ``data``
+property to the appropriate string.
+
+.. code-block:: python
+
+    class CountryTable(tables.ModelTable):
+        geo = tables.Column(data='city__geo')
+
+Note that you don't need to define a relationship's fields as separate
+columns if you already have a column for the relationship itself, i.e.:
+
+.. code-block:: python
+
+    class CountryTable(tables.ModelTable):
+        city = tables.Column()
+
+    for country in countries.rows:
+        print country.city.id
+        print country.city.geo
+        print country.city.founder.name
+
+
+``ModelTable`` Specialties
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+``ModelTable`` currently has some restrictions with respect to ordering:
+
+* Custom columns not based on a model field do not support ordering,
+  regardless of the ``sortable`` property (it is ignored).
+
+* A ``ModelTable`` column's ``default`` or ``data`` value does not affect
+  ordering. This differs from the non-model table behaviour.
+
+If a column is mapped to a method on the model, that method will be called
+without arguments. This behavior differs from memory tables, where a
+row object will be passed.
+
+If you are using callables (e.g. for the ``default`` or ``data`` column
+options), they will generally be run when a row is accessed, and
+possible repeatedly when accessed more than once. This behavior differs from
+memory tables, where they would be called once, when the table is
+generated.
\ No newline at end of file
diff --git a/docs/types/sql.rst b/docs/types/sql.rst
new file mode 100644 (file)
index 0000000..a21eb88
--- /dev/null
@@ -0,0 +1,9 @@
+--------
+SqlTable
+--------
+
+This table is backed by an SQL query that you specified. It'll help you
+ensure that pagination and sorting options are properly reflected in the
+query.
+
+**Currently not implemented yet.**
\ No newline at end of file
index 3563a73c663bd69a190507257c6589b6b5448fc2..8c5e2595b387c40bdcdca3578dfc0b84d56e7366 100644 (file)
@@ -1,2 +1,3 @@
 django
 nose
+Sphinx