S.
Home
AboutProjectsBlogLabGalleryContact
HomeProjectsBlogContact

© 2026 Shohjahon Rajabov. All rights reserved.

Back to blog
Engineering
February 10, 2024
2 min read
DjangoPython

Django's admin is underrated: turning it into a real back office

For internal tools I rarely build a custom dashboard first. A properly customized Django admin gets a working back office in an afternoon - here is how far I push it before reaching for anything else.

When a project needs an internal back office - somewhere staff can see records, fix data, and run the occasional bulk action - my first move is almost never a custom dashboard. It is the Django admin. People dismiss it as scaffolding for developers, but customized properly it is a genuine tool, and it costs an afternoon instead of a sprint.

Out of the box you register a model and get CRUD. That is the boring part. The value starts with ModelAdmin, where a few lines turn a raw table into something a non-developer can actually use.

@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
    list_display = ("id", "customer", "status", "total", "created_at")
    list_filter = ("status", "created_at")
    search_fields = ("customer__name", "customer__email")
    readonly_fields = ("created_at", "total")
    date_hierarchy = "created_at"

That handful of attributes gives you a scannable table, a sidebar of filters, a working search across related fields, and a date drill-down. No frontend, no queries written by hand. For most internal needs, this is already the whole product.

The next step up is inlines, which solve the thing custom dashboards usually get wrong: editing a record and its children together. An order and its line items on one page, saved in one transaction.

class OrderItemInline(admin.TabularInline):
    model = OrderItem
    extra = 0

class OrderAdmin(admin.ModelAdmin):
    inlines = [OrderItemInline]

When staff need to do something to many rows at once - mark fifty orders shipped - custom actions beat exporting to a spreadsheet and back. They appear in a dropdown above the list and operate on the selected rows.

@admin.action(description="Mark selected as shipped")
def mark_shipped(modeladmin, request, queryset):
    queryset.update(status="shipped")

Two customizations matter more than the cosmetic ones. First, business logic on save: overriding save_model lets the admin run the same rules your app does, so an edit here cannot quietly break an invariant. Second, scoping - not every staff member should see everything. Overriding get_queryset filters the rows a user can even load, so permissions are enforced at the database, not hoped for in a template.

I do know where the ceiling is. Once the workflow stops looking like "edit rows in tables" - dashboards with charts, multi-step flows, anything customer-facing - the admin is the wrong tool and I build a real UI. But that point comes much later than people assume. A large share of "we need an admin panel" is fully served by the one Django hands you for free, if you are willing to actually configure it.

Share:Share on Telegram
More posts
Infrastructure

What three Telegram bots taught me about state

2 min · April 18, 2026
Engineering

The caching bug that made me stop trusting TTLs

1 min · February 10, 2026