I'm using Django and DjangoRestFramework for my project and I'm facing some kind of "issue".
While DRF exceptions are properly returned through the response of the HTTP request, Django ones aren't.
For instance if I raise an IntegrityError from the Django part with, for instance, a duplicate key error (let's say I create 2 records with same unique primary key), I only get a "500 Server Error" in the response of my HTTP request.
How can we propagate exceptions from Django to DRF by default ? With an appropriate HTTP status code ?
The only way I found is to write a custom exception handler for DRF which tests the exception type like so :
from django.db import IntegrityError
from rest_framework.views import exception_handler
def custom_exception_handler(exc, context):
response = exception_handler(exc, context)
if isinstance(exc, IntegrityError):
# Do some custom thing on the Response object before returning it
return response
Related
I want to create custom exceptions in DRF. As the DRF documentation states that i can do that by creating a user defined class and inheriting from APIException.
That works well but the issue is my application require me to send some type of custom codes along with the HTTP status codes.
For example, when an error occurred while creating a ProductCatalog it should return something like this along with HTTP status code.
{"code": 4026, "message": "Unable to create catalog"}
Such thing is required because although my API worked correctly so the http status code will be 200 but there were some business logic that was not fulfilled hence i need to return some sort of custom code and message to let the client application handle it accordingly.
Any kind of help will be appreciated.
You can consider creating a custom exception handler, along with a custom exception. Like this:
First create exception handler which will handle errors for DRF:
# custom handler
from rest_framework.views import exception_handler
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)
# Now add the HTTP status code to the response.
if response is not None:
response.data['status_code'] = response.default_code
return response
Then update the settings.py with that error
# settings.py
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'my_project.my_app.utils.custom_exception_handler'
}
Finally, create a custom exception and use that when you need to raise Catalog creation error:
# Custom exception
from rest_framework.exceptions import APIException
class CatalogExeption(APIException):
status_code = 503
default_detail = 'Unable to create catalog'
default_code = '4026'
More information can be found regarding custom exception in documentation.
I figured it out myself. By going through the code i found that if you set default_detail to a dictionary, it will return that as it is.
In my case it would be something like this.
class ProductCatalogExeption(APIException):
status_code = 200 #or whatever you want
default_code = '4026'
# Custom response below
default_detail = {"code": 4026, "message": "Unable to create catalog"}
So when ProductCatalogException is raised it will return
{"code": 4026, "message": "Unable to create catalog"}
with HTTP Response code 200
For reference: https://github.com/encode/django-rest-framework/blob/master/rest_framework/exceptions.py
Is there a way to handle all the exceptions globally without using try-except block in django rest framework.
I want to convert html error page that django is throwing to a customised json object response.
I have created an exception.py file in my app
def custom_exception_handler(exc, context=None):
response = exception_handler(exc)
if isinstance(exc, HttpResponseServerError):
custom_response_data = {
'detail': 'Internal Server Error' # custom exception message
}
response.data = custom_response_data
return response
i have configured this in settings.py.
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10,
'EXCEPTION_HANDLER':'my_project.my_app.exceptions.custom_exception_handler'}
Since I came across with a similar situation that lead me to this question, I'll answer following the original question that is related to Django Rest Framework specifically and not just Django.
I understand that you want to handle raised exceptions from your views, globally, without having to define try/except blocks on each view module.
DRF allows you to define your own Custom Exception Handling mechanism (docs).
Here is an example:
At my_custom_except_handler.py:
import logging
from rest_framework.views import exception_handler
from django.http import JsonResponse
from requests import ConnectionError
def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first
response = exception_handler(exc, context)
# checks if the raised exception is of the type you want to handle
if isinstance(exc, ConnectionError):
# defines custom response data
err_data = {'MSG_HEADER': 'some custom error messaging'}
# logs detail data from the exception being handled
logging.error(f"Original error detail and callstack: {exc}")
# returns a JsonResponse
return JsonResponse(err_data, safe=False, status=503)
# returns response as handled normally by the framework
return response
As stated in the docs, the defined response object refers to:
The exception handler function should either return a Response object, or return None if the exception cannot be handled. If the handler returns None then the exception will be re-raised and Django will return a standard HTTP 500 'server error' response.
In other words, 'response' won't be None only when handling these exceptions docs:
Subclasses of APIException.
Django's Http404 exception.
Django's PermissionDenied exception.
If your custom handler returns None, then the exception will be handled 'normally' by the framework, returning typical 500 server error.
Finally remember to set the required key at settings.py:
REST_FRAMEWORK = {'EXCEPTION_HANDLER':
'my_project.my_app.my_custom_except_handler.custom_exception_handler'}
Hope it helps!
The definite answer to your question is no.
At least I don't know how to do it globally in Django, whereas global includes middleware exceptions).
Further, as request by #Shubham Kumar, the hook you need is process_exception and for an implementation check this SO post with the offical docs on how to activate it.
As stated in the Django docs:
request is an HttpRequest object. exception is an Exception object raised by the view function.
Django calls process_exception() when a view raises an exception. process_exception() should return either None or an HttpResponse object. If it returns an HttpResponse object, the template response and response middleware will be applied and the resulting response returned to the browser. Otherwise, default exception handling kicks in.
Again, middleware are run in reverse order during the response phase, which includes process_exception. If an exception middleware returns a response, the process_exception methods of the middleware classes above that middleware won’t be called at all.
Meaning that you will merely be able to hook into the view function and catch all those exceptions.
I have a similar issue as this question about validating data in the Django REST Framework outside of a serializer:
Raise Validation Error In Pre_Save Using Django Rest Framework
My code:
def pre_save(self, obj):
data = self.request.DATA['users']
for user in data:
if not user in allowed_users:
raise ParseError('An unpermitted user has been included')
From the trace it looks like it's trying to send the response but it fails with:
"" needs to have a value for field before this many-to-many relationship can be used.
UPDATE:
I moved raising the ParseError into a get_serializer_class() method like so:
def get_serializer_class(self):
if 'users' in self.request.DATA:
# make sure the users are allowed
data = self.request.DATA['users']
for user in data:
if not user in allowed_users:
raise ParseError(detail='Unpermitted user')
return serializer
And this raises the exception, however, it does not return it using the REST framework's JSON response. Rather I get the django stack trace and a 500 error, which is not good.
Thanks!
Have a look at APIView's handle_exception — this is where DRF processes exceptions raised during the request.
From the docs:
The default implementation handles any subclass of rest_framework.exceptions.APIException, as well as Django's Http404 and PermissionDenied exceptions, and returns an appropriate error response.
If you need to customize the error responses your API returns you should subclass this method.
So you need to override this to handle ParseError exceptions too.
Also check out the DRF docs on Exceptions.
I hope that helps.
When the exception is raised in the pre_save method(), post_save(), or even in a post() method for the viewclass, it was being handled correctly by Django-REST-Framework. Had I been using curl or similar, the error would have been returned correctly.
This actually is a bug in the browsable API, which is what I was using to test - sending the data using the "Raw data" form. When trying to render the html response, DRF apparently tries to capture the "context" of the post. In this case, it wanted the saved/completed post.
That did not exist, so a Django rendering error was being thrown and that confused me.
When testing using curl, the response was accurate.
Note that putting it in the get_serializer_class() like I did caused it to go outside of the DRF exception handler so Django rendered it correctly and showed the error was being thrown correctly.
I am having difficulty raising a validation error using the Django Rest Framework.
I have an owner field. The owner field needs to access the request object. The documentation suggests using the following approach:
def pre_save(self, obj):
obj.owner = self.request.user
My problem is how to raise a validation error if this code fails. I tried raising an error inside the pre_save method, but this doesn't work properly. It actually results in an HTML response coming back from Django, rather than a message from the Django Rest Framework.
Use the django rest framework exceptions. For example:
from rest_framework.exceptions import ParseError
...
parsed_data = self.parse(some_data)
if not parsed_data:
raise ParseError('Some error occurred')
Also note that you won't see a 404 in there, that's because it uses the django.http.Http404 exception. These are returned by the API in a nice way.
Note:
If you are doing a significant amount of validation you might want to look at placing your logic in the serializer.
I have built an API using Django Rest Framework, but I'm facing a problem with error logging, which won't displayed the original POST data sent from the AJAX call to the API. Now I know Rest Framework uses request.DATA to parse the POST data, but I'm struggling to get this data in the Django error log, which makes it hard to debug the live application.
As an example, I send the following POST call:
...
Request Payloadview source
{start:2014-07-16, end:2014-08-18}
end: "2014-08-18"
start: "2014-07-16"
And in the Django error log I get:
...
GET: No GET data
POST: No POST data
FILES: No FILES data
...
It would great if I could see in that error log which data was originally submitted via the AJAX call.
I have tried to customise the Rest Framework exception handler, but it doesn't to catch the 500 errors happening in Django.
There is a version which works for Django 1.9+ and Rest Framework 3.4
from rest_framework import permissions
from rest_framework.views import exception_handler
def custom_exception_handler(exc, context):
response = exception_handler(exc, context)
if response is None and context['request'].method not in permissions.SAFE_METHODS:
context['request']._request.POST = context['request'].data
return response
In settings.py :
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'core.exceptions.exception_handler',
}
The handler:
def exception_handler(exc, drf_request=None):
"""
Replace request.POST with drf_request.DATA so Sentry
can log request properly.
#param exc: Exception
#param drf_request: Request
#return: None
"""
response = views.exception_handler(exc)
if response is None and drf_request is not None \
and drf_request.method not in ['GET', 'HEAD', 'OPTIONS']:
request = get_request()
request.POST = drf_request.DATA
return response