web-dev-qa-db-fra.com

Plusieurs modèles en un seul Django ModelForm?

Est-il possible d'avoir plusieurs modèles inclus dans un seul ModelForm dans django? J'essaie de créer un formulaire de modification de profil. Je dois donc inclure certains champs du modèle utilisateur et le 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 consolider en un seul formulaire ou dois-je simplement créer un formulaire et gérer le chargement et la sauvegarde de la base de données?

88
Jason Webb

Vous pouvez simplement afficher les deux formulaires dans le modèle à l'intérieur d'un élément html <form>. Ensuite, traitez simplement les formulaires séparément dans la vue. Vous pourrez toujours utiliser form.save() et ne pas avoir à traiter le chargement et la sauvegarde de db.

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

84
Zach

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())
8
Miao ZhiCheng

erikbwork et moi-même avons eu le problème de ne pouvoir inclure qu'un seul modèle dans une vue basée sur une classe générique. 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 également child_model et child_field - et vous pourrez ensuite encapsuler 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.

3
LGG

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

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

2
Mitar

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