2023/09/20 [TIL] DRF에서 데이터 CREATE, READ, DELETE, UPDATE / Article 생성,상세 읽기, 삭제, 수정

장현웅·2023년 9월 20일
0

🥚 API 연결!


[ drf/urls.py ]

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('articles/', include('articles.urls')),
]

[ articles/urls.py ]

from django.urls import path
from articles import views

urlpatterns = [
    path('index/', views.index),
    path('<int:article_id>', views.article_view),
]

[article/views.py/article_view() ]

@api_view(['GET','PUT', 'DELETE'])
def article_view(request, article_id):
    return Response(article_id)

🐣 DRF에서 제공하는 브라우저블 API에서 POST요청 해보기


들어가기에 앞서,
이전 퓨어 장고에서는 클라이언트와 서버가 데이터를 주고 받을 때는 HTML의 폼에 클라이언트가 입력한 값을 FormData에 담겼고 POST 요청한 데이터는 request.POST.get의 형식으로 받아왔다.

DRF에서는 JSON 형식의 데이터를 주고 받고 .data속성을 이용해서 데이터를 받을 수 있다.

DRF가 제공해주는 브라우저블 API에서 POST 요청을 해보자.

from rest_framework.response import Response
from rest_framework.decorators import api_view
from articles.models import Article
from articles.serializers import ArticleSerializer


@api_view(['GET','POST'])
def index(request):
    if request.method == 'GET':
        all_articles = Article.objects.all()
        all_serializer = ArticleSerializer(all_articles, many=True)
        return Response(all_serializer.data)
    elif request.method == 'POST':
    	# print(request)
        # print(request.data)
        # print(request.data['title']
        return Response()
        
# print(request)
<rest_framework.request.Request: POST '/articles/index/'>

# print(request.data)
{'title': 'JSON은 큰 따옴표로 감싸야지'}

# print(request.data['title']
JSON은 큰 따옴표로 감싸야지

JSON 형식으로 데이터의 키와 밸류를 큰 따옴표로 감싸서 보내도 서버에서는 작은 따옴표로 받네;;

POST 요청으로 보낸 content 박스 안의 내용(데이터)은 GET 요청과 마찬가지로 .data 속성을 통해 얻을 수 있다.

🐤 받아온 데이터를 Article 모델 형태로 저장하기 CREATE


DRF를 사용하여 클라이언트가 POST 요청과 함께 보낸 데이터를 모델 형태로 변환하고 저장하려면 정의한 Serializer 클래스에 넣어서 데이터를 직렬화하고 모델로 변환 후 데이터를 저장해야한다.

직렬화는 모델 인스턴스 객체를 JSON 데이터 형식으로 문자열을 Key:Value 형태로 바꿔주는 것

역직렬화는 JSON 데이터를 받아와서 이 Key:Value형태의 문자열 데이터를 모델 형태로 바꿔주는 것

직렬화와 역직렬화 모두 이것에 대한 정의는 serializer.py에서 하고 그 데이터는 .data속성으로 얻을 수 있다.

[ serializers.py ]

from rest_framework import serializers
from articles.models import Article

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

[ views.py ]

from rest_framework.response import Response
from rest_framework.decorators import api_view
from articles.models import Article
from articles.serializers import ArticleSerializer


@api_view(['GET','POST'])
def index(request):
    if request.method == 'GET':
        all_articles = Article.objects.all()
        all_serializer = ArticleSerializer(all_articles, many=True)
        return Response(all_serializer.data)
    elif request.method == 'POST':
        serializer = ArticleSerializer(data = request.data)
        if serializer.is_valid():  # 유효성 검사 필수 / 모델의 필드 속성에 조건들에 부합하는지 serializer에서 유효성 검사를 하고 모든 조건에 부합하면 DB에 저장한다.
            serializer.save()
        else :
        	print(serializer.errors)
            return Response()
        

만약, 아래의 형태로 POST 요청을 한다면,

[ models.py ]

from django.db import models


class Article(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)


    def __str__(self):
        return str(self.title)

Article 모델content 필드 속성에 null=True, blank=True속성이 없기 때문에 아래의 오류가 날 것이다.

[ 터미널 ]

{'content': [ErrorDetail(string='이 필드는 필수 항목입니다.', code='required')]}

null=True, blank=True 속성을 넣고 마이그래이션을 한 후 다시 POST 요청을 하면, 아래와 같이 DB에 잘 저장이 된다.

