Meet django-vantage: A Django Admin That Configures Itself
Columns, filters, search, charts, Kanban boards, dark mode and a JSON API, without writing an admin.py.
If you've built a few Django projects, you know the routine. You add a model, open admin.py, and type the same things again: list_display, list_filter, search_fields, ordering, list_select_related. Then you do it for the next model, and the one after that.
Django's admin is one of the best features in any web framework. Still, almost every project spends time on two jobs: telling the admin things it could work out from the model, and fixing up a UI that hasn't changed much in years.
So I built django-vantage, a Django admin panel that is smart and beautiful. Every model works with zero configuration. When you want something different, you override only that one thing and Vantage keeps inferring the rest.
In this post I'll show what it does, how it decides what to show you, and how to try it in about a minute.
The idea: smart by default, easy to override
Vantage is built on three principles.
1. Smart. Columns, filters, search fields, sort order, form widgets and query optimisation are all inferred from your model. You don't need any configuration to get a useful admin.
2. Easy. You can override any inferred default on its own. If you set list_display, only the columns change. Filters, search and widgets stay inferred. Inference works per attribute, so customising one thing doesn't mean you have to configure everything.
3. Configurable from the UI. Many settings can also be changed from the panel itself (columns, filters, stats, dashboard, sidebar, branding), so they apply without a deploy. Code and UI configuration combine in one fixed order:
SiteConfiguration row > settings.VANTAGE > conf.DEFAULTS
Up and running in 60 seconds
Vantage is in alpha, so install it with --pre:
pip install --pre django-vantage
Add it to INSTALLED_APPS, after the Django contrib apps and before your own apps:
# settings.py
INSTALLED_APPS = [
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"vantage",
# your apps
]
LOGIN_URL = "/accounts/login/"
LOGIN_REDIRECT_URL = "/admin/"
Mount it. You can also include Vantage's styled sign-in, sign-out and password-change views. Their route names match django.contrib.auth.urls, so you can switch back at any time:
# urls.py
from django.urls import include, path
from vantage import site
urlpatterns = [
path("admin/", site.urls),
path("accounts/", include("vantage.authviews")),
]
Run migrations. Vantage keeps its own tables for site configuration, the audit log, saved views and per-list preferences:
python manage.py migrate
That's all. Every model in your project now has a list view, a form, filters and search.
No Node.js required. Django is the only runtime dependency, and the CSS and JavaScript ship prebuilt.
How the "smart" part works
Inference follows fixed, documented rules, so you can predict what you'll get. Here is roughly what Vantage does with each model.
Columns (list_display). Every concrete field gets a score and the top six become columns. Choice fields rank highest, followed by booleans, foreign keys, emails and URLs, and short CharFields. Large text, JSON, binary, file and primary-key fields are never shown as columns. Audit timestamps like created_at go last.
The link column (list_link). Vantage looks for a field named name, title, label, headline, subject or display_name. If there isn't one, it uses the first plain CharField. If there's no CharField, it uses the primary key.
Filters (list_filter). Up to five filters: choices and booleans first, then one date field (a domain date such as ships_on is preferred over an audit timestamp), then foreign keys.
Search (list_search). Only text-like fields are searched. Indexed and unique fields come first. Vantage also follows one hop into each foreign key, so you can search orders by customer__name without configuring anything.
Ordering. Vantage uses your Meta.ordering if you set one. Otherwise it sorts newest first on created_at, created, date_joined, timestamp or date. If none of those exist, it sorts by descending primary key.
Queries. select_related and prefetch_related are worked out from the fields the page actually renders, which avoids N+1 queries.
Sensitive fields are never exposed. Fields whose names look like credentials (password, token, api_key, salt, hash, pin, cvv, and others) are never shown, linked, filtered or searched.
You can check the result yourself:
>>> from vantage import inference
>>> inference.infer_all(Order)
{'list_display': ['number', 'status', 'customer', 'shipping_city', 'total', 'created_at'],
'list_link': 'number',
'list_filter': ['status', 'created_at', 'customer'],
'list_search': ['number', 'shipping_city', 'note', 'customer__name'],
'ordering': ['-created_at']}
See it, then own it: vantage_scaffold
My favourite command in the project:
python manage.py vantage_scaffold shop # print what was inferred
python manage.py vantage_scaffold shop --write # save it as shop/panel.py
It prints a real panel.py that contains everything Vantage inferred:
@register(Order)
class OrderPanel(ModelPanel):
list_display = [
"number",
"status",
"customer",
"shipping_city",
"total",
"created_at",
]
list_link = "number"
list_filter = ["status", "created_at", "customer"]
list_search = ["number", "shipping_city", "note", "customer__name"]
ordering = ["-created_at"]
The file is ordinary code. Edit whatever you want, and delete any attribute to hand it back to inference. If you delete the whole file, your models stay registered.
Overriding only what you care about
Create a panel.py next to your models.py:
# blog/panel.py
from vantage import ModelPanel, register
from blog.models import Post
@register(Post)
class PostPanel(ModelPanel):
icon = "file-text"
group = "Content"
list_display = ["title", "author", "status", "published_at"]
Only the columns change. Filters, search, sorting and form widgets are still inferred.
Run python manage.py check and Vantage validates your panels and settings, including typo suggestions:
Unrecognised key(s) in the VANTAGE setting:
'SITE_TTLE' is not a Vantage setting. Did you mean 'SITE_TITLE'?
A tour of the features
A real data grid
The list view gives you saved views, grouping, density settings, a column picker, inline editing, and trash and restore. Bulk actions can apply to all matching rows, not just the current page. Select 40,000 filtered records and act on all of them.
There's also a command palette for jumping to any model or record from the keyboard:
Stats and charts, declared in Python
You declare stat cards and charts on the panel, and they're computed on the rows currently filtered. Charts render as inline SVG with no JavaScript charting library.
from django.db.models import Avg, Count, Q, Sum
from vantage import Chart, ModelPanel, Stat, register
@register(Order)
class OrderPanel(ModelPanel):
list_stats = [
Stat("Revenue", Sum("total"), format="currency", trend="created_at"),
Stat("Orders", Count("id")),
Stat("Average order", Avg("total"), format="currency"),
Stat("Refunded", Count("id", filter=Q(status="refunded")), tone="destructive"),
]
list_charts = [
Chart.timeseries("Revenue over time", date="created_at", value=Sum("total")),
Chart.breakdown("By status", group_by="status", value=Count("id"), kind="donut"),
]
-
trend="created_at"compares the value with the previous period of the same length. -
Chart.timeseriespicks day, week or month buckets based on the date range of your data, unless you setbucketyourself. - If the ORM can't express what you need, pass a callable:
Stat("Churn rate", lambda qs: churn_for(qs), format="percent"). If it raises an exception, the card shows—and the rest of the page still works.
Kanban, calendar, gallery and map views
The same list can be shown as a table, a Kanban board, a month calendar, a gallery or a map. Users pick the grouping field on the page.
@register(Order)
class OrderPanel(ModelPanel):
list_views = ["table", "kanban", "calendar"] # first one is the default
kanban_field = "status" # board columns
calendar_field = "ships_on" # where records land
kanban_fields = ["status", "priority"] # what "Group by" offers
card_fields = ["customer", "total"] # what a card shows
Forms with a layout DSL
Tabs, Tab, Section and Row nest however you like:
from vantage import ModelPanel, Row, Section, Tab, Tabs, register
@register(Post)
class PostPanel(ModelPanel):
layout = Tabs(
Tab("Content",
Section(Row("title", "slug"), "body"),
Section(Row("status", "published_at"), title="Publishing", collapsed=True),
),
Tab("SEO", Section("meta_title", "meta_description")),
)
One safety detail I like: fields you leave out of the layout aren't dropped. They're collected into an extra "Other" tab, so a required field can't quietly disappear from the form.
Editable inlines work as you'd expect:
from vantage.inlines import Inline
class OrderItemInline(Inline):
model = OrderItem
fields = ["product", "quantity", "unit_price"]
extra = 1
max_num = 50
@register(Order)
class OrderPanel(ModelPanel):
inlines = [OrderItemInline]
Tip: set
max_numexplicitly on inlines for now. In the current alpha, leaving it unset raises an error.
A cascade-delete preview
Before you delete a record, Vantage shows exactly what else will be deleted. It uses the same Collector as the real delete, so the preview always matches what happens. If a delete is blocked by a protected relation, you get a plain explanation instead of an error page:
"This customer cannot be deleted while Orders still reference it. Remove or reassign those first."
Undo that respects your teammates
Deletes, bulk edits and inline edits show an undo toast. Undo never overwrites a newer change made by someone else in the meantime.
Permissions, roles and sensitive data
- Three permission layers, enforced on views, bulk actions and API calls.
- Read-only mode and impersonation.
- Multi-tenancy that fails closed: if tenant scoping can't be resolved, nothing is shown.
- A permission matrix for Django groups, so you can see who can do what in one place.
- Sensitive fields such as passwords, tokens and card numbers are masked by default. Revealing one is restricted and logged.
There's also a read-only data-quality audit for each model, and a schema map that shows how your models relate:
A JSON API for every panel
Every panel also has a JSON API that goes through the same permission and validation path as the UI, with cursor pagination and optimistic concurrency. You don't have to keep a separate serializer layer in sync with your admin.
Theming from one colour
Give Vantage a brand colour and it derives full light and dark palettes, adjusting lightness until the text passes WCAG AA contrast (4.5:1). RTL layouts are supported too.
# settings.py
VANTAGE = {
"SITE_TITLE": "Acme",
"LOGO": "img/acme.svg",
"LOGO_DARK": "img/acme-light.svg",
"BRAND_COLOR": "#0ea5e9",
"BRAND_RADIUS": "0.5rem",
"THEME": "system",
"ENVIRONMENT": "Staging — data here is not real", # banner on every page
}
A superuser can change any of these later from the settings screen, without a deploy.
Requirements
- Python 3.12+
- Django 5.2, 6.0 or 6.1
- Runtime dependencies: only Django
- Optional integrations (used when installed, never required): django-guardian, django-rules, django-mptt, django-treebeard, django-simple-history
Status: alpha, and I'm being honest about it
The current release is 0.2.0a1. It has more than 4,600 tests, but the API may still change before 1.0. My recommendation: use it on internal tools for now, and pin the exact version.
Try the demo
The repository includes a demo wholesale shop with orders, customers and products. One command migrates it, seeds data and serves it on port 8765:
git clone https://github.com/Pythondeveloper6/django-vantage
cd django-vantage
./example/run.sh
Sign in with admin / admin12345. The demo's example/shop/panel.py is the shortest practical reference for everything in this post.
Links
- ⭐ GitHub: github.com/Pythondeveloper6/django-vantage
- 📦 PyPI: pypi.org/project/django-vantage
- 📖 Docs: pythondeveloper6.github.io/django-vantage
If you try it, I'd really like to hear what Vantage inferred correctly and what it got wrong for your models. Those cases are what improve the inference rules. Issues, ideas and stars on GitHub are all welcome.
Thanks for reading!
Mahmoud Ahmed







Top comments (0)