28 Stimmen

Dynamische Felder im Django-Admin

Ich möchte zusätzliche Felder zum Wert eines Feldes haben. Deshalb habe ich ein benutzerdefiniertes Admin-Formular erstellt, um einige neue Felder hinzuzufügen.

Bezogen auf den Blogbeitrag von Jacobian 1 ist dies, was ich gemacht habe:

class ProductAdminForm(forms.ModelForm):
    class Meta:
        model = Product

    def __init__(self, *args, **kwargs):
        super(ProductAdminForm, self).__init__(*args, **kwargs)
        self.fields['foo'] = forms.IntegerField(label="foo")

class ProductAdmin(admin.ModelAdmin):
    form = ProductAdminForm

admin.site.register(Product, ProductAdmin)

Aber das zusätzliche Feld 'foo' erscheint nicht im Adminbereich. Wenn ich das Feld so hinzufüge, funktioniert alles gut, ist aber nicht so dynamisch wie erforderlich, um die Felder entsprechend dem Wert eines anderen Feldes des Modells hinzuzufügen

class ProductAdminForm(forms.ModelForm):

    foo = forms.IntegerField(label="foo")

    class Meta:
        model = Product

class ProductAdmin(admin.ModelAdmin):
    form = ProductAdminForm

admin.site.register(Product, ProductAdmin)

Gibt es also eine Initialisierungsmethode, die ich erneut auslösen muss, um das neue Feld zum Laufen zu bringen? Oder gibt es einen anderen Ansatz?

26voto

Stephan Hoyer Punkte 4494

Hier ist eine Lösung für das Problem. Dank koniiiik habe ich versucht, dies zu lösen, indem ich die Methode *get_fieldsets* erweitert habe.

class ProductAdmin(admin.ModelAdmin):
    def get_fieldsets(self, request, obj=None):
        fieldsets = super(ProductAdmin, self).get_fieldsets(request, obj)
        fieldsets[0][1]['fields'] += ['foo'] 
        return fieldsets

Wenn Sie mehrere fieldsets verwenden, stellen Sie sicher, dass Sie sie durch Verwendung des entsprechenden Index dem richtigen fieldset hinzufügen.

15voto

little_birdie Punkte 5084

Die akzeptierte Antwort oben hat in älteren Versionen von Django funktioniert, und so habe ich es gemacht. Dies ist jetzt in späteren Django-Versionen kaputt gegangen (Ich bin im Moment auf 1.6.8, aber selbst das ist jetzt alt).

Der Grund, warum es jetzt kaputt ist, liegt darin, dass alle Felder innerhalb von Feldsets, die Sie von ModelAdmin.get_fieldsets() zurückgeben, letztendlich als das fields=parameter an modelform_factory() übergeben werden, was einen Fehler verursacht, weil die auf Ihrer Liste aufgeführten Felder nicht existieren (und erst existieren werden, wenn Ihr Formular instanziiert und sein __init__ aufgerufen wird).

Um dies zu beheben, müssen wir ModelAdmin.get_form() überschreiben und eine Liste von Feldern bereitstellen, die keine zusätzlichen Felder enthalten, die später hinzugefügt werden. Das Standardverhalten von get_form besteht darin, get_fieldsets() für diese Informationen aufzurufen, und das müssen wir verhindern:

# WÄHLEN SIE EINEN
# neuere Versionen von Django verwenden dies
from django.contrib.admin.utils import flatten_fieldsets
# wenn das obige nicht funktioniert, verwenden Sie dies
from django.contrib.admin.util import flatten_fieldsets

class MyModelForm(ModelForm):
  def __init__(self, *args, **kwargs):
      super(MyModelForm, self).__init__(*args, **kwargs)
      # fügen Sie hier Ihre dynamischen Felder hinzu..
      for fieldname in ('foo', 'bar', 'baz',):
          self.fields[fieldname] = form.CharField()

class MyAdmin(ModelAdmin): 
   form = MyModelForm

    fieldsets = [
       # hier geben Sie die Liste der Feldsets ein, die angezeigt werden sollen.. nur
       # einschließlich derjenigen, die nicht dynamisch sind
    ]

    def get_form(self, request, obj=None, **kwargs):
        # Durch die Übergabe von 'fields' verhindern wir, dass ModelAdmin.get_form die Felder selbst nachschlägt, indem es self.get_fieldsets() aufruft
        # Wenn Sie dies nicht tun, erhalten Sie einen Fehler von modelform_factory, der über nicht vorhandene Felder klagt.

        # verwende diese Zeile nur für Django vor 1.9 (aber nach 1.5??)
        kwargs['fields'] =  flatten_fieldsets(self.declared_fieldsets)
        # verwende diese Zeile nur für Django 1.9 und später 
        kwargs['fields'] =  flatten_fieldsets(self.fieldsets)

        return super(MyAdmin, self).get_form(request, obj, **kwargs)

    def get_fieldsets(self, request, obj=None):
        fieldsets = super(MyAdmin, self).get_fieldsets(request, obj)

        newfieldsets = list(fieldsets)
        fields = ['foo', 'bar', 'baz']
        newfieldsets.append(['Dynamische Felder', { 'fields': fields }])

        return newfieldsets

