diff --git a/django/forms/fields.py b/django/forms/fields.py index f3f8756e39..c50cd164ed 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -720,12 +720,9 @@ class BooleanField(Field): raise ValidationError(self.error_messages['required'], code='required') def has_changed(self, initial, data): - # Sometimes data or initial could be None or '' which should be the - # same thing as False. - if initial == 'False': - # show_hidden_initial may have transformed False to 'False' - initial = False - return bool(initial) != bool(data) + # Sometimes data or initial may be a string equivalent of a boolean + # so we should run it through to_python first to get a boolean value + return self.to_python(initial) != self.to_python(data) class NullBooleanField(BooleanField): @@ -754,14 +751,6 @@ class NullBooleanField(BooleanField): def validate(self, value): pass - def has_changed(self, initial, data): - # None (unknown) and False (No) are not the same - if initial is not None: - initial = bool(initial) - if data is not None: - data = bool(data) - return initial != data - class CallableChoiceIterator(object): def __init__(self, choices_func): diff --git a/tests/forms_tests/field_tests/test_booleanfield.py b/tests/forms_tests/field_tests/test_booleanfield.py index 9c69c96762..0fa5357ae3 100644 --- a/tests/forms_tests/field_tests/test_booleanfield.py +++ b/tests/forms_tests/field_tests/test_booleanfield.py @@ -54,3 +54,8 @@ class BooleanFieldTest(SimpleTestCase): self.assertTrue(f.has_changed(True, '')) # Initial value may have mutated to a string due to show_hidden_initial (#19537) self.assertTrue(f.has_changed('False', 'on')) + # HiddenInput widget sends string values for boolean but doesn't clean them in value_from_datadict + self.assertFalse(f.has_changed(False, 'False')) + self.assertFalse(f.has_changed(True, 'True')) + self.assertTrue(f.has_changed(False, 'True')) + self.assertTrue(f.has_changed(True, 'False')) diff --git a/tests/forms_tests/field_tests/test_nullbooleanfield.py b/tests/forms_tests/field_tests/test_nullbooleanfield.py index 74a2d7c173..ff4a16060e 100644 --- a/tests/forms_tests/field_tests/test_nullbooleanfield.py +++ b/tests/forms_tests/field_tests/test_nullbooleanfield.py @@ -67,3 +67,10 @@ class NullBooleanFieldTest(FormFieldAssertionsMixin, SimpleTestCase): self.assertTrue(f.has_changed(True, False)) self.assertTrue(f.has_changed(True, None)) self.assertTrue(f.has_changed(True, False)) + # HiddenInput widget sends string values for boolean but doesn't clean them in value_from_datadict + self.assertFalse(f.has_changed(False, 'False')) + self.assertFalse(f.has_changed(True, 'True')) + self.assertFalse(f.has_changed(None, '')) + self.assertTrue(f.has_changed(False, 'True')) + self.assertTrue(f.has_changed(True, 'False')) + self.assertTrue(f.has_changed(None, 'False'))