Merge remote-tracking branch 'jthurner/docs-fix-toc'
Conflicts: docs/index.rst
This commit is contained in:
commit
d2ed593abe
1269
docs/index.rst
1269
docs/index.rst
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,30 @@
|
||||||
|
.. _accessors:
|
||||||
|
|
||||||
|
Specifying alternative data for a column
|
||||||
|
========================================
|
||||||
|
|
||||||
|
Each column has a "key" that describes which value to pull from each record to
|
||||||
|
populate the column's cells. By default, this key is just the name given to the
|
||||||
|
column, but it can be changed to allow foreign key traversal or other complex
|
||||||
|
cases.
|
||||||
|
|
||||||
|
To reduce ambiguity, rather than calling it a "key", it's been given the
|
||||||
|
special name "accessor".
|
||||||
|
|
||||||
|
Accessors are just dotted paths that describe how an object should be traversed
|
||||||
|
to reach a specific value. To demonstrate how they work we'll use them
|
||||||
|
directly::
|
||||||
|
|
||||||
|
>>> from django_tables2 import A
|
||||||
|
>>> data = {"abc": {"one": {"two": "three"}}}
|
||||||
|
>>> A("abc.one.two").resolve(data)
|
||||||
|
"three"
|
||||||
|
|
||||||
|
Dots represent a relationships, and are attempted in this order:
|
||||||
|
|
||||||
|
1. Dictionary lookup ``a[b]``
|
||||||
|
2. Attribute lookup ``a.b``
|
||||||
|
3. List index lookup ``a[int(b)]``
|
||||||
|
|
||||||
|
Then, if the value is callable, it is called and the result is used.
|
||||||
|
|
|
@ -0,0 +1,322 @@
|
||||||
|
API Reference
|
||||||
|
=============
|
||||||
|
|
||||||
|
`.Accessor` (`.A`)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
.. autoclass:: django_tables2.utils.Accessor
|
||||||
|
|
||||||
|
|
||||||
|
`.RequestConfig`
|
||||||
|
----------------
|
||||||
|
|
||||||
|
.. autoclass:: django_tables2.config.RequestConfig
|
||||||
|
|
||||||
|
|
||||||
|
`.Table`
|
||||||
|
--------
|
||||||
|
|
||||||
|
.. autoclass:: django_tables2.tables.Table
|
||||||
|
:members: paginate, as_html
|
||||||
|
|
||||||
|
|
||||||
|
`.Table.Meta`
|
||||||
|
-------------
|
||||||
|
|
||||||
|
.. 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:`.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"}
|
||||||
|
|
||||||
|
.. versionadded:: 0.15.0
|
||||||
|
|
||||||
|
It's possible to use callables to create *dynamic* values. A few caveats:
|
||||||
|
|
||||||
|
- It's not supported for ``dict`` keys, i.e. only values.
|
||||||
|
- All values will be resolved on table instantiation.
|
||||||
|
|
||||||
|
Consider this example where a unique ``id`` is given to each instance
|
||||||
|
of the table::
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
counter = itertools.count()
|
||||||
|
|
||||||
|
class UniqueIdTable(tables.Table):
|
||||||
|
name = tables.Column()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
attrs = {"id": lambda: "table_%d" % next(counter)}
|
||||||
|
|
||||||
|
.. 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: `unicode`
|
||||||
|
: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 `unicode`
|
||||||
|
: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 `.Table.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:: fields
|
||||||
|
|
||||||
|
Used in conjunction with `~.Table.Meta.model`, specifies which fields
|
||||||
|
should have columns in the table.
|
||||||
|
|
||||||
|
:type: tuple of `unicode` or `None`
|
||||||
|
:default: `None`
|
||||||
|
|
||||||
|
If `None`, all fields are used, otherwise only those named.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
# models.py
|
||||||
|
class Person(models.Model):
|
||||||
|
first_name = models.CharField(max_length=200)
|
||||||
|
last_name = models.CharField(max_length=200)
|
||||||
|
|
||||||
|
# tables.py
|
||||||
|
class PersonTable(tables.Table):
|
||||||
|
class Meta:
|
||||||
|
model = Person
|
||||||
|
fields = ("first_name", )
|
||||||
|
|
||||||
|
.. 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:: orderable
|
||||||
|
|
||||||
|
Default value for column's *orderable* attribute.
|
||||||
|
|
||||||
|
:type: `bool`
|
||||||
|
:default: `True`
|
||||||
|
|
||||||
|
If the table and column don't specify a value, a column's
|
||||||
|
``orderable`` value will fallback to this. object specify. This
|
||||||
|
provides an easy mechanism to disable ordering on an entire table,
|
||||||
|
without adding ``orderable=False`` to each column in a table.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This functionality is also available via the ``orderable`` keyword
|
||||||
|
argument to a table's constructor.
|
||||||
|
|
||||||
|
.. attribute:: template
|
||||||
|
|
||||||
|
The default template to use when rendering the table.
|
||||||
|
|
||||||
|
:type: `unicode`
|
||||||
|
:default: ``"django_tables2/table.html"``
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This functionality is also available via the *template* keyword
|
||||||
|
argument to a table's constructor.
|
||||||
|
|
||||||
|
|
||||||
|
.. attribute:: localize
|
||||||
|
|
||||||
|
Specifies which fields should be localized in the table.
|
||||||
|
Read :ref:`localization-control` for more information.
|
||||||
|
|
||||||
|
:type: tuple of `unicode`
|
||||||
|
:default: empty tuple
|
||||||
|
|
||||||
|
|
||||||
|
.. attribute:: unlocalize
|
||||||
|
|
||||||
|
Specifies which fields should be unlocalized in the table.
|
||||||
|
Read :ref:`localization-control` for more information.
|
||||||
|
|
||||||
|
:type: tuple of `unicode`
|
||||||
|
:default: empty tuple
|
||||||
|
|
||||||
|
|
||||||
|
`.BooleanColumn`
|
||||||
|
----------------
|
||||||
|
|
||||||
|
.. autoclass:: django_tables2.columns.BooleanColumn
|
||||||
|
|
||||||
|
|
||||||
|
`.Column`
|
||||||
|
---------
|
||||||
|
|
||||||
|
.. autoclass:: django_tables2.columns.Column
|
||||||
|
|
||||||
|
|
||||||
|
`.CheckBoxColumn`
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
.. autoclass:: django_tables2.columns.CheckBoxColumn
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
`.DateColumn`
|
||||||
|
-------------
|
||||||
|
|
||||||
|
.. autoclass:: django_tables2.columns.DateColumn
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
`.DateTimeColumn`
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
.. autoclass:: django_tables2.columns.DateTimeColumn
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
`.EmailColumn`
|
||||||
|
--------------
|
||||||
|
|
||||||
|
.. autoclass:: django_tables2.columns.EmailColumn
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
`.FileColumn`
|
||||||
|
-------------
|
||||||
|
|
||||||
|
.. autoclass:: django_tables2.columns.FileColumn
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
`.LinkColumn`
|
||||||
|
-------------
|
||||||
|
|
||||||
|
.. autoclass:: django_tables2.columns.LinkColumn
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
`.TemplateColumn`
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
.. autoclass:: django_tables2.columns.TemplateColumn
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
`.URLColumn`
|
||||||
|
------------
|
||||||
|
|
||||||
|
.. autoclass:: django_tables2.columns.URLColumn
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
See :doc:`internal` for internal classes.
|
|
@ -0,0 +1,18 @@
|
||||||
|
.. _builtin-columns:
|
||||||
|
|
||||||
|
Built-in columns
|
||||||
|
================
|
||||||
|
|
||||||
|
For common use-cases the following columns are included:
|
||||||
|
|
||||||
|
- `.BooleanColumn` -- renders boolean values
|
||||||
|
- `.Column` -- generic column
|
||||||
|
- `.CheckBoxColumn` -- renders checkbox form inputs
|
||||||
|
- `.DateColumn` -- date formatting
|
||||||
|
- `.DateTimeColumn` -- datetime formatting in the local timezone
|
||||||
|
- `.FileColumn` -- renders files as links
|
||||||
|
- `.EmailColumn` -- renders ``<a href="mailto:...">`` tags
|
||||||
|
- `.LinkColumn` -- renders ``<a href="...">`` tags (compose a django url)
|
||||||
|
- `.TemplateColumn` -- renders template code
|
||||||
|
- `.URLColumn` -- renders ``<a href="...">`` tags (absolute url)
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
.. _column-attributes:
|
||||||
|
|
||||||
|
Column attributes
|
||||||
|
=================
|
||||||
|
|
||||||
|
Column attributes can be specified using the `dict` with specific keys.
|
||||||
|
The dict defines HTML attributes for one of more elements within the column.
|
||||||
|
Depending on the column, different elements are supported, however ``th``,
|
||||||
|
``td``, and ``cell`` are supported universally.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
|
||||||
|
.. sourcecode:: python
|
||||||
|
|
||||||
|
>>> import django_tables2 as tables
|
||||||
|
>>>
|
||||||
|
>>> class SimpleTable(tables.Table):
|
||||||
|
... name = tables.Column(attrs={"th": {"id": "foo"}})
|
||||||
|
...
|
||||||
|
>>> SimpleTable(data).as_html()
|
||||||
|
"{snip}<thead><tr><th id="foo" class="name">{snip}<tbody><tr><td class="name">{snip}"
|
||||||
|
|
||||||
|
|
||||||
|
``th`` and ``td`` are special cases because they're extended during rendering
|
||||||
|
to add the column name as a class. This is done to make writing CSS easier.
|
||||||
|
Have a look at each column's API reference to find which elements are
|
||||||
|
supported.
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
.. _column-headers:
|
||||||
|
|
||||||
|
Customising column headings
|
||||||
|
===========================
|
||||||
|
|
||||||
|
The header cell for each column comes from `~.Column.header`. By default this
|
||||||
|
method returns `~.Column.verbose_name`, falling back to the titlised attribute
|
||||||
|
name of the column in the table class.
|
||||||
|
|
||||||
|
When using queryset data and a verbose name hasn't been explicitly
|
||||||
|
defined for a column, the corresponding model field's verbose name will be
|
||||||
|
used.
|
||||||
|
|
||||||
|
Consider the following:
|
||||||
|
|
||||||
|
>>> class Person(models.Model):
|
||||||
|
... first_name = models.CharField(verbose_name='model verbose name', max_length=200)
|
||||||
|
... last_name = models.CharField(max_length=200)
|
||||||
|
... region = models.ForeignKey('Region')
|
||||||
|
...
|
||||||
|
>>> class Region(models.Model):
|
||||||
|
... name = models.CharField(max_length=200)
|
||||||
|
...
|
||||||
|
>>> 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'].header
|
||||||
|
u'Model Verbose Name'
|
||||||
|
>>> table.columns['ln'].header
|
||||||
|
u'Last Name'
|
||||||
|
>>> table.columns['region_name'].header
|
||||||
|
u'Name'
|
||||||
|
|
||||||
|
As you can see in the last example (region name), the results are not always
|
||||||
|
desirable when an accessor is used to cross relationships. To get around this
|
||||||
|
be careful to define `.Column.verbose_name`.
|
|
@ -0,0 +1,142 @@
|
||||||
|
.. _custom-rendering:
|
||||||
|
|
||||||
|
Custom rendering
|
||||||
|
================
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
.. _table.render_foo:
|
||||||
|
|
||||||
|
:meth:`Table.render_FOO` methods
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
To change how a column is rendered, implement a ``render_FOO`` method on the
|
||||||
|
table (where ``FOO`` is the :term:`column name`). This approach is suitable if
|
||||||
|
you have a one-off change that you don't want to use in multiple tables.
|
||||||
|
|
||||||
|
Supported keyword arguments include:
|
||||||
|
|
||||||
|
- ``record`` -- the entire record for the row from the :term:`table data`
|
||||||
|
- ``value`` -- the value for the cell retrieved from the :term:`table data`
|
||||||
|
- ``column`` -- the `.Column` object
|
||||||
|
- ``bound_column`` -- the `.BoundColumn` object
|
||||||
|
- ``bound_row`` -- the `.BoundRow` object
|
||||||
|
- ``table`` -- alias for ``self``
|
||||||
|
|
||||||
|
Here's an example where the first column displays the current row number::
|
||||||
|
|
||||||
|
>>> import django_tables2 as tables
|
||||||
|
>>> import itertools
|
||||||
|
>>> class SimpleTable(tables.Table):
|
||||||
|
... row_number = tables.Column(empty_values=())
|
||||||
|
... id = tables.Column()
|
||||||
|
... age = tables.Column()
|
||||||
|
...
|
||||||
|
... def __init__(self, *args, **kwargs):
|
||||||
|
... super(SimpleTable, self).__init__(*args, **kwargs)
|
||||||
|
... self.counter = itertools.count()
|
||||||
|
...
|
||||||
|
... def render_row_number(self):
|
||||||
|
... return 'Row %d' % next(self.counter)
|
||||||
|
...
|
||||||
|
... def render_id(self, value):
|
||||||
|
... return '<%s>' % value
|
||||||
|
...
|
||||||
|
>>> table = SimpleTable([{'age': 31, 'id': 10}, {'age': 34, 'id': 11}])
|
||||||
|
>>> for cell in table.rows[0]:
|
||||||
|
... print cell
|
||||||
|
...
|
||||||
|
Row 0
|
||||||
|
<10>
|
||||||
|
31
|
||||||
|
|
||||||
|
Python's `inspect.getargspec` is used to only pass the arguments declared by the
|
||||||
|
function. This means it's not necessary to add a catch all (``**``) keyword
|
||||||
|
argument.
|
||||||
|
|
||||||
|
.. important::
|
||||||
|
|
||||||
|
`render` methods are *only* called if the value for a cell is determined to
|
||||||
|
be not an :term:`empty value`. When a value is in `.Column.empty_values`,
|
||||||
|
a default value is rendered instead (both `.Column.render` and
|
||||||
|
``Table.render_FOO`` are skipped).
|
||||||
|
|
||||||
|
.. _subclassing-column:
|
||||||
|
|
||||||
|
Subclassing `.Column`
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Defining a column subclass allows functionality to be reused across tables.
|
||||||
|
Columns have a `render` method that behaves the same as :ref:`table.render_foo`
|
||||||
|
methods on tables::
|
||||||
|
|
||||||
|
>>> import django_tables2 as tables
|
||||||
|
>>>
|
||||||
|
>>> class UpperColumn(tables.Column):
|
||||||
|
... def render(self, value):
|
||||||
|
... return value.upper()
|
||||||
|
...
|
||||||
|
>>> class Example(tables.Table):
|
||||||
|
... normal = tables.Column()
|
||||||
|
... upper = UpperColumn()
|
||||||
|
...
|
||||||
|
>>> data = [{'normal': 'Hi there!',
|
||||||
|
... 'upper': 'Hi there!'}]
|
||||||
|
...
|
||||||
|
>>> table = Example(data)
|
||||||
|
>>> table.as_html()
|
||||||
|
u'<table><thead><tr><th>Normal</th><th>Upper</th></tr></thead><tbody><tr><td>Hi there!</td><td>HI THERE!</td></tr></tbody></table>\n'
|
||||||
|
|
||||||
|
See :ref:`table.render_foo` for a list of arguments that can be accepted.
|
||||||
|
|
||||||
|
For complicated columns, you may want to return HTML from the
|
||||||
|
:meth:`~Column.render` method. This is fine, but be sure to mark the string as
|
||||||
|
safe to avoid it being escaped::
|
||||||
|
|
||||||
|
>>> from django.utils.safestring import mark_safe
|
||||||
|
>>> from django.utils.html import escape
|
||||||
|
>>>
|
||||||
|
>>> class ImageColumn(tables.Column):
|
||||||
|
... def render(self, value):
|
||||||
|
... return mark_safe('<img src="/media/img/%s.jpg" />'
|
||||||
|
... % escape(value))
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
.. _css:
|
||||||
|
|
||||||
|
CSS
|
||||||
|
---
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
.. sourcecode:: python
|
||||||
|
|
||||||
|
>>> import django_tables2 as tables
|
||||||
|
>>> class SimpleTable(tables.Table):
|
||||||
|
... id = tables.Column()
|
||||||
|
... age = tables.Column()
|
||||||
|
...
|
||||||
|
... class Meta:
|
||||||
|
... attrs = {'class': 'mytable'}
|
||||||
|
...
|
||||||
|
>>> table = SimpleTable()
|
||||||
|
>>> table.as_html()
|
||||||
|
'<table class="mytable">...'
|
||||||
|
|
||||||
|
.. _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
|
||||||
|
`.Table` subclass into your own template, and render it yourself.
|
||||||
|
|
||||||
|
Have a look at the ``django_tables2/table.html`` template for an example.
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
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.
|
||||||
|
|
||||||
|
The following view parameters are supported:
|
||||||
|
|
||||||
|
- ``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
|
||||||
|
- ``table_pagination`` -- pagination options to pass to `.RequestConfig`
|
||||||
|
|
||||||
|
.. __: https://docs.djangoproject.com/en/1.3/topics/class-based-views/
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
.. sourcecode:: python
|
||||||
|
|
||||||
|
from django_tables2 import SingleTableView
|
||||||
|
|
||||||
|
|
||||||
|
class Person(models.Model):
|
||||||
|
first_name = models.CharField(max_length=200)
|
||||||
|
last_name = models.CharField(max_length=200)
|
||||||
|
|
||||||
|
|
||||||
|
class PersonTable(tables.Table):
|
||||||
|
class Meta:
|
||||||
|
model = Simple
|
||||||
|
|
||||||
|
|
||||||
|
class PersonList(SingleTableView):
|
||||||
|
model = Person
|
||||||
|
table_class = PersonTable
|
||||||
|
|
||||||
|
|
||||||
|
The template could then be as simple as:
|
||||||
|
|
||||||
|
.. sourcecode:: django
|
||||||
|
|
||||||
|
{% load render_table from 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 need more than one table on a page, use `.SingleTableView` and use
|
||||||
|
`.get_context_data` to initialise the other tables and add them to the
|
||||||
|
context.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
You don't have to base your view on `ListView`, you're able to mix
|
||||||
|
`SingleTableMixin` directly.
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
Glossary
|
||||||
|
========
|
||||||
|
|
||||||
|
.. glossary::
|
||||||
|
|
||||||
|
accessor
|
||||||
|
Refers to an `.Accessor` object
|
||||||
|
|
||||||
|
column name
|
||||||
|
The name given to a column. In the follow example, the *column name* is
|
||||||
|
``age``.
|
||||||
|
|
||||||
|
.. sourcecode:: python
|
||||||
|
|
||||||
|
class SimpleTable(tables.Table):
|
||||||
|
age = tables.Column()
|
||||||
|
|
||||||
|
empty value
|
||||||
|
An empty value is synonymous with "no value". Columns have an
|
||||||
|
``empty_values`` attribute that contains values that are considered
|
||||||
|
empty. It's a way to declare which values from the database correspond
|
||||||
|
to *null*/*blank*/*missing* etc.
|
||||||
|
|
||||||
|
order by alias
|
||||||
|
A prefixed column name that describes how a column should impact the
|
||||||
|
order of data within the table. This allows the implementation of how
|
||||||
|
a column affects ordering to be abstracted, which is useful (e.g. in
|
||||||
|
querystrings).
|
||||||
|
|
||||||
|
.. sourcecode:: python
|
||||||
|
|
||||||
|
class ExampleTable(tables.Table):
|
||||||
|
name = tables.Column(order_by=('first_name', 'last_name'))
|
||||||
|
|
||||||
|
In this example ``-name`` and ``name`` are valid order by aliases. In
|
||||||
|
a querystring you might then have ``?order=-name``.
|
||||||
|
|
||||||
|
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 `.Table` into
|
||||||
|
HTML.
|
||||||
|
|
||||||
|
template
|
||||||
|
A Django template.
|
||||||
|
|
||||||
|
table data
|
||||||
|
An interable of :term:`records <record>` that
|
||||||
|
`.Table` uses to populate its rows.
|
|
@ -0,0 +1,50 @@
|
||||||
|
.. _localization-control:
|
||||||
|
|
||||||
|
Controlling localization
|
||||||
|
========================
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
This functionality doesn't work in Django prior to version 1.3
|
||||||
|
|
||||||
|
Django_tables2 allows you to define which column of a table should or should not
|
||||||
|
be localized. For example you may want to use this feature in following use cases:
|
||||||
|
|
||||||
|
* You want to format some columns representing for example numeric values in the given locales
|
||||||
|
even if you don't enable `USE_L10N` in your settings file.
|
||||||
|
|
||||||
|
* You don't want to format primary key values in your table
|
||||||
|
even if you enabled `USE_L10N` in your settings file.
|
||||||
|
|
||||||
|
This control is done by using two filter functions in Django's `l10n` library
|
||||||
|
named `localize` and `unlocalize`. Check out Django docs about
|
||||||
|
:ref:`localization <django:format-localization>` for more information about them.
|
||||||
|
|
||||||
|
There are two ways of controling localization in your columns.
|
||||||
|
|
||||||
|
First one is setting the `~.Column.localize` attribute in your column definition
|
||||||
|
to `True` or `False`. Like so::
|
||||||
|
|
||||||
|
class PersonTable(tables.Table):
|
||||||
|
id = tables.Column(name="id", accessor="pk", localize=False)
|
||||||
|
class Meta:
|
||||||
|
model = Person
|
||||||
|
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
The default value of the `localize` attribute is `None` which means the formatting
|
||||||
|
of columns is dependant from the `USE_L10N` setting.
|
||||||
|
|
||||||
|
The second way is to define a `~.Table.Meta.localize` and/or `~.Table.Meta.unlocalize`
|
||||||
|
tuples in your tables Meta class (jutst like with `~.Table.Meta.fields`
|
||||||
|
or `~.Table.Meta.exclude`). You can do this like so::
|
||||||
|
|
||||||
|
class PersonTable(tables.Table):
|
||||||
|
id = tables.Column(accessor="pk")
|
||||||
|
value = tables.Column(accessor="some_numerical_field")
|
||||||
|
class Meta:
|
||||||
|
model = Person
|
||||||
|
unlocalize = ('id',)
|
||||||
|
localize = ('value',)
|
||||||
|
|
||||||
|
If you define the same column in both `localize` and `unlocalize` then the value
|
||||||
|
of this column will be "unlocalized" which means that `unlocalize` has higher precedence.
|
|
@ -0,0 +1,48 @@
|
||||||
|
.. _order-by-accessors:
|
||||||
|
|
||||||
|
Specifying alternative ordering for a column
|
||||||
|
============================================
|
||||||
|
|
||||||
|
When using queryset data, it's possible for a column to present a computed
|
||||||
|
value that doesn't correspond to a column in the database. In this situation
|
||||||
|
attempting to order the column will cause a database exception.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
# models.py
|
||||||
|
class Person(models.Model):
|
||||||
|
first_name = models.CharField(max_length=200)
|
||||||
|
family_name = models.CharField(max_length=200)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return u"%s %s" % (self.first_name, self.family_name)
|
||||||
|
|
||||||
|
# tables.py
|
||||||
|
class PersonTable(tables.Table):
|
||||||
|
name = tables.Column()
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
>>> table = PersonTable(Person.objects.all())
|
||||||
|
>>> table.order_by = "name"
|
||||||
|
>>> table.as_html()
|
||||||
|
...
|
||||||
|
FieldError: Cannot resolve keyword u'name' into field. Choices are: first_name, family_name
|
||||||
|
|
||||||
|
The solution is to declare which fields should be used when ordering on via the
|
||||||
|
``order_by`` argument::
|
||||||
|
|
||||||
|
# tables.py
|
||||||
|
class PersonTable(tables.Table):
|
||||||
|
name = tables.Column(order_by=("first_name", "family_name"))
|
||||||
|
|
||||||
|
Accessor syntax can be used for the values, but they must terminate on a model
|
||||||
|
field.
|
||||||
|
|
||||||
|
If ordering doesn't make sense for a particular column, it can be disabled via
|
||||||
|
the ``orderable`` argument::
|
||||||
|
|
||||||
|
class SimpleTable(tables.Table):
|
||||||
|
name = tables.Column()
|
||||||
|
actions = tables.Column(orderable=False)
|
|
@ -0,0 +1,24 @@
|
||||||
|
.. _pagination:
|
||||||
|
|
||||||
|
Pagination
|
||||||
|
==========
|
||||||
|
|
||||||
|
Pagination is easy, just call :meth:`.Table.paginate` and
|
||||||
|
pass in the current page number, e.g.
|
||||||
|
|
||||||
|
.. sourcecode:: python
|
||||||
|
|
||||||
|
def people_listing(request):
|
||||||
|
table = PeopleTable(Person.objects.all())
|
||||||
|
table.paginate(page=request.GET.get('page', 1), per_page=25)
|
||||||
|
return render(request, 'people_listing.html', {'table': table})
|
||||||
|
|
||||||
|
If you're using `.RequestConfig`, pass pagination options to the constructor,
|
||||||
|
e.g.:
|
||||||
|
|
||||||
|
.. sourcecode:: python
|
||||||
|
|
||||||
|
def people_listing(request):
|
||||||
|
table = PeopleTable(Person.objects.all())
|
||||||
|
RequestConfig(request, paginate={"per_page": 25}).configure(table)
|
||||||
|
return render(request, 'people_listing.html', {'table': table})
|
|
@ -0,0 +1,34 @@
|
||||||
|
.. _query-string-fields:
|
||||||
|
|
||||||
|
Querystring fields
|
||||||
|
==================
|
||||||
|
|
||||||
|
Tables pass data via the querystring to indicate ordering and pagination
|
||||||
|
preferences.
|
||||||
|
|
||||||
|
The names of the querystring variables are configurable via the options:
|
||||||
|
|
||||||
|
- ``order_by_field`` -- default: ``"sort"``
|
||||||
|
- ``page_field`` -- default: ``"page"``
|
||||||
|
- ``per_page_field`` -- default: ``"per_page"``, **note:** this field currently
|
||||||
|
isn't used by ``{% render_table %}``
|
||||||
|
|
||||||
|
Each of these can be specified in three places:
|
||||||
|
|
||||||
|
- ``Table.Meta.foo``
|
||||||
|
- ``Table(..., foo=...)``
|
||||||
|
- ``Table(...).foo = ...``
|
||||||
|
|
||||||
|
If you're using multiple tables on a single page, you'll want to prefix these
|
||||||
|
fields with a table-specific name. e.g.
|
||||||
|
|
||||||
|
.. sourcecode:: python
|
||||||
|
|
||||||
|
def people_listing(request):
|
||||||
|
config = RequestConfig(request)
|
||||||
|
table1 = PeopleTable(Person.objects.all(), prefix="1-") # prefix specified
|
||||||
|
table2 = PeopleTable(Person.objects.all(), prefix="2-") # prefix specified
|
||||||
|
config.configure(table1)
|
||||||
|
config.configure(table2)
|
||||||
|
return render(request, "people_listing.html",
|
||||||
|
{"table1": table1, "table2": table2})
|
|
@ -0,0 +1,21 @@
|
||||||
|
.. _swapping-columns:
|
||||||
|
|
||||||
|
Swapping the position of columns
|
||||||
|
================================
|
||||||
|
|
||||||
|
By default columns are positioned in the same order as they are declared,
|
||||||
|
however when mixing auto-generated columns (via `Table.Meta.model`) with
|
||||||
|
manually declared columns, the column sequence becomes ambiguous.
|
||||||
|
|
||||||
|
To resolve the ambiguity, columns sequence can be declared via the
|
||||||
|
`.Table.Meta.sequence` option::
|
||||||
|
|
||||||
|
class PersonTable(tables.Table):
|
||||||
|
selection = tables.CheckBoxColumn(accessor="pk", orderable=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Person
|
||||||
|
sequence = ("selection", "first_name", "last_name")
|
||||||
|
|
||||||
|
The special value ``"..."`` can be used to indicate that any omitted columns
|
||||||
|
should inserted at that location. As such it can be used at most once.
|
|
@ -0,0 +1,48 @@
|
||||||
|
.. _table-data:
|
||||||
|
|
||||||
|
Populating a table with data
|
||||||
|
============================
|
||||||
|
|
||||||
|
Tables are compatible with a range of input data structures. If you've seen the
|
||||||
|
tutorial you'll have seen a queryset being used, however any iterable that
|
||||||
|
supports :func:`len` and contains items that expose key-based accessed to
|
||||||
|
column values is fine.
|
||||||
|
|
||||||
|
An an example we'll demonstrate using list of dicts. When defining a table it's
|
||||||
|
necessary to declare each column. If your data matches the fields in a model,
|
||||||
|
columns can be declared automatically for you via the `Table.Meta.model`
|
||||||
|
option, but for non-queryset data you'll probably want to declare
|
||||||
|
them manually::
|
||||||
|
|
||||||
|
import django_tables2 as tables
|
||||||
|
|
||||||
|
data = [
|
||||||
|
{"name": "Bradley"},
|
||||||
|
{"name": "Stevie"},
|
||||||
|
]
|
||||||
|
|
||||||
|
class NameTable(tables.Table):
|
||||||
|
name = tables.Column()
|
||||||
|
|
||||||
|
table = NameTable(data)
|
||||||
|
|
||||||
|
You can use this technique to override columns that were automatically created
|
||||||
|
via `Table.Meta.model` too::
|
||||||
|
|
||||||
|
# models.py
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
class Person(models.Model):
|
||||||
|
name = models.CharField(max_length=200)
|
||||||
|
|
||||||
|
|
||||||
|
# tables.py
|
||||||
|
import django_tables2 as tables
|
||||||
|
from .models import Person
|
||||||
|
|
||||||
|
class PersonTable(tables.Table):
|
||||||
|
name = tables.Column(verbose_name="full name")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Person
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
Table Mixins
|
||||||
|
============
|
||||||
|
|
||||||
|
It's possible to create a mixin for a table that overrides something, however
|
||||||
|
unless it itself is a subclass of `.Table` class variable instances of
|
||||||
|
`.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
|
||||||
|
`~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']
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
.. _tables-for-models:
|
||||||
|
|
||||||
|
Tables for models
|
||||||
|
=================
|
||||||
|
|
||||||
|
If you build use tables to display `.QuerySet` data, rather than defining each
|
||||||
|
column manually in the table, the `.Table.Meta.model` option allows tables to
|
||||||
|
be dynamically created based on a model::
|
||||||
|
|
||||||
|
# models.py
|
||||||
|
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()
|
||||||
|
|
||||||
|
# tables.py
|
||||||
|
class PersonTable(tables.Table):
|
||||||
|
class Meta:
|
||||||
|
model = Person
|
||||||
|
|
||||||
|
This has a number of benefits:
|
||||||
|
|
||||||
|
- Less code, easier to write, more DRY
|
||||||
|
- Columns use the field's `~.models.Field.verbose_name`
|
||||||
|
- Specialised columns are used where possible (e.g. `.DateColumn` for a
|
||||||
|
`~.models.DateField`)
|
||||||
|
|
||||||
|
When using this approach, the following options are useful:
|
||||||
|
|
||||||
|
- `~.Table.Meta.sequence` -- reorder columns
|
||||||
|
- `~.Table.Meta.fields` -- specify model fields to *include*
|
||||||
|
- `~.Table.Meta.exclude` -- specify model fields to *exclude*
|
|
@ -0,0 +1,19 @@
|
||||||
|
Template filters
|
||||||
|
================
|
||||||
|
|
||||||
|
title
|
||||||
|
-----
|
||||||
|
|
||||||
|
String filter that performs title case conversion on a per-word basis, leaving
|
||||||
|
words containing upper-case letters alone.
|
||||||
|
|
||||||
|
.. sourcecode:: django
|
||||||
|
|
||||||
|
{{ "start 6PM"|title }} # Start 6PM
|
||||||
|
{{ "sTart 6pm"|title }} # sTart 6pm
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
Be careful when loading the ``django_tables2`` template library to not
|
||||||
|
in advertantly load ``title``. You should always use the
|
||||||
|
``{% load ... from ... %}`` syntax.
|
|
@ -0,0 +1,66 @@
|
||||||
|
.. _template_tags:
|
||||||
|
|
||||||
|
Template tags
|
||||||
|
=============
|
||||||
|
|
||||||
|
.. _template-tags.render_table:
|
||||||
|
|
||||||
|
render_table
|
||||||
|
------------
|
||||||
|
|
||||||
|
Renders a `~django_tables2.tables.Table` object to HTML and enables as
|
||||||
|
many features in the output as possible.
|
||||||
|
|
||||||
|
.. sourcecode:: django
|
||||||
|
|
||||||
|
{% load django_tables2 %}
|
||||||
|
{% render_table table %}
|
||||||
|
|
||||||
|
{# Alternatively a specific template can be used #}
|
||||||
|
{% render_table table "path/to/custom_table_template.html" %}
|
||||||
|
|
||||||
|
If the second argument (template path) is given, the template will be rendered
|
||||||
|
with a `.RequestContext` and the table will be in the variable ``table``.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This tag temporarily modifies the `.Table` object during rendering. A
|
||||||
|
``context`` attribute is added to the table, providing columns with access
|
||||||
|
to the current context for their own rendering (e.g. `.TemplateColumn`).
|
||||||
|
|
||||||
|
This tag requires that the template in which it's rendered contains the
|
||||||
|
`~.http.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 add the following to your ``settings.py``:
|
||||||
|
|
||||||
|
.. sourcecode:: python
|
||||||
|
|
||||||
|
from django.conf.global_settings import TEMPLATE_CONTEXT_PROCESSORS
|
||||||
|
TEMPLATE_CONTEXT_PROCESSORS += ('django.core.context_processors.request',)
|
||||||
|
|
||||||
|
|
||||||
|
.. _template-tags.querystring:
|
||||||
|
|
||||||
|
querystring
|
||||||
|
-----------
|
||||||
|
|
||||||
|
A utility that allows you to update a portion of the query-string without
|
||||||
|
overwriting the entire thing.
|
||||||
|
|
||||||
|
Let's assume we have the querystring ``?search=pirates&sort=name&page=5`` and
|
||||||
|
we want to update the ``sort`` parameter:
|
||||||
|
|
||||||
|
.. sourcecode:: django
|
||||||
|
|
||||||
|
{% querystring "sort"="dob" %} # ?search=pirates&sort=dob&page=5
|
||||||
|
{% querystring "sort"="" %} # ?search=pirates&page=5
|
||||||
|
{% querystring "sort"="" "search"="" %} # ?page=5
|
||||||
|
|
||||||
|
{% with "search" as key %} # supports variables as keys
|
||||||
|
{% querystring key="robots" %} # ?search=robots&page=5
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
This tag requires the ``django.core.context_processors.request`` context
|
||||||
|
processor, see :ref:`template-tags.render_table`.
|
|
@ -0,0 +1,97 @@
|
||||||
|
Tutorial
|
||||||
|
========
|
||||||
|
|
||||||
|
1. ``pip install django-tables2``
|
||||||
|
2. Add ``'django_tables2'`` to ``INSTALLED_APPS``
|
||||||
|
3. Add ``'django.core.context_processors.request'`` to ``TEMPLATE_CONTEXT_PROCESSORS``
|
||||||
|
|
||||||
|
We're going to run through creating a tutorial app. Let's start with a simple model::
|
||||||
|
|
||||||
|
# tutorial/models.py
|
||||||
|
class Person(models.Model):
|
||||||
|
name = models.CharField(verbose_name="full name")
|
||||||
|
|
||||||
|
Add some data so you have something to display in the table. Now write a view
|
||||||
|
to pass a ``Person`` queryset into a template::
|
||||||
|
|
||||||
|
# tutorial/views.py
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
def people(request):
|
||||||
|
return render(request, "people.html", {"people": Person.objects.all()})
|
||||||
|
|
||||||
|
Finally, implement the template:
|
||||||
|
|
||||||
|
.. sourcecode:: django
|
||||||
|
|
||||||
|
{# tutorial/templates/people.html #}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="{{ STATIC_URL }}django_tables2/themes/paleblue/css/screen.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% render_table people %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
Hook the view up in your URLs, and load the page, you should see:
|
||||||
|
|
||||||
|
.. figure:: /_static/tutorial.png
|
||||||
|
:align: center
|
||||||
|
:alt: An example table rendered using django-tables2
|
||||||
|
|
||||||
|
While simple, passing a queryset directly to ``{% render_table %}`` doesn't
|
||||||
|
allow for any customisation. For that, you must define a `.Table` class.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
# tutorial/tables.py
|
||||||
|
import django_tables2 as tables
|
||||||
|
from tutorial.models import Person
|
||||||
|
|
||||||
|
class PersonTable(tables.Table):
|
||||||
|
class Meta:
|
||||||
|
model = Person
|
||||||
|
# add class="paleblue" to <table> tag
|
||||||
|
attrs = {"class": "paleblue"}
|
||||||
|
|
||||||
|
|
||||||
|
You'll then need to instantiate and configure the table in the view, before
|
||||||
|
adding it to the context.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
# tutorial/views.py
|
||||||
|
from django.shortcuts import render
|
||||||
|
from django_tables2 import RequestConfig
|
||||||
|
from tutorial.models import Person
|
||||||
|
from tutorial.tables import PersonTable
|
||||||
|
|
||||||
|
def people(request):
|
||||||
|
table = PersonTable(Person.objects.all())
|
||||||
|
RequestConfig(request).configure(table)
|
||||||
|
return render(request, 'people.html', {'table': table})
|
||||||
|
|
||||||
|
Using `.RequestConfig` automatically pulls values from ``request.GET`` and
|
||||||
|
updates the table accordingly. This enables data ordering and pagination.
|
||||||
|
|
||||||
|
Rather than passing a queryset to ``{% render_table %}``, instead pass the
|
||||||
|
table.
|
||||||
|
|
||||||
|
.. sourcecode:: django
|
||||||
|
|
||||||
|
{% render_table table %}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
``{% render_table %}`` works best when it's used in a template that
|
||||||
|
contains the current request in the context as ``request``. The easiest way
|
||||||
|
to enable this, is to ensure that the ``TEMPLATE_CONTEXT_PROCESSORS``
|
||||||
|
setting contains ``"django.core.context_processors.request"``.
|
||||||
|
|
||||||
|
At this point you haven't actually customised anything, you've merely added the
|
||||||
|
boilerplate code that ``{% render_table %}`` does for you when given a
|
||||||
|
queryset. The remaining sections in this document describe how to change
|
||||||
|
various aspects of the table.
|
|
@ -0,0 +1,62 @@
|
||||||
|
Upgrading from django-tables Version 1
|
||||||
|
======================================
|
||||||
|
|
||||||
|
- Change your ``INSTALLLED_APPS`` entry from ``"django_tables.app"`` to
|
||||||
|
``"django_tables2"``.
|
||||||
|
|
||||||
|
- Change all your import references from ``django_tables`` to
|
||||||
|
``django_tables2``.
|
||||||
|
|
||||||
|
- Replace all references to the old ``MemoryTable`` and ``ModelTable``
|
||||||
|
classes with simply ``Table``.
|
||||||
|
|
||||||
|
- In your templates, load the ``django_tables2`` template library;
|
||||||
|
``{% load django_tables2 %}`` instead of ``{% load tables %}``.
|
||||||
|
|
||||||
|
- A table object is no longer iterable; rather than ``for row in table``,
|
||||||
|
instead you now do explicitly: ``for row in table.rows``.
|
||||||
|
|
||||||
|
- If you were using ``row.data`` to access a row's underlying data,
|
||||||
|
replace it with ``row.record`` instead.
|
||||||
|
|
||||||
|
- When declaring columns, replace the use of::
|
||||||
|
|
||||||
|
name_in_dataset = tables.Column(name="wanted_column_name")
|
||||||
|
|
||||||
|
with::
|
||||||
|
|
||||||
|
wanted_column_name = tables.Column(accessor="name_in_dataset")
|
||||||
|
|
||||||
|
- When declaring columns, replace the use of::
|
||||||
|
|
||||||
|
column_to_override = tables.Column(name="wanted_column_name", data="name_in_dataset")
|
||||||
|
|
||||||
|
with::
|
||||||
|
|
||||||
|
wanted_column_name = tables.Column(accessor="name_in_dataset")
|
||||||
|
|
||||||
|
and exclude ``column_to_override`` via the table meta data.
|
||||||
|
|
||||||
|
- When generating the link to order the column, instead of:
|
||||||
|
|
||||||
|
.. sourcecode:: django
|
||||||
|
|
||||||
|
{% set_url_param sort=column.name_toggled %}
|
||||||
|
|
||||||
|
use:
|
||||||
|
|
||||||
|
.. sourcecode:: django
|
||||||
|
|
||||||
|
{% querystring table.order_by_field=column.order_by_alias.next %}
|
||||||
|
|
||||||
|
- Replace:
|
||||||
|
|
||||||
|
.. sourcecode:: django
|
||||||
|
|
||||||
|
{{ column.is_ordered_reverse }} and {{ column.is_ordered_straight }}
|
||||||
|
|
||||||
|
with:
|
||||||
|
|
||||||
|
.. sourcecode:: django
|
||||||
|
|
||||||
|
{{ column.order_by.is_descending }} and {{ column.order_by.is_ascending }}
|
Loading…
Reference in New Issue