Django Social Auth: now with images

16 августа 2011

В рамках текущего проекта возникла задача при регистрации пользователей через социальную сеть кроме их контактных данных получать еще и изображение. Небольшое расследование показало, что каждая сеть практикует свой метод отдачи изображений, что в результате вылилось в следующий код, который вызывается в обработчике сигнала pre_update:

from social_auth.signals import pre_update

@receiver(pre_update)
def update_person_details(sender, **kwargs):
    person = kwargs.get('user')
    details = kwargs.get('details')

…

    load_person_avatar(sender, person, kwargs.get('response'))
…

def load_person_avatar(sender, person, info):    
    image_url = None
    
    if sender.name == 'vkontakte-oauth2':
        vk_response = info.get('response')
        if vk_response:            
            image_url = vk_response.get('user_photo') # If photo is absent user_photo is absent too
    
    elif sender.name == 'odnoklassniki':
        image_url = info.get('pic_2')
        if 'stub' in image_url: # No real image
            image_url = None
    
    elif sender.name == 'mailru-oauth2':
        if info.get('has_pic'):
            image_url = info.get('pic_big')
        
    elif sender.name == 'twitter':
        image_url = info.get('profile_image_url')
        if not 'default_profile' in image_url:
            image_url = image_url.replace('_normal', '_bigger')
        else: # No real image
            image_url = None

    elif sender.name == 'yandex-oauth2':
        image_url = info.get('userpic')
    
    elif sender.name == 'facebook':
        image_url = 'http://graph.facebook.com/%s/picture?type=large' % info.get('id')
    
    if image_url:
        try:
            image_content = urlopen(image_url)
            
            # Facebook default image check
            if sender.name == 'facebook' and 'image/gif' in str(image_content.info()):
                return
            
            image_name = default_storage.get_available_name(person.avatar.field.upload_to + '/' + str(person.id) + '.' + image_content.headers.subtype)
            person.avatar.save(image_name, ContentFile(image_content.read()))
            person.save()
        except Exception:
            pass # Here we completely do not care about errors

Примечания:

  1. В качестве backend для Вконтакте был выбран OAuth2 вариант — он в отличие от OpenAPI предоставляет информацию об изображении
  2. В качестве библиотеки для поля изображения мы испольуем sorl.thumbnail

