Django: Mehrere Modelle in einer Vorlage mithilfe von Formularen [geschlossen]

113

Ich erstelle eine Support-Ticket-Tracking-App und habe einige Modelle, die ich auf einer Seite erstellen möchte. Tickets gehören einem Kunden über einen ForeignKey. Notizen gehören auch über einen ForeignKey zu Tickets. Ich möchte die Möglichkeit haben, einen Kunden auszuwählen (das ist ein ganz separates Projekt) ODER einen neuen Kunden zu erstellen, dann ein Ticket zu erstellen und schließlich eine dem neuen Ticket zugewiesene Notiz zu erstellen.

Da ich für Django ziemlich neu bin, arbeite ich in der Regel iterativ und probiere jedes Mal neue Funktionen aus. Ich habe mit ModelForms gespielt, möchte aber einige der Felder ausblenden und eine komplexe Validierung durchführen. Es scheint, als ob die von mir gesuchte Kontrollebene entweder Formsets erfordert oder alles von Hand erledigt, einschließlich einer mühsamen, handcodierten Vorlagenseite, die ich zu vermeiden versuche.

Gibt es eine schöne Funktion, die mir fehlt? Hat jemand eine gute Referenz oder ein gutes Beispiel für die Verwendung von Formsets? Ich habe ein ganzes Wochenende mit den API-Dokumenten für sie verbracht und bin immer noch ahnungslos. Ist es ein Designproblem, wenn ich alles zerlege und von Hand codiere?

neoice
quelle
Zuerst sollten Sie Ihr Kundenformular validieren und, falls es gültig war, eine Kopie aus request.POST (new_data = request.POST.copy ()) erstellen und dann die Kunden-ID (aus dem validierten Kundenformular) abrufen und mit der Aktualisierung von new_data erstellen Kunden-ID ein Wert für das Fremdschlüsselfeld (möglicherweise Kunde in Ihrem Modell). Und schließlich berücksichtigen Sie new_data, um Ihr zweites Formular (Tickets) zu validieren
Negar37

Antworten:

87

Dies ist mit ModelForms nicht allzu schwer zu implementieren . Nehmen wir also an, Sie haben Formulare A, B und C. Sie drucken jedes Formular und jede Seite aus und müssen nun den POST bearbeiten.

if request.POST():
    a_valid = formA.is_valid()
    b_valid = formB.is_valid()
    c_valid = formC.is_valid()
    # we do this since 'and' short circuits and we want to check to whole page for form errors
    if a_valid and b_valid and c_valid:
        a = formA.save()
        b = formB.save(commit=False)
        c = formC.save(commit=False)
        b.foreignkeytoA = a
        b.save()
        c.foreignkeytoB = b
        c.save()

Hier sind die Dokumente zur benutzerdefinierten Validierung.

Jason Christa
quelle
2
Übrigens denke ich nicht, dass Formsets eine gute Lösung für das von Ihnen beschriebene Problem sind. Ich habe sie immer verwendet, um mehrere Instanzen eines Modells darzustellen. Sie haben beispielsweise ein Antragsformular und möchten, dass 3 Verweise auf Sie ein Formularset erstellen, das 3 Instanzen des Referenzmodells enthält.
Jason Christa
1
Beachten Sie, dass der Aufruf von .is_valid () bei Ihrer Vorgehensweise nicht kurzgeschlossen wird. Wenn Sie es kurzschließen möchten, müssen Sie den Aufruf der Funktion .is_valid () bis zum 'und' verzögern.
Lie Ryan
66

Ich war vor einem Tag in ungefähr der gleichen Situation und hier sind meine 2 Cent:

1) Ich fand hier die wohl kürzeste und prägnanteste Demonstration mehrerer Modelleinträge in einer einzigen Form: http://collingrady.wordpress.com/2008/02/18/editing-multiple-objects-in-django-with-newforms/ .

Auf den Punkt gebracht: Machen Sie ein Formular für jedes Modell, legt sie sowohl auf Vorlage in einem einzigen <form>, mitprefixKurz Erstellen keyarg und lassen Sie die Ansichtsüberprüfung durchführen. Wenn eine Abhängigkeit besteht, stellen Sie einfach sicher, dass Sie das "übergeordnete" Modell vor der Abhängigkeit speichern und die ID des übergeordneten Elements für den Fremdschlüssel verwenden, bevor Sie das Speichern des "untergeordneten" Modells festschreiben. Der Link hat die Demo.

