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.0 requires 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
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_flexible_reports-0.4.1.tar.gz.
File metadata
- Download URL: django_flexible_reports-0.4.1.tar.gz
- Upload date:
- Size: 34.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2965052069ab298ca1077e2d910d71eb6274baed41845f9add696da863e199ad
|
|
| MD5 |
be0e6bbdeb6bf232184d47ba08979993
|
|
| BLAKE2b-256 |
445acf1d02255d2e664d5decb7d8216f0714dbe446cc518573a2baa7acd617f2
|
Provenance
The following attestation bundles were made for django_flexible_reports-0.4.1.tar.gz:
Publisher:
release.yml on mpasternak/django-flexible-reports
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_flexible_reports-0.4.1.tar.gz -
Subject digest:
2965052069ab298ca1077e2d910d71eb6274baed41845f9add696da863e199ad - Sigstore transparency entry: 2233773745
- Sigstore integration time:
-
Permalink:
mpasternak/django-flexible-reports@96c994d2a71166b52f93a15a432604680af77dc2 -
Branch / Tag:
refs/tags/v0.4.1 - Owner: https://github.com/mpasternak
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@96c994d2a71166b52f93a15a432604680af77dc2 -
Trigger Event:
push
-
Statement type:
File details
Details for the file django_flexible_reports-0.4.1-py3-none-any.whl.
File metadata
- Download URL: django_flexible_reports-0.4.1-py3-none-any.whl
- Upload date:
- Size: 51.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5fe474db25913633c277324ef48b357a67ab051c9da4350d042cdb73b1f21da1
|
|
| MD5 |
1a5f61b8bd4a1338f12dc70e89961c01
|
|
| BLAKE2b-256 |
ad4102c6dc56476cb528f1142fd52081514f0986d4abae344d630b4d468ebaee
|
Provenance
The following attestation bundles were made for django_flexible_reports-0.4.1-py3-none-any.whl:
Publisher:
release.yml on mpasternak/django-flexible-reports
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_flexible_reports-0.4.1-py3-none-any.whl -
Subject digest:
5fe474db25913633c277324ef48b357a67ab051c9da4350d042cdb73b1f21da1 - Sigstore transparency entry: 2233774475
- Sigstore integration time:
-
Permalink:
mpasternak/django-flexible-reports@96c994d2a71166b52f93a15a432604680af77dc2 -
Branch / Tag:
refs/tags/v0.4.1 - Owner: https://github.com/mpasternak
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@96c994d2a71166b52f93a15a432604680af77dc2 -
Trigger Event:
push
-
Statement type: