I want to write django custom error handler as in flask custom error handler .
Suppose i have 100 api's which gets the same error everytime let's say
json.decoder.JSONDecodeError
Sample code
def post(self, request):
if not request: return Response({"message": "Please enter credentials"})
input_param = json.load(request)
print(input_param)
return "something"
The above code will return json.decoder.JSONDecodeError if no params are passed in post request .
In flask this can be handled by writing custom error handler like
#app.errorhandler(json.decoder.JSONDecodeError)
def handle_marshmallow_validaton_errors(err):
return jsonify({"error": "Bad request"}), 400
In django is there any way we can write custom error handlers
Thanks in advance
I think you can use DRF custom exception handler https://www.django-rest-framework.org/api-guide/exceptions/#custom-exception-handling.
you can use it to check the spawn exception and return a proper response.
Related
when I try to delete Django model object that is ForeignKey in another model with option on_delete=models.PROTECT, the error returned is the normal Django 500 Exception HTML web page, how to make Django rest frame work return json response with the error, is there a way for DRF to do that by default or it should be customized?
Raising 500 Exception is the expected behavior. You have to customize to have a custom error. You can refer to this similar question
You can catch the error by using ProtectedError expceptions.
Like this-
from django.db.models import ProtectedError
try:
# Write here your code
except ProtectedError:
# Return your customer validation error
To complete MSI Shafik answer, specifically for django-rest-framework, you can override the destroy method to catch the exception and return protected objects in a response to handle an error message in your application.
For example:
from django.db.models.deletion import ProtectedError
def destroy(self, request, *args, **kwargs):
try:
return super().destroy(request, *args, **kwargs)
except ProtectedError as protected_error:
protected_elements = [
{"id": protected_object.pk, "label": str(protected_object)}
for protected_object in protected_error.protected_objects
]
response_data = {"protected_elements": protected_elements}
return Response(data=response_data, status=status.HTTP_400_BAD_REQUEST)
The get_object_or_404 shortcut function in Django when encountering an exception gives a very nice error message in the following format:
'No %s matches the given query.' % queryset.model._meta.object_name)
However, while using this inside a DRF 3.X Class based view, the final 404 response data has a very stripped down version, which is as follows:
{"detail": "Not found."}
As is evident, the DRF message is very generic with no information about the Model name.I am assuming that the DRF NotFound Exception class defined here strips down the message to its current bare minimum.
How can I get the original nice error message that Django returns in-spite of using it within a DRF Class Based View ?
The default exception handler for an APIView class is decided by the get_exception_handler method. Ie,
def get_exception_handler(self):
"""
Returns the exception handler that this view uses.
"""
return self.settings.EXCEPTION_HANDLER
In other words the handler function that an APIView class use by default is rest_framework.views.exception_handler(Which is specified on the default settings).
As per the DRF Doc,
By default DRF handle the REST framework APIException, and
alsoDjango's built-in Http404 and PermissionDenied exceptions.
Since in this case you want to customize the error message only for the Http404, you can create your own handler like this.
ie,
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 overide the error message.
if response is not None and isinstance(exc, Http404):
# set the custom message data on response object
response.data['detail'] = exc.args[0] # exc.args[0] will have the message text,
return response
Then place this custom handler in your project settings.
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'my_project.my_app.utils.custom_exception_handler'
}
Note: This solution is not tested.
I'm using Python 3.6 with Django 1.11.9 and rest_framework 3.6.2.
I have a view (APIView) which can only be accessed by some users who successfully pass a given HasUnlimitedAccesPermission check. In case of failure to pass the latter, I raise a PermissionDenied with a detailed message of my choice about the error to pass on to front-end. So far so good, all of that is easily achievable by applying the HasUnlimitedAccessPermission to my view thanks to the "permission_classes" decorator (yeah, I'm using a function_based view, here).
Now, what I would like to achieve is passing an additional attribute to my error response JSON (when the user fails to pass the permission test). This attribute would be an "error_id" attribute that would give the front-end developer the ability to adapt the error display depending on the "error_id" value. An example of the response JSON would be :
{
"error": "To enjoy this feature, go pick one of our pay offer!",
"error_id": "no_unlimited_access"
}
Any idea on how to achieve this?
Your problem can be solved using a middleware issue.
I would recommend you to build a custom middleware. So the custom middleware can help you to create the response as per your need. Just plug the middleware in your django application.
You can read more about it in this blog or this blog
Define a Custom Exception class as,
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 use in views as,
from rest_framework import status
def my_view(request):
if some_condition:
error_msg = {
"error": "To enjoy this feature, go pick one of our pay offer!",
"error_id": "no_unlimited_access"
}
raise CustomAPIException(error_msg)
OK, thanks to Bear Brown's comment and Jerin Peter George's answer (completed by rest_framework source code), I've done as follows:
1) Created a custom PermissionDenied exception:
class CustomPermissionDenied(PermissionDenied):
def __init__(self, detail=None, code=None, error_id=None):
super().__init__(detail=detail, code=code)
self.error_id = error_id
which is raised in HasUnlimitedAccessPermission, for instance, this way:
raise CustomPermissionDenied(detail=_("To use this feature, subscribe to one of our plans."),
error_id="no_unlimited_access")
2) In a custom_exception_handler (that I already had for other purpose) I added the lines between the ellipses
def custom_exception_handler(exc, context):
...
error_id = getattr(exc, "error_id", None)
if error_id is not None:
new_response_data["error_id"] = error_id
...
response.data = new_response_data
return response
And here it is, I have the error response format I was looking for. Thanks to all of you for your help!
There are some URLs which are handled by my Pyramid application. When an unauthenticated user tries to open any URL then the user is redirected to login form:
def forbidden(request):
if request.user.keyname == 'guest':
return HTTPFound(location=request.route_url('auth.login',))
request.response.status = 403
return dict(subtitle=u"Access denied")
config.add_view(forbidden, context=HTTPForbidden, renderer='auth/forbidden.mako')
But for some urls (routes) I have to return not the login form, but a 401 Unauthorized status code with WWW-Authenticate header. How I can setup my routes to accomplish this? I am guessing that I have to use route_predicate.
You can wrap those two errors into your custom one, and raise that in place of both. Then you can handle your exception and run the desired scenario for each error. Here is an example:
class YourError(HTTPException):
def __init__(self, error):
super(YourError, self).__init__()
self.error = error
def handle(self, request):
if error is HTTPForbidden or is instance(error, HTTPForbidden):
return self._handle403(request)
elif error is HTTPUnauthorized or is instance(error, HTTPUnauthorized):
return self._handle401(request)
def _handle403(self, request):
# do your stuff for 403
def _handle401(self, request):
# do your stuff for 401
# ...modify your view
def forbidden(context, request):
return context.handle(request)
Then edit your view configuration:
config.add_view(forbidden, context=YourError, renderer='auth/forbidden.mako')
And then in other views where you need to return 403 or 401, go this way:
def another_view(request)
...
raise YourError(HTTPForbidden)
# or
raise YourError(HTTPUnauthorized)
...
Then you will only need to implement your handling logic inside the YourError class.
I came across some discussion on the Pyramid issues list that looks like it might address this problem.
That said, I think what you might be able to do is override the Forbidden view using hooks and create a custom exception handler. Then in there I think you could differentiate between 403 and 401 errors and redirect / display an appropriate response message and customize the response however you need.
I have like this:
try:
#bunch of code
except:
return HttpResponse....
I want to send an error to client and to print stack trace on console. How can i do that?
You can do something like this
import traceback
from django.http import HttpReponse
def view(request):
try:
#throw exception
except:
tb = traceback.format_exc()
return HttpResponse(tb)
# normal flow
You can create a custom middleware class where you can catch all exceptions:
class ErrorMiddleware(object):
def process_exception(self, request, exception):
# send error
return HttpResponse(...) # or None
and put it on first place to MIDDLEWARE_CLASSES tuple in settings.py.
From middleware process_exception docs:
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.
You might want to enable logging of all exceptions How do you log server errors on django sites
and make a custom 500 django error page https://docs.djangoproject.com/en/dev/topics/http/views/#the-500-server-error-view
and you should also make a 500 error page at web server level (apache / nginx) just in case the framework doesn't work.
That way you can "catch" all errors and show a nice error message to the client.