Tables

How do I customize the rendering of a table?

Table rendering can be customized on multiple levels. You pass a template with the template argument, which is either a template name or a Template object.

Customize the HTML attributes of the table tag via the attrs argument. See attrs.

To customize the row, see How do I customize the rendering of a row?

To customize the cell, see How do I customize the rendering of a cell?

To customize the rendering of the table, see table-as-div

How do you turn off pagination?

Specify page_size=None:

table = Table(
    auto__model=Album,
    page_size=None,
)
▼ Hide result

Or in the declarative style:

class MyTable(Table):
    name = Column()

    class Meta:
        page_size = None
▼ Hide result

How do I customize the rendering of a cell?

You can customize the Cell rendering in several ways:

  • You can modify the html attributes via cell__attrs. See attrs.

  • Use cell__template to specify a template. You can give a string and it will be interpreted as a template name, or you can pass a Template object.

  • Pass a url (or callable that returns a url) to cell__url to make the cell a link (see next question).

How do I create a column based on computed data (i.e. a column not based on an attribute of the row)?

Let’s say we have a model like this:

class Foo(models.Model):
    value = models.IntegerField()

And we want a computed column square that is the square of the value, then we can do:

table = Table(
    auto__model=Foo,
    columns__square=Column(
        # computed value:
        cell__value=lambda row, **_: row.value * row.value,
    )
)
▼ Hide result

or we could do:

Table(
    auto__model=Foo,
    columns__square=Column(
        attr='value',
        cell__format=lambda value, **_: value * value,
    )
)

This only affects the formatting when we render the cell value. Which might make more sense depending on your situation but for the simple case like we have here the two are equivalent.

How do I get iommi tables to understand my Django ModelField subclasses?

See Registrations.

How do I reorder columns?

By default the columns come in the order defined so if you have an explicit table defined, just move them around there. If the table is generated from a model definition, you can also move them in the model definition if you like, but that might not be a good idea. So to handle this case we can set the ordering on a column by giving it the after argument. Let’s start with a simple model:

class Foo(models.Model):
    a = models.IntegerField()
    b = models.IntegerField()
    c = models.IntegerField()

If we just do Table(auto__model=Foo) we’ll get the columns in the order a, b, c. But let’s say I want to put c first, then we can pass it the after value -1:

table = Table(auto__model=Foo, columns__c__after=-1)
▼ Hide result

-1 means the first, other numbers mean index. We can also put columns after another named column like so:

table = Table(auto__model=Foo, columns__c__after='a')
▼ Hide result

this will put the columns in the order a, c, b.

There is a special value LAST (import from iommi.declarative) to put something last in a list:

table = Table(auto__model=Foo, columns__a__after=LAST)
▼ Hide result

How do I enable searching/filter on columns?

Pass the value filter__include=True to the column, to enable searching in the advanced query language.

table = Table(
    auto__model=Album,
    columns__name__filter__include=True,
)

The filter namespace here is used to configure a Filter so you can configure the behavior of the searching by passing parameters here.

The filter__field namespace is used to configure the Field, so here you can pass any argument to Field here to customize it.

If you just want to have the filter available in the advanced query language, you can turn off the field in the generated form by passing filter__field__include=False:

▼ Hide result

How do I make a freetext search field?

If you want to filter based on a freetext query on one or more columns we’ve got a nice little feature for this:

table = Table(
    auto__model=Album,
    columns__name__filter=dict(
        freetext=True,
        include=True,
    ),
    columns__year__filter__freetext=True,
    columns__year__filter__include=True,
)

This will display one search box to search both year and name columns:

▼ Hide result

How do I customize HTML attributes, CSS classes or CSS style specifications?

The attrs namespace has special handling to make it easy to customize. There are three main cases:

First the straight forward case where a key/value pair is rendered in the output:

>>> from iommi.attrs import render_attrs
>>> from iommi.declarative.namespace import Namespace
>>> render_attrs(Namespace(foo='bar'))
' foo="bar"'

Then there’s a special handling for CSS classes:

