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.")}}
```
This commit is contained in:
Sigve Sebastian Farstad 2022-12-04 15:37:47 +01:00 committed by GitHub
parent cc3c89a11c
commit ee15731cbc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 32 additions and 0 deletions

View File

@ -1639,6 +1639,8 @@ class ListField(Field):
result.append(self.child.run_validation(item)) result.append(self.child.run_validation(item))
except ValidationError as e: except ValidationError as e:
errors[idx] = e.detail errors[idx] = e.detail
except DjangoValidationError as e:
errors[idx] = get_error_detail(e)
if not errors: if not errors:
return result return result

View File

@ -17,6 +17,7 @@ from rest_framework import exceptions, serializers
from rest_framework.fields import ( from rest_framework.fields import (
BuiltinSignatureError, DjangoImageField, is_simple_callable BuiltinSignatureError, DjangoImageField, is_simple_callable
) )
from tests.models import UUIDForeignKeyTarget
utc = datetime.timezone.utc utc = datetime.timezone.utc
@ -2074,6 +2075,35 @@ class TestNestedListField(FieldValues):
field = serializers.ListField(child=serializers.ListField(child=serializers.IntegerField())) 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): class TestEmptyListField(FieldValues):
""" """
Values for `ListField` with allow_empty=False flag. Values for `ListField` with allow_empty=False flag.