diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 7f4c83b5df..3a5df4abea 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -690,6 +690,12 @@ class BooleanField(Field): } NULL_VALUES = {'null', 'Null', 'NULL', '', None} + def __init__(self, **kwargs): + if kwargs.get('allow_null', False): + self.default_empty_html = None + self.initial = None + super().__init__(**kwargs) + def to_internal_value(self, data): with contextlib.suppress(TypeError): if data in self.TRUE_VALUES: diff --git a/tests/test_fields.py b/tests/test_fields.py index 11e293107d..23e7931cd3 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -369,7 +369,7 @@ class TestBooleanHTMLInput: def test_empty_html_checkbox(self): """ HTML checkboxes do not send any value, but should be treated - as `False` by BooleanField. + as `False` by BooleanField if allow_null=False. """ class TestSerializer(serializers.Serializer): archived = serializers.BooleanField() @@ -381,7 +381,8 @@ class TestSerializer(serializers.Serializer): def test_empty_html_checkbox_not_required(self): """ HTML checkboxes do not send any value, but should be treated - as `False` by BooleanField, even if the field is required=False. + as `False` by BooleanField when the field is required=False + and allow_null=False. """ class TestSerializer(serializers.Serializer): archived = serializers.BooleanField(required=False) @@ -390,6 +391,34 @@ class TestSerializer(serializers.Serializer): assert serializer.is_valid() assert serializer.validated_data == {'archived': False} + def test_empty_html_checkbox_allow_null(self): + """ + HTML checkboxes do not send any value and should be treated + as `None` by BooleanField if allow_null is True. + """ + class TestSerializer(serializers.Serializer): + archived = serializers.BooleanField(allow_null=True) + + serializer = TestSerializer(data=QueryDict('')) + assert serializer.is_valid() + assert serializer.validated_data == {'archived': None} + + def test_empty_html_checkbox_allow_null_with_default(self): + """ + BooleanField should respect default if set and still allow + setting null values. + """ + class TestSerializer(serializers.Serializer): + archived = serializers.BooleanField(allow_null=True, default=True) + + serializer = TestSerializer(data=QueryDict('')) + assert serializer.is_valid() + assert serializer.validated_data == {'archived': True} + + serializer = TestSerializer(data=QueryDict('archived=')) + assert serializer.is_valid() + assert serializer.validated_data == {'archived': None} + class TestHTMLInput: def test_empty_html_charfield_with_default(self):