Loading django/forms/formsets.py +11 −2 Original line number Diff line number Diff line Loading @@ -54,13 +54,14 @@ class BaseFormSet(object): A collection of instances of the same Form class. """ def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, initial=None, error_class=ErrorList): initial=None, error_class=ErrorList, form_kwargs=None): self.is_bound = data is not None or files is not None self.prefix = prefix or self.get_default_prefix() self.auto_id = auto_id self.data = data or {} self.files = files or {} self.initial = initial self.form_kwargs = form_kwargs or {} self.error_class = error_class self._errors = None self._non_form_errors = None Loading Loading @@ -139,9 +140,16 @@ class BaseFormSet(object): Instantiate forms at first property access. """ # DoS protection is included in total_form_count() forms = [self._construct_form(i) for i in range(self.total_form_count())] forms = [self._construct_form(i, **self.get_form_kwargs(i)) for i in range(self.total_form_count())] return forms def get_form_kwargs(self, index): """ Return additional keyword arguments for each individual formset form. """ return self.form_kwargs.copy() def _construct_form(self, i, **kwargs): """ Instantiates and returns the i-th form instance in a formset. Loading Loading @@ -184,6 +192,7 @@ class BaseFormSet(object): auto_id=self.auto_id, prefix=self.add_prefix('__prefix__'), empty_permitted=True, **self.get_form_kwargs(None) ) self.add_fields(form, None) return form Loading docs/topics/forms/formsets.txt +39 −0 Original line number Diff line number Diff line Loading @@ -238,6 +238,8 @@ pre-filled, and is also used to determine how many forms are required. You will probably never need to override either of these methods, so please be sure you understand what they do before doing so. .. _empty_form: ``empty_form`` ~~~~~~~~~~~~~~ Loading Loading @@ -533,6 +535,43 @@ default fields/attributes of the order and deletion fields:: <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date" /></td></tr> <tr><th><label for="id_form-0-my_field">My field:</label></th><td><input type="text" name="form-0-my_field" id="id_form-0-my_field" /></td></tr> Passing custom parameters to formset forms ------------------------------------------ Sometimes your form class takes custom parameters, like ``MyArticleForm``. You can pass this parameter when instantiating the formset:: >>> from django.forms.formsets import BaseFormSet >>> from django.forms.formsets import formset_factory >>> from myapp.forms import ArticleForm >>> class MyArticleForm(ArticleForm): ... def __init__(self, *args, **kwargs): ... self.user = kwargs.pop('user') ... super(MyArticleForm, self).__init__(*args, **kwargs) >>> ArticleFormSet = formset_factory(MyArticleForm) >>> formset = ArticleFormSet(form_kwargs={'user': request.user}) The ``form_kwargs`` may also depend on the specific form instance. The formset base class provides a ``get_form_kwargs`` method. The method takes a single argument - the index of the form in the formset. The index is ``None`` for the :ref:`empty_form`:: >>> from django.forms.formsets import BaseFormSet >>> from django.forms.formsets import formset_factory >>> class BaseArticleFormSet(BaseFormSet): ... def get_form_kwargs(self, index): ... kwargs = super(BaseArticleFormSet, self).get_form_kwargs(index) ... kwargs['custom_kwarg'] = index ... return kwargs .. versionadded:: 1.9 The ``form_kwargs`` argument was added. Using a formset in views and templates -------------------------------------- Loading tests/forms_tests/tests/test_formsets.py +37 −0 Original line number Diff line number Diff line Loading @@ -57,6 +57,12 @@ class SplitDateTimeForm(Form): SplitDateTimeFormSet = formset_factory(SplitDateTimeForm) class CustomKwargForm(Form): def __init__(self, *args, **kwargs): self.custom_kwarg = kwargs.pop('custom_kwarg') super(CustomKwargForm, self).__init__(*args, **kwargs) class FormsFormsetTestCase(SimpleTestCase): def make_choiceformset(self, formset_data=None, formset_class=ChoiceFormSet, Loading Loading @@ -114,6 +120,37 @@ class FormsFormsetTestCase(SimpleTestCase): self.assertFalse(formset.is_valid()) self.assertFalse(formset.has_changed()) def test_form_kwargs_formset(self): """ Test that custom kwargs set on the formset instance are passed to the underlying forms. """ FormSet = formset_factory(CustomKwargForm, extra=2) formset = FormSet(form_kwargs={'custom_kwarg': 1}) for form in formset: self.assertTrue(hasattr(form, 'custom_kwarg')) self.assertEqual(form.custom_kwarg, 1) def test_form_kwargs_formset_dynamic(self): """ Test that form kwargs can be passed dynamically in a formset. """ class DynamicBaseFormSet(BaseFormSet): def get_form_kwargs(self, index): return {'custom_kwarg': index} DynamicFormSet = formset_factory(CustomKwargForm, formset=DynamicBaseFormSet, extra=2) formset = DynamicFormSet(form_kwargs={'custom_kwarg': 'ignored'}) for i, form in enumerate(formset): self.assertTrue(hasattr(form, 'custom_kwarg')) self.assertEqual(form.custom_kwarg, i) def test_form_kwargs_empty_form(self): FormSet = formset_factory(CustomKwargForm) formset = FormSet(form_kwargs={'custom_kwarg': 1}) self.assertTrue(hasattr(formset.empty_form, 'custom_kwarg')) self.assertEqual(formset.empty_form.custom_kwarg, 1) def test_formset_validation(self): # FormSet instances can also have an error attribute if validation failed for # any of the forms. Loading Loading
django/forms/formsets.py +11 −2 Original line number Diff line number Diff line Loading @@ -54,13 +54,14 @@ class BaseFormSet(object): A collection of instances of the same Form class. """ def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, initial=None, error_class=ErrorList): initial=None, error_class=ErrorList, form_kwargs=None): self.is_bound = data is not None or files is not None self.prefix = prefix or self.get_default_prefix() self.auto_id = auto_id self.data = data or {} self.files = files or {} self.initial = initial self.form_kwargs = form_kwargs or {} self.error_class = error_class self._errors = None self._non_form_errors = None Loading Loading @@ -139,9 +140,16 @@ class BaseFormSet(object): Instantiate forms at first property access. """ # DoS protection is included in total_form_count() forms = [self._construct_form(i) for i in range(self.total_form_count())] forms = [self._construct_form(i, **self.get_form_kwargs(i)) for i in range(self.total_form_count())] return forms def get_form_kwargs(self, index): """ Return additional keyword arguments for each individual formset form. """ return self.form_kwargs.copy() def _construct_form(self, i, **kwargs): """ Instantiates and returns the i-th form instance in a formset. Loading Loading @@ -184,6 +192,7 @@ class BaseFormSet(object): auto_id=self.auto_id, prefix=self.add_prefix('__prefix__'), empty_permitted=True, **self.get_form_kwargs(None) ) self.add_fields(form, None) return form Loading
docs/topics/forms/formsets.txt +39 −0 Original line number Diff line number Diff line Loading @@ -238,6 +238,8 @@ pre-filled, and is also used to determine how many forms are required. You will probably never need to override either of these methods, so please be sure you understand what they do before doing so. .. _empty_form: ``empty_form`` ~~~~~~~~~~~~~~ Loading Loading @@ -533,6 +535,43 @@ default fields/attributes of the order and deletion fields:: <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date" /></td></tr> <tr><th><label for="id_form-0-my_field">My field:</label></th><td><input type="text" name="form-0-my_field" id="id_form-0-my_field" /></td></tr> Passing custom parameters to formset forms ------------------------------------------ Sometimes your form class takes custom parameters, like ``MyArticleForm``. You can pass this parameter when instantiating the formset:: >>> from django.forms.formsets import BaseFormSet >>> from django.forms.formsets import formset_factory >>> from myapp.forms import ArticleForm >>> class MyArticleForm(ArticleForm): ... def __init__(self, *args, **kwargs): ... self.user = kwargs.pop('user') ... super(MyArticleForm, self).__init__(*args, **kwargs) >>> ArticleFormSet = formset_factory(MyArticleForm) >>> formset = ArticleFormSet(form_kwargs={'user': request.user}) The ``form_kwargs`` may also depend on the specific form instance. The formset base class provides a ``get_form_kwargs`` method. The method takes a single argument - the index of the form in the formset. The index is ``None`` for the :ref:`empty_form`:: >>> from django.forms.formsets import BaseFormSet >>> from django.forms.formsets import formset_factory >>> class BaseArticleFormSet(BaseFormSet): ... def get_form_kwargs(self, index): ... kwargs = super(BaseArticleFormSet, self).get_form_kwargs(index) ... kwargs['custom_kwarg'] = index ... return kwargs .. versionadded:: 1.9 The ``form_kwargs`` argument was added. Using a formset in views and templates -------------------------------------- Loading
tests/forms_tests/tests/test_formsets.py +37 −0 Original line number Diff line number Diff line Loading @@ -57,6 +57,12 @@ class SplitDateTimeForm(Form): SplitDateTimeFormSet = formset_factory(SplitDateTimeForm) class CustomKwargForm(Form): def __init__(self, *args, **kwargs): self.custom_kwarg = kwargs.pop('custom_kwarg') super(CustomKwargForm, self).__init__(*args, **kwargs) class FormsFormsetTestCase(SimpleTestCase): def make_choiceformset(self, formset_data=None, formset_class=ChoiceFormSet, Loading Loading @@ -114,6 +120,37 @@ class FormsFormsetTestCase(SimpleTestCase): self.assertFalse(formset.is_valid()) self.assertFalse(formset.has_changed()) def test_form_kwargs_formset(self): """ Test that custom kwargs set on the formset instance are passed to the underlying forms. """ FormSet = formset_factory(CustomKwargForm, extra=2) formset = FormSet(form_kwargs={'custom_kwarg': 1}) for form in formset: self.assertTrue(hasattr(form, 'custom_kwarg')) self.assertEqual(form.custom_kwarg, 1) def test_form_kwargs_formset_dynamic(self): """ Test that form kwargs can be passed dynamically in a formset. """ class DynamicBaseFormSet(BaseFormSet): def get_form_kwargs(self, index): return {'custom_kwarg': index} DynamicFormSet = formset_factory(CustomKwargForm, formset=DynamicBaseFormSet, extra=2) formset = DynamicFormSet(form_kwargs={'custom_kwarg': 'ignored'}) for i, form in enumerate(formset): self.assertTrue(hasattr(form, 'custom_kwarg')) self.assertEqual(form.custom_kwarg, i) def test_form_kwargs_empty_form(self): FormSet = formset_factory(CustomKwargForm) formset = FormSet(form_kwargs={'custom_kwarg': 1}) self.assertTrue(hasattr(formset.empty_form, 'custom_kwarg')) self.assertEqual(formset.empty_form.custom_kwarg, 1) def test_formset_validation(self): # FormSet instances can also have an error attribute if validation failed for # any of the forms. Loading