django 사용자 정의 User 만들기

One To One Field와 signal 이용 방법

프로그래밍
SungYong SungYong

Jan. 16, 2024, 1:10 a.m.

django를 이용해 웹사이트를 개발하면,  django에서 기본 제공하는 User 모델을 이용하여 회원가입, 로그인, 로그아웃 등의 authentication 관련 기능을 쉽게 구현할 수 있다. 그러나 웹사이트마다 수집해야 하는 회원 정보가 다르기 때문에 결국은 기본 User 모델만으로는 불충분하게 된다. 

개발자들마다 회원 정보를 담기 위해 여러가지 방법을 사용하고 있는데, User모델을 상속받아 Custom User 모델을 만드는 여러가지 방법을 쓰거나, Profile같은 모델을 새로 만들고, 여기에서 User 모델과 One-to-One 필드로 1:1로 연결하는 방법을 많이 쓴다. 나도 예전에는 여러가지 방법 중에서 고민을 했었지만, 지금은 아예 고민하지 않고 후자, 즉 Profile모델 만든 뒤 One-to-One 필드를 사용하는 방법만 택하고 있다. 기본 User 모델에 수정을 가하는 순간, 장고에서 기본 제공하는 수많은 기능들을 못쓰게 될 가능성이 발생할 뿐만 아니라, migration의 난이도도 올라가기 때문에 나 조차도 정신이 복잡해지고, 특히 장고나 파이썬에 익숙치 않은 다른 사람과 작업해야 할 때는 기술의 허들이 더 높아지기 때문이다. 

최근 공부+연습 삼아 만들고 있는 웹사이트의 Profile 모델은 아래와 같이 정의했다. 아래 내용은 python manage.py startapp accounts로 accounts 앱을 만든 뒤, accounts 폴더 내의 models.py에 정의한 내용이다.  

class Profile(models.Model):
user = models.OneToOneField('auth.User', on_delete=models.CASCADE)
nickname = models.CharField(max_length=20, default='-', blank=True, null=True)
phone = models.CharField(max_length=20, blank=True, null=True)
address = models.CharField(max_length=200, blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def get_avatar(self):
if self.avatar_set.exists():
return f'{BASE_URL}{self.avatar_set.last().avatar.url}'
else:
return get_avatar_url(self.user.pk)
def __str__(self) -> str:
return f'{self.user.username}\t{self.pk}'


이처럼 Profile과 같은 모델을 따로 만들면, 본인이 원하는 필드와 메소드를 추가할 수 있다. nickname, phone, address 등의 필드가 연습용도로 추가되었으며, 사용자 아바타를 보여주기 위한 get_avatar 메소드도 추가했다. (Avatar라는 모델이 Profile 모델 아래에 정의되어 있으나, get_avatar 메소드가 하는 역할은 이 글에서 중요하지 않으므로 넘어가자.)

문제는 새로운 회원이 가입을 해서 User 인스턴스 (DB기준으로는 row)가 생성된다 해도, 그 User와 one-to-one 관계인 Profile이 추가되지 않는다는 점이다. 

이런 문제를 해결하기 위해 signals의 post_save를 사용한다. 

from django.db.models.signals import post_save

# 중략

def create_profile(sender, instance, created, **kwargs):
if created:
profile = Profile.objects.create(user=instance)
profile.nickname = instance.username
profile.save()
post_save.connect(create_profile, sender='auth.User')


 

이렇게 create_profile 함수를 정의한 뒤, post_save에서 sender를 'auth.User'로 설정하면, 새로운 User 인스턴스가 생성될 때, create_profile 함수가 자동으로 실행되면서, Profile모델의 user 필드를 지금 막 생성된 User 인스턴스로 채워 새로운 profile 인스턴스를 생성한다. 

이때, Profile의 nickname을 User의 username으로 설정하고 싶다면, profile.nickname = instance.username 으로 한 뒤, 저장하면 된다. 

이렇게 하면 어떤 방식으로 User 인스턴스가 새로 생성되든 Profile이 새로 생성되고 그 user 인스턴스와 one-to-one 관계가 설정된다. 


위의 코드를 receiver를 이용해 직접적으로 표현해줄 수 있다. 

from django.dispatch import receiver
# 중략

@receiver(post_save, sender='auth.User')
def create_profile(sender, instance, created, **kwargs):
if created:
profile = Profile.objects.create(user=instance)
profile.nickname = instance.username
profile.save()
# post_save.connect(create_profile, sender='auth.User')


어떤 방식으로 표현하든 결과는 동일하다. 나는 지금 쓴 reciever를 쓰는 방법이 더 깔끔하다고 생각해서 주로 이렇게 쓰려고 한다. 

Leave a Comment: