django-advanced-datatables
The django-advance-utils line of Ian Jones's
django-filtered-datatables, forked so that releases can be
cut as the downstream libraries and django-advanced-report-builder need them. The Python package
is still django_datatables, so existing imports and INSTALLED_APPS entries do not change; only the
pip name does. It depends on ajax-advanced-helpers,
django-advanced-menus and
django-advanced-modals.
A Django library for building interactive data tables powered by DataTables.js, with server-side processing, dynamic JavaScript filters, inline editing, and a rich plugin system.
Define your tables in Python using Django models and ORM queries -- columns, filters, sorting, annotations, and rendering are all configured from your views, with minimal JavaScript required.
Features
- 20+ column types -- dates, currency, booleans, links, many-to-many, choice fields, lambdas, and more
- AJAX and non-AJAX modes -- load data dynamically or render inline
- JavaScript filters -- pivot, tag, select2, date range, and text search filters
- Modal filter dialogs -- form-based filtering with crispy-forms support
- Inline editing -- edit cells directly in the table with select2 dropdowns or text inputs
- Row selection -- checkbox-based row selection with bulk actions
- Plugins -- column totals, conditional row colouring, drag-and-drop reordering, save/restore filter state
- Data export -- Excel (.xlsx) download and clipboard copy
- Client-side rendering -- render functions for badges, links, conditional formatting, and lookups
- Django ORM integration -- annotations, aggregations, foreign key traversal, callable model methods
- Model-defined columns -- configure columns directly on your Django models via an inner
Datatableclass - Multiple tables per view -- display several tables on a single page
- Spreadsheet mode -- JSpreadsheet integration for spreadsheet-like editing
- Form widgets --
DataTableWidgetandDataTableReorderWidgetfor use in Django forms - Mobile responsive -- automatic device detection with per-device column visibility
- Redis caching -- optional cache layer with signal-based invalidation
Installation
pip install django-advanced-datatables
Add to your INSTALLED_APPS:
INSTALLED_APPS = [
# ...
'django_datatables',
'ajax_helpers',
# ...
]
Include the required JavaScript and CSS in your base template. The library depends on DataTables.js, jQuery, and Bootstrap.
Quick Start
1. Define a view
from django_datatables.datatables import DatatableView
class CompanyList(DatatableView):
model = Company
@staticmethod
def setup_table(table):
table.add_columns(
'id',
'name',
'dissolved',
)
2. Wire up the URL
urlpatterns = [
path('companies/', CompanyList.as_view(), name='company-list'),
]
3. Use the template
The default template renders a complete DataTables table. You can override it via template_name on your view.
Usage
Adding Columns
Columns can be added in several ways:
def setup_table(table):
table.add_columns(
'name', # model field name
('id', {'title': 'ID', 'width': '30px'}), # tuple with options
DateColumn('date_entered', column_name='Date'), # column class instance
'company__name', # foreign key traversal
'Tags', # model-defined column
)
String prefix syntax for quick column options:
.field-- hidden column (still available for filtering/rendering)_field-- calculated column (not from the database)$field-- secure column (hidden from client source)
Column Types
| Column | Purpose |
|---|---|
DatatableColumn |
Standard column with customisable row_result and col_setup |
DateColumn |
Formats dates (dd/mm/yyyy) |
DateTimeColumn |
Formats datetimes |
ChoiceColumn |
Maps integer choices to display labels |
CurrencyColumn |
Currency formatting |
BooleanColumn |
Boolean display |
ColumnLink |
Clickable link using Django URL names |
ManyToManyColumn |
Displays many-to-many relationships |
CallableColumn |
Calls a model method |
LambdaColumn |
Processes values with a lambda function |
SelectColumn |
Row selection checkboxes |
TextFieldColumn |
Long text with truncation |
Model-Defined Columns
Configure columns directly on your models:
class Company(models.Model):
name = models.CharField(max_length=80)
class Datatable(DatatableModel):
# Annotation-based column
people = {'annotations': {'people': Count('person__id')}}
# Link column
collink_1 = ColumnLink(title='View', field=['id', 'name'], url_name='company-detail')
# Column list (renders multiple fields as a group)
company_list = ['id', 'name']
# Custom column class
class ModelIdColumn(DatatableColumn):
def col_setup(self):
self.field = 'id'
self.title = 'Custom ID'
These columns can then be referenced by name in add_columns():
table.add_columns('id', 'name', 'people', 'collink_1', 'ModelIdColumn')
Filters
def setup_table(table):
table.add_columns('id', 'name', 'company__name', 'Tags')
# JavaScript filters
table.add_js_filters('tag', 'Tags') # tag filter
table.add_js_filters('select2', 'company__name') # select2 dropdown
table.add_js_filters('totals', 'id') # totals/count filter
table.add_js_filters('date', 'date_entered') # date range
table.add_js_filters('expand', 'level', id_column='id') # tree expand
# ORM-level filters
table.filter = {'company__id': 1}
table.exclude = {'dissolved': True}
Modal Filter (form-based)
from django_datatables.modal_filter.mixins import DatatableFilterMixin, DatatableFilterField
class CompanyList(DatatableFilterMixin, DatatableView):
model = Company
filter_fields = [
DatatableFilterField('Company Name', CharField(required=False), datatable_field='name__contains'),
DatatableFilterField('Tags', FilterModelMultipleChoiceField(queryset=Tags.objects.all()), datatable_field='tags__in'),
]
Sorting
table.sort('name') # ascending
table.sort('-name') # descending
table.sort('name', '-id') # multi-column
Column Search
Per-column search boxes appear below the header by default. Customise them:
table.add_columns(
ColumnBase(column_name='status', field='dissolved',
col_search_select=[['true', 'Dissolved'], ['false', 'Active']]),
ColumnBase(column_name='order', field='order', no_col_search=True),
)
table.table_options['no_col_search'] = True # disable all column search
Client-Side Rendering
Render functions transform cell data in the browser without extra server round-trips:
from django_datatables.helpers import render_replace, row_button
table.add_columns(
# Badge rendering
ColumnBase(column_name='people', field='people', render=[
render_replace(column='people', html='<span class="badge badge-primary">%1%</span>'),
]),
# Row action button
ColumnBase(column_name='action', render=[row_button('delete', 'Delete')]),
# Lookup-based rendering
('id', {'title': 'Status', 'render': [
{'function': 'ReplaceLookup', 'html': '%1%', 'var': '%1%'}
], 'lookup': [[1, 'Active'], [2, 'Inactive']]}),
)
Available render functions: Replace, ReplaceLookup, Html, MergeArray, ValueInColumn.
Plugins
from django_datatables.plugins.colour_rows import ColourRows
from django_datatables.plugins.column_totals import ColumnTotals
from django_datatables.plugins.reorder import Reorder
# Conditional row colouring
table.add_plugin(ColourRows, [
{'column': 'status', 'values': {'overdue': 'table-danger', 'pending': 'table-warning'}}
])
# Column totals in footer
table.add_plugin(ColumnTotals, {
'amount': {'sum': True},
'id': {'text': 'Total', 'css_class': 'text-danger'},
'percentage': {'sum': 'percentage', 'numerator': 'vans', 'denominator': 'total', 'decimal_places': 1},
})
# Drag-and-drop row reordering
table.add_plugin(Reorder)
Inline Editing
def setup_table(table):
table.edit_fields = ['first_name', 'title']
table.edit_options = {'company__name': {'select2': True}}
Row Actions (AJAX Commands)
Handle row-level actions from buttons:
class MyView(DatatableView):
ajax_commands = ['row']
def row_delete(self, **kwargs):
return self.command_response('delete_row', row_no=kwargs['row_no'], table_id=kwargs['table_id'])
def row_toggle_tag(self, **kwargs):
row_data = json.loads(kwargs['row_data'])
# ... process action ...
table = self.tables[kwargs['table_id']]
return table.refresh_row(self.request, kwargs['row_no'])
Data Export
from django_datatables.downloads.excel_download import ExcelDownload
from django_datatables.downloads.clipboard import ClipboardCopy
class MyView(ExcelDownload, ClipboardCopy, DatatableView):
model = MyModel
def setup_menu(self):
self.add_menu('menu', 'buttons').add_items(
self.download_menu_item(),
self.clipboard_menu_item(),
)
super().setup_menu()
Multiple Tables Per View
class TwoTableView(DatatableView):
template_name = 'two_tables.html'
def add_tables(self):
self.add_table('t1', model=Company)
self.add_table('t2', model=Person)
@staticmethod
def setup_t1(table):
table.add_columns('id', 'name')
@staticmethod
def setup_t2(table):
table.add_columns('id', 'first_name')
Non-Model Data
Load data from any source (JSON files, APIs, etc.):
class JsonTableView(DatatableView):
@staticmethod
def setup_table(table):
table.add_columns(
ColumnBase(column_name='field1'),
ColumnBase(column_name='field2'),
)
@staticmethod
def get_table_query(table, **kwargs):
with open('data.json') as f:
return json.load(f)
Custom Column Classes
from django_datatables.columns import DatatableColumn
class FullName(DatatableColumn):
def col_setup(self):
self.field = ['first_name', 'surname']
def row_result(self, data_dict, _page_results):
return f'{data_dict["first_name"]} {data_dict["surname"]}'
Table Options
Common options passed via table.table_options:
table.table_options['pageLength'] = 50
table.table_options['no_col_search'] = True
table.table_options['scrollX'] = True
table.table_options['ajax_url'] = '/custom/ajax/url/'
table.table_options['row_href'] = [render_replace(column='id', html='/detail/%1%/')]
Demo Application
A Docker-based demo is included with sample data and 15+ examples covering filters, plugins, editing, rendering, and more.
docker-compose up
Then visit http://localhost:8006.
Dependencies
- Python >= 3.6
- Django
- ajax-advanced-helpers >= 1.0.1
Optional:
openpyxl-- for Excel exportredis-- for table caching
License
MIT
Metadata
Release files for django-advanced-datatables 1.0.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| django_advanced_datatables-1.0.4.tar.gz | 335.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_advanced_datatables-1.0.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 710.8 kB
Release files / django_advanced_datatables-1.0.4.tar.gz
| Download URL | django_advanced_datatables-1.0.4.tar.gz |
|---|---|
| Size | 335.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8b3e588567f811299796df60095b5ec5dafa75ea23ace539696f42ad33ad5c41
|
|
BLAKE2b-256 checksum How to use checksums |
4cb285ceb6a04b6e553c78b13ba224cab12a8591bbad1f0f0dad37497d41d53e
|
| 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 Oct 9, 2026.
Transparency logRelease files / django_advanced_datatables-1.0.4-py3-none-any.whl
| Download URL | django_advanced_datatables-1.0.4-py3-none-any.whl |
|---|---|
| Size | 375.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
bd43efc9f74d8bf7ae1c442d3caaffe94691ab583e80354c392c2f4f7d086258
|
|
BLAKE2b-256 checksum How to use checksums |
0f184889d8684d52dcceee1f863844560d8532050f14e36fdbb385bcb218a000
|
| 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 Oct 9, 2026.
Transparency log