How to attach logged in user to Django exception error messages? - django

When I get an error exception email from my Django site it would be useful to see the User and/or UserProfile information for the currently logged in user. How do I add this to the Django site exception error emails?

Django appends repr(request) at the end of the e-mail. Using the default wsgi development server you can find the logged in user as
'LOGNAME': 'myuser',
This may be hidden in some e-mail clients as it is wrapped in angle brackets.
<WSGIRequest
GET:<QueryDict: {}>,
POST:<QueryDict: {}>,
...
'LOGNAME': 'myuser',
...
wsgi.run_once': False,
'wsgi.url_scheme': 'http',
'wsgi.version': (1, 0)}>
Also, you can implement a custom middleware that implements the process_exception 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.
Again, middleware are run in reverse order during the response phase, which includes process_exception. If an exception middleware return a response, the middleware classes above that middleware will not be called at all.

#jsamsa's answer doesn't appear to be true anymore due to changes in Django (at least I don't get a 'LOGNAME' field in error emails).
I am posting an alternative, to receive the full django html error page via email. Details on that are here:
https://docs.djangoproject.com/en/dev/topics/logging/#django.utils.log.AdminEmailHandler
and this ticket helpfully points out that you can do this:
LOGGING['handlers']['mail_admins']['include_html'] = True

From Django version 1.10 onwards, this is supported natively: see https://docs.djangoproject.com/en/dev/releases/1.10/.
Added request.user to the debug view.

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.

Passing a cause description to a 403 handler view

I want to show an informative error message to a user hitting a page that results in a 403 Forbidden. I'm using Django 1.8.
My idea of passing an error message is this:
# in a view
if not ...:
raise PermissionDenied('You have no power here')
Then somewhere in the handler403 view, I'd like to retrieve the message (or maybe even a structured object) and render an informative page based on it.
Though the handler seems to only receive the request object and the template name.
Is there a way to pass a custom message to the handler? (Of course I could assign a custom attribute to the request object before raising the exception, and hope that the same object will be passed to the handler, but this feels like a hack.)
Update: Indeed there is a middleware for that already: contrib/messages is capable of piggybacking messages on the request object, and more.
Looks like you would have to implement this using a middleware:
class ExceptionMiddleware:
def process_exception(self, request, exception):
if exception.args:
request.message = exception.args[0]
Then in your handler403 you could access request.message.
Obviously you could flesh-out this middleware to allow passing more than just a message to the view.

Using ParseError Django REST Framework to return invalid data

I have a similar issue as this question about validating data in the Django REST Framework outside of a serializer:
Raise Validation Error In Pre_Save Using Django Rest Framework
My code:
def pre_save(self, obj):
data = self.request.DATA['users']
for user in data:
if not user in allowed_users:
raise ParseError('An unpermitted user has been included')
From the trace it looks like it's trying to send the response but it fails with:
"" needs to have a value for field before this many-to-many relationship can be used.
UPDATE:
I moved raising the ParseError into a get_serializer_class() method like so:
def get_serializer_class(self):
if 'users' in self.request.DATA:
# make sure the users are allowed
data = self.request.DATA['users']
for user in data:
if not user in allowed_users:
raise ParseError(detail='Unpermitted user')
return serializer
And this raises the exception, however, it does not return it using the REST framework's JSON response. Rather I get the django stack trace and a 500 error, which is not good.
Thanks!
Have a look at APIView's handle_exception — this is where DRF processes exceptions raised during the request.
From the docs:
The default implementation handles any subclass of rest_framework.exceptions.APIException, as well as Django's Http404 and PermissionDenied exceptions, and returns an appropriate error response.
If you need to customize the error responses your API returns you should subclass this method.
So you need to override this to handle ParseError exceptions too.
Also check out the DRF docs on Exceptions.
I hope that helps.
When the exception is raised in the pre_save method(), post_save(), or even in a post() method for the viewclass, it was being handled correctly by Django-REST-Framework. Had I been using curl or similar, the error would have been returned correctly.
This actually is a bug in the browsable API, which is what I was using to test - sending the data using the "Raw data" form. When trying to render the html response, DRF apparently tries to capture the "context" of the post. In this case, it wanted the saved/completed post.
That did not exist, so a Django rendering error was being thrown and that confused me.
When testing using curl, the response was accurate.
Note that putting it in the get_serializer_class() like I did caused it to go outside of the DRF exception handler so Django rendered it correctly and showed the error was being thrown correctly.

Will Django send me an email if exception rises at admin site?

