장고(Django) - REST framework
Serializer
serializer는 serialize와 deserialize의 변환 작업을 담당하는 매개체이다. 일반적으로 API 서버에서는 JSON 형식으로 데이터를 주고받기 때문에 serializer를 활용해 JSON 형식으로 변환하여 사용한다.
- serialize : Model 인스턴스나 QuerySet과 같은 데이터를 JSON 형식의 파일로 변환하는 작업
- deserialize : JSON 형식의 데이터를 정의된 포맷에 맞춰 다시 Model 인스턴스로 변환하는 작업
rest framework 설치
rest framework를 처음 사용할 경우 아래의 명령어로 설치가 필요하다.
pip install djangorestframework
polls_api App 생성
serializer를 생성 및 사용하는 실습을 진행하기 위해 새로운 App을 생성한다.
python manage.py startapp polls_api
polls_api/serializers.py
polls_api 폴더에 serializers.py 파일을 만들고, 아래와 같이 Question의 필드를 모두 가져왔다. 또한 create와 update 메서드를 생성하여 json 파일이 주어졌을 때, 생성하고 업데이트하는 작업을 실시하도록 하였다.
from rest_framework import serializers
from polls.models import Question
class QuestionSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
question_text = serializers.CharField(max_length=200)
pub_date = serializers.DateTimeField(read_only=True)
def create(self, validated_data):
return Question.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.question_text = validated_data.get('question_text', instance.question_text)
instance.save()
return instance
Django Shell에서 Serializer 사용하기
Serialize
Question 모델에 저장돼있는 질문을 JSONRenderer을 활용해서 JSON 형태로 변형하였다.
from polls.models import Question
from polls_api.serializers import QuestionSerializer
from rest_framework.renderers import JSONRenderer
q = Question.objects.first()
# <Question: 제목: 휴가를 어디서 보내고 싶으세요?, 날짜: 2023-02-05 18:52:59+09:00>
serializer = QuestionSerializer(q)
# {'id': 1, 'question_text': '휴가를 어디서 보내고 싶으세요?', 'pub_date': '2023-02-05T18:52:59Z'}
json_str = JSONRenderer().render(serializer.data)
# b'{"id":1,"question_text":"\xed\x9c\xb4\xea\xb0\x80\xeb\xa5\xbc
# \xec\x96\xb4\xeb\x94\x94\xec\x84\x9c
# \xeb\xb3\xb4\xeb\x82\xb4\xea\xb3\xa0
# \xec\x8b\xb6\xec\x9c\xbc\xec\x84\xb8\xec\x9a\x94?",
# "pub_date":"2023-02-05T18:52:59Z"}'
Deserialize - create
json_str은 위 코드의 변수를 그대로 사용한다. json으로 저장된 데이터를 받아와 create 메서드를 실행한다. json_str은 Question의 첫 번째 오브젝트를 가져오므로 똑같은 질문이 하나 더 생긴다.
import json
data = json.loads(json_str)
# {'id': 1, 'question_text': '휴가를 어디서 보내고 싶으세요?', 'pub_date': '2023-02-05T18:52:59Z'}
serializer = QuestionSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
# OrderedDict([('question_text', '휴가를 어디서 보내고 싶으세요?')])
new_question = serializer.save() # Create
# <Question: 제목: 휴가를 어디서 보내고 싶으세요?, 날짜: 2023-02-14 18:46:56.209215+00:00>
Deserialize - update
new_question은 위 코드의 변수를 그대로 사용한다. 저장된 model의 data를 수정하였기 때문에 update 메서드가 실행된다.
data={'question_text': '제목수정'}
serializer = QuestionSerializer(new_question, data=data)
serializer.is_valid()
# True
serializer.save() # Update
# <Question: 제목: 제목수정[시리얼라이저에서 업데이트], 날짜: 2023-04-25 13:15:05.852404+00:00>
Validation False
Question_text의 최대 길이가 200으로 설정돼 있는데, 초과되어 is_valid()가 False를 반환하였다. errors를 활용하면 어떤 이유로 Validation이 False가 발생했는지 확인할 수 있다.
long_text = "abcd"*300
data = {'question_text':long_text}
serializer = QuestionSerializer(data=data)
serializer.is_valid()
# False
serializer.errors
# {'question_text': [ErrorDetail(string='Ensure this field has no more than 200 characters.', code='max_length')]}
ModelSerializer
Serializer를 ModelSerializer로 변경하면서 QuestionSerializer 코드가 간결해졌다. 이전에 작성했던 것과 다르게 Meta 정보만 넣어주면, create 및 update를 알아서 수행해 준다.
from rest_framework import serializers
from polls.models import Question
class QuestionSerializer(serializers.ModelSerializer):
class Meta:
model = Question
fields = ['id','question_text', 'pub_date']
Deserialize - create
print(QuestionSerializer())
# result
# QuestionSerializer():
# id = IntegerField(read_only=True)
# question_text = CharField(max_length=200)
# pub_date = DateTimeField(read_only=True)
serializer = QuestionSerializer(data={'question_text':'모델시리얼라이저로 만들어 봅니다.'})
serializer.is_valid()
serializer.save()
# <Question: 제목: 모델시리얼라이저로 만들어 봅니다., 날짜: 2023-02-14 19:41:081444+00:00>
GET
GET을 통해 Question의 정보를 확인할 수 있는 페이지를 만드는 실습을 진행한다.
polls_api/views.py
question_list 메서드를 생성하여 serialize된 데이터를 페이지에 출력할 수 있도록 작성한다. 여기서 데코레이터로 @api_view()에서 괄호 안이 비어있으면 GET 메서드를 사용한다는 것을 의미한다. 또한 serializer에 여러 개의 Question가 입력되기 때문에 many=True 인자를 추가해 준다.
from polls.models import Question
from polls_api.serializers import QuestionSerializer
from rest_framework.response import Response
from rest_framework.decorators import api_view
@api_view()
def question_list(request):
questions = Question.objects.all()
serializer = QuestionSerializer(questions, many = True)
return Response(serializer.data)
polls_api/urls.py
페이지의 path를 추가한다.
from django.urls import path
from .views import *
urlpatterns = [
path('question/', question_list, name='question-list')
]
mysite/urls.py
mysite의 urls.py에도 path를 추가해주어야 한다.
from django.urls import include, path
from django.contrib import admin
urlpatterns = [
path('admin/', admin.site.urls),
path('polls/', include('polls.urls')),
path('rest/', include('polls_api.urls')),
]
결과
데이터를 쉽게 확인할 수 있도록 조정하여 보여준다. 그러나 실제로 구현한 것은 오직 텍스트만 작성된 것을 확인할 수 있다.
HTTP Methods
- 데이터 생성(Create): POST
- 데이터 조회(Read) : GET
- 데이터 업데이트(Update) : PUT
- 데이터 삭제(Delete) : DELETE
POST
작성했던 question_list를 수정하여 POST 메서드를 활용한 데이터 생성을 하는 실습을 진행한다.
polls_api/views.py
데코레이터 @api_view의 인자로 GET과 POST를 넘겨주어 페이지에서 POST 메서드도 받을 수 있도록 한다. question_list 내에는 GET과 POST를 따로 처리하도록 작성하였고, 입력 값의 validation에 따라 출력 데이터와 status 코드를 반환하도록 하였다.
from rest_framework.decorators import api_view
from polls.models import Question
from polls_api.serializers import QuestionSerializer
from rest_framework.response import Response
from rest_framework import status
@api_view(['GET','POST'])
def question_list(request):
if request.method == 'GET':
questions = Question.objects.all()
serializer = QuestionSerializer(questions, many = True)
return Response(serializer.data)
if request.method == 'POST':
serializer = QuestionSerializer(data=request.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)
PUT/DELETE
Question 모델의 질문을 수정하고 삭제하는 페이지를 만드는 실습을 진행한다.
polls_api/urls.py
id에 따른 특정 질문을 확인할 수 있도록 urls.py에 새로운 path를 생성한다.
from django.urls import path
from .views import *
urlpatterns = [
path('question/', question_list, name='question-list'),
path('question/<int:id>', question_detail, name='question-detail')
]
polls_api/views.py
question_list는 여러 개의 질문을 출력하도록 했지만, 특정 id의 질문만을 확인할 수 있도록 question_detail 페이지를 생성하였다. 데코레이터의 인자에는 GET, PUT, DELETE 메서드를 입력하여 수정 및 삭제 작업을 할 수 있도록 하였다. DELETE 메서드의 상태 코드는 제대로 지워졌다는 의미를 표현하기 위해 204를 반환하도록 한다.
from django.shortcuts import render, get_object_or_404
from rest_framework.decorators import api_view
from polls.models import Question
from polls_api.serializers import QuestionSerializer
from rest_framework.response import Response
from rest_framework import status
@api_view(['GET', 'PUT', 'DELETE'])
def question_detail(request, id):
question = get_object_or_404(Question, pk=id)
if request.method == 'GET':
serializer = QuestionSerializer(question)
return Response(serializer.data)
if request.method == 'PUT':
serializer = QuestionSerializer(question, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
if request.method == 'DELETE':
question.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
Class 기반의 뷰(Views)
메서드 기반의 뷰를 작성할 수도 있지만, Class 기반의 뷰도 작성이 가능하다. 이전에 작성했던 뷰를 Class 기반의 뷰로 변환하는 실습을 진행한다.
polls_api/urls.py
urls.py의 링크 부분에는 Class 이름에 as_view()를 사용하도록 변경해야 한다.
from django.urls import path
from .views import *
urlpatterns = [
path('question/', QuestionList.as_view(), name='question-list'),
path('question/<int:id>', QuestionDetail.as_view(), name='question-detail')
]
polls_api/views.py
메서드 기반의 뷰에서는 데코레이터 api_view를 사용해야 했지만, 여기서는 사용하지 않아도 된다. 또한 클래스 메서드 명이 http 메서드 명이기 때문에 직관적으로 이해가 가능하다.
from django.shortcuts import render, get_object_or_404
from polls.models import Question
from polls_api.serializers import QuestionSerializer
from rest_framework.response import Response
from rest_framework import status
from rest_framework.views import APIView
class QuestionList(APIView):
def get(self, request):
questions = Question.objects.all()
serializer = QuestionSerializer(questions, many=True)
return Response(serializer.data)
def post(self, request):
serializer = QuestionSerializer(data=request.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)
class QuestionDetail(APIView):
def get(self, request, id):
question = get_object_or_404(Question, pk=id)
serializer = QuestionSerializer(question)
return Response(serializer.data)
def put(self, request, id):
question = get_object_or_404(Question, pk=id)
serializer = QuestionSerializer(question, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, id):
question = get_object_or_404(Question, pk=id)
question.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
MIXIN
Class 기반의 뷰를 작성했던 위의 코드에서 APIView 대신에 mixins와 generics를 사용해서 http 메서드를 구현하는 실습을 진행한다.
polls_api/urls.py
링크는 그대로 as_view()를 받아오면 되지만, mixins의 내부에서 id가 아닌 pk로 데이터를 처리하므로 <int:pk>로 변경해주어야 한다.
from django.urls import path
from .views import *
urlpatterns = [
path('question/', QuestionList.as_view(), name='question-list'),
path('question/<int:pk>', QuestionDetail.as_view(), name='question-detail')
]
polls_api/views.py
APIView 대신에 mixins와 generics를 사용하여 views.py를 구현하였다. 이전보다 훨씬 간결하게 작성된 것을 확인할 수 있다.
from polls.models import Question
from polls_api.serializers import QuestionSerializer
from rest_framework import mixins, generics
class QuestionList(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
queryset = Question.objects.all()
serializer_class = QuestionSerializer
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 QuestionDetail(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView):
queryset = Question.objects.all()
serializer_class = QuestionSerializer
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
Generic API View
polls_api/views.py
위의 코드를 더 간결하게 표현도 가능하다. generics를 사용하면 get, post 등의 메서드가 이미 정의되어 있기 때문에 따로 메서드를 정의해 줄 필요가 없다.
from polls.models import Question
from polls_api.serializers import QuestionSerializer
from rest_framework import generics
class QuestionList(generics.ListCreateAPIView):
queryset = Question.objects.all()
serializer_class = QuestionSerializer
class QuestionDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Question.objects.all()
serializer_class = QuestionSerializer
결론
느낀 점
REST framework를 사용하면 CRUD 작업을 간단하게 수행할 수 있다는 것을 알게 되었다. 배우기 전까지는 어렵다고만 생각하고 있었던 부분이었는데, 물론 쉬워 보이진 않지만 생각보다 간단히 수행되는 게 신기했다. 사용해야 할 메서드가 너무 많아서 프로젝트를 진행할 때 잘 사용할 수 있을지는 모르겠다.
어려웠던 점
HTTP 메서드에 관해서는 반복적으로 학습했기 때문에 그나마 어렵지 않았는데, Serialize/Deserialize에 대한 개념을 완벽히 이해하기는 힘들었다.
'[프로그래머스] 데이터 엔지니어링 데브코스 3기 > TIL(Today I Learn)' 카테고리의 다른 글
[TIL - 15일 차] 파이썬 장고 프레임웍을 사용해서 API 서버 만들기 (5) (0) | 2024.04.12 |
---|---|
[TIL - 14일 차] 파이썬 장고 프레임웍을 사용해서 API 서버 만들기 (4) (0) | 2024.04.11 |
[TIL - 12일 차] 파이썬 장고 프레임웍을 사용해서 API 서버 만들기 (2) (0) | 2024.04.09 |
[TIL - 11일 차] 파이썬 장고 프레임웍을 사용해서 API 서버 만들기 (1) (0) | 2024.04.08 |
[TIL - 10일 차] 데이터 엔지니어링 : 파이썬으로 웹 데이터를 크롤하고 분석하기 (5) (0) | 2024.04.05 |