DevCourse TIL Day3 Week4

김태준·2023년 4월 26일
1

Data Enginnering DevCourse

목록 보기
14/93
post-thumbnail

금일 학습 내용은 django rest 프레임워크에서 등장하는 새로운 개념인 Serializer

각 용어를 살펴보면 다음과 같다!

  • Serialize : 모델 인스턴스, QuerySet 같은 데이터를 JSON 형식으로 변환하는 작업
  • Deserialize : JSON 형식의 데이터를 정의된 포맷에 맞춰 다시 모델 인스턴스로 변환하는 작업

+) 일반적으로 API 서버에서 JSON 형식의 데이터를 주고받는다

✅ Serializer

이전에 생성한 Question 모델을 바탕으로 Serializer를 만들고 각 작업을 잘 수행하는지 확인하기!
절차는 다음과 같다.

  • 기존 만들어 놓은 프로젝트에 (django rest framework를 구현할 새로운 앱 설치하기)
  • 이후 settings에 INSTALLED_APPS에 새로만든 앱의 Config와 rest_framework 추가해주기
# 아래 코드를 통해 serializer 구현위한 앱설치 진행
$ python manage.py startapp polls_api

# polls_api/serializers.py
from rest_framework import serializers
from polls.models import Question

class QuestionSerializer(serializers.Serializer):
    # 자동으로 생성되는 id 필드 포함해 기존 Question 모델에 속하는 필드 다 가져오기
    id = serializers.IntegerField(read_only = True)
    question_text = serializers.CharField(max_length = 200)
    pub_date = serializers.DatetimeField(read_only = True)
    # 새로 만듬
    # 유효성 검사 통과한 데이터 : validated_data로 주어짐
    def create(self, validated_data):
        return Question.objects.create(**validated_data)
    # 기존에 있던 걸 변경하므로 instance 매개변수 필요
    def update(self, instance, validated_data):
    	# validated_data에 값이 없을수도 있으므로 get(a, b) 형태로 구현
        instance.question_text = validated_data.get('question_text', instance.question_text)
        instance.save()
        return instance
-------------------------------------------------------------------------------------------------------------
# < django shell > 

#####  Serialize (모델 인스턴스 -> JSON)
from polls.models import Question
from polls_api.serializers import QuestionSerializer

q = question.object.first()
q # (쿼리셋-오브젝트 하나)
# 결과
<Question: 제목: 휴가를 어디서 보내고 싶으세요?, 날짜: 2023-02-05 18:52:59+09:00>

serializer = QustionSerializer(q)
serializer.data 
# field_name : value 형태로 결과 출력
{'id': 1, 'question_text': '휴가를 어디서 보내고 싶으세요?', 'pub_date': '2023-02-05T18:52:59Z'}

# JSON 형태로 변환 위해 라이브러리 호출
from rest_framework.renderers import JSONRenderer
json_str = JSONRenderer().render(serializer.data)
json_str
# 결과 (serializer.data 결과가 한글이라서 JSON 변환 시 인코딩된 결과) 
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 (JSON -> 모델 인스턴스)
import json
data = json.loads(json_str)
data
# 결과 (JSON TO DICTIONARY)
{'id': 1, 'question_text': '휴가를 어디서 보내고 싶으세요?', 'pub_date': '2023-02-05T18:52:59Z'}

# create
serializer = QuestionSerializer(data = data)
serializer.is_valid()
# 결과 : True
serializer.validated_data
# 결과 read_only 제거하고 보여줌 
: OrderedDict([('question_text', '휴가를 어디서 보내고 싶으세요?')])
# 만들어진 instance 유무 여부로 create or update 동작
new_question = serializer.save() 
new_question
# 결과
<Question: 제목: 휴가를 어디서 보내고 싶으세요?, 날짜: 2023-02-14 18:46:56.209215+00:00>

# Update
data = {'question_text': '제목수정'}
serializer = QuestionSerializer(new_question, data = data)
# save이전 필수작업
serializer.is_valid() 
# 결과 : True
serializer.save() 
# 결과
<Question: 제목: 제목수정[시리얼라이저에서 업데이트], 날짜: 2023-04-25 13:15:05.852404+00:00>

