본문 바로가기

내일배움캠프

Django DRF과제 - 트러블슈팅

길고도 짧았던 장고 개인과제가 끝이 났다. 구현해 보고 싶은 기능은 더 많았지만(이메일인증이라던가...) 내일부터는 LLM과제를 시작해야하기 때문에 아쉽지만 기초 + 심화 기능 구현으로 마무리지으려고 한다.

 

그리고 과제의 마지막은 트러블 슈팅으로...

 

1. 슈퍼유저 생성 관련 문제

  • 장고에서 기본적으로 제공되는 유저모델을 상속받아서 커스텀 유저모델로 사용을 하던 중 문제가 발생을 했다. 커스텀 유저모델을 사용한 이유는 필요시에 다른 필드들은 손쉽게 추가하기 위해서였는데 필드를 추가할때마다 이전에 만들어두었던 admin계정에도 기본 값을 추가해야한다는 메시지가 발생했기 때문이다.
  • 여기까지는 사소한 문제라고 생각했다. 조금 귀찮긴해도 필드를 추가할때마다 admin계정에 따로 값을 설정해주면 된다고 생각했으니까. 다만 문제는 이 다음이었다.
  • 초보자에게 실수는 필연적인것이고 나 역시 마찬가지였다. 과제를 하던 중에 DB를 초기화 해야하는 일이 발생했고 나는 모든 migration과 db를 삭제한 뒤에 새롭게 DB를 정의하고 admin계정을 생성하려고 했다. 하지만 이전에는 기존 계정에 값을 추가해주었지만 이번에는 생성자체가 되지 않았다. admin계정또한 유저이기 때문에 User모델에서 지정한 모든 필수적인 필드들이 필요했다. 하지만 createsuperuser 명령으로는 username, password, email만 입력이 가능했다.
  • 가장 먼저 생각한 해결책은 반대로 생각하는 것이었다. 이전에는 이미 존재하는 admin계정에 필드를 추가하면서 admin에 따로 값을 추가해주었으니 이번에도 User모델에 추가한 필드들을 모두 지워준 뒤에 admin을 새로 생성하고 그 뒤에 필드를 추가하면 되는 것이었다. 아무튼 이렇게 해서 한동안은 정상적으로 과제를 진행할 수 있었다.
  • 하지만 임시방편 해결책의 한계는 명확했다. 상품에 카테고리를 추가하는 과정에서 요구사항중하나는 장고의 admin페이지를 통해서 카테고리들을 생성하는 것이 있었다. 즉, 일반 유저가 요청을 보내서 카테고리를 생성할 수는 없었다. 나는 당연히 새로 Caterogy라는 필드를 생성했고 Product 모델의 category필드의 외래키로 연결을 했다. DB를 새로 정의했기때문에 나는 migration을 하려고 했지만 category 는 필수 필드였고 아직 category에 입력될 수 있는 Caterogy의 값이 존재하지 않았기 때문에 나는 admin페이지를 통해서 Caterogy에 데이터를 생성해야했다.
  • 문제가 발생한 것은 바로 이때이다. 즉, DB가 초기화 되었고 -> admin페이지에 접근하려면 admin 계정이 필요하고 -> admin계정을 생성하기 위해 임시방편 해결책으로 User의 필수 필드들을 제외하고 다시 계정을 생성하려고 했지만 계정이 제대로 생성이 되지 않았고 admin페이지에 접근 할 수 없는 상황이 반복되었다. 해결하기 위해서는 우선 admin계정이 필요했다.
  • 결국 임시방편으로는 한계가 있다고 인정하고 근본적인 해결책을 찾기로 했다. create_superuser 를 오버라이딩하는 것이다. 내가 커스텀한 User모델은 AbstractUser라는 클래스를 상속받았는데 AbstractUser의 objects라는 변수는 UserManager라는 클래스를 지정하고 있었다. 나는 그 클래스에서  create_superuser라는 메서드를 발견했는데 그 메서드를 보니 아마도 이 메서드가 createsuperuser를 할때 기본값들에 관여하는것같았다.
