背景
我正在开发一个应用程序,用户可以邀请其他人协作不同的资源。被邀请的人可能已经是该应用程序的用户,也可能完全是新用户。由于我正在使用allauth进行注册/登录,被邀请者可以通过标准的注册/登录表单或三个社交账户之一(fb、twitter、google)回复邀请。
由于这些要求,子类化DefaultAccountAdapter
并重写is_open_for_signup
方法将无法起作用,因为如果现有用户接受邀请,则这不是登录流程的一部分。
流程
- 用户提交邀请表单,指定收件人的电子邮件地址
- 发送邀请电子邮件,其中包含邀请接受表单的链接
- 用户单击链接以访问接受表单 - 他们可能已经拥有自己的应用程序用户帐户,也可能没有
- 由于邀请接受链接包含此邀请的唯一密钥,因此视图将“invite_key”添加到会话中
- 被邀请者可以选择注册或登录到现有用户帐户来接受邀请
- 一旦被邀请者完成注册/登录,就会收到“user_signed_up”或“user_signed_in”信号,并检查会话是否有“invite_key”以确认新用户刚刚接受了邀请
- 使用密钥检索邀请并将邀请与新用户处理
逻辑
接受视图的URL模式
url(r'^invitation/(?P<invite_key>[\w\d]+)/$', views.ResourceInviteAcceptanceView.as_view(), name='resource-invite-accept'),
这些是我视图的基础类。 这是接受邀请视图的视图逻辑。 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']
问题:
只要被邀请者点击链接并完成邀请接受过程,这一切都能正常工作。如果他们在该过程中的任何时候退出(明确或由于错误),则“invite_key”仍然存在于会话中,因此当下一个人(他们或其他人)注册或登录时会被处理。
问题是如何解决这个问题?有没有不同的时间点可以将“invite_key”添加到会话中,以确保用户实际上已经接受了邀请?
对于标准的注册/登录流程,这可以在重写的“forms_valid”方法中完成,因为我们知道此时用户已经完成了其中的任何一个过程。但是我不知道当他们使用社交注册/登录时在哪里/如何添加“invite_key”?
--更新--
可能的解决方案#1
通过社交登录,将邀请密钥添加到会话中的最佳位置 - 以确保用户正在通过社交登录接受邀请的过程中 - 似乎是向“pre_social_login”信号添加接收器。我的问题是如何确保在触发信号时仍然可以访问关键内容,以便将其添加到会话中?
一个失败的解决方案是简单地在接收器函数中访问HTTP_REFERER,它可以包含邀请URL。可以从中删除密钥,然后将其添加到会话中。但是,如果用户是应用程序的新用户或当前未登录其社交帐户,则此方法会失败,因为他们首先被重定向到社交帐户登录页面(位于社交帐户域上),然后当回调重定向发生并触发信号时,HTTP_REFERER的值不再存在。
我无法找出一种好的方法来使邀请密钥值在信号接收器函数中可访问,而不会导致同样的原始问题。
django-invitations
并不能解决我所描述的使用情况。如果您阅读了背景部分,我已经给出了原因---由于这些要求,子类化DefaultAccountAdapter并覆盖is_open_for_signup方法将不起作用,因为如果现有用户接受邀请,则这不是登录流程的一部分。
- james