mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #32984 -- Allowed customizing a deletion field widget in formsets.
This commit is contained in:
		
				
					committed by
					
						 Mariusz Felisiak
						Mariusz Felisiak
					
				
			
			
				
	
			
			
			
						parent
						
							47cb85b542
						
					
				
				
					commit
					4f3acf9579
				
			| @@ -2,7 +2,7 @@ from django.core.exceptions import ValidationError | ||||
| from django.forms import Form | ||||
| from django.forms.fields import BooleanField, IntegerField | ||||
| from django.forms.utils import ErrorList | ||||
| from django.forms.widgets import HiddenInput, NumberInput | ||||
| from django.forms.widgets import CheckboxInput, HiddenInput, NumberInput | ||||
| from django.utils.functional import cached_property | ||||
| from django.utils.html import html_safe | ||||
| from django.utils.safestring import mark_safe | ||||
| @@ -55,6 +55,7 @@ class BaseFormSet: | ||||
|     """ | ||||
|     A collection of instances of the same Form class. | ||||
|     """ | ||||
|     deletion_widget = CheckboxInput | ||||
|     ordering_widget = NumberInput | ||||
|     default_error_messages = { | ||||
|         'missing_management_form': _( | ||||
| @@ -283,6 +284,10 @@ class BaseFormSet: | ||||
|     def get_default_prefix(cls): | ||||
|         return 'form' | ||||
|  | ||||
|     @classmethod | ||||
|     def get_deletion_widget(cls): | ||||
|         return cls.deletion_widget | ||||
|  | ||||
|     @classmethod | ||||
|     def get_ordering_widget(cls): | ||||
|         return cls.ordering_widget | ||||
| @@ -417,7 +422,11 @@ class BaseFormSet: | ||||
|                     widget=self.get_ordering_widget(), | ||||
|                 ) | ||||
|         if self.can_delete and (self.can_delete_extra or index < initial_form_count): | ||||
|             form.fields[DELETION_FIELD_NAME] = BooleanField(label=_('Delete'), required=False) | ||||
|             form.fields[DELETION_FIELD_NAME] = BooleanField( | ||||
|                 label=_('Delete'), | ||||
|                 required=False, | ||||
|                 widget=self.get_deletion_widget(), | ||||
|             ) | ||||
|  | ||||
|     def add_prefix(self, index): | ||||
|         return '%s-%s' % (self.prefix, index) | ||||
|   | ||||
| @@ -229,6 +229,13 @@ Forms | ||||
|   an additional class of ``nonform`` to help distinguish them from | ||||
|   form-specific errors. | ||||
|  | ||||
| * :class:`~django.forms.formsets.BaseFormSet` now allows customizing the widget | ||||
|   used when deleting forms via | ||||
|   :attr:`~django.forms.formsets.BaseFormSet.can_delete` by setting the | ||||
|   :attr:`~django.forms.formsets.BaseFormSet.deletion_widget` attribute or | ||||
|   overriding :meth:`~django.forms.formsets.BaseFormSet.get_deletion_widget` | ||||
|   method. | ||||
|  | ||||
| Generic Views | ||||
| ~~~~~~~~~~~~~ | ||||
|  | ||||
|   | ||||
| @@ -636,6 +636,49 @@ On the other hand, if you are using a plain ``FormSet``, it's up to you to | ||||
| handle ``formset.deleted_forms``, perhaps in your formset's ``save()`` method, | ||||
| as there's no general notion of what it means to delete a form. | ||||
|  | ||||
| :class:`~django.forms.formsets.BaseFormSet` also provides a | ||||
| :attr:`~django.forms.formsets.BaseFormSet.deletion_widget` attribute and | ||||
| :meth:`~django.forms.formsets.BaseFormSet.get_deletion_widget` method that | ||||
| control the widget used with | ||||
| :attr:`~django.forms.formsets.BaseFormSet.can_delete`. | ||||
|  | ||||
| ``deletion_widget`` | ||||
| ^^^^^^^^^^^^^^^^^^^ | ||||
|  | ||||
| .. versionadded:: 4.0 | ||||
|  | ||||
| .. attribute:: BaseFormSet.deletion_widget | ||||
|  | ||||
| Default: :class:`~django.forms.CheckboxInput` | ||||
|  | ||||
| Set ``deletion_widget`` to specify the widget class to be used with | ||||
| ``can_delete``:: | ||||
|  | ||||
|     >>> from django.forms import BaseFormSet, formset_factory | ||||
|     >>> from myapp.forms import ArticleForm | ||||
|     >>> class BaseArticleFormSet(BaseFormSet): | ||||
|     ...     deletion_widget = HiddenInput | ||||
|  | ||||
|     >>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet, can_delete=True) | ||||
|  | ||||
| ``get_deletion_widget`` | ||||
| ^^^^^^^^^^^^^^^^^^^^^^^ | ||||
|  | ||||
| .. versionadded:: 4.0 | ||||
|  | ||||
| .. method:: BaseFormSet.get_deletion_widget() | ||||
|  | ||||
| Override ``get_deletion_widget()`` if you need to provide a widget instance for | ||||
| use with ``can_delete``:: | ||||
|  | ||||
|     >>> from django.forms import BaseFormSet, formset_factory | ||||
|     >>> from myapp.forms import ArticleForm | ||||
|     >>> class BaseArticleFormSet(BaseFormSet): | ||||
|     ...     def get_deletion_widget(self): | ||||
|     ...         return HiddenInput(attrs={'class': 'deletion'}) | ||||
|  | ||||
|     >>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet, can_delete=True) | ||||
|  | ||||
| ``can_delete_extra`` | ||||
| -------------------- | ||||
|  | ||||
|   | ||||
| @@ -551,6 +551,38 @@ class FormsFormsetTestCase(SimpleTestCase): | ||||
|         self.assertEqual(formset._errors, []) | ||||
|         self.assertEqual(len(formset.deleted_forms), 1) | ||||
|  | ||||
|     def test_formset_with_deletion_custom_widget(self): | ||||
|         class DeletionAttributeFormSet(BaseFormSet): | ||||
|             deletion_widget = HiddenInput | ||||
|  | ||||
|         class DeletionMethodFormSet(BaseFormSet): | ||||
|             def get_deletion_widget(self): | ||||
|                 return HiddenInput(attrs={'class': 'deletion'}) | ||||
|  | ||||
|         tests = [ | ||||
|             (DeletionAttributeFormSet, '<input type="hidden" name="form-0-DELETE">'), | ||||
|             ( | ||||
|                 DeletionMethodFormSet, | ||||
|                 '<input class="deletion" type="hidden" name="form-0-DELETE">', | ||||
|             ), | ||||
|         ] | ||||
|         for formset_class, delete_html in tests: | ||||
|             with self.subTest(formset_class=formset_class.__name__): | ||||
|                 ArticleFormSet = formset_factory( | ||||
|                     ArticleForm, | ||||
|                     formset=formset_class, | ||||
|                     can_delete=True, | ||||
|                 ) | ||||
|                 formset = ArticleFormSet(auto_id=False) | ||||
|                 self.assertHTMLEqual( | ||||
|                     '\n'.join([form.as_ul() for form in formset.forms]), | ||||
|                     ( | ||||
|                         f'<li>Title: <input type="text" name="form-0-title"></li>' | ||||
|                         f'<li>Pub date: <input type="text" name="form-0-pub_date">' | ||||
|                         f'{delete_html}</li>' | ||||
|                     ), | ||||
|                 ) | ||||
|  | ||||
|     def test_formsets_with_ordering(self): | ||||
|         """ | ||||
|         formset_factory's can_order argument adds an integer field to each | ||||
|   | ||||
		Reference in New Issue
	
	Block a user