djangospice-table
Reusable, composable, server-side data tables for Django.
djangospice-table provides declarative data table widgets for Django applications, built on top of django-tables2, with integrated filtering, searching, pagination, actions, HTMX-compatible rendering, and optional dynamic JavaScript rendering.
Features
- Server-side data table rendering
- Built on
django-tables2 - Declarative table widgets
- Automatic table generation from Django models
- Custom
django-tables2table classes - Field inclusion and exclusion
django-filterintegration- Server-side search
- Server-side sorting
- Server-side pagination
- Configurable page-size options
- Table, row, and bulk actions
- Selectable rows
- Context-menu support
- DataTable API
- Declarative JSON table definitions
- Remote lookup-compatible filters
- HTMX-compatible rendering
- Automatic JavaScript table discovery
- Django template tags
Installation
pip install djangospice-table
Add the application to INSTALLED_APPS:
INSTALLED_APPS = [
# ...
"djangospice_table",
]
Basic Usage
A data table can be generated directly from a Django model:
from djangospice_table import TableWidget
class StudentTable(TableWidget):
model = Student
You can restrict the displayed fields:
class StudentTable(TableWidget):
model = Student
fields = (
"student_number",
"name",
"program",
)
Or exclude fields:
class StudentTable(TableWidget):
model = Student
exclude = (
"created_at",
"updated_at",
)
Custom Tables
For more control, provide a django-tables2 table class:
import django_tables2 as tables
from djangospice_table import TableWidget
class StudentTableDefinition(tables.Table):
student_number = tables.Column(
verbose_name="Student Number",
)
name = tables.Column()
class Meta:
model = Student
fields = (
"student_number",
"name",
"program",
)
class StudentTable(TableWidget):
table_class = StudentTableDefinition
This allows you to use the django-tables2 table API for custom columns and table behavior.
Querysets
Customize the queryset using the normal Django queryset API:
class StudentTable(TableWidget):
model = Student
def get_queryset(self):
return (
Student.objects
.select_related("program")
.filter(active=True)
)
Searching
Enable server-side search with search_fields:
class StudentTable(TableWidget):
model = Student
search_fields = (
"student_number",
"first_name",
"last_name",
"email",
)
The default search parameter is:
q
For example:
/students/?q=Brian
Search is performed server-side, making it suitable for large datasets.
Filtering
TableWidget integrates with django-filter:
import django_filters
from djangospice_table import TableWidget
class StudentFilter(django_filters.FilterSet):
program = django_filters.ModelChoiceFilter(
queryset=Program.objects.all(),
)
class Meta:
model = Student
fields = (
"program",
)
class StudentTable(TableWidget):
model = Student
filterset_class = StudentFilter
Filters are applied server-side.
For large filter datasets, use remote lookup-backed filters so that large option sets do not need to be embedded in the table response.
Pagination
Pagination is enabled by default:
class StudentTable(TableWidget):
model = Student
paginate_by = 20
Configure page-size options:
class StudentTable(TableWidget):
model = Student
paginate_by = 20
page_size_options = (
10,
20,
50,
100,
500,
)
The default query parameters are:
page
page_size
Example:
/students/?page=2&page_size=50
Row Selection
Enable row selection:
class StudentTable(TableWidget):
model = Student
selectable = True
Selected records are submitted using:
selected_ids
Actions
TableWidget uses the DjangoSpice action system for table operations. Actions are declared as action collections and are evaluated against an ActionContext before they are exposed to the user.
Three action collections are available:
actions— global actions for the tablerow_actions— actions for an individual rowbulk_actions— actions for a selected set of rows
The collections contain DjangoSpice Action objects, rather than action names or strings.
Table Actions
Table actions are global actions displayed by the table toolbar. They operate in the context of the table and do not have a specific object associated with them.
from djangospice_table import TableWidget
from djangospice_widget.actions import Action, Actions
class StudentTable(TableWidget):
model = Student
actions = Actions(
Action(
name="export",
label="Export",
),
)
A table action receives an ActionContext containing the widget, request, and widget data.
Row Actions
Row actions operate on a specific record. They are rendered for each row, typically through the table's row-action column.
class StudentTable(TableWidget):
model = Student
row_actions = Actions(
Action(
name="edit",
label="Edit",
),
Action(
name="delete",
label="Delete",
),
)
For a row action, the ActionContext contains:
widget— the currentTableWidgetrequest— the current Django requestobject— the row's objectobjects— a tuple containing that objectdata— the widget data
This allows an action to work directly with the record it is being invoked against.
Bulk Actions
Bulk actions operate on multiple selected records.
Enable selection and define the bulk actions:
class StudentTable(TableWidget):
model = Student
selectable = True
bulk_actions = Actions(
Action(
name="activate",
label="Activate",
),
Action(
name="deactivate",
label="Deactivate",
),
)
For a bulk action, the ActionContext contains:
widget— the currentTableWidgetrequest— the current Django requestobjects— the selected objectsdata— the widget data
The selected objects are resolved by the table widget before the action is bound.
Action Context
The action context is different depending on where the action is used.
A table action receives table-level context:
ActionContext(
widget=table,
request=request,
data=table.get_data(),
)
A row action receives the current object:
ActionContext(
widget=table,
request=request,
object=student,
objects=(student,),
data=table.get_data(),
)
A bulk action receives the selected objects:
ActionContext(
widget=table,
request=request,
objects=selected_students,
data=table.get_data(),
)
This gives actions a consistent interface while preserving the distinction between table, row, and bulk operations.
Action Visibility
Actions are evaluated against their context before being exposed.
For example, a row action can determine whether it should be visible for a particular record:
class StudentTable(TableWidget):
model = Student
row_actions = Actions(
Action(
name="activate",
label="Activate",
visible=lambda context: not context.object.is_active,
),
Action(
name="deactivate",
label="Deactivate",
visible=lambda context: context.object.is_active,
),
)
The table widget binds each visible action to its ActionContext, producing a BoundAction.
This means templates and table columns work with actions that are already associated with their execution context rather than raw, unbound action definitions.
Context Menu Actions
A table can also define actions for its row context menu.
class StudentTable(TableWidget):
model = Student
row_actions = Actions(
Action(
name="view",
label="View",
),
Action(
name="edit",
label="Edit",
),
)
context_menu_actions = Actions(
Action(
name="view",
label="View",
),
Action(
name="edit",
label="Edit",
),
)
When context_menu_actions is not explicitly defined, the table uses row_actions for the row context menu.
Complete Actions Example
A table can combine all three action scopes:
from djangospice_table import TableWidget
from djangospice_widget.actions import Action, Actions
class StudentTable(TableWidget):
model = Student
actions = Actions(
Action(
name="export",
label="Export",
),
)
row_actions = Actions(
Action(
name="view",
label="View",
),
Action(
name="edit",
label="Edit",
),
Action(
name="delete",
label="Delete",
),
)
bulk_actions = Actions(
Action(
name="activate",
label="Activate",
),
Action(
name="deactivate",
label="Deactivate",
),
)
selectable = True
The same action architecture is used by the table's server-rendered UI and its DataTable representation.
Context Menus
Context-menu support is available for table rows and actions.
Load the context-menu asset with:
{% load table %}
{% djangospice_table_contextmenu_js %}
DataTable
DataTable provides client-side rendering while retaining server-side data processing.
from djangospice_table import DataTable
class StudentTable(DataTable):
model = Student
fields = (
"student_number",
"name",
"program",
)
search_fields = (
"student_number",
"name",
)
paginate_by = 20
DataTable provides the same table capabilities as TableWidget while additionally exposing the table through the DataTable API and JavaScript client.
Rendering
DataTables are rendered through the standard DjangoSpice widget system:
{% load djangospice_widget %}
{% render_widget "students" %}
The initial response contains a lightweight DataTable placeholder.
The DataTable JavaScript client automatically discovers the placeholder and loads the table data from its API endpoint.
No manual JavaScript initialization is required.
DataTable API
DataTables expose a JSON API endpoint.
A typical endpoint is:
/api/tables/<app_name>/<name>/
For example:
/api/tables/academic/students/
The endpoint returns the current table definition.
DataTable Response
A DataTable response contains the table configuration, columns, rows, filters, search state, sorting, pagination, and actions.
{
"type": "table",
"id": "students",
"configuration": {
"selectable": true,
"searchable": true,
"sortable": true,
"filterable": true,
"pagination": true,
"page_size": 20,
"page_size_options": [10, 20, 50, 100, 500]
},
"columns": [
{
"name": "student_number",
"label": "Student Number",
"orderable": true
},
{
"name": "name",
"label": "Name",
"orderable": true
}
],
"rows": [],
"filters": [],
"search": {},
"sorting": {},
"pagination": {},
"actions": {
"table": [],
"row": [],
"bulk": []
}
}
Remote Lookups
Large filter datasets can use remote lookup sources instead of embedding all available choices in the table response.
Example:
{
"name": "program",
"type": "lookup",
"label": "Program",
"multiple": true,
"source": {
"type": "remote",
"endpoint": "/lookups/programs/",
"searchable": true,
"page_size": 20
}
}
The lookup endpoint independently provides its available options and pagination.
This keeps DataTable responses lightweight even when filters contain large datasets.
JavaScript
The package provides:
datatable.js
The JavaScript client automatically discovers DataTable placeholders.
It handles:
- Initial table loading
- Rendering
- Searching
- Filtering
- Sorting
- Pagination
- Page-size changes
- Row selection
- Table actions
- Bulk actions
- Dynamic updates
- HTMX-inserted tables
No explicit initialization is required when using the standard widget rendering flow.
Assets
The package provides three frontend assets:
table.css
datatable.js
contextmenu.js
Load the stylesheet:
{% load table %}
{% djangospice_table_css %}
Load the DataTable JavaScript:
{% djangospice_table_js %}
Load the context-menu JavaScript:
{% djangospice_table_contextmenu_js %}
Or load all table assets:
{% djangospice_table_assets %}
HTMX
DataTables support tables rendered inside HTMX requests and dynamically inserted fragments.
Newly inserted DataTable placeholders are automatically discovered by the JavaScript client.
This allows DataTables to be used in:
- Dialogs
- Tabs
- Drawers
- Partial page updates
- Dashboard widgets
- Dynamically loaded content
Complete Example
from djangospice_table import DataTable
class StudentTable(DataTable):
model = Student
fields = (
"student_number",
"name",
"program",
"study_mode",
"status",
)
search_fields = (
"student_number",
"name",
"email",
)
paginate = True
paginate_by = 20
page_size_options = (
10,
20,
50,
100,
500,
)
selectable = True
show_search = True
show_filters = True
Render it through the standard widget system:
{% load djangospice_widget %}
{% render_widget "students" %}
Load the assets once in the page:
{% load table %}
{% djangospice_table_assets %}
The DataTable is then discovered and initialized automatically.
Requirements
- Python
- Django
django-tables2django-filterdjangospice-frameworkdjangospice-widget
License
This package is licensed under the MIT License.
See LICENSE for the full license text.
Release files for djangospice-table 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| djangospice_table-0.1.0.tar.gz | 23.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| djangospice_table-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 58.0 kB
Release files / djangospice_table-0.1.0.tar.gz
| Download URL | djangospice_table-0.1.0.tar.gz |
|---|---|
| Size | 23.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5aac2b8bcdd7feb33879e9dc8402932b4d54c91e11e5108d8f2958efe3b5d148
|
|
BLAKE2b-256 checksum How to use checksums |
85933d4858b6914549bccf229fd8b134c4e86a46265e3d62ce8dec09fa8a4d44
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.
Transparency logRelease files / djangospice_table-0.1.0-py3-none-any.whl
| Download URL | djangospice_table-0.1.0-py3-none-any.whl |
|---|---|
| Size | 34.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a5082925a4ff055abead9baecdf83fdc08e5ef488e3aeeae3afae2b9ba451e65
|
|
BLAKE2b-256 checksum How to use checksums |
e47f63006ce33622bd8c60a4f1f4360e1099bc17606a4c18b1963bd08b604767
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.
Transparency log