Personalizzazione Django Admin 2026: Actions, Filtri e Domande da Colloquio

Guida completa alla personalizzazione di Django Admin con list_display, SimpleListFilter, Actions personalizzate e facet counts. Include domande frequenti nei colloqui tecnici Django.

Personalizzazione Django Admin 2026: Actions, Filtri e Domande da Colloquio

La personalizzazione del Django Admin trasforma l'interfaccia integrata da uno strumento di scaffolding in un back office su misura per applicazioni in produzione. Django 6 include funzionalità admin avanzate come i conteggi facet per i filtri, form personalizzabili per il cambio password e icone Font Awesome, rendendo un admin ben configurato effettivamente piacevole da usare.

Punto Chiave

ModelAdmin è la classe centrale per tutte le personalizzazioni admin. Padroneggiare list_display, list_filter, search_fields, fieldsets e actions personalizzate copre il 90% delle esigenze di configurazione admin.

Fondamenti di ModelAdmin: list_display e search_fields

list_display controlla quali colonne appaiono nella pagina dell'elenco modifiche. Oltre ai semplici nomi di campo, accetta callable decorati con @admin.display() per valori calcolati. search_fields abilita la casella di ricerca che filtra i record usando icontains di default.

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)

Il decoratore @admin.display() sostituisce il vecchio pattern di impostare short_description come attributo della funzione. Il parametro boolean=True renderizza un'icona di spunta o X invece del testo True/False. L'uso di list_select_related previene le query N+1 quando si accede ai campi foreign key in list_display.

list_filter Personalizzati con SimpleListFilter

list_filter attiva i filtri nella sidebar nella pagina dell'elenco modifiche. I nomi dei campi funzionano per il filtraggio base, ma SimpleListFilter gestisce logiche di filtraggio complesse che coinvolgono più campi o richiedono query personalizzate.

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 limita il dropdown degli autori solo a quelli che hanno effettivamente articoli, invece di elencare ogni utente nel database. Questo migliora l'usabilità quando il modello correlato ha molti record.

Facet di Django 6: Conteggi Filtri nella Sidebar

Django 6 ha introdotto i conteggi facet che mostrano il numero di oggetti corrispondenti accanto a ogni opzione di filtro. L'attributo show_facets controlla questo comportamento.

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)

I conteggi facet eseguono query COUNT aggiuntive per ogni opzione di filtro. Su tabelle con milioni di righe, impostare show_facets = ShowFacets.NEVER per evitare degradazioni delle prestazioni. I conteggi si aggiornano dinamicamente quando i filtri vengono applicati, mostrando quanti record corrispondono alla combinazione di filtri corrente.

Admin Actions Personalizzate per Operazioni in Blocco

Le Admin Actions eseguono operazioni in blocco sugli oggetti selezionati. Il decoratore @admin.action imposta i permessi e la descrizione mostrata nel dropdown.

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

Restituire una HttpResponse da un'action attiva un download di file o reindirizza a una pagina di conferma. Il parametro permissions limita l'action agli utenti con permessi specifici del modello. Sovrascrivere get_actions() permette di mostrare o nascondere dinamicamente le actions in base alla richiesta corrente.

Pronto a superare i tuoi colloqui su Django?

Pratica con i nostri simulatori interattivi, flashcards e test tecnici.

Fieldsets e Modelli Inline per Form Organizzati

fieldsets organizza i form di aggiunta e modifica in sezioni comprimibili. TabularInline e StackedInline incorporano i modelli correlati direttamente nel form del parent, eliminando la navigazione tra pagine.

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 visualizza gli oggetti correlati in un formato tabella compatto, adatto per modelli semplici con pochi campi. StackedInline mostra ogni oggetto correlato come un blocco form separato, migliore per modelli con molti campi. L'opzione autocomplete_fields sostituisce il dropdown predefinito con un widget di ricerca AJAX, essenziale quando il modello correlato ha migliaia di record.

Personalizzazione dell'Admin Site

Creare sottoclassi di AdminSite permette interfacce admin multiple con branding, permessi o registrazioni di modelli diversi. Django 6 ha aggiunto la personalizzazione di password_change_form per policy password più rigide.

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

Ogni istanza AdminSite ha il proprio namespace URL, pagina di login e set di modelli registrati. Questo pattern è adatto per applicazioni con ruoli utente distinti dove gli editor non dovrebbero vedere la gestione utenti o le impostazioni di sistema.

Domande da Colloquio: Personalizzazione Django Admin

