getting access to the exception in Django 500.html? - django

How can I get access to the exception details in 500.html?

Easiest way is to write a middleware that overrides process_exception.
http://docs.djangoproject.com/en/dev/topics/http/middleware/#process-exception
class ProcessExceptionMiddleware(object):
def process_exception(self, request, exception):
response = direct_to_template(request, "my_500_template", {'exception': exception})
response.status_code = 500
return response

You can subclass django.core.handlers.base.BaseHandler, or better one of the implementations like django.core.handlers.wsgi.WSGIHandler, and change the handle_uncaught_exception(self, request, resolver, exc_info) method. The last argument is the exception info as returned by sys.exc_info. In the case of WSGI, you would define the custom handler in your WSGI file, for instance.
Simply overwriting handler500 in your URLconf won't work because that function does not receive any information about the actual exception.

Related

Global Exception Handling in Django-rest-framework

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.

Convert HttpError to JSON

In my Django app in several places I do:
raise HttpError(
message="Bla bla bla",
status=400,
)
Is there some way to intercept all HttpErrors being raised and wrap them in a nice JSON body? Something like:
{
"status": "error",
"message":" "Bla bla bla"
}
Custom middleware is the place where you should do the job.
Create a middleware class, define process_exception() method; in case of HttpError return a json response, otherwise return None (do not re-raise the exception):
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.
Here's an example:
class CustomMiddleware():
def process_exception(self, request, exception):
if not isinstance(exception, HttpError):
return None
response = json.dumps({'status': exception.status,
'message': exception.message})
return HttpResponse(response,
content_type='application/json; charset=utf-8')
Also, don't forget to add your middleware to MIDDLEWARE_CLASSES setting.
Hope that helps.
Create or find middleware that will catch the exception and check the URI or view to see if it should convert the exception or reraise it.

How to catch specific error in any template in Django?

I want to catch custom exception that I raise in a method called from base.html. When it happens, I need to redirect user to another page. Where is the best place to do this?
Below is simplified example of code to show the problem
base.html:
{% if something %}{{ widget_provider.get_header }}{% endif %}
widget_providers.py
class SpecificWidgetProvider(BaseWidgetProvider):
def get_header(self):
if self.user.is_anonimous():
raise AuthenticationRequired()
return third_party_api.get_top_secret_header_widget()
AuthenticationRequired exception may be raised by many parts of my site (most time in views), and I want to keep the handler in one place. So I created a middleware to catch this type of exception.
middleware.py
def process_exception(request, exception):
if isinstance(exception, AuthenticationRequired):
return redirect('accounts:login')
But I've found that class based views may leave the template rendering phase for later. They just return TemplateResponse instance, and Django's request handler (get_response() in django.core.handlers.base) calls response.render() outside of all middlewares stack. So now my middleware can't catch the exception. Maybe it is better to call get_header() from context processor, but this does not help too.
I solved the problme by prefetching the data within try/except in process_template_response() in the same middleware, and moved error processing to common private method. This allows me to keep error processing in the same place.
def process_template_response(self, request, response)
try:
widget_provider.get_header()
except AuthenticationRequired:
response = self._process_auth_required()
# Bind render() method that just returns itself, because we must
# return TemplateResponse or similar here.
def render(self):
return self
response.render = types.MethodType(render, response)
return response
def process_exception(self, request, exception):
if isinstance(exception, AuthenticationRequired):
return self._process_auth_required()
def _process_auth_required(self):
return redirect('accounts:login')

Execute code in Django after response has been sent to the client

