Fixed #29956 -- Allowed overriding an order field widget in formsets.

This commit is contained in:
Hasan Ramezani 2018-11-21 21:58:04 +01:00 committed by Mariusz Felisiak
parent 413d50b5ff
commit 5fc5d93512
4 changed files with 91 additions and 4 deletions

View File

@ -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
from django.forms.widgets import HiddenInput, NumberInput
from django.utils.functional import cached_property
from django.utils.html import html_safe
from django.utils.safestring import mark_safe
@ -47,6 +47,8 @@ class BaseFormSet:
"""
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,
initial=None, error_class=ErrorList, form_kwargs=None):
self.is_bound = data is not None or files is not None
@ -264,6 +266,10 @@ class BaseFormSet:
def get_default_prefix(cls):
return 'form'
@classmethod
def get_ordering_widget(cls):
return cls.ordering_widget
def non_form_errors(self):
"""
Return an ErrorList of errors that aren't associated with a particular
@ -368,9 +374,18 @@ class BaseFormSet:
if self.can_order:
# Only pre-fill the ordering field for initial forms.
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:
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:
form.fields[DELETION_FIELD_NAME] = BooleanField(label=_('Delete'), required=False)

View File

@ -139,7 +139,10 @@ File Uploads
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
~~~~~~~~~~~~~

View File

@ -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, 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``
--------------

View File

@ -8,6 +8,7 @@ from django.forms import (
)
from django.forms.formsets import BaseFormSet, all_valid, formset_factory
from django.forms.utils import ErrorList
from django.forms.widgets import HiddenInput
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):
"""
Ordering fields are allowed to be left blank. If they are left blank,