skip to Main Content

I’m building a REST API with Django Rest Framework and Django Rest Auth.

My users have a consumer profile.

class UserConsumerProfile(
    SoftDeletableModel,
    TimeStampedModel,
    UniversallyUniqueIdentifiable,
    Userable,
    models.Model
):
    def __str__(self):
        return f'{self.user.email} ({str(self.uuid)})'

As you can see it consists of some mixins that give it a UUID, a timestamp and an updated field and a OneToOne relationship to the user. I use this consumerprofile in relations to link data to the user.

This consumerprofile should get created as soon as a user signs up.

Here is the serializer that I wrote for the registration:

from profiles.models import UserConsumerProfile
from rest_auth.registration.serializers import RegisterSerializer

class CustomRegisterSerializer(RegisterSerializer):
    def custom_signup(self, request, user):
        profile = UserConsumerProfile.objects.create(user=user)
        profile.save()

I connected this serializer in the settings:

REST_AUTH_REGISTER_SERIALIZERS = {
    "REGISTER_SERIALIZER": "accounts.api.serializers.CustomRegisterSerializer"
}

It works flawlessly when the users signs up using his email. But when he signs up using facebook, no consumer profile gets created.

I thought the social view would also use the register serializer when creating users? How can I run custom logic after a social sign up?

EDIT for the bounty:

Here are the settings that I use for Django Rest Auth:

# django-allauth configuration

ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_USERNAME_REQUIRED = False
ACCOUNT_AUTHENTICATION_METHOD = 'email'
ACCOUNT_CONFIRM_EMAIL_ON_GET = True
ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = 1
ACCOUNT_LOGOUT_ON_PASSWORD_CHANGE = True
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
ACCOUNT_ADAPTER = 'accounts.adapter.CustomAccountAdapter'

SOCIALACCOUNT_ADAPTER = 'accounts.adapter.CustomSocialAccountAdapter'
SOCIALACCOUNT_PROVIDERS = {
    'facebook': {
        'METHOD': 'oauth2',
        'SCOPE': ['email', 'public_profile', 'user_friends'],
        'AUTH_PARAMS': {'auth_type': 'reauthenticate'},
        'INIT_PARAMS': {'cookie': True},
        'FIELDS': [
            'id',
            'email',
            'name',
            'first_name',
            'last_name',
            'verified',
            'locale',
            'timezone',
            'link',
            'gender',
            'updated_time',
        ],
        'EXCHANGE_TOKEN': True,
        'LOCALE_FUNC': 'path.to.callable',
        'VERIFIED_EMAIL': True,
        'VERSION': 'v2.12',
    }
}

# django-rest-auth configuration

REST_SESSION_LOGIN = False
OLD_PASSWORD_FIELD_ENABLED = True

REST_AUTH_SERIALIZERS = {
    "TOKEN_SERIALIZER": "accounts.api.serializers.TokenSerializer",
    "USER_DETAILS_SERIALIZER": "accounts.api.serializers.UserDetailSerializer",
}

REST_AUTH_REGISTER_SERIALIZERS = {
    "REGISTER_SERIALIZER": "accounts.api.serializers.CustomRegisterSerializer"
}

And here are the custom adapters (in case they matter):

from allauth.account.adapter import DefaultAccountAdapter
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from allauth.utils import build_absolute_uri
from django.http import HttpResponseRedirect
from django.urls import reverse


class CustomAccountAdapter(DefaultAccountAdapter):

    def get_email_confirmation_url(self, request, emailconfirmation):
        """Constructs the email confirmation (activation) url."""
        url = reverse(
            "accounts:account_confirm_email",
            args=[emailconfirmation.key]
        )
        ret = build_absolute_uri(
            request,
            url
        )
        return ret

    def get_email_confirmation_redirect_url(self, request):
        """
        The URL to return to after successful e-mail confirmation.
        """
        url = reverse(
            "accounts:email_activation_done"
        )
        ret = build_absolute_uri(
            request,
            url
        )
        return ret

    def respond_email_verification_sent(self, request, user):
        return HttpResponseRedirect(
            reverse('accounts:account_email_verification_sent')
        )


class CustomSocialAccountAdapter(DefaultSocialAccountAdapter):
    def get_connect_redirect_url(self, request, socialaccount):
        """
        Returns the default URL to redirect to after successfully
        connecting a social account.
        """
        assert request.user.is_authenticated
        url = reverse('accounts:socialaccount_connections')
        return url

Lastly, here are the views:

from allauth.socialaccount.providers.facebook.views import 
    FacebookOAuth2Adapter
from rest_auth.registration.views import SocialConnectView, SocialLoginView


class FacebookLogin(SocialLoginView):
    adapter_class = FacebookOAuth2Adapter


class FacebookConnect(SocialConnectView):
    adapter_class = FacebookOAuth2Adapter

I thought that if I connected the serializer like I did in the initial part of the question the register serializer logic would also get run when someone signs up using facebook.

What do I need to do to have that logic also run when someone signs up using facebook?

(If I can’t fix it, I could make a second server request after each facebook sign up on the client side which creates the userconsumerprofile, but that would be kinda overkill and would introduce new code surface which leads to a higher likelihood of bugs.)

2

Answers


  1. The REGISTER_SERIALIZER that you created is only used by the RegisterView.
    The social login & connect views use different serializers: SocialLoginSerializer and SocialConnectSerializer, that cannot be overwritten per settings.

    I can think of two ways to achieve your desired behavior:

    • create serializers for the social login & connect views (inherriting the default serializers) and set them as serializer_class for the view,

    • use Django signals, especially the post_save signal for the User model and when an instance is created, create your UserConsumerProfile.

    Login or Signup to reply.
  2. Looking briefly at the DefaultAccountAdapter and DefaultSocialAccountAdapter it may be an opportunity for you to override/implement the save_user(..) in your CustomAccountAdapter/CustomSocialAccountAdapter to setup the profile?

    Looking just at code it seems that the DefaultSocialAccountAdapter.save_user will finally call the DefaultAccountAdapter.save_user.

    Something like this maybe?

    class CustomAccountAdapter(DefaultAccountAdapter):
        def save_user(self, request, user, form, commit=True):
            user = super(CustomAccountAdapter, self).save_user(request, user, form,
                                                               commit)
            UserConsumerProfile.objects.get_or_create(user=user)
    
            return user
    

    There are a few other “hooks”/functions in the adapters that may we worth to investigate if the save_user doesn’t work for your scenario.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search