장고 시작하기 23. DRF API 개발하기

DRF API : 지난 포스트에서 DRF 시리얼라이져를 사용해서 모델에 맞는 직렬화를 자동으로 해주고 API로 응답하는 방법을 정리했습니다. 이번에는 지난 정리에 이어서 DRF를 이용해서 CRUD에 대한 API를 생성하는 법을 공부하고 정리해보았습니다.

DRF 개념 정리

API 설계

다음과 같이 API를 설계했다고 가정하고 이를 어떻게 DRF로 구현하는지 공부해보았습니다.

NameMethodEndpoint
Article 목록 조회GET/articles/
Article 상세 조회GET/articles/<int:article_id>/
Article 생성POST/articles/
Article 수정PUT/articles/<int:article_id>/
Article 삭제DELETE/articles/<int:article_id>/

목록 조회 DRF API

urls.py

  • DRF에서는 Template를 사용하지 않기 때문에 name이나 app_name을 적지 않아도 됩니다.
from django.urls import path
from . import views

app_name = "articles"
urlpatterns = [
    path("", views.article_list, name="article_list"),
]

serializer.py

  • ModelSerializer를 상속 받아서 시리얼라이져 클래스를 만듭니다.
from rest_framework import serializers
from .models import Article


class ArticleSerializer(serializers.ModelSerializer):
    class Meta:
        model = Article
        fields = "__all__"

views.py

  • 조회는 GET method 이므로 @api_view([“GET”]) 데코레이터를 넣습니다.
  • 데이터를 가져와서 시리얼라이져에 넣어서 Response로 반환해줍니다.
from django.shortcuts import get_object_or_404
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .serializers import ArticleSerializer
from .models import Article


@api_view(["GET"])
def article_list(request):
    articles = get_object_or_404(Article)
    serializer = ArticleSerializer(articles, many=True)
    return Response(serializer.data)

API 요청 결과

상세 조회 DRF API

urls.py

  • 상세 요청은 URL 쿼리스트링으로 해당 id 값을 받아야 함으로 이에 맞게 `<int:article_pk>/`와 같이 variable routing을 넣어줍니다.
from django.urls import path
from . import views

app_name = "articles"
urlpatterns = [
    path("", views.article_list, name="article_list"),
    path("<int:article_pk>/", views.article_detail, name="article_detail"),
]

views.py

  • 동일하게 데이터를 가져오는데 특정 pk의 데이터만 가져와서 시리얼라이져에 넣어주고 Response를 반환해줍니다.
@api_view(["GET"])
def article_detail(request, article_pk):
    article = get_object_or_404(Article, pk=article_pk)
    serializer = ArticleSerializer(article)
    return Response(serializer.data)

API 요청

정상적인 요청

  • 정상적으로 API가 호출되어 JSON 데이터를 받아오는 것을 확인 할 수 있습니다.

모델에 없는 데이터 요청

  • 현재 모델에 없는 999번 데이터를 요청하면 404 Not Found가 뜨는 것을 확인 할 수 있습니다.

생성 DRF API

urls.py

  • Endpoint를 동일하게 하고 방법만 POST로 변경하면 CREATE 요청하도록 설계했기 때문에 urls.py에서는 변경사항 없음
from django.urls import path
from . import views

app_name = "articles"
urlpatterns = [
    path("", views.article_list, name="article_list"),
    path("<int:article_pk>/", views.article_detail, name="article_detail"),
]

views.py

  • @api_view()에서 POST를 추가
  • POST 로 요청 받았을 때 시리얼라이져에 CREATE를 요청하고 결과를 반환하는 로직을 구성
    • Serializer는 모델에 맞게 데이터를 받아서 유효성 검사까지 해줌
    • Serializerdata 파라미터에 request.data인자를 넘기고 serializer 인스턴스 생성
    • serializer 인스턴스를 is_valid()로 유효성 검사 후, save() 메소드로 저장
    • Response에서 serializer.data를 인자로 받고 이것을 반환
    • 에러가 있는 경우 Response(serializer.errors, status=400)로 반환
@api_view(["GET", "POST"])
def article_list(request):
    if request.method == "GET":
        articles = Article.objects.all()
        serializer = ArticleSerializer(articles, many=True)
        return Response(serializer.data)
    elif request.method == "POST":
        serializer = ArticleSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=201)
        return Response(serializer.errors, status=400)

API 요청

  • method를 POST로 변경하고 API를 요청해봅니다.
  • POST는 GET과 달리 query string으로 요청하지 않으므로 Body 탭을 클릭해서 원하는 형태로 생성 요청을 해줍니다.
  • 정상적으로 요청이 되면 views에서 작성한대로 201 상태코드가 반환 됩니다.
  • 잘못된 요청을 하면 views에서 작성한대로 400 코드를 반환 하는 것을 확인 할 수 있습니다.

