Fixed #29956 -- Allowed overriding an order field widget in formsets.
This commit is contained in:
parent
413d50b5ff
commit
5fc5d93512
|
@ -2,7 +2,7 @@ from django.core.exceptions import ValidationError
|
||||||
from django.forms import Form
|
from django.forms import Form
|
||||||
from django.forms.fields import BooleanField, IntegerField
|
from django.forms.fields import BooleanField, IntegerField
|
||||||
from django.forms.utils import ErrorList
|
from django.forms.utils import ErrorList
|
||||||
from django.forms.widgets import HiddenInput
|
from django.forms.widgets import HiddenInput, NumberInput
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.html import html_safe
|
from django.utils.html import html_safe
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
@ -47,6 +47,8 @@ class BaseFormSet:
|
||||||
"""
|
"""
|
||||||
A collection of instances of the same Form class.
|
A collection of instances of the same Form class.
|
||||||
"""
|
"""
|
||||||
|
ordering_widget = NumberInput
|
||||||
|
|
||||||
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, form_kwargs=None):
|
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
|
||||||
|
@ -264,6 +266,10 @@ class BaseFormSet:
|
||||||
def get_default_prefix(cls):
|
def get_default_prefix(cls):
|
||||||
return 'form'
|
return 'form'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_ordering_widget(cls):
|
||||||
|
return cls.ordering_widget
|
||||||
|
|
||||||
def non_form_errors(self):
|
def non_form_errors(self):
|
||||||
"""
|
"""
|
||||||
Return an ErrorList of errors that aren't associated with a particular
|
Return an ErrorList of errors that aren't associated with a particular
|
||||||
|
@ -368,9 +374,18 @@ class BaseFormSet:
|
||||||
if self.can_order:
|
if self.can_order:
|
||||||
# Only pre-fill the ordering field for initial forms.
|
# Only pre-fill the ordering field for initial forms.
|
||||||
if index is not None and index < self.initial_form_count():
|
if index is not None and index < self.initial_form_count():
|
||||||
form.fields[ORDERING_FIELD_NAME] = IntegerField(label=_('Order'), initial=index + 1, required=False)
|
form.fields[ORDERING_FIELD_NAME] = IntegerField(
|
||||||
|
label=_('Order'),
|
||||||
|
initial=index + 1,
|
||||||
|
required=False,
|
||||||
|
widget=self.get_ordering_widget(),
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
form.fields[ORDERING_FIELD_NAME] = IntegerField(label=_('Order'), required=False)
|
form.fields[ORDERING_FIELD_NAME] = IntegerField(
|
||||||
|
label=_('Order'),
|
||||||
|
required=False,
|
||||||
|
widget=self.get_ordering_widget(),
|
||||||
|
)
|
||||||
if self.can_delete:
|
if self.can_delete:
|
||||||
form.fields[DELETION_FIELD_NAME] = BooleanField(label=_('Delete'), required=False)
|
form.fields[DELETION_FIELD_NAME] = BooleanField(label=_('Delete'), required=False)
|
||||||
|
|
||||||
|
|
|
@ -139,7 +139,10 @@ File Uploads
|
||||||
Forms
|
Forms
|
||||||
~~~~~
|
~~~~~
|
||||||
|
|
||||||
* ...
|
* Formsets may control the widget used when ordering forms via
|
||||||
|
:attr:`~django.forms.formsets.BaseFormSet.can_order` by setting the
|
||||||
|
:attr:`~django.forms.formsets.BaseFormSet.ordering_widget` attribute or
|
||||||
|
overriding :attr:`~django.forms.formsets.BaseFormSet.get_ordering_widget()`.
|
||||||
|
|
||||||
Generic Views
|
Generic Views
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
|
@ -448,6 +448,49 @@ happen when the user changes these values::
|
||||||
{'pub_date': datetime.date(2008, 5, 11), 'ORDER': 1, 'title': 'Article #2'}
|
{'pub_date': datetime.date(2008, 5, 11), 'ORDER': 1, 'title': 'Article #2'}
|
||||||
{'pub_date': datetime.date(2008, 5, 10), 'ORDER': 2, 'title': 'Article #1'}
|
{'pub_date': datetime.date(2008, 5, 10), 'ORDER': 2, 'title': 'Article #1'}
|
||||||
|
|
||||||
|
:class:`~django.forms.formsets.BaseFormSet` also provides an
|
||||||
|
:attr:`~django.forms.formsets.BaseFormSet.ordering_widget` attribute and
|
||||||
|
:meth:`~django.forms.formsets.BaseFormSet.get_ordering_widget` method that
|
||||||
|
control the widget used with
|
||||||
|
:attr:`~django.forms.formsets.BaseFormSet.can_order`.
|
||||||
|
|
||||||
|
``ordering_widget``
|
||||||
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. versionadded:: 3.0
|
||||||
|
|
||||||
|
.. attribute:: BaseFormSet.ordering_widget
|
||||||
|
|
||||||
|
Default: :class:`~django.forms.NumberInput`
|
||||||
|
|
||||||
|
Set ``ordering_widget`` to specify the widget class to be used with
|
||||||
|
``can_order``::
|
||||||
|
|
||||||
|
>>> from django.forms import BaseFormSet, formset_factory
|
||||||
|
>>> from myapp.forms import ArticleForm
|
||||||
|
>>> class BaseArticleFormSet(BaseFormSet):
|
||||||
|
... ordering_widget = HiddenInput
|
||||||
|
|
||||||
|
>>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet, can_order=True)
|
||||||
|
|
||||||
|
``get_ordering_widget``
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. versionadded:: 3.0
|
||||||
|
|
||||||
|
.. method:: BaseFormSet.get_ordering_widget()
|
||||||
|
|
||||||
|
Override ``get_ordering_widget()`` if you need to provide a widget instance for
|
||||||
|
use with ``can_order``::
|
||||||
|
|
||||||
|
>>> from django.forms import BaseFormSet, formset_factory
|
||||||
|
>>> from myapp.forms import ArticleForm
|
||||||
|
>>> class BaseArticleFormSet(BaseFormSet):
|
||||||
|
... def get_ordering_widget(self):
|
||||||
|
... return HiddenInput(attrs={'class': 'ordering'})
|
||||||
|
|
||||||
|
>>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet, can_order=True)
|
||||||
|
|
||||||
``can_delete``
|
``can_delete``
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ from django.forms import (
|
||||||
)
|
)
|
||||||
from django.forms.formsets import BaseFormSet, all_valid, formset_factory
|
from django.forms.formsets import BaseFormSet, all_valid, formset_factory
|
||||||
from django.forms.utils import ErrorList
|
from django.forms.utils import ErrorList
|
||||||
|
from django.forms.widgets import HiddenInput
|
||||||
from django.test import SimpleTestCase
|
from django.test import SimpleTestCase
|
||||||
|
|
||||||
|
|
||||||
|
@ -591,6 +592,31 @@ class FormsFormsetTestCase(SimpleTestCase):
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_formsets_with_order_custom_widget(self):
|
||||||
|
class OrderingAttributFormSet(BaseFormSet):
|
||||||
|
ordering_widget = HiddenInput
|
||||||
|
|
||||||
|
class OrderingMethodFormSet(BaseFormSet):
|
||||||
|
def get_ordering_widget(self):
|
||||||
|
return HiddenInput(attrs={'class': 'ordering'})
|
||||||
|
|
||||||
|
tests = (
|
||||||
|
(OrderingAttributFormSet, '<input type="hidden" name="form-0-ORDER">'),
|
||||||
|
(OrderingMethodFormSet, '<input class="ordering" type="hidden" name="form-0-ORDER">'),
|
||||||
|
)
|
||||||
|
for formset_class, order_html in tests:
|
||||||
|
with self.subTest(formset_class=formset_class):
|
||||||
|
ArticleFormSet = formset_factory(ArticleForm, formset=formset_class, can_order=True)
|
||||||
|
formset = ArticleFormSet(auto_id=False)
|
||||||
|
self.assertHTMLEqual(
|
||||||
|
'\n'.join(form.as_ul() for form in formset.forms),
|
||||||
|
(
|
||||||
|
'<li>Title: <input type="text" name="form-0-title"></li>'
|
||||||
|
'<li>Pub date: <input type="text" name="form-0-pub_date">'
|
||||||
|
'%s</li>' % order_html
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
def test_empty_ordered_fields(self):
|
def test_empty_ordered_fields(self):
|
||||||
"""
|
"""
|
||||||
Ordering fields are allowed to be left blank. If they are left blank,
|
Ordering fields are allowed to be left blank. If they are left blank,
|
||||||
|
|
Loading…
Reference in New Issue