如何使用Django allauth为注册/登录实现invite流?

2024-06-06 17:04:37 发布

您现在位置:Python中文网/ 问答频道 /正文

背景

我正在开发一个应用程序,用户可以邀请其他人就不同的资源进行协作。被邀请的人可能已经是该应用程序的用户,也可能是全新用户。当我使用allauth注册/登录时,被邀请者可以通过标准的注册/登录表单或通过三个社交帐户(fb、twitter、google)中的一个来响应邀请。在

由于这些要求,子类化DefaultAccountAdapter和重写{}方法将不起作用,因为这不是登录流的一部分,如果现有用户接受邀请。在


流量

  • 用户提交邀请表单,指定收件人的电子邮件地址
  • 发送邀请电子邮件,包含邀请接受表单的链接
  • 用户点击链接到接受表单-他们可能已经或可能没有自己的应用程序用户帐户
  • 由于“邀请接受”链接包含此邀请的唯一密钥,因此视图会将“邀请密钥”添加到会话中
  • 被邀请者可以选择注册或登录到现有用户帐户以接受邀请
  • 一旦被邀请人完成了注册/登录,就会收到“用户注册”或“用户登录”信号,并检查会话是否有“邀请密钥”,以确认新用户刚刚接受了邀请
  • 使用密钥检索invite,并针对新用户处理invite


逻辑

接受视图的url模式

url(r'^invitation/(?P<invite_key>[\w\d]+)/$', views.ResourceInviteAcceptanceView.as_view(), name='resource-invite-accept'),

这些是我的视图的基类

https://gist.github.com/jamesbrobb/748c47f46b9bd224b07f

这是invite acceptance视图的视图逻辑

^{pr2}$


问题

只要被邀请者单击链接并完成邀请接受过程,这一切都可以正常工作。在

但是。。。在

如果他们在该过程中的任何时候退出(显式或由于错误),那么“invite_key”仍然存在于会话中,因此在下一个人(无论是他们还是其他人)注册或登录时,都会得到处理。在


问题

处理这个问题最好的办法是什么?是否有一个不同的点可以将“invite_key”添加到会话中,以保证用户已经实际接受了邀请?在

对于标准注册/登录,这可以在重写的“forms\u valid”方法中进行,因为我们现在知道用户已经完成了这些过程中的任何一个。但我不知道当他们使用社交注册/登录时,在哪里/如何添加“邀请密钥”?在


--更新--

可能的解决方案1

通过社交登录,将邀请密钥添加到会话的最佳位置(以确保用户正在接受通过社交登录的邀请的过程中)似乎是通过在“pre_social_login”信号中添加接收器来实现的。我遇到的问题是,如何确保密钥在信号被触发时仍然是可访问的,以便可以将其添加到会话中?在

一个失败的解决方案是简单地访问receiver函数中的HTTP_REFERER,可以包含邀请url。可以从中取出密钥,然后将其添加到会话中。但是,如果用户是新应用程序或当前没有登录到其社交帐户,则此操作将失败,因为他们首先被重定向到社交帐户登录页(在社交帐户域上),然后当回调重定向发生并触发信号时,HTTP_REFERER的值不再存在。在

我无法找到一个好的方法,使邀请密钥值在信号接收器功能中可访问,如果没有它,会导致相同的原始问题?在


Tags: 方法key用户视图应用程序url表单标准
2条回答

与此同时,有人刚刚构建了一个python包(django-invitations),它做得非常好,而且没有猴子补丁。在

在Github上查看:enter link description here

我已经想出了一个解决方案,但我不是百分之百满意,因为它涉及到monkey在allauth.socialaccount.models.SocialLogin上修补state_from_request类方法。在

原因是

  • 这是一个单一的共享逻辑点,所有提供商在启动其社交身份验证过程时都会调用该点

  • 在社交登录过程中,SocialLogin的“state”属性已存储在会话中,然后在完成过程中进行检索,并使用“pre-ung-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

这是补丁

^{pr2}$

我已经从视图中删除了重写的get方法,并重写了forms_valid方法,以将invite键添加到会话中,因为在标准登录/注册期间,我们知道invite已被接受

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)

这些是信号接收器的功能

@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

相关问题 更多 >