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

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.

Related

django messages framework not adding messages in RedirectView

I have the following redirect view code for a payment gateway web callback
class Callback(RedirectView):
def get_redirect_url(self, transaction_goid):
try:
# do some work
except Exception as e:
messages.error(self.request, str(e))
print dir(messages.get_messages(self.request))
print messages.get_messages(self.request)._loaded_messages
return reverse('home')
The problem is messages are not adding to the messages storage, _loaded_messages always give empty list [] and on home page not displaying any messages
I have double checked settings with documentation and ensure it is configured properly.
My gut feeling is that since it's a redirect and it's not going through the middlewares properly
The problem is not within messages framework, the actual problem is str(e) was returning an empty string and messages do not add empty strings.

Custom Django AuthenticationForm with validation depending on prior error messages

I am extending Django's AuthenticationForm in order to provide better error messaging to the user. If login is going to fail, I want to perform an additional check on the user's input to provide a less generic message explaining to the user why login failed.
urls:py:
urlpatterns = patterns(
'django.contrib.auth.views',
url(r'^login/$', 'login', {'authentication_form': RemoteLoginAwareLoginForm}, name='auth_login'),
)
forms.py:
class RemoteLoginAwareLoginForm(AuthenticationForm):
def clean(self):
cleaned_data = super(AuthenticationForm, self).clean()
if self.errors: # <==== self.errors is {}, even when login will fail
# Perform additional checks
return cleaned_data
I thought that the right way to do this was to override clean() as above, but as indicated, self.errors is empty even when I logged in with bad credentials. I tried to override full_clean() and validate() as well, but neither got called at all.
Update:
I realize that the error I'm looking for -- that the credentials aren't valid -- doesn't come until the code actually attempts to log the user in. I added a custom ModelBackend where I am able to catch the error... but I am not sure how to report it back up as there is no request here.
Is the answer that this requires a custom login view?
Thanks!
There is a similar problem on stackoverflow. But I'm not sure if it solves your problem.
Using AuthenticationForm in Django

is there a way to show the Django debug stacktrace page to admin users even if DEBUG=False in settings?

Scenario: instead of me having to SSH into a box to retrieve a stacktrace, I'd prefer non-techie co-workers to just email me the error!
Is there a way or hook in Django to do something like this? e.g.
def 500_error_happened(request): # psuedocode >__<
if request.user.is_staff:
show_the_debug_stack_trace_page()
else:
show_user_friendly_500_html_page()
You may want to take a look into Sentry:
https://github.com/getsentry/sentry
With that, you can record the errors and stacktraces you would usually see with DEBUG=True, aggregate them and take a deeper look into them. Sentry can be configured to send emails to you so that you are notified instantly.
Another option that does not require a new dependency would be to use the AdminEmailHandler:
https://docs.djangoproject.com/en/dev/topics/logging/#django.utils.log.AdminEmailHandler
However, some information you might need for debugging may be sensitive and should not be sent via email. This is why even the Django docs mentioned above recommend using something like Sentry.
found an answer! You can use a middleware which displays technical_500_response to superusers:
from django.views.debug import technical_500_response
import sys
class UserBasedExceptionMiddleware(object):
def process_exception(self, request, exception):
if request.user.is_superuser:
return technical_500_response(request, *sys.exc_info())
(from https://djangosnippets.org/snippets/935/)

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

Non-destructively handling Django user messages

I'm displaying user messages through templates using RequestContext in Django, which gives access to user messages through {{messages}} template variable - that's convenient.
I'd like user him/herself delete the messages - is there a way to do it in Django without rewriting much code? Unfortunately Django automatically deletes messages at each request - not very useful in this case.
Django doc says:
"Note that RequestContext calls get_and_delete_messages() behind the scenes"
Would be perfect if there were a way to simply turn off the automatic deletion of messages!
NOTE: Unfortunately solution below makes admin interface unusable. I don't know how to get around this, really annoying.
EDIT - found a solution - use custom auth context processor that calls user.message_set.all() as Alex Martelli suggested. There's no need to change the application code at all with this solution. (context processor is a component in django that injects variables into templates.)
create file myapp/context_processors.py
and replace in settings.py in TEMPLATE_CONTEXT_PROCESSORS tuple
django.core.context_processors.auth with myapp.context_processors.auth_processor
put into myapp/context_processors.py:
def auth_processor(request):
"""
this function is mostly copy-pasted from django.core.context_processors.auth
it does everything the same way except keeps the messages
"""
messages = None
if hasattr(request, 'user'):
user = request.user
if user.is_authenticated():
messages = user.message_set.all()
else:
from django.contrib.auth.models import AnonymousUser
user = AnonymousUser()
from django.core.context_processors import PermWrapper
return {
'user': user,
'messages': messages,
'perms': PermWrapper(user),
}
I know it sounds like a strange approach, but you could copy into your own list
request.user.message_set.all()
before instantiating RequestContext, and later put them back in..