추가로,
에러 내용을 return Response(serializer.errors, status = 400)
이렇게 프론트엔드로 보내주는 것은 보안상 좋지 않다.

HTTP 400 Bad Request는 클라이언트의 오류이다.

하지만, 개발단계에서는 괜찮으니 한번 해볼까?

from rest_framework import status
from rest_framework.response import Response
from rest_framework.decorators import api_view
from articles.models import Article
from articles.serializers import ArticleSerializer


@api_view(['GET','POST'])
def index(request):
    if request.method == 'GET':
        all_articles = Article.objects.all()
        all_serializer = ArticleSerializer(all_articles, many=True)
        return Response(all_serializer.data)
    elif request.method == 'POST':
        serializer = ArticleSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else :
            print(serializer.errors)
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
  • 잘못된 경우

  • 알맞은 경우


Serializer를 쓰니까 코드가 훨씬 간결해진 것 같다.

전체 Article을 페이지에 띄우기(Read)와 Article Create의 경우와는 다르게 상세 Article 페이지 가기(Read), Article 삭제(Delete), Article 수정(Update)의 경우에는 특정 Article의 id값이 필요하다.

🐥 Article 디테일 Read


API가 연결되었으니 'GET'요청방식으로 특정 article을 보여주는 Data Read를 해보자.

일단 로직은, 데이터베이스에서 url에서 받은 'article_id'와 'id값'이 같은 데이터를 가져와서 그 모델 인스턴스를 serializer에 넣고 JSON 직렬화를 시켜 .data로 직렬화된 데이터를 가져와서 Response 객체에 넣어 클라이언트에게 보내준다.

[ articles/views.py/article_view() ]

@api_view(['GET','PUT', 'DELETE'])
def article_view(request, article_id):
    if request.method == 'GET':
        article = Article.objects.get(id=article_id)
        serializer = ArticleSerializer(article)
        return Response(serializer.data)


여기서 만약 데이터에 저장되어 있지 않은 article을 조회한다면 아래와 같이 데이터가 없다는 에러페이지가 뜬다.

그래서 데이터가 있는 경우는 정상적으로 데이터를 가져와서 보내주고 없는 경우는 status = 404 에러를 띄워줄 수 있는 get_object_or_404 메서드를 사용해보자.

@api_view(['GET','PUT', 'DELETE'])
def article_view(request, article_id):
    if request.method == 'GET':
        # article = Article.objects.get(id=article_id)
        article = get_object_or_404(Article, id=article_id)
        serializer = ArticleSerializer(article)
        return Response(serializer.data)

🐥 Article 수정 UPDATE


로직은 이렇다.

db로부터 수정할 article 데이터를 가져온다.
'serializer'에 모델 인스턴스를 넣는데,
기존 데이터(가져온 모델 인스턴스)와 새로 수정할 데이터를 함께 넣는다.
DB의 데이터에 들어갈 새로 수정할 데이터의 유효성 검사를 해서 모든 조건에 부합하면 저장한다.
그리고 저장된 새 데이터를 'Response 객체'에 넣어 클라이언트에게 보내준다.

