Skip to content


Fixed #27039 -- Fixed empty data fallback to model field default in m…
Browse files Browse the repository at this point in the history
…odel forms.
  • Loading branch information
timgraham committed Aug 24, 2016
1 parent 426bca0 commit 4bc6b93
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 2 deletions.
5 changes: 5 additions & 0 deletions django/forms/
Expand Up @@ -52,6 +52,11 @@ def construct_instance(form, instance, fields=None, exclude=None):
if exclude and in exclude:
# Leave defaults for fields that aren't in POST data, except for
# checkbox inputs because they don't appear in POST data if not checked.
if (f.has_default() and not in and
not getattr(form[].field.widget, 'dont_use_model_field_default_for_empty_data', False)):
# Defer saving file-type fields until after the other fields, so a
# callable upload_to can use the values from other fields.
if isinstance(f, models.FileField):
Expand Down
4 changes: 4 additions & 0 deletions django/forms/
Expand Up @@ -481,6 +481,10 @@ def boolean_check(v):

class CheckboxInput(Widget):
# Don't use model field defaults for fields that aren't in POST data,
# because checkboxes don't appear in POST data if not checked.
dont_use_model_field_default_for_empty_data = True

def __init__(self, attrs=None, check_test=None):
super(CheckboxInput, self).__init__(attrs)
# check_test is a callable that takes a value and returns True
Expand Down
3 changes: 3 additions & 0 deletions docs/releases/1.10.1.txt
Expand Up @@ -79,3 +79,6 @@ Bugfixes

* Reallowed subclassing ``UserCreationForm`` without ``USERNAME_FIELD`` in
``Meta.fields`` (:ticket:`27111`).

* Fixed a regression in model forms where model fields with a ``default`` that
didn't appear in POST data no longer used the ``default`` (:ticket:`27039`).
15 changes: 15 additions & 0 deletions docs/topics/forms/modelforms.txt
Expand Up @@ -332,6 +332,21 @@ Note that if the form :ref:`hasn't been validated
``form.errors``. A ``ValueError`` will be raised if the data in the form
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 with
``dont_use_model_field_default_for_empty_data=True``) 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 ``BooleanField``.

.. versionchanged:: 1.10.1

Older versions don't have the exception for
:class:`~django.forms.CheckboxInput` which means that unchecked checkboxes
receive a value of ``True`` if that's the model field default.

This ``save()`` method accepts an optional ``commit`` keyword argument, which
accepts either ``True`` or ``False``. If you call ``save()`` with
``commit=False``, then it will return an object that hasn't yet been saved to
Expand Down
1 change: 1 addition & 0 deletions tests/model_forms/
Expand Up @@ -122,6 +122,7 @@ class PublicationDefaults(models.Model):
date_published = models.DateField(
mode = models.CharField(max_length=2, choices=MODE_CHOICES, default=default_mode)
category = models.IntegerField(choices=CATEGORY_CHOICES, default=default_category)
active = models.BooleanField(default=True)

class Author(models.Model):
Expand Down
43 changes: 41 additions & 2 deletions tests/model_forms/
Expand Up @@ -565,6 +565,42 @@ class Meta:
['slug', 'name'])

def test_default_populated_on_optional_field(self):
class PubForm(forms.ModelForm):
mode = forms.CharField(max_length=255, required=False)

class Meta:
model = PublicationDefaults
fields = ('mode',)

# Empty data uses the model field default.
mf1 = PubForm({})
self.assertEqual(mf1.errors, {})
m1 =
self.assertEqual(m1.mode, 'di')
self.assertEqual(m1._meta.get_field('mode').get_default(), 'di')

# Blank data doesn't use the model field default.
mf2 = PubForm({'mode': ''})
self.assertEqual(mf2.errors, {})
m2 =
self.assertEqual(m2.mode, '')

def test_default_not_populated_on_optional_checkbox_input(self):
class PubForm(forms.ModelForm):
class Meta:
model = PublicationDefaults
fields = ('active',)

# Empty data doesn't use the model default because CheckboxInput
# doesn't have a value in HTML form submission.
mf1 = PubForm({})
self.assertEqual(mf1.errors, {})
m1 =
self.assertIs(, False)
self.assertIsInstance(mf1.fields['active'].widget, forms.CheckboxInput)
self.assertIs(m1._meta.get_field('active').get_default(), True)

class FieldOverridesByFormMetaForm(forms.ModelForm):
class Meta:
Expand Down Expand Up @@ -774,7 +810,10 @@ def test_abstract_inherited_unique(self):
title = 'Boss'
isbn = '12345'
DerivedBook.objects.create(title=title, author=self.writer, isbn=isbn)
form = DerivedBookForm({'title': 'Other', 'author':, 'isbn': isbn})
form = DerivedBookForm({
'title': 'Other', 'author':, 'isbn': isbn,
'suffix1': '1', 'suffix2': '2',
self.assertEqual(len(form.errors), 1)
self.assertEqual(form.errors['isbn'], ['Derived book with this Isbn already exists.'])
Expand Down Expand Up @@ -2491,7 +2530,7 @@ def test_callable_field_default(self):
class PublicationDefaultsForm(forms.ModelForm):
class Meta:
model = PublicationDefaults
fields = '__all__'
fields = ('title', 'date_published', 'mode', 'category')

self.maxDiff = 2000
form = PublicationDefaultsForm()
Expand Down

0 comments on commit 4bc6b93

Please sign in to comment.