[drf | agiliq] More views and viewsets

More views and viewsets

A better URL structure

현재 API endpoints

  • /polls/ and /polls/<pk>/
  • /choices/
  • /vote/

위 기존 API URLs를 만들었지만 더 직관적이고 중첩된 소스코드를 통해서 리디자인할텐데요.

  • /polls//polls/<pk>
  • /polls/<pk>/choices/
  • /polls/<pk>/choices/<choice_pk>/vote/

Changing the views

ChoiceListCreateVote를 바꿔볼게요. /polls//polls/<pk>변경되지 않았거든요.

from rest_framework import generics
from rest_framework.views import APIView
from rest_framework import status
from rest_framework.response import Response

from .models import Poll, Choice
from .serializers import PollSerializer, ChoiceSerializer, VoteSerializer

# ...
# PollList and PollDetail views

class ChoiceList(generics.ListCreateAPIView):
    def get_queryset(self):
        queryset = Choice.objects.filter(poll_id=self.kwargs["pk"])
        return queryset
    serializer_class = ChoiceSerializer


class CreateVote(APIView):
    serializer_class = VoteSerializer

    def post(self, request, pk, choice_pk):
        voted_by = request.data.get("voted_by")
        data = {'choice': choice_pk, 'poll': pk, 'voted_by': voted_by}
        serializer = VoteSerializer(data=data)
        if serializer.is_valid():
            vote = serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

nested구조로 urls.py를 바꿀게요.

urlpatterns = [
    path("polls/<int:pk>/choices/", ChoiceList.as_view(), name="choice_list"),
    path("polls/<int:pk>/choices/<int:choice_pk>/vote/", CreateVote.as_view(), name="create_vote"),

]

GET을 수행하여 변경 사항을 볼 수 있습니다 http://localhost:8000/polls/1/choices/

[
    {
        "id": 1,
        "votes": [],
        "choice_text": "Flask",
        "poll": 1
    },
    {
        "id": 2,
        "votes": [
        ],
        "choice_text": "Django",
        "poll": 1
    }
]

http://localhost:8000/polls/1/choices/2/vote/data로 POST를 수행하여 투표 1 중 선택 2에 투표 할 수 있습니다 . {"voted_by": 1}

{
    "id": 2,
    "choice": 2,
    "poll": 1,
    "voted_by": 1
}

ChoiceList로 가볼게요.

# urls.py
#...
urlpatterns = [
    # ...
    path("polls/<int:pk>/choices/", ChoiceList.as_view(), name="choice_list"),
]

# apiviews.py
# ...

class ChoiceList(generics.ListCreateAPIView):
    def get_queryset(self):
        queryset = Choice.objects.filter(poll_id=self.kwargs["pk"])
        return queryset
    serializer_class = ChoiceSerializer

urls에서 pkChoiceList에 전달해줘요.
get_queryset 메서드를 오버라이드 해줘서 choices에대한 filter를 poll_id로 하게되요.
그리고 나머지는 DRF에서 다루게 하고요.

그리고 CreateVote의 경우는,

# urls.py
#...
urlpatterns = [
    # ...
    path("polls/<int:pk>/choices/<int:choice_pk>/vote/", CreateVote.as_view(), name="create_vote"),
]

# apiviews.py
# ...

class CreateVote(APIView):

    def post(self, request, pk, choice_pk):
        voted_by = request.data.get("voted_by")
        data = {'choice': choice_pk, 'poll': pk, 'voted_by': voted_by}
        serializer = VoteSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

poll id == pk, choice id를 전달하게되는데요. 그리고 subclass로 generic을 사용하지 않고 APIView를 사용하는데 그 이유는 커스터마이징에 초점을 뒀기 때문이에요.
초기 시리얼라이저에 데이터를 직접 넣었던 APIView에 더 유사합니다. 그리고 is_valid()를 통해서 데이터 유효성에 따라 성공시 데이터 반환과 오류시 에러 반환이 분기되게 되요.

Introducing Viewsets and Routers

지금 소스 코드 역시 괜찮지만 더욱더 효율적인 리팩토링이 가능해요.

현재 잘보면 /polls/, /polls/<pk>/엔드포인트는 동일한 queryset, serializer_class를 받고 있어요.

요것들을 viewset하나로 그룹시킬 수 있어요. 그리고 urls에서는 router를 이용해서 연결시킬 수 있어요.

# urls.py
# ...
from rest_framework.routers import DefaultRouter
from .apiviews import PollViewSet


router = DefaultRouter()
router.register('polls', PollViewSet, basename='polls')


urlpatterns = [
    # ...
]

urlpatterns += router.urls

# apiviews.py
# ...
from rest_framework import viewsets

from .models import Poll, Choice
from .serializers import PollSerializer, ChoiceSerializer, VoteSerializer


class PollViewSet(viewsets.ModelViewSet):
    queryset = Poll.objects.all()
    serializer_class = PollSerializer

URL이나 응답에는 전혀 변경이 없습니다. /polls/, /polls/<pk>/에서 GET 수행을 통해 확인할 수 있습니다.

Choosing the base class to use

지금까지 API 뷰를 빌드하는 4 가지 방법을 보았습니다.

  • 순수한 장고 뷰
  • APIView 하위 클래스
  • generics.* 하위 클래스
  • viewsets.ModelViewSet

그렇다면 언제 어떤 것을 사용해야 할까요?

  • viewsets.ModelViewSet은 대부분의 모델CRUD에 대한 작업을 일임 할때
  • generics.* viewset 낮은 단계로 본인이 허용하길 원하는 몇가지 모델에 국한 할때
  • APIView 본인이 자유롭게 커스터마이징 하길 원할때

Next steps

다음 장에서는 API에 액세스 제어를 추가하는 방법을 살펴 보겠습니다.

좋은 웹페이지 즐겨찾기