I'm calling Django from within another application.
While debugging, if a Django exception occurs, the (html) response body is being picked up and then wrapped in an exception generated in the calling app.
This makes it a real pain to determine what the problem was as I need to wade through CSS, Html, Js, etc.
Is there any way to get Django to only output an exception message and a stack trace as plain text?
You can write your own exception-handling middleware class. See the documentation for the process_exception middleware method:
process_exception(self, request, exception)
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 response will be returned to the browser. Otherwise, default exception handling kicks in.
So I think you need to write something like this:
import traceback
from django.http import HttpResponse
class PlainTextExceptionMiddleware(object):
def process_exception(self, request, exception):
return HttpResponse(traceback.format_exc(), "text/plain")
and then add the class to your MIDDLEWARE_CLASSES setting.
Just set the DEBUG_PROPAGATE_EXCEPTIONS in your settings:
If set to True, Django’s normal exception handling of view functions
will be suppressed, and exceptions will propagate upwards. This
can be useful for some test setups, and should never be used on a live site.
See the Django docs
Related
I'm using Django in debug mode in dev environment.
When some views fail, it returns the debug page (not debug_toolbar, just the page with list of installed apps, environment variables, stack trace, ...)
In my middleware, I have some cases (specific URLs, specific users, ...) where I want to remove this data and just return the raw response.
How should I do that?
currently my best idea is to just:
response.data = {}
return response
But I'm not sure if it's the proper way to do that, whether it covers all cases and so on. I just want to use a middleware to control in some cases and avoid the DEBUG mode for them.
You may use exception middleware. See Django docs for more info.
Here is a sample implementation of the exception middleware:
class ExceptionHandlerMiddleware:
def __init__(self, get_response):
# if DEBUG is False we do not need this middleware
if not settings.DEBUG:
raise MiddlewareNotUsed
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
def process_exception(self, request, exception):
if request.user.is_authenticated: #any custom logic based on request and/or exception
#returning None kicks in default exception handling
#i.e. it will show full debug info page if settings.DEBUG is True
return None
else:
#returning HttpResponse will force applying template response and response
#middleware and the resulting response will be returned to the browser
return HttpResponse('Something went wrong')
Since Django 3.1 you may also use a custom error reporter class by defining the DEFAULT_EXCEPTION_REPORTER setting. The custom error reporter class needs to inherit from django.views.debug.ExceptionReporter and you may override get_traceback_data() to implement custom logic. See Django docs for more info.
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.
Django middleware have a process_exception hook which can be used to capture exception and handler.
But there is some problem while using Django restframe work
class ExceptionHandler(MiddlewareMixin):
#staticmethod
def process_exception(request, exception):
if isinstance(exception, ValidationError):
return Response(data=exception.messages, status=status.HTTP_400_BAD_REQUEST)
For example, I try to use above middleware to capture the ValidationError and return HTTP 400
But it will not work and raise below error
AssertionError: .accepted_renderer not set on Response
It turns out that the rest-framework view layer will add a .accepted_renderer to the response.
If I handle the exception outside view. This attribute will be missed and cause another exception.
So my question is: Is it wrong to handle exception in middleware when using django rest-framework?
What is the correct way to do ?
A better way to do this in Django Rest framework is to create a custom exception handler and replace the default exception handler with your custom handler. For more details on it you can check out the official documentation: http://www.django-rest-framework.org/api-guide/exceptions/#custom-exception-handling
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.
Django-Tastypie has ImmediateHttpResponse exception which allow to return to the client an immediate response:
raise ImmediateHttpResponse(response='a message')
Django has Http404, but i couldn't find a more universal exception like ImmediateHttpResponse.
What technique do you use to return to client an immediate 400 response?
For example having model:
class Subscriber(Model):
def delete(self, *args, **kwargs):
raise ImmediateHttpResponse('Deleting subcribers is not allowed!')
and trying to delete an object would return to the client a 400 response with the given message.
I think what you want is a middleware which implements a process_exception.
It works like this: you raise an Exception on you view (e.g. ImmediateHttpResponse). If the exception is catched by your middleware, the middleware returns a response, in your case the with a status 400. If you don't catch the exception, Django will catch it in the end, returning a status 500 (server error), or other status.
The simplest example is a middleware that catches Http404. If you catch the exception Http404 in your middleware, you can return any response you want (status 200, 400, etc). If you don't catch (i.e. the process_exception method of you middleware returns None), Django will catch it for you and return a Response with status 404. This is actually the standard way of having your own custom exceptions that you want to respond in a custom way.
It is not an exception, but there is HttpResponseBadRequest, which is your normal HttpResponse but with 400.
The Http404 exception is simply an empty Exception class, there is nothing special about it:
class Http404(Exception):
pass
So you can easily create your own if you must:
class Http400(Exception):
pass
ImmediateHttpResponse isn't that much different than Http404 in that its also a generic Exception but with a specific property, which makes it more like HttpResponseBadRequest:
class ImmediateHttpResponse(TastypieError):
"""
This exception is used to interrupt the flow of processing to immediately
return a custom HttpResponse.
Common uses include::
* for authentication (like digest/OAuth)
* for throttling
"""
_response = HttpResponse("Nothing provided.")
def __init__(self, response):
self._response = response
#property
def response(self):
return self._response