From ee15731cbc78d364b4a5428c09a15a1019050ccd Mon Sep 17 00:00:00 2001 From: Sigve Sebastian Farstad Date: Sun, 4 Dec 2022 15:37:47 +0100 Subject: [PATCH] Handle Django's ValidationErrors in ListField (#6423) 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 {'uuids': ["'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.")}} ``` --- rest_framework/fields.py | 2 ++ tests/test_fields.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 4eae004dc..cb1b28167 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1639,6 +1639,8 @@ class ListField(Field): result.append(self.child.run_validation(item)) except ValidationError as e: errors[idx] = e.detail + except DjangoValidationError as e: + errors[idx] = get_error_detail(e) if not errors: return result diff --git a/tests/test_fields.py b/tests/test_fields.py index 648c2a9c9..51a661ddc 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -17,6 +17,7 @@ from rest_framework import exceptions, serializers from rest_framework.fields import ( BuiltinSignatureError, DjangoImageField, is_simple_callable ) +from tests.models import UUIDForeignKeyTarget utc = datetime.timezone.utc @@ -2074,6 +2075,35 @@ class TestNestedListField(FieldValues): field = serializers.ListField(child=serializers.ListField(child=serializers.IntegerField())) +class TestListFieldWithDjangoValidationErrors(FieldValues, TestCase): + """ + Values for `ListField` with UUIDField as child + (since UUIDField can throw ValidationErrors from Django). + The idea is to test that Django's ValidationErrors raised + from Django internals are caught and serializers in a way + that is structurally consistent with DRF's ValidationErrors. + """ + + valid_inputs = [] + invalid_inputs = [ + ( + ['not-a-valid-uuid', 'd7364368-d1b3-4455-aaa3-56439b460ca2', 'some-other-invalid-uuid'], + { + 0: [exceptions.ErrorDetail(string='“not-a-valid-uuid” is not a valid UUID.', code='invalid')], + 1: [ + exceptions.ErrorDetail( + string='Invalid pk "d7364368-d1b3-4455-aaa3-56439b460ca2" - object does not exist.', + code='does_not_exist', + ) + ], + 2: [exceptions.ErrorDetail(string='“some-other-invalid-uuid” is not a valid UUID.', code='invalid')], + }, + ), + ] + outputs = {} + field = serializers.ListField(child=serializers.PrimaryKeyRelatedField(queryset=UUIDForeignKeyTarget.objects.all())) + + class TestEmptyListField(FieldValues): """ Values for `ListField` with allow_empty=False flag.