Skip to main content

django-dynamic-admin-columns

PyPI Python versions License Tests

User-controllable, in-database list_display for Django admin. End-users enable, disable and reorder columns at runtime through the admin itself — no code changes, no redeploy.

Column picker modal open over the Books changelist

Why

django.contrib.admin.ModelAdmin.list_display is a developer-time setting. If end-users want different columns visible, the developer has to ship a code change. In bibliographic, archival and CRUD-heavy admin sites the column set varies between users and projects; this library moves that choice into the database so the admin UI itself is the configuration surface.

Extracted from the BPP academic bibliography system where it has run in production since 2022.

Features

  • Drop-in DynamicColumnsMixin for any ModelAdmin.
  • In-changelist picker. A Columns button in the standard object-tools area opens a modal where end-users toggle columns and reorder them via drag-and-drop — no admin training, no separate preferences page.
  • Per-user layouts. Each staff user keeps their own column configuration; users without a personal layout fall back to the global defaults. Resetting is one click away.
  • Three column tiers: always (pinned, code-only), default (visible out of the box, user can toggle), allowed (hidden by default, user-discoverable).
  • Per-admin and project-wide regex denylists (list_display_forbidden, DYNAMIC_ADMIN_COLUMNS_FORBIDDEN_COLUMN_NAMES) to keep sensitive or noisy fields out of the picker.
  • "__all__" shorthand: expose every model field and let the user pick.
  • Dictionary form of list_select_related that activates joins only for columns that are actually visible — no overhead for columns the user has hidden.
  • Vanilla-JS picker UI — no SortableJS, no jQuery, no Bootstrap.
  • Settings-gated import allowlist (DYNAMIC_ADMIN_COLUMNS_ALLOWED_IMPORT_PATHS) to prevent arbitrary class loading from untrusted database content.
  • Polish translation included.

Installation

uv add django-dynamic-admin-columns
# or
pip install django-dynamic-admin-columns

Add the apps and the import allowlist to your settings:

INSTALLED_APPS = [
    # ...
    "adminsortable2",
    "dynamic_admin_columns",
]

DYNAMIC_ADMIN_COLUMNS_ALLOWED_IMPORT_PATHS = [
    "myapp.admin",
]

# Optional global regex denylist applied to every dynamic admin.
DYNAMIC_ADMIN_COLUMNS_FORBIDDEN_COLUMN_NAMES = [
    r".*_cache$",
    r"^cached_.*",
]

# django-admin-sortable2's reordering view triggers admin.E117 with a dict
# ``list_select_related`` — silence it.
SILENCED_SYSTEM_CHECKS = ["admin.E117"]

Then run migrations:

python manage.py migrate dynamic_admin_columns

Usage

# myapp/admin.py
from django.contrib import admin
from dynamic_admin_columns.mixins import DynamicColumnsMixin

from myapp.models import Book


@admin.register(Book)
class BookAdmin(DynamicColumnsMixin, admin.ModelAdmin):
    # Pinned columns: always visible, always first, cannot be toggled.
    list_display_always = ["title"]

    # Visible out of the box, user can hide or reorder.
    list_display_default = ["author", "isbn"]

    # Hidden by default, user can enable via the admin.
    list_display_allowed = ["pages", "notes"]

    # Per-admin denylist (regex). Wins over ``__all__``.
    list_display_forbidden = [r"^legacy_.*"]

First time a user opens the changelist, the matching ModelAdmin row and its ModelAdminColumn rows are created automatically as the global defaults (user IS NULL). Subsequent edits through the in-changelist Columns picker write to that user's personal copy (user=<request.user>); users without a personal copy fall back to the global row.

Superuser: editing global defaults

A superuser sees an extra radio switch at the top of the modal:

  • My personal layout — the default. Saves create or update a personal row that affects only the current user.
  • Global defaults — saves rewrite the user IS NULL row, so every user without a personal layout sees the new column set on their next changelist load.

The accompanying Discard personal layout / Reset global defaults from code button operates on whichever scope is currently selected.

Pay the JOIN cost only for columns that are actually visible:

class BookAdmin(DynamicColumnsMixin, admin.ModelAdmin):
    list_display_default = ["author"]
    list_display_allowed = ["publisher"]

    list_select_related = {
        "__always__": ["category"],  # always joined
        "author": ["author"],  # joined only if the column is visible
        "publisher": ["publisher"],
    }

Example project

A minimal Django project demonstrating the library lives in example/. Two ways to run it:

# Plain Django — SQLite, no extras:
uv pip install -e ".[dev]"
cd example
python manage.py migrate
python manage.py loaddata sample
python manage.py runserver

or, via run-site + django-dev-helpers (Postgres + Redis testcontainers, autologin, recommended for exploration and for LLM coding agents):

uv pip install -e .
uv pip install -r example/requirements-dev.txt
cd example
uv run --no-sync python manage.py run_site

run_site spins up the containers, migrates, creates an admin / admin superuser and opens your browser. The example/runsite.toml file configures the container names, the superuser credentials, and the welcome banner.

See example/README.md for the full walk-through and the list of pre-loaded users.

Supported versions

This package targets actively supported Django releases. Older Django versions (4.2 LTS, 5.0, 5.1) are end-of-life upstream and are not covered by CI; pin django-dynamic-admin-columns < 0.2 if you need to stay on them.

Django 3.11 3.12 3.13 3.14 Status
5.2 LTS ✓ ✓ ✓ — Active LTS, extended support Apr 2028
6.0 — ✓ ✓ ✓ Previous mainstream
6.1 — ✓ ✓ ✓ Current mainstream

CI exercises every ✓ cell on GitHub Actions.

License

MIT. See LICENSE.

Metadata

Release files for django-dynamic-admin-columns 0.6.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-dynamic-admin-columns 0.6.0
File Size Uploaded Provenance Actions
django_dynamic_admin_columns-0.6.0.tar.gz 52.5 kB Without provenance Details for django_dynamic_admin_columns-0.6.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for django-dynamic-admin-columns 0.6.0
File Interpreter ABI Platform Actions
django_dynamic_admin_columns-0.6.0-py3-none-any.whl Python 3 none any Details for django_dynamic_admin_columns-0.6.0-py3-none-any.whl

Total release size: 89.1 kB

Release files / django_dynamic_admin_columns-0.6.0.tar.gz

Download URL django_dynamic_admin_columns-0.6.0.tar.gz
Size 52.5 kB
Tags Source
SHA-256 checksum
How to use checksums
c9ca73ef40ba5be6936181fed2ecd73300dd028134e65017409aee38165f5538
BLAKE2b-256 checksum
How to use checksums
fa00a4e9671c1c75b8f6abf5f7f7fc86fa82a4342b7a2d863c3e12cea31f9756
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / django_dynamic_admin_columns-0.6.0-py3-none-any.whl

Download URL django_dynamic_admin_columns-0.6.0-py3-none-any.whl
Size 36.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
afb56b868959b9eaef5bb15336444e691fc5276edeef26b75abb98e0329c507f
BLAKE2b-256 checksum
How to use checksums
855eb35c000533642719f1c163814a568876f8f5276ebf7488f101cd886390b0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for django-dynamic-admin-columns 0.6.0

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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