Django Admin Anpassung 2026: Actions, Filter und Interviewfragen

Umfassende Anleitung zur Django Admin Anpassung mit list_display, SimpleListFilter, Custom Actions und Facetten. Inklusive häufiger Interviewfragen für Django-Entwickler.

Django Admin Anpassung 2026: Actions, Filter und Interviewfragen

Die Anpassung des Django Admin verwandelt das integrierte Interface von einem einfachen Gerüst in ein maßgeschneidertes Backoffice für Produktionsanwendungen. Django 6 liefert erweiterte Admin-Funktionen wie Facettenzähler für Filter, anpassbare Passwortänderungsformulare und Font Awesome Icons, wodurch ein gut konfigurierter Admin tatsächlich angenehm zu bedienen ist.

Kernaussage

ModelAdmin ist die zentrale Klasse für alle Admin-Anpassungen. Die Beherrschung von list_display, list_filter, search_fields, fieldsets und benutzerdefinierten Actions deckt 90% der Admin-Konfigurationsanforderungen ab.

ModelAdmin Grundlagen: list_display und search_fields

list_display steuert, welche Spalten auf der Änderungslistenseite erscheinen. Über einfache Feldnamen hinaus akzeptiert es Callables, die mit @admin.display() für berechnete Werte dekoriert sind. search_fields aktiviert das Suchfeld, das Datensätze standardmäßig mit icontains filtert.

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)

Der @admin.display()-Dekorator ersetzt das ältere Muster, short_description als Funktionsattribut zu setzen. Der Parameter boolean=True rendert ein Häkchen- oder X-Symbol anstelle von True/False-Text. Die Verwendung von list_select_related verhindert N+1-Abfragen beim Zugriff auf Fremdschlüsselfelder in list_display.

Benutzerdefinierte list_filter mit SimpleListFilter

list_filter aktiviert Seitenleistenfilter auf der Änderungslistenseite. Feldnamen funktionieren für einfache Filterung, aber SimpleListFilter behandelt komplexe Filterlogik, die mehrere Felder umfasst oder benutzerdefinierte Abfragen erfordert.

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 beschränkt das Autoren-Dropdown auf Autoren, die tatsächlich Artikel haben, anstatt jeden Benutzer in der Datenbank aufzulisten. Dies verbessert die Benutzerfreundlichkeit, wenn das zugehörige Modell viele Datensätze hat.

Django 6 Facetten: Filteranzahlen in der Seitenleiste

Django 6 führte Facettenzähler ein, die die Anzahl der übereinstimmenden Objekte neben jeder Filteroption anzeigen. Das Attribut show_facets steuert dieses Verhalten.

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)

Facettenzähler führen zusätzliche COUNT-Abfragen für jede Filteroption aus. Bei Tabellen mit Millionen von Zeilen sollte show_facets = ShowFacets.NEVER gesetzt werden, um Leistungseinbußen zu vermeiden. Die Zähler aktualisieren sich dynamisch, wenn Filter angewendet werden, und zeigen, wie viele Datensätze der aktuellen Filterkombination entsprechen.

Benutzerdefinierte Admin Actions für Massenoperationen

Admin Actions führen Massenoperationen auf ausgewählten Objekten durch. Der @admin.action-Dekorator legt Berechtigungen und die im Dropdown angezeigte Beschreibung fest.

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

Die Rückgabe einer HttpResponse aus einer Action löst einen Dateidownload aus oder leitet zu einer Bestätigungsseite weiter. Der Parameter permissions beschränkt die Action auf Benutzer mit bestimmten Modellberechtigungen. Das Überschreiben von get_actions() ermöglicht das dynamische Ein- oder Ausblenden von Actions basierend auf der aktuellen Anfrage.

Bereit für deine Django-Interviews?

Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.

Fieldsets und Inline-Modelle für organisierte Formulare

fieldsets organisiert die Hinzufügen- und Änderungsformulare in zusammenklappbare Abschnitte. TabularInline und StackedInline betten zugehörige Modelle direkt in das übergeordnete Formular ein und eliminieren die Navigation zwischen Seiten.

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 zeigt zugehörige Objekte in einem kompakten Tabellenformat an, geeignet für einfache Modelle mit wenigen Feldern. StackedInline zeigt jedes zugehörige Objekt als separaten Formularblock, besser für Modelle mit vielen Feldern. Die Option autocomplete_fields ersetzt das Standard-Dropdown durch ein AJAX-Suchwidget, unverzichtbar wenn das zugehörige Modell Tausende von Datensätzen hat.

Anpassung der Admin-Site

Das Erstellen von Unterklassen von AdminSite ermöglicht mehrere Admin-Interfaces mit unterschiedlichem Branding, Berechtigungen oder Modellregistrierungen. Django 6 fügte die Anpassung von password_change_form für strengere Passwortrichtlinien hinzu.

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

Jede AdminSite-Instanz hat ihren eigenen URL-Namespace, Login-Seite und Satz registrierter Modelle. Dieses Muster eignet sich für Anwendungen mit unterschiedlichen Benutzerrollen, bei denen Redakteure keine Benutzerverwaltung oder Systemeinstellungen sehen sollten.

Interviewfragen: Django Admin Anpassung

Diese Fragen erscheinen häufig in technischen Django-Interviews. Das Verständnis der zugrunde liegenden Mechanismen, nicht nur der Syntax, unterscheidet Mid-Level-Kandidaten von Senior Engineers.

