Django: plusieurs modèles dans un modèle utilisant des formulaires [fermé]

114

Je suis en train de créer une application de suivi des tickets de support et j'ai quelques modèles que je voudrais créer à partir d'une page. Les billets appartiennent à un client via une clé étrangère. Les notes appartiennent également aux tickets via une clé étrangère. J'aimerais avoir la possibilité de sélectionner un client (c'est un projet entièrement séparé) OU de créer un nouveau client, puis de créer un ticket et enfin de créer une note attribuée au nouveau ticket.

Depuis que je suis assez nouveau sur Django, j'ai tendance à travailler de manière itérative, en essayant à chaque fois de nouvelles fonctionnalités. J'ai joué avec ModelForms mais je veux masquer certains champs et faire des validations complexes. Il semble que le niveau de contrôle que je recherche nécessite des jeux de formulaires ou tout fait à la main, avec une page de modèle fastidieuse et codée à la main, ce que j'essaie d'éviter.

Y a-t-il une fonctionnalité intéressante qui me manque? Quelqu'un a-t-il une bonne référence ou un exemple d'utilisation des jeux de formulaires? J'ai passé un week-end entier sur la documentation de l'API pour eux et je n'ai toujours aucune idée. Est-ce un problème de conception si je décompose et que je code tout à la main?

néoice
la source
dans un premier temps, vous devez valider votre formulaire client et s'il était valide, créer une copie à partir de request.POST (new_data = request.POST.copy ()). puis obtenir l'identifiant client (à partir du formulaire client validé) et avec la mise à jour de new_data, faire ID client une valeur dans le champ clé étrangère (peut-être client dans votre modèle) .Et enfin, considérez new_data pour valider votre deuxième formulaire (Tickets)
Negar37

Réponses:

87

Ce n'est vraiment pas trop difficile à implémenter avec ModelForms . Disons donc que vous avez les formulaires A, B et C. Vous imprimez chacun des formulaires et la page et vous devez maintenant gérer le POST.

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

Voici les documents pour la validation personnalisée.

Jason Christa
la source
2
btw, je ne pense pas que les ensembles de formulaires soient une bonne solution au problème que vous avez décrit. Je les ai toujours utilisés pour représenter plusieurs instances d'un modèle. Par exemple, vous avez un formulaire de candidature et vous souhaitez que 3 références vous permettent de créer un ensemble de formulaires contenant 3 instances du modèle de référence.
Jason Christa
1
notez qu'avec la façon dont vous le faites, l'appel .is_valid () n'est pas court-circuité. Si vous voulez le court-circuiter, vous devrez retarder l'appel de la fonction .is_valid () jusqu'au 'et'.
Lie Ryan
66

J'étais à peu près dans la même situation il y a un jour, et voici mes 2 cents:

1) J'ai trouvé sans doute la démonstration la plus courte et la plus concise de l'entrée de plusieurs modèles sous forme unique ici: http://collingrady.wordpress.com/2008/02/18/editing-multiple-objects-in-django-with-newforms/ .

En un mot: créez un formulaire pour chaque modèle, soumettez-les tous les deux au modèle en un seul <form>, en utilisant prefixkeyarg et faites valider la vue. S'il y a une dépendance, assurez-vous simplement d'enregistrer le modèle "parent" avant la dépendance, et utilisez l'ID du parent pour la clé étrangère avant de valider la sauvegarde du modèle "enfant". Le lien a la démo.

2) Peut-être que les ensembles de formulaires peuvent être battus pour faire cela, mais pour autant que je me sois plongé dans les détails, les ensembles de formulaires sont principalement destinés à entrer des multiples du même modèle, qui peuvent éventuellement être liés à un autre modèle / modèles par des clés étrangères. Cependant, il ne semble pas y avoir d'option par défaut pour entrer les données de plus d'un modèle et ce n'est pas à cela que formset semble être destiné.

Gnudiff
la source
26

