DRF로 SNS 클론코딩 후기

이주환·2023년 10월 28일

파이썬 냉장고

목록 보기
1/3
post-thumbnail

기능을 추가 할 땐 오버라이딩 직렬화 구성

정의 된 모델 필드에서 필요한 함수 구현

  • 💬  코드

    • serializers.py

      class FriendSerializer(serializer.ModelSerializer):
              when_request = serializers.SerializerMethodField()
      
              def get_when_request(self, obj):
                      created_at = obj.created_at.date()
                      return created_at
    • 위 코드에서 MethodField를 정의 하면, *get_필드네임 을 찾는다. 그렇기 때문에, 저런식으로 네이밍을 해주면 인자 값을 넘겨주지 않아도 사용 할 수 있다.

    • object는 메타클래스에서 사용하는 모델 클래스이다.

시리얼라이저는 결국, 파이썬에서 만든 클래스를 다른 어플리케이션에서 읽기 위함이다. 그렇기 때문에 웹에 보여지는 부분을 커스텀 한다고 생각 하면 편하다.

위 내용을 작성 하고 나서, 강의를 보고 나니 같은 얘기를 하고있다. 하지만, DRF를 그래도 어렵게 만드는건 시리얼라이저이니 공부가 필요한 객체 중 하나이다.


모델링은 단순 데이터베이스를 관리하는 모델 일 뿐이다

비즈니스 로직에 걸맞는 ER을 하고 나면, 그 관계도를 정의 하는 것이 모델링이다. 이 땐 테이블이 갖춰야 할필드를 명시 하는데 그 필드가 관계를 어떻게 나타내는지 정도만 필요할 뿐 모델링을 어렵게 느낄 필요가 없었다.


모델 뷰 세트를 이용하면 CRUD를 알아서 해준다

Model Viewset에 대해 자세히 알고 있지 않았지만, Django Rest Framework 핵심사항 강의를 보고 난 후 살짝 알게 된 계기가 되었다. 모델 뷰 셋은 프로젝트의 시작 단계에서 굉장히 빠른 빌드를 요구할 때 사용 하며, 그 이후 요구사항에 맞춰 Generic한 API view 들과 ModelViewSets을 사용 하여 커스터마이징 해 나가는 것이 중요하다. 그렇다면, 우리가 흔히 알고 있는 API View는? 이제 이 기능은 위의 제너릭한 뷰 들로 처리할 수 없는 기능을 구현 할 때 사용 하지만, 대부분 API View 까지 처리하지 않아도 모든 기능을 지원 받을 수 있다.


One To One Field를 직렬화 하기

OneToOne Field와 ForeingKey는 인터넷에서 많이 떠돌았던 문제 중 하나였다. 사실, 1:N이냐 1:1이냐의 차이 이지만, 실제 사용 해보니 차이를 알기 어려웠다. 더 깊게 차이점을 공부하고 싶다면 더 검색하고 알아보면 되고, 이 문단에서 배운 점을 공유 하자면 OneToOne필드에서 관계를 짓고 있는 필드를 사용 하고 싶었다.
하지만, 접근을 잘못 했기 때문에 included 에러가 났었다.

데이터 모델링 구조

유저: 회원가입시 간단한 정보를 담고 있음

유저 프로필 → 유저를 OneToOneField로 참조중

요구사항

  1. 유저 프로필은 이름 필드가 필요 없으니, 유저에서 가져와야지

첫번째 접근 방식

  • Serializer의 source를 사용하여 참조 필드 가져오기
    • 💬  코드
      • ForeignKey에서 접근 했던 것 처럼 똑같이 해야겠다.
        sender_name = serializers.EmailField(source="sender.name", read_only=True)
        included 에러 발생 이렇게, source 내부에 참조 되고 있는 필드 명(sender)에서 참조한 모델 객체의 필드 명을 불러온다.

해결 방식

  • 유저라는 시리얼라이저 생성, 유저 프로필 시리얼라이저에서 인스턴스화

    • 💬  코드

      • serializers.py

         class UserProfile(serializers.ModelSerializer):
             class Meta:
                 model = UserProfile
                 fields = "__all__"
        
         class UserProfileSerializer(serializers.ModelSerializer):
             profile = UserProfile()
        
             class Meta:
                 model = CustomUser
                 fields = ["name", "profile"]
        • views.py
        class ProfileViewSet(viewsets.ModelViewSet):
            queryset = UserProfile.objects.all()
            serializer_class = UserProfileSerializer

        이렇게 하면, 유저 프로필에서는 모든 정보를 가져올 수 있고 커스텀 유저의 이름만 가져올 수 있다.

        DRF 만세,,,✌️

      👉 대충 이런 그림이 나온다

    • DRF 이미지


DRF decorator: @action

ModelViewSet에서 사용 되는 추가 기능 같은 존재다.

ModelViewSet은 흔히 알고 있듯이, 여러개의 Generic한 View의 집합인데, 그 기능 외에 추가적인 옵션이 더 필요할 때 사용했다.

예시

  • 이 코드는 페이스북 클론 코딩 프로젝트를 진행 하면서 사용 한 친구 추가 코드인데, 이렇게 POST에 추가적인 옵션을 부여하면서 친구 수락 기능을 만들 때 사용 했다.
    • detail → Bool
      • 이 페이지의 고유 아이디 번호가 존재 하는 디테일 페이지의 여부
    • methods → http requests
      • Http 통신 방식에 의한 접근 방법 명시
    • url_path → url
      • 라우터에 등록 되지는 않지만, 해당 메서드를 실행 시키는 URL endpoint이다.

