Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Django Invitations & All-Auth: Invitation Accepted Only on Account Creation

django-invitations & django-allauth: Invitation Accepted Only on Account Creation

The Problem

As per this django-invitations issue, django-invitations considers an invite "accepted" if the invite link is clicked. This is problematic for two reasons (or more):

  1. Automatic mail crawlers which visit links checking for malware etc., and verifying that you're not a spammer
  2. A user which clicks a link to "check out" an invite, but doesn't necessarily want to sign up right now

Something to consider is that this solution assumes you're using django-allauth as it uses the EmailAddress model of that package.

Solution

This has been tested, but not extensively.

We take advantage of the fact that Django's url matching magic follows a "first match principle"; the first match that hits is it. So we copy the api of django-invitation's accept url and insert it above our include for django-invitations, pointing to our custom view.

The view is courtesy @clokep (thanks!). It receives the user signed up signal, checks for all matching existing EmailAddresses, accepting the relevant invites. The imports have been added in for clarity.

Thanks to @beekeeper for the project

# this order matters
url(r'^invitations/accept-invite/(?P<key>\w+)/?$', AcceptInviteView.as_view(), name='accept-invite'),
url(r'^invitations/', include('invitations.urls', namespace='invitations')),
# a custom view hooked into a custom url
from django.http import HttpResponse
from django.contrib import messages
from django.shortcuts import redirect
from django.dispatch import receiver
from invitations.views import AcceptInvite
from invitations.app_settings import app_settings
from invitations.adapters import get_invitations_adapter
from invitations.models import Invitation
from invitations.signals import invite_accepted
from allauth.account.signals import user_signed_up
from allauth.account.models import EmailAddress
class AcceptInviteView(AcceptInvite):
def post(self, *args, **kwargs):
self.object = invitation = self.get_object()
# Compatibility with older versions: return an HTTP 410 GONE if there
# is an error. # Error conditions are: no key, expired key or
# previously accepted key.
if app_settings.GONE_ON_ACCEPT_ERROR and \
(not invitation or
(invitation and (invitation.accepted or
invitation.key_expired()))):
return HttpResponse(status=410)
# No invitation was found.
if not invitation:
# Newer behavior: show an error message and redirect.
get_invitations_adapter().add_message(
self.request,
messages.ERROR,
'invitations/messages/invite_invalid.txt')
return redirect(app_settings.LOGIN_REDIRECT)
# The invitation was previously accepted, redirect to the login
# view.
if invitation.accepted:
get_invitations_adapter().add_message(
self.request,
messages.ERROR,
'invitations/messages/invite_already_accepted.txt',
{'email': invitation.email})
# Redirect to login since there's hopefully an account already.
return redirect(app_settings.LOGIN_REDIRECT)
# The key was expired.
if invitation.key_expired():
get_invitations_adapter().add_message(
self.request,
messages.ERROR,
'invitations/messages/invite_expired.txt',
{'email': invitation.email})
# Redirect to sign-up since they might be able to register anyway.
return redirect(app_settings.SIGNUP_REDIRECT)
get_invitations_adapter().stash_verified_email(
self.request, invitation.email)
get_invitations_adapter().add_message(
self.request,
messages.SUCCESS,
'invitations/messages/invite_accepted.txt',
{'email': invitation.email})
return redirect(app_settings.SIGNUP_REDIRECT)
@receiver(user_signed_up)
def accept_invite(sender, request, user, **kwargs):
# Traverse from the user to verified email addresses. It is possible for a
# user to already have multiple email addresses if they typed in a different
# email after accepted the invite. In this case, the user will have two
# emails, but only one will be verified.
addresses = EmailAddress.objects.filter(user=user, verified=True) \
.values_list('email', flat=True)
# Check if any invites exist for this address and were accepted.
invites = Invitation.objects.filter(email__in=addresses)
if invites:
# Mark all these invites as accepted.
invites.update(accepted=True)
for invite in invites:
# Note that this doesn't send it with a request.
invite_accepted.send(sender=Invitation, email=invite.email)
# Figure out if you care if there are multiple invites here.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.