python-social-authでプロフィール画像の保存

Oauth認証した際に、プロフィール画像を保存させる。

はじめに

python-social-authでOauth認証した際に、プロフィール画像を保存したかったのでpipelineを追加してみました。

環境

requirements.txt

Django==1.5.12
python-social-auth==0.2.13

まず画像を扱えるようにする

ImageFieldを扱えるように

models.ImageFiledを利用して画像を保存したいので、まずはその準備。

モデルフィールドリファレンス — Django 1.4 documentationによると、 Python Imaging Libraryが必要らしい。

pipでインストールする場合は、

$ pip install Pillow

MEDIA_ROOTとか

settings.py

import os

# ./
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# ./media
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')


注意
自身の環境に合わせて変更してください。

これでとりあえず画像が扱えます。

CustomUserを定義

settings.py

SOCIAL_AUTH_USER_MODEL = 'app.CustomUser'

models.py

from django.contrib.auth.models import AbstractUser, UserManager
from django.db import models


class CustomUser(AbstractUser):
    class Meta:
        app_label = 'app'

    # profile_picture_urlは別に無くてもいい
    profile_picture_url = models.URLField()
    # upload_toでprofilesを指定
    # 今回の場合./media/profilesになる
    profile_picture = models.ImageField(upload_to='profiles')
    
    objects = UserManager()
    
    def __unicode__(self):
        return self.username

CustomUserにImageFieldを持たせて、upload_to='path'で画像の保存先を指定。

pipelineを作る

まずは設定から

自分でpipelineを足す場合は、デフォルト + 自作pipelineにしないと正常に認証処理が通らない。

settings.py

SOCIAL_AUTH_PIPELINE = (
    'social.pipeline.social_auth.social_details',
    'social.pipeline.social_auth.social_uid',
    'social.pipeline.social_auth.auth_allowed',
    'social.pipeline.social_auth.social_user',
    'social.pipeline.user.get_username',
    # 'social.pipeline.mail.mail_validation',
    # 'social.pipeline.social_auth.associate_by_email',
    'social.pipeline.user.create_user',
    'social.pipeline.social_auth.associate_user',
    'social.pipeline.social_auth.load_extra_data',
    'social.pipeline.user.user_details',
    
    # 'social.pipeline.debug.debug',

    # ここから上はデフォルトなので必須
    # save_profile_pictureがこれから作るpipeline
    'app.pipeline.save_profile_picture',

pipelineを作る

SOCIAL_AUTH_PIPELINEで指定した場所に、pipelineを作る。
今回だと、./app/pipeline.py

pipeline.py

import os
from urllib2 import urlopen

from django.core.files.base import ContentFile

import settings


def save_profile_picture(backend, strategy, details, response ,user=None, *args, **kwargs):
    url = None
    if backend.name == 'google-oauth2':
        # 画像のurlを保存
        url = response['image'].get('url')
        # 拡張子を用意
        ext = url.split('.')[-1].split('?')[0]
    if url:
        user.profile_picture_url = url
        picture = os.path.join(settings.MEDIA_ROOT, 'profiles', '%s.%s' % (user.username, ext))
        # 既にプロフィール画像がある場合、削除
        if os.path.exists(picture):
            os.remove(picture)
        user.profile_picture.save('%s.%s' % (user.username, ext), ContentFile(urlopen(url).read()))
        user.save()

save_profile_pictureの解説

urlが、
https://………/photo.jpg?sz=50
となってたので、urlにsplit()を2回当てて、拡張子のみを取得。

./media/profiles/にusername.jpgとして保存するのだが、
ImageFieldは既に画像が存在した場合、
suffixが付きの別画像として保存されてしまう。

だから、プロフィール画像が既に存在する場合は削除してから、保存させる。

注意
今回はGoogleOauth2の場合。
twitterとかgithubを足したいなら、if文を追加。

おわりに

最初からプロフィール画像を保存するようにしてくれればいいのに…
と思って時期が私にもありました。