Noted another random idea.
[django-tables2.git] / README
diff --git a/README b/README
index 6a161f5ef31651595b2d599040812321afa4fa99..2bc8b8161c8c6253d70499aba7eb0766991d8197 100644 (file)
--- a/README
+++ b/README
@@ -1,602 +1,4 @@
-django-tables\r
-=============\r
+django-tables - a Django QuerySet renderer.\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.Table):\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.Table):\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.Table):\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
-\r
-\r
-TODO\r
-----\r
-    - as_html methods are all empty right now\r
-    - table.column[].values is a stub\r
-    - filters\r
-    - grouping\r
-    - choices support for columns (internal column value will be looked up\r
-      for output)\r
-    - for columns that span model relationships, automatically generate\r
-      select_related(); this is important, since right now such an e.g.\r
-      order_by would cause rows to be dropped (inner join).\r
-    - initialize auto-generated columns with the relevant properties of the\r
-      model fields (verbose_name, editable=visible?, ...)\r
-    - remove support for callable fields? this has become obsolete since we\r
-      Column.data property; also, it's easy to make the call manually, or let\r
-      the template engine handle it\r
-    - tests could use some refactoring, they are currently all over the place\r
-    - what happens if duplicate column names are used? we currently don't\r
-      check for that at all\r
-\r
-Filters\r
-~~~~~~~\r
-\r
-Filtering is already easy (just use the normal queryset methods), but\r
-filter support in django-tables would want to hide the Django ORM syntax\r
-from the user.\r
-\r
-    * For example, say a ``models.DateTimeField`` should be filtered\r
-      by year: the user would only see ``date=2008`` rather than maybe\r
-      ``published_at__year=2008``.\r
-\r
-    * Say you want to filter out ``UserProfile`` rows that do not have\r
-      an avatar image set. The user would only see ```no_avatar``, which\r
-      in Django ORM syntax might map to\r
-      ``Q(avatar__isnull=True) | Q(avatar='')``.\r
-\r
-Filters would probably always belong to a column, and be defined along\r
-side one.\r
-\r
-    class BookTable(tables.ModelTable):\r
-        date = tables.Column(filter='published_at__year')\r
-\r
-If a filter is needed that does not belong to a single colunn, a column\r
-would have to be defined for just that filter. A ``tables.Filter`` object\r
-could be provided that would basically be a column, but with default\r
-properties set so that the column functionality is disabled as far as\r
-possible (i.e. ``visible=False`` etc):\r
-\r
-    class BookTable(tables.ModelTable):\r
-        date = tables.Column(filter='published_at__year')\r
-        has_cover = tables.Filter('cover__isnull', value=True)\r
-\r
-Or, if Filter() gets a lot of additional functionality like ``value``,\r
-we could generally make it available to all filters like so:\r
-\r
-    class BookTable(tables.ModelTable):\r
-        date = tables.Column(filter=tables.Filter('published_at__year', default=2008))\r
-        has_cover = tables.Filter('cover__isnull', value=True)\r
-\r
-More complex filters should be supported to (e.g. combine multiple Q\r
-objects, support ``exclude`` as well as ``filter``). Allowing the user\r
-to somehow specify a callable probably is the easiest way to enable this.\r
-\r
-The filter querystring syntax, as exposed to the user, could look like this:\r
-\r
-    /view/?filter=name:value\r
-    /view/?filter=name\r
-\r
-It would also be cool if filters could be combined. However, in that case\r
-it would also make sense to make it possible to choose individual filters\r
-which cannot be combined with any others, or maybe even allow the user\r
-to specify complex dependencies. That may be pushing it though, and anyway\r
-won't make it into the first version.\r
-\r
-    /view/?filter=name:value,foo:bar\r
-\r
-We need to think about how we make the functionality available to\r
-templates.\r
-\r
-Another feature that would be important is the ability to limit the valid\r
-values for a filter, e.g. in the date example only years from 2000 to 2008.
\ No newline at end of file
+Documentation:\r
+    http://elsdoerfer.name/docs/django-tables/\r