[Django]-Different initial data for each form in a Django formset

79👍

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])

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.

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.

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.

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

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)

Leave a comment