Die Administrationsoberfläche ausbauen

Wir wollen die Event-Verwaltung noch ein bisschen ausbauen. Die erste Änderung soll eine kleine Schönheitskorrektur auf der Event-Detailseite sein. Aktuell kann man die Kategorie des Events in einem Drop-Down-Feld auswählen. Wir wollen dies ändern und aus dem Drop-Down-Feld ein Reihe von Radio-Buttons machen, die horizontal angeordnet sind.

Zusätzlich wollen wir das Min-Group-Feld als vertikal angeordnete Radio-Group anlegen.

Choice-Felder als Radio Buttons in der Admin darstellen

Wir öffnen die Datei event_manager/events/admin.py und modifizieren das EventAdmin-Klasse:

@admin.register(Event)
class EventAdmin(admin.ModelAdmin):

    # anderer Code

    radio_fields = {
        "category": admin.HORIZONTAL,
        "min_group": admin.VERTICAL,
    }

Die Eigenschaft radio_fields der ModelAdmin-Klasse ermöglicht uns kompfortabel das Ändern der beiden Eigenschaften in Radiofields.

Admin Actions

Nun möchten wir auf der Events-Übersicht mehrere Events markieren und alle auf einen Schlag inaktiv setzen können. Bisher gibt es im Aktion-Dropdown nur eine Aktion: ausgewählte Events löschen.

Wollten wir aber ausgewählte Events nicht löschen, sondern nur auf aktiv bzw. inaktiv stellen, müssen wir jedes Event anklicken und auf der Eventseite die is_active-Eigenschaft ändern. Das ist mühselig, wenn man das bei mehreren Events machen möchte. Dafür gibt es sogenannte Actions.

Wir fügen nun diese beiden Methoden hier in usere EventAdmin-Klasse ein:

@admin.register(Event)
class EventAdmin(admin.ModelAdmin):

    # ... mehr Code
    list_filter = ("category",)
    list_display = "date", "slug", "author", "name", "category"
    actions = ["make_active", "make_inactive"]

    @admin.action(description="Setze Events active")
    def make_active(self, request, queryset):
        """Set all Events to active."""
        queryset.update(is_active=True)

    @admin.action(description="Setze Events inactive")
    def make_inactive(self, request, queryset):
        """Set all Events to inactive."""
        queryset.update(is_active=False)

Der @admin.action-Decorator ist optional und macht nichts weiter, als der Aktion in dem Aktionen-Dropdown-Feld einen greifbaren Namen zu geben. Interessant sind jetzt die beiden Methoden make_active bzw. make_inactive.

Diese beiden Methoden unserer EventAdmin-Klasse haben den Parameter queryset. Dieses queryset entspricht den auswählten also markierteren Events in der Eventübersicht. Führt man nun eine Aktion aus, bekommt die jeweilige Methode diese Events als queryset übergeben und wir können jetzt eine Aktion auf jeden dieser Events ausführen.

make_active zum Beispiel setzt die is_active Eigenschaft auf true und aktiviert damit die ausgewählten Events. Der @admin.action-Dekorator setzt noch die Eigenschaft description auf einen sprechenden Namen.

@admin.action(description="Setze Events active")
def make_active(self, request, queryset):
    """Set all Events to active."""
    queryset.update(is_active=True)

Damit diese beiden Methoden jetzt in der Admin auch registriert sind, müssen wir die Methoden-Referenzen an die actions-Liste übergeben.

actions = ["make_active", "make_inactive"]

Mehr zu AdminActions gibt es in der Django-Doku: https://docs.djangoproject.com/en/4.1/ref/contrib/admin/actions/

Den Kategorie-Slug in der Event-Übersicht darstellen

Bisher zeigen wir nur die Kategorie in der Eventübesicht an. Wir wollen die Ansicht aber erweitern, und den Slug der Category auch noch darstellen.

Leider funktioniert der Zugriff via category__slug in der list_display-Eigenschaft nicht. Es kommt zu einem Fehler

@admin.register(Event)
class EventAdmin(admin.ModelAdmin):

    # ... mehr Code
    list_display = ("date", "slug", "author", "name", "category", "category__slug")

Um das Problem zu umgehen, schreiben wir uns eine eigene Methode, dekorieren sie mit dem @admin.display-Dekorator und nutzen den Methoden-Namen in list_display, um die Spalte anzuzeigen.

@admin.register(Event)
class EventAdmin(admin.ModelAdmin):

    # mehr Code
    list_display = ("date",
                    "slug",
                    "author",
                    "name",
                    "category",
                    "category_slug")

    @admin.display(description="Kategorie Slug")
    def category_slug(self, obj):
        return obj.category.slug

Wir können problemlos eigene Methoden in der EventAdmin-Klasse definieren, und diese als Felder in der Übersicht unserer Events darstellen. Das übergebene obj ist im Fall von category_slug das Event-Objekt selbst.

Read Only Felder

Falls wir Felder in der Administrationsoberfläche readonly haben möchten, d.h., dass sie zumindest über die Admin nicht mehr geändert werden können, gibt es das Attribut readonly_fields. Wir wollen, dass der Autor eines Events nachträglich nicht mehr geändert werden kann und fügen folgendes in die Klasse ein:

readonly_fields = ("author",)

Damit sind alle Felder des Tuples auf readonly. Die fertige EventAdmin-Klasse sieht jetzt so aus:

@admin.register(Event)
class EventAdmin(admin.ModelAdmin):
    prepopulated_fields = {"slug": ("name",)}
    list_display = (
        "date",
        "slug",
        "author",
        "name",
        "category",
        "category_slug",
    )
    list_filter = ("category",)
    search_fields = ["name"]
    actions = ["make_active", "make_inactive"]
    # date_hierachy = "date"
    readonly_fields = ("author",)
    list_display_links = ("name", "slug")
    radio_fields = {
        "category": admin.HORIZONTAL,
        "min_group": admin.VERTICAL,
    }

    @admin.action(description="Setze Events active")
    def make_active(self, request, queryset):
        """Set all Entries to active."""
        queryset.update(active=True)

    @admin.action(description="Setze Events inactive")
    def make_inactive(self, request, queryset):
        """Set all Entries to inactive."""
        queryset.update(active=False)

    @admin.display(description="Kategorie Slug")
    def category_slug(self, obj):
        return obj.category.slug