2009-02-20 10 views
91

Je construis une application de suivi des tickets de support et j'ai quelques modèles que je voudrais créer à partir d'une page. Les tickets appartiennent à un client via un ForeignKey. Les Notes appartiennent également à des Tickets via un ForeignKey. J'aimerais avoir la possibilité de sélectionner un client (c'est un projet distinct) OU de créer un nouveau client, puis de créer un ticket et enfin de créer une note assignée au nouveau ticket.Django: plusieurs modèles dans un modèle utilisant des formulaires

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

Y a-t-il une fonctionnalité intéressante qui me manque? Est-ce que quelqu'un a une bonne référence ou un bon exemple pour utiliser des formsets? J'ai passé tout un week-end sur les documents de l'API pour eux et je suis toujours désemparé. Est-ce un problème de conception si je casse et code à la main tout?

Répondre

2

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

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

  1. Construire le ModelForm pour afficher les champs souhaités. Étendre le formulaire avec les règles de validation dans le formulaire

  2. Habituellement, il s'agit d'une méthode clean.

    Assurez-vous que cette pièce fonctionne raisonnablement bien.

Une fois cela 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. C'est un tas de modèles pour présenter tous les formulaires sur une seule page.

Ensuite, vous devez écrire la fonction de vue pour lire et valider les diverses choses de formulaire et faire les différentes sauvegardes d'objets().

"Est-ce un problème de conception si je casse et code à la main tout?" Non, c'est juste beaucoup de temps pour peu d'avantages.

+0

Je ne sais pas comment, donc ne le fait pas – orokusaki

+1

@orokusaki: Que voulez-vous? Cela semble décrire une solution. Que devrait-on dire de plus? La question est vague, il est donc difficile de fournir un code réel. Plutôt que de vous plaindre, veuillez fournir une suggestion d'amélioration. Que suggérez-vous? –

71

Ce n'est vraiment pas trop difficile à implémenter avec ModelForms. Alors disons que vous avez les formulaires A, B et C. Vous imprimez chacun des formulaires et la page et maintenant vous devez 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() 

Here sont les documents pour la validation personnalisée.

+2

btw, je ne pense pas que formsets sont 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 demandeur et vous voulez que 3 références à vous fassiez un formset qui a 3 instances du modèle de référence. –

+0

Notez que, 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'. –

22

J'ai très récemment eu le problème et j'ai trouvé comment faire. En supposant que vous avez trois classes, primaire, B, C et B, C ont une clé étrangère 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 tout ce que la validation dont vous avez besoin, ainsi que de générer les trois objets sur la même page. J'ai également utilisé javascript et les champs cachés pour permettre la génération de plusieurs objets B, C sur la même page.

+2

Dans cet exemple, comment définissez-vous les clés étrangères pour les modèles B et C afin qu'elles pointent vers le modèle principal? – User

+0

J'ai seulement deux modèles que je veux montrer sur le même formulaire. Mais je n'obtiens pas l'instruction exclude = ('primary',). Qu'est-ce qui est primaire? Si vous avez 2 modèles CustomerConfig et Contract. Le contrat a la clé étrangère à CustomerConfig. Comme customer_config = models.ForeignKey ('CustomerPartnerConfiguration') Qu'est-ce que 'primary'? – pitchblack408

62

Je viens était à peu près la même situation il y a un jour, et voici mes 2 cents:

1) Je trouve sans doute la plus courte et la démonstration la plus concise d'entrée de modèle multiple sous forme simple 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 dans un <form> unique, en utilisant prefix keyarg et avez la validation du handle de vue. S'il y a une dépendance, assurez-vous de sauvegarder le modèle "parent" avant de dépendant, 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 formsets peuvent être battus en faisant cela, mais pour autant que je fouillais dans, formsets sont essentiellement pour la saisie des multiples du même modèle, qui peut être éventuellement lié à un autre modèle/modèles par clés étrangères. Cependant, il ne semble pas y avoir d'option par défaut pour saisir plus d'un modèle de données et ce n'est pas ce à quoi le formset semble être destiné.

3

J'ai actuellement une solution de contournement fonctionnelle (il passe mes tests unitaires). C'est une bonne solution à mon avis quand vous voulez seulement ajouter un nombre limité de champs d'autres modèles.

Ai-je oublié quelque chose ici?

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 
8

Le MultiModelForm de django-betterforms est un emballage pratique pour faire ce qui est décrit dans Gnudiff's answer. Il enveloppe ModelForm s dans une seule classe qui est transparente (au moins pour un usage basique) utilisée comme une seule forme. 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 
Questions connexes