Переглянути джерело

Fixed #27993 -- Fixed model form default fallback for SelectMultiple.

heathervm 8 роки тому
батько
коміт
7d1e237753

+ 5 - 0
django/forms/widgets.py

@@ -714,6 +714,11 @@ class SelectMultiple(Select):
             getter = data.get
         return getter(name)
 
+    def value_omitted_from_data(self, data, files, name):
+        # An unselected <select multiple> doesn't appear in POST data, so it's
+        # never known if the value is actually omitted.
+        return False
+
 
 class RadioSelect(ChoiceWidget):
     input_type = 'radio'

+ 6 - 5
docs/ref/forms/widgets.txt

@@ -297,11 +297,12 @@ foundation for custom widgets.
         The method's result affects whether or not a field in a model form
         :ref:`falls back to its default <topics-modelform-save>`.
 
-        Special cases are :class:`~django.forms.CheckboxInput` and
-        :class:`~django.forms.CheckboxSelectMultiple`, which always return
-        ``False`` because an unchecked checkbox doesn't appear in the data of
-        an HTML form submission, so it's unknown whether or not the user
-        actually submitted a value.
+        Special cases are :class:`~django.forms.CheckboxInput`,
+        :class:`~django.forms.CheckboxSelectMultiple`, and
+        :class:`~django.forms.SelectMultiple`, which always return
+        ``False`` because an unchecked checkbox and unselected
+        ``<select multiple>`` don't appear in the data of an HTML form
+        submission, so it's unknown whether or not the user submitted a value.
 
     .. method:: use_required_attribute(initial)
 

+ 3 - 0
docs/releases/1.10.7.txt

@@ -11,3 +11,6 @@ Bugfixes
 
 * Made admin's ``RelatedFieldWidgetWrapper`` use the wrapped widget's
   ``value_omitted_from_data()`` method (:ticket:`27905`).
+
+* Fixed model form ``default`` fallback for ``SelectMultiple``
+  (:ticket:`27993`).

+ 7 - 5
docs/topics/forms/modelforms.txt

@@ -335,12 +335,14 @@ doesn't validate -- i.e., if ``form.errors`` evaluates to ``True``.
 If an optional field doesn't appear in the form's data, the resulting model
 instance uses the model field :attr:`~django.db.models.Field.default`, if
 there is one, for that field. This behavior doesn't apply to fields that use
-:class:`~django.forms.CheckboxInput` and
-:class:`~django.forms.CheckboxSelectMultiple` (or any custom widget whose
+:class:`~django.forms.CheckboxInput`,
+:class:`~django.forms.CheckboxSelectMultiple`, or
+:class:`~django.forms.SelectMultiple` (or any custom widget whose
 :meth:`~django.forms.Widget.value_omitted_from_data` method always returns
-``False``) since an unchecked checkbox doesn't appear in the data of an HTML
-form submission. Use a custom form field or widget if you're designing an API
-and want the default fallback for a :class:`~django.db.models.BooleanField`.
+``False``) since an unchecked checkbox and unselected ``<select multiple>``
+don't appear in the data of an HTML form submission. Use a custom form field or
+widget if you're designing an API and want the default fallback behavior for a
+field that uses one of these widgets.
 
 This ``save()`` method accepts an optional ``commit`` keyword argument, which
 accepts either ``True`` or ``False``. If you call ``save()`` with

+ 5 - 0
tests/forms_tests/widget_tests/test_selectmultiple.py

@@ -123,3 +123,8 @@ class SelectMultipleTest(WidgetTest):
             </optgroup>
             </select>"""
         ))
+
+    def test_value_omitted_from_data(self):
+        widget = self.widget(choices=self.beatles)
+        self.assertIs(widget.value_omitted_from_data({}, {}, 'field'), False)
+        self.assertIs(widget.value_omitted_from_data({'field': 'value'}, {}, 'field'), False)

+ 16 - 0
tests/model_forms/tests.py

@@ -614,6 +614,22 @@ class ModelFormBaseTest(TestCase):
         self.assertEqual(m1.mode, '')
         self.assertEqual(m1._meta.get_field('mode').get_default(), 'di')
 
+    def test_default_not_populated_on_selectmultiple(self):
+        class PubForm(forms.ModelForm):
+            mode = forms.CharField(required=False, widget=forms.SelectMultiple)
+
+            class Meta:
+                model = PublicationDefaults
+                fields = ('mode',)
+
+        # Empty data doesn't use the model default because an unselected
+        # SelectMultiple doesn't have a value in HTML form submission.
+        mf1 = PubForm({})
+        self.assertEqual(mf1.errors, {})
+        m1 = mf1.save(commit=False)
+        self.assertEqual(m1.mode, '')
+        self.assertEqual(m1._meta.get_field('mode').get_default(), 'di')
+
     def test_prefixed_form_with_default_field(self):
         class PubForm(forms.ModelForm):
             prefix = 'form-prefix'