mirror of https://github.com/django/django.git
Fixed #32984 -- Allowed customizing a deletion field widget in formsets.
This commit is contained in:
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
|
||||
|
|
Loading…
Reference in New Issue