from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('articles/', include('articles.urls')),
]
from django.urls import path
from articles import views
urlpatterns = [
path('index/', views.index),
path('<int:article_id>', views.article_view),
]
@api_view(['GET','PUT', 'DELETE'])
def article_view(request, article_id):
return Response(article_id)
들어가기에 앞서,
이전 퓨어 장고에서는 클라이언트와 서버가 데이터를 주고 받을 때는 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
속성을 통해 얻을 수 있다.
DRF
를 사용하여 클라이언트가 POST 요청과 함께 보낸 데이터를 모델 형태로 변환하고 저장하려면 정의한 Serializer 클래스
에 넣어서 데이터를 직렬화하고 모델로 변환 후 데이터를 저장해야한다.
직렬화
는 모델 인스턴스 객체를 JSON 데이터 형식으로 문자열을 Key:Value 형태로 바꿔주는 것
역직렬화
는 JSON 데이터를 받아와서 이 Key:Value형태의 문자열 데이터를 모델 형태로 바꿔주는 것
직렬화와 역직렬화 모두 이것에 대한 정의는
serializer.py
에서 하고 그 데이터는.data
속성으로 얻을 수 있다.
from rest_framework import serializers
from articles.models import Article
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = "__all__"
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 요청을 한다면,
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값이 필요하다.
API가 연결되었으니 'GET'요청방식으로 특정 article을 보여주는 Data Read를 해보자.
일단 로직은, 데이터베이스에서 url에서 받은 'article_id'와 'id값'이 같은 데이터를 가져와서 그 모델 인스턴스를 serializer
에 넣고 JSON 직렬화를 시켜 .data
로 직렬화된 데이터를 가져와서 Response 객체
에 넣어 클라이언트에게 보내준다.
@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)
로직은 이렇다.
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에도 수정된 값이 나타날 것이다.
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방식으로 해봤는데 훨~~신 더 간단하고 쉬웠던 것 같다. 여러 다른 기능들을 추가해줬을 때는 어떤 방식으로 해야할지 아직 감이 오지 않지만 아주 편리한 프레임워크같다.
지금까지 일반 퓨어 장고 프로젝트에서는 함수 기반 뷰(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)
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)의 데이터를 처리하는 동작을 구현했다.
이전에는 DRF의 from rest_framework.decorators import api_view
기능으로 브라우저블 API에서 데이터 CRUD 작업을 했다.
프로젝트를 진행할수록 기능도 많아지고 그에 따른 URL도 많아질텐데 그때마다 맞는 URL주소를 쳐서 이동하기에는 너무 귀찮기 때문에 POSTMAN
과 Swagger
를 사용해보려고 한다.
- POSTMAN
POSTMAN
을 사용하면 HTTP 요청 매서드(GET, POST, PUT, DELETE 등)을 쉽게 선택하고 사용할 수 있다. 또한 테스트 스크립트를 작성하여 자동화된 테스트를 수행하고 결과를 검증할 수 있다.
POSTMAN
내에 추가해 놓은 요청들에서 각 요청과 응답을 모두 테스트 해볼 수 있다.
POSTMAN
의Environment
는 환경 변수를 관리하는데 사용되는 기능으로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_schema
는drf-yasg
의 일부로, API 뷰의Swagger 문서
를 정의하는 데 사용되는 데코레이터이다.
swagger_auto_schema 데코레이터
를 사용하면 API 뷰의 각 엔드포인트에 대한Swagger 문서
를 세부적으로 정의하고 커스터마이징할 수 있다. 이를 통해 API 사용자들에게 API 엔드포인트와 관련된 정보를 더 자세하게 제공할 수 있다.
@swagger_auto_schema(
operation_summary="요약 설명",
operation_description="상세 설명",
request_body=나의 Serializer,
responses={200: MyResponseSerializer},
tags=["태그 이름"],
)
def ....
원하는 커스터마이징 내용을 선택적으로 적어주면 된다.
이 정보들은 Swagger 문서
에 표시되고, API 사용자에게 엔드포인트의 동작 및 사용법을 자세히 설명하는데 도움이 된다.
swagger_auto_schema
를 사용하면 각 엔드포인트마다 Swagger 문서
를 정의하고 구성할 수 있으며, API 문서화 작업을 보다 효율적으로 수행할 수 있다.
예를들어, post 함수에 이 데코레이터를 써주면 Swagger
에서 POST 작업이 가능하다.
아직 request_body 속성밖에 써보지 않았지만 개인 프로젝트에서는 다른 속성들도 써봐야겠다.
🥚🐣🐤🐥🐓🐔