How to implement an invitation stream using django allauth to register / sign?

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?

+8
python django django-allauth
source share
2 answers

I came up with a solution, but I'm not 100% satisfied with this, since it involves decapitating the state_from_request class state_from_request on allauth.socialaccount.models.SocialLogin .

The reasons for this:

  • this is a single common point of logic that all providers call when initiating their social process auth

  • the 'state' SocialLogin already saved in the session during the social login process, and then retrieved at completion time and passed with the 'pre_social_login' signal

This is an original method that extracts specific values ​​from a query, which are then stored in a session for later use by allauth after the process has completed

 @classmethod def state_from_request(cls, request): state = {} next_url = get_next_redirect_url(request) if next_url: state['next'] = next_url state['process'] = request.REQUEST.get('process', 'login') return state 

This is a patch

 def state_from_request_wrapper(wrapped_func): wrapped_func = wrapped_func.__func__ def _w(cls, request): state = wrapped_func(cls, request) invite_key = extract_invite_key(request) if invite_key: state['invite_key'] = invite_key return state return classmethod(_w) def extract_invitation_key(request): referer = request.META.get('HTTP_REFERER') if not referer: return None p = re.compile('iv/(?P<invite_key>[\w\d]+)/$') match = p.search(referer) if not match: return None return match.group(1) SocialLogin.state_from_request = state_from_request_wrapper(SocialLogin.state_from_request) 

I removed the overriden get method from the view and redefined the forms_valid method instead to add the invitation key to the session, because at the moment during the standard signin / signup we know that the invitation is accepted

 def forms_valid(self, forms, form_name): session = self.request.session session['invite_key'] = self.get_invite().key return super(ResourceInviteAcceptanceView, self).forms_valid(forms, form_name) 

And these are the functions of the signal receiver

 @receiver (pre_social_login, sender=SocialLogin) def check_pre_social_login(sender, **kwargs): social_login = kwargs['sociallogin'] request = kwargs['request'] session = request.session invite_key = social_login.state.get('invite_key') if invite_key: session['invite_key'] = invite_key @receiver ([user_signed_up, user_logged_in], sender=User) def check_for_invite(sender, **kwargs): request = kwargs['request'] session = request.session invite_key = session.get('invite_key') if invite_key: invite = get_object_or_404(ResourceInvite, key=invite_key) process_invite(kwargs['user'], invite, True) del session['invite_key'] def process_invite(user, invite, accept): ... # process invite here 
+2
source share

In the meantime, someone just built a python package (django-invitations) that does this very well and without intercepting monkeys.

Take a look at Github: enter link description here

0
source share

All Articles