@api_view(['GET','PUT', 'DELETE'])
def article_view(request, article_id):
    if request.method == 'GET':
        # article = Article.objects.get(id=article_id)
        article = get_object_or_404(Article, id=article_id)
        serializer = ArticleSerializer(article)
        return Response(serializer.data)
    elif request.method == 'PUT':
        article = get_object_or_404(Article, id=article_id)
        serializer = ArticleSerializer(article, data = request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


수정할 새 데이터를 입력해주고 PUT 버튼을 누르면 기존 데이터가 새 데이터로 수정되고, DB에도 수정된 값이 나타날 것이다.

🐥 Article 삭제 DELETE


DRF에서 데이터 삭제는 간단하다.
삭제할 데이터를 가져와서 삭제해주면 반환해줘야할 내용도 없다.

@api_view(['GET','PUT', 'DELETE'])
def article_view(request, article_id):
    if request.method == 'GET':
        # article = Article.objects.get(id=article_id)
        article = get_object_or_404(Article, id=article_id)
        serializer = ArticleSerializer(article)
        return Response(serializer.data)
    elif request.method == 'PUT':
        article = get_object_or_404(Article, id=article_id)
        serializer = ArticleSerializer(article, data = request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    elif request.method == 'DELETE':
        article = get_object_or_404(Article, id=article_id)
        article.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)




데이터베이스에도 3번 article이 삭제된 것을 볼 수 있다.
이제 데이터베이스에 3번 article이 없으니 GET 요청으로 3번 article을 READ하면 HTTP 404 Not Found가 반환될 것이다.

전체 페이지를 가서 봐도 3번 article만 삭제된 것을 볼 수 있다.

여기까~지 장고에서 CRUD했던 것을 DRF방식으로 해봤는데 훨~~신 더 간단하고 쉬웠던 것 같다. 여러 다른 기능들을 추가해줬을 때는 어떤 방식으로 해야할지 아직 감이 오지 않지만 아주 편리한 프레임워크같다.

🐓 DRF Class-Based Views


지금까지 일반 퓨어 장고 프로젝트에서는 함수 기반 뷰(Function-Based Views, FBV)를 사용했지만 이번 DRF 프로젝트에서는 Class 기반 뷰(Class-Based Views,CBV)를 사용할 것이기에 API를 재작성 해보자.

참고 : https://www.django-rest-framework.org/tutorial/3-class-based-views/

[ 기존 코드 ]

from rest_framework.decorators import api_view
from articles.models import Article
from rest_framework.generics import get_object_or_404
from articles.serializers import ArticleSerializer
from rest_framework.response import Response
from rest_framework import status


@api_view(['GET','POST'])
def articleAPI(request):
    if request.method == 'GET':
        all_articles = Article.objects.all()
        all_serializer = ArticleSerializer(all_articles, many=True)
        return Response(all_serializer.data)
    elif request.method == 'POST':
        serializer = ArticleSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else :
            print(serializer.errors)
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


@api_view(['GET','PUT', 'DELETE'])
def articleDetailAPI(request, article_id):
    if request.method == 'GET':
        # article = Article.objects.get(id=article_id)
        article = get_object_or_404(Article, id=article_id)
        serializer = ArticleSerializer(article)
        return Response(serializer.data)
    elif request.method == 'PUT':
        article = get_object_or_404(Article, id=article_id)
        serializer = ArticleSerializer(article, data = request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    elif request.method == 'DELETE':
        article = get_object_or_404(Article, id=article_id)
        article.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

[ 클래스 기반 뷰로 재작성]

from rest_framework.views import APIView
from articles.models import Article
from rest_framework.generics import get_object_or_404
from articles.serializers import ArticleSerializer
from rest_framework.response import Response
from rest_framework import status
# from rest_framework.decorators import api_view


class ArticleList(APIView):

    def get(self, request, format=None):
        all_articles = Article.objects.all()
        all_serializer = ArticleSerializer(all_articles, many=True)
        return Response(all_serializer.data)
    

    def post(self, request, format=None):
        serializer = ArticleSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        else :
            # print(serializer.errors)
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
            
            
class ArticleDetail(APIView):

    def get(self, request, article_id, format=None):
        article = get_object_or_404(Article, id=article_id)
        serializer = ArticleSerializer(article)
        return Response(serializer.data)
    
    def put(self, request, article_id, format=None):
        article = get_object_or_404(Article, id=article_id)
        serializer = ArticleSerializer(article, data = request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        else:
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
        
    def delete(self, request, article_id, format=None):
        article = get_object_or_404(Article, id=article_id)
        article.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

[ articles/urls.py ]

from django.urls import path
from articles import views


urlpatterns = [
    # path('', views.articleAPI),
    # path('<int:article_id>/', views.articleDetailAPI),
    path("", views.ArticleList.as_view()),
    path('<int:article_id>/', views.ArticleDetail.as_view()),
]

함수 기반 뷰(Function-Based Views, FBV)나 Class 기반 뷰(Class-Based Views,CBV)가 각각의 장단점이 있지만 실무에서는 대부분 CBV를 활용한다.

클래스 기반 뷰(CBV)는 'GET', 'POST' 등 HTTP 메소드에 따른 처리 코드를 작성할 때 if 함수 대신에 메소드 명으로 코드의 구조가 깔끔하다. 그리고 다중상속 같은 객체지향 기법을 활용해 사용 가능한 기능들이 많고 코드의 재사용을 줄이고 생산성을 높일 수 있다.

위에 정의한 클래스들은 'APIView'라는 클래스를 상속하여 해당 클래스 내에서 API의 엔드포인트에서 특정 게시물(Article)의 데이터를 처리하는 동작을 구현했다.

🐔 POSTMAN & Swagger


이전에는 DRF의 from rest_framework.decorators import api_view 기능으로 브라우저블 API에서 데이터 CRUD 작업을 했다.

프로젝트를 진행할수록 기능도 많아지고 그에 따른 URL도 많아질텐데 그때마다 맞는 URL주소를 쳐서 이동하기에는 너무 귀찮기 때문에 POSTMANSwagger를 사용해보려고 한다.

- POSTMAN

POSTMAN을 사용하면 HTTP 요청 매서드(GET, POST, PUT, DELETE 등)을 쉽게 선택하고 사용할 수 있다. 또한 테스트 스크립트를 작성하여 자동화된 테스트를 수행하고 결과를 검증할 수 있다.

POSTMAN내에 추가해 놓은 요청들에서 각 요청과 응답을 모두 테스트 해볼 수 있다.

POSTMANEnvironment는 환경 변수를 관리하는데 사용되는 기능으로 POSTMAN에서 요청을 보낼 떄 사용하는 고정된 값들을 저장하고 관리할 떄 유용하다.
예를 들어, 환경 변수로 Base URL을 설정해서 모든 요청의 URL에서 값을 참조할 수 있다.

이렇게 로컬 환경 변수를 설정해서 요청을 할 떄마다 일일히 'http://127.0.0.1:8000'를 치지 않고 '{{ host }}'로 대채할 수 있어서 편하다.

- Swagger

Swagger는 API를 자동으로 문서화하고, 이를 쉽게 클라이언트에게 제공할 수 있다. 이로 인해서 개발 단계에서 API의 엔드포인트, 요청/응답 형식, 변수 등을 쉽게 이해할 수 있다.

참고 : https://drf-yasg.readthedocs.io/en/stable/readme.html

Swagger에 문서화된 OpenAPI를 띄우기 위한 작업들은 참고 사이트를 따라하면 된다.

이번에는 swagger_auto_schema 데코레이터에 대해서 잠깐 알아보고 포스팅을 마무리하려고 한다.

views.py에서 swagger_auto_schema 데코레이터를 사용하기 위해서는 drf-yasg (Yet Another Swagger Generator)라는 도구에서 임포트해와야 한다.

drf-yasg (Yet Another Swagger Generator)는 DRF 프로젝트에서 Swagger 문서를 자동으로 생성하고 관리하는 도구이다.

swagger_auto_schemadrf-yasg의 일부로, API 뷰의 Swagger 문서를 정의하는 데 사용되는 데코레이터이다.

swagger_auto_schema 데코레이터를 사용하면 API 뷰의 각 엔드포인트에 대한 Swagger 문서를 세부적으로 정의하고 커스터마이징할 수 있다. 이를 통해 API 사용자들에게 API 엔드포인트와 관련된 정보를 더 자세하게 제공할 수 있다.

[ swagger_auto_schema 데코레이터 ]

@swagger_auto_schema(
    operation_summary="요약 설명",
    operation_description="상세 설명",
    request_body=나의 Serializer,
    responses={200: MyResponseSerializer},
    tags=["태그 이름"],
)
def ....
  • operation_summary: 엔드포인트의 요약 설명을 지정
  • operation_description: 엔드포인트에 대한 상세한 설명을 지정
  • request_body: 요청 바디의 스웨거 스키마를 지정
  • responses: 응답의 스웨거 스키마를 지정
  • tags: 엔드포인트를 그룹화하는 데 사용되는 태그를 지정

원하는 커스터마이징 내용을 선택적으로 적어주면 된다.

이 정보들은 Swagger 문서에 표시되고, API 사용자에게 엔드포인트의 동작 및 사용법을 자세히 설명하는데 도움이 된다.

swagger_auto_schema를 사용하면 각 엔드포인트마다 Swagger 문서를 정의하고 구성할 수 있으며, API 문서화 작업을 보다 효율적으로 수행할 수 있다.

예를들어, post 함수에 이 데코레이터를 써주면 Swagger에서 POST 작업이 가능하다.

아직 request_body 속성밖에 써보지 않았지만 개인 프로젝트에서는 다른 속성들도 써봐야겠다.


🥚🐣🐤🐥🐓🐔

0개의 댓글