2) Vielleicht können Formularsätze in geschlagen werden dies zu tun, aber so weit ich forschte in, Formularsätze sind in erster Linie für ein Vielfaches des gleichen Modells eingeben, das kann optional auf ein anderes Modell / Modelle von Fremdschlüssel gebunden werden. Es scheint jedoch keine Standardoption für die Eingabe von mehr als einem Modell zu geben, und dafür scheint Formset nicht gedacht zu sein.

Gnudiff
quelle
26

Ich hatte vor kurzem ein Problem und habe gerade herausgefunden, wie das geht. Angenommen, Sie haben drei Klassen: Primary, B, C und B, C haben einen Fremdschlüssel für Primary

    class PrimaryForm(ModelForm):
        class Meta:
            model = Primary

    class BForm(ModelForm):
        class Meta:
            model = B
            exclude = ('primary',)

    class CForm(ModelForm):
         class Meta:
            model = C
            exclude = ('primary',)

    def generateView(request):
        if request.method == 'POST': # If the form has been submitted...
            primary_form = PrimaryForm(request.POST, prefix = "primary")
            b_form = BForm(request.POST, prefix = "b")
            c_form = CForm(request.POST, prefix = "c")
            if primary_form.is_valid() and b_form.is_valid() and c_form.is_valid(): # All validation rules pass
                    print "all validation passed"
                    primary = primary_form.save()
                    b_form.cleaned_data["primary"] = primary
                    b = b_form.save()
                    c_form.cleaned_data["primary"] = primary
                    c = c_form.save()
                    return HttpResponseRedirect("/viewer/%s/" % (primary.name))
            else:
                    print "failed"

        else:
            primary_form = PrimaryForm(prefix = "primary")
            b_form = BForm(prefix = "b")
            c_form = Form(prefix = "c")
     return render_to_response('multi_model.html', {
     'primary_form': primary_form,
     'b_form': b_form,
     'c_form': c_form,
      })

Mit dieser Methode sollten Sie die erforderliche Validierung durchführen und alle drei Objekte auf derselben Seite generieren können. Ich habe auch Javascript und versteckte Felder verwendet, um die Erzeugung mehrerer B, C-Objekte auf derselben Seite zu ermöglichen.


quelle
3
Wie stellen Sie in diesem Beispiel die Fremdschlüssel für die Modelle B und C so ein, dass sie auf das Primärmodell verweisen?
Benutzer
Ich habe nur zwei Modelle, die ich auf demselben Formular zeigen möchte. Aber ich bekomme nicht die Anweisung exclude = ('primary',). Was ist primär? Wenn 2 Modelle CustomerConfig und Vertrag haben. Der Vertrag hat den Fremdschlüssel für CustomerConfig. Beispiel: customer_config = models.ForeignKey ('CustomerPartnerConfiguration') Was ist 'primär'?
Pitchblack408
10

Das MultiModelForm von django-betterformsist ein praktischer Wrapper, um das zu tun, was in Gnudiffs Antwort beschrieben ist . Es verpackt reguläre ModelForms in eine einzelne Klasse, die transparent (zumindest für den grundlegenden Gebrauch) als einzelne Form verwendet wird. Ich habe ein Beispiel aus den folgenden Dokumenten kopiert.

# forms.py
from django import forms
from django.contrib.auth import get_user_model
from betterforms.multiform import MultiModelForm
from .models import UserProfile

User = get_user_model()

class UserEditForm(forms.ModelForm):
    class Meta:
        fields = ('email',)

class UserProfileForm(forms.ModelForm):
    class Meta:
        fields = ('favorite_color',)

class UserEditMultiForm(MultiModelForm):
    form_classes = {
        'user': UserEditForm,
        'profile': UserProfileForm,
    }

# views.py
from django.views.generic import UpdateView
from django.core.urlresolvers import reverse_lazy
from django.shortcuts import redirect
from django.contrib.auth import get_user_model
from .forms import UserEditMultiForm

User = get_user_model()

class UserSignupView(UpdateView):
    model = User
    form_class = UserEditMultiForm
    success_url = reverse_lazy('home')

    def get_form_kwargs(self):
        kwargs = super(UserSignupView, self).get_form_kwargs()
        kwargs.update(instance={
            'user': self.object,
            'profile': self.object.profile,
        })
        return kwargs
