Spass mit Newforms-Admin - Automatische Felder

Im letzten Artikel Rowlevel-Permissions ging es darum, wie man Einträge nur vom Benutzer selber bearbeiten lassen kann. Wer den Artikel verfolgt und aufgebaut hat, wird festgestellt haben, dass die ganze Sache einen gewaltigen Haken hatte:

In der Detailansicht konnte das Autorenfeld frei definiert werden, sprich Benutzer A konnte sich für Benutzer B ausgeben und umgekehrt. In diesem Artikel geht es darum, dem eingeloggten Autor automatisch den Artikel zuzuweisen.

Holzhammer

Die einfachste Lösung ist: Zeige das Autorenfeld gar nicht erst an und weise beim Speichern des Artikels dem Autorenfeld den eingeloggten User zu. Das Ganze ist schnell geschehen. Ähnlich wie die "newforms save()" Methode stellt auch ModelAdmin eine Methode zum Überschreiben zur Verfügung:

class EntryAdmin(admin.ModelAdmin):

    # Autorenfeld ausblenden (mit #7937 geht das irgendwann mal einfacher)
    # #7937: http://code.djangoproject.com/ticket/7973
    fields = (
        'title',
        'content',
    )

    # Formular valdidieren, Autor setzen und speichern
    def save_form(self, request, form, change):
        instance = form.save(commit=False)
        # Nur bei neuen Artikeln den User setzen
        if not change:
            instance.author = request.user
        return instance

Fertig ist die ganze Geschichte. Schnell, sicher und kaum zu überlisten.

Nicht ganz so krass

Vielleicht ist das aber auch schon einen Schritt zu weit. In meinem aktuellen Projekt möchte ich es trotzdem dem Benutzer überlassen, den Autor zu setzen, allerdings mit einer Kleinigkeit: Der aktuelle Benutzer soll in der Liste schon vorausgewählt sein.

Das Ganze ist ein wenig mehr "tricky". In ModelAdmin gibt es wiederrum eine Methode, die vor der Anzeige der Felder aufgerufen wird und mit der man ihr Verhalten und Aussehen (Widgets) beeinflussen kann: formfield_for_dbfield.

class EntryAdmin(admin.ModelAdmin):

    def formfield_for_dbfield(self, db_field, **kwargs):
        field = super(EntryAdmin, self).formfield_for_dbfield(db_field, **kwargs)

        # Autorenfeld hat als Vorauswahl den aktuellen User
        if db_field.name == "author":
            field.initial = request.user.pk
        return field

In dieser Methode prüfen wir, ob das aktuelle Feld unser "Autorenfeld" ist, dann wird ihm als Startwert (initial) der aktuelle User zugewiesen.

Nächster Haken: Der obige Code funktioniert nicht, in der Methode formfield_for_dbfield hat man keinen Zugriff auf das request-Objekt. Ein wenig Patching muss her:

class EntryAdmin(admin.ModelAdmin):

    def add_view(self, request,  *args, **kwargs):
        self._request = request
        return super(EntryAdmin, self).add_view(request,  *args, **kwargs)

    def change_view(self, request, *args, **kwargs):
        self._request = request
        return super(EntryAdmin, self).change_view(request,  *args, **kwargs)

    def formfield_for_dbfield(self, db_field, **kwargs):
        field = super(EntryAdmin, self).formfield_for_dbfield(db_field, **kwargs)

        # Autorenfeld hat als Vorauswahl den aktuellen User
        if db_field.name == "author":
            field.initial = self._request.user.pk
        return field

Glücklicherweise ist ModelAdmin flexibel genug, das Problem zu lösen. Die Methoden add_view und change_view werden vor der Anzeige des Formulares aufgerufen und beiden steht das Request-Objekt zur Verfügung. Das ist genau der richtige Ort, das Request-Objekt als globale Eigenschaft (self._request) der Klasse zuzuweisen.

Somit kann man in der formfield-Methode einfach auf das "globale" Request-Objekt zugreifen und mit den paar Handgriffen ist der Benutzer in der Autorenliste vorausgewählt.

Mit ein wenig Javascript kann man auch einfach aus diesen beiden Sachen eins machen:

Und nicht vergessen: Auch hier ist es wieder möglich mit Permissions zu arbeiten. So könnte ein Administrator immer den Autor setzen (zweite Variante), wo ein "normaler" User kein Autorenfeld zu Gesicht bekommt und automatisch gesetzt wird (erste Variante).


  • Martin Geber Aug. 26, 2008

    Das Video ist schön... Wo ist das JS? :P Ja, ja ich zerbreche mir selbst den Kopf...

    Wiedereinmal ein spitze Artikel. Danke. (Mir war auch nicht bewusst, dass ich endlich auf request zugreifen kann.)

    *nun threadlocals endlich weg werfen kann*

    Immer weiter so.

  • Pascal Aug. 26, 2008

    Wunderbar! Habs direkt bei mir eingebaut -- und das JS in jQuery nachgebaut: http://gist.github.com/7323

  • Max Meier Sept. 4, 2008

    wie wuerde das denn konkret Ausschauen mit der Kombination von beidem, also Admin bekommt Feld zur auswahl und normaler User nicht?

  • Max Meier Sept. 4, 2008

    so gehts, enthaelt aber leider viel redundanten code:

    [ganz ganz viel code entfernt, das ist kein pastebin; Martin]

  • Yuri Baburov July 8, 2009

    Please add note: Concurrent modification issues for request can happen, since the only ModelAdmin exists for a process, so self.request will fail sometimes when GIL have switched the thread between self._request assignment and usage. Needs to use thread local storage or create ModelAdmin each time.


Comments closed

Sorry, new comments are no longer allowed for this entry.

Write me an email if you have feedback or any questions regarding this post. If you found this post useful and just want to say thank you then don't forget that I have an Amazon Wishlist. :-)


↑ to the elevators

© 2001—2010 Martin Mahner. This is an I ♥ Django Project.

Admin | Generated: Fri, 12 Mar 2010 04:46:05 +0100