I am using django class based view and rest framework
object = self.get_object()
In Detail view if object does not exist and i do get request like
/user/10
then i get this response
{"detail": "not found"}
Now i want to customize that response
like
try:
obj = self.get_object()
except:
raise Exception("This object does not exist")
But thats not working
We can implement a custom exception handler function that returns the custom response in case the object does not exist.
In case a object does not exist, Http404 exception is raised. So, we will check if the exception raised is Http404 and if that is the case, we will return our custom exception message in the response.
from rest_framework.views import exception_handler
from django.http import Http404
def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)
if isinstance(exc, Http404):
custom_response_data = {
'detail': 'This object does not exist.' # custom exception message
}
response.data = custom_response_data # set the custom response data on response object
return response
After defining our custom exception handler, we need to add this custom exception handler to our DRF settings.
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'my_project.my_app.utils.custom_exception_handler'
}
You could create a custom exception class as below, and it would raise APIException with a custom message and custom status_code
from rest_framework.serializers import ValidationError
from rest_framework import status
class CustomAPIException(ValidationError):
"""
raises API exceptions with custom messages and custom status codes
"""
status_code = status.HTTP_400_BAD_REQUEST
default_code = 'error'
def __init__(self, detail, status_code=None):
self.detail = detail
if status_code is not None:
self.status_code = status_code
and in your views,
from rest_framework import status
try:
obj = self.get_object()
except:
raise CustomAPIException("This object does not exist", status_code=status.HTTP_404_NOT_FOUND)
The response will be like this
{"detail": "This object does not exist"}
NOTE
the detail parameter of CustomAPIException class takes str,list and dict objects. If you provide a dict object, then it will return that dict as exception response
UPDATE
As #pratibha mentioned, it's not possible to produce desired output if we use this exception class in Serializer's validate() or validate_xxfieldName() methods.
Why this behaviour ?
I wrote a similar answer in SO, here Django REST Framework ValidationError always returns 400
How to obtain desired output within the serializer's validate() method?
Inherit CustomAPIException from rest_framework.exceptions.APIException instead of from rest_framework.serializers.ValidationError
ie,
from rest_framework.exceptions import APIException
class CustomAPIException(APIException):
# .... code
Related
How can I return a different error code with django rest framework's serializers?
I have in my serializer.py file:
def create(self, validated_data):
if 'Message' not in validated_data:
# If message is blank, don't create the message
return PermissionDenied()
But when I do that, it just returns 201 with the body {"deleted":null} instead of returning a 403 error.
How can I make it return the 403 error?
You can override the validate_message method as follow:
from rest_framework.exceptions import ValidationError
def validate_message(self, message):
if not message:
raise ValidationError('error message here')
return message
Note that the ValidationError will return a 400 Bad Request status code which is better for when a required field is missing from the POST data
#Marcell Erasmus's answer is nice, but it's not covered the status code part (how to return a HTTP 403 status code)
First, you need to add a Custom exception class as below,
from rest_framework import exceptions
from rest_framework import status
class CustomAPIException(exceptions.APIException):
status_code = status.HTTP_403_FORBIDDEN
default_code = 'error'
def __init__(self, detail, status_code=None):
self.detail = detail
if status_code is not None:
self.status_code = status_code
and use the class wherever you want by ,
if some_condition:
raise CustomAPIException({"some": "data"})
One of the most advantage of this particular class is that you could raise API exception with custom status codes by specifying the status_code parameter
Ex.
if some_condition:
raise CustomAPIException({"some": "data"},status_code=status.HTTP_409_CONFLICT)
I have written
class CustomApiException(APIException):
#public fields
detail = None
status_code = None
# create constructor
def __init__(self, status_code, message):
#override public fields
CustomApiException.status_code = status_code
CustomApiException.detail = message
CustomApiException.message = message
which is working for APIView but for TemplateView it gives error. What is the proper way to write custom API Exception which will work for both views.
In your views define the template view as following
from django.http import HttpResponseForbidden
#rest of the views
class yourview(TemplateView):
def get(request):
try:
#business logic
except CustomApiException:
return HttpResponseForbidden()
This will raise a Error 403
If you want to render your own template instead of raising HttpResponseForbidden, you could pass the exception variables to the view in following way and write the response of the view appropriately
class yourview(TemplateView):
def get(request):
try:
#business logic
except CustomApiException as e:
status_code = e.status_code
detail = e.message
message = e.message
#Handle the response here.
I am using Django Rest Framework 3.2.3 (DRF) and Django Rest Framework JWT 1.7.2 (DRF-JWT, https://github.com/GetBlimp/django-rest-framework-jwt) to create Login tokens.
I need to change the Status Code for invalid credentials when issuing a JWT from 400 to 202 (FYI: my client can't read the body of non-200 responses). I use a custom exception handler as described by Django Rest Framework to achieve it: http://www.django-rest-framework.org/api-guide/exceptions/#custom-exception-handling
restapi/custom_exception.py
from rest_framework.views import exception_handler
from rest_framework import status
def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)
print ('Exception raised: ' + str(response.status_code))
# Now add the HTTP status code to the response.
if response is not None:
if response.status_code != status.HTTP_403_FORBIDDEN:
response.status_code = status.HTTP_202_ACCEPTED
return response
And in the config:
'EXCEPTION_HANDLER': 'restapi.custom_exception.custom_exception_handler',
The DRF-JWT should raise ValidationError, when invalid credentials are used. I still get a 400 Bad Request response code, when posting invalid credentials to JWT token-auth interface.
With every other DRF interface I am getting the 202 status code as expected.
How can I get DRF-JWT to use the custom exception handler for their ValidationErrors?
Why we are not able to use custom exception handler here?
This is happening because raise_exception flag has not been passed to the JSONWebTokenSerializer when calling .is_valid() on it. ( JSONWebTokenSerializer is the serializer class used to validate a username and password.)
DRF-JWT source code for post() method:
def post(self, request):
serializer = self.get_serializer(
data=get_request_data(request)
)
if serializer.is_valid(): # 'raise_exception' flag has not been passed here
user = serializer.object.get('user') or request.user
token = serializer.object.get('token')
response_data = jwt_response_payload_handler(token, user, request)
return Response(response_data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Now, we can see that raise_exception flag has not been passed to is_valid(). When this happens, ValidationError is not raised thereby resulting in non-execution of your custom_exception_handler code.
As per the DRF section on Raising an exception on invalid data:
The .is_valid() method takes an optional raise_exception flag that
will cause it to raise a serializers.ValidationError exception if
there are validation errors.
These exceptions are automatically dealt with by the default exception
handler that REST framework provides, and will return HTTP 400 Bad
Request responses by default.
SOLUTION:
If you pass the raise_exception flag as True when calling the .is_valid() function, the code for custom_exception_handler will be executed.
You will need to create a CustomObtainJSONWebToken view which will inherit from the default ObtainJSONWebToken view. In this, we will override the .post() method to pass the raise_exception flag. Then will specify this view in our urls.
my_app/views.py
from rest_framework_jwt.views import ObtainJSONWebToken
class CustomObtainJSONWebToken(ObtainJSONWebToken):
def post(self, request):
serializer = self.get_serializer(
data=get_request_data(request)
)
serializer.is_valid(raise_exception=True) # pass the 'raise_exception' flag
user = serializer.object.get('user') or request.user
token = serializer.object.get('token')
response_data = jwt_response_payload_handler(token, user, request)
return Response(response_data)
urls.py
# use this view to obtain token
url(r'^api-token-auth/', CustomObtainJSONWebToken.as_view())
I think maybe a cleaner way to do it that doesn't involve overriding the post method is
serializers.py
from rest_framework_jwt import serializers as jwt_serializers
class JSONWebTokenSerializer(jwt_serializers.JSONWebTokenSerializer):
"""
Override rest_framework_jwt's ObtainJSONWebToken serializer to
force it to raise ValidationError exception if validation fails.
"""
def is_valid(self, raise_exception=None):
"""
If raise_exception is unset, set it to True by default
"""
return super().is_valid(
raise_exception=raise_exception if raise_exception is not
None else True)
views.py
from rest_framework_jwt import views as jwt_views
from .serializers import JSONWebTokenSerializer
class ObtainJSONWebToken(jwt_views.ObtainJSONWebToken):
"""
Override the default JWT ObtainJSONWebToken view to use the custom serializer
"""
serializer_class = JSONWebTokenSerializer
urls.py
from django.conf.urls import url
from .views import ObtainJSONWebToken
urlpatterns = [
...
url(r'^api-token-auth/', ObtainJSONWebToken.as_view(), name='jwt-create'),
]
I am not sure how can I catch an Exception in my django app.
I have a view, where I use my own class object which uses Requests framework to make remote API requests.
I need to catch Timeout exception inside my class code. When Timeout exception appears I need to return custom error page. My class code:
try:
response = requests.request(
method=method, url=self.url, auth=self.auth, timeout=3, verify=False, **kwargs
)
except requests.Timeout:
return HttpResponse('API connection timeout')
return response
However, in my django view code, I use something like:
user_details = auth_client['auth/users/%s/details' % (request.POST.get('username'))].get().json()['result']
so, when I try to return HttpResponse from class code, I get error:
'HttpResponse' object has no attribute 'json'
I don't know how can I "omit" code from view and show HttpResponse directly. Any suggestions?
You shouldn't be trying to return an HttpResponse in your requests class. Instead of catching the exception and replacing it with an unwanted HttpResponse, let it propagate and catch it in the view:
try:
user_details = auth_client['auth/users/%s/details' % (request.POST.get('username'))].get().json()['result']
except requests.Timeout:
return HttpResponse('whatever')
Edit OK, so here's another solution. You say that this is a class: so you can define a method, say get_user_details, which either returns the actual JSON, or none.
def get_user_details(username):
try:
user_details = self['auth/users/%s/details' % 'username'].get()
except requests.Timeout:
return []
return user_details.json()['result']
so now in your view you just call auth_client.get_user_details(request.POST['username']).
I’m trying to create a custom error response from the REST Django framework.
I’ve included the following in my views.py,
from rest_framework.views import exception_handler
def custom_exception_handler(exc):
"""
Custom exception handler for Django Rest Framework that adds
the `status_code` to the response and renames the `detail` key to `error`.
"""
response = exception_handler(exc)
if response is not None:
response.data['status_code'] = response.status_code
response.data['error'] = response.data['detail']
response.data['detail'] = "CUSTOM ERROR"
return response
And also added the following to settings.py.
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.AllowAny',
),
'EXCEPTION_HANDLER': 'project.input.utils.custom_exception_handler'
}
Am I missing something as I don’t get the expected response. i.e a custom error message in the 400 API response.
Thanks,
As Bibhas said, with custom exception handlers, you only can return own defined errors when an exception is called. If you want to return a custom response error without exceptions being triggered, you need to return it in the view itself. For example:
return Response({'detail' : "Invalid arguments", 'args' : ['arg1', 'arg2']},
status = status.HTTP_400_BAD_REQUEST)
From the documentation -
Note that the exception handler will only be called for responses generated by raised exceptions. It will not be used for any responses returned directly by the view, such as the HTTP_400_BAD_REQUEST responses that are returned by the generic views when serializer validation fails.