3

I have a basic Form subclass with a formset generated such as:

MyFormset = formset_factory(
    MyForm,
    extra=5,
    max_num=5,
)

I would like to be able to access the index of the form from within the form.save() and form.__init__ methods.

DanH
  • 5,498
  • 4
  • 49
  • 72
  • 1
    Use JS for this kind of work, if you need to either check in your view or a template then a for loop and a counter is all you need. Specific in your template you can use **forloop.counter** – petkostas Aug 06 '14 at 09:43
  • Thanks, I'll look into that, seems a bit of a long way around though. To clarify, I would like to access the index from `form.save()`. – DanH Aug 06 '14 at 09:50
  • Actually I've realised I'll need to access it in `form.__init__` as well, so I don't think template/JS can help with that. Maybe I'll just subclass BaseFormSet to pass the index, as it seems like currently that's not happening. – DanH Aug 06 '14 at 09:51

4 Answers4

3

You can subclass BaseFormSet to pass the index to the form:

from django.forms.formsets import BaseFormSet

class BaseMyFormSet(BaseFormSet):
    def add_fields(self, form, index):
        """A hook for adding extra fields on to each form instance."""
        super(BaseMyFormSet, self).add_fields(form, index)
        # here call a custom method or perform any other required action
        form.set_index(index)

MyFormset = formset_factory(
    MyForm,
    formset=BaseMyFormSet,
    extra=5,
    max_num=5,
)

This method will be called for each created form in the formset, you may perform whatever operation you need.

To get the index on save, I see two possibilities:

1) You use an hidden field that you set in the set_index method above

class MyForm(forms.Form):
    formset_index = forms.IntegerField(widget=forms.HiddenInput())

    def set_index(self, index):
        self.fields['formset_index'].initial = index

2) You can use enumerate

# definition
class MyForm(forms.Form):
    def save(self, index):
    ...

# save
for index, form in enumerate(my_formset):
   form.save(index)
ACPrice
  • 667
  • 2
  • 10
  • 25
trecouvr
  • 743
  • 5
  • 7
  • Edited the answer because the line `super(MyFormSet, self).add_fields(form, index)` should be `super(BaseMyFormSet, self).add_fields(form, index)` – ACPrice Jul 09 '15 at 22:51
2

The form itself never know what index it has in the formset, much like any given list and its containing objects.

Unless you tell it to know it, so in your case first you have to tell the .save() to accept an additional parameter.

class MyForm(forms.Form):
   def save(self, index):
     #do save magix

Then loop over your formset when validating and pass the form its index.

for index, form in enumerate(my_formset):
   form.save(index)
Henrik Andersson
  • 45,354
  • 16
  • 98
  • 92
0

If you need to have it in a form, create a form_id hidden field bound only to the form, then either with JS, python or a template loop populate the field. In your form methods you can then access the id.

petkostas
  • 7,250
  • 3
  • 26
  • 29
0

As far as I can see, the form's index within a formset is only stored as part of its prefix string.

It is not pretty, but, based on the formset source, you could do something like this:

form.prefix.split('-')[1]

Don't forget to convert to number, if necessary.

Or you could do something like this in your template:

{{ form.prefix|cut:formset.prefix|cut:'-' }}

This just takes the form prefix string, which includes the form index, then removes the irrelevant parts.

djvg
  • 11,722
  • 5
  • 72
  • 103