diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 8fe284bc8..a13645075 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -652,6 +652,26 @@ class ListSerializer(BaseSerializer): self.child.initial_data = data return super().run_child_validation(data) """ + child_instance = getattr(self.child, "instance", None) + + if self.instance is not None: + pk_name = None + child_meta = getattr(self.child, "Meta", None) + model = getattr(child_meta, "model", None) if child_meta else None + + if model is not None: + pk_name = model._meta.pk.name + + if pk_name: + obj_id = data.get(pk_name, data.get("pk", data.get("id"))) + if obj_id is not None: + for obj in self.instance: + if hasattr(obj, pk_name) and getattr(obj, pk_name) == obj_id: + child_instance = obj + break + + self.child.instance = child_instance + self.child.initial_data = data return self.child.run_validation(data) def to_internal_value(self, data): diff --git a/tests/models.py b/tests/models.py index 88e3d8dca..a3ee73456 100644 --- a/tests/models.py +++ b/tests/models.py @@ -150,3 +150,21 @@ class CustomManagerModel(RESTFrameworkModel): help_text='OneToOneTarget', verbose_name='OneToOneTarget', on_delete=models.CASCADE) + + +class ListModelForTest(RESTFrameworkModel): + name = models.CharField(max_length=100) + status = models.CharField(max_length=100, blank=True) + + @property + def is_valid(self): + return self.name == 'valid' + + +class EmailPKModel(RESTFrameworkModel): + email = models.EmailField(primary_key=True) + name = models.CharField(max_length=100) + + @property + def is_valid(self): + return self.name == 'valid' diff --git a/tests/test_serializer_lists.py b/tests/test_serializer_lists.py index 42ebf4771..c86c1bd15 100644 --- a/tests/test_serializer_lists.py +++ b/tests/test_serializer_lists.py @@ -5,7 +5,8 @@ from django.utils.datastructures import MultiValueDict from rest_framework import serializers from rest_framework.exceptions import ErrorDetail from tests.models import ( - CustomManagerModel, NullableOneToOneSource, OneToOneTarget + CustomManagerModel, EmailPKModel, ListModelForTest, NullableOneToOneSource, + OneToOneTarget ) @@ -775,3 +776,67 @@ class TestToRepresentationManagerCheck: queryset = NullableOneToOneSource.objects.all() serializer = self.serializer(queryset, many=True) assert serializer.data + + +@pytest.mark.django_db +class TestManyTrueValidationCheck: + """ + Tests ListSerializer validation with many=True across different primary key types + (integer and email). + """ + + def setup_method(self): + self.obj1 = ListModelForTest.objects.create(name="valid", status="new") + self.obj2 = ListModelForTest.objects.create(name="invalid", status="") + self.email_obj1 = EmailPKModel.objects.create(email="test@test.com", name="A") + self.email_obj2 = EmailPKModel.objects.create(email="test2@test.com", name="B") + + self.serializer, self.email_serializer = self.get_serializers() + + def get_serializers(self): + class ListModelForTestSerializer(serializers.ModelSerializer): + class Meta: + model = ListModelForTest + fields = ("id", "name", "status") + + def validate_status(self, value): + if value and not self.instance.is_valid: + return False + return value + + class EmailPKSerializer(serializers.ModelSerializer): + class Meta: + model = EmailPKModel + fields = ("email", "name") + read_only_fields = ('email',) + + def validate_name(self, value): + if value and not self.instance.is_valid: + return False + return value + + return ListModelForTestSerializer, EmailPKSerializer + + def test_run_child_validation_with_many_true(self): + input_data = [ + {"id": self.obj1.pk, "name": "other", "status": "new"}, + {"id": self.obj2.pk, "name": "valid", "status": "progress"}, + ] + + serializer = self.serializer([self.obj1, self.obj2], data=input_data, many=True) + assert serializer.is_valid(), serializer.errors + + serializer = self.serializer(ListModelForTest.objects.all(), data=input_data, many=True) + assert serializer.is_valid(), serializer.errors + + def test_validation_error_for_invalid_data(self): + input_data = [{"id": self.obj1.pk, "name": "", "status": "mystatus"}] + + serializer = self.serializer([self.obj1], data=input_data, many=True) + assert not serializer.is_valid() + assert "name" in serializer.errors[0] + + def test_email_pk_instance_validation(self): + input_data = [{"email": "test@test.com", "name": "bar"}] + serializer = self.email_serializer(instance=EmailPKModel.objects.all(), data=input_data, many=True) + assert serializer.is_valid(), serializer.errors