HTTP 상태코드를 자동으로 반환

  • 위에서는 return Response(serializer.errors, status=400) 를 추가적으로 명시함으로써 특정 상태를 반환해주었는데요.
  • serializer.is_valid(raise_exception=True)와 같이 raise_exception=True인자를 추가해주면 장고가 잘못된 상태에 대한 반환을 자동으로 알아서 해줍니다.

상태코드를 명시적으로 입력

  • Response(serializer.data, status=201) 처럼 Response에 상태코드를 201로만 입력할 수 있지만, 아래 방법을 통해서 상태코드의 의미를 조금 더 명확하게 명시해줄 수도 있습니다.
  • Response(serializer.data, status=status.HTTP_201_CREATED) 처럼 status.HTTP_201_CREATED을 status 인자로 입력하면 누구나 어떤 상태인지 명확하게 이해할 수 있습니다.
  • status code는 장고에서 미리 정의해주었기 때문에 import만 해서 사용하면 됩니다. from rest_framework import status
from rest_framework import status

@api_view(["GET", "POST"])
def article_list(request):
    if request.method == "GET":
        articles = Article.objects.all()
        serializer = ArticleSerializer(articles, many=True)
        return Response(serializer.data)
    elif request.method == "POST":
        serializer = ArticleSerializer(data=request.data)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)

삭제 DRF API

urls.py

  • article 상세에서 method만 DELETE로 변경하는 것이기 때문에 urls.py에는 변경이 없음

views.py

@api_view(["GET", "DELETE"])
def article_detail(request, article_pk):
    article = get_object_or_404(Article, pk=pk)
    if request.method == "GET":
        serializer = ArticleSerializer(article)
        return Response(serializer.data)

    elif request.method == "DELETE":
        article.delete()
        data = {"delete": f"Article({article_pk}) is deleted."}
        return Response(data, status=status.HTTP_200_OK)

api 요청

  • DELETE 요청을 날려봅니다.
  • 삭제되었을 때 JSON이 출력 됩니다.
  • 주의 : URL에 trail slash(/)을 마지막에 제대로 붙여줘야 정상 작동 합니다.
  • GET으로 삭제한 ID를 조회하면 404가 뜨는 것을 확인할 수 있습니다.

수정 DRF API

  • serializer = ArticleSerializer(article, data=request.data, partial=True)
    • serializer에 data만 binding시키면 CREATE로 작동하고 모델에서 가져온 데이터를 같이 binding해주면 UPDATE로 작동합니다.
    • 이는 Model form에서 data만 넣으면 CREATE이지만 instance를 넣으면 UPDATE로 작동하는 것과 유사한 방식임을 알 수 있습니다.
    • 시리얼라이져를 선언할 때, field = '__all__'로 선언했기 때문에 UPDATE 요청에는 모든 필드의 값을 다시 입력 받아야합니다.
    • 하지만 partial=True 인자를 시리얼라이져에 전달하면 모든 필드의 값을 받지 않고, 일부 필드의 값만 받아도 수정이 가능하게 해줍니다.
    • 예를 들어, Article 모델에 title, content, image 필드가 있다고 가정할 때, 클라이언트가title 필드만 업데이트하려는 경우, partial=True 없이는 content와 image도 함께 전송해야 하지만, partial=True를 사용하면 title 필드만 전송하고 나머지 필드는 그대로 유지할 수 있습니다.

views.py

@api_view(["GET", "PUT", "DELETE"])
def article_detail(request, article_pk):
    article = get_object_or_404(Articles, pk=article_pk)
    if request.method == "GET":
        serializer = ArticleSerializer(article)
        return Response(serializer.data)

    elif request.method == "PUT":
        article = get_object_or_404(Articles, pk=article_pk)
        serializer = ArticleSerializer(article, data=request.data, partial=True)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response(serializer.data)

    elif request.method == "DELETE":
        article.delete()
        data = {"delete": f"Article({article_pk}) is deleted."}
        return Response(data, status=status.HTTP_200_OK)

마치며

생성, 조회, 수정, 삭제에 대한 API를 DRF로 생성해보았습니다. DRF가 참 편하네요.. 역시 만들어져 있는 걸 가져다 쓰는 게 참 편한 것 같습니다.

또한 API를 만들면서 느낀점인데 PostMan을 통해서 손쉽게 결과를 보면서 연습하니까 굉장히 좋은 것 같습니다.

다만 주의점은 브라우저에서는 trail slash(/)를 명시적으로 입력하지 않아도 자동으로 붙여서 요청을 보내주지만 PostMan에서는 trail slash를 명시적으로 붙여줘야 정상 작동 합니다.

해당 사실을 몰라서 코드를 뜯어보면서 1시간을 보낸 것 같네요…ㅎ

참고하면 좋은 글

Leave a Comment

목차