Today I Learned/django

DRF APIView -> generic&mixin -> ViewSets 과정 차근 차근 알아보자

하나719 2023. 10. 16. 21:48
반응형

DRF에서 기능 구현은 아주아주 다양한 방법으로 가능한데, 제일 심플한 ViewSet으로 구현하게 될 경우 코드양은 매우 줄여지고 간편하지만, 유연하게 커스텀해서 사용하기 어려울 수 있고 APIView 단계로 내려가면 직접 구현해주어야하는게 많아 코드양은 많아지지만 요구사항이 복잡할 때 유연하게 커스텀해서 사용할 수 있다. 그런데 ViewSet이 간단하다고 APIView 구현 로직을 모르고 계속 ViewSet만 사용한다면 나중에 직접 함수로 다양한 요구사항을 처리하기 어려울 수도 있다. 

 

다른 블로그 글들을 보면서 과정은 계속 익혔는데, 아무래도 한번 직접 정리를 해야 완벽하게 정리가 될 것 같아서 직접 정리하기로!!

 

1. APIView 로 List & Detail 구현

장고 DRF의 기본 CBV(ClassBaseView)는 APIView

APIView는 하나의 CBV이므로, 하나의 URL만 처리가능하다. 

(ViewSet을 이미 알고 있다면, ViewSet은 하나의 클래스로 List URL과 Detail URL 처리가 가능해진다는것을 알고 있을 것이다.)

 

1) PostListAPIView

get, post 요청에 응답해줄 함수를 각각 구현하였다. 

class PostListAPIView(APIView):
	def get(self, request):
    	qs = Post.objects.all()
        serializer = PostSerializer(qs, many=True)
        return Response(serializer.data)
        
    def post(self, request):
    	serializer = PostSerializer(data = request.data)
        if serializer.is_valid():
        	serializer.save()
            return Response(seralizer.data , status=201)
        return Response(Serializer.errors, status=400)

 

2) PostDetailAPIView

get, put, delete 요청에 응답해줄 함수를 각각 구현하였다.

class PostDetailAPIView(APIView):
	def get_object(self,pk):
    	return get_object_or_404(Post, pk=pk)
        
    def get(self, request, pk):
    	post = get_object(pk)
        serializer = PostSerializer(post)
        return Response(serializer.data)
    
    def put(self, request, pk):
    	post = get_object(pk)
        serializer = PostSerializer(post, data=request.data)
        if serializer.is_valid():
        	serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST)
    
    def delete(self,request, pk):
    	post = get_object(pk)
        post.delete()
        return Response(status = status.HTTP_204_NO_CONTENT)

2. generics & Mixin

rest_framework의 generics와 함께 이미 구현된 Mixin을 함께 사용해서 코드를 좀더 간결화 할 수 있습니다.

* Mixin, generic 순서로 상속 받을 것

 

아까 위에서 APIView를 상속받아 구현했을 때 우리는 같은 클래스내에  get, post에서 반복적으로 쿼리셋을 작성하고, serializer를 지정해주었다. 같은 모델을 사용하는 클래스라면, 쿼리셋과 사용할 serializer를 한번에 지정하고 각 메소드를 불러와서 응답해주는 개념이다.

 

* Mixin 

  • CreateModelMixin
  • ListModelMixin
  • RetrieveModelMixin
  • UpdateModelMixin
  • DestroyModelMixin
class PostListAPIView((mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
	qs = Post.objects.all()
	serializer_class = PostSerializer
    
    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)
        
    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

위에서 get함수와 post 함수에 각각 지정해주었던 쿼리셋과 serializer를 클래스내에서 한번 지정해주고 각각 함수에서는 mixin내에 구현되어 있는 self.list() , self.create() 함수를 사용해주었다.

 

class PostDetailAPIView(mixin.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView):
	queryset = Post.objects.all()
    serializer_class = PostSerializer
    
    def get(self, request, *args, **kwargs):
    	return self.retrieve(request, *args, **kwargs)
    
    def put(self, request, *args, **kwargs):
    	return self.update(request, *args, **kwargs)
    
    def delete(self, request, *args, **kwargs):
    	return self.destroy(request, *args, **kwargs)

detail뷰도 마찬가지로 쿼리셋과 시리얼라이져를 한번 지정해주고, 각각 아래 함수에서 retrieve, update, destroy함수를 사용해서 구현해주었다.

 

+ 자주 같이 사용되는 함수를 합친 Mixin도 있다 (mixins가 아닌 generics에 있음 -> generics.BasicAPIView 추가 안해주고 generics.ListCreateAPIView 이렇게 하나만 상속받아서 사용하면 됨. 그리고 각각 함수들이랑 매핑해줄 필요없이 쿼리셋이랑 시리얼라이져만 정해주면 됨, 이걸 사용하는게 훨씬 편할것 같음) 

  • ListCreateAPIView( get -> list, post -> create)
  • RetrieveUpdateAPIView( get -> retrieve, put -> update, patch -> partial_update)
  • RetrieveDestroyAPIView( get -> retrieve, delete -> destroy)
  • RetrieveUpdateDestroyAPIView (get -> retrieve, put -> update, patch -> partial_update, delete -> destroy)

예시)

이게 끝이다..!

class PostListAPIView(generics.ListCreateAPIView):
	queryset = Post.objects.all()
    serializer_class = PostSerializer

3. ViewSet & Router

위에 두가지 방법은 모두 하나의 리소스에 대해서 두가지 클래스를 가지고 각각의 클래스는 하나의 URL을 만든다.

  • list , create 
  • retrieve, put, delete 

그런데 viewset은 이마저도 하나로 줄여버린다.

하나의 클래스로 두가지 url을 모두 만들어낸다. ( /post/ , /post/<id>/ )

 

먼저 기본 viewset을 사용!

# views.py

class PostViewSet(viewsets.ViewSet):
	def list(self, request):
    	qs = Post.objects.all()
        serializer = PostSerializer(qs, many= True)
        return Respone(serializer.data)
    
    def retrieve(self, request, pk):
    	qs = Post.objects.get(pk=pk)
        serializer = PostSerializer(qs)
        return Response(serializer.data)
        
 # urls.py
 
 router = DefaultRouter()
 router.register('post', PostViewSet, basename= 'post')
 router.urls

 

post viewset 클래스 하나에 list함수와 Retrieve함수를 둘다 구현한걸 볼 수 있다.

urls에서 router를 사용하면 알아서 ! url경로를 두개로 생성해서 요청에 맞게 연결해준다. 

아주 간편하다.

그런데 처음 APIView를 사용했을때처럼 함수 내에서 쿼리셋과 시리얼라이져를 각각 넣어서 중복이 또 생기고 있다. 

하나의 리소스 (모델) 를 사용한다면, 이것을 또 줄일 수 있다.

 

ModelViewSet을 사용해보자.

class PostViewSet(ModelViewSet):
	queryset = Post.objects.all()
    serializer_class = PostSerializer

자 이 세줄이면 기본적인 list, create, put, retrieve, delete를 모오두 만들어준다.

잘 알고쓰면 최고인 것 같다.

 

다만 커스텀이 필요할때를 위해 이 ModelViewSet이 생기게 된 과정을 직접 구현해보길 추천

 

반응형