Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions rest_framework/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,52 @@ def run_child_validation(self, data):
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

obj_id = None
if pk_name:
for field_name, field in self.child.fields.items():
if getattr(field, "source", None) == pk_name:
obj_id = data.get(field_name)
if obj_id is not None:
break

if obj_id is None:
obj_id = data.get(pk_name) or data.get("pk") or data.get("id")

resolved_instance = None

if obj_id is not None and pk_name:
try:
obj_id = model._meta.pk.to_python(obj_id)
except Exception:
pass

if not hasattr(self, "_instance_index"):
self._instance_index = {
getattr(obj, pk_name): obj for obj in self.instance
}

resolved_instance = self._instance_index.get(obj_id)

if resolved_instance is None:
if model is not None and self.context.get("allow_create", True):
resolved_instance = model()
else:
resolved_instance = child_instance

child_instance = resolved_instance

self.child.instance = child_instance
self.child.initial_data = data
return self.child.run_validation(data)

def to_internal_value(self, data):
Expand Down
27 changes: 27 additions & 0 deletions tests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,30 @@ def __new__(cls, *args, **kwargs):
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'


class PersonUUID(RESTFrameworkModel):
id = models.UUIDField(primary_key=True)
name = models.CharField(max_length=100)

@property
def is_valid(self):
return self.name == 'valid'
102 changes: 101 additions & 1 deletion tests/test_serializer_lists.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
from rest_framework import serializers
from rest_framework.exceptions import ErrorDetail
from tests.models import (
CustomManagerModel, NullableOneToOneSource, OneToOneTarget
CustomManagerModel, EmailPKModel, ListModelForTest, NullableOneToOneSource,
OneToOneTarget, PersonUUID
)


Expand Down Expand Up @@ -775,3 +776,102 @@ def test(self):
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).
"""

class PersonUUIDSerializer(serializers.ModelSerializer):
uuid = serializers.UUIDField(source="id")

class Meta:
model = PersonUUID
fields = ("uuid", "name")
read_only_fields = ("uuid",)

def validate_name(self, value):
if value and not self.instance.is_valid:
return False
return value

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="[email protected]", name="A")
self.email_obj2 = EmailPKModel.objects.create(email="[email protected]", 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": "[email protected]", "name": "bar"}]
serializer = self.email_serializer(instance=EmailPKModel.objects.all(), data=input_data, many=True)
assert serializer.is_valid(), serializer.errors

def test_uuid_validate_many(self):
PersonUUID.objects.create(id="c20f2f31-65a3-451f-ae7d-e939b7d9f84b", name="valid")
PersonUUID.objects.create(id="3308237e-18d8-4074-9d05-79cc0fdb5bb3", name="other")

input_data = [
{
"uuid": "c20f2f31-65a3-451f-ae7d-e939b7d9f84b",
"name": "bar",
},
]
serializer = self.PersonUUIDSerializer(instance=list(PersonUUID.objects.all()), data=input_data, many=True)
assert serializer.is_valid(), serializer.errors

def test_uuid_validate_single(self):
instance = PersonUUID.objects.create(id="c20f2f31-65a3-451f-ae7d-e939b7d9f84b", name="food")

serializer = self.PersonUUIDSerializer(
instance=instance,
data={"uuid": "c20f2f31-65a3-451f-ae7d-e939b7d9f84b", "name": "valid"},
)
assert serializer.is_valid(), serializer.errors