views:

392

answers:

2

I have a Django form with several fields in it one of which needs to be repeated n times (where n is not known at design time) how would I go about coding this (if it is possible at all)?

e.g. instead of :-

Class PaymentsForm(forms.form):
    invoice = forms.CharField(widget=ValueHiddenInput())
    total = forms.CharField(widget=ValueHiddenInput())
    item_name_1 = forms.CharField(widget=ValueHiddenInput())
    item_name_2 = forms.CharField(widget=ValueHiddenInput())
    .
    .
    .
    item_name_n = forms.CharField(widget=ValueHiddenInput())

I need something like :-

Class PaymentsForm(forms.form):
    invoice = forms.CharField(widget=ValueHiddenInput())
    total = forms.CharField(widget=ValueHiddenInput())
    item_name[n] = forms.CharField(widget=ValueHiddenInput())

Thanks,
Richard.

+3  A: 

Use formsets.

Daniel Roseman
formsets repeats the model multiple times, not the field.
nbv4
How would this work if I have other values in the form that should only be repeated once? I have edited the question to show this.
Frozenskys
+1 This is a cleaner solution than adding extra fields in the __init__ method. Remember that an HTML form doesn't need to be a single Django Form object, it can be many Form objects. In this case you'd use one Form for all the one-time-only fields, and then another Form containing the repeated field(s) - it's the second Form that you would use in a Formset. This is why Formsets exist.
Carl Meyer
+5  A: 

You can create the repeated fields in the __init__ method of your form:

class PaymentsForm(forms.Form):
    invoice = forms.CharField(widget=forms.HiddenInput())
    total = forms.CharField(widget=forms.HiddenInput())

    def __init__(self, *args, **kwargs):
        super(PaymentsForm, self).__init__(*args, **kwargs)
        for i in xrange(10):
            self.fields['item_name_%d' % i] = forms.CharField(widget=forms.HiddenInput())

More about dynamic forms can be found e.g. here

edit: to answer the question in your comment: just give the number of repetitions as an argument to the __init__ method, something like this:

    def __init__(self, repetitions, *args, **kwargs):
        super(PaymentsForm, self).__init__(*args, **kwargs)
        for i in xrange(repetitions):
            self.fields['item_name_%d' % i] = forms.CharField(widget=forms.HiddenInput())

and then in your view (or wherever you create the form):

payments_form = PaymentsForm(10)
piquadrat
OK that looks cool, how could I pass the xrange(n) value into the class when I create an instance of the form? - (Guess I need to get myself some python books :) )
Frozenskys
This works, but I'd recommend splitting out the "n times" field into a separate Form object and using a Formset, as in Daniel Roseman's answer.
Carl Meyer
I've done it this way on several occasions :)
Jiaaro