9voto

Gillanius Punkte 76

Vielleicht bin ich ein bisschen spät dran... Trotzdem benutze ich Django 3.0 und wollte auch einige benutzerdefinierte Felder dynamisch zum Formular hinzufügen, abhängig von der Anfrage.

Ich bin zu einer Lösung gekommen, die der von @tehfink beschriebenen ähnelt, kombiniert mit @little_birdie.

Allerdings hat es nicht gereicht, einfach self.form.declared_fields zu aktualisieren, wie vorgeschlagen. Das Ergebnis dieses Verfahrens ist, dass die Liste der benutzerdefinierten Felder, die in self.form.declared_fields definiert sind, von Anfrage zu Anfrage immer weiter wächst.

Ich habe dies gelöst, indem ich dieses Wörterbuch zuerst initialisiert habe:

class ModelAdminGetCustomFieldsMixin(object):
    def get_fields(self, request, obj=None):
        fields = super().get_fields(request, obj=None)
        self.form.declared_fields = {}
        if obj:
            for custom_attribute in custom_attribute_list:
                self.form.declared_fields.update({custom_attribute.name: custom_attribute.field})
        return fields

wo custom_attribute.field eine Formularfeldinstanz ist.

Zusätzlich musste auch ein ModelForm definiert werden, in dem während der Initialisierung die benutzerdefinierten Felder ebenfalls dynamisch hinzugefügt wurden:

class SomeModelForm(forms.ModelForm):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for custom_attribute in custom_attribute_list:
            self.fields[custom_attribute.name] = custom_attribute.field

und dieses ModelForm im ModelAdmin verwenden.

Nachher können die neu definierten Attribute z.B. in einem Feldset verwendet werden.

7voto

tehfink Punkte 407

Dies funktioniert für das Hinzufügen dynamischer Felder in Django 1.9.3, indem nur eine ModelAdmin-Klasse (kein ModelForm) verwendet wird und get_fields überschrieben wird. Ich weiß noch nicht, wie robust es ist:

class MyModelAdmin(admin.ModelAdmin):

    fields = [('title','status', ), 'description', 'contact_person',]
    exclude = ['material']

    def get_fields(self, request, obj=None):
        gf = super(MyModelAdmin, self).get_fields(request, obj)

        new_dynamic_fields = [
            ('test1', forms.CharField()),
            ('test2', forms.ModelMultipleChoiceField(MyModel.objects.all(), widget=forms.CheckboxSelectMultiple)),
        ]

        #ohne Aktualisierung von get_fields wird das Admin-Formular ohne neue Felder angezeigt
        #ohne Aktualisierung von base_fields oder declared_fields wirft Django einen Fehler: django.core.exceptions.FieldError: Unknown field(s) (test) specified for MyModel. Check fields/fieldsets/exclude attributes of class MyModelAdmin.

        for f in new_dynamic_fields:
            #`gf.append(f[0])` führt zu mehreren Instanzen der neuen Felder
            gf = gf + [f[0]]
            #Das Aktualisieren von base_fields scheint den gleichen Effekt zu haben
            self.form.declared_fields.update({f[0]:f[1]})
        return gf

5voto

koniiiik Punkte 4070

Während der Beitrag von Jacob für normale ModelForms möglicherweise gut funktioniert (auch wenn er mehr als anderthalb Jahre alt ist), ist das Admin-Panel eine etwas andere Angelegenheit.

Der deklarative Weg zur Definition von Modellen, Formularen, ModelAdmins und ähnlichem macht intensiven Gebrauch von Metaklassen und Klassenintrospektion. Das gilt auch für das Admin-Panel – wenn Sie einem ModelAdmin sagen, dass es ein bestimmtes Formular verwenden soll anstatt ein Standardformular zu erstellen, inspiziert es die Klasse. Es erhält die Liste der Felder und anderer Informationen von der Klasse selbst, ohne sie instanziiert zu haben.

Ihre benutzerdefinierte Klasse definiert jedoch das zusätzliche Formularfeld nicht auf Klassenebene, stattdessen fügt es eins dynamisch hinzu nachdem es instanziiert wurde – das ist zu spät für den ModelAdmin, um diese Änderung zu erkennen.

Eine Möglichkeit, Ihr Problem anzugehen, könnte darin bestehen, von ModelAdmin abzuleiten und seine Methode get_fieldsets zu überschreiben, um tatsächlich die ModelForm-Klasse zu instanziieren und die Liste der Felder von der Instanz anstatt von der Klasse zu erhalten. Sie sollten jedoch im Hinterkopf behalten, dass dies möglicherweise etwas langsamer ist als die Standardimplementierung.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X