Skip to content
Merged
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
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ Beni Keller <beni@matraxi.ch>
Stas S. <stas@nerd.ro>
Nathanael Gordon <nathanael.l.gordon@gmail.com>
Charlie Allatson <charles.allatson@gmail.com>
Joseba Mendivil <git@jma.email>
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ This release is not backwards compatible. For easy migration best upgrade first
### Added

* Add support for Django REST framework 3.10.
* Add code from ErrorDetail into the JSON:API error object

### Removed

Expand Down
3 changes: 2 additions & 1 deletion example/tests/test_generic_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ def test_generic_validation_error(self):
'source': {
'pointer': '/data'
},
'detail': 'Oh nohs!'
'detail': 'Oh nohs!',
'code': 'invalid',
}]
}

Expand Down
3 changes: 3 additions & 0 deletions example/tests/test_generic_viewset.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,15 @@ def test_default_validation_exceptions(self):
'pointer': '/data/attributes/email',
},
'detail': 'Enter a valid email address.',
'code': 'invalid',
},
{
'status': '400',
'source': {
'pointer': '/data/attributes/first-name',
},
'detail': 'There\'s a problem with first name',
'code': 'invalid',
}
]
}
Expand Down Expand Up @@ -104,6 +106,7 @@ def test_custom_validation_exceptions(self):
'pointer': '/data/attributes/email',
},
'detail': 'Enter a valid email address.',
'code': 'invalid',
},
]
}
Expand Down
2 changes: 1 addition & 1 deletion example/tests/test_model_viewsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ def test_404_error_pointer(self):
not_found_url = reverse('user-detail', kwargs={'pk': 12345})
errors = {
'errors': [
{'detail': 'Not found.', 'status': '404'}
{'detail': 'Not found.', 'status': '404', 'code': 'not_found'}
]
}

Expand Down
9 changes: 6 additions & 3 deletions example/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,8 @@ def test_if_returns_error_on_empty_post(self):
expected = [{
'detail': 'Received document does not contain primary data',
'status': '400',
'source': {'pointer': '/data'}
'source': {'pointer': '/data'},
'code': 'parse_error',
}]
self.assertEqual(expected, response.data)

Expand All @@ -443,7 +444,8 @@ def test_if_returns_error_on_missing_form_data_post(self):
expected = [{
'status': '400',
'detail': 'This field is required.',
'source': {'pointer': '/data/attributes/name'}
'source': {'pointer': '/data/attributes/name'},
'code': 'required',
}]
self.assertEqual(expected, response.data)

Expand All @@ -457,7 +459,8 @@ def test_if_returns_error_on_bad_endpoint_name(self):
"represented by the endpoint (blogs)."
),
'source': {'pointer': '/data'},
'status': '409'
'status': '409',
'code': 'error',
}]
self.assertEqual(expected, response.data)

Expand Down
52 changes: 20 additions & 32 deletions rest_framework_json_api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,13 +308,7 @@ def format_drf_errors(response, context, exc):
# handle generic errors. ValidationError('test') in a view for example
if isinstance(response.data, list):
for message in response.data:
errors.append({
'detail': message,
'source': {
'pointer': '/data',
},
'status': encoding.force_text(response.status_code),
})
errors.append(format_error_object(message, '/data', response))
# handle all errors thrown from serializers
else:
for field, error in response.data.items():
Expand All @@ -325,46 +319,40 @@ def format_drf_errors(response, context, exc):
errors.append(error)
elif isinstance(exc, Http404) and isinstance(error, str):
# 404 errors don't have a pointer
errors.append({
'detail': error,
'status': encoding.force_text(response.status_code),
})
errors.append(format_error_object(error, None, response))
elif isinstance(error, str):
classes = inspect.getmembers(exceptions, inspect.isclass)
# DRF sets the `field` to 'detail' for its own exceptions
if isinstance(exc, tuple(x[1] for x in classes)):
pointer = '/data'
errors.append({
'detail': error,
'source': {
'pointer': pointer,
},
'status': encoding.force_text(response.status_code),
})
errors.append(format_error_object(error, pointer, response))
elif isinstance(error, list):
for message in error:
errors.append({
'detail': message,
'source': {
'pointer': pointer,
},
'status': encoding.force_text(response.status_code),
})
errors.append(format_error_object(message, pointer, response))
else:
errors.append({
'detail': error,
'source': {
'pointer': pointer,
},
'status': encoding.force_text(response.status_code),
})
errors.append(format_error_object(error, pointer, response))

context['view'].resource_name = 'errors'
response.data = errors

return response


def format_error_object(message, pointer, response):
error_obj = {
'detail': message,
'status': encoding.force_text(response.status_code),
}
if pointer is not None:
error_obj['source'] = {
'pointer': pointer,
}
code = getattr(message, "code", None)
if code is not None:
error_obj['code'] = code
return error_obj


def format_errors(data):
if len(data) > 1 and isinstance(data, list):
data.sort(key=lambda x: x.get('source', {}).get('pointer', ''))
Expand Down