Anonymous avatar Anonymous committed e85ef5b

Added support for specifying initial values to model formsets and inline formsets.

This make them consistent with the similar capability of regular
formsets. Thanks to simon29 form the report and to Claude Paroz for the
patch.

Fixes #14574.

Comments (0)

Files changed (6)

django/forms/formsets.py

         if self.is_bound:
             defaults['data'] = self.data
             defaults['files'] = self.files
-        if self.initial:
+        if self.initial and not 'initial' in kwargs:
             try:
                 defaults['initial'] = self.initial[i]
             except IndexError:

django/forms/models.py

     def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
                  queryset=None, **kwargs):
         self.queryset = queryset
+        self.initial_extra = kwargs.pop('initial', None)
         defaults = {'data': data, 'files': files, 'auto_id': auto_id, 'prefix': prefix}
         defaults.update(kwargs)
         super(BaseModelFormSet, self).__init__(**defaults)
             kwargs['instance'] = self._existing_object(pk)
         if i < self.initial_form_count() and not kwargs.get('instance'):
             kwargs['instance'] = self.get_queryset()[i]
+        if i >= self.initial_form_count() and self.initial_extra:
+            # Set initial values for extra forms
+            try:
+                kwargs['initial'] = self.initial_extra[i-self.initial_form_count()]
+            except IndexError:
+                pass
         return super(BaseModelFormSet, self)._construct_form(i, **kwargs)
 
     def get_queryset(self):
 class BaseInlineFormSet(BaseModelFormSet):
     """A formset for child objects related to a parent."""
     def __init__(self, data=None, files=None, instance=None,
-                 save_as_new=False, prefix=None, queryset=None):
+                 save_as_new=False, prefix=None, queryset=None, **kwargs):
         from django.db.models.fields.related import RelatedObject
         if instance is None:
             self.instance = self.fk.rel.to()
             queryset = self.model._default_manager
         qs = queryset.filter(**{self.fk.name: self.instance})
         super(BaseInlineFormSet, self).__init__(data, files, prefix=prefix,
-                                                queryset=qs)
+                                                queryset=qs, **kwargs)
 
     def initial_form_count(self):
         if self.save_as_new:

docs/releases/1.4.txt

 * The MySQL database backend can now make use of the savepoint feature
   implemented by MySQL version 5.0.3 or newer with the InnoDB storage engine.
 
+* It is now possible to pass initial values to the model forms that are part of
+  both model formsets and inline model formset as returned from factory
+  functions ``modelformset_factory`` and ``inlineformset_factory`` respectively
+  just like with regular formsets. However, initial values only apply to extra
+  forms i.e. those which are not bound to an existing model instance.
+
 Backwards incompatible changes in 1.4
 =====================================
 

docs/topics/forms/formsets.txt

 override ``__iter__``, you will need to also override ``__getitem__`` to have
 matching behavior.
 
+.. _formsets-initial-data:
+
 Using initial data with a formset
 ---------------------------------
 

docs/topics/forms/modelforms.txt

 
     >>> AuthorFormSet = modelformset_factory(Author, exclude=('birth_date',))
 
+Providing initial values
+------------------------
+
+.. versionadded:: 1.4
+
+As with regular formsets, it is possible to :ref:`specify initial data
+<formsets-initial-data>` for forms in the formset by specifying an ``initial``
+parameter when instantiating the model formset class returned by
+``modelformset_factory``. However, with model formsets the initial values only
+apply to extra forms, those which are not bound to an existing object instance.
+
 .. _saving-objects-in-the-formset:
 
 Saving objects in the formset

tests/regressiontests/model_formsets_regress/tests.py

             ["<Host: matrix.de.eu.dal.net>", "<Host: tranquility.hub.dal.net>"]
             )
 
+    def test_initial_data(self):
+        user = User.objects.create(username="bibi", serial=1)
+        UserSite.objects.create(user=user, data=7)
+        FormSet = inlineformset_factory(User, UserSite, extra=2)
+
+        formset = FormSet(instance=user, initial=[{'data': 41}, {'data': 42}])
+        self.assertEqual(formset.forms[0].initial['data'], 7)
+        self.assertEqual(formset.extra_forms[0].initial['data'], 41)
+        self.assertTrue(u'value="42"' in formset.extra_forms[1].as_p())
+
+
 class FormsetTests(TestCase):
     def test_error_class(self):
         '''
             self.assertTrue(isinstance(form.errors, ErrorDict))
             self.assertTrue(isinstance(form.non_field_errors(), ErrorList))
 
+    def test_initial_data(self):
+        User.objects.create(username="bibi", serial=1)
+        Formset = modelformset_factory(User, extra=2)
+        formset = Formset(initial=[{'username': u'apollo11'}, {'username': u'apollo12'}])
+        self.assertEqual(formset.forms[0].initial['username'], "bibi")
+        self.assertEqual(formset.extra_forms[0].initial['username'], "apollo11")
+        self.assertTrue(u'value="apollo12"' in formset.extra_forms[1].as_p())
+
 class CustomWidget(forms.CharField):
     pass
 
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.