Sfoglia il codice sorgente

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

Hasan Ramezani 6 anni fa
parent
commit
5fc5d93512

+ 18 - 3
django/forms/formsets.py

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

+ 4 - 1
docs/releases/3.0.txt

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

+ 43 - 0
docs/topics/forms/formsets.txt

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

+ 26 - 0
tests/forms_tests/tests/test_formsets.py

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