43

Is it possible to prepopulate a formset with different data for each row? I'd like to put some information in hidden fields from a previous view.

According to the docs you can only set initial across the board.

cerial
  • 439
  • 1
  • 4
  • 4

6 Answers6

78

If you made the same mistake as me, you've slightly mistaken the documentation.

When I first saw this example...

formset = ArticleFormSet(initial=[
 {'title': 'Django is now open source',
  'pub_date': datetime.date.today(),}
])

I assumed that each form is given the same set of initial data based on a dictionary.

However, if you look carefully you'll see that the formset is actually being passed a list of dictionaries.

In order to set different initial values for each form in a formset then, you just need to pass a list of dictionaries containing the different data.

Formset = formset_factory(SomeForm, extra=len(some_objects)
some_formset = FormSet(initial=[{'id': x.id} for x in some_objects])
Alistair
  • 1,104
  • 9
  • 15
  • 4
    This seems to be the simplest way to do this, and the default Django way to do it, and pinpoints the very misconception the OP had. Should be the top answer. – ACPrice Jul 07 '15 at 01:17
  • 1
    Note that this will make `ArticleFormSet` an [unbound form](https://docs.djangoproject.com/en/1.10/topics/forms/#bound-and-unbound-form-instances), as initial values will overwrite the values returned from the Article queryset. – Myer Oct 04 '16 at 08:55
  • 2
    @Alistair, You've made a little mistake here, it's `{'id': x.id}` instead of `{'id': 'x.id'}` – RendoJack Mar 15 '18 at 07:08
  • Great way to do it! Thx! – Ron Apr 06 '18 at 07:37
  • Amazing solution! thank you! Important I have tested out that in formset_factory you have identify exact length of extra forms. – Vyachez Aug 28 '19 at 14:08
  • I tried this and think the extra should be set to 0 if you don't want extra empty forms to be rendered. – mfnx Jun 02 '20 at 23:11
11

You need to use the technique described in this post in order to be able to pass parameters in. Credit to that author for an excellent post. You achieve this in several parts:

A form aware it is going to pick up additional parameters

Example from the linked question:

def __init__(self, *args, **kwargs):
    someeobject = kwargs.pop('someobject')
    super(ServiceForm, self).__init__(*args, **kwargs)
    self.fields["somefield"].queryset = ServiceOption.objects.filter(
                                                           somem2mrel=someobject)

Or you can replace the latter code with

    self.fields["somefield"].initial = someobject

Directly, and it works.

A curried form initialisation setup:

formset = formset_factory(Someform, extra=3)
formset.form = staticmethod(curry(someform, somem2mrel=someobject))

That gets you to passing custom form parameters. Now what you need is:

A generator to acquire your different initial parameters

I'm using this:

def ItemGenerator(Item):
    i = 0
    while i < len(Item):
        yield Item[i]
        i += 1

Now, I can do this:

iterdefs = ItemGenerator(ListofItems) # pass the different parameters 
                                      # as an object here
formset.form = staticmethod(curry(someform, somem2mrel=iterdefs.next()))

Hey presto. Each evaluation of the form method is being evaluated in parts passing in an iterated parameter. We can iterate over what we like, so I'm using that fact to iterate over a set of objects and pass the value of each one in as a different initial parameter.

Community
  • 1
  • 1
  • hey! I think your answer may solve the problem I address in this question http://stackoverflow.com/questions/6123278/same-form-different-variables-how-to-implement. But I actually don't really understand what is happening – psoares May 25 '11 at 15:46
  • 1
    I tried using this approach but can't get the iterator to iterate on each call. My code: `DecorationFileFormSet.form = staticmethod(curry(DecorationFileForm, filetype=counter.next()))` creates forms where the filetype attribute is 1 each time. counter is an instance of `itertools.count(1)` – Jamie Forrest Apr 18 '12 at 15:50
  • Anyone got solution already? The generator is not working(doesn't iterate at all) for me as well. – wgx731 Jul 11 '13 at 04:27
  • Same deal here. Generator doesn't iterate on .next(). Are there any people for whom this answer DID work? – ACPrice Jul 07 '15 at 21:43
3

Building on Antony Vennard's answer, I am not sure what version of python/django he is using but I could not get the generator to work in the curry method either. I am currently on python2.7.3 and django1.5.1. Instead of using a custom Generator, I ended up using the built-in iter() on a list of things to create an iterator and passing the iterator itself in the curry method and calling next() on it in the Form __init__(). Here is my solution:

# Build the Formset:
my_iterator = iter(my_list_of_things) # Each list item will correspond to a form.
Formset = formset_factory(MyForm, extra=len(my_list_of_things))
Formset.form = staticmethod(curry(MyForm, item_iterator=my_iterator))

And in the form:

# forms.py
class MyForm(forms.Form):
    def __init__(self, *args, **kwargs):
        # Calling next() on the iterator/generator here:
        list_item = kwargs.pop('item_iterator').next()

        # Now you can assign whatever you passed in to an attribute
        # on one of the form elements.
        self.fields['my_field'].initial = list_item

Some Key things I found were that you need to either specify an 'extra' value in the formset_factory or use the initial kwarg on the formset to specify a list that corresponds to the list you pass to the iterator (In above example I pass the len() of the my_list_of_things list to 'extra' kwarg to formset_factory). This is necessary to actually create a number of forms in the formset.

vishes_shell
  • 22,409
  • 6
  • 71
  • 81
emispowder
  • 1,787
  • 19
  • 21
2

I had this problem and I made a new widget:

from django.forms.widgets import Select
from django.utils.safestring import mark_safe
class PrepolutatedSelect(Select):
    def render(self, name, value, attrs=None, choices=()):
        if value is None: value = ''
        if value == '':
            value = int(name.split('-')[1])+1
        final_attrs = self.build_attrs(attrs, name=name)
        output = [u'<select%s>' % flatatt(final_attrs)]
        options = self.render_options(choices, [value])
        if options:
            output.append(options)
        output.append('</select>')
        return mark_safe(u'\n'.join(output))

Maybe this will work for you too.

Brock Adams
  • 90,639
  • 22
  • 233
  • 295
diegueus9
  • 29,351
  • 16
  • 62
  • 74
  • I have a similar problem to the OP. I'm trying to create a formset with several forms, with each form having it's own unique set of options for a select element (i.e. "choices"). I can't quite see how this solution would help. Am I missing something? – ACPrice Jul 08 '15 at 06:44
0
formset = BookFormset(request.GET or None,initial=[{'formfield1': x.modelfield_name1,'formfield2':x.modelfield_name2} for x in model])

formfield1,formfield2 are the names of the formfields.

modelfield_name1,modelfield_name2 are the modal field names.

model is name of your modal class in models.py file.

BookFormset is the form or formset name which is defined in your forms.py file

sreekanth
  • 11
  • 4
0

I countered this problem when I created one form for both creating and updating, and the following worked for me. For the form

class SomeForm(forms.ModelForm):

    class Meta:
        model = SomeModel
        fields = ['some_field']

    def __init__(self, *args, **kwargs):
        # Check if an instnace exists, then set the initial values 
        if 'instance' in kwargs:
            kwargs['initial'] = {'some_field': kwargs['instance'].some_field}
        super().__init__(*args, **kwargs)
        ........

For the update view, I did the following:

FormSet = modelformset_factory(
    SomeModel, form=SomeForm)
formset = FormSet(queryset=some_qs, data=request.POST or None)
Marco
  • 2,368
  • 6
  • 22
  • 48