get_object, get_queryset 오버라이딩

get object와 get queryset은 ModelViewSet을 작성 하다보면 필요한 요소 중 하나이다. 그 이유는 뷰셋은 상속 받은 클래스에 의해 기능이 구현 되도록 설계 되어 있기 때문에 우리가 알 수 없는 곳에서 에러가 발생 하고 그 에러를 트래킹 하면 결국 full system을 알아야만 뷰 셋을 이용 할 수 있게 되는데, 이러한 불편한 부분을 해소 하기 위해 많은 메서드들을 오버라이딩 한다. 그 중에 List와 Retreive, Update 상황에서 흔히 마주칠 수 있는 오버라이딩 방식을 공유한다.

우선, 나의 개인 프로젝트 중에서 유저의 마이페이지를 가지고 오고 싶었고 프로필이라는 ViewSet이 존재 했다. 하지만, 이 뷰셋은 타인의 프로필을 접근하는고 친구 요청을 보내고 하는 것은 자연스러웠다. 하지만, 내 프로필에 접근 하려면? 만일, retrieve를 건들었다면 타인의 프로필을 못 봤을 것이다.

그래서 고민 했던 점이 프로필에서 action을 써서 마이 페이지를 구성 할지, 새로운 APIView 클래스를 만들지 고민했고 무분별한 action 보다는 하나의 깔끔한 뷰가 나을 것 같다는 판단 하에 뷰를 만들었다.

우선 코드를 보기 전, 본론으로 돌아와 get object와 get queryset을 간단하게 설명하고 예제를 본다면 이해 하기 쉬울 것이다.

get_object()

Retrieve, Update, Delete와 같이 pk가 요구 되는 detail 페이지의 기능을 구현 할 때 필터링 할 수 있는 오버라이딩 메소드이다.

👉 get_object 메서드는 단일 객체를 가져오는 역할을 수행한다. 일반적으로 get_queryset 메서드에서 반환된 QuerySet을 기반으로 조회하며, 주로 상세조회(detail) 기능에서 사용된다.

get_queryset()

List와 같이 전체적인 데이터를 조회 할 때 사용 할 수 있고, objects.all()과 같이 전체 데이터를 조회 하는 것이 아닌, 필터링 또는 정렬 등 다양한 옵션을 사용 해서 리스트의 쿼리셋을 조율 할 때 사용한다.

👉 get_queryset: 이 메서드는 목록(list) 조회 시 사용되며, 특정 모델의 객체들을 가져오는 역할을 수행한다. 일반적으로 QuerySet을 반환하고, 이를 통해 필터링, 정렬 등의 추가 조작을 수행할 수 있다.


그럼, 메서드를 간단하게 파악 했다면 코드를 보고 다시 이해 해보자

  • 💬  코드
    class MyPageView(RetrieveUpdateAPIView):
        """마이 페이지 접근"""
    
        model = UserProfile
        queryset = UserProfile.objects.all()
        serializer_class = UserProfileSerializer
    에러를 직역 해보자면, 마이페이지 뷰는 URL의 pk값을 갖고 있어야 한다. URL을 수정 하거나, 룩업 필드를 올바르게 작성해라 라고 되어있다. lookup_field의 default는 pk이다. 하지만, 내가 원하는 것은 /api/mypage 했을 때 내 개인 페이지가 나왔으면 좋겠다고 요구사항을 만들었고, pk값은 보다시피 없다. 그리고 밑에 조금 내려가다 보면 Exception Location을 소개 해주는데, get_object 메서드 부분에서 에러가 났다는 것을 보여준다. 그렇다면 우리는 get_object를 오버라이딩 하면 이 문제를 해결 할 수 있을 것이다. 위 코드에서 get_object만 추가 한 형태
    def get_object(self):
            queryset = self.get_queryset()
            user_id = self.request.user.id
            obj = get_object_or_404(queryset, profile_user_id=user_id)
    
            return obj

잘 나오는 것을 확인 할 수 있는데 get_object는 기본적으로 쿼리셋과 필터링 된 객체가 필요하다. 위에 간단하게 언급 했듯이 단일 페이지에 대해 접근 할 수 있는 메서드 라고 설명 했듯이, my_paged URL은 pk가 없으니 요청하는 유저의 id값을 받아 프로필에서 필터링 하여 오브젝트로 반환 하면 매끄러운 오버라이딩을 경험 할 수 있다.

get_queryset은 크게 에러를 범하고 하는 부분이 아니기 때문에 언급할 것은 없지만, 사용 예시를 들어보자면 이러한 형태이다.

사실 이 밑에 코드는 효율적이진 않기 때문에, 이럴 때 위 처럼 get_object를 쓰는게 더 올바르지 않았을까 라는 생각이든다.

  • 💬  코드
    def get_queryset(self):
            """요청 한 접근자의 프로필 정보 가져오기"""
            if self.action == "retrieve":
                user_id = self.kwargs["pk"]
                return UserProfile.objects.filter(profile_user=user_id)
            return UserProfile.objects.all()
    하지만, 여기서 알아둘게 있다면 self.action은 현재 내가 접근하는 방식이다. Http 통신 방식에 매핑 되어 있는 뷰셋의 action이 있는데, 이 때 접근 방식을 볼 수 있다. 마지막 return 값을 어떻게 내야 하는지만 잘 보면 된다. 그저 필터링 한 쿼리셋 객체를 return 하고 있는 모습이다.
profile
안녕하새우

0개의 댓글