Django Admin'i Müşteri İçin İçerik Paneline Dönüştürmek
Bir müşteri projesinde en pahalı kararlardan biri, içerik yönetimi için sıfırdan bir panel yazmaya karar vermektir. Haftalar gider, sonuçta Django'nun kutudan çıkan admin panelinin yaptığı işi yapan bir şey elde edersiniz.
Çoğu durumda gerekli olan şey yeni bir panel değil, mevcut admin'i teknik olmayan birinin rahatça kullanabileceği hale getirmektir. Bu yazıda bunu yapan somut ayarları anlatıyorum.
Liste ekranını okunabilir hale getirmek
Varsayılan admin listesi her kaydı __str__ çıktısıyla gösterir. On kayıtta sorun değil, binlerce kayıtta kullanılamaz hale gelir.
from django.contrib import admin
@admin.register(Listing)
class ListingAdmin(admin.ModelAdmin):
list_display = ("title", "city", "price", "is_published", "updated_at")
list_filter = ("is_published", "city", "category")
search_fields = ("title", "description")
ordering = ("-updated_at",)
list_per_page = 50Bu beş satır, listeyi "kayıt yığını"ndan "çalışılabilir tablo"ya çevirir. list_filter sağ tarafta filtre paneli açar, search_fields üstte arama kutusu koyar.
Bir uyarı: search_fields'a çok sayıda alan eklemek, her aramada OR zinciri üreteceği için büyük tablolarda yavaşlar. Gerçekten arananları koyun.
Doğrudan listeden düzenleme
İçerik ekibinin en sık yaptığı işlem genelde tek bir alanı değiştirmektir — bir ilanı yayından kaldırmak gibi. Her seferinde detay sayfasına girmek yerine:
list_editable = ("is_published",)Artık liste ekranındaki onay kutusundan doğrudan değiştirebilirler. list_editable içindeki alanların list_display'de de bulunması gerekir ve bağlantı sütunu olmamalıdır. Varsayılan olarak ilk sütun detay sayfasına giden bağlantıdır; bu yüzden düzenlenebilir alanı ondan sonraya koyun ya da list_display_links'i açıkça verin. İyi çalışan bir desen: is_published gibi bir boolean ile sort_order gibi bir sayıyı listeden düzenlenebilir yapmak, bağlantıyı ise başlıkta bırakmak.
Toplu işlemler
Bazı işler tek tek yapılamayacak kadar tekrarlı olur. Özel bir admin action bunu çözer:
@admin.action(description="Seçili kayıtları yayınla")
def publish_selected(modeladmin, request, queryset):
updated = queryset.update(is_published=True)
modeladmin.message_user(request, f"{updated} kayıt yayınlandı.")
class ListingAdmin(admin.ModelAdmin):
actions = [publish_selected]queryset.update() kullanmak tek sorguda çalışır — döngüyle save() çağırmaktan çok daha hızlıdır. Ancak dikkat: update() model sinyallerini tetiklemez. Eğer kaydetme sırasında çalışan bir sinyaliniz varsa (ör. önbellek temizleme), döngü kullanmanız veya sinyali elle çağırmanız gerekir.
Formu anlaşılır bölümlere ayırmak
Yirmi alanlı bir model, admin formunda yirmi satırlık bir liste olarak görünür. Hangi alanın neyle ilgili olduğu belli olmaz:
fieldsets = (
("Temel Bilgiler", {
"fields": ("title", "slug", "category"),
}),
("Fiyatlandırma", {
"fields": ("price", "currency"),
}),
("SEO", {
"fields": ("meta_title", "meta_description"),
"classes": ("collapse",),
"description": "Boş bırakılırsa başlık ve açıklamadan otomatik üretilir.",
}),
)collapse sınıfı bölümü katlanmış başlatır — nadiren dokunulan alanları gizler. description alanı, kullanıcının "burayı doldurmak zorunda mıyım" sorusunu sormadan cevaplar.
İlişkili kayıtları aynı ekranda düzenlemek
Bir ilana ait fotoğrafları ayrı bir menüden yönetmek zorunda kalmak can sıkıcıdır. Inline bunu çözer:
class ListingImageInline(admin.TabularInline):
model = ListingImage
extra = 1
fields = ("image", "alt_text", "order")
class ListingAdmin(admin.ModelAdmin):
inlines = [ListingImageInline]TabularInline kompakt bir tablo verir, StackedInline ise her kaydı alt alta form olarak gösterir. Alan sayısı azsa tabular, çoksa stacked daha okunaklıdır.
Çok dilli içerik Django admin'de nasıl yönetilir?
Bir sitede içeriğin 15 dilde tutulması gerektiğinde, her dil için ayrı alan açmak modeli şişirir. İki yaygın yaklaşım var.
Seçenek 1: kendi çeviri modeliniz, inline ile düzenlenir. Bu belirli bir projeden değil, genel bir taslaktır:
class RouteTranslation(models.Model):
route = models.ForeignKey("Route", on_delete=models.CASCADE,
related_name="translations")
language = models.CharField(max_length=5, choices=LANGUAGES)
title = models.CharField(max_length=200)
description = models.TextField(blank=True)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["route", "language"], name="one_translation_per_language"
),
]
class RouteTranslationInline(admin.TabularInline):
model = RouteTranslation
extra = 0Kısıt aynı dilin iki kez eklenmesini veritabanı seviyesinde engeller; içerik ekibi tek ekrandan tüm dilleri görüp düzenler.
Seçenek 2: django-parler. Çok dilli bir müşteri projesinde bunu kullanıyorum. Her çevrilebilir model kendi *_translation tablosunu alır, yani yeni dil eklemek migration gerektirmez; TranslatableAdmin de düzenleme formuna dil başına bir sekme koyar. Eksik çeviri, PARLER_LANGUAGES üzerinden varsayılan dile düşer.
from parler.admin import TranslatableAdmin
@admin.register(BlogPost)
class BlogPostAdmin(TranslatableAdmin):
list_display = ("__str__", "author", "is_published", "published_at")
list_editable = ("is_published",)
search_fields = ("translations__title", "translations__slug", "author")Bu yolda karşılaştığım birkaç şey:
- Arama ilişki üzerinden gider. Çevrilen alanlar çeviri tablosunda durur;
search_fields'atitledeğiltranslations__titleyazılır. prepopulated_fieldsuymaz. Slug dil bazlıdır; başlıktan üretilmek yerine her dil sekmesinde elle girilir.- Kayıt sonrası işler
save_related'a yazılır. Kayıttan sonra bir şey çalışacaksa (örneğin ön yüzdeki bir önbelleği tazelemek),save_relateden güvenli yerdir: Django onu ana nesne ve ilişkili veriler kaydedildikten sonra çağırır. - Üçüncü taraf admin temaları parler'i tanımayabilir. Temalı bir
ModelAdminileTranslatableAdmin'i tek bir taban sınıfta birleştirdim; tema sınıfı solda durunca şablonları kazanıyor. Çeviri formundaki widget stillerini iseget_formiçinde elle düzeltmem gerekti. Tema seçmeden önce bunu deneyin.
Kaydetmeden önce doğrulama nasıl yapılır?
İçerik girenler her zaman kuralları hatırlamaz. Kuralı modele koymak, admin'de otomatik hata mesajına dönüşür (genel bir örnek; alanları kendi modelinize uyarlayın):
from django.core.exceptions import ValidationError
class Route(models.Model):
price_min = models.DecimalField(max_digits=10, decimal_places=2)
price_max = models.DecimalField(max_digits=10, decimal_places=2)
def clean(self):
if self.price_max < self.price_min:
raise ValidationError({
"price_max": "Üst fiyat, alt fiyattan küçük olamaz.",
})Hata sözlük olarak verildiğinde admin, mesajı ilgili alanın altında gösterir. Sadece string verirseniz formun en üstünde çıkar ve kullanıcı hangi alanı düzelteceğini aramak zorunda kalır.
Yayın akışını görünür kılmak
Taslak/yayında ayrımı olan bir sistemde, durumu renkli göstermek listeyi taranabilir yapar:
from django.utils.html import format_html
@admin.display(description="Durum")
def status_badge(self, obj):
color = "#0d7d72" if obj.is_published else "#94a3b8"
label = "Yayında" if obj.is_published else "Taslak"
return format_html(
'<span style="color:{};font-weight:600">{}</span>', color, label
)format_html kullanmak önemlidir — string birleştirmek yerine bunu kullanmak, içeriğin HTML olarak kaçırılmasını (escape) sağlar ve XSS riskini önler.
Editörün zamanını kurtaran iki ayar daha
autocomplete_fields, binlerce seçenekli açılır listeyi arama kutusuyla değiştirir. Yalnızca ilişkili modelin admin'inde search_fields tanımlıysa çalışır; tanımlı değilse Django sistem kontrolü hatasıyla admin'i başlatmaz. Bir çıkış ve bir varış yerini bağlayan bir rota için autocomplete_fields = ("origin", "destination") formu kullanılabilir tutar.
readonly_fields, dışarıdan gelen kayıtlar için doğru araçtır; iletişim formu mesajları gibi. Gönderilen tüm alanları salt okunur yapın, list_editable'da yalnızca bir is_read işareti bırakın. Editör listeyi ayıklar ama müşterinin yazdığını değiştiremez.
İçerik ekibine hangi admin izinleri verilmeli?
İçerik ekibinin kullanıcı silme veya ayar değiştirme yetkisi olmamalı. Django'nun grup sistemi bunun için yeterlidir: bir "İçerik Editörü" grubu oluşturup sadece ilgili modellerin add, change, view izinlerini verin. delete iznini vermemek çoğu senaryoda doğru varsayılandır — yanlışlıkla silinen içerik geri gelmez.
Ek olarak admin panelinin adresini varsayılan yolda bırakmamak, otomatik tarama botlarının büyük kısmını eler:
# config/urls.py
urlpatterns = [
path("<tahmin-edilemez-yol>/", admin.site.urls),
]Bunun gerçek bir güvenlik katmanı olmadığını unutmayın — asıl koruma güçlü parolalar, iki faktörlü doğrulama ve ALLOWED_HOSTS gibi ayarlardır. Yol değiştirmek sadece gürültüyü azaltır.
Django admin ne zaman yetersiz kalır?
Django admin her senaryoyu karşılamaz. Şu durumlarda ayrı bir arayüz yazmak gerekebilir:
- Kullanıcının sürükle-bırak ile sıralama yapması gerekiyorsa
- Aynı ekranda birden fazla modelin karmaşık bir akışla düzenlenmesi gerekiyorsa
- Panel son kullanıcıya (müşterinin müşterisine) açılacaksa
Bunların dışında, yukarıdaki ayarlarla admin çoğu içerik yönetimi ihtiyacını karşılar ve size haftalarca geliştirme süresi kazandırır.