Skip to content

Basic usage

Here you can find complete usage instructions for the Tables extension. The process is fairly simple and involves only several steps:

  1. Defining a table
  2. Creating a view to display the table

We're going to use a demo table definition called PeopleTable for demonstration purposes.

It's a working example located in a separate extension and can be enabled alongside the tables extension. Just add tables_demo to your ckan.plugins configuration.

The demo table uses all the features of the tables extension, including data sources, formatters, all action types, and exporters. A minimal example could be much simpler, but this one demonstrates the full power of the extension.

Defining a Table

First, create a table definition by inheriting from TableDefinition.

We will use the ListDataSource with a mock data for demonstration purposes, but in a real-world scenario, you might want to use DatabaseDataSource or create a custom data source. Read more about data sources here.

In general, ListDataSource is suitable for small datasets or testing, while DatabaseDataSource is recommended for production use with larger datasets.

If you're interested in how we're generating the mock data, check out the generate_mock_data function below:

Source code in ckanext/tables_demo/utils.py
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
def generate_mock_data(num_records: int) -> list[dict[str, str]]:
    fake = Faker()

    return [
        {
            "id": str(i),
            "name": fake.first_name(),
            "surname": fake.last_name(),
            "email": fake.email(),
            "created": fake.date_time_this_decade().isoformat(),
        }
        for i in range(1, num_records + 1)
    ]

Below is the full code of the PeopleTable definition:

import ckanext.tables.shared as t
from ckanext.tables_demo.utils import generate_mock_data

DATA = generate_mock_data(1000)


class PeopleTable(t.TableDefinition):
    """Demo table definition for the people table."""

    def __init__(self):
        super().__init__(
            name="people",
            data_source=t.ListDataSource(data=DATA),
            columns=[
                t.ColumnDefinition(field="id", width=70),
                t.ColumnDefinition(field="name"),
                t.ColumnDefinition(field="surname", title="Last Name"),
                t.ColumnDefinition(field="email"),
                t.ColumnDefinition(
                    field="created",
                    formatters=[
                        (t.formatters.DateFormatter, {"date_format": "%d %B %Y"})
                    ],
                ),
            ],
            row_actions=[
                t.RowActionDefinition(
                    action="remove_user",
                    label="Remove User",
                    icon="fa fa-trash",
                    callback=self.remove_user,
                    with_confirmation=True,
                ),
            ],
            bulk_actions=[
                t.BulkActionDefinition(
                    action="remove_user",
                    label="Remove Selected Users",
                    icon="fa fa-trash",
                    callback=self.remove_users,
                ),
            ],
            table_actions=[
                t.TableActionDefinition(
                    action="remove_all_users",
                    label="Remove All Users",
                    icon="fa fa-trash",
                    callback=self.remove_all_users,
                ),
                t.TableActionDefinition(
                    action="recreate_users",
                    label="Recreate Users",
                    icon="fa fa-refresh",
                    callback=self.recreate_users,
                ),
            ],
            exporters=t.ALL_EXPORTERS,
        )

    def remove_user(self, row: t.Row) -> t.ActionHandlerResult:
        """Callback to remove a user from the data source."""
        DATA[:] = [r for r in DATA if r["id"] != row["id"]]
        return t.ActionHandlerResult(success=True, message="User removed.")

    def remove_users(self, rows: list[t.Row]) -> t.ActionHandlerResult:
        """Callback to remove a user from the data source."""
        ids_to_remove = {row["id"] for row in rows}
        DATA[:] = [r for r in DATA if r["id"] not in ids_to_remove]
        return t.ActionHandlerResult(success=True, message="Users removed.")

    def remove_all_users(self) -> t.ActionHandlerResult:
        """Callback to remove all users from the data source."""
        DATA.clear()
        return t.ActionHandlerResult(success=True, message="All users removed.")

    def recreate_users(self) -> t.ActionHandlerResult:
        """Callback to recreate the mock users."""
        DATA[:] = generate_mock_data(1000)
        return t.ActionHandlerResult(success=True, message="Users recreated.")

Using Formatters

The tables extension provides several built-in formatters to change the way data is rendered in the table cells. You can apply one or more formatters to a column by specifying them in the formatters attribute of ColumnDefinition.

For example, from the above PeopleTable, we are using the datetime formatter to format the created field. So this 2024-02-25T11:10:00Z value will be displayed as 2024-02-25.

ColumnDefinition(
    field="created",
    formatters=[(formatters.DateFormatter, {"date_format": "%Y-%m-%d"})],
    sortable=True
),

Using Exporters

The tables extension also provides several built-in exporters to export the table data in different formats. You can specify the exporters to be used in the exporters attribute of the TableDefinition.

In the above PeopleTable, we are using all the available exporters by specifying t.ALL_EXPORTERS. You can also specify individual exporters if you want to limit the available export options.

exporters=[
    t.exporters.CSVExporter,
    t.exporters.TSVExporter,
],

Obviously, you can write your own custom exporters as well. See the exporters documentation for more information.

Using Actions

The tables extension allows you to define actions that can be performed on individual rows or on multiple selected rows. You can define these actions in the row_actions, table_actions and bulk_actions attributes of the TableDefinition.

Basically, it's just a matter of defining the action and providing a callback function that will be called when the action is triggered.

Read more about actions in the actions documentation.

Creating a View

Once your table is defined, you can create a view to display it using the GenericTableView:

from flask import Blueprint

from ckanext.tables.shared import GenericTableView
from ckanext.tables_demo.table import PeopleTable

bp = Blueprint("tables_demo", __name__, url_prefix="/tables-demo")

bp.add_url_rule("/people", view_func=GenericTableView.as_view("people", table=PeopleTable))

As you can see, this view does not require any custom code to render the table. The GenericTableView takes care of everything.

Results

After completing the above steps, you can navigate to /admin/people in your CKAN instance to see the rendered table.

"Rendered Table Example"

Next Steps