Kustomisasi Django Admin 2026: Actions, Filters, dan Pertanyaan Interview
Pelajari kustomisasi Django admin dengan custom actions, list filters, fieldsets, dan inlines untuk Django 6. Lengkap dengan pertanyaan interview.

Kustomisasi Django admin mengubah antarmuka admin bawaan dari sekadar alat scaffolding menjadi back office yang dirancang khusus untuk aplikasi produksi. Django 6 hadir dengan fitur admin yang ditingkatkan termasuk facet counts untuk filters, form perubahan password yang dapat dikustomisasi, dan ikon Font Awesome, menjadikan admin yang dikonfigurasi dengan baik benar-benar menyenangkan untuk digunakan.
ModelAdmin adalah kelas sentral untuk semua kustomisasi admin. Kuasai list_display, list_filter, search_fields, fieldsets, dan custom actions untuk menangani 90% kebutuhan konfigurasi admin.
Dasar ModelAdmin: list_display dan search_fields
list_display mengontrol kolom mana yang muncul di halaman change list. Selain nama field sederhana, parameter ini juga menerima callable yang didekorasi dengan @admin.display() untuk nilai yang dihitung. search_fields mengaktifkan kotak pencarian yang memfilter record menggunakan icontains secara default.
# admin.py
from django.contrib import admin
from django.utils.html import format_html
from .models import Article
class ArticleAdmin(admin.ModelAdmin):
list_display = ["title", "author_name", "status", "publish_date", "is_recent"]
search_fields = ["title", "author__email", "content"]
list_per_page = 50
list_select_related = ["author"] # Avoid N+1 queries
@admin.display(description="Author", ordering="author__last_name")
def author_name(self, obj):
return f"{obj.author.first_name} {obj.author.last_name}"
@admin.display(description="Recent?", boolean=True)
def is_recent(self, obj):
from django.utils import timezone
return obj.publish_date >= timezone.now() - timezone.timedelta(days=7)
admin.site.register(Article, ArticleAdmin)Dekorator @admin.display() menggantikan pola lama pengaturan short_description sebagai atribut fungsi. Parameter boolean=True merender ikon centang atau X sebagai pengganti teks True/False. Penggunaan list_select_related mencegah query N+1 saat mengakses field foreign key di list_display.
Custom list_filter dengan SimpleListFilter
list_filter mengaktifkan filter sidebar di halaman change list. Nama field bekerja untuk filtering dasar, tetapi SimpleListFilter menangani logika filtering kompleks yang melibatkan beberapa field atau memerlukan query khusus.
# admin.py
from datetime import date, timedelta
from django.contrib import admin
from django.utils.translation import gettext_lazy as _
from .models import Article
class PublishDateFilter(admin.SimpleListFilter):
title = _("publish period")
parameter_name = "published" # URL parameter: ?published=this_week
def lookups(self, request, model_admin):
# Returns (value, label) tuples for the filter sidebar
return [
("this_week", _("This week")),
("this_month", _("This month")),
("this_year", _("This year")),
("unpublished", _("Not published")),
]
def queryset(self, request, queryset):
# Filter the queryset based on self.value()
today = date.today()
if self.value() == "this_week":
return queryset.filter(publish_date__gte=today - timedelta(days=7))
if self.value() == "this_month":
return queryset.filter(
publish_date__year=today.year, publish_date__month=today.month
)
if self.value() == "this_year":
return queryset.filter(publish_date__year=today.year)
if self.value() == "unpublished":
return queryset.filter(publish_date__isnull=True)
return queryset
class ArticleAdmin(admin.ModelAdmin):
list_filter = [
"status",
PublishDateFilter,
("author", admin.RelatedOnlyFieldListFilter), # Show only authors with articles
]RelatedOnlyFieldListFilter membatasi dropdown author hanya kepada author yang benar-benar memiliki artikel, bukan menampilkan semua user di database. Ini meningkatkan kegunaan ketika model terkait memiliki banyak record.
Django 6 Facets: Jumlah Filter di Sidebar
Django 6 memperkenalkan facet counts yang menampilkan jumlah objek yang cocok di samping setiap opsi filter. Atribut show_facets mengontrol perilaku ini.
# admin.py
from django.contrib import admin
from django.contrib.admin import ShowFacets
from .models import Article
class ArticleAdmin(admin.ModelAdmin):
list_display = ["title", "status", "category"]
list_filter = ["status", "category", "author"]
show_facets = ShowFacets.ALWAYS # Always display counts
# Alternative values:
# ShowFacets.ALLOW - Show counts when ?_facets=1 in URL
# ShowFacets.NEVER - Never show counts (for large datasets)Facet counts mengeksekusi query COUNT tambahan untuk setiap opsi filter. Pada tabel dengan jutaan baris, atur show_facets = ShowFacets.NEVER untuk menghindari degradasi performa. Jumlah tersebut diperbarui secara dinamis saat filter diterapkan, menunjukkan berapa banyak record yang cocok dengan kombinasi filter saat ini.
Custom Admin Actions untuk Operasi Bulk
Admin actions melakukan operasi bulk pada objek yang dipilih. Dekorator @admin.action mengatur permissions dan deskripsi yang ditampilkan di dropdown.
# admin.py
from django.contrib import admin
from django.contrib import messages
from django.utils.translation import ngettext
from .models import Article
@admin.action(description="Publish selected articles", permissions=["change"])
def publish_articles(modeladmin, request, queryset):
# Exclude already published articles
unpublished = queryset.exclude(status="published")
updated = unpublished.update(status="published")
modeladmin.message_user(
request,
ngettext(
"%d article was published.",
"%d articles were published.",
updated,
)
% updated,
messages.SUCCESS,
)
@admin.action(description="Export selected as JSON")
def export_as_json(modeladmin, request, queryset):
from django.http import HttpResponse
from django.core import serializers
response = HttpResponse(content_type="application/json")
response["Content-Disposition"] = 'attachment; filename="articles.json"'
serializers.serialize("json", queryset, stream=response)
return response # Returning HttpResponse triggers file download
class ArticleAdmin(admin.ModelAdmin):
actions = [publish_articles, export_as_json]
def get_actions(self, request):
# Conditionally remove actions based on user permissions
actions = super().get_actions(request)
if not request.user.has_perm("articles.delete_article"):
del actions["delete_selected"]
return actionsMengembalikan HttpResponse dari action akan memicu download file atau redirect ke halaman konfirmasi. Parameter permissions membatasi action hanya untuk pengguna dengan permissions model tertentu. Override get_actions() untuk menampilkan atau menyembunyikan actions secara dinamis berdasarkan request saat ini.
Siap menguasai wawancara Django Anda?
Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.
Fieldsets dan Inline Models untuk Form yang Terorganisir
fieldsets mengorganisir form add dan change ke dalam section yang dapat dilipat. TabularInline dan StackedInline menyematkan model terkait langsung di form induk, menghilangkan kebutuhan navigasi antar halaman.
# admin.py
from django.contrib import admin
from .models import Article, ArticleImage, ArticleComment
class ArticleImageInline(admin.TabularInline):
model = ArticleImage
extra = 1 # Number of empty forms to display
max_num = 5 # Maximum images per article
fields = ["image", "caption", "order"]
class ArticleCommentInline(admin.StackedInline):
model = ArticleComment
extra = 0
readonly_fields = ["created_at", "author"]
can_delete = True
class ArticleAdmin(admin.ModelAdmin):
fieldsets = [
(
None,
{
"fields": ["title", "slug", "author"],
},
),
(
"Content",
{
"fields": ["content", "excerpt"],
"classes": ["wide"], # Full-width fields
},
),
(
"Publishing",
{
"fields": ["status", "publish_date", "category", "tags"],
"classes": ["collapse"], # Collapsed by default
},
),
(
"SEO",
{
"fields": ["meta_title", "meta_description"],
"classes": ["collapse"],
"description": "Search engine optimization fields",
},
),
]
inlines = [ArticleImageInline, ArticleCommentInline]
prepopulated_fields = {"slug": ["title"]} # Auto-generate slug from title
autocomplete_fields = ["author", "category"] # AJAX-powered select
filter_horizontal = ["tags"] # Widget for M2M selection
admin.site.register(Article, ArticleAdmin)TabularInline menampilkan objek terkait dalam format tabel yang ringkas, cocok untuk model sederhana dengan sedikit field. StackedInline menampilkan setiap objek terkait sebagai blok form terpisah, lebih baik untuk model dengan banyak field. Opsi autocomplete_fields mengganti dropdown default dengan widget pencarian AJAX, sangat penting ketika model terkait memiliki ribuan record.
Kustomisasi Admin Site
Membuat subclass dari AdminSite memungkinkan multiple antarmuka admin dengan branding, permissions, atau registrasi model yang berbeda. Django 6 menambahkan kustomisasi password_change_form untuk kebijakan password yang lebih ketat.
# admin.py
from django.contrib import admin
from django.contrib.admin import AdminSite
from .models import Article, Category
class ContentAdminSite(AdminSite):
site_header = "Content Management"
site_title = "CMS"
index_title = "Dashboard"
def has_permission(self, request):
# Restrict to staff in the 'editors' group
return (
request.user.is_active
and request.user.is_staff
and request.user.groups.filter(name="editors").exists()
)
content_admin = ContentAdminSite(name="content_admin")
content_admin.register(Article, ArticleAdmin)
content_admin.register(Category)
# In urls.py:
# path('content-admin/', content_admin.urls),Setiap instance AdminSite memiliki namespace URL sendiri, halaman login, dan set model yang terdaftar. Pola ini cocok untuk aplikasi dengan role pengguna yang berbeda di mana editor tidak seharusnya melihat manajemen pengguna atau pengaturan sistem.
Pertanyaan Interview: Kustomisasi Django Admin
Pertanyaan-pertanyaan ini sering muncul dalam interview teknis Django. Memahami mekanisme yang mendasari, bukan hanya sintaksnya, membedakan kandidat level menengah dari engineer senior.
T: Bagaimana cara menambahkan kolom yang dihitung ke list_display yang tidak bisa diurutkan?
Dekorator @admin.display() menerima parameter ordering yang menentukan field database mana yang digunakan untuk pengurutan. Tanpa itu, header kolom tidak dapat diklik. Nilai yang dihitung yang mengagregasi beberapa record atau memanggil layanan eksternal tidak dapat diurutkan karena tidak ada kolom database yang mendasari untuk pengurutan.
T: Apa dampak performa dari facet counts, dan kapan harus dinonaktifkan?
Facets mengeksekusi satu query COUNT per opsi filter, dikalikan dengan jumlah filter yang aktif. Pada tabel dengan 10 juta baris dan 5 opsi filter masing-masing di 3 field, itu berarti 15 query COUNT tambahan per load halaman. Atur show_facets = ShowFacets.NEVER ketika filter menargetkan tabel dengan lebih dari 100.000 baris tanpa index yang sesuai.
T: Apa perbedaan admin actions dengan custom admin views?
Actions beroperasi pada queryset objek yang dipilih dan terintegrasi dengan checkbox seleksi change list. Custom admin views adalah halaman standalone yang diakses melalui URL khusus, cocok untuk laporan, import, atau dashboard yang tidak beroperasi pada record yang dipilih. Actions tidak seharusnya melakukan operasi yang memakan waktu lama karena akan memblokir request.
T: Mengapa menggunakan RelatedOnlyFieldListFilter daripada filter default?
RelatedFieldListFilter default melakukan query semua objek di tabel terkait. Ketika ForeignKey menunjuk ke model User dengan 100.000 record, dropdown filter memuat semua 100.000 opsi, membuat browser freeze. RelatedOnlyFieldListFilter hanya melakukan query nilai yang ada di record model saat ini, biasanya set yang jauh lebih kecil.
Kesalahan Umum yang Harus Dihindari di Produksi
Beberapa pola yang bekerja di development menyebabkan masalah saat scale.
# admin.py
from django.contrib import admin
from .models import Order
class OrderAdmin(admin.ModelAdmin):
# Mistake: Accessing related fields without list_select_related
# This causes N+1 queries, one per row in the list view
list_display = ["id", "customer_email", "total"]
# Fix: Prefetch related objects
list_select_related = ["customer"]
def customer_email(self, obj):
return obj.customer.email
# Mistake: search_fields on unindexed large text columns
# search_fields = ["notes"] # Slow on large tables
# Fix: Limit search to indexed fields
search_fields = ["id", "customer__email"]
# Mistake: Expensive computation in list_display
# def order_total(self, obj):
# return sum(item.price * item.qty for item in obj.items.all()) # N+1
# Fix: Use annotation in get_queryset
def get_queryset(self, request):
from django.db.models import Sum, F
qs = super().get_queryset(request)
return qs.annotate(computed_total=Sum(F("items__price") * F("items__qty")))
@admin.display(description="Total", ordering="computed_total")
def total(self, obj):
return obj.computed_totalMeng-override get_queryset() untuk menambahkan annotations menjaga nilai yang dihitung dalam satu query daripada mengakses database sekali per baris. Profile admin dengan Django Debug Toolbar untuk menangkap query N+1 sebelum mencapai produksi.
Tema Admin Pihak Ketiga: Django Unfold
Django Unfold adalah tema admin yang paling aktif dikelola untuk Django 6, dibangun dengan Tailwind CSS. Ini memperluas daripada mengganti admin standar, sehingga konfigurasi ModelAdmin yang ada tetap berfungsi.
# settings.py
INSTALLED_APPS = [
"unfold", # Must come before django.contrib.admin
"unfold.contrib.filters", # Advanced filter widgets
"unfold.contrib.forms", # Enhanced form widgets
"django.contrib.admin",
# ...
]Unfold menambahkan dark mode, layout responsif, navigasi sidebar, dan tipe filter lanjutan seperti RangeNumericFilter dan RangeDateFilter. Integrasi HTMX dan Alpine.js menyediakan interaktivitas tanpa overhead framework SPA penuh. Untuk tim yang membutuhkan admin yang dipoles tanpa membangun dashboard kustom, Unfold menghemat berminggu-minggu pekerjaan frontend.
Mulai berlatih!
Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.
Hal yang Perlu Diingat tentang Kustomisasi Django Admin
list_displaydengan@admin.display()mengontrol kolom; tambahkanorderinguntuk nilai yang dihitung yang dapat diurutkanSimpleListFiltermenangani logika filter kustom yang melibatkan beberapa field atau memerlukan rentang tanggal- Django 6 facets menampilkan jumlah filter; nonaktifkan dengan
ShowFacets.NEVERpada tabel besar untuk menghindari overhead query COUNT - Admin actions dengan
@admin.action(permissions=[...])menangani operasi bulk; kembalikanHttpResponseuntuk ekspor file - Gunakan
list_select_relateddan queryset dengan annotations diget_queryset()untuk mencegah query N+1 di list views RelatedOnlyFieldListFiltermencegah browser freeze saat memfilter berdasarkan ForeignKey ke tabel besar- Praktikkan pola-pola ini dengan pertanyaan interview Django yang menargetkan konfigurasi admin secara khusus
Bisakah kamu menemukan bug di Django?
Satu potongan kode nyata, satu bug tersembunyi, satu percobaan per hari. Tanpa akun untuk mencoba.

Ditulis oleh
Anthony Fillion-MailletPendiri SharpSkill
Developer fullstack selama lebih dari 10 tahun. Ia menjalankan SharpSkill dan bertanggung jawab atas semua yang diterbitkan di sini.
Diperbarui 13 September 2026
Bagikan
Artikel terkait

Advanced Django Middleware 2026: Custom Middleware, Logging, dan Pertanyaan Interview
Panduan lengkap Django middleware di tahun 2026 mencakup pembuatan custom middleware, implementasi logging yang efektif, serta pertanyaan interview yang sering muncul untuk developer Django tingkat senior.

Django Signals vs Celery Tasks 2026: Kapan Menggunakan Yang Mana
Pelajari kapan menggunakan Django signals versus Celery tasks. Membahas penanganan event sinkron vs asinkron, implikasi performa, dan pertanyaan interview untuk developer Django.

Django Channels 2026: WebSockets, Real-Time, dan Pertanyaan Interview
Kuasai Django Channels 4.x dengan WebSocket consumers, Redis channel layers, dan pertanyaan interview umum. Contoh kode production-ready dengan deployment ASGI.