iommi tables makes it easy to create full featured HTML tables easily:

  • generates header, rows and cells

  • sorting

  • filtering

  • pagination

  • bulk edit

  • link creation

  • customization on multiple levels, all the way down to templates for cells

  • automatic rowspan

  • grouping of headers


The code for the example above:


Read the full documentation and the Cookbook for more.

Creating tables from models

Say I have some model:

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

    def __str__(self):
        return f'Foo: {self.a}'
class Bar(models.Model):
    b = models.ForeignKey(Foo, on_delete=models.CASCADE)
    c = models.CharField(max_length=255)

Now I can display a list of Bar in a table like this:

def my_view(request):
    return Table(auto__model=Bar)

This automatically creates a table with pagination and sorting. If you pass query_from_indexes=True you will get filters for all the model fields that have database indexes. This filtering system includes an advanced filter language. See Queries for more on filtering.

Explicit tables

You can also create tables explicitly:

def albums(request):
    class AlbumTable(Table):
        # Shortcut for creating checkboxes to select rows
        select =

        name = Column()

        # Show the name field from Artist. This works for plain old objects too.
        artist_name = Column(

            # put this field into the query language
        year = Column.number(
            # Enable bulk editing for this field

    return AlbumTable(rows=Album.objects.all())

This gives me a view with filtering, sorting, bulk edit and pagination.

▼ Hide result

Table as CSV

Tables are able to render as CSV files. This is enabled if there is specified a name to use on the resulting file, as a value of the table parameter extra_evaluated__report_name, and a file header name for each column that is to be included, specified by the column parameter extra_evaluated__report_name.

For example:

def albums(request):
    class AlbumTable(Table):
        class Meta:
            extra_evaluated__report_name = 'Albums'
            actions__download = Action(
                attrs__href=lambda table, **_: '?' + table.endpoints.csv.endpoint_path,

        name = Column(extra_evaluated__report_name='Name')
        artist = Column(extra_evaluated__report_name='Artist')
        year = Column.number(extra_evaluated__report_name='Artist')

    return AlbumTable(rows=Album.objects.all())

This will behave like an ordinary table but when the csv rendering endpoint is invoked the content will be returned as a text file in CSV format.

▼ Hide result

You can also pass kwargs to the csv.writer such as delimiter, quotechar, etc. with extra_evaluated__csv_writer_kwargs = {'delimiter': ';', ...}.

Table of plain python objects

def plain_objs_view(request):
    # Say I have a class...
    class Foo(object):
        def __init__(self, i):
            self.a = i
            self.b = 'foo %s' % (i % 3)
            self.c = (i, 1, 2, 3, 4)

    # and a list of them
    foos = [Foo(i) for i in range(4)]

    # I can declare a table:
    class FooTable(Table):
        a = Column.number()

        b = Column()

        # Display the last value of the tuple
        c = Column(
            cell__format=lambda value, **_: value[-1],

        # Calculate a value not present in Foo
        sum_c = Column(
            cell__value=lambda row, **_: sum(row.c),

    # now to get an HTML table:
    return FooTable(rows=foos)
▼ Hide result

All these examples and a bigger example using many more features can be found in the examples project.