Plusieurs modèles dans un seul ModelForm django?

98

Est-il possible d'avoir plusieurs modèles inclus dans un seul ModelFormdans django? J'essaye de créer un formulaire de modification de profil. Je dois donc inclure certains champs du modèle User et du modèle UserProfile. Actuellement, j'utilise 2 formulaires comme celui-ci

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

Existe-t-il un moyen de les regrouper en un seul formulaire ou dois-je simplement créer un formulaire et gérer le chargement et l'enregistrement de la base de données?

Jason Webb
la source
Copie possible de Django: plusieurs modèles dans un modèle utilisant des formulaires
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Réponses:

93

Vous pouvez simplement afficher les deux formulaires dans le modèle à l'intérieur d'un <form>élément html. Traitez ensuite simplement les formulaires séparément dans la vue. Vous pourrez toujours utiliser form.save()et ne pas avoir à traiter vous-même le chargement et l'enregistrement de la base de données.

Dans ce cas, vous ne devriez pas en avoir besoin, mais si vous allez utiliser des formulaires avec les mêmes noms de champ, regardez dans le prefixkwarg pour les formulaires django. (J'ai répondu à une question à ce sujet ici ).

Zach
la source
C'est un bon conseil, mais dans certains cas, cela n'est pas applicable, par exemple. formulaire de modèle personnalisé pour un formset.
Wtower
8
Quel serait un moyen simple de créer une vue basée sur une classe capable d'afficher plus d'un formulaire et un modèle qui les combine ensuite dans le même <form>élément?
jozxyqk
1
Mais comment? Habituellement, un FormViewseul lui est form_classassigné.
erikbwork
@erikbwork Vous ne devez pas utiliser FormView dans ce cas. Sous TemplateView- classez simplement et implémentez la même logique que FormView, mais avec plusieurs formulaires.
moppag
10

Vous pouvez essayer d'utiliser ces morceaux de code:

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

Exemple d'utilisation:

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())
Miao ZhiCheng
la source
On dirait que cela ne peut pas être utilisé dans l'administrateur en raison de certaines vérifications explicites:admin.E016) The value of 'form' must inherit from 'BaseModelForm'.
WhyNotHugo
Comment puis-je l'utiliser avec UpdateView?
Pavel Shlepnev le
3

erikbwork et moi-même avons eu le problème que l'on ne peut inclure qu'un seul modèle dans une vue générique basée sur les classes. J'ai trouvé une manière similaire de l'aborder comme Miao, mais plus modulaire.

J'ai écrit un Mixin pour que vous puissiez utiliser toutes les vues génériques basées sur les classes. Définissez le modèle, les champs et maintenant aussi child_model et child_field - et vous pouvez ensuite envelopper les champs des deux modèles dans une balise comme Zach le décrit.

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

Exemple d'utilisation:

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

Ou avec ModelFormClass:

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

Terminé. J'espère que cela aide quelqu'un.

LGG
la source
En cela save_child_form.course_key = self.object, qu'est-ce que c'est .course_key?
Adam Starrh
Je pense que course_key est le modèle associé, dans mon cas c'est "user" comme dans UserProfile.user qui est un backref, peut-être que ce nom de champ devrait être personnalisable s'il devait être un mixin réutilisable. Mais j'ai toujours un autre problème où le formulaire enfant n'est pas réellement rempli avec les données initiales, tous les champs de l'utilisateur sont préremplis mais pas pour UserProfile. Je devrai peut-être résoudre ce problème en premier.
robvdl
Le problème pour lequel le formulaire enfant n'est pas rempli est que dans la méthode get_child_form, il appelle return self.child_form_class(**self.get_form_kwargs())mais il obtient la mauvaise instance de modèle kwargs['instance'], par exemple, instance est le modèle principal et non le modèle enfant. Pour résoudre ce problème, vous devez d'abord enregistrer les kwargs dans une variable, kwargs = self.get_form_kwargs()puis mettre kwargs['initial']à jour avec l'instance de modèle correcte avant d'appeler return self.child_form_class(**kwargs). Dans mon cas, c'était kwargs['instance'] = kwargs['instance'].profilesi cela avait du sens.
robvdl
Malheureusement, lors de la sauvegarde, il plantera toujours à deux endroits, l'un où self.object n'est pas encore là dans form_valid, donc il lance une AttributeError, et une autre instance de lieu n'est pas là. Je ne suis pas sûr que cette solution ait été entièrement testée avant d'être publiée, il serait donc préférable d'utiliser l'autre réponse en utilisant CombinedFormBase.
robvdl
1
Qu'est-ce que c'est model_forms?
Granny Aching le
2

Vous devriez probablement jeter un œil aux ensembles de formulaires en ligne . Les ensembles de formulaires en ligne sont utilisés lorsque vos modèles sont liés par une clé étrangère.

John Percival Hackworth
la source
1
Les jeux de formulaires en ligne sont utilisés lorsque vous devez travailler avec une relation un à plusieurs. Comme une entreprise où vous ajoutez des employés. J'essaye de combiner 2 tables en une seule forme. C'est une relation un à un.
Jason Webb
L'utilisation d'un formset en ligne fonctionnerait, mais serait probablement loin d'être idéale. Vous pouvez également créer un modèle qui gère la relation à votre place, puis utiliser un seul formulaire. Le simple fait d'avoir une seule page avec 2 formulaires comme suggéré dans stackoverflow.com/questions/2770810/… fonctionnerait.
John Percival Hackworth
2

Vous pouvez vérifier ma réponse ici pour un problème similaire.

Il explique comment combiner l'enregistrement et le profil utilisateur dans un seul formulaire, mais il peut être généralisé à n'importe quelle combinaison ModelForm.

Mitar
la source
0

J'ai utilisé MultiForm et MultiModelForm de django betterforms dans mon projet. Le code peut cependant être amélioré. Par exemple, il dépend de django.six, qui n'est pas pris en charge par 3. +, mais tous peuvent être facilement corrigés

Cette question est apparue plusieurs fois dans StackOverflow, donc je pense qu'il est temps de trouver un moyen standardisé de faire face à cela.

J Eti
la source