jozxyqk
quelle
Ich habe gerade django-betterformsdie MultiModelForm- Klasse gesehen, bevor ich auf Ihre Antwort gestoßen bin . Ihre Lösung sieht wirklich gut aus, aber es scheint, dass sie seit einiger Zeit nicht mehr aktualisiert wurde. Verwenden Sie dieses @jozxyqk noch? Irgendwelche Probleme?
Enchance
@enchance es ist ein paar Jahre her. Damals fand ich es praktisch und eine der besseren Möglichkeiten. Wenn Sie nicht zu ausgefallen sind, spart dies Zeit. Ich kann mir vorstellen, dass es einfacher ist, eigene Formulare zu erstellen, wenn Sie mit dem Anpassen und Erstellen nicht trivialer Formulare beginnen möchten. Das einfache Mischen von Formen und Kontexten in Ansichten ist das erste Feature, das ich in Django wirklich vermisst habe.
jozxyqk
Danke für die Antwort Mann. Ich denke darüber nach, es einfach zu gabeln und vielleicht ein paar Dinge auf dem Weg zu aktualisieren. Nach dem, was ich bisher gesehen habe, funktioniert es einwandfrei. Sie haben Recht, es spart viel Zeit.
Enchance
5

Ich habe derzeit eine Problemumgehungsfunktion (sie besteht meine Komponententests). Es ist meiner Meinung nach eine gute Lösung, wenn Sie nur eine begrenzte Anzahl von Feldern aus anderen Modellen hinzufügen möchten.

Vermisse ich hier etwas?

class UserProfileForm(ModelForm):
    def __init__(self, instance=None, *args, **kwargs):
        # Add these fields from the user object
        _fields = ('first_name', 'last_name', 'email',)
        # Retrieve initial (current) data from the user object
        _initial = model_to_dict(instance.user, _fields) if instance is not None else {}
        # Pass the initial data to the base
        super(UserProfileForm, self).__init__(initial=_initial, instance=instance, *args, **kwargs)
        # Retrieve the fields from the user model and update the fields with it
        self.fields.update(fields_for_model(User, _fields))

    class Meta:
        model = UserProfile
        exclude = ('user',)

    def save(self, *args, **kwargs):
        u = self.instance.user
        u.first_name = self.cleaned_data['first_name']
        u.last_name = self.cleaned_data['last_name']
        u.email = self.cleaned_data['email']
        u.save()
        profile = super(UserProfileForm, self).save(*args,**kwargs)
        return profile
Paul Bormans
quelle
3

"Ich möchte einige der Felder ausblenden und eine komplexe Validierung durchführen."

Ich beginne mit der eingebauten Admin-Oberfläche.

  1. Erstellen Sie die ModelForm, um die gewünschten Felder anzuzeigen.

  2. Erweitern Sie das Formular mit den Validierungsregeln im Formular. Normalerweise ist dies einclean Methode.

    Stellen Sie sicher, dass dieser Teil einigermaßen gut funktioniert.

Sobald dies erledigt ist, können Sie sich von der integrierten Administrationsoberfläche entfernen.

Dann können Sie mit mehreren, teilweise verwandten Formularen auf einer einzelnen Webseite herumalbern. Dies ist eine Reihe von Vorlagen, mit denen alle Formulare auf einer einzigen Seite dargestellt werden können.

Dann müssen Sie die Ansichtsfunktion schreiben, um die verschiedenen Formulare zu lesen und zu validieren und die verschiedenen Objektspeicherungen durchzuführen ().

"Ist es ein Designproblem, wenn ich alles kaputt mache und von Hand codiere?" Nein, es ist nur viel Zeit für wenig Nutzen.

S.Lott
quelle
Ich weiß nicht wie, deshalb tu es nicht
orokusaki
1
@orokusaki: Was möchtest du mehr? Das scheint eine Lösung zu beschreiben. Was sollte noch gesagt werden? Die Frage ist vage, daher ist es schwierig, tatsächlichen Code bereitzustellen. Anstatt sich zu beschweren, geben Sie bitte einen Verbesserungsvorschlag. Was schlagen Sie vor?
S.Lott