From ca0047e0b198ec502b7a2a347158f2545505e28f Mon Sep 17 00:00:00 2001 From: alairjt Date: Sun, 22 Feb 2026 10:25:02 -0300 Subject: [PATCH] Fix allow_null=True fields being skipped from validated_data when partial=False When a field has allow_null=True and required=False but no explicit default, omitting it from input data with partial=False would silently skip it from validated_data, effectively allowing unintended partial updates. The fix catches SkipField from get_default() in validate_empty_values() and returns None for nullable fields, ensuring they appear in validated_data during full (non-partial) updates. Refs #9501 Co-Authored-By: Claude Opus 4.6 --- rest_framework/fields.py | 7 +++- tests/test_serializer.py | 89 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index f5009a7303..3e01f58e1c 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -508,7 +508,12 @@ def validate_empty_values(self, data): raise SkipField() if self.required: self.fail('required') - return (True, self.get_default()) + try: + return (True, self.get_default()) + except SkipField: + if self.allow_null: + return (True, None) + raise if data is None: if not self.allow_null: diff --git a/tests/test_serializer.py b/tests/test_serializer.py index ed8a749118..e2c1df50f3 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -609,6 +609,95 @@ def test_default_should_not_be_included_on_partial_update(self): assert serializer.errors == {} +class TestAllowNullNotRequiredInclusions: + """ + Test that allow_null=True, required=False fields are included in + validated_data as None when partial=False. Refs #9501. + """ + + def test_allow_null_not_required_missing_field_included_on_create(self): + class ExampleSerializer(serializers.Serializer): + name = serializers.CharField() + nullable_field = serializers.CharField( + required=False, allow_null=True + ) + + serializer = ExampleSerializer(data={'name': 'test'}) + assert serializer.is_valid() + assert serializer.validated_data == { + 'name': 'test', + 'nullable_field': None, + } + + def test_allow_null_not_required_missing_field_included_on_update(self): + class ExampleSerializer(serializers.Serializer): + name = serializers.CharField() + nullable_field = serializers.CharField( + required=False, allow_null=True + ) + + instance = MockObject(name='old', nullable_field='old_value') + serializer = ExampleSerializer(instance, data={'name': 'test'}) + assert serializer.is_valid() + assert serializer.validated_data == { + 'name': 'test', + 'nullable_field': None, + } + + def test_allow_null_not_required_missing_field_skipped_on_partial_update(self): + class ExampleSerializer(serializers.Serializer): + name = serializers.CharField() + nullable_field = serializers.CharField( + required=False, allow_null=True + ) + + instance = MockObject(name='old', nullable_field='old_value') + serializer = ExampleSerializer( + instance, data={'name': 'test'}, partial=True + ) + assert serializer.is_valid() + assert serializer.validated_data == {'name': 'test'} + + def test_allow_null_not_required_explicit_null_still_works(self): + class ExampleSerializer(serializers.Serializer): + name = serializers.CharField() + nullable_field = serializers.CharField( + required=False, allow_null=True + ) + + serializer = ExampleSerializer( + data={'name': 'test', 'nullable_field': None} + ) + assert serializer.is_valid() + assert serializer.validated_data == { + 'name': 'test', + 'nullable_field': None, + } + + def test_allow_null_not_required_with_explicit_default(self): + class ExampleSerializer(serializers.Serializer): + name = serializers.CharField() + nullable_field = serializers.CharField( + required=False, allow_null=True, default='fallback' + ) + + serializer = ExampleSerializer(data={'name': 'test'}) + assert serializer.is_valid() + assert serializer.validated_data == { + 'name': 'test', + 'nullable_field': 'fallback', + } + + def test_not_required_without_allow_null_still_skipped(self): + class ExampleSerializer(serializers.Serializer): + name = serializers.CharField() + optional_field = serializers.CharField(required=False) + + serializer = ExampleSerializer(data={'name': 'test'}) + assert serializer.is_valid() + assert serializer.validated_data == {'name': 'test'} + + class TestSerializerValidationWithCompiledRegexField: def setup_method(self): class ExampleSerializer(serializers.Serializer):