# validation이 통과하지 않는 경우
long_text = 'abcd' * 3000
data = {'qustion_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

: 기존 코드 간편화 (필드 일일이 정의 필요 X, create, update 메소드 생성 필요 X)

# polls_api/serializer.py
from rest_framework import serializers
from polls.models import Question

class QuestionSerializer(serializers.ModelSerializer):
    class Meta:
    	# model 이름과 fields명 명시 반드시 필요
        model = Question
        fields = ['id','question_text', 'pub_date']

# < django shell > 
from polls_api.serializers import QuestionSerializer
print(QustionSerializer())
# 결과 
QuestionSerializer():
    id = IntegerField(read_only=True)
    question_text = CharField(max_length=200)
    pub_date = DateTimeField(read_only=True)
# create 제대로 작동 하는지 확인
serializer = QuestionSerializer(data={'question_text':'모델시리얼라이저로 만들어 봅니다.'})
# 유효성 검사
serializer.is_valid()
# 결과 : True
serializer.save()
# create 결과 확인 가능
<Question: 제목: 모델시리얼라이저로 만들어 봅니다., 날짜: 2023-02-14 19:41:081444+00:00>

✨ HTTP Request Method & STATUS code

CRUD API 설계 시 기본이 되는 핵심 개념!!

  • CREATE : POST (새로운 데이터 적용 시 사용!)

  • READ : GET

  • UPDATE : PUT (기존에 있던 데이터 변경 시 사용)

  • DELETE : DELETE

  • 200번대 : 정상처리. 200: OK, 201: CREATED

  • 400번대 : 사용자의 잘못된 요청. 400: BAD REQUEST, 404: NOT FOUND

  • 500번대 : 서버 내부 오류

✅ GET (Read)

Qustion에 대해 JSON 형식으로 데이터를 제공하는 API 서버 구현!
-> views.py로 구현 가능

  • 정보를 조회하는 기능 : HTTP에서 GET 사용
  • 정보를 조회하지 않고 새로운 데이터 생성 : POST 메소드 사용
# polls_api/views.py
from django.shortcuts import render
# 메소드 정의 시 사용하는 라이브러리
from rest_framework.decorators import api_view
from polls.models import Question
from polls_api.serializers import QuestionSerializer
from rest_framework.response import Response
# 데코레이터의 () 가 비어있는 경우 GET 요청 처리
@api_view() # quetion_list 뷰는 http 요청을 객체로 받음
# 여러개의 question을 처리한다는 의미 부여를 위해 quetion_list로 함수명 지정
def question_list(request):
    questions = Question.objects.all()
    # 여러개 인식 위해 many 주기
    serializer = QuestionSerializer(questions, many = True)
    # 데이터를 HTTP 응답으로 반환
    return Response(serializer.data)

###  기존 project_name/urls.py , api/urls에 변경 사항 적용 必

# polls_api/urls.py
from django.urls import path
from .views import *
urlpatterns = [
    path('question/', question_list, name='question-list')]
    
# project/urls.py
# urlpattern 에 아래 내용 추가!
    path('rest/', include('polls_api.urls'))


# 결과 - Allow에 GET 추가된 것 확인 가능

✅ POST (Create)

question을 만드는 역할

# POST- CREATE 역할
from rest_framework import status
@api_view(['GET', 'POST'])
# 여러개의 question을 처리한다는 의미 부여를 위해 quetion_list로 함수명 지정
def question_list(request):
    if request.method == 'GET':
        questions = Qustions.objects.all()
        serializer = QuestionSerializer(questions, many = True)
        return Response(serializer.data)
    if request.method == 'POST':
        # JSON으로 올라온 요청 serializer에 전달 
        # 새로만들때는 인스턴스 부여 X, 이후 유효성 검사해 save 할 것.
        serializer = QuestionSerializer(data = request.data)
        if serializer.is_valid():
            serializer.save()
            # 생성되었음을 의미하도록 201 부여
            return Response(serializer.data, status = status.HTTP_201_CREATED)
        # 잘못된 응답에 대해 400_BAD_REQUEST 처리
        else:
            return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST)
# 결과 - Allow 에 Post 생긴 것 확인 가능

✅ PUT(Update) / DELETE(Delete)

앞서 여러 qustion에 대해 처리했다면 이번에는 단일 question에 대한 처리가 필요하다

# 하나의 question에 대해 처리
from django.shortcuts import get_object_or_404
@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 기반으로 위 코드 구현하면 다음과 같다.

from rest_framework.views import APIView

class QuestionList(APIView):
    def get(self, request):
        questions = Qustion.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)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

class QustionDetail(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)

# api/urls.py
## 클래스로 구현한 것 urlpattern
urlpatterns = [
    path('question/', QuestionList.as_view(), name = 'question-list'),
    path('question/<int:id>', QuestionDetail.as_view(), name = 'question-detail'),
]

✍️ 클래스로 구현한 부분이 데코레이터도 필요가 없어서 조금 더 깔끔한 느낌!!
+) 다양한 클래스를 활용해 (상속을 받을 수 있기에) 반복 코드 필요 없이 쉽게 view를 만들 수 있다.

✅ Mixin 활용해 코드 수정

django rest_framework

### Mixin 활용해 처리
from polls.models import Question
from polls_api.serializers import QuestionSerializer
from rest_framework import mixins
from rest_framework import 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)

## Mixin 활용해 구현한 URLpattern
urlpatterns = [
    path('question/', QuestionList.as_view(), name='question-list'),
    # Class와 다른 점은 id가 아닌 pk로 객체를 받아옴!
    path('question/<int:pk>/', QuestionDetail.as_view(), name='question-detail'),
]

✍️ 클래스에 비해 각 class 별로 미리 question에 대한 queryset과 serializer를 구현해 놓았기에 훨씬 더 수월하게 구현된 느낌.

✅ Generic API View

훨씬 더 간단하게 구현이 가능하다. 기존 generics.GenericAPIView에서 Generic에 해당하는 부분에 ListCreate, RetrieveUpdateDestory를 지정하여 그냥 처리가 가능하다..

from polls.models import Question
from polls_api.serializers import QuestionSerializer
from rest_framework import generics
# GET, POST 구현
class QuestionList(generics.ListCreateAPIView):
    queryset = Question.objects.all()
    serializer_class = QuestionSerializer
# GET, PUT, DELETE 구현
class QuestionDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Question.objects.all()
    serializer_class = QuestionSerializer

🎇 정리

rest_framework 모듈을 불러오는데 밑줄이 계속 뜬다.. install도 했고, 뭐 다해봤는데도 이러네
음... 이유를 좀 찾아봐야 할 것 같고,
확실히 월요일부터 시작해서 가상환경 세팅해서 프로젝트, app 만들어서 템플릿 꾸리고 모델 만들어서 migration하는 작업이 사전에 완성도 있게 이루어져야 쫌 뒷부분이 괜찮을 것 같다는 생각이 든다. 가상 환경 만들고, 기본적으로 구성하는 과정이 제일 어려운 듯😭

주말에 무조건 복습 꼭 하기❗❗

profile
To be a DataScientist

0개의 댓글