[D.R.F]Mixin CBV로 게시글 C.R.U.D API 구현하기

2021. 11. 15. 16:31코딩일지/DRF

Django Mixin CBV이란?

지금까지의 Class Based View의 API View에서 반복되는 로직의 view를 보였 주었습니다.

 

이를, CBV에서 상속이라는 특성을 사용해 불필요한 코드의 중복을 줄일 수 있게 해 주는 것이 Mixin입니다.

 

Function based view(FBV)에서 사용한 models.py와 serializers.py를 그대로 사용하겠습니다.

 

1. 시리얼라이저 수정

Commentserializer를 만들면서 종속관계인 게시글의 pk가 꼭 필요로 해서 fields에 'board'를 넣어주었는데 여기서 문제가 생겼습니다.

 

수정할 때 모든 보드의 정보를 가져오기 때문에 각 게시글마다 생성된 댓글을 수정할 때 게시글이 변경이 가능해지는 이슈를 발견해 시리얼라이저를 수정합니다.

 

빠른 이해를 위해 예를 들자면 1번 게시글의 10번 댓글을 수정할 때 3번 게시글의 10번 댓글로 수정이 가능해져 버린 이상한? 상황이 만들어져 버렸습니다ㅜㅜ.. (아래 사진 참조)

 

1번 게시글의 10번 댓글을 수정하려고 불렀는데 3번 게시글로 바꿔지네..? 이게모람..

 

PUT 실행 후

 

진짜 3번게시글로 가네..엌ㅋㅋㅋㅋㅋㅋ

 

  • Serializers.py 수정
from rest_framework import serializers
from .models        import Board, Comment


class BoardSerializers(serializers.ModelSerializer):
    class Meta:
        model  = Board
        fields = '__all__'

# 편의를 위해 댓글의 id값을 불러오고 게시글의 pk값을 불러오는 'board'를 삭제합니다.
class CommentSerializers(serializers.ModelSerializer):
    class Meta:
        model  = Comment
        fields = ['id','contents']

'''게시글을 불러올때 연결되어있는 댓글을 불러오기 위해서
model은 Board이지만 댓글을 따로 지정해줘 fields에 추가해준다.'''
class BoardDetailSerializers(serializers.ModelSerializer):
    '''CommentSerializers를 comments라는 변수에 담고
    여러개의 댓글이 가능하게 many = True를 넣어준다.'''
    comments = CommentSerializers(many = True)
    class Meta:
        model = Board
        fields = ['title','contents','comments']

 

2. Mixin과 GenericViewAPI를 활용한 Views.py 작성

Mixin을 활용하여 Views.py를 작성합니다.

 

APIView로 작성했을 때 보다 훨씬 더 적은 코드를 치고 간결해진 것을 볼 수 있습니다.

 

오버라이드 된 부분은 따로 3번에서 설명하겠습니다.

 

  • Views.py 작성
from .models      import Board, Comment
from .serializers import BoardSerializers, CommentSerializers, BoardDetailSerializers

from rest_framework import mixins, generics, status
from rest_framework.response import Response


# 게시글의 목록과 생성
class BoardList(mixins.ListModelMixin,
                mixins.CreateModelMixin,
                generics.GenericAPIView):

    queryset         = Board.objects.all()
    serializer_class = BoardSerializers

    # 게시글 목록 불러오기
    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)
	
    # 게시글 생성하기
    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)


# 게시글불러오기와 수정, 삭제
class BoardDetail(mixins.RetrieveModelMixin,
                    mixins.UpdateModelMixin,
                    mixins.DestroyModelMixin,
                    generics.GenericAPIView):

    queryset = Board.objects.all()
    serializer_class = BoardSerializers

	
    '''1개의 게시글을 불러올 때 댓글을 같이 불러와야 한다.
    'GET'요청이 들어올 때 불러오는 serializer_class에 따로 생성한
    Serializers를 불러오게 합니다.'''
    def get_serializer_class(self):
        if self.request.method == 'GET':
            self.serializer_class = BoardDetailSerializers
        return self.serializer_class

	# 게시글 불러오기
    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

	# 게시글 삭제하기
    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

	# 게시글 수정하기
    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

# 게시글에 댓글달기
class CommentCreate(mixins.CreateModelMixin,
                    mixins.ListModelMixin,
                generics.GenericAPIView):

    queryset = Comment.objects.all()
    serializer_class = CommentSerializers

    #board = pk값을 지정해서 원하는 게시글의 댓글만 생성하게끔 오버라이드한다.
    def create(self, request, pk, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer, pk)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    #save()변수를 지정해 시리얼라이저에서 따로 지정해줄 필요 없다.
    def perform_create(self, serializer, pk):
        serializer.save(board_id = pk)

	# 게시글 댓글추가
    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

# 댓글 수정,삭제
class CommentUpdate(mixins.RetrieveModelMixin,
                mixins.DestroyModelMixin,
                mixins.UpdateModelMixin,
                generics.GenericAPIView):

    queryset = Comment.objects.all()
    serializer_class = CommentSerializers
    lookup_url_kwarg = 'comment_pk'

	#게시글 댓글 불러오기
    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

	#게시글 댓글 삭제
    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

	#게시글 댓글 수정
    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

 