Django documentation says:
When DEBUG is False, Django will email the users listed in the ADMINS
setting whenever your code raises an unhandled exception and results
in an internal server error (HTTP status code 500).
But does this includes django admin site? And if not, how can I enable such reporting?
I'm asking this because when I intensionally rise an Exception at ModelAdmin subclass I receive no email.
On the other hand I tried to send manually and it works fine.
$ ./manage.py shell
>>> from django.core.mail import EmailMessage
>>> email = EmailMessage('Hello', 'World', to=['email#example.com'])
>>> email.send()
UPDATE:
Also Django does sends crash reports when exception rises at API part of application (driven by piston).
Any help is much appreciated.
There is nothing special about the admin site in this instance. Django will send you an email when an admin view raises an unhandled exception.
Troubleshooting idea 1
Have you tested whether you receive an email for a non-admin view? Could it be a permissions issue? The webserver might be running as a different user than when you test emailing from the shell.
Troubleshooting idea 2
Where in the ModelAdmin are you raising the exception?
The following example will not work, because the exception is raised when the ModelAdmin class is defined, not when the request is processed.
class MyModelAdmin(ModelAdmin):
raise Exception
Instead, raise the exception in a method. You should get an email for the following model, if you go to it's change view url (e.g /admin/app/mymodel/)
class MyModelAdmin(ModelAdmin):
def get_queryset(self, request, queryset):
raise Exception

How can I handle Exceptions raised by dango-social-auth?

In django-social-auth, there are a few instances where a back-end will raise a ValueError (such as when a user cancels a login request or if a user tries to associate with an account that's already been associated with another User). If a User runs into one of these scenarios, they'll be presented with a 500 error on your site.
So, what's the best way to catch these? I'd prefer to be able to display a useful message (via the messages framework) when this happens, but I'm at a loss as to the best way to do this.
I'm thinking about writing my own view (in a separate app) that just wraps social_auth's associate_complete view, but this seems clunky... Any ideas?
I could fork django-social-auth and customize this behavior, but I'd prefer not to maintain a separate fork - especially since I can't assume everone would want to handle these Exceptions in the same manner.
Rather old question but worth mention that recent version of DSA supports a custom exception processor where you can do whatever you want with the exception message. The default version stores them in the messages app.
Also the exceptions are differentiated now instead of the not-useful ValueError used. Check the docs http://django-social-auth.readthedocs.org/en/latest/configuration.html.
Update (13/08/2013):
Since I've posted the above things have changed, now DSA has an exception middleware that when enabled stores the exception message in the jango builtin messages app. It's preferable to subclass the middleware and add the custom behavior to it. Check the doc at http://django-social-auth.readthedocs.org/en/latest/configuration.html#exceptions-middleware
Sample middleware:
# -*- coding: utf-8 -*-
from social_auth.middleware import SocialAuthExceptionMiddleware
from social_auth.exceptions import AuthFailed
from django.contrib import messages
class CustomSocialAuthExceptionMiddleware( SocialAuthExceptionMiddleware):
def get_message(self, request, exception):
msg = None
if (isinstance(exception, AuthFailed) and
exception.message == u"User not allowed"):
msg = u"Not in whitelist"
else:
msg = u"Some other problem"
messages.add_message(request, messages.ERROR, msg)
I've ecountered the same problem and it seems, that creating wrapper view is the best way to handle this situation, at this point, atleast. Here is how I had mine done:
def social_auth_login(request, backend):
"""
This view is a wrapper to social_auths auth
It is required, because social_auth just throws ValueError and gets user to 500 error
after every unexpected action. This view handles exceptions in human friendly way.
See https://convore.com/django-social-auth/best-way-to-handle-exceptions/
"""
from social_auth.views import auth
try:
# if everything is ok, then original view gets returned, no problem
return auth(request, backend)
except ValueError, error:
# in case of errors, let's show a special page that will explain what happened
return render_to_response('users/login_error.html',
locals(),
context_instance=RequestContext(request))
You will have to setup url for it:
urlpatterns = patterns('',
# ...
url(r'^social_auth_login/([a-z]+)$', social_auth_login, name='users-social-auth-login'),
)
And then use it as before in template:
Log in with Google
Hope this helps, even aftern two months after question was asked :)
You need add social auth middleware:
MIDDLEWARE_CLASSES += ('social_auth.middleware.SocialAuthExceptionMiddleware',)
If any error occurs user will be redirected to erorr url(LOGIN_ERROR_URL from settings).
For detailed explanation please see documentation:
http://django-social-auth.readthedocs.org/en/latest/configuration.html#exceptions-middleware
In my app's views.py:
from social_auth.views import associate_complete
def associate_complete_wrapper(request, backend):
try:
return associate_complete(request, backend)
except ValueError, e:
if e and len(e.args) > 0:
messages.error(request, "Failed to Associate: %s" % e.args[0])
return redirect(reverse('pieprofile-edit-account'))
Then in the Root URLconf (notice the order of these url patterns):
url(r'^associate/complete/(?P<backend>[^/]+)/$', 'myapp.views.associate_complete_wrapper'),
url(r'', include('social_auth.urls')),
My associate_complete_wrapper url essentially hijacks social_auth's socialauth_associate_complete url.