From f23c03ebc81c64b8b41f3aae0dbe93fc283cc43d Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 30 Sep 2016 14:49:50 -0400 Subject: [PATCH] [1.10.x] Refs #27186 -- Fixed model form default fallback for CheckboxSelectMultiple. Backport of 87c5e7efebd040aef0f0479ccf86877155bb5cea from master --- django/forms/widgets.py | 5 +++++ docs/ref/forms/widgets.txt | 9 +++++---- docs/releases/1.10.2.txt | 8 ++++---- docs/topics/forms/modelforms.txt | 3 ++- .../test_checkboxselectmultiple.py | 5 +++++ tests/model_forms/tests.py | 18 +++++++++++++++++- 6 files changed, 38 insertions(+), 10 deletions(-) diff --git a/django/forms/widgets.py b/django/forms/widgets.py index 965f84f7df..454ecd7c91 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -812,6 +812,11 @@ class CheckboxSelectMultiple(RendererMixin, SelectMultiple): # require all checkboxes to be checked instead of at least one. return False + def value_omitted_from_data(self, data, files, name): + # HTML checkboxes don't appear in POST data if not checked, so it's + # never known if the value is actually omitted. + return False + class MultiWidget(Widget): """ diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt index fde7806b02..443b12cce0 100644 --- a/docs/ref/forms/widgets.txt +++ b/docs/ref/forms/widgets.txt @@ -281,10 +281,11 @@ foundation for custom widgets. The method's result affects whether or not a field in a model form :ref:`falls back to its default `. - A special case is :class:`~django.forms.CheckboxInput`, which always - returns ``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` 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. ``MultiWidget`` --------------- diff --git a/docs/releases/1.10.2.txt b/docs/releases/1.10.2.txt index 075eb947f9..78447dccba 100644 --- a/docs/releases/1.10.2.txt +++ b/docs/releases/1.10.2.txt @@ -18,10 +18,10 @@ Bugfixes * Disabled system check for URL patterns beginning with a '/' when ``APPEND_SLASH=False`` (:ticket:`27238`). -* Fixed model form ``default`` fallback for ``MultiWidget``, ``FileInput``, - ``SplitDateTimeWidget``, ``SelectDateWidget``, and ``SplitArrayWidget`` - (:ticket:`27186`). Custom widgets affected by this issue may need to - implement a :meth:`~django.forms.Widget.value_omitted_from_data` method. +* Fixed model form ``default`` fallback for ``CheckboxSelectMultiple``, + ``MultiWidget``, ``FileInput``, ``SplitDateTimeWidget``, ``SelectDateWidget``, + and ``SplitArrayWidget`` (:ticket:`27186`). Custom widgets affected by this + issue should implement :meth:`~django.forms.Widget.value_omitted_from_data`. * Fixed a crash in ``runserver`` logging during a "Broken pipe" error (:ticket:`27271`). diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index 3d5bc746fe..768dc3938c 100644 --- a/docs/topics/forms/modelforms.txt +++ b/docs/topics/forms/modelforms.txt @@ -335,7 +335,8 @@ 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` (or any custom widget whose +:class:`~django.forms.CheckboxInput` and +:class:`~django.forms.CheckboxSelectMultiple` (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 diff --git a/tests/forms_tests/widget_tests/test_checkboxselectmultiple.py b/tests/forms_tests/widget_tests/test_checkboxselectmultiple.py index b9d2840f4d..803991e85b 100644 --- a/tests/forms_tests/widget_tests/test_checkboxselectmultiple.py +++ b/tests/forms_tests/widget_tests/test_checkboxselectmultiple.py @@ -120,3 +120,8 @@ class CheckboxSelectMultipleTest(WidgetTest): self.assertIs(widget.use_required_attribute(None), False) self.assertIs(widget.use_required_attribute([]), False) self.assertIs(widget.use_required_attribute(['J', 'P']), False) + + 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) diff --git a/tests/model_forms/tests.py b/tests/model_forms/tests.py index f2931262c2..2f9b3945a6 100644 --- a/tests/model_forms/tests.py +++ b/tests/model_forms/tests.py @@ -585,6 +585,22 @@ class ModelFormBaseTest(TestCase): self.assertIsInstance(mf1.fields['active'].widget, forms.CheckboxInput) self.assertIs(m1._meta.get_field('active').get_default(), True) + def test_default_not_populated_on_checkboxselectmultiple(self): + class PubForm(forms.ModelForm): + mode = forms.CharField(required=False, widget=forms.CheckboxSelectMultiple) + + class Meta: + model = PublicationDefaults + fields = ('mode',) + + # Empty data doesn't use the model default because an unchecked + # CheckboxSelectMultiple 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' @@ -635,7 +651,7 @@ class ModelFormBaseTest(TestCase): m2 = mf2.save(commit=False) self.assertEqual(m2.file.name, 'name') - def test_selectdatewidget(self): + def test_default_selectdatewidget(self): class PubForm(forms.ModelForm): date_published = forms.DateField(required=False, widget=forms.SelectDateWidget)