Skip to content

Commit

Permalink
Handle Django's ValidationErrors in ListField
Browse files Browse the repository at this point in the history
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.")}}
```
  • Loading branch information
sigvef committed Dec 3, 2022
1 parent cc3c89a commit 6d6900d
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 0 deletions.
2 changes: 2 additions & 0 deletions rest_framework/fields.py
Expand Up @@ -1639,6 +1639,8 @@ def run_child_validation(self, data):
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
Expand Down
30 changes: 30 additions & 0 deletions tests/test_fields.py
Expand Up @@ -17,6 +17,7 @@
from rest_framework.fields import (
BuiltinSignatureError, DjangoImageField, is_simple_callable
)
from tests.models import UUIDForeignKeyTarget

utc = datetime.timezone.utc

Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit 6d6900d

Please sign in to comment.