I am using http response to handle exception in my website. I want to show proper message / validation during create and update data. But It shows HTTP responses like Bad Request , Internel server error. Here is my code:
from django import http
from rest_framework import viewsets
class SaleViewSet(viewsets.ModelViewSet):
def create(self, request, *args, **kwargs):
data = request.data
try:
// Some code
return http.JsonResponse(response)
except Exception as e:
logger.error(e)
return http.HttpResponseBadRequest(content=e)
In dialog box message it shows ,"Bad Request". Instead of "Bad Request" in dialog box message I want to show custom message.
I want to do ,
except Exception as e:
logger.error(e)
return http.HttpResponseBadRequest(My message)
You can make use of JsonResponse object
create a custom error response handler
from django.http import JsonResponse
def error_response(message, status, error=None):
response = dict()
response["error"] = error
response["detail"] = message
return JsonResponse(response, status=status)
in your views
except Exception as e:
logger.error(e)
# call custom error_response handler
message = 'My message'
return error_response(message=message, error=e, status=status.HTTP_400_BAD_REQUEST)
Related
In Flask, is there a way to check which error from errorhandler triggered before_request to be executed?
I want to avoid querying the database for users in 500 error pages in case it's a database-related error. Currently, I have a setup like this:
from flask import request, g, app
from my_app import db, generate_my_sitemap
skip_load_user = set()
#app.before_request
def load_user():
g.user = None
if request.endpoint in skip_load_user:
return
if "user_id" in session:
g.user = db.session.get(session["user_id"])
def add_to_skip_load_user(view):
skip_load_user.add(view.__name__)
return view
#app.route('/sitemap.xml')
#add_to_skip_load_user
def my_view_without_auth():
return generate_my_sitemap()
#app.errorhandler(404)
#app.errorhandler(500)
#add_to_skip_load_user
def http_errors(error):
return render_template('http_error.html')
I noticed that in a 404 error handler, endpoint is None, so it's possible to do this:
if not request.endpoint:
return
However, I only want to make an exception for 500 error pages, not 404 error pages. Is there a way to check if before_request was triggered by a 500 errorhandler?
Edit: I did a workaround by not loading user if endpoint is None on before_request, and then calling the function explicitly on error_handler
#app.before_request
def load_user():
...
if request.endpoint is None:
return
...
#app.errorhandler(404)
#app.errorhandler(500)
def http_errors(error):
if getattr(error, 'code', None) == 404:
load_user()
...
I'm starting to dive into DRF a little deeper of late, and I was wondering I would like to start customising the error messaging that gets return via the API for incorrect permissions, I'd like to wrap a little extra detail.
For example, if authentication credentials were not provided for an endpoint that is permission restricted, the API returns:
{
"detail": "Authentication credentials were not provided."
}
Which comes from line 171 from the rest_framework.exceptions: https://github.com/encode/django-rest-framework/blob/master/rest_framework/exceptions.py. Really, I'd like this to be consistent with the
{
"success": false,
"message": "Authentication credentials were not provided.",
"data": null
}
So, I assume I now need to begin customising my own exceptions.
How best should I go about doing this?
Perhaps it has some tie in with default_error_messages = {} inside the serializer ...
You can override DRF's default exception handler and JSON parser on your settings.py:
REST_FRAMEWORK = {
...
'EXCEPTION_HANDLER': 'helpers.exceptions.custom_exception_handler',
'DEFAULT_RENDERER_CLASSES': [
'helpers.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
]
}
And then it's just a matter of customizing how to handle your exceptions and how to render the responses:
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)
# Customize your exception handling here
return response
And you can use the custom JSON renderer in case you need to do any extra formatting on the response, in my case I had to add a "status_code" to the payload:
class JSONRenderer(BaseJsonRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
"""
Render `data` into JSON, returning a bytestring.
"""
<Base code from the original class...>
response = renderer_context.get('response')
if response and 200 <= response.status_code <= 299 and 'status_code' not in response.data:
response.data = Errors.success(response.data)
<Base code from the original class...>
My Errors.success(response.data) was just a simpler way to merge the success status code to the data.
There is a decorator solution that creates custom Response on each type of your exceptions:
# project/api/exceptions.py
from functools import wraps
from rest_framework import status
def handle_exceptions(func):
#wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except AuthCredentialsError as exc:
return Response(
{"message": exc.message},
status=status.HTTP_405_METHOD_NOT_ALLOWED,
)
return wrapper
# project/api/your_code.py
from project.api.exceptions import handle_exceptions
class SomeViewSet():
#handle_exceptions
def create(self, request, *args, **kwargs):
raise AuthCredentialsError("Authentication credentials were not provided")
I have a API endpoint where it will do input validation using rest_framework's serializer.is_valid() where it will return custom error message and response.
serializer = FormSerializer(data=data)
if not serializer.is_valid(raise_exception=False):
return Response({"Failure": "Error"}, status=status.HTTP_400_BAD_REQUEST)
Is it possible to populate validation errors without using the generic response provided by raise_exception=True? I am trying to avoid using the generic response as it will display all the validation errors if there are more than one error.
The response will be something like
return Response(
{
"Failure": "Error",
"Error_list": {"field1": "This field is required"}
},
status=status.HTTP_400_BAD_REQUEST
)
Create a Custom Exception class as,
from rest_framework.exceptions import PermissionDenied
from rest_framework import status
class MyCustomExcpetion(PermissionDenied):
status_code = status.HTTP_400_BAD_REQUEST
default_detail = "Custom Exception Message"
default_code = 'invalid'
def __init__(self, detail, status_code=None):
self.detail = detail
if status_code is not None:
self.status_code = status_code
Why I'm inherrited from PermissionDenied exception class ??
see this SO post -- Why DRF ValidationError always returns 400
Then in your serializer, raise exceptions as,
class SampleSerializer(serializers.ModelSerializer):
class Meta:
fields = '__all__'
model = SampleModel
def validate_age(self, age): # field level validation
if age > 10:
raise MyCustomExcpetion(detail={"Failure": "error"}, status_code=status.HTTP_400_BAD_REQUEST)
return age
def validate(self, attrs): # object level validation
if some_condition:
raise MyCustomExcpetion(detail={"your": "exception", "some_other": "key"}, status_code=status.HTTP_410_GONE)
return attrs
age and name are two fields of SampleModel class
Response will be like this
By using this method,
1. You can customize the JSON Response
2. You can return any status codes
3. You don't need to pass True in serializer.is_valid() method (This is not reccomended)
A simple way is to use one of the exception messages, eg NotFound. See docs
# views.py
from rest_framework.exceptions import NotFound
class myview(viewsets.ModelViewSet):
def perform_create(self, serializer):
raise NotFound("My text here")
That will return a 404 and change the response to your text
HTTP 404 Not Found
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
{
"detail": "my text here"
}
You can write custom error handler:
from rest_framework.views import exception_handler
def custom_exception_handler(exc, context):
response = exception_handler(exc, context)
if response is not None:
response.data['Failure'] = 'Error'
return response
How to send customised response if the unauthorised credentials were provided in django rest.
class StockList(APIView):
permission_classes = [IsAuthenticated]
def get(self,request):
stocks = Stock.objects.all()
serializer = StockSerializer(stocks,many=True)
return Response({'user': serializer.data,'post': serializer.data})
def post(self):
pass
Here when I Hit url by invalid credentials i get 401 error on development server.
But i want to send customised response on client using json.
any suggestions are welcomed.
Thank you.
You can use a custom exception handler to customized response of api exception.
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.status_code
if response.status_code == 401:
response.data['some_message'] = "Some custom message"
return response
Then in setting.py you have to add your custom error handler
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'my_project.path.to.custom_exception_handler'
}
Refs: docs
I currently have some code for a view based on the Django REST Framework.
Ive been using a customer exception class, but ideally I want to use the inbuilt Django REST exceptions.
From the code below I feel this probably not the best or cleanest way to utilize the REST Framework exceptions to its maximum.
Has anyone got any good examples where they are catching issues and returning them cleanly with the REST built in exceptions ?
class JSONResponse(HttpResponse):
def __init__(self, data, **kwargs):
content = JSONRenderer().render(data)
kwargs['content_type'] = 'application/json'
super(JSONResponse, self).__init__(content, **kwargs)
def queryInput(request):
try:
auth_token = session_id = getAuthHeader(request)
if not auth_token:
return JSONResponse({'detail' : "fail", "error" : "No X-Auth-Token Found", "data" : None}, status=500)
if request.method:
data = JSONParser().parse(request)
serializer = queryInputSerializer(data=data)
if request.method == 'POST':
if serializer.is_valid():
input= serializer.data["input"]
fetchData = MainRunner(input=input,auth_token=auth_token)
main_data = fetchData.main()
if main_data:
return JSONResponse({'detail' : "success", "error" : None, "data" : main_data}, status=201)
return JSONResponse({'detail' : "Unknown Error","error" : True, "data" : None}, status=500)
except Exception as e:
return JSONResponse({'error' : str(e)},status=500)
The Django REST framework provides several built in exceptions, which are mostly subclasses of DRF's APIException.
You can raise exceptions in your view like you normally would in Python:
from rest_framework.exceptions import APIException
def my_view(request):
raise APIException("There was a problem!")
You could also create your own custom exception by inheriting from APIException and setting status_code and default_detail. Some of the built in ones are: ParseError, AuthenticationFailed, NotAuthenticated, PermissionDenied, NotFound, NotAcceptable, ValidationError, etc.
These will then get converted to a Response by the REST Framework's exception handler. Each exception is associated with a status code that is added to the Response. By default the exception handler is set to the built in handler:
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'
}
But you can set it to your own custom exception handler if you want to convert the exceptions yourself by changing this in your settings.py file:
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'my_project.my_app.utils.custom_exception_handler'
}
And then create the custom handler in that location:
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.status_code
return response
You can use the build in DRF exception, just import and raise
from rest_framework.exceptions import ParseError
...
raise ParseError('I already have a status code!')