pythondjangoinstagramdjango-allauthprofile-picture

django-allauth: Check whether user signed up using a social account


So I have integrated django-allauth in my app, and now users have the ability to log in via instagram. Let's say I have a model called UserProfile and it has a field

user_avatar = models.ImageField(upload_to='profile_images', blank=True, default=None)

And with that I have a signal that creates a user profile as soon as a new user registers:

def create_user_profile(sender, instance, created, **kwargs):
     if created:
         UserProfile.objects.create(user=instance)
 post_save.connect(create_user_profile, sender=User)

So usually when the user registers the user_avatar is blank since the default is set as None, now I want to add in the signal(if that's the correct way of doing it), to check if the user created his account via signing in using instagram, to go and fetch his profile picture and use it in the user_avatar. I think it's possible https://instagram.com/developer/endpoints/users/, but since I am a complete noob in python and django I don't know how to exactly do it.

So I found this signal from the django-allauth docs allauth.socialaccount.signals.pre_social_login(request, social_login) so this states that I can check that the user has signed up using a social account, but how would I use it with my create_user_profile function? The steps that I thought of is to first create the profile which I did and then to check whether the user signed up using a social account or not, if they did then the user_avatar which use their instagram profile picture and if not it would stay as none.

And as a plus I know that I can fetch the users social account profile picture in a template using {{user.socialaccount_set.all.0.get_avatar_url}}, but I don't want to do it via templates rather than doing it via Models which is the best way.

This might look really stupid but I gave it a go and tried to come up with something (this is what a newbie thinks would work, I thought this on top of my head, as I have no idea if this how signals work)

def create_user_profile(sender, instance, created, **kwargs):
    if created:
        UserProfile.objects.create(user=instance)
        def pre_social_login(request, social_login):
            user_logged_social = social_login.account.user
            if user_logged_social:
                UserProfile.objects.get(user_avatar=user_logged_social.profile_picture)
            else:
                pass
post_save.connect(create_user_profile, sender=User)

UPDATE Got it working with the help of @bellum! Thank you!

Here is the code that I used:

models.py

class UserProfile(models.Model):
    user = models.OneToOneField(User, related_name="profile")
    user_avatar = models.ImageField(upload_to='profile_images'
                                blank=True,
                                default=None)


    def __unicode__(self):
        return self.user.username

    class Meta:
        verbose_name_plural = "User Profiles"

def create_user_profile(sender, instance, created, **kwargs):
    if created:
        UserProfile.objects.create(user=instance)
post_save.connect(create_user_profile, sender=User)

3utils.py

def download_file_from_url(url):
    # Stream the image from the url
    try:
        request = requests.get(url, stream=True)
    except requests.exceptions.RequestException as e:
        # TODO: log error here
        return None

    if request.status_code != requests.codes.ok:
        # TODO: log error here
        return None

    # Create a temporary file
    lf = tempfile.NamedTemporaryFile()

    # Read the streamed image in sections
    for block in request.iter_content(1024 * 8):

        # If no more file then stop
        if not block:
            break

        # Write image block to temporary file
        lf.write(block)

    return files.File(lf)

class SocialAccountAdapter(DefaultSocialAccountAdapter):
    def save_user(self, request, sociallogin, form=None):
        user = super(SocialAccountAdapter, self).save_user(request, sociallogin, form)

        url = sociallogin.account.get_avatar_url()
        avatar = download_file_from_url(url)
        if avatar:
            profile = user.profile  # access your profile from user by correct name
            profile.user_avatar.save('avatar%d.jpg' % user.pk, avatar)
        return user

settings.py

SOCIALACCOUNT_ADAPTER = 'main.s3utils.SocialAccountAdapter'

The signal for creating the profile on sign up in my models was left the same, just added an SocialAccountAdapter!


Solution

  • I have done the same task for Facebook provider. allauth gives possibility to achive this in another way. I think you don't need to get avatar every time user logins in your system. If yes then you can override class like this:

    from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
    
    class SocialAccountAdapter(DefaultSocialAccountAdapter):
        def save_user(self, request, sociallogin, form=None):
            user = super(SocialAccountAdapter, self).save_user(request, sociallogin, form)
    
            url = sociallogin.account.get_avatar_url()
    
            avatar = download_file_from_url(url)  # here you should download file from provided url, the code is below
            if avatar:
                profile = user.user_profile  # access your profile from user by correct name
                profile.user_avatar.save('avatar%d.jpg' % user.pk, avatar)
    
            return user
    

    You should add this line to your config: SOCIALACCOUNT_ADAPTER = 'path-to-your-adapter.SocialAccountAdapter'.

    As result this code will be called only during new socialaccount registration process, fetch avatar url, download it and save in your User model.

    import requests
    import tempfile
    
    from django.core import files
    
    def download_file_from_url(url):
        # Stream the image from the url
        try:
            request = requests.get(url, stream=True)
        except requests.exceptions.RequestException as e:
            # TODO: log error here
            return None
    
        if request.status_code != requests.codes.ok:
            # TODO: log error here
            return None
    
        # Create a temporary file
        lf = tempfile.NamedTemporaryFile()
    
        # Read the streamed image in sections
        for block in request.iter_content(1024 * 8):
    
            # If no more file then stop
            if not block:
                break
    
            # Write image block to temporary file
            lf.write(block)
    
        return files.File(lf)