J'ai récemment eu un problème et j'ai juste compris comment faire cela. En supposant que vous ayez trois classes, Primary, B, C et que B, C ont une clé étrangère vers primaire

    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,
      })

Cette méthode devrait vous permettre de faire la validation dont vous avez besoin, ainsi que de générer les trois objets sur la même page. J'ai également utilisé du javascript et des champs cachés pour permettre la génération de plusieurs objets B, C sur la même page.


la source
3
Dans cet exemple, comment définissez-vous les clés étrangères pour les modèles B et C pour qu'elles pointent vers le modèle principal?
Utilisateur
Je n'ai que deux modèles que je souhaite afficher sur le même formulaire. Mais je n'obtiens pas l'instruction exclude = ('primary',). Qu'est-ce que le primaire? Si vous avez 2 modèles CustomerConfig et Contract. Le contrat a la clé étrangère de CustomerConfig. Tels que customer_config = models.ForeignKey ('CustomerPartnerConfiguration') Qu'est-ce que 'primary'?
pitchblack408
10

Le MultiModelForm de django-betterformsest un wrapper pratique pour faire ce qui est décrit dans la réponse de Gnudiff . Il encapsule les ModelForms réguliers dans une seule classe qui est utilisée de manière transparente (au moins pour un usage de base) comme un formulaire unique. J'ai copié un exemple de leurs documents ci-dessous.

# 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
la source
Juste vu django-betterformset sa classe MultiModelForm avant de trouver votre réponse. Leur solution semble vraiment bonne, mais il semble qu'elle n'a pas été mise à jour depuis un certain temps. Utilisez-vous toujours ce @jozxyqk? Des problèmes?
enchance
@enchance ça fait quelques années. À l'époque, je trouvais cela pratique et l'une des meilleures options. Si vous n'avez pas trop de fantaisie, cela vous fait gagner du temps. J'imagine que lorsque vous voulez commencer à personnaliser et à créer des formulaires non triviaux, il serait plus facile de créer les vôtres. Mélanger facilement les formulaires et les contextes dans les vues est la première fonctionnalité que je pense vraiment avoir manquée dans django.
jozxyqk
Merci pour l'homme de réponse. J'envisage juste de le bifurquer et peut-être de mettre à jour quelques choses en cours de route. D'après ce que j'ai vu jusqu'à présent, cela fonctionne très bien. Vous avez raison, c'est un énorme gain de temps.
enchance
5

J'ai actuellement une solution de contournement fonctionnelle (elle passe mes tests unitaires). C'est une bonne solution à mon avis lorsque vous ne souhaitez ajouter qu'un nombre limité de champs d'autres modèles.

Est-ce que j'ai râté quelque chose ?

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
la source
3

"Je souhaite masquer certains champs et effectuer des validations complexes."

Je commence par l'interface d'administration intégrée.

  1. Créez le ModelForm pour afficher les champs souhaités.

  2. Étendez le formulaire avec les règles de validation dans le formulaire. C'est généralement une cleanméthode.

    Assurez-vous que cette partie fonctionne raisonnablement bien.

Une fois que cela est fait, vous pouvez vous éloigner de l'interface d'administration intégrée.

Ensuite, vous pouvez vous amuser avec plusieurs formulaires partiellement liés sur une seule page Web. Ceci est un tas de trucs de modèle pour présenter tous les formulaires sur une seule page.

Ensuite, vous devez écrire la fonction view pour lire et valider les différentes choses du formulaire et faire les différents objets save ().

"Est-ce un problème de conception si je décompose et que je code tout à la main?" Non, c'est juste beaucoup de temps pour pas grand chose.

S.Lott
la source
Je ne sais pas comment, donc ne le faites pas
orokusaki
1
@orokusaki: Que voulez-vous de plus? Cela semble décrire une solution. Que dire de plus? La question est vague, il est donc difficile de fournir du code réel. Plutôt que de vous plaindre, veuillez fournir une suggestion d'amélioration. Que suggérez-vous?
S.Lott