Django Flexible Reports
A framework for database-defined reports in Django.
Instead of hardcoding a report in a template or a view, you describe it in the database — which rows it shows, which columns it has, how the cells are formatted, how it is sorted — and edit all of that through the Django admin. Your application code only picks a report, hands it a queryset and renders it with one template tag:
{% load flexible_reports_tags %}
{% flexible report %}
Rendering is done by django-tables2, so you get sortable headers, footers/totals and export for free.
Supported Versions
| Python 3.10 | Python 3.11 | Python 3.12 | Python 3.13 | Python 3.14 | |
|---|---|---|---|---|---|
| Django 5.2 LTS | ✔ | ✔ | ✔ | ✔ | ✔ |
| Django 6.0 | — | — | ✔ | ✔ | ✔ |
| Django 6.1 | — | — | ✔ | ✔ | ✔ |
Django 6.0 and 6.1 require Python 3.12+. Django 4.2, 5.0 and 5.1 are no longer supported or tested — the floor is Django 5.2 LTS.
Documentation
The full documentation is at https://mpasternak.github.io/django-flexible-reports/.
Features
- Reports live in the database, not in your code.
Datasource,Table,Column,ColumnOrder,ReportandReportElementare ordinary Django models with a full admin, so non-programmers can change what a report shows without a deployment. - Two query languages for selecting rows. A
Datasourcenarrows a base queryset using either django-dsl (the default) or DjangoQL, chosen per datasource. - Parametrised queries. Every query is rendered as a Django template first,
so it can take values from the report context (
pages > {{ min_pages }}). Asample_contextfield supplies example values so a parametrised query can still be validated when it is saved. - Query shortcuts. A model can declare
django_dsl_shortcuts = {"author": "author__name"}and datasources may then use the short name. - Queries are validated on save. The admin refuses a query that does not compile or that the database rejects, instead of producing a silently empty report.
- Columns are templates. A column either reads an attribute
(
attr_name, dot notation crosses relations:author.name) or renders a Django template snippet withrecord,valueanddefaultin its context — or both. - Footers and totals.
display_totalssums a column and rendersfooter_templatewithvalue,countanderror. - Coordinated sorting. Tables can sort independently, in a named group, or all together — clicking one header re-sorts every table on the page that has a column with the same label.
- Default ordering per table via
ColumnOrder(any number of columns, ascending or descending). - "Everything else" tables. A report element can be fed with except catchall data: every record from the base queryset that none of the report's datasources picked up.
- Export. A report renders to HTML, to a
.docx(through pypandoc) or to a tablibDataset/Databook(CSV, XLSX, …). Columns can be excluded from export or have their HTML stripped. - Cloning.
Table.clone()andReport.clone()— plus a Clone button on the admin change form — copy a definition (a table with its columns and sort order, a report with its elements) under a(copy)name. - Optional grappelli support, detected at import time: inlines become drag-and-drop sortable when grappelli is installed, with no configuration.
- Translatable: all model verbose names, help texts and admin labels use
gettext.
Quickstart
Install Django Flexible Reports, with uv:
uv add django-flexible-reports
or with pip:
pip install django-flexible-reports
Add it (and django_tables2, which renders the tables) to your
INSTALLED_APPS:
INSTALLED_APPS = [
...
"django.contrib.admin", # to edit reports
"django.contrib.contenttypes", # required: models point at ContentType
...
"django_tables2",
"flexible_reports",
]
Make sure the request context processor is enabled — the {% flexible %} tag
passes the surrounding template context to django-tables2, which needs
request in it:
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
...
"OPTIONS": {
"context_processors": [
...
"django.template.context_processors.request",
],
},
},
]
Then run the migrations:
python manage.py migrate
No URL patterns to include. flexible_reports.urls exists but is empty —
the app ships no views of its own. You render reports from your own views. (Any
instructions telling you to include(flexible_reports.urls) are obsolete.)
Now define a report in the admin (Flexible reports → Datasources / Tables / Reports) and render it from your own view:
from django.shortcuts import render
from flexible_reports.models import Report
from .models import Book
def library_report(request):
report = Report.objects.get(slug="library-report")
# Required: every datasource narrows *this* queryset.
report.set_base_queryset(Book.objects.select_related("author"))
# Optional: values for parametrised queries, e.g. "pages > {{ min_pages }}".
report.set_context({"min_pages": 300})
return render(request, "library/report.html", {"report": report})
{% load flexible_reports_tags %}
{% flexible report %}
That is the whole integration. Which tables the report has, which columns they carry, how they are sorted and how the cells look is all read from the database.
Demo project
A complete, self-contained demo lives in demo/ — two models, two
datasources (one per query language, one of them parametrised), a table with
six columns (dot notation, custom cell templates, totals) and a three-element
report including an except catchall table. It runs on SQLite, so no services
are needed:
make demo # stock Django admin, http://127.0.0.1:8000/
make demo-grappelli # django-grappelli admin, http://127.0.0.1:8001/
make demo-reset # throw the demo database away
Log into the admin as admin / admin. See demo/README.md
for a walkthrough of what the seeded report demonstrates.
Optional system dependencies
- Exporting a report to
.docxshells out to pandoc throughpypandoc, so pandoc has to be installed on the machine (apt install pandoc,brew install pandoc). - Writing
.xlsxneedsopenpyxl(pip install "tablib[xlsx]"), which tablib does not pull in by default. CSV/JSON/YAML/TSV work out of the box.
HTML rendering needs nothing extra.
Running Tests
uv sync --all-extras
uv run pytest
The test suite runs against PostgreSQL; see tests/settings.py (the
POSTGRES_HOST / POSTGRES_PORT environment variables are honoured), or start
one with the bundled docker-compose.yml.
Contributing
See CONTRIBUTING.md for the development setup, how to run
the tests and the grappelli integration run, linting, and what CI checks.
The changelog is HISTORY.md.
The manual under docs/ is Markdown, built with
MkDocs and the
Material theme:
make docs # live preview on http://127.0.0.1:8000/
make docs-build # build with --strict, exactly as CI does
License
MIT
Release files for django-flexible-reports 0.5.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 | |
|---|---|---|---|
| django_flexible_reports-0.5.0.tar.gz | 35.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_flexible_reports-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 88.8 kB
Release files / django_flexible_reports-0.5.0.tar.gz
| Download URL | django_flexible_reports-0.5.0.tar.gz |
|---|---|
| Size | 35.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
181e9042d95de7282d1d94b09200a8ea2ae37a0eb0032d10693a2ab8ec07c4ec
|
|
BLAKE2b-256 checksum How to use checksums |
a150704b363c4f2040f0ffd557f8e6e1e969a291aef073781a45bf20a73c6e5a
|
| 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 Aug 18, 2026.
Transparency logRelease files / django_flexible_reports-0.5.0-py3-none-any.whl
| Download URL | django_flexible_reports-0.5.0-py3-none-any.whl |
|---|---|
| Size | 53.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e9cb14dcd1e61e3256f67b0dddbcd3399ba7c5c1d4e009ad1975713035e942b3
|
|
BLAKE2b-256 checksum How to use checksums |
62cf9706122019a583c6238970657442edb10cc03ac933199a783421b2bbca0d
|
| 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 Aug 18, 2026.
Transparency log