diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py index 09f111102e..bc20fcaa37 100644 --- a/rest_framework/exceptions.py +++ b/rest_framework/exceptions.py @@ -144,17 +144,30 @@ class ValidationError(APIException): status_code = status.HTTP_400_BAD_REQUEST default_detail = _('Invalid input.') default_code = 'invalid' + default_params = {} - def __init__(self, detail=None, code=None): + def __init__(self, detail=None, code=None, params=None): if detail is None: detail = self.default_detail if code is None: code = self.default_code + if params is None: + params = self.default_params # For validation failures, we may collect many errors together, # so the details should always be coerced to a list if not already. - if isinstance(detail, tuple): - detail = list(detail) + if isinstance(detail, str): + detail = [detail % params] + elif isinstance(detail, ValidationError): + detail = detail.detail + elif isinstance(detail, (list, tuple)): + final_detail = [] + for detail_item in detail: + if isinstance(detail_item, ValidationError): + final_detail += detail_item.detail + else: + final_detail += [detail_item % params if isinstance(detail_item, str) else detail_item] + detail = final_detail elif not isinstance(detail, dict) and not isinstance(detail, list): detail = [detail] diff --git a/tests/test_validation_error.py b/tests/test_validation_error.py index 341c4342a5..7b8b3190fa 100644 --- a/tests/test_validation_error.py +++ b/tests/test_validation_error.py @@ -109,3 +109,89 @@ def test_validation_error_details(self): assert len(error.detail) == 2 assert str(error.detail[0]) == 'message1' assert str(error.detail[1]) == 'message2' + + +class TestValidationErrorWithDjangoStyle(TestCase): + def test_validation_error_details(self): + error = ValidationError('Invalid value: %(value)s', params={'value': '42'}) + assert str(error.detail[0]) == 'Invalid value: 42' + + def test_validation_error_details_tuple(self): + error = ValidationError( + detail=('Invalid value: %(value1)s', 'Invalid value: %(value2)s'), + params={'value1': '42', 'value2': '43'}, + ) + assert isinstance(error.detail, list) + assert len(error.detail) == 2 + assert str(error.detail[0]) == 'Invalid value: 42' + assert str(error.detail[1]) == 'Invalid value: 43' + + def test_validation_error_details_list(self): + error = ValidationError( + detail=['Invalid value: %(value1)s', 'Invalid value: %(value2)s', ], + params={'value1': '42', 'value2': '43'} + ) + assert isinstance(error.detail, list) + assert len(error.detail) == 2 + assert str(error.detail[0]) == 'Invalid value: 42' + assert str(error.detail[1]) == 'Invalid value: 43' + + def test_validation_error_details_validation_errors(self): + error = ValidationError( + detail=ValidationError( + detail='Invalid value: %(value1)s', + params={'value1': '42'}, + ), + ) + assert isinstance(error.detail, list) + assert len(error.detail) == 1 + assert str(error.detail[0]) == 'Invalid value: 42' + + def test_validation_error_details_validation_errors_list(self): + error = ValidationError( + detail=[ + ValidationError( + detail='Invalid value: %(value1)s', + params={'value1': '42'}, + ), + ValidationError( + detail='Invalid value: %(value2)s', + params={'value2': '43'}, + ), + 'Invalid value: %(value3)s' + ], + params={'value3': '44'} + ) + assert isinstance(error.detail, list) + assert len(error.detail) == 3 + assert str(error.detail[0]) == 'Invalid value: 42' + assert str(error.detail[1]) == 'Invalid value: 43' + assert str(error.detail[2]) == 'Invalid value: 44' + + def test_validation_error_details_validation_errors_nested_list(self): + error = ValidationError( + detail=[ + ValidationError( + detail='Invalid value: %(value1)s', + params={'value1': '42'}, + ), + ValidationError( + detail=[ + 'Invalid value: %(value2)s', + ValidationError( + detail='Invalid value: %(value3)s', + params={'value3': '44'}, + ) + ], + params={'value2': '43'}, + ), + 'Invalid value: %(value4)s' + ], + params={'value4': '45'} + ) + assert isinstance(error.detail, list) + assert len(error.detail) == 4 + assert str(error.detail[0]) == 'Invalid value: 42' + assert str(error.detail[1]) == 'Invalid value: 43' + assert str(error.detail[2]) == 'Invalid value: 44' + assert str(error.detail[3]) == 'Invalid value: 45'