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.
 | 
			
		||||
    """
 | 
			
		||||
    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
 | 
			
		||||
@@ -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.
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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``
 | 
			
		||||
~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
@@ -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
 | 
			
		||||
--------------------------------------
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
@@ -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.
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user