Background
I am creating an application in which users can invite other people to work together on different resources. The people who were invited may already be users of the application or may be completely new to it. Since I use allauth for my registration / signing, the invitee can respond to the invitation through standard registration / signing forms or through one of three social accounts (fb, twitter, google).
Because of these requirements, subclassing the DefaultAccountAdapter and overriding the is_open_for_signup method is_open_for_signup not work, as it is not part of the sign stream if an existing user accepts the invitation.
Flow
- The user submits the invitation form, indicating the email address of the recipient
- An email invitation containing a link to an invitation to accept the form
- the user clicks a link to the admission form - they may or may not have their own user account for the application
- since the invitation invitation link contains a unique key for this invitation, the view adds the 'session_key' invitation to the session
- the invitee is offered the opportunity to register or log into an existing user account to accept the invitation
- After the invitation completes registration / signature, the signal 'user_signed_up' or 'user_signed_in' will be received and the session will be checked for the invitation 'invite_key' to confirm that the new user has just accepted the invitation
- the invitation is retrieved using the key, and the invitation is processed against the new user.
Logic
URL pattern for presentation submission
url(r'^invitation/(?P<invite_key>[\w\d]+)/$', views.ResourceInviteAcceptanceView.as_view(), name='resource-invite-accept'),
These are the base classes for my presentation.
https://gist.github.com/jamesbrobb/748c47f46b9bd224b07f
and this is presentation logic for presenting invitation invitations
from django.contrib.auth.models import User from django.shortcuts import get_object_or_404 from django.dispatch import receiver from allauth.account import app_settings from allauth.account.forms import LoginForm, SignupForm from allauth.account.utils import get_next_redirect_url, complete_signup from allauth.account.signals import user_signed_up, user_logged_in from forms.views import MultiFormsView from api.models import ResourceInvite class ResourceInviteAcceptanceView(MultiFormsView): template_name = 'public/resource_invite_accept.html' form_classes = {'login': LoginForm, 'signup': SignupForm} redirect_field_name = "next" def get_invite(self): invite_key = self.kwargs['invite_key'] invite = get_object_or_404(ResourceInvite, key=invite_key) return invite def get_login_initial(self): invite = self.get_invite() return {'login':invite.email} def get_signup_initial(self): invite = self.get_invite() return {'email':invite.email} def get_context_data(self, **kwargs): context = super(ResourceInviteAcceptanceView, self).get_context_data(**kwargs) context.update({"redirect_field_name": self.redirect_field_name, "redirect_field_value": self.request.REQUEST.get(self.redirect_field_name)}) return context def get_success_url(self): # Explicitly passed ?next= URL takes precedence ret = (get_next_redirect_url(self.request, self.redirect_field_name) or self.success_url) return ret def login_form_valid(self, form): return form.login(self.request, redirect_url=self.get_success_url()) def signup_form_valid(self, form): user = form.save(self.request) return complete_signup(self.request, user, app_settings.EMAIL_VERIFICATION, self.get_success_url()) def get(self, request, *args, **kwargs): session = request.session session['invite_key'] = self.kwargs['invite_key'] return super(ResourceInviteAcceptanceView, self).get(request, *args, **kwargs) @receiver ([user_signed_up, user_logged_in], sender=User) def check_for_invite(sender, **kwargs): signal = kwargs.get('signal', None) user = kwargs.get('user', None) request = kwargs.get('request', None) session = request.session invite_key = session.get('invite_key') if invite_key: invite = get_object_or_404(ResourceInvite, key=invite_key) """ logic to process invite goes here """ del session['invite_key']
Problems
All this works fine, while the invitee clicks on the link and completes the process of accepting the invitation.
But...
If they are locked at any time during this process (either explicitly or because of an error), the "key_ invitation" is still present in the session and therefore is processed when the next person (or someone else) signs or signs.
Question
What is the best way to deal with this problem? Is there another point at which key_ invitation can be added to the session, which ensures that the user has already accepted the invitation?
For the standard signup / signin, this may be in the redundant "forms_valid" method, as we know, at the moment the user has completed any of these processes. But I have no idea where / how to add an “invitation” when they use social signup / sigin?
- UPDATE -
Possible Solution # 1
Due to social inclusion, the best place to add an invitation to a session is to ensure that the user accepts the invitation through a social login, appears by adding a receiver to the 'pre_social_login' signal. The problem I have is how to make sure that the key is still actually available at the point where the signal is triggered so that it can be added to the session?
One of the failed solutions was to simply access the HTTP_REFERER in the receiver function, which may contain the invitation URL. The key can be removed from this and then added to the session. But this does not work if the user is new to the application or is not currently registered in his social account, because he is first redirected to the login page of the social account (in the social account domain), then when the callback is redirected, and the signal, the value of HTTP_REFERER no longer exists.
I can’t find a good way to make the value of the invitation key available in the function of the signal receiver, without leading to the same original release?