2010-01-02 3 views
22

Étant donné les modèles suivants:Django: Initialiser un FormSet de formulaires personnalisés avec des instances

class Graph(models.Model): 
    owner = models.ForeignKey(User) 

    def __unicode__(self): 
     return u'%d' % self.id 

class Point(models.Model): 
    graph = models.ForeignKey(Graph) 
    date = models.DateField(primary_key = True) 
    abs = models.FloatField(null = True) 
    avg = models.FloatField(null = True) 

    def __unicode__(self): 
     return u'%s' % self.date 

Je suis en train de créer un formulaire pour les listes d'édition de points. Les balises d'entrée HTML requièrent des attributs supplémentaires à régler, donc je me sers du formulaire personnalisé suivant:

class PointForm(forms.ModelForm): 
    graph = forms.ModelChoiceField(queryset = Graph.objects.all(), 
            widget = forms.HiddenInput()) 
    date = forms.DateField(widget = forms.HiddenInput(), label = 'date') 
    abs = forms.FloatField(widget = forms.TextInput(
             attrs = {'class': 'abs-field'}), 
          required = False) 

    class Meta: 
     model = Point 
     fields = ('graph', 'date', 'abs') # Other fields are not edited. 

    def pretty_date(self): 
     return self.data.strftime('%B') 

A ce stade, je ne sais pas comment passer des instances de la classe Point à un FormSet:

def edit(request): 
    PointFormSet = forms.formsets.formset_factory(PointForm, extra = 0) 
    if request.method == 'POST': 
     return 

    # Receive 3 points to edit from the database. 
    graph, res = Graph.objects.get_or_create(id = 1) 
    one_day = datetime.timedelta(days = 1) 
    today  = datetime.date.today() 
    do_edit = [] 
    for date in [today - (x * one_day) for x in range(3)]: 
     point, res = Point.objects.get_or_create(graph = graph, date = date) 
     do_edit.append(point) 

    formset = PointFormSet(????) # How is this initialized with the points? 

J'ai trouvé un hack qui fonctionne un peu, mais il conduit à des erreurs plus tard en essayant de traiter les données POST résultant:

do_edit = [] 
for date in [today - (x * one_day) for x in range(3)]: 
    point, res = Point.objects.get_or_create(graph = graph, date = date) 
    data   = point.__dict__.copy() 
    data['graph'] = graph 
    do_edit.append(data) 

formset = PointFormSet(initial = do_edit) 

Comment cela se fait correctement?

Pour la référence, mon modèle ressemble à ceci:

<form action="" method="post"> 
{{ formset.management_form }} 
<table> 
    <tbody> 
    {% for form in formset.forms %} 
     <tr> 
      <td>{{ form.graph }} {{ form.date }} {{ form.pretty_date }}:</td> 
      <td width="100%">{{ form.abs }}</td> 
     </tr> 
    {% endfor %} 
    </tbody> 
</table> 
</form> 

Répondre

23

L'astuce est d'utiliser un « ModelFormset » au lieu d'une formset car ils permettent l'initialisation d'un queryset. Les docs sont here, ce que vous faites est de fournir un formulaire = * lors de la création du formset du modèle et queryset = * lors de l'instanciation du formset. L'argument form = * n'est pas bien documenté (il a fallu creuser un peu dans le code pour s'assurer qu'il est bien là).

def edit(request): 
    PointFormSet = modelformset_factory(Point, form = PointForm) 
    qset = Point.objects.all() #or however your getting your Points to modify 
    formset = PointFormset(queryset = qset) 
    if request.method == 'POST': 
     #deal with posting the data 
     formset = PointFormset(request.POST) 
     if formset.is_valid(): 
      #if it is not valid then the "errors" will fall through and be returned 
      formset.save() 
     return #to your redirect 

    context_dict = {'formset':formset, 
        #other context info 
        } 

    return render_to_response('your_template.html', context_dict) 

Le code passe ainsi facilement. Si la requête est un GET, le formulaire instancié est renvoyé à l'utilisateur. Si la requête est un POST et que le formulaire est not .is_valid(), les erreurs "tombent" et sont renvoyées dans le même modèle. Si la requête est un POST et que les données sont valides, le formset est sauvegardé.

Espérons que ça aide.

-Est

+1

Ahhhh, merci beaucoup, qui fonctionne magnifiquement. Correction mineure: Lors de la validation des données POST, l'initialisation ressemble à ceci: "PointFormset (request.POST, queryset = qset)". Sinon, vous obtiendrez cette erreur: "Point avec cette date existe déjà". – knipknap

+0

doh ... bonne capture ... J'ai oublié que lorsque vous utilisiez model_formset et que vous fournissiez des données, vous deviez donner aussi le queryset ... Je n'ai jamais vraiment eu d'utilisation pour ce cas d'utilisation. – JudoWill

0

Si vous avez une seule valeur possible que vous souhaitez définir, ou peut-être une fermeture des valeurs, il est possible de les mettre après que l'utilisateur les données à POTEAUX votre serveur en utilisant commit=False

S'il vous plaît examiner le code suivant:

class UserReferralView(View): 
    ReferralFormSet = modelformset_factory(ReferralCode, 
              form=ReferralTokenForm, extra=1) 

    def get(self, request): 
     pass 

    def post(self, request): 
     referral_formset = UserUpdateView.ReferralFormSet(request.POST) 

     if referral_formset.is_valid(): 
      instances = referral_formset.save(commit=False) 
      for instance in instances: 
       instance.user = request.user 
       instance.save() 
      return redirect(reverse('referrals.success_view')) 
     else: 
      return redirect(reverse('referrals.failure_view')) 
Questions connexes