Più modelli in un unico django ModelForm?


96

È possibile avere più modelli inclusi in un unico ModelFormin django? Sto cercando di creare un modulo di modifica del profilo. Quindi ho bisogno di includere alcuni campi dal modello Utente e dal modello UserProfile. Attualmente sto usando 2 moduli come questo

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")

C'è un modo per consolidarli in un unico modulo o devo solo creare un modulo e gestire il caricamento e il salvataggio del database?


Risposte:


92

Puoi semplicemente mostrare entrambe le forme nel modello all'interno di un <form>elemento html. Quindi elabora i moduli separatamente nella vista. Sarai comunque in grado di utilizzare form.save()e non dovrai elaborare il caricamento e il salvataggio del db.

In questo caso non dovresti averne bisogno, ma se intendi usare moduli con gli stessi nomi di campo, guarda nel prefixkwarg per i moduli django. (Ho risposto a una domanda al riguardo qui ).


Questo è un buon consiglio, ma in alcuni casi non è applicabile, ad es. modulo modello personalizzato per un formset.
Wtower

8
Quale sarebbe un modo semplice per rendere una visualizzazione basata su classi in grado di mostrare più di un modulo e un modello che poi li combina nello stesso <form>elemento?
jozxyqk

1
Ma come? Di solito a un FormViewsolo è form_classassegnato un singolo .
erikbwork

@erikbwork Non dovresti usare un FormView per questo caso. Basta TemplateViewcreare una sottoclasse e implementare la stessa logica di FormView, ma con più moduli.
moppag

10

Puoi provare a utilizzare questi pezzi di codice:

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

Utilizzo di esempio:

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())

Sembra che questo non possa essere utilizzato admin.E016) The value of 'form' must inherit from 'BaseModelForm'.
nell'amministratore

Come posso usarlo con UpdateView?
Pavel Shlepnev

3

Sia io che erikbwork abbiamo avuto il problema di poter includere solo un modello in una visualizzazione generica basata su classi. Ho trovato un modo simile di approcciarlo come Miao, ma più modulare.

Ho scritto un mixin in modo da poter utilizzare tutte le viste generiche basate su classi. Definisci modello, campi e ora anche child_model e child_field, quindi puoi racchiudere i campi di entrambi i modelli in un tag come Zach descrive.

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())

Utilizzo di esempio:

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

O con ModelFormClass:

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

Fatto. Spero che aiuti qualcuno.


In questo save_child_form.course_key = self.objectcosa c'è .course_key?
Adam Starrh

Penso che course_key sia il modello correlato, nel mio caso è "user" come in UserProfile.user che è un backref, forse quel nome di campo dovrebbe essere personalizzabile se dovesse essere un mixin riutilizzabile. Ma ho ancora un altro problema in cui il modulo figlio non è effettivamente popolato con i dati iniziali, tutti i campi da Utente sono precompilati ma non per UserProfile. Potrei doverlo aggiustare prima.
robvdl

Il problema per cui il form figlio non viene popolato è perché nel metodo get_child_form, chiama return self.child_form_class(**self.get_form_kwargs())ma ottiene l'istanza del modello sbagliata kwargs['instance'], ad esempio l'istanza è il modello principale e non il modello figlio. Per risolvere il problema devi prima salvare kwargs in una variabile, kwargs = self.get_form_kwargs()quindi aggiornare kwargs['initial']con l'istanza del modello corretta prima di chiamare return self.child_form_class(**kwargs). Nel mio caso era kwargs['instance'] = kwargs['instance'].profilese questo avesse senso.
robvdl

Sfortunatamente al salvataggio si bloccherà ancora in due punti, uno in cui self.object non è ancora presente in form_valid, quindi genera un AttributeError e un'altra istanza di luogo non è presente. Non sono sicuro che questa soluzione sia stata completamente testata prima di essere pubblicata, quindi potrebbe essere meglio andare con l'altra risposta usando CombinedFormBase.
robvdl

1
Che cos'è model_forms?
Granny Aching

2

Probabilmente dovresti dare un'occhiata ai formset in linea . I formet inline vengono utilizzati quando i modelli sono correlati da una chiave esterna.


1
I formet inline vengono utilizzati quando è necessario lavorare con una relazione uno a molti. Come un'azienda in cui aggiungi dipendenti. Sto provando a combinare 2 tabelle in un unico modulo. È una relazione uno a uno.
Jason Webb

L'uso di un formset in linea funzionerebbe, ma probabilmente non sarebbe l'ideale. Puoi anche creare un modello che gestisce la relazione per te e quindi utilizzare un unico modulo. Avere solo una singola pagina con 2 moduli come suggerito in stackoverflow.com/questions/2770810/… funzionerebbe.
John Percival Hackworth

2

Puoi controllare la mia risposta qui per un problema simile.

Parla di come combinare la registrazione e il profilo utente in un unico modulo, ma può essere generalizzato a qualsiasi combinazione ModelForm.


0

Ho usato Django betterforms 's multiforme e MultiModelForm nel mio progetto. Il codice può essere migliorato, però. Ad esempio, dipende da django.six, che non è supportato da 3. +, ma tutti questi problemi possono essere facilmente risolti

Questa domanda è apparsa più volte in StackOverflow, quindi penso che sia ora di trovare un modo standardizzato per affrontarla.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.