본문 바로가기

프로그래밍/Django

[Django] Django allauth 유저 모델 커스텀하기

네이버로그인

소셜계정으로 회원가입한 유저 구분하기

제목을 커스텀 유저모델이라고 썼지만 사실 유저 인스턴스를 커스텀하는 것에 더 가깝다. 현재 만들고 있는 프로젝트에서 소셜계정으로 로그인하는 유저에게 특정 필드값을 부여하고 싶었다. 예를 들면 네이버로 로그인 하는 유저에게는 N이라는 값을, 페이스북으로 로그인하는 유저에게는 F 값을 주고 싶었다. 나중에 어떤 곳에서 유입이 더 많이 될지 살펴볼 수 있지 않을까?

  1. 처음에 혼란스러웠던 점은 어디서부터 시작해야 할지 모르겠다는 점이었다. 라이브러리 활용이 처음이었기 때문에 아예 백지 상태였다. 결론부터 말하자면 django-allauth에 어딘가를 오버라이딩해야 한다.


  1. django allauth custom usermodel이라고 검색하니 다시 공식문서로 회귀하게 되었다. 문서를 뒤져보니 Custom User Model이라는 항목이 있었다. (http://django-allauth.readthedocs.io/en/latest/advanced.html)


  1. 공식문서를 확인해보니 DefaultAccountAdapter도 있고, DefaultSocialAccountAdapter라는 항목도 있었다. 둘의 차이점을 알기 위해서 직접 소스코드를 열어봐야 했다. 유저를 저장할 때 커스텀하고 싶었기 때문에 save_user 메서드를 살펴봤다.

  • DefaultAccountAdapter
    스크린샷, 2017-07-22 15-54-32

  • DefaultSocialAccountAdapter
    스크린샷, 2017-07-22 15-54-44

  • 하나는 form을 이용한 회원가입을 할 때였다. 다른 하나는 소셜계정을 이용해 자동 회원가입(auto-signup)이 될 때 save_user 메서드가 작동한다. 나는 소셜계정을 이용한 회원가입을 할 것이므로 DefaultSocialAccountAdapter을 활용해야 한다는 결론을 얻었다.

  1. 상속까지는 하겠는데, 다음은 오버라이딩이 문제였다. 해보지 않았기에 여러 시행착오들을 겪었다. 나의 개념부족을 탓해야지.

    class SocialAccountAdapter(DefaultSocialAccountAdapter):
      def save_user(self, request, sociallogin, form=None):
          """
          allauth를 통해서 유저를 저장할 때 호출됩니다. 유저 객체에 추가적인 정보를 담기 위해서 오버라이드를 실시했습니다.
          """
     
          user = super(SocialAccountAdapterself).save_user(requestsocialloginform)

    메소드 오버라이딩을 진행하기 위해서 이 글을 참조했다. 결론은 부모의 메서드를 불러오기 위해서 super()를 활용해야 한다는 것이다.

  2. 이 메서드는 3개의 인자를 받는데, request, sociallogin, form=None이다. 딱봐도 sociallogin을 뒤져봐야 뭔가 나올것 같다는 느낌을 가져야한다. dir(sociallogin)으로 일일이 print해서 뒤져보는 노가다를 했다.
    dir(sociallogin)

    dir(sociallogin :  '__class__''__delattr__''__dict__''__dir__''__doc__''__eq__''__format__''__ge__''__getattribute__''__gt__''__hash__''__init__''__init_subclass__''__le__''__lt__''__module__''__ne__''__new__''__reduce__''__reduce_ex__''__repr__''__setattr__''__sizeof__''__str__', '__subclasshook__', '__weakref__', 'account', 'connect', 'deserialize', 'email_addresses', 'get_redirect_url', 'is_existing', 'lookup', 'save', 'serialize', 'stash_state', 'state', 'state_from_request', 'token', 'unstash_state', 'user', 'verify_and_unstash_state']


  1. 여기서 다시 어떤걸 뒤져봐야 할지 감을 잡아야 한다. 나는 처음에 감이 안잡혀서 반을 dir()로 확인해봤다. 그중 눈에 띈 것이 account라는 항목이었다. 한국어로 말하면 계정이다. 여기를 다시 뒤져본다.
    dir(sociallogin.account)

    dir(sociallogin.account) : ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'account', 'connect', 'deserialize', 'email_addresses', 'get_redirect_url', 'is_existing', 'lookup', 'save', 'serialize', 'stash_state', 'state', 'state_from_request', 'token', 'unstash_state', 'user', 'verify_and_unstash_state']


  1. 오호, 여기까지 접근하니까 대충 DB에 저장되기 위한 어떤 것들이 모인다는 것을 짐작할 수 있다. 다시 뭘 골라야할까. extra_data였다. 이것을 print함수로 찍어보니까 드디어!! 원하는 결과값들이 전송되었따.
    스크린샷, 2017-07-22 16-17-22
    이제 비밀을 밝혀냈으니, 네이버 로그인인지, 페이스북 로그인인지 알아내야 했다.


  1. 이것은 sociallogin.account.proivder에 숨어있었다. 이를 이용해서 뷰를 담음과 같이 작성했다.

    class SocialAccountAdapter(DefaultSocialAccountAdapter):
      def save_user(self, request, sociallogin, form=None):
     
        user = super(SocialAccountAdapterself).save_user(requestsocialloginform)
     
        social_app_name = sociallogin.account.provider.upper()
     
        if social_app_name == "FACEBOOK":
          User.objects.get_or_create_facebook_user(user_pk=user.pk, extra_data=extra_data)
     
        elif social_app_name == "NAVER":
          User.objects.get_or_create_naver_user(user_pk=user.pk, extra_data=extra_data)


  1. 모델로 넘어와서 UserManager를 커스텀했다.

    from django.contrib.auth.models import UserManager as DefaultUserManager
     
    class UserManager(DefaultUserManager):
        # 페이스북으로 가입하면 user_type을 F(Facebook)으로 지정한다. 
        def get_or_create_facebook_user(self, user_pk, extra_data, <):
            user = User.objects.get(pk=user_pk)
            user.user_type = "F"
            user.profile_image = 
            user.save()
            
            return user
     
        # 네이버로 가입하면 user_type을 N(Naver)으로 지정한다. 그외에 커스텀 저장을 한다. 
        def get_or_create_naver_user(self, user_pk, extra_data):
            user = User.objects.get(pk=user_pk)
            user.username = extra_data['name']
            user.nickname = extra_data['nickname']
            user.email = extra_data['email']
            user.first_name = extra_data['name'][0]
            user.last_name = extra_data['name'][1:]
            user.profile_image = extra_data['profile_image']
            user.user_type = "N"
            user.save()
     
            return user

    facebook이 주는 프로필 정보와, naver가 주는 프로필 정보가 달랐다. 따라서 입맛에 맞게 조정했다. 다소 방법이 좀 지저분해보이는데, 좀 더 좋은 의견 있으면 알려주시면 감사하겠습니다.

Adapter

소셜계정에는 페이스북도 있을 것이고, 네이버도 있을 것이고, 인스타그램도 있을 것이다. django-allauth에서 다루는 소셜계정만 66개다(2017-07 기준). 소셜계정마다 회원가입을 다르게 처리하면 그것은 엄청난 소스코드 낭비다. 가장 핵심이 되는 회원가입 필드만을 모아서 회원가입을 공통으로 처리할 수 있는 어댑터가 필요하다. 결국 Adapter로 모인다.