.. default-domain:: py
-=====================================================
-django-tables - An app for creating HTML tables
-=====================================================
+================================================
+django-tables2 - An app for creating HTML tables
+================================================
-django-tables simplifies the task of turning sets of datainto HTML tables. It
+django-tables2 simplifies the task of turning sets of datainto HTML tables. It
has native support for pagination and sorting. It does for HTML tables what
``django.forms`` does for HTML forms.
Quick start guide
=================
-1. Download and install the package.
-2. Install the tables framework by adding ``'django_tables'`` to your
+1. Download and install from https://github.com/bradleyayers/django-tables2.
+ Grab a ``.tar.gz`` of the latest tag, and run ``pip install <tar.gz>``.
+2. Hook the app into your Django project by adding ``'django_tables2'`` to your
``INSTALLED_APPS`` setting.
-3. Ensure that ``'django.core.context_processors.request'`` is in your
- ``TEMPLATE_CONTEXT_PROCESSORS`` setting.
-4. Write table classes for the types of tables you want to display.
-5. Create an instance of a table in a view, provide it your data, and pass it
- to a template for display.
-6. Use ``{{ table.as_html }}``, the
- :ref:`template tag <template_tags.render_table>`, or your own
- custom template code to display the table.
+3. Write a subclass of :class:`~django_tables2.tables.Table` that describes the
+ structure of your table.
+4. Create an instance of your table in a :term:`view`, provide it with
+ :term:`table data`, and pass it to a :term:`template` for display.
+5. Use ``{{ table.as_html }}``, the
+ :ref:`template tag <template-tags.render_table>`, or your own
+ :ref:`custom template <custom-template>` to display the table.
-Tables
-======
+Slow start guide
+================
-For each type of table you want to display, you will need to create a subclass
-of ``django_tables.Table`` that describes the structure of the table.
-
-In this example we are going to take some data describing three countries and
-turn it into a HTML table. We start by creating our data:
+We're going to take some data that describes three countries and
+turn it into an HTML table. This is the data we'll be using:
.. code-block:: python
- >>> countries = [
- ... {'name': 'Australia', 'population': 21, 'tz': 'UTC +10', 'visits': 1},
- ... {'name': 'Germany', 'population', 81, 'tz': 'UTC +1', 'visits': 2},
- ... {'name': 'Mexico', 'population': 107, 'tz': 'UTC -6', 'visits': 0},
- ... ]
+ countries = [
+ {'name': 'Australia', 'population': 21, 'tz': 'UTC +10', 'visits': 1},
+ {'name': 'Germany', 'population': 81, 'tz': 'UTC +1', 'visits': 2},
+ {'name': 'Mexico', 'population': 107, 'tz': 'UTC -6', 'visits': 0},
+ ]
+
-Next we subclass ``django_tables.Table`` to create a table that describes our
-data. The API should look very familiar since it's based on Django's
-database model API:
+The first step is to subclass :class:`~django_tables2.tables.Table` and describe
+the table structure. This is done by creating a column for each attribute in
+the :term:`table data`.
.. code-block:: python
- >>> import django_tables as tables
- >>> class CountryTable(tables.Table):
- ... name = tables.Column()
- ... population = tables.Column()
- ... tz = tables.Column(verbose_name='Time Zone')
- ... visits = tables.Column()
+ import django_tables2 as tables
+ class CountryTable(tables.Table):
+ name = tables.Column()
+ population = tables.Column()
+ tz = tables.Column(verbose_name='Time Zone')
+ visits = tables.Column()
-Providing data
---------------
-To use the table, simply create an instance of the table class and pass in your
-data. e.g. following on from the above example:
+Now that we've defined our table, it's ready for use. We simply create an
+instance of it, and pass in our table data.
.. code-block:: python
- >>> table = CountryTable(countries)
+ table = CountryTable(countries)
-Tables have support for any iterable data that contains objects with
-attributes that can be accessed as property or dictionary syntax:
+Now we add it to our template context and render it to HTML. Typically you'd
+write a view that would look something like:
.. code-block:: python
- >>> table = SomeTable([{'a': 1, 'b': 2}, {'a': 4, 'b': 8}]) # valid
- >>> table = SomeTable(SomeModel.objects.all()) # also valid
-
-Each item in the data corresponds to one row in the table. By default, the
-table uses column names as the keys (or attributes) for extracting cell values
-from the data. This can be changed by using the :attr:`~Column.accessor`
-argument.
-
+ def home(request):
+ table = CountryTable(countries)
+ return render_to_response('home.html', {'table': table},
+ context_instance=RequestContext(request))
-Displaying a table
-------------------
-
-There are two ways to display a table, the easiest way is to use the table's
-own ``as_html`` method:
+In your template, the easiest way to :term:`render` the table is via the
+:meth:`~django_tables2.tables.Table.as_html` method:
.. code-block:: django
{{ table.as_html }}
-Which will render something like:
+…which will render something like:
-+--------------+------------+---------+
-| Country Name | Population | Tz |
-+==============+============+=========+
-| Australia | 21 | UTC +10 |
-+--------------+------------+---------+
-| Germany | 81 | UTC +1 |
-+--------------+------------+---------+
-| Mexico | 107 | UTC -6 |
-+--------------+------------+---------+
++--------------+------------+-----------+--------+
+| Country Name | Population | Time Zone | Visit |
++==============+============+===========+========+
+| Australia | 21 | UTC +10 | 1 |
++--------------+------------+-----------+--------+
+| Germany | 81 | UTC +1 | 2 |
++--------------+------------+-----------+--------+
+| Mexico | 107 | UTC -6 | 0 |
++--------------+------------+-----------+--------+
-The downside of this approach is that pagination and sorting will not be
-available. These features require the use of the ``{% render_table %}``
-template tag:
+This approach is easy, but it's not fully featured (e.g. no pagination, no
+sorting). Don't worry it's very easy to add these. First, you must render the
+table via the :ref:`template tag <template-tags.render_table>` rather than
+``as_html()``:
.. code-block:: django
- {% load django_tables %}
+ {% load django_tables2 %}
{% render_table table %}
-See :ref:`template_tags` for more information.
+.. note::
+
+ ``{% render_table %}`` requires that the ``TEMPLATE_CONTEXT_PROCESSORS``
+ setting contains ``"django.core.context_processors.request"``. See
+ :ref:`template-tags.render_table` for details.
+
+This is all that's required for the template, but in the view you'll need to
+tell the table to which column to order by, and which page of data to display
+(pagination). This is achieved as follows:
+
+.. code-block:: python
+
+ def home(request):
+ countries = Country.objects.all()
+ table = CountryTable(countries, order_by=request.GET.get('sort'))
+ table.paginate(page=request.GET.get('page', 1))
+ return render_to_response('home.html', {'table': table},
+ context_instance=RequestContext(request))
+
+See :ref:`ordering`, and :ref:`pagination` for more information.
+
+The table will be rendered, but chances are it will still look quite ugly. An
+easy way to make it pretty is to use the built-in *paleblue* theme. For this to
+work, you must add a CSS class to the ``<table>`` tag. This can be achieved by
+adding a ``class Meta:`` to the table class and defining a ``attrs`` variable.
+
+.. code-block:: python
+
+ import django_tables2 as tables
+
+ class CountryTable(tables.Table):
+ name = tables.Column()
+ population = tables.Column()
+ tz = tables.Column(verbose_name='Time Zone')
+ visits = tables.Column()
+
+ class Meta:
+ attrs = {'class': 'paleblue'}
+
+The last thing to do is to include the stylesheet in the template.
+
+.. code-block:: html
+ <link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}django_tables2/themes/paleblue/css/screen.css" />
+
+Save your template and reload the page in your browser.
+
+
+.. _table-data:
+
+Table data
+==========
+
+The data used to populate a table is called :term:`table data`. To provide a
+table with data, pass it in as the first argument when instantiating a table.
+
+.. code-block:: python
+
+ table = CountryTable(countries) # valid
+ table = CountryTable(Country.objects.all()) # also valid
+
+Each item in the :term:`table data` is called a :term:`record` and is used to
+populate a single row in the table. By default, the table uses column names
+as :term:`accessors <accessor>` to retrieve individual cell values. This can
+be changed via the :attr:`~django_tables2.columns.Column.accessor` argument.
+
+Any iterable can be used as table data, and there's builtin support for
+:class:`QuerySet` objects (to ensure they're handled effeciently).
+
+
+.. _ordering:
Ordering
---------
+========
+
+.. note::
+
+ If you want to change the order in which columns are displayed, see
+ :attr:`Table.Meta.sequence`. Alternatively if you're interested in the
+ order of records within the table, read on.
-Controlling the order that the rows are displayed (sorting) is simple, just use
-the :attr:`~Table.order_by` property or pass it in when initialising the
-instance:
+Changing the way records in a table are ordered is easy and can be controlled
+via the :attr:`.Table.Meta.order_by` option. The following examples all achieve
+the same thing:
.. code-block:: python
- >>> # order_by argument when creating table instances
- >>> table = CountryTable(countries, order_by='name, -population')
- >>> table = CountryTable(countries, order_by=('name', '-population'))
- >>> # order_by property on table instances
- >>> table = CountryTable(countries)
- >>> table.order_by = 'name, -population'
- >>> table.order_by = ('name', '-population')
+ class SimpleTable(tables.Table):
+ name = tables.Column()
+ class Meta:
+ order_by = 'name'
-Customising the output
-======================
+By passing in a value for ``order_by`` into the ``Table`` constructor, the
+``Meta.order_by`` option can be overridden on a per-instance basis.
-There are a number of options available for changing the way the table is
-rendered. Each approach provides balance of ease-of-use and control (the more
-control you want, the less easy it is to use).
+.. code-block:: python
-CSS
----
+ class SimpleTable(tables.Table):
+ name = tables.Column()
+
+ table = SimpleTable(..., order_by='name')
-If you want to affect the appearance of the table using CSS, you probably want
-to add a ``class`` or ``id`` attribute to the ``<table>`` element. This can be
-achieved by specifying an ``attrs`` variable in the table's ``Meta`` class.
+This approach allows column sorting to be enabled for use with the ``{%
+render_table %}`` template tag. The template tag converts column headers into
+hyperlinks that add the querystring parameter ``sort`` to the current URL. This
+means your view will need to look something like:
.. code-block:: python
- >>> import django_tables as tables
- >>> class SimpleTable(tables.Table):
- ... id = tables.Column()
- ... age = tables.Column()
+ def home(request):
+ countries = Country.objects.all()
+ table = CountryTable(countries, order_by=request.GET.get('sort'))
+ return render_to_response('home.html', {'table': table},
+ context_instance=RequestContext(request))
+
+The final approach allows both of the previous approaches to be overridden. The
+instance property ``order_by`` can be
+
+.. code-block:: python
+
+ class SimpleTable(tables.Table):
+ name = tables.Column()
+
+ table = SimpleTable(...)
+ table.order_by = 'name'
+
+----
+
+By default all table columns support sorting. This means that if you're using
+the :ref:`template tag <template-tags.render_table>` to render the table,
+users can sort the table based on any column by clicking the corresponding
+header link.
+
+In some cases this may not be appropriate. For example you may have a column
+which displays data that isn't in the dataset:
+
+.. code-block:: python
+
+ class SimpleTable(tables.Table):
+ name = tables.Column()
+ lucky_rating = tables.Column()
+
+ class Meta:
+ sortable = False
+
+ def render_lucky_rating(self):
+ import random
+ return random.randint(1, 10)
+
+In these types of scenarios, it's simply not possible to sort the table based
+on column data that isn't in the dataset. The solution is to disable sorting
+for these columns.
+
+Sorting can be disabled on a column, table, or table instance basis via the
+:attr:`.Table.Meta.sortable` option.
+
+To disable sorting by default for all columns:
+
+.. code-block:: python
+
+ class SimpleTable(tables.Table):
+ name = tables.Column()
+
+ class Meta:
+ sortable = False
+
+To disable sorting for a specific table instance:
+
+.. code-block:: python
+
+ class SimpleTable(tables.Table):
+ name = tables.Column()
+
+ table = SimpleTable(..., sortable=False)
+ # or
+ table.sortable = False
+
+
+.. _column-headers:
+
+Column headers
+==============
+
+The header cell for each column comes from the column's
+:meth:`~django_tables2.columns.BoundColumn.header` method. By default this
+method returns the column's ``verbose_name``, which is either explicitly
+specified, or generated automatically based on the column name.
+
+When using queryset input data, rather than falling back to the column name if
+a ``verbose_name`` has not been specified explicitly, the queryset model's
+field ``verbose_name`` is used.
+
+Consider the following:
+
+ >>> class Person(models.Model):
+ ... first_name = models.CharField(verbose_name='FIRST name', max_length=200)
+ ... last_name = models.CharField(max_length=200)
+ ... region = models.ForeignKey('Region')
...
- ... class Meta:
- ... attrs = {'class': 'mytable'}
+ >>> class Region(models.Model):
+ ... name = models.CharField(max_length=200)
...
- >>> table = SimpleTable()
- >>> table.as_html()
- '<table class="mytable">...'
+ >>> class PersonTable(tables.Table):
+ ... first_name = tables.Column()
+ ... ln = tables.Column(accessor='last_name')
+ ... region_name = tables.Column(accessor='region.name')
+ ...
+ >>> table = PersonTable(Person.objects.all())
+ >>> table.columns['first_name'].verbose_name
+ u'FIRST name'
+ >>> table.columns['ln'].verbose_name
+ u'Last name'
+ >>> table.columns['region_name'].verbose_name
+ u'Name'
+
+As you can see in the last example, the results are not always desirable when
+an accessor is used to cross relationships. To get around this be careful to
+define a ``verbose_name`` on such columns.
+
+
+.. _pagination:
-The :attr:`Table.attrs` property actually returns an :class:`AttributeDict`
-object. These objects are identical to :class:`dict`, but have an
-:meth:`AttributeDict.as_html` method that returns a HTML tag attribute string.
+Pagination
+==========
+
+Pagination is easy, just call :meth:`.Table.paginate` and pass in the current
+page number, e.g.
.. code-block:: python
- >>> from django_tables.utils import AttributeDict
- >>> attrs = AttributeDict({'class': 'mytable', 'id': 'someid'})
- >>> attrs.as_html()
- 'class="mytable" id="someid"'
+ def people_listing(request):
+ table = PeopleTable(Person.objects.all())
+ table.paginate(page=request.GET.get('page', 1))
+ return render_to_response('people_listing.html', {'table': table},
+ context_instance=RequestContext(request))
+
+The last set is to render the table. :meth:`.Table.as_html` doesn't support
+pagination, so you must use :ref:`{% render_table %}
+<template-tags.render_table>`.
+
+.. _custom-rendering:
-The returned string is marked safe, so it can be used safely in a template.
+Custom rendering
+================
-Column formatter
-----------------
+Various options are available for changing the way the table is :term:`rendered
+<render>`. Each approach has a different balance of ease-of-use and
+flexibility.
-Using a formatter is a quick way to adjust the way values are displayed in a
-column. A limitation of this approach is that you *only* have access to a
-single attribute of the data source.
+CSS
+---
-To use a formatter, simply provide the :attr:`~Column.formatter` argument to a
-:class:`Column` when you define the :class:`Table`:
+In order to use CSS to style a table, you'll probably want to add a
+``class`` or ``id`` attribute to the ``<table>`` element. ``django-tables2`` has
+a hook that allows abitrary attributes to be added to the ``<table>`` tag.
.. code-block:: python
- >>> import django_tables as tables
+ >>> import django_tables2 as tables
>>> class SimpleTable(tables.Table):
- ... id = tables.Column(formatter=lambda x: '#%d' % x)
- ... age = tables.Column(formatter=lambda x: '%d years old' % x)
+ ... id = tables.Column()
+ ... age = tables.Column()
...
- >>> table = SimpleTable([{'age': 31, 'id': 10}, {'age': 34, 'id': 11}])
- >>> row = table.rows[0]
- >>> for cell in row:
- ... print cell
+ ... class Meta:
+ ... attrs = {'class': 'mytable'}
...
- #10
- 31 years old
-
-As you can see, the only the value of the column is available to the formatter.
-This means that **it's impossible create a formatter that incorporates other
-values of the record**, e.g. a column with an ``<a href="...">`` that uses
-:func:`reverse` with the record's ``pk``.
-
-If formatters aren't powerful enough, you'll need to either :ref:`create a
-Column subclass <subclassing-column>`, or to use the
-:ref:`Table.render_FOO method <table.render_foo>`.
+ >>> table = SimpleTable()
+ >>> table.as_html()
+ '<table class="mytable">...'
.. _table.render_foo:
-:meth:`Table.render_FOO` Method
--------------------------------
+:meth:`Table.render_FOO` Methods
+--------------------------------
+
+If you want to adjust the way table cells in a particular column are rendered,
+you can implement a ``render_FOO`` method. ``FOO`` is replaced with the
+:term:`name <column name>` of the column.
This approach provides a lot of control, but is only suitable if you intend to
customise the rendering for a single table (otherwise you'll end up having to
copy & paste the method to every table you want to modify – which violates
DRY).
-The example below has a number of different techniques in use:
+For convenience, a bunch of commonly used/useful values are passed to
+``render_FOO`` functions, when writing the signature, simply accept the
+arguments you're interested in, and the function will recieve them
-* :meth:`Column.render` (accessible via :attr:`BoundColumn.column`) applies the
- *formatter* if it's been provided. The effect of this behaviour can be seen
- below in the output for the ``id`` column. Square brackets (from the
- *formatter*) have been applied *after* the angled brackets (from the
- :meth:`~Table.render_FOO`).
-* Completely abitrary values can be returned by :meth:`render_FOO` methods, as
- shown in :meth:`~SimpleTable.render_row_number` (a :attr:`_counter` attribute
- is added to the :class:`SimpleTable` object to keep track of the row number).
+.. note:: Due to the implementation, a "catch-extra" arguments (e.g. ``*args``
+ or ``**kwargs``) will not recieve any arguments. This is because
+ ``inspect.getargsspec()`` is used to check what arguments a ``render_FOO``
+ method expect, and only to supply those.
- This is possible because :meth:`render_FOO` methods override the default
- behaviour of retrieving a value from the data-source.
+:param value: the value for the cell retrieved from the :term:`table data`
+:param record: the entire record for the row from :term:`table data`
+:param column: the :class:`.Column` object
+:param bound_column: the :class:`.BoundColumn` object
+:param bound_row: the :class:`.BoundRow` object
+:param table: alias for ``self``
.. code-block:: python
- >>> import django_tables as tables
+ >>> import django_tables2 as tables
>>> class SimpleTable(tables.Table):
... row_number = tables.Column()
- ... id = tables.Column(formatter=lambda x: '[%s]' % x)
- ... age = tables.Column(formatter=lambda x: '%d years old' % x)
+ ... id = tables.Column()
+ ... age = tables.Column()
...
- ... def render_row_number(self, bound_column, bound_row):
+ ... def render_row_number(self):
... value = getattr(self, '_counter', 0)
... self._counter = value + 1
... return 'Row %d' % value
...
- ... def render_id(self, bound_column, bound_row):
- ... value = bound_column.column.render(table=self,
- ... bound_column=bound_column,
- ... bound_row=bound_row)
+ ... def render_id(self, value):
... return '<%s>' % value
...
>>> table = SimpleTable([{'age': 31, 'id': 10}, {'age': 34, 'id': 11}])
... print cell
...
Row 0
- <[10]>
- 31 years old
-
-The :meth:`Column.render` method is what actually performs the lookup into a
-record to retrieve the column value. In the example above, the
-:meth:`render_row_number` never called :meth:`Column.render` and as a result
-there was not attempt to access the data source to retrieve a value.
-
-
-Custom Template
----------------
-
-And of course if you want full control over the way the table is rendered,
-ignore the built-in generation tools, and instead pass an instance of your
-:class:`Table` subclass into your own template, and render it yourself:
-
-.. code-block:: django
-
- {% load django_tables %}
- <table>
- <thead>
- <tr>
- {% for column in table.columns %}
- <th><a href="{% set_url_param sort=column.name_toggled %}">{{ column }}</a></th>
- {% endfor %}
- </tr>
- </thead>
- <tbody>
- {% for row in table.rows %}
- <tr>
- {% for cell in row %}
- <td>{{ cell }}</td>
- {% endfor %}
- </tr>
- {% endfor %}
- </tbody>
- </table>
+ <10>
+ 31
.. _subclassing-column:
.. code-block:: python
- >>> import django_tables as tables
+ >>> import django_tables2 as tables
>>>
>>> class AngryColumn(tables.Column):
- ... def render(self, *args, **kwargs):
- ... raw = super(AngryColumn, self).render(*args, **kwargs)
- ... return raw.upper()
+ ... def render(self, value):
+ ... return value.upper()
...
>>> class Example(tables.Table):
... normal = tables.Column()
>>> table.as_html()
u'<table><thead><tr><th>Normal</th><th>Angry</th></tr></thead><tbody><tr><td>May I have some food?</td><td>GIVE ME THE FOOD NOW!</td></tr><tr><td>Hello!</td><td>WHAT ARE YOU LOOKING AT?</td></tr></tbody></table>\n'
+See :ref:`table.render_foo` for a list of arguments that can be accepted.
+
Which, when displayed in a browser, would look something like this:
+-----------------------+--------------------------+
+-----------------------+--------------------------+
-If you plan on returning HTML from a :meth:`~Column.render` method, you must
-remember to mark it as safe (otherwise it will be escaped when the table is
+For complicated columns, it's sometimes necessary to return HTML from a :meth:`~Column.render` method, but the string
+must be marked as safe (otherwise it will be escaped when the table is
rendered). This can be achieved by using the :func:`mark_safe` function.
.. code-block:: python
>>> from django.utils.safestring import mark_safe
>>>
>>> class ImageColumn(tables.Column):
- ... def render(self, **kwargs):
- ... raw = super(AngryColumn, self).render(**kwargs)
- ... return mark_safe('<img src="/media/img/%s.jpg" />' % raw)
+ ... def render(self, value):
+ ... return mark_safe('<img src="/media/img/%s.jpg" />' % value)
...
+.. _custom-template:
+
+Custom Template
+---------------
+
+And of course if you want full control over the way the table is rendered,
+ignore the built-in generation tools, and instead pass an instance of your
+:class:`Table` subclass into your own template, and render it yourself:
+
+.. code-block:: django
+
+ {% spaceless %}
+ {% load django_tables2 %}
+ {% if table.page %}
+ <div class="table-container">
+ {% endif %}
+ <table{% if table.attrs %} {{ table.attrs.as_html }}{% endif %}>
+ <thead>
+ <tr class="{% cycle "odd" "even" %}">
+ {% for column in table.columns %}
+ {% if column.sortable %}
+ {% with column.order_by as ob %}
+ <th class="{% spaceless %}{% if column.sortable %}sortable {% endif %}{% if ob %}{% if ob.is_descending %}desc{% else %}asc{% endif %}{% endif %}{% endspaceless %}"><a href="{% if ob %}{% set_url_param sort=ob.opposite %}{% else %}{% set_url_param sort=column.name %}{% endif %}">{{ column.header }}</a></th>
+ {% endwith %}
+ {% else %}
+ <th>{{ column.header }}</th>
+ {% endif %}
+ {% endfor %}
+ </tr>
+ </thead>
+ <tbody>
+ {% for row in table.page.object_list|default:table.rows %} {# support pagination #}
+ <tr class="{% cycle "odd" "even" %}">
+ {% for cell in row %}
+ <td>{{ cell }}</td>
+ {% endfor %}
+ </tr>
+ {% empty %}
+ {% if table.empty_text %}
+ <tr><td colspan="{{ table.columns|length }}">{{ table.empty_text }}</td></tr>
+ {% endif %}
+ {% endfor %}
+ </tbody>
+ </table>
+ {% if table.page %}
+ <ul class="pagination">
+ {% if table.page.has_previous %}
+ <li class="previous"><a href="{% set_url_param page=table.page.previous_page_number %}">Previous</a></li>
+ {% endif %}
+ <li class="current">Page {{ table.page.number }} of {{ table.paginator.num_pages }}</li>
+ {% if table.page.has_next %}
+ <li class="next"><a href="{% set_url_param page=table.page.next_page_number %}">Next</a></li>
+ {% endif %}
+ </ul>
+ </div>
+ {% endif %}
+ {% endspaceless %}
+
+.. note::
+
+ This is a complicated example, and is actually the template code that
+ ``{% render_table %}`` uses.
+
.. _template_tags:
Template tags
=============
-.. _template_tags.render_table:
+.. _template-tags.render_table:
render_table
------------
-If you want to render a table that provides support for sorting and pagination,
-you must use the ``{% render_table %}`` template tag. In this example ``table``
-is an instance of a :class:`django_tables.Table` that has been put into the
-template context:
+Renders a :class:`~django_tables2.tables.Table` object to HTML and includes as
+many features as possible.
+
+Sample usage:
.. code-block:: django
- {% load django_tables %}
+ {% load django_tables2 %}
{% render_table table %}
+This tag temporarily modifies the :class:`.Table` object while it is being
+rendered. It adds a ``request`` attribute to the table, which allows
+:class:`Column` objects to have access to a ``RequestContext``. See
+:class:`.TemplateColumn` for an example.
+
+This tag requires that the template in which it's rendered contains the
+``HttpRequest`` inside a ``request`` variable. This can be achieved by ensuring
+the ``TEMPLATE_CONTEXT_PROCESSORS`` setting contains
+``"django.core.context_processors.request"``. By default it is not included,
+and the setting itself is not even defined within your project's
+``settings.py``. To resolve this simply add the following to your
+``settings.py``:
+
+.. code-block:: python
+
+ TEMPLATE_CONTEXT_PROCESSORS = (
+ "django.contrib.auth.context_processors.auth",
+ "django.core.context_processors.debug",
+ "django.core.context_processors.i18n",
+ "django.core.context_processors.media",
+ "django.core.context_processors.static",
+ "django.contrib.messages.context_processors.messages",
+ "django.core.context_processors.request",
+ )
-.. _template_tags.set_url_param:
+
+.. _template-tags.set_url_param:
set_url_param
-------------
with your table (e.g. change the ordering), because you will need to create
urls with the appropriate queries.
-Let's assume we have the query-string
-``?search=pirates&sort=name&page=5`` and we want to update the ``sort``
-parameter:
+Let's assume we have the querystring ``?search=pirates&sort=name&page=5`` and
+we want to update the ``sort`` parameter:
.. code-block:: django
</table>
+Class Based Generic Mixins
+==========================
+
+Django 1.3 introduced `class based views`__ as a mechanism to reduce the
+repetition in view code. django-tables2 comes with a single class based view
+mixin: ``SingleTableMixin``. It makes it trivial to incorporate a table into a
+view/template, however it requires a few variables to be defined on the view:
+
+- ``table_class`` –- the table class to use, e.g. ``SimpleTable``
+- ``table_data`` (or ``get_table_data()``) -- the data used to populate the
+ table
+- ``context_table_name`` -- the name of template variable containing the table
+ object
+
+.. __: https://docs.djangoproject.com/en/1.3/topics/class-based-views/
+
+For example:
+
+.. code-block:: python
+
+ from django_tables2.views import SingleTableMixin
+ from django.generic.views.list import ListView
+
+
+ class Simple(models.Model):
+ first_name = models.CharField(max_length=200)
+ last_name = models.CharField(max_length=200)
+
+
+ class SimpleTable(tables.Table):
+ first_name = tables.Column()
+ last_name = tables.Column()
+
+
+ class MyTableView(SingleTableMixin, ListView):
+ model = Simple
+ table_class = SimpleTable
+
+
+The template could then be as simple as:
+
+.. code-block:: django
+
+ {% load django_tables2 %}
+ {% render_table table %}
+
+Such little code is possible due to the example above taking advantage of
+default values and ``SimpleTableMixin``'s eagarness at finding data sources
+when one isn't explicitly defined.
+
+.. note::
+
+ If you want more than one table on a page, at the moment the simplest way
+ to do it is to use ``SimpleTableMixin`` for one table, and write the
+ boilerplate for the other yourself in ``get_context_data()``. Obviously
+ this isn't particularly elegant, and as such will hopefully be resolved in
+ the future.
+
+
+Table Mixins
+============
+
+It's possible to create a mixin for a table that overrides something, however
+unless it itself is a subclass of :class:`.Table` class
+variable instances of :class:`.Column` will **not** be added to the class which is using the mixin.
+
+Example::
+
+ >>> class UselessMixin(object):
+ ... extra = tables.Column()
+ ...
+ >>> class TestTable(UselessMixin, tables.Table):
+ ... name = tables.Column()
+ ...
+ >>> TestTable.base_columns.keys()
+ ['name']
+
+To have a mixin contribute a column, it needs to be a subclass of
+:class:`~django_tables2.tables.Table`. With this in mind the previous example
+*should* have been written as follows::
+
+ >>> class UsefulMixin(tables.Table):
+ ... extra = tables.Column()
+ ...
+ >>> class TestTable(UsefulMixin, tables.Table):
+ ... name = tables.Column()
+ ...
+ >>> TestTable.base_columns.keys()
+ ['extra', 'name']
+
+
+Tables for models
+=================
+
+Most of the time you'll probably be making tables to display queryset data, and
+writing such tables involves a lot of duplicate code, e.g.::
+
+ >>> class Person(models.Model):
+ ... first_name = models.CharField(max_length=200)
+ ... last_name = models.CharField(max_length=200)
+ ... user = models.ForeignKey("auth.User")
+ ... dob = models.DateField()
+ ...
+ >>> class PersonTable(tables.Table):
+ ... first_name = tables.Column()
+ ... last_name = tables.Column()
+ ... user = tables.Column()
+ ... dob = tables.Column()
+ ...
+
+Often a table will become quite complex after time, e.g. `table.render_foo`_,
+changing ``verbose_name`` on columns, or adding an extra
+:class:`~.CheckBoxColumn`.
+
+``django-tables2`` offers the :attr:`.Table.Meta.model` option to ease the pain.
+The ``model`` option causes the table automatically generate columns for the
+fields in the model. This means that the above table could be re-written as
+follows::
+
+ >>> class PersonTable(tables.Table):
+ ... class Meta:
+ ... model = Person
+ ...
+ >>> PersonTable.base_columns.keys()
+ ['first_name', 'last_name', 'user', 'dob']
+
+If you want to customise one of the columns, simply define it the way you would
+normally::
+
+ >>> from django_tables2 import A
+ >>> class PersonTable(tables.Table):
+ ... user = tables.LinkColumn("admin:auth_user_change", args=[A("user.pk")])
+ ...
+ ... class Meta:
+ ... model = Person
+ ...
+ >>> PersonTable.base_columns.keys()
+ ['first_name', 'last_name', 'dob', 'user']
+
+It's not immediately obvious but if you look carefully you'll notice that the
+order of the fields has now changed -- ``user`` is now last, rather than
+``dob``. This follows the same behaviour of Django's model forms, and can be
+fixed in a similar way -- the :attr:`.Table.Meta.sequence` option::
+
+ >>> class PersonTable(tables.Table):
+ ... user = tables.LinkColumn("admin:auth_user_change", args=[A("user.pk")])
+ ...
+ ... class Meta:
+ ... model = Person
+ ... sequence = ("first_name", "last_name", "user", "dob")
+ ...
+ >>> PersonTable.base_columns.keys()
+ ['first_name', 'last_name', 'user', 'dob']
+
+… or use a shorter approach that makes use of the special ``"..."`` item::
+
+ >>> class PersonTable(tables.Table):
+ ... user = tables.LinkColumn("admin:auth_user_change", args=[A("user.pk")])
+ ...
+ ... class Meta:
+ ... model = Person
+ ... sequence = ("...", "dob")
+ ...
+ >>> PersonTable.base_columns.keys()
+ ['first_name', 'last_name', 'user', 'dob']
+
+
API Reference
=============
-:class:`Table` Objects:
-------------------------
+:class:`Accessor` Objects:
+--------------------------
-.. autoclass:: django_tables.tables.Table
+.. autoclass:: django_tables2.utils.Accessor
:members:
-:class:`TableOptions` Objects:
+:class:`Table` Objects:
+-----------------------
+
+.. autoclass:: django_tables2.tables.Table
+
+
+:class:`Table.Meta` Objects:
+----------------------------
+
+.. class:: Table.Meta
+
+ Provides a way to define *global* settings for table, as opposed to
+ defining them for each instance.
+
+ .. attribute:: attrs
+
+ Allows custom HTML attributes to be specified which will be added to
+ the ``<table>`` tag of any table rendered via
+ :meth:`~django_tables2.tables.Table.as_html` or the
+ :ref:`template-tags.render_table` template tag.
+
+ :type: ``dict``
+ :default: ``{}``
+
+ This is typically used to enable a theme for a table (which is done by
+ adding a CSS class to the ``<table>`` element). i.e.::
+
+ class SimpleTable(tables.Table):
+ name = tables.Column()
+
+ class Meta:
+ attrs = {"class": "paleblue"}
+
+ .. note::
+
+ This functionality is also available via the ``attrs`` keyword
+ argument to a table's constructor.
+
+ .. attribute:: empty_text
+
+ Defines the text to display when the table has no rows.
+
+ :type: ``string``
+ :default: ``None``
+
+ If the table is empty and ``bool(empty_text)`` is ``True``, a row is
+ displayed containing ``empty_text``. This is allows a message such as
+ *There are currently no FOO.* to be displayed.
+
+ .. note::
+
+ This functionality is also available via the ``empty_text`` keyword
+ argument to a table's constructor.
+
+ .. attribute:: exclude
+
+ Defines which columns should be excluded from the table. This is useful
+ in subclasses to exclude columns in a parent.
+
+ :type: tuple of ``string`` objects
+ :default: ``()``
+
+ Example::
+
+ >>> class Person(tables.Table):
+ ... first_name = tables.Column()
+ ... last_name = tables.Column()
+ ...
+ >>> Person.base_columns
+ {'first_name': <django_tables2.columns.Column object at 0x10046df10>,
+ 'last_name': <django_tables2.columns.Column object at 0x10046d8d0>}
+ >>> class ForgetfulPerson(Person):
+ ... class Meta:
+ ... exclude = ("last_name", )
+ ...
+ >>> ForgetfulPerson.base_columns
+ {'first_name': <django_tables2.columns.Column object at 0x10046df10>}
+
+ .. note::
+
+ This functionality is also available via the ``exclude`` keyword
+ argument to a table's constructor.
+
+ However, unlike some of the other ``Meta`` options, providing the
+ ``exclude`` keyword to a table's constructor **won't override** the
+ ``Meta.exclude``. Instead, it will be effectively be *added*
+ to it. i.e. you can't use the constructor's ``exclude`` argument to
+ *undo* an exclusion.
+
+ .. attribute:: model
+
+ A model to inspect and automatically create corresponding columns.
+
+ :type: Django model
+ :default: ``None``
+
+ This option allows a Django model to be specified to cause the table to
+ automatically generate columns that correspond to the fields in a
+ model.
+
+ .. attribute:: order_by
+
+ The default ordering. e.g. ``('name', '-age')``. A hyphen ``-`` can be
+ used to prefix a column name to indicate *descending* order.
+
+ :type: ``tuple``
+ :default: ``()``
+
+ .. note::
+
+ This functionality is also available via the ``order_by`` keyword
+ argument to a table's constructor.
+
+ .. attribute:: sequence
+
+ The sequence of the table columns. This allows the default order of
+ columns (the order they were defined in the Table) to be overridden.
+
+ :type: any iterable (e.g. ``tuple`` or ``list``)
+ :default: ``()``
+
+ The special item ``"..."`` can be used as a placeholder that will be
+ replaced with all the columns that weren't explicitly listed. This
+ allows you to add columns to the front or back when using inheritence.
+
+ Example::
+
+ >>> class Person(tables.Table):
+ ... first_name = tables.Column()
+ ... last_name = tables.Column()
+ ...
+ ... class Meta:
+ ... sequence = ("last_name", "...")
+ ...
+ >>> Person.base_columns.keys()
+ ['last_name', 'first_name']
+
+ The ``"..."`` item can be used at most once in the sequence value. If
+ it's not used, every column *must* be explicitly included. e.g. in the
+ above example, ``sequence = ("last_name", )`` would be **invalid**
+ because neither ``"..."`` or ``"first_name"`` were included.
+
+ .. note::
+
+ This functionality is also available via the ``sequence`` keyword
+ argument to a table's constructor.
+
+ .. attribute:: sortable
+
+ Whether columns are by default sortable, or not. i.e. the fallback for
+ value for a column's sortable value.
+
+ :type: ``bool``
+ :default: ``True``
+
+ If the ``Table`` and ``Column`` don't specify a value, a column's
+ ``sortable`` value will fallback to this. object specify. This provides
+ an easy mechanism to disable sorting on an entire table, without adding
+ ``sortable=False`` to each ``Column`` in a ``Table``.
+
+ .. note::
+
+ This functionality is also available via the ``sortable`` keyword
+ argument to a table's constructor.
+
+
+:class:`TableData` Objects:
------------------------------
-.. autoclass:: django_tables.tables.TableOptions
- :members:
+.. autoclass:: django_tables2.tables.TableData
+ :members: __init__, order_by, __getitem__, __len__
:class:`Column` Objects:
------------------------
-.. autoclass:: django_tables.columns.Column
- :members: __init__, default, render
+.. autoclass:: django_tables2.columns.Column
-:class:`Columns` Objects
-------------------------
+:class:`CheckBoxColumn` Objects:
+--------------------------------
+
+.. autoclass:: django_tables2.columns.CheckBoxColumn
+ :members:
+
+
+:class:`LinkColumn` Objects:
+----------------------------
+
+.. autoclass:: django_tables2.columns.LinkColumn
+ :members:
-.. autoclass:: django_tables.columns.Columns
- :members: __init__, all, items, names, sortable, visible, __iter__,
+
+:class:`TemplateColumn` Objects:
+--------------------------------
+
+.. autoclass:: django_tables2.columns.TemplateColumn
+ :members:
+
+
+:class:`BoundColumns` Objects
+-----------------------------
+
+.. autoclass:: django_tables2.columns.BoundColumns
+ :members: all, items, sortable, visible, __iter__,
__contains__, __len__, __getitem__
:class:`BoundColumn` Objects
----------------------------
-.. autoclass:: django_tables.columns.BoundColumn
- :members: __init__, table, column, name, accessor, default, formatter,
- sortable, verbose_name, visible
+.. autoclass:: django_tables2.columns.BoundColumn
+ :members:
-:class:`Rows` Objects
----------------------
+:class:`BoundRows` Objects
+--------------------------
-.. autoclass:: django_tables.rows.Rows
- :members: __init__, all, page, __iter__, __len__, count, __getitem__
+.. autoclass:: django_tables2.rows.BoundRows
+ :members: __iter__, __len__, count
:class:`BoundRow` Objects
-------------------------
-.. autoclass:: django_tables.rows.BoundRow
- :members: __init__, __getitem__, __contains__, __iter__, record, table
+.. autoclass:: django_tables2.rows.BoundRow
+ :members: __getitem__, __contains__, __iter__, record, table
:class:`AttributeDict` Objects
------------------------------
-.. autoclass:: django_tables.utils.AttributeDict
+.. autoclass:: django_tables2.utils.AttributeDict
:members:
:class:`OrderBy` Objects
------------------------
-.. autoclass:: django_tables.utils.OrderBy
+.. autoclass:: django_tables2.utils.OrderBy
:members:
:class:`OrderByTuple` Objects
-----------------------------
-.. autoclass:: django_tables.utils.OrderByTuple
- :members: __contains__, __getitem__, __unicode__
+.. autoclass:: django_tables2.utils.OrderByTuple
+ :members: __unicode__, __contains__, __getitem__, cmp
Glossary
.. glossary::
+ accessor
+ Refers to an :class:`~django_tables2.utils.Accessor` object
+
+ bare orderby
+ The non-prefixed form of an :class:`~django_tables2.utils.OrderBy`
+ object. Typically the bare form is just the ascending form.
+
+ Example: ``age`` is the bare form of ``-age``
+
+ column name
+ The name given to a column. In the follow example, the *column name* is
+ ``age``.
+
+ .. code-block:: python
+
+ class SimpleTable(tables.Table):
+ age = tables.Column()
+
table
The traditional concept of a table. i.e. a grid of rows and columns
containing data.
+
+ view
+ A Django view.
+
+ record
+ A single Python object used as the data for a single row.
+
+ render
+ The act of serialising a :class:`~django_tables2.tables.Table` into
+ HTML.
+
+ template
+ A Django template.
+
+ table data
+ An interable of :term:`records <record>` that
+ :class:`~django_tables2.tables.Table` uses to populate its rows.