Skip to content

Tables

A Table shows rows, searchable, sortable and split into pages. Its rows come from events, or from your own data through a loader.

Rows from events

By default, every event is a new row:

import dachshund
from dachshund.widgets import Table

with dachshund.row():
    dachshund.add(Table("requests", columns=["route", "status", "ms"]))

dachshund.emit("requests", route="/users", status=200, ms=12)

The table holds the latest 500 events of its source, like every widget, see How long events are kept.

One row per key

With a key, an event whose key matches a row replaces it, so the table shows the current state of each thing:

with dachshund.row():
    dachshund.add(Table("sessions", columns=["user", "ip"], key="user"))

dachshund.emit("sessions", user="ada", ip="10.0.0.1")
dachshund.emit("sessions", user="ada", ip="10.0.0.2")  # replaces ada's row
dachshund.emit("sessions", user="ada", _delete=True)  # removes it

A keyed table can keep its rows across restarts with persist=True, see Persistent Storage.

Row actions

A table's actions show as a button on every row. The row's fields are passed to the handler by name. A field starting with _ is hidden from the table, and passed without the underscore:

@dachshund.action("ban", label="Ban", confirm=True)
def ban(ctx, user: str, team_id: str) -> None: ...


with dachshund.row():
    dachshund.add(Table("sessions", columns=["user"], key="user", actions=["ban"]))

dachshund.emit("sessions", user="ada", _team_id="42")

See Actions for how handlers are called.

Rows from your data

For data that lives elsewhere, like users in a database, give the table a loader instead of emitting rows. It's called every time a page of the table is shown, with the dashboard's context and a TableQuery: the search, the sort, and the offset and limit of the page.

from dachshund.widgets import Table, TableQuery


async def load_users(ctx, query: TableQuery):
    users = await ctx["db"].users(
        search=query.search,
        order_by=query.sort_by,
        descending=query.descending,
        offset=query.offset,
        limit=query.limit,
    )
    total = await ctx["db"].count_users(search=query.search)

    return [{"id": u.id, "name": u.name} for u in users], total


dachshund.init("My service", context={"db": db})

with dachshund.row():
    dachshund.add(Table("users", columns=["id", "name"], loader=load_users))

A loader can be sync or async. It returns the rows of that page and the number of rows matching the search, or None when counting them is too costly. Without a total, the table offers a next page as long as a full page comes back.

To refresh a loaded table, emit to its source. The open page of the table is loaded again.