여기서 queryset과 serializer_class는 GerericAPIView, lookup_url_kwarg는 상속받아서 쓴 것입니다.

 

아래의 깃허브에서 Generics.py를 볼 수 있습니다.

 

github.com/encode/django-rest-framework/blob/master/rest_framework/generics.py

 

GitHub - encode/django-rest-framework: Web APIs for Django. 🎸

Web APIs for Django. 🎸. Contribute to encode/django-rest-framework development by creating an account on GitHub.

github.com

 

 

queryset = Noneserializer_class = None에 저희 Views.py에서 지정해준 값이 들어간다고 생각하시면 편합니다.

 

또한, lookup_url_kwarg에는 url에 comment_pk가 들어오는 것을 알려줘야 댓글을 추가, 수정할 때 인식이 가능하기에 지정해주었습니다.

 

3. Mixin 오버라이드

우리는 mixins.py에서 상속을 받아서 Views.py에 import 해줌으로써 만들어진 코드를 받아와 사용할 수 있었습니다.

 

처음에 말씀드렸던 대로 viewset으로 갈수록 규칙성이 높아진다고 하였는데, 위의 코드처럼 정해진 틀에서 필요한 값을 받아오게끔 하기 위해 실행하는 것이 오버라이드입니다. 

 

오버라이드란,  프로그래밍에서는 덮어 씌우는 것으로 생각하면 좋습니다.

 

상속 관계인 부모 클래스와 자식 클래스 사이에서 부모 클래스의 메서드를 똑같이 가져와 사용하는 것입니다.

 

게시글 한 개를 불러오기 위한 연결된 댓글을 불러오기 위해 우리는 시리얼라이저를 따로 생성해두었는데 이를 오버라이드 해서 지정해주지 않으면 BoardSerializer를 불러오기 때문에 상속받아온 GenericAPIView에 get_serializers_class를 오버라이드 해줍니다.

 

  • generics.py / get_serializer_class

 

위의 로직대로 serializer를 받아오게 됩니다.

우리는 GET 요청을 받을 때만 시리얼라이저를 지정해주면 되기 때문에 GET요청을 받을 때 get_serializer_calss가 DetailSerializer를 불러오게끔 로직을 구성해줍니다.

 

    '''1개의 게시글을 불러올 때 댓글을 같이 불러와야 한다.
    'GET'요청이 들어올 때 불러오는 serializer_class에 따로 생성한
    Serializers를 불러오게 합니다.'''
    def get_serializer_class(self):
        if self.request.method == 'GET':
            self.serializer_class = BoardDetailSerializers
        return self.serializer_class

 

또한, 댓글을 생성해주는 CreateMixins도 아래의 그림처럼 되어있습니다.

 

  • mixins.py / CreateModelMixin

 

생김새가 저희가 만들었던 APIView와 똑같이 생겼죠?

APIView와 거의 흡사합니다. (serializer를 지정해주고 유효성 검사 후 저장하고 그대로 받아온 데이터를 반환해주는)

 

여기서 perform_create에서 save()할 때 값을 지정해줄 수 있기에 댓글 생성 시 보드의 pk를 받아와야 하기에 pk값을 지정해주었습니다.

    #board = pk값을 지정해서 원하는 게시글의 댓글만 생성하게끔 오버라이드한다.
    def create(self, request, pk, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer, pk)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    #save()변수를 지정해 시리얼라이저에서 따로 지정해줄 필요 없다.
    def perform_create(self, serializer, pk):
        serializer.save(board_id = pk)

 

 

4. URL 지정

클래스별로 맞는 urls.py를 작성해줍니다.

 

  • urls.py 작성
from django.urls import path

from .views import BoardList, BoardDetail, CommentCreate, CommentUpdate


app_name = 'boards'

urlpatterns = [
    path('', BoardList().as_view()),
    path('<int:pk>/', BoardDetail.as_view()),
    path('<int:pk>/comments/', CommentCreate.as_view()),
    path('<int:pk>/comments/<int:comment_pk>', CommentUpdate.as_view())
]

 

5. 서버 가동

  • 서버 실행
python manage.py runserver

 

6. 기능 확인

  • POST

 

127.0.0.1:8000/board [POST]

 

  • ListGET

 

127.0.0.1:8000/Board [GET]

 

  • GET/5 PUT DELETE

 

127.0.0.1:8000/Board/5 [GET/PUT/DELETE]

 

이번엔 Mixin을 기반으로 한 C.R.U.D를 작성해보았습니다.

 

보시다시피 CBV에서 상속을 받아와 코드를 작성하기에 더 간결하게 보입니다.

 

하지만, GenericAPIView이나 Viewset을 하게 되면 지금보다 더 간소화된 로직을 보여드릴 수 있을 것 같습니다.

 

다음 시간에는 GenericAPIView로 C.R.U.D를 작성해보겠습니다 ㅎㅎ 

 

긴 글 읽어주셔서 감사합니다