Skip to content

Table

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

You can check the full code example to see available options for TableDefinition:

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: str | None

columns

(Optional) List of ColumnDefinition objects.

TYPE: list[ColumnDefinition]

actions

(Optional) List of ActionDefinition objects for each row.

TYPE: list[ActionDefinition]

global_actions

(Optional) List of GlobalActionDefinition objects for bulk actions.

TYPE: list[GlobalActionDefinition]

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_action_snippet

(Optional) Snippet to render table actions.

TYPE: str | None

table_template

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

TYPE: str

Source code in ckanext/tables/table.py
 29
 30
 31
 32
 33
 34
 35
 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
@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.
        actions: (Optional) List of ActionDefinition objects for each row.
        global_actions: (Optional) List of GlobalActionDefinition objects for bulk actions.
        placeholder: (Optional) Placeholder text for an empty table.
        page_size: (Optional) Number of rows per page. Defaults to 10.
        table_action_snippet: (Optional) Snippet to render table actions.
        table_template: (Optional) Template to render the table. Defaults to `tables/base.html`.
    """

    name: str
    data_source: BaseDataSource
    ajax_url: str | None = None
    columns: list[ColumnDefinition] = dataclass_field(default_factory=list)
    actions: list[ActionDefinition] = dataclass_field(default_factory=list)
    global_actions: list[GlobalActionDefinition] = dataclass_field(default_factory=list)
    placeholder: str | None = None
    page_size: int = 10
    table_action_snippet: str | None = None
    table_template: str = "tables/base.html"

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

        if self.ajax_url is None:
            self.ajax_url = tk.url_for("tables.ajax", table_name=self.name)

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

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

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

        if self.selectable:
            options.update(
                {
                    "selectableRows": True,
                    "selectableRangeMode": "click",
                    "selectableRollingSelection": False,
                    "selectablePersistence": False,
                }
            )

        return options

    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) -> list[dict[str, Any]]:
        return (
            self.data_source.filter(params.field, params.operator, params.value)
            .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.field, params.operator, params.value).count()

    def _apply_formatters(self, row: dict[str, Any]) -> dict[str, Any]:
        """Apply formatters to each cell in a 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, row, self).format(cell_value, formatter_options)

            row[column.field] = cell_value

        return 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 `package_search` permission,
        which means that the table is publicly accessible.

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

    def get_global_action(self, action: str) -> GlobalActionDefinition | None:
        for ga in self.global_actions:
            if ga.action != action:
                continue
            return ga

        return 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 package_search permission, which means that the table is publicly accessible.

RAISES DESCRIPTION
NotAuthorized

If the user does not have an access

Source code in ckanext/tables/table.py
127
128
129
130
131
132
133
134
135
136
137
138
139
140
@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 `package_search` permission,
    which means that the table is publicly accessible.

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