>>> render_attrs(Namespace(class__foo=True, class__bar=True))
' class="bar foo"'

Note that the class names are sorted alphabetically on render.

Lastly there is the special handling of style:

>>> render_attrs(Namespace(style__font='Arial'))
' style="font: Arial"'

If you need to add a style with - in the name you have to do this:

>>> render_attrs(Namespace(**{'style__font-family': 'sans-serif'}))
' style="font-family: sans-serif"'

Everything together:

>>> render_attrs(
...     Namespace(
...         foo='bar',
...         class__foo=True,
...         class__bar=True,
...         style__font='Arial',
...         **{'style__font-family': 'serif'}
...     )
... )
' class="bar foo" foo="bar" style="font-family: serif; font: Arial"'

How do I customize the rendering of a row?

You can customize the row rendering in two ways:

  • You can modify the html attributes via row__attrs. See attrs.

  • Use row__template to specify a template. You can give a string and it will be interpreted as a template name, or you can pass a Template object.

In templates you can access the raw row via row. This would typically be one of your model objects. You can also access the cells of the table via cells. A naive template for a row would be <tr>{% for cell in cells %}<td>{{ cell }}{% endfor %}</tr>. You can access specific cells by their column names like {{ cells.artist }}.

To customize the cell, see How do I customize the rendering of a cell?

How do I customize the rendering of a header?

You can customize headers in two ways:

  • You can modify the html attributes via header__attrs. See attrs.

  • Use header__template to specify a template. You can give a string and it will be interpreted as a template name, or you can pass a Template object.

How do I turn off the header?

Set header__template to None.

How do I add fields to a table that is generated from a model?

See the question column-computed-data

How do I specify which columns to show?

Pass include=False to hide the column or include=True to show it. By default columns are shown, except the primary key column that is by default hidden. You can also pass a callable here like so:

Table(
    auto__model=Album,
    columns__name__include=
        lambda request, **_: request.GET.get('some_parameter') == 'hello!',
)

This will show the column name only if the GET parameter some_parameter is set to hello!.

To be more precise, include turns off the entire column. Sometimes you want to have the searching turned on, but disable the rendering of the column. To do this use the render_column parameter instead. This is useful to for example turn on filtering for a column, but not render it:

table = Table(
    auto__model=Album,
    columns__year__render_column=False,
    columns__year__filter__include=True,
)
▼ Hide result

Use auto__include, to specify the complete list of columns you want:

table = Table(
    auto__model=Album,
    auto__include=['name', 'artist'],
)
▼ Hide result

Instead of using auto__include, you can also use auto__exclude to just exclude the columns you don’t want:

table = Table(
    auto__model=Album,
    auto__exclude=['year'],
)
▼ Hide result

There is also a config option default_included which is by default True, which is where iommi’s default behavior of showing all columns comes from. If you set it to False columns are now opt-in:

table = Table(
    auto__model=Album,
    auto__default_included=False,
    # Turn on only the name column
    columns__name__include=True,
)
▼ Hide result

How do I access table data programmatically (like for example to dump to json)?

Here’s a simple example that prints a table to stdout:

def print_table(table):
    for row in table.cells_for_rows():
        for cell in row:
            print(cell.render_formatted(), end=' ')
        print()

table = Table(auto__model=Album).bind(request=req('get'))
print_table(table)
▼ Hide result

How do I turn off sorting? (on a column or table wide)

To turn off column on a column pass it sortable=False (you can also use a lambda here!):

table = Table(
    auto__model=Album,
    columns__name__sortable=False,
)
▼ Hide result

and to turn it off on the entire table:

table = Table(
    auto__model=Album,
    sortable=False,
)
▼ Hide result

How do I specify the title of a header?

The display_name property of a column is displayed in the header.

table = Table(
    auto__model=Album,
    columns__name__display_name='header title',
)
▼ Hide result

How do I set the default sort direction of a column to be descending instead of ascending?

table = Table(
    auto__model=Album,
    columns__name__sort_default_desc=True,  # or a lambda!
)
► Show result

How do I set the default sorting column of a table?

