프로젝트

프로젝트 중 알게된 점 - get_queryset(), get_object()

Y0un9Ki 2024. 5. 25. 19:19

필자는 앞에서도 얘기했듯이 은둔형 외톨이를 위한 서비스를 기획하고 개발하고 있다. 

https://srilankakim66.tistory.com/79

 

파이널 프로젝트-기획

필자는 부트캠프를 다니면서 마지막 과제인 파이널 프로젝트를 진행을 하고 있다.어느정도 진행이 된다음에 블로그를 작성하는 거라 설명이 세세하지 못하지만 블로그에 남겨보고자 한다. 조

srilankakim66.tistory.com

 

여기서 편지기능에 해당하는 API를 짜고 있는데 여기서 알게 된 점을 적어보고자 한다.

 

위에 블로그에서 작성을 해놨듯이 관리자가 질문지를 만들고 그 질문지를 사용자들에게 보내주고 사용자는 그 질문에 해당하는 답장을 작성하는 식의 편지기능을 만들고 있다. 그런데 여기서 질문지 밑에는 여러개의 답장이 존재하게 되고 요청을 한 사용자에 대한 답장만을 보여주는 식의 아키텍처로 설계를 했다. 그렇기에 클라이언트가 GET으로 답장을 요청한다면 우리는 선택적인 객체들만 가지고 와서 요청에 대한 응답으로 줘야 한다.

 

밑에 코드를 간단하게 봐보자.

 

get_object()

class AnswerDetailQuestion(APIView):
    permission_classes = [IsAuthenticated]
    authentication_classes = [JWTAuthentication]
    
    def get_object(self, pk):
        try:
            question = Question.objects.get(pk=pk)
            user = self.request.user  # 프론트에서 전달된 JWT 토큰을 사용하여 사용자를 식별하는 코드이다. 프론트엔드에서 JWT토큰을 header로 보내주면 서버에서 JWT토큰을 받아서 user를 식별한다.
            answer = Answer.objects.get(question=question, user=user)
            return answer

        except (Question.DoesNotExist, Answer.DoesNotExist):
            raise NotFound({'message': '해당하는 데이터가 존재하지 않습니다.'})
        
    def get(self, request, pk, format=None):
        answer=self.get_object(pk)
        self.check_object_permissions(request, answer)
        
        serializer = AnswerSerializer(answer)
        return Response(serializer.data)

 

여기서 보면 특정 질문지를 클릭을 했을 때 요청한 사용자와 같은 사용자의 특정 질문지에 대한 단일 답장을 보여주는 API이다.

여기서 특정한 단일 객체를 들고오기 위해서 get_object라는 함수를 정의해 줬다. 질문지에 id를 프론트에 요청으로 받는다면 프론트에서 header로 준 JWT토큰을 이용하여 사용자를 식별하고 그 사용자에 대해 요청으로 받은 질문지의 단일 답장을 전달 하는 방식이 된다. 

단일 객체로 들고 오기 때문에 answer = Answer.objects.get(question=question, user=user)로 작성이 되어있는 것을 볼 수 있다.

 

그러나 밑에 잘못된 코드를 보자.

class AnswerOwnerList(APIView):
    permission_classes = (IsOwnerOnly,)
    authentication_classes = [JWTAuthentication]
    def get_object(self, email):
        try:
            user = User.objects.get(email=email)
            return Answer.objects.filter(user = user)
        except User.DoesNotExist:
            raise NotFound({'message': '이 요청은 존재하지 않는 요청입니다.'})
        
    def get(self, request, email, format=None):
        answer = self.get_object(email)
        
        for answers in answer:
            self.check_object_permissions(request, answers)
        serializer = AnswerSerializer(answer, many=True)
        return Response(serializer.data)

 

이것 자체가 아키텍처에 맞지 않은 API이지만 예시를 보기 위해서 가지고 왔다.

여기서 볼 때 필자는 get_object라고 함수를 만들었지만 get_object 함수 밑에 코드를 본다면 Answer.objects.filter()를 사용하는 것을 볼 수 있다.

필자는 이 코드를 짤때 문제점을 알지 못했다. 왜냐하면 잘 작동을 했기 때문이다. 하지만 잘못된 코드이다.

 

왜냐하면 get_object라는 함수 자체가 단일객체를 들고 올 때 사용하는 함수이다. 그렇기에 get 메서드를 사용해서 단일 객체를 가지고 온다면 문제가 되지 않지만 filter 메서드를 사용해서 들고 올 때에는 여러개의 객체(쿼리셋)로 들고 오기 때문에 따로 정의가 되어져 있는 함수가 있었다.

 

그렇다면 쿼리셋을 가지고 올 때 사용하는 함수는 뭔지 알아보았더니 get_queryset이라는 함수가 generic 라이브러리에 정의가 되어있었다. 밑에는 어떻게 정의가 되어있는지 올린 것이다.

 

 

그렇기에 filter 메서드를 통해서 여러개의 객체(쿼리셋)를 들고 올때에는 get_queryset을 사용해서 정의를 해줘야 한다.

이번에는 잘 만들어진 코드를 살펴보자.

 

get_queryset()

class AnswerList(APIView):
    permission_classes = [IsAuthenticated]
    authentication_classes = [JWTAuthentication]
    
    def get_queryset(self):
        user = self.request.user
        answer = Answer.objects.filter(user=user)
        if answer.exists():
            return answer
        else:
            raise NotFound({'message': '해당하는 데이터가 존재하지 않습니다.'})
    
        
    def get(self, request, format=None):
        answer = self.get_queryset()
        serializer = AnswerSerializer(answer, many=True)
        return Response(serializer.data)

 

위에 코드는 답장 리스트들을 모두 가지고 오는 API이다. 여기서는 get_queryset라는 함수를 정의하고 filter 메서드를 이용해서 모든 답장의 객체들을 가지고 오는 것을 볼 수 있다.

 

이것을 통해서 데이터베이스에 특정 데이터만을 가지고 올 때 사용하는 get_object(), get_queryset() 함수를 알아보았다.

 

굉장히 많이 쓰이니 꼭 알아두어야 겠다.

 

@@