Mehrere Modelle in einem Django ModelForm?


96

Ist es möglich, ModelFormin Django mehrere Modelle in einem einzigen zu haben? Ich versuche, ein Profilbearbeitungsformular zu erstellen. Daher muss ich einige Felder aus dem Benutzermodell und dem UserProfile-Modell einfügen. Derzeit verwende ich 2 Formulare wie dieses

class UserEditForm(ModelForm):

    class Meta:
        model = User
        fields = ("first_name", "last_name")

class UserProfileForm(ModelForm):

    class Meta:
        model = UserProfile
        fields = ("middle_name", "home_phone", "work_phone", "cell_phone")

Gibt es eine Möglichkeit, diese in einem Formular zu konsolidieren, oder muss ich nur ein Formular erstellen und das Laden und Speichern der Datenbank selbst durchführen?


Antworten:


92

Sie können einfach beide Formulare in der Vorlage innerhalb eines <form>HTML-Elements anzeigen. Verarbeiten Sie dann einfach die Formulare separat in der Ansicht. Sie können die Datenbank weiterhin verwenden form.save()und müssen sie nicht selbst verarbeiten und speichern.

In diesem Fall sollten Sie es nicht benötigen, aber wenn Sie Formulare mit denselben Feldnamen verwenden prefixmöchten , suchen Sie im kwarg nach Django-Formularen. (Ich habe hier eine Frage dazu beantwortet ).


Dies ist ein guter Ratschlag, aber es gibt Fälle, in denen dies nicht zutrifft, z. Benutzerdefiniertes Modellformular für ein Formularset.
Wtower

8
Was wäre eine einfache Möglichkeit, eine klassenbasierte Ansicht so zu gestalten, dass mehr als ein Formular und eine Vorlage angezeigt werden können, die sie dann zu demselben <form>Element kombiniert ?
jozxyqk

1
Aber wie? Normalerweise ist einem FormViewnur eine einzige form_classzugeordnet.
Erikbwork

@erikbwork Sie sollten für diesen Fall keine FormView verwenden. Unterklassifizieren TemplateViewund implementieren Sie einfach dieselbe Logik wie in FormView, jedoch mit mehreren Formularen.
Moppag

10

Sie können versuchen, diesen Code zu verwenden:

class CombinedFormBase(forms.Form):
    form_classes = []

    def __init__(self, *args, **kwargs):
        super(CombinedFormBase, self).__init__(*args, **kwargs)
        for f in self.form_classes:
            name = f.__name__.lower()
            setattr(self, name, f(*args, **kwargs))
            form = getattr(self, name)
            self.fields.update(form.fields)
            self.initial.update(form.initial)

    def is_valid(self):
        isValid = True
        for f in self.form_classes:
            name = f.__name__.lower()
            form = getattr(self, name)
            if not form.is_valid():
                isValid = False
        # is_valid will trigger clean method
        # so it should be called after all other forms is_valid are called
        # otherwise clean_data will be empty
        if not super(CombinedFormBase, self).is_valid() :
            isValid = False
        for f in self.form_classes:
            name = f.__name__.lower()
            form = getattr(self, name)
            self.errors.update(form.errors)
        return isValid

    def clean(self):
        cleaned_data = super(CombinedFormBase, self).clean()
        for f in self.form_classes:
            name = f.__name__.lower()
            form = getattr(self, name)
            cleaned_data.update(form.cleaned_data)
        return cleaned_data

Anwendungsbeispiel:

class ConsumerRegistrationForm(CombinedFormBase):
    form_classes = [RegistrationForm, ConsumerProfileForm]

class RegisterView(FormView):
    template_name = "register.html"
    form_class = ConsumerRegistrationForm

    def form_valid(self, form):
        # some actions...
        return redirect(self.get_success_url())

Sieht so aus, als ob dies im Administrator aufgrund einiger expliziter Überprüfungen nicht verwendet werden kann:admin.E016) The value of 'form' must inherit from 'BaseModelForm'.
WhyNotHugo

Wie kann ich es verwenden UpdateView?
Pavel Shlepnev

3

erikbwork und ich hatten beide das Problem, dass man nur ein Modell in eine generische klassenbasierte Ansicht aufnehmen kann. Ich fand eine ähnliche Herangehensweise wie Miao, aber modularer.

Ich habe ein Mixin geschrieben, damit Sie alle generischen klassenbasierten Ansichten verwenden können. Definieren Sie Modell, Felder und jetzt auch child_model und child_field - und dann können Sie Felder beider Modelle in ein Tag einschließen, wie Zach es beschreibt.