Queste domande appaiono frequentemente nei colloqui tecnici Django. Comprendere i meccanismi sottostanti, non solo la sintassi, distingue i candidati mid-level dai senior engineer.

D: Come si aggiunge una colonna calcolata a list_display che non può essere ordinata?

Il decoratore @admin.display() accetta un parametro ordering che specifica quale campo del database usare per l'ordinamento. Senza di esso, l'intestazione della colonna non è cliccabile. I valori calcolati che aggregano più record o chiamano servizi esterni non possono essere ordinati perché non c'è una colonna database sottostante su cui ordinare.

D: Qual è l'impatto sulle prestazioni dei conteggi facet e quando dovrebbero essere disabilitati?

I facet eseguono una query COUNT per opzione di filtro, moltiplicata per il numero di filtri attivi. Su una tabella con 10 milioni di righe e 5 opzioni di filtro ciascuna su 3 campi, sono 15 query COUNT aggiuntive per caricamento pagina. Impostare show_facets = ShowFacets.NEVER quando qualsiasi filtro punta a una tabella con oltre 100.000 righe senza indici appropriati.

D: Come differiscono le admin actions dalle view admin personalizzate?

Le actions operano su un queryset di oggetti selezionati e si integrano con le checkbox di selezione dell'elenco modifiche. Le view admin personalizzate sono pagine standalone accessibili tramite URL personalizzati, adatte per report, import o dashboard che non operano su record selezionati. Le actions non dovrebbero eseguire operazioni di lunga durata perché bloccano la richiesta.

D: Perché usare RelatedOnlyFieldListFilter invece del filtro predefinito?

Il RelatedFieldListFilter predefinito interroga tutti gli oggetti nella tabella correlata. Quando una ForeignKey punta a un modello User con 100.000 record, il dropdown del filtro carica tutte le 100.000 opzioni, bloccando il browser. RelatedOnlyFieldListFilter interroga solo i valori che esistono nei record del modello corrente, tipicamente un set molto più piccolo.

Errori Comuni da Evitare in Produzione

Diversi pattern che funzionano in sviluppo causano problemi a scala.

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

Sovrascrivere get_queryset() per aggiungere annotazioni mantiene i valori calcolati in una singola query invece di interrogare il database una volta per riga. Profilare l'admin con Django Debug Toolbar per individuare le query N+1 prima che raggiungano la produzione.

Temi Admin di Terze Parti: Django Unfold

Django Unfold è il tema admin più attivamente mantenuto per Django 6, costruito con Tailwind CSS. Estende piuttosto che sostituire l'admin standard, quindi le configurazioni ModelAdmin esistenti continuano a funzionare.

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 aggiunge dark mode, layout responsive, navigazione sidebar e tipi di filtro avanzati come RangeNumericFilter e RangeDateFilter. L'integrazione HTMX e Alpine.js fornisce interattività senza l'overhead di un framework SPA completo. Per i team che necessitano di un admin curato senza costruire una dashboard personalizzata, Unfold risparmia settimane di lavoro frontend.

Inizia a praticare!

Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.

Riepilogo sulla Personalizzazione Django Admin

  • list_display con @admin.display() controlla le colonne; aggiungere ordering per valori calcolati ordinabili
  • SimpleListFilter gestisce logiche di filtro personalizzate che coinvolgono più campi o richiedono intervalli di date
  • I facet di Django 6 mostrano i conteggi dei filtri; disabilitare con ShowFacets.NEVER su tabelle grandi per evitare l'overhead delle query COUNT
  • Admin actions con @admin.action(permissions=[...]) gestiscono operazioni in blocco; restituire HttpResponse per export di file
  • Usare list_select_related e queryset annotati in get_queryset() per prevenire query N+1 nelle view elenco
  • RelatedOnlyFieldListFilter previene il blocco del browser quando si filtra per ForeignKey verso tabelle grandi
  • Esercitarsi con questi pattern usando le domande da colloquio Django che mirano specificamente alla configurazione admin
Sfida del giorno

Sapresti trovare il bug in Django?

Uno snippet reale, un bug nascosto, un tentativo al giorno. Senza account per provare.

Anthony Fillion-Maillet

Scritto da

Anthony Fillion-Maillet

Fondatore di SharpSkill

Sviluppatore fullstack da oltre 10 anni. Guida SharpSkill e risponde di tutto ciò che vi viene pubblicato.

Aggiornato il 13 settembre 2026

Condividi

Articoli correlati