2026'da Django Admin Özelleştirmesi: Eylemler, Filtreler ve Mülakat Soruları

Özel eylemler, liste filtreleri, fieldsetler ve inline modellerle Django 6 admin özelleştirmesinde uzmanlaşma rehberi. Mülakat soruları dahil.

2026'da Django Admin Özelleştirmesi: Eylemler, Filtreler ve Mülakat Soruları

Django admin özelleştirmesi, yerleşik admin arayüzünü basit bir iskele aracından üretim uygulamaları için amaca yönelik bir arka ofise dönüştürür. Django 6, filtreler için facet sayıları, özelleştirilebilir şifre değiştirme formları ve Font Awesome ikonları gibi gelişmiş admin özellikleriyle birlikte gelir ve iyi yapılandırılmış bir admin panelini gerçekten kullanımı keyifli hale getirir.

Temel Bilgi

ModelAdmin, tüm admin özelleştirmelerinin merkezi sınıfıdır. list_display, list_filter, search_fields, fieldsets ve özel eylemlerde ustalaşmak, admin yapılandırma ihtiyaçlarının %90'ını karşılar.

ModelAdmin Temelleri: list_display ve search_fields

list_display, değişiklik listesi sayfasında hangi sütunların görüneceğini kontrol eder. Basit alan adlarının ötesinde, hesaplanmış değerler için @admin.display() dekoratörü ile işaretlenmiş çağrılabilir fonksiyonları kabul eder. search_fields, varsayılan olarak icontains kullanarak kayıtları filtreleyen arama kutusunu etkinleştirir.

python
# 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)

@admin.display() dekoratörü, short_description'ı fonksiyon özelliği olarak ayarlama şeklindeki eski kalıbın yerini alır. boolean=True parametresi, True/False metni yerine onay işareti veya X ikonu render eder. list_select_related kullanımı, list_display'de foreign key alanlarına erişirken N+1 sorgularını önler.

SimpleListFilter ile Özel list_filter

list_filter, değişiklik listesi sayfasındaki kenar çubuğu filtrelerini etkinleştirir. Alan adları temel filtreleme için çalışır, ancak SimpleListFilter birden fazla alanı kapsayan veya özel sorgular gerektiren karmaşık filtreleme mantığını ele alır.

python
# 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, yazar açılır listesini veritabanındaki her kullanıcıyı listelemek yerine gerçekten makale sahibi olan yazarlarla sınırlar. Bu, ilişkili modelin çok sayıda kaydı olduğunda kullanılabilirliği artırır.

Django 6 Facetleri: Kenar Çubuğunda Filtre Sayıları

Django 6, her filtre seçeneğinin yanında eşleşen nesne sayısını gösteren facet sayılarını tanıttı. show_facets özelliği bu davranışı kontrol eder.

python
# 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 sayıları, her filtre seçeneği için ek COUNT sorguları çalıştırır. Milyonlarca satır içeren tablolarda performans düşüşünü önlemek için show_facets = ShowFacets.NEVER ayarlanmalıdır. Sayılar, filtreler uygulandıkça dinamik olarak güncellenir ve mevcut filtre kombinasyonuyla kaç kaydın eşleştiğini gösterir.

Toplu İşlemler için Özel Admin Eylemleri

Admin eylemleri, seçilen nesneler üzerinde toplu işlemler gerçekleştirir. @admin.action dekoratörü, izinleri ve açılır menüde gösterilen açıklamayı ayarlar.

python
# 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 actions

Bir eylemden HttpResponse döndürmek dosya indirmeyi tetikler veya bir onay sayfasına yönlendirir. permissions parametresi, eylemi belirli model izinlerine sahip kullanıcılarla sınırlar. get_actions() metodunu geçersiz kılmak, mevcut isteğe göre eylemleri dinamik olarak göstermeyi veya gizlemeyi sağlar.

Django mülakatlarında başarılı olmaya hazır mısın?

İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.

Düzenli Formlar için Fieldsetler ve Inline Modeller

fieldsets, ekleme ve değiştirme formlarını daraltılabilir bölümler halinde düzenler. TabularInline ve StackedInline, ilişkili modelleri doğrudan üst formda gömülü hale getirir ve sayfalar arasında gezinmeyi ortadan kaldırır.

python
# 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, ilişkili nesneleri az alan içeren basit modeller için uygun kompakt tablo formatında görüntüler. StackedInline, her ilişkili nesneyi ayrı bir form bloğu olarak gösterir ve çok alanlı modeller için daha uygundur. autocomplete_fields seçeneği, varsayılan açılır listeyi AJAX arama widget'ı ile değiştirir; bu, ilişkili modelin binlerce kaydı olduğunda gereklidir.

Admin Sitesini Özelleştirme

AdminSite alt sınıflaması, farklı markalama, izinler veya model kayıtlarına sahip birden fazla admin arayüzüne olanak tanır. Django 6, daha katı şifre politikaları için password_change_form özelleştirmesi ekledi.

python
# 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),

Her AdminSite örneğinin kendi URL ad alanı, giriş sayfası ve kayıtlı modeller kümesi vardır. Bu kalıp, editörlerin kullanıcı yönetimini veya sistem ayarlarını görmemesi gereken farklı kullanıcı rollerine sahip uygulamalar için uygundur.

Mülakat Soruları: Django Admin Özelleştirmesi

