django-tables2: turning Django querysets into sortable, paginated HTML tables
django-tables2 - An app for creating HTML tables
At a glance
- What is it?
- Django-tables2 applies the same pattern as django.forms to HTML tables: a Table class wraps a model or queryset and handles column generation, sorting links and pagination, leaving the template with a single tag to call. Its built-in rendering requires no JavaScript, but every sort and page change is a full HTTP round trip.
- Who is it for?
- Django-tables2 is a good fit for server-rendered Django views that need sortable, paginated model lists without client-side JavaScript. The Meta.model shortcut covers simple cases; custom columns require subclassing and careful queryset prefetching to avoid per-row database overhead.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 53 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The django.forms analogy applied to HTML tables: what django-tables2 replaces in a view
Django-tables2 addresses a specific and repetitive task: turning a dataset into an HTML table with working sort links and pagination controls. A Django view that lists database records needs to sort those records, paginate them, render the table HTML and wire up sort parameters in the URL so that clicking a column header refreshes the view in the right order. Without a library, each of those steps is straightforward but repetitive across every list view in a project.
The README draws one direct comparison: django-tables2 does for HTML tables what `django.forms` does for HTML forms. Django forms take a model or a set of fields and handle HTML rendering, data binding and validation. Django-tables2 takes a model or a queryset and handles column generation, sort links, pagination and template rendering in the same way. The Table class plays the same role that a Form class plays in the forms framework.
Its scope is server-rendered list views. Sorting and pagination use URL query parameters and full page reloads, not client-side JavaScript. Projects already built around server rendering fit that pattern naturally; projects that need instant column sorting without a page reload do not.
Any Python iterable is a valid data source, but QuerySets get database-level sorting
Data can arrive at a django-tables2 table as any Python iterable: a list of dictionaries, a list of model instances, or a Django QuerySet. Any iterable is a valid data source. QuerySets receive special support beyond what other iterables get, though that support is described only at a high level in the project.
The practical difference matters most for sorting and pagination. A QuerySet-backed table can apply sorting at the database layer rather than sorting an in-memory Python list, and can paginate by fetching only the current page's rows. A table backed by a Python list evaluates the entire list first. For small datasets the distinction is negligible; for large tables it determines whether the page is fast or slow.
The project does not document this behavior for non-QuerySet data sources. Testing on a representative data size before putting a list-backed sorted table in production is the cautious step, and one that the README does not take the reader through.
Registering the app and wiring up the first sortable table in three files
Django-tables2 is available on PyPI under the package name django-tables2. After installing it, add the app to `INSTALLED_APPS` in the Django settings file:
INSTALLED_APPS = (
...,
"django_tables2",
)A minimal table for a model called `Simple` needs only a class with a Meta inner class:
import django_tables2 as tables
class SimpleTable(tables.Table):
class Meta:
model = SimpleConnect the table to a queryset and a template name using the generic view mixin:
class TableView(tables.SingleTableView):
table_class = SimpleTable
queryset = Simple.objects.all()
template_name = "simple_list.html"In the template, load the template tag library and call render_table:
{% load django_tables2 %}
{% render_table table %}Clicking a column header after this sends a request with a sort parameter in the URL, and the view re-renders the table sorted by that column. No JavaScript is involved in any step.
Meta.model generates one column per model field, but stops at the model boundary
Setting `model` in a Table class's Meta class instructs django-tables2 to create one column for each field on that model automatically. Column labels default to each field's verbose name, and column types follow field types so that a DateField and a CharField produce different default renderings without any additional configuration.
The auto-generation has a clear boundary: it works from the model definition alone. A ForeignKey column renders the related object's string representation by default. Getting a link to the related object's detail page, or any other custom rendering of that relation, requires defining the column explicitly in the Table class rather than relying on the Meta shortcut. Annotated values added to the queryset in the view work if they are present at runtime, but the Meta.model mechanism does not account for them in advance.
A full catalogue of column types and their constructor arguments is at django-tables2.readthedocs.io. The README example is the minimal model case; everything beyond that is on the documentation site.
No-JavaScript sorting and pagination: the trade-off in full-page reloads
The built-in table UI requires no JavaScript. Column headers are anchor tags pointing to the current URL with a sort query parameter added or toggled, and pagination controls are similar anchor tags for the page number. Every interaction is a standard GET request that renders a new full page.
For projects already built around server rendering, that is a natural fit. The page works without client-side JavaScript, the URL carries the table's sort and page state so it is shareable, and accessibility tools that expect navigation through anchor links work without modifications.
The limitation is latency. Each sort or page change waits for a full HTTP round trip and a database query. A JavaScript-based data grid sorts and paginates in-browser without a round trip. No built-in hooks exist in django-tables2 for connecting that kind of grid, and the project does not document a way to add one.
Custom column behavior requires subclassing, and N+1 queries live in the column class
Custom column rendering in django-tables2 is done by subclassing the appropriate column class. There is no keyword argument or configuration option for changing how a column displays a value; the documented path is creating a new Python class that inherits from the column type and overrides its rendering method.
For simple cases like formatting a number or wrapping a URL in an anchor tag, the subclass pattern is a few lines. For cases where the custom column needs related data, the subclass can call any ORM code per row. A column that accesses a related object without that object being prefetched produces one extra database query per row. Resolving it requires adding select_related or prefetch_related to the queryset in the view, not in the column class; the library does not document a prefetch interface at the column level.
The pyproject.toml defines an optional dependency group named tablib. Its purpose is not described in the README.
Python 3.11 and Django 5.2 are the current minimums, and the legal file is reported as unclassifiable on GitHub
The pyproject.toml file sets requires-python = ">=3.11" and dependencies = ["Django>=5.2"]. The classifiers list Python 3.11, 3.12, 3.13 and 3.14 and Django 5.2 and 6.0. Any project on an older Python or Django version cannot install the current package without pinning to an earlier one. Testing policy follows Django's own supported Python versions, so the floor will move when Django's support changes.
No GitHub tags mark individual builds; the last push was on 2026-08-31, and changes are tracked in CHANGELOG.md rather than in a dedicated releases page.
The pyproject.toml classifiers claim BSD terms, but GitHub reports the legal file as having an unclassified format; the pyproject.toml's own `license = {file = "LICENSE"}` entry points to the file rather than encoding the terms directly. Reading that file is the right step before any redistribution, since its format caused the two sources to disagree.
Editorial conclusion
Django-tables2 is a good fit for server-rendered Django views that need sortable, paginated model lists without client-side JavaScript. The Meta.model shortcut covers simple cases; custom columns require subclassing and careful queryset prefetching to avoid per-row database overhead. Skip it when the UI needs instant column sorting without a full page reload, since no hooks exist for that interaction. Before adopting, confirm Python 3.11 or newer and Django 5.2 or newer, and read the legal file at the repository root before redistribution, since the pyproject.toml classifiers and GitHub's classification of that file disagree.
Frequently asked questions
What is django-tables2?
Django-tables2 is a Django app that turns data sets into HTML tables with column sorting and pagination built in. It auto-generates columns from a Django model and provides both a template tag and a class-based view mixin for rendering.
How do I install django-tables2?
Install it from PyPI under the package name django-tables2, then add "django_tables2" to INSTALLED_APPS. Django 5.2 or newer and Python 3.11 or newer are required by the current version.
Does django-tables2 require JavaScript?
The built-in UI does not rely on JavaScript. Sorting and pagination work through URL query parameters and full page reloads, not client-side code.
Can django-tables2 generate columns automatically from a Django model?
Setting model in a Table class's Meta generates one column per model field automatically, using each field's verbose name as the column header and matching the column type to the field type.
What Django and Python versions does django-tables2 support?
The current version supports Django 5.2 and 6.0 and Python 3.11 through 3.14, per pyproject.toml. Earlier versions require pinning to an older package version.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/jieter-django-tables2)Was this useful?