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

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.