Skip to content

Commit cea9527

Browse files
committed
Handle Django's ValidationErrors in ListField
Without this, Django's ValidationErrors will bypass the error collection from ListField's children. Here is an example that illustrates this change. Consider a Serializer that uses ListField like this: ```python class SomeSerializer(serializers.Serializer): uuids = serializers.ListField( child=serializers.PrimaryKeyRelatedField( queryset=Model.objects.something(), validators=[SomeCustomValidator()] ) ) ``` Validating data that looks like this works fine: ```python {uuids: ['some-valid-uuid', 'some-valid-uuid']} ``` Raising a DRF ValidationError for one of the children works fine, giving an error object like: ```python {'uuids': {0: ErrorDetail(string='Some validation error')}} ``` Raising a Django ValidationError for one of the children works differently (which serializers.PrimaryKeyRelatedField can do in some cases, like when the uuid is malformed). It gives an error object like: ```python ["'X' is not a valid UUID."] ``` Handling Django's ValidationErrors in ListField explicitly (like in this pull request), will maintain a regular error interface in this case: ```python {'uuids': {0: ErrorDetail(string="'X' is not a valid UUID.")}} ```
1 parent 2de5081 commit cea9527

File tree

2 files changed

+32
-0
lines changed

2 files changed

+32
-0
lines changed

rest_framework/fields.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1634,6 +1634,8 @@ def run_child_validation(self, data):
16341634
result.append(self.child.run_validation(item))
16351635
except ValidationError as e:
16361636
errors[idx] = e.detail
1637+
except DjangoValidationError as e:
1638+
errors[idx] = get_error_detail(e)
16371639

16381640
if not errors:
16391641
return result

tests/test_fields.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from rest_framework.fields import (
1717
BuiltinSignatureError, DjangoImageField, is_simple_callable
1818
)
19+
from tests.models import UUIDForeignKeyTarget
1920

2021
utc = datetime.timezone.utc
2122

@@ -2040,6 +2041,35 @@ class TestNestedListField(FieldValues):
20402041
field = serializers.ListField(child=serializers.ListField(child=serializers.IntegerField()))
20412042

20422043

2044+
class TestListFieldWithDjangoValidationErrors(FieldValues, TestCase):
2045+
"""
2046+
Values for `ListField` with UUIDField as child
2047+
(since UUIDField can throw ValidationErrors from Django).
2048+
The idea is to test that Django's ValidationErrors raised
2049+
from Django internals are caught and serializers in a way
2050+
that is structurally consistent with DRF's ValidationErrors.
2051+
"""
2052+
2053+
valid_inputs = []
2054+
invalid_inputs = [
2055+
(
2056+
['not-a-valid-uuid', 'd7364368-d1b3-4455-aaa3-56439b460ca2', 'some-other-invalid-uuid'],
2057+
{
2058+
0: [exceptions.ErrorDetail(string='“not-a-valid-uuid” is not a valid UUID.', code='invalid')],
2059+
1: [
2060+
exceptions.ErrorDetail(
2061+
string='Invalid pk "d7364368-d1b3-4455-aaa3-56439b460ca2" - object does not exist.',
2062+
code='does_not_exist',
2063+
)
2064+
],
2065+
2: [exceptions.ErrorDetail(string='“some-other-invalid-uuid” is not a valid UUID.', code='invalid')],
2066+
},
2067+
),
2068+
]
2069+
outputs = {}
2070+
field = serializers.ListField(child=serializers.PrimaryKeyRelatedField(queryset=UUIDForeignKeyTarget.objects.all()))
2071+
2072+
20432073
class TestEmptyListField(FieldValues):
20442074
"""
20452075
Values for `ListField` with allow_empty=False flag.

0 commit comments

Comments
 (0)