F: Wie fügt man eine berechnete Spalte zu list_display hinzu, die nicht sortierbar ist?

Der @admin.display()-Dekorator akzeptiert einen ordering-Parameter, der angibt, welches Datenbankfeld für die Sortierung verwendet werden soll. Ohne diesen ist die Spaltenüberschrift nicht anklickbar. Berechnete Werte, die mehrere Datensätze aggregieren oder externe Dienste aufrufen, können nicht sortiert werden, da es keine zugrunde liegende Datenbankspalte gibt, nach der sortiert werden kann.

F: Was ist die Leistungsauswirkung von Facettenzählern und wann sollten sie deaktiviert werden?

Facetten führen eine COUNT-Abfrage pro Filteroption aus, multipliziert mit der Anzahl aktiver Filter. Bei einer Tabelle mit 10 Millionen Zeilen und jeweils 5 Filteroptionen auf 3 Feldern sind das 15 zusätzliche COUNT-Abfragen pro Seitenaufruf. show_facets = ShowFacets.NEVER sollte gesetzt werden, wenn ein Filter auf eine Tabelle mit über 100.000 Zeilen ohne entsprechende Indizes zielt.

F: Wie unterscheiden sich Admin Actions von benutzerdefinierten Admin-Views?

Actions arbeiten mit einem Queryset ausgewählter Objekte und integrieren sich mit den Auswahlcheckboxen der Änderungsliste. Benutzerdefinierte Admin-Views sind eigenständige Seiten, auf die über benutzerdefinierte URLs zugegriffen wird, geeignet für Berichte, Importe oder Dashboards, die nicht mit ausgewählten Datensätzen arbeiten. Actions sollten keine lang laufenden Operationen durchführen, da sie die Anfrage blockieren.

F: Warum RelatedOnlyFieldListFilter anstelle des Standardfilters verwenden?

Der Standard-RelatedFieldListFilter fragt alle Objekte in der zugehörigen Tabelle ab. Wenn ein ForeignKey auf ein User-Modell mit 100.000 Datensätzen zeigt, lädt das Filter-Dropdown alle 100.000 Optionen und lässt den Browser einfrieren. RelatedOnlyFieldListFilter fragt nur die Werte ab, die in den Datensätzen des aktuellen Modells existieren, typischerweise eine viel kleinere Menge.

Häufige Fehler in der Produktion vermeiden

Mehrere Muster, die in der Entwicklung funktionieren, verursachen Probleme bei der Skalierung.

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

Das Überschreiben von get_queryset() zum Hinzufügen von Annotationen hält berechnete Werte in einer einzigen Abfrage, anstatt die Datenbank einmal pro Zeile abzufragen. Der Admin sollte mit der Django Debug Toolbar profiliert werden, um N+1-Abfragen zu erkennen, bevor sie die Produktion erreichen.

Drittanbieter-Admin-Themes: Django Unfold

Django Unfold ist das aktivste gepflegte Admin-Theme für Django 6, erstellt mit Tailwind CSS. Es erweitert den Standard-Admin anstatt ihn zu ersetzen, sodass bestehende ModelAdmin-Konfigurationen weiterhin funktionieren.

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 fügt Dark Mode, responsive Layouts, Seitenleistennavigation und erweiterte Filtertypen wie RangeNumericFilter und RangeDateFilter hinzu. Die HTMX- und Alpine.js-Integration bietet Interaktivität ohne den Overhead eines vollständigen SPA-Frameworks. Für Teams, die einen polierten Admin ohne den Aufbau eines benutzerdefinierten Dashboards benötigen, spart Unfold Wochen an Frontend-Arbeit.

Fang an zu üben!

Teste dein Wissen mit unseren Interview-Simulatoren und technischen Tests.

Zusammenfassung zur Django Admin Anpassung

  • list_display mit @admin.display() steuert Spalten; ordering für sortierbare berechnete Werte hinzufügen
  • SimpleListFilter behandelt benutzerdefinierte Filterlogik, die mehrere Felder umfasst oder Datumsbereiche erfordert
  • Django 6 Facetten zeigen Filteranzahlen; mit ShowFacets.NEVER bei großen Tabellen deaktivieren, um COUNT-Abfrage-Overhead zu vermeiden
  • Admin Actions mit @admin.action(permissions=[...]) behandeln Massenoperationen; HttpResponse für Dateiexporte zurückgeben
  • list_select_related und annotierte Querysets in get_queryset() verwenden, um N+1-Abfragen in Listenansichten zu verhindern
  • RelatedOnlyFieldListFilter verhindert Browser-Einfrieren beim Filtern nach ForeignKey zu großen Tabellen
  • Diese Muster mit Django-Interviewfragen üben, die speziell auf Admin-Konfiguration abzielen
Tägliche Challenge

Findest du den Bug in Django?

Ein echter Codeausschnitt, ein versteckter Bug, ein Versuch pro Tag. Zum Ausprobieren ohne Konto.

Anthony Fillion-Maillet

Geschrieben von

Anthony Fillion-Maillet

Gründer von SharpSkill

Seit über 10 Jahren Fullstack-Entwickler. Er leitet SharpSkill und verantwortet alles, was hier erscheint.

Aktualisiert am 13. September 2026

Teilen

Verwandte Artikel