Refactor social connect views and serializers

# 347
This commit is contained in:
Maxim Kukhtenkov 2018-01-18 21:08:41 -05:00
parent 41ae498be0
commit fed6b9840c
3 changed files with 169 additions and 183 deletions

View File

@ -1,5 +1,4 @@
from django.http import HttpRequest from django.http import HttpRequest
from django.conf import settings
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
@ -9,6 +8,9 @@ try:
get_username_max_length) get_username_max_length)
from allauth.account.adapter import get_adapter from allauth.account.adapter import get_adapter
from allauth.account.utils import setup_user_email from allauth.account.utils import setup_user_email
from allauth.socialaccount.helpers import complete_social_login
from allauth.socialaccount.models import SocialAccount
from allauth.socialaccount.providers.base import AuthProcess
except ImportError: except ImportError:
raise ImportError("allauth needs to be added to INSTALLED_APPS.") raise ImportError("allauth needs to be added to INSTALLED_APPS.")
@ -16,6 +18,21 @@ from rest_framework import serializers
from requests.exceptions import HTTPError from requests.exceptions import HTTPError
class SocialAccountSerializer(serializers.ModelSerializer):
"""
serialize allauth SocialAccounts for use with a REST API
"""
class Meta:
model = SocialAccount
fields = (
'id',
'provider',
'uid',
'last_login',
'date_joined',
)
class SocialLoginSerializer(serializers.Serializer): class SocialLoginSerializer(serializers.Serializer):
access_token = serializers.CharField(required=False, allow_blank=True) access_token = serializers.CharField(required=False, allow_blank=True)
code = serializers.CharField(required=False, allow_blank=True) code = serializers.CharField(required=False, allow_blank=True)
@ -105,7 +122,7 @@ class SocialLoginSerializer(serializers.Serializer):
login = self.get_social_login(adapter, app, social_token, access_token) login = self.get_social_login(adapter, app, social_token, access_token)
complete_social_login(request, login) complete_social_login(request, login)
except HTTPError: except HTTPError:
raise serializers.ValidationError(_('Incorrect value')) raise serializers.ValidationError(_("Incorrect value"))
if not login.is_existing: if not login.is_existing:
# We have an account already signed up in a different flow # We have an account already signed up in a different flow
@ -130,6 +147,22 @@ class SocialLoginSerializer(serializers.Serializer):
return attrs return attrs
class SocialConnectMixin(object):
def get_social_login(self, *args, **kwargs):
"""
Set the social login process state to connect rather than login
Refer to the implementation of get_social_login in base class and to the
allauth.socialaccount.helpers module complete_social_login function.
"""
social_login = super(SocialConnectMixin, self).get_social_login(*args, **kwargs)
social_login.state['process'] = AuthProcess.CONNECT
return social_login
class SocialConnectSerializer(SocialConnectMixin, SocialLoginSerializer):
pass
class RegisterSerializer(serializers.Serializer): class RegisterSerializer(serializers.Serializer):
username = serializers.CharField( username = serializers.CharField(
max_length=get_username_max_length(), max_length=get_username_max_length(),
@ -182,44 +215,3 @@ class RegisterSerializer(serializers.Serializer):
class VerifyEmailSerializer(serializers.Serializer): class VerifyEmailSerializer(serializers.Serializer):
key = serializers.CharField() key = serializers.CharField()
# Import is needed only if we are using social login, in which
# case the allauth.socialaccount will be declared
if 'allauth.socialaccount' in settings.INSTALLED_APPS:
from allauth.socialaccount.helpers import complete_social_login
from allauth.socialaccount.models import SocialAccount
from allauth.socialaccount.providers.base import AuthProcess
class SocialAccountSerializer(serializers.ModelSerializer):
"""
serialize allauth SocialAccounts for use with a REST API
"""
class Meta:
model = SocialAccount
fields = (
'id',
'provider',
'uid',
'last_login',
'date_joined',
'extra_data',
)
class SocialConnectMixin(object):
def get_social_login(self, *args, **kwargs):
"""
set the social login process state to connect rather than login
Refer to the implementation of get_social_login in base class and to the
allauth.socialaccount.helpers module complete_social_login function.
"""
social_login = super(SocialConnectMixin, self).get_social_login(*args, **kwargs)
social_login.state['process'] = AuthProcess.CONNECT
return social_login
class SocialConnectSerializer(SocialConnectMixin, SocialLoginSerializer):
pass

View File

@ -7,21 +7,25 @@ from rest_framework.views import APIView
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.permissions import (AllowAny, from rest_framework.permissions import (AllowAny,
IsAuthenticated) IsAuthenticated)
from rest_framework.decorators import detail_route from rest_framework.generics import CreateAPIView, ListAPIView, GenericAPIView
from rest_framework.viewsets import GenericViewSet from rest_framework.exceptions import NotFound
from rest_framework.generics import CreateAPIView
from rest_framework import status from rest_framework import status
from allauth.account.adapter import get_adapter
from allauth.account.views import ConfirmEmailView from allauth.account.views import ConfirmEmailView
from allauth.account.utils import complete_signup from allauth.account.utils import complete_signup
from allauth.account import app_settings as allauth_settings from allauth.account import app_settings as allauth_settings
from allauth.socialaccount import signals
from allauth.socialaccount.adapter import get_adapter as get_social_adapter
from allauth.socialaccount.models import SocialAccount
from rest_auth.app_settings import (TokenSerializer, from rest_auth.app_settings import (TokenSerializer,
JWTSerializer, JWTSerializer,
create_token) create_token)
from rest_auth.models import TokenModel from rest_auth.models import TokenModel
from rest_auth.registration.serializers import (SocialLoginSerializer, from rest_auth.registration.serializers import (VerifyEmailSerializer,
VerifyEmailSerializer, SocialLoginSerializer,
SocialAccountSerializer,
SocialConnectSerializer) SocialConnectSerializer)
from rest_auth.utils import jwt_encode from rest_auth.utils import jwt_encode
from rest_auth.views import LoginView from rest_auth.views import LoginView
@ -94,15 +98,7 @@ class VerifyEmailView(APIView, ConfirmEmailView):
return Response({'detail': _('ok')}, status=status.HTTP_200_OK) return Response({'detail': _('ok')}, status=status.HTTP_200_OK)
if 'allauth.socialaccount' in settings.INSTALLED_APPS: class SocialLoginView(LoginView):
from allauth.socialaccount import signals
from allauth.socialaccount.models import SocialAccount
from allauth.socialaccount.adapter import get_adapter
from rest_auth.registration.serializers import SocialAccountSerializer
class SocialLoginView(LoginView):
""" """
class used for social authentications class used for social authentications
example usage for facebook with access_token example usage for facebook with access_token
@ -125,14 +121,13 @@ if 'allauth.socialaccount' in settings.INSTALLED_APPS:
callback_url = 'localhost:8000' callback_url = 'localhost:8000'
------------- -------------
""" """
serializer_class = SocialLoginSerializer serializer_class = SocialLoginSerializer
def process_login(self): def process_login(self):
get_adapter(self.request).login(self.request, self.user) get_adapter(self.request).login(self.request, self.user)
class SocialConnectView(SocialLoginView): class SocialConnectView(LoginView):
""" """
class used for social account linking class used for social account linking
@ -144,42 +139,42 @@ if 'allauth.socialaccount' in settings.INSTALLED_APPS:
adapter_class = FacebookOAuth2Adapter adapter_class = FacebookOAuth2Adapter
------------- -------------
""" """
serializer_class = SocialConnectSerializer serializer_class = SocialConnectSerializer
permission_classes = (IsAuthenticated,) permission_classes = (IsAuthenticated,)
def process_login(self):
get_adapter(self.request).login(self.request, self.user)
class SocialAccountViewSet(GenericViewSet):
class SocialAccountListView(ListAPIView):
""" """
allauth SocialAccount REST API read and disconnect views for logged in users List SocialAccounts for the currently logged in user
Refer to the django-allauth package implementation of the models and
account handling logic for more details, this viewset emulates the allauth web UI.
""" """
serializer_class = SocialAccountSerializer serializer_class = SocialAccountSerializer
permission_classes = (IsAuthenticated,) permission_classes = (IsAuthenticated,)
queryset = SocialAccount.objects.none()
def get_queryset(self): def get_queryset(self):
return SocialAccount.objects.filter(user=self.request.user) return SocialAccount.objects.filter(user=self.request.user)
def list(self, request):
"""
list SocialAccounts for the currently logged in user
"""
return Response(self.get_serializer(self.get_queryset(), many=True).data) class SocialAccountDisconnectView(GenericAPIView):
@detail_route(methods=['POST'])
def disconnect(self, request, pk):
""" """
disconnect SocialAccount from remote service for the currently logged in user Disconnect SocialAccount from remote service for
the currently logged in user
""" """
serializer_class = SocialConnectSerializer
permission_classes = (IsAuthenticated,)
def get_queryset(self):
return SocialAccount.objects.filter(user=self.request.user)
def post(self, request, *args, **kwargs):
accounts = self.get_queryset() accounts = self.get_queryset()
account = accounts.get(pk=pk) account = accounts.filter(pk=kwargs['pk']).first()
get_adapter(self.request).validate_disconnect(account, accounts) if not account:
raise NotFound
get_social_adapter(self.request).validate_disconnect(account, accounts)
account.delete() account.delete()
signals.social_account_removed.send( signals.social_account_removed.send(

View File

@ -1,7 +1,6 @@
from django.conf import settings from django.conf import settings
from django.http import HttpRequest from django.http import HttpRequest
from rest_framework import serializers from rest_framework import serializers
# Import is needed only if we are using social login, in which # Import is needed only if we are using social login, in which
# case the allauth.socialaccount will be declared # case the allauth.socialaccount will be declared
if 'allauth.socialaccount' in settings.INSTALLED_APPS: if 'allauth.socialaccount' in settings.INSTALLED_APPS:
@ -12,7 +11,7 @@ if 'allauth.socialaccount' in settings.INSTALLED_APPS:
from rest_auth.registration.serializers import SocialConnectMixin from rest_auth.registration.serializers import SocialConnectMixin
class TwitterLoginSerializer(serializers.Serializer): class TwitterLoginSerializer(serializers.Serializer):
access_token = serializers.CharField() access_token = serializers.CharField()
token_secret = serializers.CharField() token_secret = serializers.CharField()
@ -78,5 +77,5 @@ if 'allauth.socialaccount' in settings.INSTALLED_APPS:
return attrs return attrs
class TwitterConnectSerializer(SocialConnectMixin, TwitterLoginSerializer): class TwitterConnectSerializer(SocialConnectMixin, TwitterLoginSerializer):
pass pass