jam*_*mes 9 python django django-allauth
背景
我正在构建一个应用程序,用户可以邀请其他人在不同的资源上进行协作.被邀请的人可能已经是该应用的用户,或者可能是全新的用户.由于我正在使用allauth进行注册/签名,被邀请者可以通过标准注册/登录表单或通过三个社交帐户(fb,twitter,google)中的一个来回复邀请.
由于这些要求,如果现有用户接受邀请,则子类化DefaultAccountAdapter和覆盖该is_open_for_signup方法将不起作用,因为这不是登录流程的一部分.
流
逻辑
接受视图的url模式
url(r'^invitation/(?P<invite_key>[\w\d]+)/$', views.ResourceInviteAcceptanceView.as_view(), name='resource-invite-accept'),
Run Code Online (Sandbox Code Playgroud)
这些是我视图的基类
https://gist.github.com/jamesbrobb/748c47f46b9bd224b07f
这是邀请接受视图的视图逻辑
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']
Run Code Online (Sandbox Code Playgroud)
问题
只要被邀请者单击链接并完成邀请接受过程,这一切都可以正常工作.
但...
如果他们在该过程中的任何时候(明确地或由于错误)保释,则"invite_key"仍然存在于会话中,因此当下一个人(他们或其他人)报名或登录时会被处理.
题
处理这个问题的最佳方法是什么?是否可以将"invite_key"添加到会话中,这可以保证用户已经实际接受了邀请?
对于标准注册/签名,这可能是覆盖'forms_valid'方法,因为我们知道用户已经完成了这些进程中的任何一个.但我不知道在使用社交注册/ sigin时在哪里/如何添加'invite_key'?
- 更新 -
可能的解决方案#1
通过社交登录,向会话添加邀请密钥的最佳位置 - 确保用户正在通过社交登录接受邀请 - 似乎是通过在'pre_social_login'信号中添加接收者.我遇到的问题是如何确保在信号被触发时仍然可以实际访问密钥,以便可以将其添加到会话中?
一个失败的解决方案是简单地访问接收器函数中的HTTP_REFERER,该函数可以包含邀请URL.密钥可以从中删除,然后添加到会话中.但是如果用户是应用程序的新用户或当前没有登录到他们的社交帐户,则会失败,因为他们首先被重定向到社交帐户登录页面(在社交帐户域上),然后当回调重定向发生时和信号触发后,HTTP_REFERER的值不再存在.
我无法找到一种好的方法来使信号接收器功能中的邀请键值可访问,而不会产生相同的原始问题?
我已经想出了一个解决方案,但我对它并不是 100% 满意,因为它涉及到state_from_request对allauth.socialaccount.models.SocialLogin.
其原因是
这是所有提供商在启动社交身份验证过程时调用的单一共享逻辑点
的 'state' 属性SocialLogin已在社交登录过程中存储在会话中,然后在完成期间检索并通过 'pre_social_login' 信号传递
这是原始方法,它从请求中检索特定值,然后将这些值存储在会话中,供 allauth 稍后在该过程完成后使用
@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
Run Code Online (Sandbox Code Playgroud)
这是补丁
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)
Run Code Online (Sandbox Code Playgroud)
get我已从视图中删除了重写方法,并重写了该forms_valid方法以将邀请密钥添加到会话中,因为此时在标准登录/注册期间我们知道邀请已被接受
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)
Run Code Online (Sandbox Code Playgroud)
这些是信号接收器功能
@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
Run Code Online (Sandbox Code Playgroud)