def create_superuser(self, username, email=None, password=None, **extra_fields):
        extra_fields.setdefault("is_staff", True)
        extra_fields.setdefault("is_superuser", True)

        if extra_fields.get("is_staff") is not True:
            raise ValueError("Superuser must have is_staff=True.")
        if extra_fields.get("is_superuser") is not True:
            raise ValueError("Superuser must have is_superuser=True.")

        return self._create_user(username, email, password, **extra_fields)

 

나는 models.py로 돌아와서 UserManager를 상속받아서 CustomUserManager라는 클래스를 새로 정의했다. 그리고 create_superuser를 오버라이딩해서 User모델의 필수 필드에 대한 기본값들을 지정했다.

class CustomUserManager(UserManager):
    def create_superuser(self, username, email=None, password=None, **extra_fields):
        extra_fields.setdefault("is_staff", True)
        extra_fields.setdefault("is_superuser", True)
        
        if extra_fields.get("is_staff") is not True:
            raise ValueError("Superuser must have is_staff=True.")
        if extra_fields.get("is_superuser") is not True:
            raise ValueError("Superuser must have is_superuser=True.")
        
        extra_fields.setdefault('nickname', 'Admin')
        extra_fields.setdefault('birth_day', '2000-01-01')
        extra_fields.setdefault('first_name', 'Ad')
        extra_fields.setdefault('last_name', 'Min')
        return self._create_user(username, email, password, **extra_fields)

 

이렇게 해서 admin계정을 정상적으로 생성할 수 있게 되었다. 솔직히 오버라이딩은 그저 강의를 따라하는 수준에 불과했지만 이번 문제를 해결하면서 클래스에 대해서 더 잘 이해하게 된 것같다.

 

2. 'password' 유저

 

 비밀번호를 변경하는 url을 api/accounts/password/ 라고하고 기능을 테스트하는데 계속해서 유저가 존재하지 않는다는 메시지를 출력하는 문제가 있었다. 문법적 오류가 있는지도 검증해보았고 오타가 발생했는지도 찾아보았지만 문제가 제대로 해결되지 않았다. 문제해결방법은 오류메시지에서 찾을 수 있었는데 메시지를 자세히 읽어보니 데이터가 존재하지 않는다는 이해할 수 없는 메시지만 반복되었다. 비밀번호를 변경하는데 데이터가 존재하지 않는다는 이야기가 대체 뭐지? 라고 생각하던 중, url을 설정할 때, 실수를 했다는 것을 알게되었다.

 api/accounts/password 를 작성하기 전에 유저의 정보를 조회하기 위한 데이터를 전송하는 url을  api/accounts/<str:username>/ 으로 설정을 해두었는데 이것이 api/accounts/password/ 보다 위쪽에 존재하는 것이 문제였다. 파이썬은 코드를 한줄단위로 순서대로 처리하는데 password를 password라는 문자열로 먼저 인식하여 'password'라는 유저의 유저정보를 조회하는 url주소로 연결되었기 때문이다.

고민은 오래했지만 문제는 쉽게 해결할 수 있었다. 그냥 두 코드의 위치를 바꿔주기만 하면 되는 것이었다.

 

 

3. 트러블 슈팅 그 자체

 

팀프로젝트가 아니라 혼자서 개인적으로 과제를 진행하면서 생긴 문제점이다. 팀으로 진행을 할때에는 서로 공부하는 입장이었기때문에 문제가 발생하면 그 즉시 트러블슈팅을 작성하여 다른 팀원들과 공유를 했지만 혼자서 프로젝트를 진행해보니 팀원들에게 공유할 필요가 없어졌기 때문에 트러블슈팅 자체보다 트러블 해결에 더 중점을 두게 되었고 어떻게 문제를 해결했는지를 적어놓지 않아 나중에 그것은 문서화하는 것이 힘들었다. 위에 써둔 내용도 가장 해결하는데 오래 걸렸던 문제였기 때문에 기억에 남아서 작성이 가능했던것.

 

이것을 바탕으로 다음부터는 트러블슈팅을 문제 발생 즉시 작성해야겠다는 생각이 들었다.