Tables¶
iommi tables make it easy to create full featured HTML tables.
This page explains what a Table is and what you get for free. If you want to
do something specific, go to the table cookbook; for
the exhaustive list of options see Table and Column.
A table generates the header, rows and cells, and comes with sorting, filtering, pagination, bulk edit, link creation, automatic rowspan and header grouping already wired up. The point is that these aren’t features you assemble: they are on by default and you turn them off or reconfigure them from the outside, which is why a useful table is usually one line of code.
Customization goes all the way down – table, row, cell, and the template for any of them – so you rarely have to abandon the abstraction to get the markup you want.
A simple example:
Table(
auto__model=Album,
page_size=10,
)
Read the full documentation and the Cookbook for more.
Incremental pagination¶
By default, people move between numbered pages. You can instead let them see more rows on the same page. This example starts with 30 albums and adds another batch when someone clicks “Load more”:
from iommi import PaginatorTrigger
Table(
auto__model=Album,
page_size=30,
parts__page__trigger=PaginatorTrigger.LOAD_MORE,
)
Choose one of the available loading behaviors:
PaginatorTrigger.LOAD_MOREadds a “Load more” button after each batch.PaginatorTrigger.INFINITE_SCROLLloads another batch as people reach the end of the current rows.PaginatorTrigger.LOAD_MORE_THEN_INFINITE_SCROLLshows a button for the first batch, then loads further batches as people scroll.
Numbered page links are shown with the button behavior and hidden with the
scrolling behaviors. Set parts__page__show_page_links to True or
False to change that. Hiding the links lets iommi check whether more
rows exist without first counting all matching rows.
The buttons use the active iommi style. To add a CSS class to the “Load more” button, for example, add this option:
parts__page__load_more__attrs__class__my_button=True
These loading behaviors also work with Table.div() and EditTable.
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:
class AlbumTable(Table):
# Shortcut for creating checkboxes to select rows
select = Column.select()
name = Column()
# Show the name field from Artist. This works for plain old objects too.
artist_name = Column(
attr='artist__name',
# put this field into the query language
filter__include=True,
)
year = Column.number(
# Enable bulk editing for this field
bulk__include=True,
)
class Meta:
rows = Album.objects.all()
title = 'Albums'
albums = AlbumTable().as_view()
This gives me a view with filtering, sorting, bulk edit and pagination.
Table of plain python objects¶
# 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)
# 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),
sortable=False,
)
def plain_objs_view(request):
# and now create a list of items
foos = [Foo(i) for i in range(4)]
# now to get an HTML table:
return FooTable(rows=foos)
All these examples and a bigger example using many more features can be found in the examples project.