Tables are sorted by default on the order specified in the models Meta and then on pk. Set default_sort_order to set another default ordering:

table = Table(
    auto__model=Album,
    default_sort_order='year',
)
▼ Hide result

How do I group columns?

table = Table(
    auto__model=Album,
    columns__name__group='foo',
    columns__artist__group='bar',
    columns__year__group='bar',
)

The grouping only works if the columns are next to each other, otherwise you’ll get multiple groups. The groups are rendered by default as a second header row above the normal header row with colspans to group the headers.

▼ Hide result

How do I group rows?

Use row_group. By default this will output a <th> tag. You can configure it like any other fragment if you want to change that to a <td>. Note that the order of the columns in the table is used for grouping. This is why in the example below the year column is moved to index zero: we want to group on year first.

table = Table(
    auto__rows=Album.objects.order_by('year', 'artist', 'name'),
    columns__artist=dict(
        row_group__include=True,
        render_column=False,
    ),
    columns__year=dict(
        after=0,
        render_column=False,
        row_group=dict(
            include=True,
            template=Template('''
            <tr>
                {{ row_group.iommi_open_tag }}
                    {{ value }} in our hearts
                {{ row_group.iommi_close_tag }}
            </tr>
            '''),
        ),
    ),
)
▼ Hide result

How do I get rowspan on a table?

You can manually set the rowspan attribute via row__attrs__rowspan but this is tricky to get right because you also have to hide the cells that are “overwritten” by the rowspan. We supply a simpler method: auto_rowspan. It automatically makes sure the rowspan count is correct and the cells are hidden. It works by checking if the value of the cell is the same, and then it becomes part of the rowspan.

table = Table(
    auto__model=Album,
    columns__year__auto_rowspan=True,
    columns__year__after=0,  # put the column first
)
▼ Hide result

How do I enable bulk editing?

Editing multiple items at a time is easy in iommi with the built in bulk editing. Enable it for a columns by passing bulk__include=True:

table = Table(
    auto__model=Album,
    columns__select__include=True,
    columns__year__bulk__include=True,
)

The bulk namespace here is used to configure a Field for the GUI so you can pass any parameter you can pass to Field there to customize the behavior and look of the bulk editing for the column.

You also need to enable the select column, otherwise you can’t select the columns you want to bulk edit.

▼ Hide result

How do I enable bulk delete?

table = Table(
    auto__model=Album,
    columns__select__include=True,
    bulk__actions__delete__include=True,
)

To enable the bulk delete, enable the delete action.

You also need to enable the select column, otherwise you can’t select the columns you want to delete.

▼ Hide result

How do I make a custom bulk action?

You need to first show the select column by passing columns__select__include=True, then define a submit Action with a post handler:

def my_action_post_handler(table, request, **_):
    queryset = table.bulk_queryset()
    queryset.update(name='Paranoid')
    return HttpResponseRedirect(request.META['HTTP_REFERER'])

t = Table(
    auto__model=Album,
    columns__select__include=True,
    bulk__actions__my_action=Action.submit(
        post_handler=my_action_post_handler,
    )
)

What is the difference between attr and _name?

attr is the attribute path of the value iommi reads from a row. In the simple case it’s just the attribute name, but if you want to read the attribute of an attribute you can use __-separated paths for this: attr='foo__bar' is functionally equivalent to cell__value=lambda row, **_: row.foo.bar. Set attr to None to not read any attribute from the row.

_name is the name used internally. By default attr is set to the value of _name. This name is used when accessing the column from Table.columns and it’s the name used in the GET parameter to sort by that column. This is a required field.

How do I show a reverse foreign key relationship?

By default reverse foreign key relationships are hidden. To turn it on, pass include=True to the column:

t = Table(
    auto__model=Artist,
    columns__albums__include=True,
)
▼ Hide result

How do I show a reverse many-to-many relationship?

By default reverse many-to-many relationships are hidden. To turn it on, pass include=True to the column:

t = Table(
    auto__model=Genre,
    columns__albums__include=True,
)
▼ Hide result

How do I insert arbitrary html into a Table?

