1

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!

qasimalbaqali
  • 2,079
  • 24
  • 51

1 Answers1

3

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)
bellum
  • 3,642
  • 1
  • 17
  • 22
  • What's the import for files in `files.File(lf)` – qasimalbaqali Jun 26 '15 at 23:30
  • Okay so I guess it's working but it gives me `Exception Value: 'User' object has no attribute 'user_avatar'` so I am not trying to add it to the User model, but instead I am trying to add it to the UserProfile. How could I achieve that? Thank you. – qasimalbaqali Jun 26 '15 at 23:36
  • @qasimalbaqali what django version do you use? I suggest to create custom user model and add any custom data there. https://docs.djangoproject.com/en/1.8/topics/auth/customizing/#substituting-a-custom-user-model . If you don't want it and don't create userprofile anywhere else then you can create it here and populate user_avatar field with downloaded file. – bellum Jun 26 '15 at 23:41
  • could you post an example of a custom User model that you used that can work with django-allauth? I am afraid of creating a custom one and that would break everything that I do, sorry for my noobness. You have already helped me a lot! – qasimalbaqali Jun 26 '15 at 23:46
  • @qasimalbaqali I have updated answer with advice how to preserve your UserProfile model usage and to solve your problem. Does it work for you? – bellum Jun 26 '15 at 23:56
  • Do I use your new update instead of my `cretae_user_profile` or I should add it to the rest of the code that you have posted? – qasimalbaqali Jun 26 '15 at 23:58
  • @qasimalbaqali You can use it instead your signal. By this solution we separate creation of userprofiles into two places: `AccountAdapter.save_user` for regular users and `SocialAccountAdapter.save_user` for social users with adding profile image. – bellum Jun 27 '15 at 00:01
  • Understood. When I try to log in I get `No module named adapter`. When using `users` in `ACCOUNT_ADAPTER` I get the `No module named users.adapter` so I change it to `user.adapter` and now it says no module named `adapter`. Any idea why? Here are the settings I am using `SOCIALACCOUNT_ADAPTER = 'main.s3utils.SocialAccountAdapter'` and `ACCOUNT_ADAPTER = 'user.adapter.AccountAdapter'`. For the social account adapter it's in my main app inside of s3utils, which works when I tried before, as it told me there was no user_avatar in User model. – qasimalbaqali Jun 27 '15 at 00:06
  • @qasimalbaqali actually it can become a problem if user is created in admin. Ok, I know what to do I think. Remove `AccountAdapter`. Leave your signal as is AND just get profile in `SocialAccountAdapter` from `user` by correct name. You have post_save signal so it should be already executed at that moment. Upd: I have written that incorrect. I will update answer again) – bellum Jun 27 '15 at 00:07
  • 1
    @qasimalbaqali I have updated `SocialAccountAdapter.save_user` method. UserProfile should already exist at that moment so you need to get it and save user_avatar inside it. I should go. Good luck :) – bellum Jun 27 '15 at 00:17
  • Got it working! Updated with the code I used, thank you so much. – qasimalbaqali Jun 27 '15 at 00:43