Skip to main content

PyPI version

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 Datatable class
  • Multiple tables per view -- display several tables on a single page
  • Spreadsheet mode -- JSpreadsheet integration for spreadsheet-like editing
  • Form widgets -- DataTableWidget and DataTableReorderWidget for 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

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

Optional:

  • openpyxl -- for Excel export
  • redis -- for table caching

License

MIT

Metadata

Release files for django-advanced-datatables 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-advanced-datatables 1.0.0
File Size Uploaded
django_advanced_datatables-1.0.0.tar.gz 334.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-advanced-datatables 1.0.0
File Interpreter ABI Platform
django_advanced_datatables-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 708.6 kB

Release files / django_advanced_datatables-1.0.0.tar.gz

Download URL django_advanced_datatables-1.0.0.tar.gz
Size 334.5 kB
Tags Source
SHA-256 checksum
How to use checksums
c44331f70036a836dba579821db7403cbe9218d5b7a441b8923682f164cbf66f
BLAKE2b-256 checksum
How to use checksums
99c0521387782c84fe66bac23c65c5e6f2e79caeebb1f8604e8f9c0281225102
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 5, 2026.

Transparency log

Release files / django_advanced_datatables-1.0.0-py3-none-any.whl

Download URL django_advanced_datatables-1.0.0-py3-none-any.whl
Size 374.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
886b9bbf7e4d252064169c5a31d5d9afbaeca85862af0f989f0a0e7bab47ca20
BLAKE2b-256 checksum
How to use checksums
65e0858559ccdf20c4a8e73a4900c69a8caf571c3a9ad34cd5b6f5d1832aecb6
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 5, 2026.

Transparency log

Release history Release notifications | RSS feed

1.0.7

2 release files

1.0.6

2 release files

1.0.5

2 release files

1.0.4

2 release files

1.0.3

2 release files

1.0.2

2 release files

1.0.1

2 release files

This release

1.0.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page