115 votes

Plusieurs modèles dans un seul django ModelForm ?

Est-il possible d'inclure plusieurs modèles dans une même ModelForm dans django ? J'essaie de créer un formulaire de modification de profil. J'ai donc besoin d'inclure certains champs du modèle User. y le modèle UserProfile. Actuellement, j'utilise deux 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 moi-même le chargement et la sauvegarde de la base de données ?

0 votes

0 votes

118voto

Zach Points 4886

Vous pouvez simplement montrer les deux formulaires dans le modèle à l'intérieur d'un seul <form> élément html. Il suffit ensuite de traiter 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 la sauvegarde de la base de données.

Dans ce cas, vous ne devriez pas en avoir besoin, mais si vous devez utiliser des formulaires avec les mêmes noms de champs, consultez l'option prefix kwarg pour les formulaires de django. (J'ai répondu à une question à ce sujet aquí ).

0 votes

C'est un bon conseil, mais il y a des cas où cela n'est pas applicable, par exemple un modèle de formulaire personnalisé pour un jeu de formulaires.

12 votes

Quel serait le moyen le plus 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 formulaire ? <form> élément ?

4 votes

Mais comment ? Habituellement, un FormView n'a qu'un seul form_class qui lui est attribué.

12voto

Miao ZhiCheng Points 41

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

0 votes

Il semble que cela ne puisse pas être utilisé dans l'administration en raison de certaines vérifications explicites : admin.E016) The value of 'form' must inherit from 'BaseModelForm'.

0 votes

Comment puis-je l'utiliser avec UpdateView ?

5voto

J Eti Points 100

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

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

4voto

LGG Points 41

Erikbwork et moi avons tous deux rencontré le problème suivant : on ne peut inclure qu'un seul modèle dans une vue générique basée sur une classe. J'ai trouvé une façon similaire d'aborder le problème comme Miao, mais plus modulaire.

J'ai écrit un Mixin pour que vous puissiez utiliser tous les génériques Class Based Views. Définir le modèle, les champs et maintenant aussi child_model et child_field - et ensuite vous pouvez envelopper les champs des deux modèles dans un tag 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

C'est fait. J'espère que cela aidera quelqu'un.

2voto

Mitar Points 1621

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

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X