Ответы на: Django Social Auth: now with images

  • Willi пишет:

    Август 22, 2011

    Здорово спасибо больше это иммнно то что мне нужен. Я только не понял куда вставить надо

    • krvss пишет:

      Август 22, 2011

      Без разницы :) У меня это в файле модели пользователя — там все остальные операции с пользователями лежат, так что место подходящее.

  • maxmoriss пишет:

    Август 22, 2011

    Не сохраняются аватарки (вобще ничего не происходит), не могу понять почему, возможно проблема в этих строчках:

    image_name = str(person.id) + '.' + image_content.headers.subtype
    person.avatar.save(image_name, ContentFile(image_content.read()))
    person.save()

    Код полнстью тут: https://gist.github.com/1163408

    • krvss пишет:

      Август 23, 2011

      max,

      1. Как объявлено поле avatar в модели?
      2. Что говорит трассировка — какие значения принимают image_url, image_name и image_content?

      • maxmoriss пишет:

        Август 24, 2011

        Поле avatar объявлено так:
        avatar = models.FileField(upload_to=’avatars’, blank=True, null=True, default=»avatars/no_avatar.png», verbose_name=u»Аватар»)

        Сори за дурацкий вопрос, как протрассировать image_content ?
        Я попробовал заполнить другое поле, и ничего, такое чувство что сигнал не принимается…

        • krvss пишет:

          Август 24, 2011

          Трассировать как обычно — через pdb:
          import pdb; pdb.set_trace() в том месте кода, которое хочется посмотреть. Лучше всего поставить перед image_content = urlopen(image_url). Как код остановится — p image_url

          max, если ты прочитал то, что я написал и не понял — значит тебе срочно нужно читать про трассировку кода — это must :) Не знать это на первых порах — нормально, потом — большой грех.

          • max пишет:

            Август 29, 2011

            Сигнала нет!
            Пробовал socialauth_registered, все отлично перехватывается.
            Можно ли данный функционал перенести в другой сигнал (я смотрю там параметры разные…)

          • krvss пишет:

            Август 29, 2011

            Очень странно — оба этих сигнала вызываются из одной функции, более того — сначала вызывается pre_update, в social_auth.backends.__init__.py, метод update_user_details:


            signal_response = lambda (receiver, response): response

            kwargs = {'sender': self.__class__, 'user': user,
            'response': response, 'details': details}
            changed |= any(filter(signal_response, pre_update.send(**kwargs)))

            # Fire socialauth_registered signal on new user registration
            if is_new:
            changed |= any(filter(signal_response,
            socialauth_registered.send(**kwargs)))

            В твоем коде https://gist.github.com/1163408 — update_person_details не вызывается?

            Переносить куда-то не советую, советую разобраться :) Потому что больше нареканий не было ни у кого, а более правильного места нет.

  • Willi пишет:

    Август 23, 2011

    Привет Стас ещё раз. Спасибо за ответ и проделанню работу. Попробовал вставить на разные место но у меня всё таки не получилось вставить этот скрипт. Я просто получаю 500 Internal Server Error Я ещё новичок в Django но socialauth я успешно установил. Это строка

    load_person_avatar(sender, person, kwargs.get(‘response’))

    надо где-то в Backend вставить? У меня стандартный модел ползователя от Django (Django 1.2) и профил. Может мне надо писать user.save не person.save?

    или тут неправильно выбрал куда картика вставляю (Я sorl.thumbnail утсановил)
    image_name = default_storage.get_available_name(person.avatar.field.upload_to + ‘/’ + str(person.id) + ‘.’ + image_content.headers.subtype)
    person.avatar.save(image_name, ContentFile(image_content.read()))

    Подскажешь что-нубудь или вставишь пример в Коммите?

  • krvss пишет:

    Август 23, 2011

    Willi,

    Вставлять в этот код ничего не надо — он лучший пример, так как взят из живого проекта, где постоянно работает :)

    Я не совсем понял один момент — если у тебя стандартный профиль, то где же объявлено поле avatar?

    Тебе нужно
    1. Завести свою модель для пользователя — как это сделать написано, например, здесь: http://scottbarnham.com/blog/2008/08/21/extending-the-django-user-model-with-inheritance/

    2. Указать эту новую модель в настройках django-social-auth: CUSTOM_USER_MODEL = модель

    3. Для поля аватар нужно либо использовать встроенный тип ImageField, либо ImageField из sorl.thumbnail.
    Вот пример объявления поля:


    class Person(User):
    avatar = ImageField(upload_to='upload/avatar/%Y/%m/%d', verbose_name=u'Моя фотография', blank=True, null=True)

  • Willi пишет:

    Август 29, 2011

    Спасибо за подсказку теперь всё заработало. Декоратор @receiver не работал в Django 1.2 скопровал туда папку dispatch от Django 1.3 и всё зароботало (Мне просто надо 1.2) Тоже сначало на сообразил что надо быловставить этот from django.core.files.base import ContentFile и from django.dispatch import receiver

    Спасибо Стас очень помог и тоже чему-то новому научился.

  • max пишет:

    Август 30, 2011

    Победил я эту проблему! Сел, повнимательнее присмотрелся и заметил что urlopen у меня как-то криво импортируется, собственно в этом и было дело.
    Сейчас c Facebook и Одноклассников все прекрасно загружается, а вот из Вконтакте почему-то не хочет… У тебя работает? Может они API поменяли или еще чего…

  • Александр пишет:

    Ноябрь 21, 2011

    Привет, спасибо за код
    Создал модель, как описано выше
    Сделал CUSTOM_USER_MODEL= ‘app.CustomUser’ в settings.py
    Добавил бекенд для юзера
    Насколько я понимаю штатный пользователь теперь должен автоматически подменяться кастомным

    class CustomUser(User):
    avatar = ImageField(upload_to=’upload/avatar/%Y/%m/%d’, verbose_name=u’Моя фотография’, blank=True, null=True)
    objects = UserManager()

    , в котором уже есть поле avatar: person = kwargs.get(‘user’). Но нет его, может что-то неправильно делаю?

    • krvss пишет:

      Ноябрь 21, 2011

      А где его нет? :)

      Когда ты добавляешь кастомную модель, в результате в БД будут две таблицы, одна из них джанговская стандартная, а вторая — твой CustomUser (кстати syncdb не забыл сделать?)

  • Александр пишет:

    Ноябрь 21, 2011

    Syncdb сделал, в результате появилась вторая таблица, в которой уже есть поле avatar, однако при создании пользователя заполняется старая таблица, новая остается пустой

    • krvss пишет:

      Ноябрь 21, 2011

      Если ты имеешь в виду создание пользователя при авторизации через DSA, то нужно еще сделать настройку SOCIAL_AUTH_USER_MODEL = ‘myapp.CustomUser’

  • Александр пишет:

    Ноябрь 21, 2011

    ага, сделал. Написал также бекенд:
    class CustomUserModelBackend(ModelBackend):
    def authenticate(self, username=None, password=None):
    try:
    user = self.user_class.objects.get(username=username)
    if user.check_password(password):
    return user
    except self.user_class.DoesNotExist:
    return None

    def get_user(self, user_id):
    try:
    return self.user_class.objects.get(pk=user_id)
    except self.user_class.DoesNotExist:
    return None

    @property
    def user_class(self):
    if not hasattr(self, ‘_user_class’):
    self._user_class = get_model(*settings.CUSTOM_USER_MODEL.split(‘.’, 2))
    if not self._user_class:
    raise ImproperlyConfigured(‘Could not get custom user model’)
    return self._user_class

    Появляется ошибка:

    AssertionError: ForeignKey(None) is invalid. First parameter to ForeignKey must be either a model, a model name, or the string ‘self’

    Создание пользователя что ли нужно описывать в бекенде?

    • krvss пишет:

      Ноябрь 21, 2011

      backend’a я не делаю. Кстати, загляни в пример, там по-моему кастомная модель используется тоже и все ок.

      • Александр пишет:

        Ноябрь 22, 2011

        В примере ведь не написано SOCIAL_AUTH_USER_MODEL = ‘app.CustomUser’
        А когда его пишешь в сетинг, сразу валится ошибка:

        class UserSocialAuth(models.Model):
        File «/home/alex/Envs/coposition/local/lib/python2.7/site-packages/social_auth/models.py», line 27, in UserSocialAuth
        user = models.ForeignKey(User, related_name=’social_auth’)
        File «/home/alex/Envs/coposition/local/lib/python2.7/site-packages/django/db/models/fields/related.py», line 822, in __init__
        assert isinstance(to, basestring), «%s(%r) is invalid. First parameter to ForeignKey must be either a model, a model name, or the string %r» % (self.__class__.__name__, to, RECURSIVE_RELATIONSHIP_CONSTANT)
        AssertionError: ForeignKey(None) is invalid. First parameter to ForeignKey must be either a model, a model name, or the string ‘self’

        • krvss пишет:

          Ноябрь 22, 2011

          Магия :) Я убрал комментарий с SOCIAL_AUTH_USER_MODEL = ‘app.CustomUser’ в примере, сделал syncdb на всякий случай — и все окей, работает без ошибок.

  • tenoclock пишет:

    Декабрь 6, 2012

    Друзья, простите, но я просто не понимаю откуда брать «info». Что-то протормаживается у меня очень. Расскажите, пожалуйста.

    • krvss пишет:

      Декабрь 7, 2012

      info определяется в функции def update_person_details(sender, **kwargs) — обработчике сигнала pre_update от Django Social Auth. Она там в коде выше. На всякий случай уточняю, что сама по себе функция load_person_avatar работать не будет :)

  • Nabu пишет:

    Февраль 25, 2014

    Если кто забредет сюда:

    следует добавить в settings.py
    VK_EXTRA_DATA = [‘photo_big’]
    И дальше работать уже с этим адресом
    image_url = vk_response.get(‘photo_big’)
    Т.к. размеры картинки отдаваемой вконтактом по умолчанию больше похожи на издевку…

Оставить ответ