Bu sorular Django teknik mülakatlarında sıkça karşılaşılır. Sadece sözdizimini değil, altta yatan mekanizmaları anlamak orta seviye adayları kıdemli mühendislerden ayırır.

S: list_display'e sıralanamayan hesaplanmış bir sütun nasıl eklenir?

@admin.display() dekoratörü, sıralama için hangi veritabanı alanının kullanılacağını belirten bir ordering parametresi kabul eder. Bu olmadan sütun başlığı tıklanabilir değildir. Birden fazla kaydı toplayan veya harici servisleri çağıran hesaplanmış değerler, sıralama yapılacak temel bir veritabanı sütunu olmadığından sıralanamaz.

S: Facet sayılarının performans etkisi nedir ve ne zaman devre dışı bırakılmalıdır?

Facetler, aktif filtre sayısıyla çarpılan her filtre seçeneği için bir COUNT sorgusu çalıştırır. 10 milyon satırlı bir tabloda 3 alanda her birinde 5 filtre seçeneği varsa, bu sayfa yüklemesi başına 15 ek COUNT sorgusudur. Herhangi bir filtre uygun indeksler olmadan 100.000 satırın üzerinde bir tabloyu hedeflediğinde show_facets = ShowFacets.NEVER ayarlanmalıdır.

S: Admin eylemleri özel admin görünümlerinden nasıl farklıdır?

Eylemler, seçilen nesnelerin queryset'i üzerinde çalışır ve değişiklik listesinin seçim kutularıyla entegre olur. Özel admin görünümleri, seçili kayıtlar üzerinde çalışmayan raporlar, içe aktarmalar veya panolar için uygun, özel URL'ler aracılığıyla erişilen bağımsız sayfalardır. Eylemler isteği engellediğinden uzun süren işlemler gerçekleştirmemelidir.

S: Varsayılan filtre yerine neden RelatedOnlyFieldListFilter kullanılır?

Varsayılan RelatedFieldListFilter, ilişkili tablodaki tüm nesneleri sorgular. Bir ForeignKey 100.000 kayıtlı User modeline işaret ettiğinde, filtre açılır listesi tüm 100.000 seçeneği yükler ve tarayıcıyı dondurur. RelatedOnlyFieldListFilter, yalnızca mevcut modelin kayıtlarında var olan değerleri sorgular, tipik olarak çok daha küçük bir kümedir.

Üretimde Kaçınılması Gereken Yaygın Hatalar

Geliştirmede çalışan birkaç kalıp ölçekte sorunlara neden olur.

python
# 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_total

get_queryset() metodunu anotasyonlar eklemek için geçersiz kılmak, her satır için veritabanını sorgulamak yerine hesaplanmış değerleri tek bir sorguda tutar. Django Debug Toolbar ile admin profillemesi, N+1 sorgularını üretime ulaşmadan önce yakalamayı sağlar.

Üçüncü Parti Admin Temaları: Django Unfold

Django Unfold, Tailwind CSS ile oluşturulmuş Django 6 için en aktif olarak sürdürülen admin temasıdır. Standart admini değiştirmek yerine genişletir, bu nedenle mevcut ModelAdmin yapılandırmaları çalışmaya devam eder.

python
# 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, karanlık mod, duyarlı düzenler, kenar çubuğu gezintisi ve RangeNumericFilter ve RangeDateFilter gibi gelişmiş filtre türleri ekler. HTMX ve Alpine.js entegrasyonu, tam bir SPA framework'ün yükü olmadan etkileşim sağlar. Özel bir pano oluşturmadan cilalı bir admin paneline ihtiyaç duyan ekipler için Unfold haftalarca frontend çalışmasından tasarruf sağlar.

Pratik yapmaya başla!

Mülakat simülatörleri ve teknik testlerle bilgini test et.

Django Admin Özelleştirmesi Hakkında Hatırlanması Gerekenler

  • @admin.display() ile list_display sütunları kontrol eder; sıralanabilir hesaplanmış değerler için ordering eklenir
  • SimpleListFilter, birden fazla alanı kapsayan veya tarih aralıkları gerektiren özel filtreleme mantığını ele alır
  • Django 6 facetleri filtre sayılarını gösterir; COUNT sorgu yükünü önlemek için büyük tablolarda ShowFacets.NEVER ile devre dışı bırakılır
  • @admin.action(permissions=[...]) ile admin eylemleri toplu işlemleri yönetir; dosya dışa aktarımları için HttpResponse döndürülür
  • Liste görünümlerinde N+1 sorgularını önlemek için list_select_related ve get_queryset() içinde anotasyonlu queryset'ler kullanılır
  • RelatedOnlyFieldListFilter, büyük tablolara ForeignKey ile filtreleme yaparken tarayıcı donmalarını önler
  • Bu kalıpları özellikle admin yapılandırmasını hedefleyen Django mülakat soruları ile pratik yapılabilir
Günün meydan okuması

Django kodundaki hatayı bulabilir misin?

Gerçek bir kod parçası, gizli bir hata, günde bir deneme. Denemek için hesap gerekmez.

Anthony Fillion-Maillet

Yazan:

Anthony Fillion-Maillet

SharpSkill kurucusu

10 yılı aşkın süredir fullstack geliştirici. SharpSkill’i yönetiyor ve burada yayımlanan her şeyden sorumlu.

13 Eylül 2026 tarihinde güncellendi

Paylaş

İlgili makaleler