mirror of
				https://github.com/encode/django-rest-framework.git
				synced 2025-11-04 01:47:59 +03:00 
			
		
		
		
	
							parent
							
								
									5f6ceee7a5
								
							
						
					
					
						commit
						a3802504a0
					
				| 
						 | 
					@ -98,7 +98,7 @@ Note that the exception handler will only be called for responses generated by r
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The **base class** for all exceptions raised inside an `APIView` class or `@api_view`.
 | 
					The **base class** for all exceptions raised inside an `APIView` class or `@api_view`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
To provide a custom exception, subclass `APIException` and set the `.status_code` and `.default_detail` properties on the class.
 | 
					To provide a custom exception, subclass `APIException` and set the `.status_code`, `.default_detail`, and `default_code` attributes on the class.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
For example, if your API relies on a third party service that may sometimes be unreachable, you might want to implement an exception for the "503 Service Unavailable" HTTP response code.  You could do this like so:
 | 
					For example, if your API relies on a third party service that may sometimes be unreachable, you might want to implement an exception for the "503 Service Unavailable" HTTP response code.  You could do this like so:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -107,10 +107,42 @@ For example, if your API relies on a third party service that may sometimes be u
 | 
				
			||||||
    class ServiceUnavailable(APIException):
 | 
					    class ServiceUnavailable(APIException):
 | 
				
			||||||
        status_code = 503
 | 
					        status_code = 503
 | 
				
			||||||
        default_detail = 'Service temporarily unavailable, try again later.'
 | 
					        default_detail = 'Service temporarily unavailable, try again later.'
 | 
				
			||||||
 | 
					        default_code = 'service_unavailable'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Inspecting API exceptions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					There are a number of different properties available for inspecting the status
 | 
				
			||||||
 | 
					of an API exception. You can use these to build custom exception handling
 | 
				
			||||||
 | 
					for your project.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The available attributes and methods are:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* `.detail` - Return the textual description of the error.
 | 
				
			||||||
 | 
					* `.get_codes()` - Return the code identifier of the error.
 | 
				
			||||||
 | 
					* `.full_details()` - Return both the textual description and the code identifier.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In most cases the error detail will be a simple item:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    >>> print(exc.detail)
 | 
				
			||||||
 | 
					    You do not have permission to perform this action.
 | 
				
			||||||
 | 
					    >>> print(exc.get_codes())
 | 
				
			||||||
 | 
					    permission_denied
 | 
				
			||||||
 | 
					    >>> print(exc.full_details())
 | 
				
			||||||
 | 
					    {'message':'You do not have permission to perform this action.','code':'permission_denied'}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In the case of validation errors the error detail will be either a list or
 | 
				
			||||||
 | 
					dictionary of items:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    >>> print(exc.detail)
 | 
				
			||||||
 | 
					    {"name":"This field is required.","age":"A valid integer is required."}
 | 
				
			||||||
 | 
					    >>> print(exc.get_codes())
 | 
				
			||||||
 | 
					    {"name":"required","age":"invalid"}
 | 
				
			||||||
 | 
					    >>> print(exc.get_full_details())
 | 
				
			||||||
 | 
					    {"name":{"message":"This field is required.","code":"required"},"age":{"message":"A valid integer is required.","code":"invalid"}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## ParseError
 | 
					## ParseError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Signature:** `ParseError(detail=None)`
 | 
					**Signature:** `ParseError(detail=None, code=None)`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Raised if the request contains malformed data when accessing `request.data`.
 | 
					Raised if the request contains malformed data when accessing `request.data`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -118,7 +150,7 @@ By default this exception results in a response with the HTTP status code "400 B
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## AuthenticationFailed
 | 
					## AuthenticationFailed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Signature:** `AuthenticationFailed(detail=None)`
 | 
					**Signature:** `AuthenticationFailed(detail=None, code=None)`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Raised when an incoming request includes incorrect authentication.
 | 
					Raised when an incoming request includes incorrect authentication.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -126,7 +158,7 @@ By default this exception results in a response with the HTTP status code "401 U
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## NotAuthenticated
 | 
					## NotAuthenticated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Signature:** `NotAuthenticated(detail=None)`
 | 
					**Signature:** `NotAuthenticated(detail=None, code=None)`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Raised when an unauthenticated request fails the permission checks.
 | 
					Raised when an unauthenticated request fails the permission checks.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -134,7 +166,7 @@ By default this exception results in a response with the HTTP status code "401 U
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## PermissionDenied
 | 
					## PermissionDenied
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Signature:** `PermissionDenied(detail=None)`
 | 
					**Signature:** `PermissionDenied(detail=None, code=None)`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Raised when an authenticated request fails the permission checks.
 | 
					Raised when an authenticated request fails the permission checks.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -142,7 +174,7 @@ By default this exception results in a response with the HTTP status code "403 F
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## NotFound
 | 
					## NotFound
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Signature:** `NotFound(detail=None)`
 | 
					**Signature:** `NotFound(detail=None, code=None)`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Raised when a resource does not exists at the given URL. This exception is equivalent to the standard `Http404` Django exception.
 | 
					Raised when a resource does not exists at the given URL. This exception is equivalent to the standard `Http404` Django exception.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -150,7 +182,7 @@ By default this exception results in a response with the HTTP status code "404 N
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## MethodNotAllowed
 | 
					## MethodNotAllowed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Signature:** `MethodNotAllowed(method, detail=None)`
 | 
					**Signature:** `MethodNotAllowed(method, detail=None, code=None)`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Raised when an incoming request occurs that does not map to a handler method on the view.
 | 
					Raised when an incoming request occurs that does not map to a handler method on the view.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -158,7 +190,7 @@ By default this exception results in a response with the HTTP status code "405 M
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## NotAcceptable
 | 
					## NotAcceptable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Signature:** `NotAcceptable(detail=None)`
 | 
					**Signature:** `NotAcceptable(detail=None, code=None)`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Raised when an incoming request occurs with an `Accept` header that cannot be satisfied by any of the available renderers.
 | 
					Raised when an incoming request occurs with an `Accept` header that cannot be satisfied by any of the available renderers.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -166,7 +198,7 @@ By default this exception results in a response with the HTTP status code "406 N
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## UnsupportedMediaType
 | 
					## UnsupportedMediaType
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Signature:** `UnsupportedMediaType(media_type, detail=None)`
 | 
					**Signature:** `UnsupportedMediaType(media_type, detail=None, code=None)`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Raised if there are no parsers that can handle the content type of the request data when accessing `request.data`.
 | 
					Raised if there are no parsers that can handle the content type of the request data when accessing `request.data`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -174,7 +206,7 @@ By default this exception results in a response with the HTTP status code "415 U
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Throttled
 | 
					## Throttled
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Signature:** `Throttled(wait=None, detail=None)`
 | 
					**Signature:** `Throttled(wait=None, detail=None, code=None)`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Raised when an incoming request fails the throttling checks.
 | 
					Raised when an incoming request fails the throttling checks.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -182,7 +214,7 @@ By default this exception results in a response with the HTTP status code "429 T
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## ValidationError
 | 
					## ValidationError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Signature:** `ValidationError(detail)`
 | 
					**Signature:** `ValidationError(detail, code=None)`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
The `ValidationError` exception is slightly different from the other `APIException` classes:
 | 
					The `ValidationError` exception is slightly different from the other `APIException` classes:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,13 +21,13 @@ class AuthTokenSerializer(serializers.Serializer):
 | 
				
			||||||
                # (Assuming the default `ModelBackend` authentication backend.)
 | 
					                # (Assuming the default `ModelBackend` authentication backend.)
 | 
				
			||||||
                if not user.is_active:
 | 
					                if not user.is_active:
 | 
				
			||||||
                    msg = _('User account is disabled.')
 | 
					                    msg = _('User account is disabled.')
 | 
				
			||||||
                    raise serializers.ValidationError(msg)
 | 
					                    raise serializers.ValidationError(msg, code='authorization')
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                msg = _('Unable to log in with provided credentials.')
 | 
					                msg = _('Unable to log in with provided credentials.')
 | 
				
			||||||
                raise serializers.ValidationError(msg)
 | 
					                raise serializers.ValidationError(msg, code='authorization')
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            msg = _('Must include "username" and "password".')
 | 
					            msg = _('Must include "username" and "password".')
 | 
				
			||||||
            raise serializers.ValidationError(msg)
 | 
					            raise serializers.ValidationError(msg, code='authorization')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        attrs['user'] = user
 | 
					        attrs['user'] = user
 | 
				
			||||||
        return attrs
 | 
					        return attrs
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,27 +17,61 @@ from rest_framework import status
 | 
				
			||||||
from rest_framework.utils.serializer_helpers import ReturnDict, ReturnList
 | 
					from rest_framework.utils.serializer_helpers import ReturnDict, ReturnList
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _force_text_recursive(data):
 | 
					def _get_error_details(data, default_code=None):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Descend into a nested data structure, forcing any
 | 
					    Descend into a nested data structure, forcing any
 | 
				
			||||||
    lazy translation strings into plain text.
 | 
					    lazy translation strings or strings into `ErrorDetail`.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    if isinstance(data, list):
 | 
					    if isinstance(data, list):
 | 
				
			||||||
        ret = [
 | 
					        ret = [
 | 
				
			||||||
            _force_text_recursive(item) for item in data
 | 
					            _get_error_details(item, default_code) for item in data
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
        if isinstance(data, ReturnList):
 | 
					        if isinstance(data, ReturnList):
 | 
				
			||||||
            return ReturnList(ret, serializer=data.serializer)
 | 
					            return ReturnList(ret, serializer=data.serializer)
 | 
				
			||||||
        return ret
 | 
					        return ret
 | 
				
			||||||
    elif isinstance(data, dict):
 | 
					    elif isinstance(data, dict):
 | 
				
			||||||
        ret = {
 | 
					        ret = {
 | 
				
			||||||
            key: _force_text_recursive(value)
 | 
					            key: _get_error_details(value, default_code)
 | 
				
			||||||
            for key, value in data.items()
 | 
					            for key, value in data.items()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if isinstance(data, ReturnDict):
 | 
					        if isinstance(data, ReturnDict):
 | 
				
			||||||
            return ReturnDict(ret, serializer=data.serializer)
 | 
					            return ReturnDict(ret, serializer=data.serializer)
 | 
				
			||||||
        return ret
 | 
					        return ret
 | 
				
			||||||
    return force_text(data)
 | 
					
 | 
				
			||||||
 | 
					    text = force_text(data)
 | 
				
			||||||
 | 
					    code = getattr(data, 'code', default_code)
 | 
				
			||||||
 | 
					    return ErrorDetail(text, code)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _get_codes(detail):
 | 
				
			||||||
 | 
					    if isinstance(detail, list):
 | 
				
			||||||
 | 
					        return [_get_codes(item) for item in detail]
 | 
				
			||||||
 | 
					    elif isinstance(detail, dict):
 | 
				
			||||||
 | 
					        return {key: _get_codes(value) for key, value in detail.items()}
 | 
				
			||||||
 | 
					    return detail.code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _get_full_details(detail):
 | 
				
			||||||
 | 
					    if isinstance(detail, list):
 | 
				
			||||||
 | 
					        return [_get_full_details(item) for item in detail]
 | 
				
			||||||
 | 
					    elif isinstance(detail, dict):
 | 
				
			||||||
 | 
					        return {key: _get_full_details(value) for key, value in detail.items()}
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        'message': detail,
 | 
				
			||||||
 | 
					        'code': detail.code
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ErrorDetail(six.text_type):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    A string-like object that can additionally
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    code = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __new__(cls, string, code=None):
 | 
				
			||||||
 | 
					        self = super(ErrorDetail, cls).__new__(cls, string)
 | 
				
			||||||
 | 
					        self.code = code
 | 
				
			||||||
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class APIException(Exception):
 | 
					class APIException(Exception):
 | 
				
			||||||
| 
						 | 
					@ -47,16 +81,35 @@ class APIException(Exception):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
 | 
					    status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
 | 
				
			||||||
    default_detail = _('A server error occurred.')
 | 
					    default_detail = _('A server error occurred.')
 | 
				
			||||||
 | 
					    default_code = 'error'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, detail=None):
 | 
					    def __init__(self, detail=None, code=None):
 | 
				
			||||||
        if detail is not None:
 | 
					        if detail is None:
 | 
				
			||||||
            self.detail = force_text(detail)
 | 
					            detail = self.default_detail
 | 
				
			||||||
        else:
 | 
					        if code is None:
 | 
				
			||||||
            self.detail = force_text(self.default_detail)
 | 
					            code = self.default_code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.detail = _get_error_details(detail, code)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __str__(self):
 | 
					    def __str__(self):
 | 
				
			||||||
        return self.detail
 | 
					        return self.detail
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_codes(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Return only the code part of the error details.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Eg. {"name": ["required"]}
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return _get_codes(self.detail)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_full_details(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Return both the message & code parts of the error details.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Eg. {"name": [{"message": "This field is required.", "code": "required"}]}
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        return _get_full_details(self.detail)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# The recommended style for using `ValidationError` is to keep it namespaced
 | 
					# The recommended style for using `ValidationError` is to keep it namespaced
 | 
				
			||||||
# under `serializers`, in order to minimize potential confusion with Django's
 | 
					# under `serializers`, in order to minimize potential confusion with Django's
 | 
				
			||||||
| 
						 | 
					@ -67,13 +120,21 @@ class APIException(Exception):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ValidationError(APIException):
 | 
					class ValidationError(APIException):
 | 
				
			||||||
    status_code = status.HTTP_400_BAD_REQUEST
 | 
					    status_code = status.HTTP_400_BAD_REQUEST
 | 
				
			||||||
 | 
					    default_detail = _('Invalid input.')
 | 
				
			||||||
 | 
					    default_code = 'invalid'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, detail):
 | 
					    def __init__(self, detail, code=None):
 | 
				
			||||||
        # For validation errors the 'detail' key is always required.
 | 
					        if detail is None:
 | 
				
			||||||
        # The details should always be coerced to a list if not already.
 | 
					            detail = self.default_detail
 | 
				
			||||||
 | 
					        if code is None:
 | 
				
			||||||
 | 
					            code = self.default_code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # For validation failures, we may collect may errors together, so the
 | 
				
			||||||
 | 
					        # details should always be coerced to a list if not already.
 | 
				
			||||||
        if not isinstance(detail, dict) and not isinstance(detail, list):
 | 
					        if not isinstance(detail, dict) and not isinstance(detail, list):
 | 
				
			||||||
            detail = [detail]
 | 
					            detail = [detail]
 | 
				
			||||||
        self.detail = _force_text_recursive(detail)
 | 
					
 | 
				
			||||||
 | 
					        self.detail = _get_error_details(detail, code)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __str__(self):
 | 
					    def __str__(self):
 | 
				
			||||||
        return six.text_type(self.detail)
 | 
					        return six.text_type(self.detail)
 | 
				
			||||||
| 
						 | 
					@ -82,62 +143,63 @@ class ValidationError(APIException):
 | 
				
			||||||
class ParseError(APIException):
 | 
					class ParseError(APIException):
 | 
				
			||||||
    status_code = status.HTTP_400_BAD_REQUEST
 | 
					    status_code = status.HTTP_400_BAD_REQUEST
 | 
				
			||||||
    default_detail = _('Malformed request.')
 | 
					    default_detail = _('Malformed request.')
 | 
				
			||||||
 | 
					    default_code = 'parse_error'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AuthenticationFailed(APIException):
 | 
					class AuthenticationFailed(APIException):
 | 
				
			||||||
    status_code = status.HTTP_401_UNAUTHORIZED
 | 
					    status_code = status.HTTP_401_UNAUTHORIZED
 | 
				
			||||||
    default_detail = _('Incorrect authentication credentials.')
 | 
					    default_detail = _('Incorrect authentication credentials.')
 | 
				
			||||||
 | 
					    default_code = 'authentication_failed'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class NotAuthenticated(APIException):
 | 
					class NotAuthenticated(APIException):
 | 
				
			||||||
    status_code = status.HTTP_401_UNAUTHORIZED
 | 
					    status_code = status.HTTP_401_UNAUTHORIZED
 | 
				
			||||||
    default_detail = _('Authentication credentials were not provided.')
 | 
					    default_detail = _('Authentication credentials were not provided.')
 | 
				
			||||||
 | 
					    default_code = 'not_authenticated'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PermissionDenied(APIException):
 | 
					class PermissionDenied(APIException):
 | 
				
			||||||
    status_code = status.HTTP_403_FORBIDDEN
 | 
					    status_code = status.HTTP_403_FORBIDDEN
 | 
				
			||||||
    default_detail = _('You do not have permission to perform this action.')
 | 
					    default_detail = _('You do not have permission to perform this action.')
 | 
				
			||||||
 | 
					    default_code = 'permission_denied'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class NotFound(APIException):
 | 
					class NotFound(APIException):
 | 
				
			||||||
    status_code = status.HTTP_404_NOT_FOUND
 | 
					    status_code = status.HTTP_404_NOT_FOUND
 | 
				
			||||||
    default_detail = _('Not found.')
 | 
					    default_detail = _('Not found.')
 | 
				
			||||||
 | 
					    default_code = 'not_found'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MethodNotAllowed(APIException):
 | 
					class MethodNotAllowed(APIException):
 | 
				
			||||||
    status_code = status.HTTP_405_METHOD_NOT_ALLOWED
 | 
					    status_code = status.HTTP_405_METHOD_NOT_ALLOWED
 | 
				
			||||||
    default_detail = _('Method "{method}" not allowed.')
 | 
					    default_detail = _('Method "{method}" not allowed.')
 | 
				
			||||||
 | 
					    default_code = 'method_not_allowed'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, method, detail=None):
 | 
					    def __init__(self, method, detail=None, code=None):
 | 
				
			||||||
        if detail is not None:
 | 
					        if detail is None:
 | 
				
			||||||
            self.detail = force_text(detail)
 | 
					            detail = force_text(self.default_detail).format(method=method)
 | 
				
			||||||
        else:
 | 
					        super(MethodNotAllowed, self).__init__(detail, code)
 | 
				
			||||||
            self.detail = force_text(self.default_detail).format(method=method)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class NotAcceptable(APIException):
 | 
					class NotAcceptable(APIException):
 | 
				
			||||||
    status_code = status.HTTP_406_NOT_ACCEPTABLE
 | 
					    status_code = status.HTTP_406_NOT_ACCEPTABLE
 | 
				
			||||||
    default_detail = _('Could not satisfy the request Accept header.')
 | 
					    default_detail = _('Could not satisfy the request Accept header.')
 | 
				
			||||||
 | 
					    default_code = 'not_acceptable'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, detail=None, available_renderers=None):
 | 
					    def __init__(self, detail=None, code=None, available_renderers=None):
 | 
				
			||||||
        if detail is not None:
 | 
					 | 
				
			||||||
            self.detail = force_text(detail)
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            self.detail = force_text(self.default_detail)
 | 
					 | 
				
			||||||
        self.available_renderers = available_renderers
 | 
					        self.available_renderers = available_renderers
 | 
				
			||||||
 | 
					        super(NotAcceptable, self).__init__(detail, code)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class UnsupportedMediaType(APIException):
 | 
					class UnsupportedMediaType(APIException):
 | 
				
			||||||
    status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE
 | 
					    status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE
 | 
				
			||||||
    default_detail = _('Unsupported media type "{media_type}" in request.')
 | 
					    default_detail = _('Unsupported media type "{media_type}" in request.')
 | 
				
			||||||
 | 
					    default_code = 'unsupported_media_type'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, media_type, detail=None):
 | 
					    def __init__(self, media_type, detail=None, code=None):
 | 
				
			||||||
        if detail is not None:
 | 
					        if detail is None:
 | 
				
			||||||
            self.detail = force_text(detail)
 | 
					            detail = force_text(self.default_detail).format(media_type=media_type)
 | 
				
			||||||
        else:
 | 
					        super(UnsupportedMediaType, self).__init__(detail, code)
 | 
				
			||||||
            self.detail = force_text(self.default_detail).format(
 | 
					 | 
				
			||||||
                media_type=media_type
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Throttled(APIException):
 | 
					class Throttled(APIException):
 | 
				
			||||||
| 
						 | 
					@ -145,12 +207,10 @@ class Throttled(APIException):
 | 
				
			||||||
    default_detail = _('Request was throttled.')
 | 
					    default_detail = _('Request was throttled.')
 | 
				
			||||||
    extra_detail_singular = 'Expected available in {wait} second.'
 | 
					    extra_detail_singular = 'Expected available in {wait} second.'
 | 
				
			||||||
    extra_detail_plural = 'Expected available in {wait} seconds.'
 | 
					    extra_detail_plural = 'Expected available in {wait} seconds.'
 | 
				
			||||||
 | 
					    default_code = 'throttled'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, wait=None, detail=None):
 | 
					    def __init__(self, wait=None, detail=None, code=None):
 | 
				
			||||||
        if detail is not None:
 | 
					        super(Throttled, self).__init__(detail, code)
 | 
				
			||||||
            self.detail = force_text(detail)
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            self.detail = force_text(self.default_detail)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if wait is None:
 | 
					        if wait is None:
 | 
				
			||||||
            self.wait = None
 | 
					            self.wait = None
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -34,7 +34,7 @@ from rest_framework import ISO_8601
 | 
				
			||||||
from rest_framework.compat import (
 | 
					from rest_framework.compat import (
 | 
				
			||||||
    get_remote_field, unicode_repr, unicode_to_repr, value_from_object
 | 
					    get_remote_field, unicode_repr, unicode_to_repr, value_from_object
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
from rest_framework.exceptions import ValidationError
 | 
					from rest_framework.exceptions import ErrorDetail, ValidationError
 | 
				
			||||||
from rest_framework.settings import api_settings
 | 
					from rest_framework.settings import api_settings
 | 
				
			||||||
from rest_framework.utils import html, humanize_datetime, representation
 | 
					from rest_framework.utils import html, humanize_datetime, representation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -224,6 +224,18 @@ def iter_options(grouped_choices, cutoff=None, cutoff_text=None):
 | 
				
			||||||
        yield Option(value='n/a', display_text=cutoff_text, disabled=True)
 | 
					        yield Option(value='n/a', display_text=cutoff_text, disabled=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_error_detail(exc_info):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Given a Django ValidationError, return a list of ErrorDetail,
 | 
				
			||||||
 | 
					    with the `code` populated.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    code = getattr(exc_info, 'code', None) or 'invalid'
 | 
				
			||||||
 | 
					    return [
 | 
				
			||||||
 | 
					        ErrorDetail(msg, code=code)
 | 
				
			||||||
 | 
					        for msg in exc_info.messages
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CreateOnlyDefault(object):
 | 
					class CreateOnlyDefault(object):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    This class may be used to provide default values that are only used
 | 
					    This class may be used to provide default values that are only used
 | 
				
			||||||
| 
						 | 
					@ -525,7 +537,7 @@ class Field(object):
 | 
				
			||||||
                    raise
 | 
					                    raise
 | 
				
			||||||
                errors.extend(exc.detail)
 | 
					                errors.extend(exc.detail)
 | 
				
			||||||
            except DjangoValidationError as exc:
 | 
					            except DjangoValidationError as exc:
 | 
				
			||||||
                errors.extend(exc.messages)
 | 
					                errors.extend(get_error_detail(exc))
 | 
				
			||||||
        if errors:
 | 
					        if errors:
 | 
				
			||||||
            raise ValidationError(errors)
 | 
					            raise ValidationError(errors)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -563,7 +575,7 @@ class Field(object):
 | 
				
			||||||
            msg = MISSING_ERROR_MESSAGE.format(class_name=class_name, key=key)
 | 
					            msg = MISSING_ERROR_MESSAGE.format(class_name=class_name, key=key)
 | 
				
			||||||
            raise AssertionError(msg)
 | 
					            raise AssertionError(msg)
 | 
				
			||||||
        message_string = msg.format(**kwargs)
 | 
					        message_string = msg.format(**kwargs)
 | 
				
			||||||
        raise ValidationError(message_string)
 | 
					        raise ValidationError(message_string, code=key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @cached_property
 | 
					    @cached_property
 | 
				
			||||||
    def root(self):
 | 
					    def root(self):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -291,32 +291,29 @@ class SerializerMetaclass(type):
 | 
				
			||||||
        return super(SerializerMetaclass, cls).__new__(cls, name, bases, attrs)
 | 
					        return super(SerializerMetaclass, cls).__new__(cls, name, bases, attrs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_validation_error_detail(exc):
 | 
					def as_serializer_error(exc):
 | 
				
			||||||
    assert isinstance(exc, (ValidationError, DjangoValidationError))
 | 
					    assert isinstance(exc, (ValidationError, DjangoValidationError))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if isinstance(exc, DjangoValidationError):
 | 
					    if isinstance(exc, DjangoValidationError):
 | 
				
			||||||
        # Normally you should raise `serializers.ValidationError`
 | 
					        detail = get_error_detail(exc)
 | 
				
			||||||
        # inside your codebase, but we handle Django's validation
 | 
					    else:
 | 
				
			||||||
        # exception class as well for simpler compat.
 | 
					        detail = exc.detail
 | 
				
			||||||
        # Eg. Calling Model.clean() explicitly inside Serializer.validate()
 | 
					
 | 
				
			||||||
        return {
 | 
					    if isinstance(detail, dict):
 | 
				
			||||||
            api_settings.NON_FIELD_ERRORS_KEY: list(exc.messages)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    elif isinstance(exc.detail, dict):
 | 
					 | 
				
			||||||
        # If errors may be a dict we use the standard {key: list of values}.
 | 
					        # If errors may be a dict we use the standard {key: list of values}.
 | 
				
			||||||
        # Here we ensure that all the values are *lists* of errors.
 | 
					        # Here we ensure that all the values are *lists* of errors.
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            key: value if isinstance(value, (list, dict)) else [value]
 | 
					            key: value if isinstance(value, (list, dict)) else [value]
 | 
				
			||||||
            for key, value in exc.detail.items()
 | 
					            for key, value in detail.items()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    elif isinstance(exc.detail, list):
 | 
					    elif isinstance(detail, list):
 | 
				
			||||||
        # Errors raised as a list are non-field errors.
 | 
					        # Errors raised as a list are non-field errors.
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            api_settings.NON_FIELD_ERRORS_KEY: exc.detail
 | 
					            api_settings.NON_FIELD_ERRORS_KEY: detail
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    # Errors raised as a string are non-field errors.
 | 
					    # Errors raised as a string are non-field errors.
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
        api_settings.NON_FIELD_ERRORS_KEY: [exc.detail]
 | 
					        api_settings.NON_FIELD_ERRORS_KEY: [detail]
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -410,7 +407,7 @@ class Serializer(BaseSerializer):
 | 
				
			||||||
            value = self.validate(value)
 | 
					            value = self.validate(value)
 | 
				
			||||||
            assert value is not None, '.validate() should return the validated data'
 | 
					            assert value is not None, '.validate() should return the validated data'
 | 
				
			||||||
        except (ValidationError, DjangoValidationError) as exc:
 | 
					        except (ValidationError, DjangoValidationError) as exc:
 | 
				
			||||||
            raise ValidationError(detail=get_validation_error_detail(exc))
 | 
					            raise ValidationError(detail=as_serializer_error(exc))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return value
 | 
					        return value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -424,7 +421,7 @@ class Serializer(BaseSerializer):
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            raise ValidationError({
 | 
					            raise ValidationError({
 | 
				
			||||||
                api_settings.NON_FIELD_ERRORS_KEY: [message]
 | 
					                api_settings.NON_FIELD_ERRORS_KEY: [message]
 | 
				
			||||||
            })
 | 
					            }, code='invalid')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ret = OrderedDict()
 | 
					        ret = OrderedDict()
 | 
				
			||||||
        errors = OrderedDict()
 | 
					        errors = OrderedDict()
 | 
				
			||||||
| 
						 | 
					@ -440,7 +437,7 @@ class Serializer(BaseSerializer):
 | 
				
			||||||
            except ValidationError as exc:
 | 
					            except ValidationError as exc:
 | 
				
			||||||
                errors[field.field_name] = exc.detail
 | 
					                errors[field.field_name] = exc.detail
 | 
				
			||||||
            except DjangoValidationError as exc:
 | 
					            except DjangoValidationError as exc:
 | 
				
			||||||
                errors[field.field_name] = list(exc.messages)
 | 
					                errors[field.field_name] = get_error_detail(exc)
 | 
				
			||||||
            except SkipField:
 | 
					            except SkipField:
 | 
				
			||||||
                pass
 | 
					                pass
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
| 
						 | 
					@ -564,7 +561,7 @@ class ListSerializer(BaseSerializer):
 | 
				
			||||||
            value = self.validate(value)
 | 
					            value = self.validate(value)
 | 
				
			||||||
            assert value is not None, '.validate() should return the validated data'
 | 
					            assert value is not None, '.validate() should return the validated data'
 | 
				
			||||||
        except (ValidationError, DjangoValidationError) as exc:
 | 
					        except (ValidationError, DjangoValidationError) as exc:
 | 
				
			||||||
            raise ValidationError(detail=get_validation_error_detail(exc))
 | 
					            raise ValidationError(detail=as_serializer_error(exc))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return value
 | 
					        return value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -581,13 +578,13 @@ class ListSerializer(BaseSerializer):
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            raise ValidationError({
 | 
					            raise ValidationError({
 | 
				
			||||||
                api_settings.NON_FIELD_ERRORS_KEY: [message]
 | 
					                api_settings.NON_FIELD_ERRORS_KEY: [message]
 | 
				
			||||||
            })
 | 
					            }, code='not_a_list')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not self.allow_empty and len(data) == 0:
 | 
					        if not self.allow_empty and len(data) == 0:
 | 
				
			||||||
            message = self.error_messages['empty']
 | 
					            message = self.error_messages['empty']
 | 
				
			||||||
            raise ValidationError({
 | 
					            raise ValidationError({
 | 
				
			||||||
                api_settings.NON_FIELD_ERRORS_KEY: [message]
 | 
					                api_settings.NON_FIELD_ERRORS_KEY: [message]
 | 
				
			||||||
            })
 | 
					            }, code='empty')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ret = []
 | 
					        ret = []
 | 
				
			||||||
        errors = []
 | 
					        errors = []
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -80,7 +80,7 @@ class UniqueValidator(object):
 | 
				
			||||||
        queryset = self.filter_queryset(value, queryset)
 | 
					        queryset = self.filter_queryset(value, queryset)
 | 
				
			||||||
        queryset = self.exclude_current_instance(queryset)
 | 
					        queryset = self.exclude_current_instance(queryset)
 | 
				
			||||||
        if qs_exists(queryset):
 | 
					        if qs_exists(queryset):
 | 
				
			||||||
            raise ValidationError(self.message)
 | 
					            raise ValidationError(self.message, code='unique')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __repr__(self):
 | 
					    def __repr__(self):
 | 
				
			||||||
        return unicode_to_repr('<%s(queryset=%s)>' % (
 | 
					        return unicode_to_repr('<%s(queryset=%s)>' % (
 | 
				
			||||||
| 
						 | 
					@ -120,13 +120,13 @@ class UniqueTogetherValidator(object):
 | 
				
			||||||
        if self.instance is not None:
 | 
					        if self.instance is not None:
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        missing = {
 | 
					        missing_items = {
 | 
				
			||||||
            field_name: self.missing_message
 | 
					            field_name: self.missing_message
 | 
				
			||||||
            for field_name in self.fields
 | 
					            for field_name in self.fields
 | 
				
			||||||
            if field_name not in attrs
 | 
					            if field_name not in attrs
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if missing:
 | 
					        if missing_items:
 | 
				
			||||||
            raise ValidationError(missing)
 | 
					            raise ValidationError(missing_items, code='required')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def filter_queryset(self, attrs, queryset):
 | 
					    def filter_queryset(self, attrs, queryset):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
| 
						 | 
					@ -167,7 +167,8 @@ class UniqueTogetherValidator(object):
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
        if None not in checked_values and qs_exists(queryset):
 | 
					        if None not in checked_values and qs_exists(queryset):
 | 
				
			||||||
            field_names = ', '.join(self.fields)
 | 
					            field_names = ', '.join(self.fields)
 | 
				
			||||||
            raise ValidationError(self.message.format(field_names=field_names))
 | 
					            message = self.message.format(field_names=field_names)
 | 
				
			||||||
 | 
					            raise ValidationError(message, code='unique')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __repr__(self):
 | 
					    def __repr__(self):
 | 
				
			||||||
        return unicode_to_repr('<%s(queryset=%s, fields=%s)>' % (
 | 
					        return unicode_to_repr('<%s(queryset=%s, fields=%s)>' % (
 | 
				
			||||||
| 
						 | 
					@ -204,13 +205,13 @@ class BaseUniqueForValidator(object):
 | 
				
			||||||
        The `UniqueFor<Range>Validator` classes always force an implied
 | 
					        The `UniqueFor<Range>Validator` classes always force an implied
 | 
				
			||||||
        'required' state on the fields they are applied to.
 | 
					        'required' state on the fields they are applied to.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        missing = {
 | 
					        missing_items = {
 | 
				
			||||||
            field_name: self.missing_message
 | 
					            field_name: self.missing_message
 | 
				
			||||||
            for field_name in [self.field, self.date_field]
 | 
					            for field_name in [self.field, self.date_field]
 | 
				
			||||||
            if field_name not in attrs
 | 
					            if field_name not in attrs
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if missing:
 | 
					        if missing_items:
 | 
				
			||||||
            raise ValidationError(missing)
 | 
					            raise ValidationError(missing_items, code='required')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def filter_queryset(self, attrs, queryset):
 | 
					    def filter_queryset(self, attrs, queryset):
 | 
				
			||||||
        raise NotImplementedError('`filter_queryset` must be implemented.')
 | 
					        raise NotImplementedError('`filter_queryset` must be implemented.')
 | 
				
			||||||
| 
						 | 
					@ -231,7 +232,9 @@ class BaseUniqueForValidator(object):
 | 
				
			||||||
        queryset = self.exclude_current_instance(attrs, queryset)
 | 
					        queryset = self.exclude_current_instance(attrs, queryset)
 | 
				
			||||||
        if qs_exists(queryset):
 | 
					        if qs_exists(queryset):
 | 
				
			||||||
            message = self.message.format(date_field=self.date_field)
 | 
					            message = self.message.format(date_field=self.date_field)
 | 
				
			||||||
            raise ValidationError({self.field: message})
 | 
					            raise ValidationError({
 | 
				
			||||||
 | 
					                self.field: message
 | 
				
			||||||
 | 
					            }, code='unique')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __repr__(self):
 | 
					    def __repr__(self):
 | 
				
			||||||
        return unicode_to_repr('<%s(queryset=%s, field=%s, date_field=%s)>' % (
 | 
					        return unicode_to_repr('<%s(queryset=%s, field=%s, date_field=%s)>' % (
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,19 +3,39 @@ from __future__ import unicode_literals
 | 
				
			||||||
from django.test import TestCase
 | 
					from django.test import TestCase
 | 
				
			||||||
from django.utils.translation import ugettext_lazy as _
 | 
					from django.utils.translation import ugettext_lazy as _
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from rest_framework.exceptions import _force_text_recursive
 | 
					from rest_framework.exceptions import ErrorDetail, _get_error_details
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ExceptionTestCase(TestCase):
 | 
					class ExceptionTestCase(TestCase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_force_text_recursive(self):
 | 
					    def test_get_error_details(self):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        s = "sfdsfggiuytraetfdlklj"
 | 
					        example = "string"
 | 
				
			||||||
        self.assertEqual(_force_text_recursive(_(s)), s)
 | 
					        lazy_example = _(example)
 | 
				
			||||||
        self.assertEqual(type(_force_text_recursive(_(s))), type(s))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertEqual(_force_text_recursive({'a': _(s)})['a'], s)
 | 
					        self.assertEqual(
 | 
				
			||||||
        self.assertEqual(type(_force_text_recursive({'a': _(s)})['a']), type(s))
 | 
					            _get_error_details(lazy_example),
 | 
				
			||||||
 | 
					            example
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        assert isinstance(
 | 
				
			||||||
 | 
					            _get_error_details(lazy_example),
 | 
				
			||||||
 | 
					            ErrorDetail
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertEqual(_force_text_recursive([[_(s)]])[0][0], s)
 | 
					        self.assertEqual(
 | 
				
			||||||
        self.assertEqual(type(_force_text_recursive([[_(s)]])[0][0]), type(s))
 | 
					            _get_error_details({'nested': lazy_example})['nested'],
 | 
				
			||||||
 | 
					            example
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        assert isinstance(
 | 
				
			||||||
 | 
					            _get_error_details({'nested': lazy_example})['nested'],
 | 
				
			||||||
 | 
					            ErrorDetail
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertEqual(
 | 
				
			||||||
 | 
					            _get_error_details([[lazy_example]])[0][0],
 | 
				
			||||||
 | 
					            example
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        assert isinstance(
 | 
				
			||||||
 | 
					            _get_error_details([[lazy_example]])[0][0],
 | 
				
			||||||
 | 
					            ErrorDetail
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -60,7 +60,7 @@ class TestNestedValidationError(TestCase):
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertEqual(serializers.get_validation_error_detail(e), {
 | 
					        self.assertEqual(serializers.as_serializer_error(e), {
 | 
				
			||||||
            'nested': {
 | 
					            'nested': {
 | 
				
			||||||
                'field': ['error'],
 | 
					                'field': ['error'],
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										101
									
								
								tests/test_validation_error.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								tests/test_validation_error.py
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,101 @@
 | 
				
			||||||
 | 
					from django.test import TestCase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from rest_framework import serializers, status
 | 
				
			||||||
 | 
					from rest_framework.decorators import api_view
 | 
				
			||||||
 | 
					from rest_framework.response import Response
 | 
				
			||||||
 | 
					from rest_framework.settings import api_settings
 | 
				
			||||||
 | 
					from rest_framework.test import APIRequestFactory
 | 
				
			||||||
 | 
					from rest_framework.views import APIView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					factory = APIRequestFactory()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ExampleSerializer(serializers.Serializer):
 | 
				
			||||||
 | 
					    char = serializers.CharField()
 | 
				
			||||||
 | 
					    integer = serializers.IntegerField()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ErrorView(APIView):
 | 
				
			||||||
 | 
					    def get(self, request, *args, **kwargs):
 | 
				
			||||||
 | 
					        ExampleSerializer(data={}).is_valid(raise_exception=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@api_view(['GET'])
 | 
				
			||||||
 | 
					def error_view(request):
 | 
				
			||||||
 | 
					    ExampleSerializer(data={}).is_valid(raise_exception=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestValidationErrorWithFullDetails(TestCase):
 | 
				
			||||||
 | 
					    def setUp(self):
 | 
				
			||||||
 | 
					        self.DEFAULT_HANDLER = api_settings.EXCEPTION_HANDLER
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def exception_handler(exc, request):
 | 
				
			||||||
 | 
					            data = exc.get_full_details()
 | 
				
			||||||
 | 
					            return Response(data, status=status.HTTP_400_BAD_REQUEST)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        api_settings.EXCEPTION_HANDLER = exception_handler
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.expected_response_data = {
 | 
				
			||||||
 | 
					            'char': [{
 | 
				
			||||||
 | 
					                'message': 'This field is required.',
 | 
				
			||||||
 | 
					                'code': 'required',
 | 
				
			||||||
 | 
					            }],
 | 
				
			||||||
 | 
					            'integer': [{
 | 
				
			||||||
 | 
					                'message': 'This field is required.',
 | 
				
			||||||
 | 
					                'code': 'required'
 | 
				
			||||||
 | 
					            }],
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def tearDown(self):
 | 
				
			||||||
 | 
					        api_settings.EXCEPTION_HANDLER = self.DEFAULT_HANDLER
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_class_based_view_exception_handler(self):
 | 
				
			||||||
 | 
					        view = ErrorView.as_view()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        request = factory.get('/', content_type='application/json')
 | 
				
			||||||
 | 
					        response = view(request)
 | 
				
			||||||
 | 
					        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
 | 
				
			||||||
 | 
					        self.assertEqual(response.data, self.expected_response_data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_function_based_view_exception_handler(self):
 | 
				
			||||||
 | 
					        view = error_view
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        request = factory.get('/', content_type='application/json')
 | 
				
			||||||
 | 
					        response = view(request)
 | 
				
			||||||
 | 
					        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
 | 
				
			||||||
 | 
					        self.assertEqual(response.data, self.expected_response_data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestValidationErrorWithCodes(TestCase):
 | 
				
			||||||
 | 
					    def setUp(self):
 | 
				
			||||||
 | 
					        self.DEFAULT_HANDLER = api_settings.EXCEPTION_HANDLER
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def exception_handler(exc, request):
 | 
				
			||||||
 | 
					            data = exc.get_codes()
 | 
				
			||||||
 | 
					            return Response(data, status=status.HTTP_400_BAD_REQUEST)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        api_settings.EXCEPTION_HANDLER = exception_handler
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.expected_response_data = {
 | 
				
			||||||
 | 
					            'char': ['required'],
 | 
				
			||||||
 | 
					            'integer': ['required'],
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def tearDown(self):
 | 
				
			||||||
 | 
					        api_settings.EXCEPTION_HANDLER = self.DEFAULT_HANDLER
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_class_based_view_exception_handler(self):
 | 
				
			||||||
 | 
					        view = ErrorView.as_view()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        request = factory.get('/', content_type='application/json')
 | 
				
			||||||
 | 
					        response = view(request)
 | 
				
			||||||
 | 
					        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
 | 
				
			||||||
 | 
					        self.assertEqual(response.data, self.expected_response_data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_function_based_view_exception_handler(self):
 | 
				
			||||||
 | 
					        view = error_view
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        request = factory.get('/', content_type='application/json')
 | 
				
			||||||
 | 
					        response = view(request)
 | 
				
			||||||
 | 
					        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
 | 
				
			||||||
 | 
					        self.assertEqual(response.data, self.expected_response_data)
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user