Sometimes you want to insert some extra html, css, or Part into a Table. You can do this with the container or outer namespaces.

For container, by default items are added after the table but you can put them above with after=0.

For outer, you can put content before the h tag even.

t = Table(
    auto__model=Genre,
    container__children__foo='Foo',
    container__children__bar=html.div('Bar', after=0),
    outer__children__bar=html.div('Baz', after=0),
)
▼ Hide result

How do I render additional rows?

Using rows__template you can render the default row with {{ cells.render }} and then your own custom data:

t = Table(
    auto__model=Album,
    row__template=Template('''
        {{ cells.render }}
        <tr>
            <td style="text-align: center" colspan="{{ cells|length }}">🤘🤘</td>
        </tr>
    '''),
)
▼ Hide result

How do I set an initial filter to a table?

The Query of a Table has a Form where you can set the initial value:

t = Table(
    auto__model=Album,
    columns__artist__filter__include=True,
    query__form__fields__artist__initial=lambda **_: Artist.objects.get(name='Dio'),
)
▼ Hide result

How do I show row numbers?

Use cells.row_index to get the index of the row in the current rendering.

t = Table(
    auto__model=Album,
    columns__index=Column(
        after=0,
        cell__value=lambda row, cells, **_: cells.row_index
    ),
)
▼ Hide result

How do I show nested foreign key relationships?

Say you have a list of tracks and you want to show the album and then from that album, you also want to show the artist:

t = Table(
    auto__model=Track,
    auto__include=[
        'name',
        'album',
        'album__artist',  # <--
    ]
)
▼ Hide result

The column created is named album_artist (as __ is reserved for traversing a namespace), so that’s the name you need to reference is you need to add more configuration to that column:

t = Table(
    auto__model=Track,
    auto__include=[
        'name',
        'album',
        'album__artist',
    ],
    columns__album_artist__cell__attrs__style__background='blue',
)
▼ Hide result

How do I stop rendering the header?

Use header__template=None to not render the header, or header__include=False to remove the processing of the header totally. The difference being that you might want the header object to access programmatically for some reason, so then it’s appropriate to use the template=None method.

t = Table(
    auto__model=Album,
    header__include=False,
)
▼ Hide result
t = Table(
    auto__model=Album,
    header__template=None,
)
▼ Hide result

How do I render a Table as divs?

You can render a Table as a div with the shortcut Table.div:

table = Table.div(
    auto__model=Album,
)
▼ Hide result

This shortcut changes the rendering of the entire table from <table> to <div> by specifying the tag configuration, changes the <tbody> to a <div> via tbody__tag, the row via row__tag and removes the header with header__template=None.

How do I do custom processing on rows before rendering?

Sometimes it’s useful to further process the rows before rendering, by fetching more data, doing calculations, etc. If you can use QuerySet.annotate(), that’s great, but sometimes that’s not enough. This is where preprocess_row and preprocess_rows come in. The first is called on each row, and the second is called for the entire list as a whole.

Note that this is all done after pagination.

Modifying row by row:

def preprocess_album(row, **_):
    row.year += 1000
    return row

table = Table(
    auto__model=Album,
    preprocess_row=preprocess_album,
)

Note that preprocess_row requires that you return the object. This is because you can return a different object if you’d like.

▼ Hide result

Modifying the entire list:

def preprocess_albums(rows, **_):
    for i, row in enumerate(rows):
        row.index = i
    return rows

table = Table(
    auto__model=Album,
    preprocess_rows=preprocess_albums,
    columns__index=Column.number(),
)
▼ Hide result

Note that preprocess_rows requires that you return the list. That is because you can also return a totally new list if you’d like.

How do I set an empty message?

By default iommi will render an empty table simply as empty:

table = Table(
    auto__model=Album,
)
▼ Hide result

If you want to instead display an explicit message when the table is empty, you use empty_message:

table = Table(
    auto__model=Album,
    empty_message='Destruction of the empty spaces is my one and only crime',
)
▼ Hide result

This setting is probably something you want to set up in your Style, and not per table.