내일배움캠프 20일차

2023년 04월 12일 by barryjung

    내일배움캠프 20일차 목차

[오늘 한일]

  • 팀 프로젝트 진행
  • 선발대 특강 수강

 

[오늘 배운점]

 

<기본 유저모델에 추가 필드 만들기>

일과 시작 전에 팀 프로젝트에 문제점이 없나 살펴봤는데,

회원가입 기능에서 사용자 정보란이 입력은 되나 DB에 저장은 안되는 문제를 발견했다.

 

우리는 장고 기본 회원가입을 사용하고 있었다.

모델과 폼의 모습은 이러했다.

#model.py
(작성내용 없음)

#forms.py
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User

class UserForm(UserCreationForm):
	bio = forms.CharField()

    class Meta:
        model = User
        fields = ['username', 'password1', 'password2', 'bio']

모델은 작성내용이 없었고,

장고의 기본 회원가입 폼과, 모델을 가져와서 그대로 작성한 형태였다.

 

문제를 알고나서 이부분을 보니 문제점이 보였다.

지금으로써는 사용자 정보(bio)는 폼의 그냥 한 구성요소가 됬을 뿐이지,

진짜 모델에 필드로 정의 되지 않은 것이다.

 

이부분을 고치기 위해 모두가 다 달려들었다.

나는 내 로컬에서 쭉 수정을 해봤는데 시행착오를 겪으며 알게된 내용을 적어보겠다.

결론부터 말하자면 도달한 해결 방법은 간단했다.

(모델을 별로도 만들며, User모델 말고 AbstractUser를 사용하는 것이다.)

하지만 아래는 시간순으로 적어본다.

 

 

나는 먼저 Model.py를 만들어줬다.

이걸 해줘야 한다는건 의심의 여지가 없었다.

from django.db import models
from django.contrib.auth.models import User

class UserModel(User):
    bio = models.TextField("자기소개", blank=True)

이렇게 User를 상속하는 우리만의 UserModel을 만들고, 여기에다가 bio를 줬다.

그리고 모든 부분에서의 User모델명을 UserModel로 바꿔줬다.

 

makemigrations를 하는데 빨간줄 여러줄의 에러가 나왔다.

tweet.TweetModel.user: (fields.E304) Reverse accessor 'User.usermodel' for 'tweet.TweetModel.user' clashes with reverse accessor for 'user.UserModel.user_ptr'.
        HINT: Add or change a related_name argument to the definition for 'tweet.TweetModel.user' or 'user.UserModel.user_ptr'

이런 에러 메세지였다.

내용을 기억해보면 장고의 기본 파일 동작들이 나열 됬었고,

tweet.TweetModel.user에서 역참조 접속자가 에러를 일으키고 있다는 내용이다.

TweetModel의 user는 유저데이터를 참조하는 외래키필드 였는데 related_name이 usermodel이였다.

그래서 User를 UserModel로 바꿔줬더니 바로 에러가 나왔던 것이다.

related_name을 tweet으로 적절히 고쳐줘서 해결할수 있었다.

 

 

그러고 나자 makemigrations와 migrate가 잘 실행되었고, 서버를 키고 웹으로 테스트 해보니,

이번엔 다른데서 문제가 생겼다.

어제 한번 난관을 안겨줬던 create_tweet기능에서 어제 봤던 에러중 하나가 나온 것이다.

 

에러 메세지는 이렇다.

Cannot assign "<SimpleLazyObject: <User: wjdgusrbs5>>": "TweetModel.user" must be a "UserModel" instance.

simplelazy 오브젝트는 허가되지 않는다.

TweetModel.user에는 오직 UserModel 인스턴스만 와야한다는 뜻이다.

 

다만, 어제도 이 에러는 나왔었다. 즉 이 부분이 문제가 아니다.

create_tweet기능에서는 request.user를 저 TweetModel.user에 대입하는데,

어제도 그랬던것처럼 requset.user의 타입을 다시 찍어보니, 역시 SimplelazyObject였다.

 

데이터 필드만 올바르다면 SimplelazyObject여도 대입은 문제없이 되고 있었다는 것이다.

그럼 우리가 조작한 부분인 데이터 필드에 어떤 문제가 생긴것일까.

 

from user.models import UserModel

class TweetModel(models.Model):
(중략)
    user = models.ForeignKey(
        UserModel, on_delete=models.CASCADE, verbose_name="이름", related_name='tweet')

우리가 작성한 TweetModel의 모습은 이렇다.

(위에서 얘기한대로 related_name을 tweet으로 변경해줬다.)

그리고 그전, 외래키로써 참고하는 모델부분이 User였는데,

우리가 만든 UserModel로 바꿔준 것이다.

 

User일때는 에러가 안났는데 왜 UserModel이 되니까 에러가 나는 가에 집중해봤다.

그렇다면 다시 User를 다시 줘봤다.

유저 모델을 다시 롤백한게 아니라, 그대로 둔채 tweet.model에서만

from django.contrib.auth.models import User를 추가해주고 저 user필드가 User를 참조하도록 설정해봤다.

makemigrations, migrate를 하고 실행한 결과 원래 동작하던대로 문제없이 동작한다.

 

 

여기서 나는 auth.model User와 우리가 만든 UserModel이 같은 곳을 지칭한다고 오해할뻔 했다.

그도 그럴것이 내 테스트환경에서는 UserModel에 class meta도 없었던 것이다.

그러다, DB를 들여다보니 상황이 확 정리됬다.

 

auth_user에는 auth_user가 입력한 유저가 쭉 생성되있고,

UserModel_model에 bio만 따로 떨어져서 저장되고 있었던 것이다.

임시 이름같은 데이터 모델이 생겨있고 거기에는 bio만 저장되고 있었다.

그래서 위 문제가 일어났던 것이다.

 

나는 DB모델을 제대로,

auth.model User를 온전히 상속받아 새로운 모델을 만들어 보려 시도했다.

model.py에서 UserModel에 class meta도 부여하고 별도의 이름을 줬다. 

class UserModel(User):
    class Meta:
        db_table = "my_user"
        
    bio=...(생략)

그런데 migrate결과는 똑같았다.

이번에는 my_user라는 이름으로 만들어 지긴했으나, 가진 필드는 bio뿐이였고,

auth_user모델이 생성되어 auth_user로 새 유저가 입력됬다.

 

 

여기까지 보고 우리는 참고하는 auth.model에서 

User말고 AbstractUser로 바꿔보자는데 의견을 모았고,

그렇게 바꿨더니 놀랍게도 문제가 해결되었다.

 

알고보니 auth.model의 User와 AbstractUser는 정확히 이런 차이가 있는 두 기본 모델이였던 것이다.

AbstractUser는 커스텀한 모델을 만들때,

auth.model의 필드들을 만든 모델에 모두 상속해주어 별도이름의 모델 생성을 가능하게 해준다는 것이다.

!!!!

 

결론, User와 AbstractUser의 개념과 차이를 알았다면 한방에, 아니 문제가 생기지도 않았을 것이다.

다만 시행착오를 통해 두 모델의 동작 차이를 정말 확실히 알게 되었다.