In my Django application I want to keep track of whether a response has been sent to the client successfully. I am well aware that there is no "watertight" way in a connectionless protocol like HTTP to ensure the client has received (and displayed) a response, so this will not be mission-critical functionality, but still I want to do this at the latest possible time. The response will not be HTML so any callbacks from the client (using Javascript or IMG tags etc.) are not possible.
The "latest" hook I can find would be adding a custom middleware implementing process_response at the first position of the middleware list, but to my understanding this is executed before the actual response is constructed and sent to the client. Are there any hooks/events in Django to execute code after the response has been sent successfully?
The method I am going for at the moment uses a subclass of HttpResponse:
from django.template import loader
from django.http import HttpResponse
# use custom response class to override HttpResponse.close()
class LogSuccessResponse(HttpResponse):
def close(self):
super(LogSuccessResponse, self).close()
# do whatever you want, this is the last codepoint in request handling
if self.status_code == 200:
print('HttpResponse successful: %s' % self.status_code)
# this would be the view definition
def logging_view(request):
response = LogSuccessResponse('Hello World', mimetype='text/plain')
return response
By reading the Django code I am very much convinced that HttpResponse.close() is the latest point to inject code into the request handling. I am not sure if there really are error cases that are handled better by this method compared to the ones mentioned above, so I am leaving the question open for now.
The reasons I prefer this approach to the others mentioned in lazerscience's answer are that it can be set up in the view alone and does not require middleware to be installed. Using the request_finished signal, on the other hand, wouldn't allow me to access the response object.
If you need to do this a lot, a useful trick is to have a special response class like:
class ResponseThen(Response):
def __init__(self, data, then_callback, **kwargs):
super().__init__(data, **kwargs)
self.then_callback = then_callback
def close(self):
super().close()
self.then_callback()
def some_view(request):
# ...code to run before response is returned to client
def do_after():
# ...code to run *after* response is returned to client
return ResponseThen(some_data, do_after, status=status.HTTP_200_OK)
...helps if you want a quick/hacky "fire and forget" solution without bothering to integrate a proper task queue or split off a separate microservice from your app.
I suppose when talking about middleware you are thinking about the middleware's process_request method, but there's also a process_response method that is called when the HttpResponse object is returned. I guess that will be the latest moment where you can find a hook that you can use.
Furthermore there's also a request_finished signal being fired.
I modified Florian Ledermann's idea a little bit... So someone can just use the httpresponse function normally, but allows for them to define a function and bind it to that specific httpresponse.
old_response_close = HttpResponse.close
HttpResponse.func = None
def new_response_close(self):
old_response_close(self)
if self.func is not None:
self.func()
HttpResponse.close = new_response_close
It can be used via:
def myview():
def myfunc():
print("stuff to do")
resp = HttpResponse(status=200)
resp.func = myfunc
return resp
I was looking for a way to send a response, then execute some time consuming code after... but if I can get a background (most likely a celery) task to run, then it will have rendered this useless to me. I will just kick off the background task before the return statement. It should be asynchronous, so the response will be returned before the code is finished executing.
---EDIT---
I finally got celery to work with aws sqs. I basically posted a "how to". Check out my answer on this post:
Cannot start Celery Worker (Kombu.asynchronous.timer)
I found a filthy trick to do this by accessing a protected member in HttpResponse.
def some_view(request):
# ...code to run before response is returned to client
def do_after():
# ...code to run *after* response is returned to client
response = HttpResponse()
response._resource_closers.append(do_after)
return response
It works in Django 3.0.6 , check the "close" function in the prototype of HttpResponse.
def close(self):
for closer in self._resource_closers:
try:
closer()
except Exception:
pass
# Free resources that were still referenced.
self._resource_closers.clear()
self.closed = True
signals.request_finished.send(sender=self._handler_class)

redirect to another page instead of recieving Django standarf 404 error

i have something like this in my views.py
instance = get_object_or_404(register,pk=request.user.id)
Now if there is no related object against this user i receive i standard django 404 eror
saying no matches found.
what i want here is instead of receiving this 404 error redirect it to another page say "something.html". but i dont know how. i am using method = "POST"
is there any way to redirect it to other page instead of receiving a 404 error
using a try/except block you can redirect if the object is not found
try:
instance = register.get(pk=request.user.id)
except register.DoesNotExist:
return HttpResponseRedirect('url that renders something.html')
FYI, definition of django get_object_or_404 function looks like this
def get_object_or_404(klass, *args, **kwargs):
"""
Uses get() to return an object, or raises a Http404 exception if the object
does not exist.
klass may be a Model, Manager, or QuerySet object. All other passed
arguments and keyword arguments are used in the get() query.
Note: Like with get(), an MultipleObjectsReturned will be raised if more than one
object is found.
"""
queryset = _get_queryset(klass)
try:
return queryset.get(*args, **kwargs)
except queryset.model.DoesNotExist:
raise Http404('No %s matches the given query.' % queryset.model._meta.object_name)
from the docs, If you raise Http404 at any point in a view function, Django will catch it and return the standard error page for your application, along with an HTTP error code 404.
look at customizing error views if you want to render a custom 404.html based on the context variables
Depending on what the Big Picture is, you might want to look at django.contrib.flatpages and see what they are doing. Basically they are dealing with the 404 in middleware and then looking at the path to decided if there is something they can return. I have used variations on this on a couple of sites.