Skip to content
C Codeloom
Django

Django Admin Customization: A Practical Tutorial

Go beyond the default Django admin: customize ModelAdmin classes, list views, search, filters, inline editing, and admin actions to build a usable backoffice your team will actually enjoy.

·4 min read · By Codeloom
Intermediate 11 min read

What you'll learn

  • How ModelAdmin classes shape the admin UI
  • Customizing list_display, filters, and search
  • Using inlines for related models
  • Building admin actions for bulk operations
  • Overriding templates and form fields safely
  • Common pitfalls when customizing admin at scale

Prerequisites

  • Comfort with Django models and migrations

The Django admin is one of the framework’s killer features, but the out-of-the-box version is rarely what you want to ship to internal users. With a few targeted customizations you can turn it into a real backoffice tool.

What and Why

Django’s admin is a generated CRUD UI for your models. By registering each model with a ModelAdmin subclass, you can change which columns appear, what filters are available, which fields are editable inline, and what bulk actions users can run. It is not a replacement for a customer-facing app, but for ops, support, and content teams it is unbeatable.

Mental Model

Think of the admin as three layers stacked on top of your ORM. The model layer defines data. The ModelAdmin layer defines how that data is presented. The template and form layer renders it as HTML. Most customizations live in the middle layer; you only drop down to templates when you genuinely need to.

Hands-on Example

Suppose you have an Order model with a customer, total, status, and many OrderItem rows. A useful admin might look like this:

from django.contrib import admin
from .models import Order, OrderItem

class OrderItemInline(admin.TabularInline):
    model = OrderItem
    extra = 0
    readonly_fields = ("unit_price",)

@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
    list_display = ("id", "customer", "status", "total", "created_at")
    list_filter = ("status", "created_at")
    search_fields = ("id", "customer__email")
    ordering = ("-created_at",)
    inlines = [OrderItemInline]
    actions = ["mark_as_shipped"]

    @admin.action(description="Mark selected orders as shipped")
    def mark_as_shipped(self, request, queryset):
        updated = queryset.update(status="shipped")
        self.message_user(request, f"{updated} orders marked shipped.")
A request enters the admin, hits the ModelAdmin for the URL, which loads querysets, applies filters and search, renders via the template, and returns HTML.

With this in place, support staff can find an order by email, filter by status, and ship a whole batch at once.

Common Pitfalls

The first pitfall is list_display with expensive computed methods. Each row calls them, so adding def total_with_tax(self, obj) that hits the database multiplies queries. Annotate in get_queryset instead.

The second is overusing raw_id_fields versus autocomplete_fields. For tables with millions of rows, the default select widget will freeze the admin; switch to autocomplete_fields and register a search_fields on the related model’s admin.

The third is forgetting permissions. Custom actions still respect has_change_permission, but custom views you wire into get_urls do not unless you check explicitly.

Finally, do not override admin templates wholesale. You will inherit a maintenance burden every Django release. Prefer template blocks or change_form_template for the specific page that needs tweaks.

Practical Tips

Use @admin.display(ordering="...", description="...") to make computed columns sortable and well-labeled. Use readonly_fields to expose internal IDs without letting staff edit them. Group fields with fieldsets for long forms. For relationship-heavy models, combine list_select_related and list_prefetch_related (3.1+) to keep the changelist snappy.

If your admin grows beyond a dozen models, split admin.py per app and consider a custom AdminSite subclass with a project-wide header and grouped navigation.

Wrap-up

The Django admin scales from prototype to internal product as long as you treat ModelAdmin as your main customization surface. Tune list_display, filters, search, and actions first; reach for templates and custom views only when the standard knobs run out. Done well, your admin becomes the tool your team opens every morning instead of avoiding.