Skip to content

Table Definition

The table definition holds the configuration for a table, including its columns, data source, and other options.

Below you can check the available attributes of the TableDefinition class and their descriptions. Also, a full code is provided at the end of this document for reference.

Table definition.

ATTRIBUTE DESCRIPTION
name

Unique identifier for the table.

TYPE: str

data_source

Data source for the table.

TYPE: BaseDataSource

ajax_url

(Optional) URL to fetch data from. Defaults to an auto-generated URL.

TYPE: BaseDataSource

columns

(Optional) List of ColumnDefinition objects.

TYPE: list[ColumnDefinition]

row_actions

(Optional) List of RowActionDefinition objects.

TYPE: list[RowActionDefinition]

bulk_actions

(Optional) List of BulkActionDefinition objects for action on multiple rows.

TYPE: list[BulkActionDefinition]

table_actions

(Optional) List of TableActionDefinition objects for actions on the table itself.

TYPE: list[TableActionDefinition]

exporters

(Optional) List of exporter classes for exporting table data.

TYPE: list[type[ExporterBase]]

placeholder

(Optional) Placeholder text for an empty table.

TYPE: str | None

page_size

(Optional) Number of rows per page. Defaults to 10.

TYPE: int

table_template

(Optional) Template to render the table. Defaults to tables/base.html.

TYPE: str

Source code in ckanext/tables/table.py
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
@dataclass
class TableDefinition:
    """Table definition.

    Attributes:
        name: Unique identifier for the table.
        data_source: Data source for the table.
        ajax_url: (Optional) URL to fetch data from. Defaults to an auto-generated URL.
        columns: (Optional) List of ColumnDefinition objects.
        row_actions: (Optional) List of RowActionDefinition objects.
        bulk_actions: (Optional) List of BulkActionDefinition objects for action on multiple rows.
        table_actions: (Optional) List of TableActionDefinition objects for actions on the table itself.
        exporters: (Optional) List of exporter classes for exporting table data.
        placeholder: (Optional) Placeholder text for an empty table.
        page_size: (Optional) Number of rows per page. Defaults to 10.
        table_template: (Optional) Template to render the table. Defaults to `tables/base.html`.
    """

    name: str
    data_source: BaseDataSource
    columns: list[ColumnDefinition] = dataclass_field(default_factory=list)
    row_actions: list[RowActionDefinition] = dataclass_field(default_factory=list)
    bulk_actions: list[BulkActionDefinition] = dataclass_field(default_factory=list)
    table_actions: list[TableActionDefinition] = dataclass_field(default_factory=list)
    exporters: list[type[ExporterBase]] = dataclass_field(default_factory=list)
    placeholder: str | None = None
    page_size: int = 10
    table_template: str = "tables/base.html"

    def __post_init__(self):
        self.id = f"table_{self.name}_{uuid.uuid4().hex[:8]}"

        if self.placeholder is None:
            self.placeholder = tk._("No data found")

        if self.row_actions:
            self.columns.append(
                ColumnDefinition(
                    field=COLUMN_ACTIONS_FIELD,
                    title=tk._(""),
                    formatters=[(formatters.ActionsFormatter, {})],
                    filterable=False,
                    tabulator_formatter="html",
                    sortable=False,
                    resizable=False,
                    width=50,
                ),
            )

    def get_tabulator_config(self) -> dict[str, Any]:
        columns = [col.to_dict() for col in self.columns]

        options: dict[str, Any] = {
            "columns": columns,
            "placeholder": self.placeholder,
            "sortMode": "remote",
            "layout": "fitColumns",
            "pagination": True,
            "paginationMode": "remote",
            "paginationSize": self.page_size,
            "paginationSizeSelector": [5, 10, 25, 50, 100],
            "minHeight": 300,
        }

        if bool(self.bulk_actions):
            options.update(
                {
                    "rowHeader": {
                        "headerSort": False,
                        "resizable": False,
                        "headerHozAlign": "center",
                        "hozAlign": "center",
                        "vertAlign": "middle",
                        "formatter": "rowSelection",
                        "titleFormatter": "rowSelection",
                        "width": 50,
                    }
                }
            )

        return options

    def get_row_actions(self) -> dict[str, dict[str, Any]]:
        return {
            action.action: {
                "name": action.action,
                "label": action.label,
                "icon": action.icon,
                "with_confirmation": action.with_confirmation,
            }
            for action in self.row_actions
        }

    def render_table(self, **kwargs: Any) -> str:
        return tk.render(self.table_template, extra_vars={"table": self, **kwargs})

    def get_data(self, params: QueryParams) -> list[Any]:
        return [self._apply_formatters(dict(row)) for row in self.get_raw_data(params)]

    def get_raw_data(self, params: QueryParams, paginate: bool = True) -> list[dict[str, Any]]:
        if not paginate:
            return self.data_source.filter(params.filters).sort(params.sort_by, params.sort_order).all()

        return (
            self.data_source.filter(params.filters)
            .sort(params.sort_by, params.sort_order)
            .paginate(params.page, params.size)
            .all()
        )

    def get_total_count(self, params: QueryParams) -> int:
        # for total count we only apply filter, without sort and pagination
        return self.data_source.filter(params.filters).count()

    def _apply_formatters(self, row: dict[str, Any]) -> dict[str, Any]:
        """Apply formatters to each cell in a row."""
        formatted_row = copy.deepcopy(row)

        for column in self.columns:
            cell_value = row.get(column.field)

            if not column.formatters:
                continue

            for formatter_class, formatter_options in column.formatters:
                cell_value = formatter_class(column, formatted_row, row, self).format(cell_value, formatter_options)

            formatted_row[column.field] = cell_value

        return formatted_row

    @classmethod
    def check_access(cls, context: Context) -> None:
        """Check if the current user has access to view the table.

        This class method can be overridden in subclasses to implement
        custom access control logic.

        By default, it checks if the user has the `sysadmin` permission,
        which means that the table is available only to system administrators.

        Raises:
            tk.NotAuthorized: If the user does not have an access
        """
        tk.check_access("sysadmin", context)

    def get_bulk_action(self, action: str) -> BulkActionDefinition | None:
        return next((a for a in self.bulk_actions if a.action == action), None)

    def get_table_action(self, action: str) -> TableActionDefinition | None:
        return next((a for a in self.table_actions if a.action == action), None)

    def get_row_action(self, action: str) -> RowActionDefinition | None:
        return next((a for a in self.row_actions if a.action == action), None)

    def get_exporter(self, name: str) -> type[ExporterBase] | None:
        return next((e for e in self.exporters if e.name == name), None)

check_access(context) classmethod

Check if the current user has access to view the table.

This class method can be overridden in subclasses to implement custom access control logic.

By default, it checks if the user has the sysadmin permission, which means that the table is available only to system administrators.

RAISES DESCRIPTION
NotAuthorized

If the user does not have an access

Source code in ckanext/tables/table.py
167
168
169
170
171
172
173
174
175
176
177
178
179
180
@classmethod
def check_access(cls, context: Context) -> None:
    """Check if the current user has access to view the table.

    This class method can be overridden in subclasses to implement
    custom access control logic.

    By default, it checks if the user has the `sysadmin` permission,
    which means that the table is available only to system administrators.

    Raises:
        tk.NotAuthorized: If the user does not have an access
    """
    tk.check_access("sysadmin", context)