Django Admin Aanpassen in 2026: Actions, Filters en Sollicitatievragen

Complete handleiding voor Django Admin aanpassingen met list_display, SimpleListFilter, custom actions en facet counts. Inclusief veelgestelde sollicitatievragen voor Django-ontwikkelaars.

Django Admin Aanpassen in 2026: Actions, Filters en Sollicitatievragen

Het aanpassen van Django Admin transformeert de ingebouwde interface van een scaffold-tool naar een doelgericht backoffice voor productieapplicaties. Django 6 bevat verbeterde admin-functies zoals facet counts voor filters, aanpasbare wachtwoordwijzigingsformulieren en Font Awesome iconen, waardoor een goed geconfigureerde admin daadwerkelijk prettig in gebruik is.

Belangrijkste Punt

ModelAdmin is de centrale klasse voor alle admin-aanpassingen. Het beheersen van list_display, list_filter, search_fields, fieldsets en custom actions dekt 90% van de admin-configuratiebehoeften.

ModelAdmin Basisprincipes: list_display en search_fields

list_display bepaalt welke kolommen verschijnen op de wijzigingslijstpagina. Naast eenvoudige veldnamen accepteert het callables die gedecoreerd zijn met @admin.display() voor berekende waarden. search_fields activeert het zoekveld dat records standaard filtert met icontains.

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)

De @admin.display() decorator vervangt het oudere patroon van het instellen van short_description als functieattribuut. De parameter boolean=True rendert een vinkje- of X-icoon in plaats van True/False tekst. Het gebruik van list_select_related voorkomt N+1 queries bij het benaderen van foreign key velden in list_display.

Aangepaste list_filter met SimpleListFilter

list_filter activeert zijbalkfilters op de wijzigingslijstpagina. Veldnamen werken voor basis filtering, maar SimpleListFilter behandelt complexe filterlogica die meerdere velden omvat of aangepaste queries vereist.

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 beperkt de auteur-dropdown tot auteurs die daadwerkelijk artikelen hebben, in plaats van elke gebruiker in de database te tonen. Dit verbetert de bruikbaarheid wanneer het gerelateerde model veel records heeft.

Django 6 Facetten: Filtertellingen in de Zijbalk

Django 6 introduceerde facet counts die het aantal overeenkomende objecten naast elke filteroptie tonen. Het attribuut show_facets beheert dit gedrag.

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 counts voeren extra COUNT queries uit voor elke filteroptie. Op tabellen met miljoenen rijen moet show_facets = ShowFacets.NEVER worden ingesteld om prestatievermindering te voorkomen. De tellingen worden dynamisch bijgewerkt wanneer filters worden toegepast, en tonen hoeveel records overeenkomen met de huidige filtercombinatie.

Aangepaste Admin Actions voor Bulkoperaties

Admin actions voeren bulkoperaties uit op geselecteerde objecten. De @admin.action decorator stelt permissies en de in de dropdown getoonde beschrijving in.

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

Het retourneren van een HttpResponse vanuit een action triggert een bestandsdownload of redirect naar een bevestigingspagina. De permissions parameter beperkt de action tot gebruikers met specifieke modelpermissies. Het overschrijven van get_actions() maakt het mogelijk om actions dynamisch te tonen of verbergen op basis van het huidige verzoek.

Klaar om je Django gesprekken te halen?

Oefen met onze interactieve simulatoren, flashcards en technische tests.

Fieldsets en Inline Modellen voor Georganiseerde Formulieren

fieldsets organiseert de toevoeg- en wijzigingsformulieren in inklapbare secties. TabularInline en StackedInline embedden gerelateerde modellen direct in het ouderformulier, waardoor navigatie tussen pagina's wordt geëlimineerd.

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 toont gerelateerde objecten in een compact tabelformaat, geschikt voor eenvoudige modellen met weinig velden. StackedInline toont elk gerelateerd object als een apart formulierblok, beter voor modellen met veel velden. De optie autocomplete_fields vervangt de standaard dropdown met een AJAX-zoekwidget, essentieel wanneer het gerelateerde model duizenden records heeft.

De Admin Site Aanpassen

Het subclassen van AdminSite maakt meerdere admin-interfaces mogelijk met verschillende branding, permissies of modelregistraties. Django 6 voegde password_change_form aanpassing toe voor strengere wachtwoordbeleid.

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

Elke AdminSite instantie heeft zijn eigen URL namespace, login pagina en set geregistreerde modellen. Dit patroon is geschikt voor applicaties met verschillende gebruikersrollen waarbij editors geen gebruikersbeheer of systeeminstellingen zouden moeten zien.

Sollicitatievragen: Django Admin Aanpassing