class ChildModelFormMixin: 
    ''' extends ModelFormMixin with the ability to include ChildModelForm '''
    child_model = ""
    child_fields = ()
    child_form_class = None

    def get_child_model(self):
        return self.child_model

    def get_child_fields(self):
        return self.child_fields

    def get_child_form(self):
        if not self.child_form_class:
            self.child_form_class = model_forms.modelform_factory(self.get_child_model(), fields=self.get_child_fields())
        return self.child_form_class(**self.get_form_kwargs())

    def get_context_data(self, **kwargs):
        if 'child_form' not in kwargs:
            kwargs['child_form'] = self.get_child_form()
        return super().get_context_data(**kwargs)

    def post(self, request, *args, **kwargs):
        form = self.get_form()
        child_form = self.get_child_form()

        # check if both forms are valid
        form_valid = form.is_valid()
        child_form_valid = child_form.is_valid()

        if form_valid and child_form_valid:
            return self.form_valid(form, child_form)
        else:
            return self.form_invalid(form)

    def form_valid(self, form, child_form):
        self.object = form.save()
        save_child_form = child_form.save(commit=False)
        save_child_form.course_key = self.object
        save_child_form.save()

        return HttpResponseRedirect(self.get_success_url())

Anwendungsbeispiel:

class ConsumerRegistrationUpdateView(UpdateView):
    model = Registration
    fields = ('firstname', 'lastname',)
    child_model = ConsumerProfile
    child_fields = ('payment_token', 'cart',)

Oder mit ModelFormClass:

class ConsumerRegistrationUpdateView(UpdateView):
    model = Registration
    fields = ('firstname', 'lastname',)
    child_model = ConsumerProfile
    child_form_class = ConsumerProfileForm

Getan. Hoffe das hilft jemandem.


In diesem save_child_form.course_key = self.object, was ist .course_key?
Adam Starrh

Ich denke, course_key ist das verwandte Modell, in meinem Fall "user" wie in UserProfile.user, das ein Backref ist. Vielleicht sollte dieser Feldname anpassbar sein, wenn es sich um ein wiederverwendbares Mixin handelt. Ich habe jedoch noch ein anderes Problem, bei dem das untergeordnete Formular nicht mit Anfangsdaten gefüllt ist. Alle Felder von User sind bereits ausgefüllt, jedoch nicht für UserProfile. Möglicherweise muss ich das zuerst beheben.
Robvdl

Das Problem, warum das untergeordnete Formular nicht ausgefüllt wird, liegt darin return self.child_form_class(**self.get_form_kwargs()), dass es in der Methode get_child_form aufruft, aber die falsche Modellinstanz erhält kwargs['instance'], z. B. ist die Instanz das Hauptmodell und nicht das untergeordnete Modell. Um dies zu beheben, müssen Sie zuerst kwargs in einer Variablen speichern und kwargs = self.get_form_kwargs()dann kwargs['initial']vor dem Aufruf mit der richtigen Modellinstanz aktualisieren return self.child_form_class(**kwargs). In meinem Fall war das, kwargs['instance'] = kwargs['instance'].profilewenn dies Sinn macht.
Robvdl

Leider stürzt es beim Speichern immer noch an zwei Stellen ab, an denen self.object noch nicht in form_valid vorhanden ist, sodass ein AttributeError ausgelöst wird und eine andere Ortsinstanz nicht vorhanden ist. Ich bin mir nicht sicher, ob diese Lösung vor dem Posten vollständig getestet wurde. Daher ist es möglicherweise besser, die andere Antwort mit CombinedFormBase zu verwenden.
Robvdl

1
Was ist model_forms?
Oma schmerzt

2

Sie sollten sich wahrscheinlich Inline-Formsets ansehen . Inline-Formsets werden verwendet, wenn Ihre Modelle durch einen Fremdschlüssel verknüpft sind.


1
Inline-Formsets werden verwendet, wenn Sie mit einer Eins-zu-Viele-Beziehung arbeiten müssen. Zum Beispiel ein Unternehmen, in dem Sie Mitarbeiter hinzufügen. Ich versuche, 2 Tabellen in einer einzigen Form zu kombinieren. Es ist eine Eins-zu-Eins-Beziehung.
Jason Webb

Die Verwendung eines Inline-Formularsatzes würde funktionieren, wäre aber wahrscheinlich weniger als ideal. Sie können auch ein Modell erstellen, das die Beziehung für Sie verwaltet, und dann ein einzelnes Formular verwenden. Nur eine einzelne Seite mit 2 Formularen, wie unter stackoverflow.com/questions/2770810/… vorgeschlagen, würde funktionieren.
John Percival Hackworth

2

Sie können meine Antwort hier auf ein ähnliches Problem überprüfen .

Es wird erläutert, wie Registrierung und Benutzerprofil in einem Formular kombiniert werden. Es kann jedoch auf jede ModelForm-Kombination verallgemeinert werden.


0

Früher habe ich django betterforms ‚s Multiform und MultiModelForm in meinem Projekt. Der Code kann jedoch verbessert werden. Zum Beispiel ist es abhängig von django.six, das von 3. + nicht unterstützt wird, aber all dies kann leicht behoben werden

Diese Frage hat sich gezeigt , mehrere Male in Stackoverflow, so dass ich es an der Zeit denke , mit dieser eine standardisierte Art und Weise des Umgangs zu finden.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.