8 Django Admin Features That Save You From Building Dashboards
It is easy to build a dashboard too early.
A team needs to review orders, edit posts, update product data, or check support records. The first instinct is often: build a custom internal dashboard.
Sometimes that is the right call. But in many Django projects, the admin can handle the first version of that workflow with less code, less frontend work, and fewer moving parts.
The Django admin is not meant to be a public product interface. It is strongest when trusted internal users need to manage structured data. If that is your situation, these admin features can save a surprising amount of custom dashboard work.
I just published the Django Admin Cheatsheet — 86 pages of reference cards covering every ModelAdmin attribute, inline, filter, permission, and customisation pattern. Built for developers who know Django Admin but still Google the same attributes every week. Check here: django.wiki/books/
Start With The Admin Before A Dashboard
Before building a dashboard, ask what the users actually need to do.
If they need charts, custom workflows, customer-facing screens, or carefully designed product interactions, the admin may not be enough. But if they need to find records, filter lists, update statuses, edit related objects, or run safe bulk actions, the admin is often a strong starting point.
That matters because dashboards tend to grow.
You start with a list page. Then you add filters. Then search. Then edit screens. Then permissions. Then bulk operations. Soon you have rebuilt a rough version of the Django admin, only with more code to maintain.
The better pattern is simple: use the admin for trusted operational workflows first, then build custom dashboards only where the workflow truly needs them.
list_display Makes Data Scannable
The default admin list page is rarely enough. It usually shows the string representation of each object, which is fine for simple models but weak for real staff workflows.
list_display lets you choose the columns that matter.
For an order admin page, that might look like this:
# admin.py
from django.contrib import admin
from .models import Order
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
list_display = [
"order_number",
"customer_email",
"status",
"total",
"created_at",
]Now the list page tells a story. Staff can scan order numbers, see who placed them, check the current status, and compare dates without opening every record.
This is the first admin feature to reach for when someone says, “We need a dashboard to see our data.”
Often, they do not need a dashboard yet. They need a better list view.
list_filter Turns Lists Into Simple Reports
Filters are where the admin starts to feel like a lightweight internal tool.
With list_filter, staff can narrow records by status, date, boolean fields, or related models:
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
list_display = [
"order_number",
"customer_email",
"status",
"total",
"created_at",
]
list_filter = ["status", "created_at", "is_paid"]This gives staff quick answers to common questions:
- Which orders are pending?
- Which orders were created today?
- Which orders are unpaid?
That covers many basic reporting screens.
The key is to choose filters that match real decisions. Adding every possible field makes the admin noisy. Adding the right few filters makes it useful.
For operational data, status and date filters are usually a good place to start.
search_fields Handles Everyday Lookup
Internal users often know one piece of information: an email address, an order number, a username, or a product SKU.
search_fields turns that into a practical lookup tool:
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
list_display = [
"order_number",
"customer_email",
"status",
"total",
"created_at",
]
list_filter = ["status", "created_at", "is_paid"]
search_fields = [
"order_number",
"customer__email",
"customer__first_name",
"customer__last_name",
]The double underscore syntax works across relationships, just like normal Django ORM lookups.
This is especially useful for support workflows. If someone messages with an email address or order number, staff can find the record quickly without a custom search page.
Search works best when it mirrors how people actually identify records. Do not only search internal IDs if staff never use them. Add fields that match real lookup habits.
actions Cover Bulk Operations
Dashboards often need bulk operations.
“Mark these as reviewed.”
“Archive selected records.”
“Set these products inactive.”
Django admin actions can cover many of these cases:
@admin.action(description="Mark selected orders as reviewed")
def mark_as_reviewed(modeladmin, request, queryset):
queryset.update(status=Order.Status.REVIEWED)
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
list_display = ["order_number", "customer_email", "status", "created_at"]
list_filter = ["status", "created_at"]
actions = [mark_as_reviewed]Actions should be boring and safe.
Avoid vague labels. Avoid actions that do too much at once. Be careful with destructive operations. If an action changes important data, make sure the name is clear enough that staff understand what will happen.
Get Django Wiki’s stories in your inbox
Join Medium for free to get updates from this writer.
For repeatable internal changes, admin actions can save an entire custom bulk-edit screen.
inlines Keep Related Data Together
Some models are hard to manage one object at a time.
An order has order items. A post may have related metadata. A customer may have profile details. If staff need to edit related records together, admin inlines can help.
For order items, a simple inline might look like this:
from django.contrib import admin
from .models import Order, OrderItem
class OrderItemInline(admin.TabularInline):
model = OrderItem
extra = 0
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
list_display = ["order_number", "customer_email", "status", "created_at"]
inlines = [OrderItemInline]Now staff can open an order and see its items on the same page.
This is often enough for internal editing workflows. Instead of building a custom “order detail dashboard,” you can improve the admin page where the data already lives.
Use inlines carefully. If a parent page ends up with too many related objects, it can become slow or messy. But for small related sets, they are extremely practical.
readonly_fields Protect Important Values
Not every visible field should be editable.
Staff may need to see timestamps, external IDs, calculated totals, or audit values, but changing those values directly could create problems.
readonly_fields helps you expose information safely:
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
list_display = ["order_number", "customer_email", "status", "total"]
readonly_fields = ["created_at", "updated_at", "calculated_total"]
def calculated_total(self, obj):
return obj.items_total()This makes the admin more useful without making it too permissive.
It also helps with trust. Staff can inspect important context while the system still protects fields that should be controlled by code, imports, or payment workflows.
When you add data to admin pages, ask two questions:
- Should staff see this?
- Should staff edit this?
Those are not always the same answer.
Custom Display Methods Add Context
The admin is not limited to raw model fields.
You can add methods that display useful context, counts, or formatted values:
from django.contrib import admin
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
list_display = [
"order_number",
"customer_email",
"status",
"item_count",
"created_at",
]
@admin.display(description="Items")
def item_count(self, obj):
return obj.items.count()
This can make list pages much more informative.
For example, a support user may not need to open every order if they can already see the status, customer email, item count, and date from the list page.
Keep these methods lightweight. If a custom display method performs expensive database work for every row, the admin can become slow. In those cases, combine display methods with queryset optimization or annotations.
The goal is context, not decoration.
Permissions Keep The Admin Practical
The admin is powerful, so access matters.
You can use Django’s built-in staff and model permissions to control who can view, add, change, or delete records. You can also customize admin behavior when a model needs stricter rules.
For example, you might prevent deletion from the admin:
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
list_display = ["order_number", "customer_email", "status", "created_at"]
def has_delete_permission(self, request, obj=None):
return False
That is a small change, but it can make the admin safer for operational use.
Permissions are one reason the admin can replace early internal dashboards. You already get authentication, staff access, model permissions, and a consistent editing interface.
Still, do not expose the admin to normal customers. It is for trusted users who understand the data and the workflow.
Choose The Admin When The Workflow Fits
The Django admin will not replace every dashboard.
If you need custom analytics, polished customer-facing UI, complex approval flows, or highly guided interactions, build the right interface.
But for many internal workflows, the admin is enough for the first version:
- Staff need to find records.
- Staff need to filter and search.
- Staff need to edit structured data.
- Staff need safe bulk actions.
- Staff need to manage related objects.
- Staff need controlled access.
That is exactly where Django admin shines.
Start there when the users are trusted internal staff and the workflow is mostly data management. Improve the admin first. Build a custom dashboard only when the admin stops matching the job.
That way, you spend less time rebuilding tools Django already gave you, and more time solving the parts of the product that actually need custom work.