Deze vragen verschijnen vaak in technische Django-sollicitaties. Het begrijpen van de onderliggende mechanismen, niet alleen de syntax, onderscheidt mid-level kandidaten van senior engineers.

V: Hoe voeg je een berekende kolom toe aan list_display die niet kan worden gesorteerd?

De @admin.display() decorator accepteert een ordering parameter die specificeert welk databaseveld te gebruiken voor sortering. Zonder dit is de kolomkop niet klikbaar. Berekende waarden die meerdere records aggregeren of externe services aanroepen kunnen niet worden gesorteerd omdat er geen onderliggende databasekolom is om op te ordenen.

V: Wat is de prestatie-impact van facet counts en wanneer moeten ze worden uitgeschakeld?

Facets voeren één COUNT query uit per filteroptie, vermenigvuldigd met het aantal actieve filters. Op een tabel met 10 miljoen rijen en 5 filteropties elk op 3 velden, zijn dat 15 extra COUNT queries per paginalading. Stel show_facets = ShowFacets.NEVER in wanneer een filter een tabel met meer dan 100.000 rijen target zonder geschikte indexes.

V: Hoe verschillen admin actions van aangepaste admin views?

Actions werken op een queryset van geselecteerde objecten en integreren met de selectie-checkboxen van de wijzigingslijst. Aangepaste admin views zijn standalone pagina's die toegankelijk zijn via aangepaste URL's, geschikt voor rapporten, imports of dashboards die niet op geselecteerde records werken. Actions zouden geen langlopende operaties moeten uitvoeren omdat ze het verzoek blokkeren.

V: Waarom RelatedOnlyFieldListFilter gebruiken in plaats van het standaardfilter?

Het standaard RelatedFieldListFilter bevraagt alle objecten in de gerelateerde tabel. Wanneer een ForeignKey verwijst naar een User model met 100.000 records, laadt de filter-dropdown alle 100.000 opties, waardoor de browser vastloopt. RelatedOnlyFieldListFilter bevraagt alleen de waarden die bestaan in de records van het huidige model, typisch een veel kleinere set.

Veelgemaakte Fouten Vermijden in Productie

Verschillende patronen die werken in ontwikkeling veroorzaken problemen op schaal.

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

Het overschrijven van get_queryset() om annotaties toe te voegen houdt berekende waarden in een enkele query in plaats van de database eenmaal per rij te benaderen. Profileer de admin met Django Debug Toolbar om N+1 queries te vangen voordat ze productie bereiken.

Third-Party Admin Thema's: Django Unfold

Django Unfold is het meest actief onderhouden admin-thema voor Django 6, gebouwd met Tailwind CSS. Het breidt de standaard admin uit in plaats van te vervangen, dus bestaande ModelAdmin configuraties blijven werken.

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 voegt dark mode, responsive layouts, zijbalknavigatie en geavanceerde filtertypen toe zoals RangeNumericFilter en RangeDateFilter. De HTMX en Alpine.js integratie biedt interactiviteit zonder de overhead van een volledig SPA-framework. Voor teams die een gepolijste admin nodig hebben zonder een aangepast dashboard te bouwen, bespaart Unfold weken frontend-werk.

Begin met oefenen!

Test je kennis met onze gespreksimulatoren en technische tests.

Samenvatting Django Admin Aanpassing

  • list_display met @admin.display() beheert kolommen; voeg ordering toe voor sorteerbare berekende waarden
  • SimpleListFilter behandelt aangepaste filterlogica die meerdere velden omvat of datumbereiken vereist
  • Django 6 facets tonen filtertellingen; uitschakelen met ShowFacets.NEVER op grote tabellen om COUNT query overhead te vermijden
  • Admin actions met @admin.action(permissions=[...]) behandelen bulkoperaties; retourneer HttpResponse voor bestandsexports
  • Gebruik list_select_related en geannoteerde querysets in get_queryset() om N+1 queries in lijstweergaven te voorkomen
  • RelatedOnlyFieldListFilter voorkomt browser-vastlopers bij filteren op ForeignKey naar grote tabellen
  • Oefen deze patronen met Django sollicitatievragen die specifiek gericht zijn op admin-configuratie
Dagelijkse challenge

Zie jij de bug in Django?

Een echt codefragment, een verborgen bug, één poging per dag. Zonder account uit te proberen.

Anthony Fillion-Maillet

Geschreven door

Anthony Fillion-Maillet

Oprichter van SharpSkill

Al meer dan 10 jaar fullstack-ontwikkelaar. Hij leidt SharpSkill en staat in voor alles wat hier verschijnt.

Bijgewerkt op 13 september 2026

Delen

Gerelateerde artikelen