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 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)

View File

@ -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
~~~~~~~~~~~~~ ~~~~~~~~~~~~~

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

View File

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