mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #18166 -- Added form_kwargs support to formsets.
By specifying form_kwargs when instantiating the formset, or overriding the `get_form_kwargs` method on a formset class, you can pass extra keyword arguments to the underlying `Form` instances. Includes tests and documentation update.
This commit is contained in:
		@@ -54,13 +54,14 @@ class BaseFormSet(object):
 | 
				
			|||||||
    A collection of instances of the same Form class.
 | 
					    A collection of instances of the same Form class.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
 | 
					    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.is_bound = data is not None or files is not None
 | 
				
			||||||
        self.prefix = prefix or self.get_default_prefix()
 | 
					        self.prefix = prefix or self.get_default_prefix()
 | 
				
			||||||
        self.auto_id = auto_id
 | 
					        self.auto_id = auto_id
 | 
				
			||||||
        self.data = data or {}
 | 
					        self.data = data or {}
 | 
				
			||||||
        self.files = files or {}
 | 
					        self.files = files or {}
 | 
				
			||||||
        self.initial = initial
 | 
					        self.initial = initial
 | 
				
			||||||
 | 
					        self.form_kwargs = form_kwargs or {}
 | 
				
			||||||
        self.error_class = error_class
 | 
					        self.error_class = error_class
 | 
				
			||||||
        self._errors = None
 | 
					        self._errors = None
 | 
				
			||||||
        self._non_form_errors = None
 | 
					        self._non_form_errors = None
 | 
				
			||||||
@@ -139,9 +140,16 @@ class BaseFormSet(object):
 | 
				
			|||||||
        Instantiate forms at first property access.
 | 
					        Instantiate forms at first property access.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        # DoS protection is included in total_form_count()
 | 
					        # 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
 | 
					        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):
 | 
					    def _construct_form(self, i, **kwargs):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Instantiates and returns the i-th form instance in a formset.
 | 
					        Instantiates and returns the i-th form instance in a formset.
 | 
				
			||||||
@@ -184,6 +192,7 @@ class BaseFormSet(object):
 | 
				
			|||||||
            auto_id=self.auto_id,
 | 
					            auto_id=self.auto_id,
 | 
				
			||||||
            prefix=self.add_prefix('__prefix__'),
 | 
					            prefix=self.add_prefix('__prefix__'),
 | 
				
			||||||
            empty_permitted=True,
 | 
					            empty_permitted=True,
 | 
				
			||||||
 | 
					            **self.get_form_kwargs(None)
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        self.add_fields(form, None)
 | 
					        self.add_fields(form, None)
 | 
				
			||||||
        return form
 | 
					        return form
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
					will probably never need to override either of these methods, so please be
 | 
				
			||||||
sure you understand what they do before doing so.
 | 
					sure you understand what they do before doing so.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. _empty_form:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
``empty_form``
 | 
					``empty_form``
 | 
				
			||||||
~~~~~~~~~~~~~~
 | 
					~~~~~~~~~~~~~~
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -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-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>
 | 
					    <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
 | 
					Using a formset in views and templates
 | 
				
			||||||
--------------------------------------
 | 
					--------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -57,6 +57,12 @@ class SplitDateTimeForm(Form):
 | 
				
			|||||||
SplitDateTimeFormSet = formset_factory(SplitDateTimeForm)
 | 
					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):
 | 
					class FormsFormsetTestCase(SimpleTestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def make_choiceformset(self, formset_data=None, formset_class=ChoiceFormSet,
 | 
					    def make_choiceformset(self, formset_data=None, formset_class=ChoiceFormSet,
 | 
				
			||||||
@@ -114,6 +120,37 @@ class FormsFormsetTestCase(SimpleTestCase):
 | 
				
			|||||||
        self.assertFalse(formset.is_valid())
 | 
					        self.assertFalse(formset.is_valid())
 | 
				
			||||||
        self.assertFalse(formset.has_changed())
 | 
					        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):
 | 
					    def test_formset_validation(self):
 | 
				
			||||||
        # FormSet instances can also have an error attribute if validation failed for
 | 
					        # FormSet instances can also have an error attribute if validation failed for
 | 
				
			||||||
        # any of the forms.
 | 
					        # any of the forms.
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user