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')
Related
Lately, I found out the elementary truth related to exception handling - our server should rarely (preferably, never) through 5XX error.
Having said that, let's consider a REST view with some parameters, and a respective URL.
urlpatterns = [
url(r'^some_view/', some_view),
]
#api_view(['POST'])
def some_view(request):
# ...
param1 = request.data['param1']
In this code, I have to manually handle the exception where the developer calls some_view without assigning value to param1, otherwise - if I don't handle this case explicitly - they will get 500 error (MultipleKeyValueError) which is bad. And we have a dilemma:
Handling it will cause irritating and repetitive try-except blocks, especially when we have multiple params
Not handling will lead to 500 error
The solution is to rewrite the view this way:
urlpatterns = [
url(r'^some_view/(?P<param1>\w+/', some_view),
]
#api_view(['POST'])
def some_view(request, param1):
# ...
Here Django will throw 400 exception - as opposed to 500 - saying that url is not found. But on the other hand, the first option (where I use request.data['param1']) gives the pleasant benefit that I can call the REST resource not only from an outside app, but also from my web app submitting params by serializing HTML form.
So here I am asking about the best practice. How do you, guys, handle this situation? Do you explicitly write try-except blocks watching for missing params, or do you use the url-parameters options, or maybe there's some third option that I didn't mention here ?
If you're writing REST views, you should be using Django Rest Framework. One of the benefits of that framework is that you define serializers in which you declare which fields you accept and which are mandatory, just as you do with forms in vanilla Django. You can therefore return validation errors rather than 500s.
So here I am asking about the best practice. How do you, guys, handle
this situation? Do you explicitly write try-except blocks watching for
missing params, or do you use the url-parameters options, or maybe >there's some third option that I didn't mention here ?
If I am trying to handle a common pattern for exception handling across views, I typically us a “third option” of writing a decorator that handles the exception and returns a JSON response with success = False:
from functools import wraps
def handle_missing_key(func)
#wraps(func)
def _decorator(*args, **kwargs):
try:
func(*args, **kwargs)
except KeyError as ex:
return JsonResponse({
'success': False,
'error': '%s' % ex
})
return _decorator
Then for your methods you can just do:
#handle_missing_key
#api_view(['POST'])
def some_view(request, param1):
pass
This way you're just sending back a 200 response but the JSON defines the error message and lets any calling application know what is missing. You can find the missing key using KeyError.args or the error message in the JsonResponse. If you want to send back a response other than a JsonResponse (i.e., status response 400 or the like), you can, of course, use something like this:
response = HttpResponse(status=400)
response.reason_phrase = 'Key %s is missing' % ex
return response
I would like to be able to render a different logged out template when the user_logged_out signal is fired. I can catch the signal and check for my condition correctly, and I have a view with a named URL that works just fine, but I can't seem to render the view.
I've tried each of these, with both a class based and functional view, but can't get them to work. Using ipdb I can get a template to render in the console, but can't figure out the right way to return it/call the view to have it returned. Thoughts?
#receiver(user_logged_out)
def my_logged_out_signal_handler(sender, request, user, **kwargs):
if user.has_condition:
# tried this
resolve(reverse('my_named_url', kwargs={'kwarg1': 'something'})).func(request, something)
# and this
render_to_response(resolve(reverse('my_named_url', kwargs={'kwarg1': something})).func(request, kwarg1=something).render())
# and this
render(MyClassView.as_view()(request, kwarg1=something))
# and this
return (resolve(reverse('my_named_url', kwargs={'kwarg1': something})).func(request, kwarg1=something).render())
# and this
return HttpResponse(resolve(reverse('my_named_url', kwargs={'kwarg1': something})).func(request, kwarg1=something).render())
A signal handler is not a view, it cannot render/return a response.
You could simply handle logic in your own view, and call or redirect to the auth logout function from there. Something like below..
from django.shortcuts import redirect
def my_logout(request):
kwargs = {}
if my_condition:
kwargs['template_name'] = 'my_template.html'
kwargs['extra_context'] = ...
return redirect('logout', **kwargs)
Found a clever solution allowing a redirect from anywhere, if you really need to. https://djangosnippets.org/snippets/2541/
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.
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)
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.