Django를 사용해 API 서버 만들기 (TIL 13)

석형원·2024년 4월 10일

TIL

목록 보기
13/52

✏️ 오늘 학습한 내용

Serializer와 Views

  • Django Shell에서 Serializer 사용
  • ModelSerializer
  • HTTP Request Method
  • GET, POST, PUT, DELETE
  • Class 기반의 Views
  • Mixin
  • Generic API View

🔎 Serializer와 Views

  • Serialize

    모델 인스턴스나 QuerySet과 같은 데이터들을 API로 주고 받기 위해서 JSON 파일의 형식으로 변환하는 작업

  • Deserialize

    JSON 형식의 데이터들을 정의된 포맷에 맞추어 다시 모델 인스턴스로 변환하는 작업

  • Serializer

    위와 같은 변환과정을 담당하는 매개체,
    모델에 있는 데이터를 json 데이터로 변환하고 json 데이터를 모델을 통해 테이블에 저장하는 역할을 함

  • polls_api App 추가
    python manage.py startapp polls_api

  • polls_api.serializers.py 생성

    from rest_framework import serializers
    # polls의 모델을 불러옴
    from polls.models import Question
    
    # Question 모델에 있는 필드들을 Serialize하기 위해선
    # 각 필드들을 매칭시켜주는 작업을 해야함
    # 단, 어떤 필드들을 Serializer 내부에 적어주지 않는다면,
    # 해당 필드에 있는 데이터는 Serialize되지않음
    class QuestionSerializer(serializers.Serailizer):
        id = serializers.IntegerField(read_only=True)
        question_text = serializers.CharField(max_length=200)
        pub_date = serializers.DateTimeField(read_only=True)
    
        # serializer는 생성할 때, 유효성 검사를 통과한 데이터를 기반으로 저장(validated_data)
        def create(self, validated_data) :
            # validated_date를 기반으로 Question을 생성
            return Question.objects.create(**validated_data)
    
        # 받아온 instance에 validated_data를 이용해서 업데이트
        def update(self, instance, validated_data):
            # question_text가져와서 instance에 넣어줌
            # validated_data에 값이 없으면 원래 값을 유지해라
            instance.question_text = validated_data.get('question_text',instance.question_text)
            instance.save()
            return instance
    • .get(key_name){key:value}에서 key값에 맞는 value를 반환하는 함수

Django Shell에서 Serializer 사용

from polls.models import Question
from polls_api.serializers import QuestionSerializer

# Serialization

# 모델을 Dictionary 형식으로 변환
q = Question.objects.first()
serializer = QuestionSerializer(q)
serializer.data

# String 형태의 JSON으로 변환
#json 형태를 바꾸기 위해선 JSONRenderer가 필요
from rest_framework.renderers import JSONRenderer
json_str = JSONRenderer().render(serializer.data)
json_str

# Deserialization

# JSON에서 Dictionary로 변환
import json
data = json.loads(json_str)
# serializer.data와 동일해졌음을 확인
data

# 모델을 만들기 위해 새로운 serializer를 생성
# dictionary를 모델로 변환
serializer = QuestionSerializer(data=data)
# 유효성 검증, 필수
serializer.is_valid()

# save()시, 만들어진 instance가 없다면 create
# instance가 있다면 update를 진행
# 이 경우 create가 동작.
new_question = serializer.save()

# update
data = {'question_text':'제목 수정'}
serializer = QuestionSerializer(new_question, data=data)
serializer.is_valid()
# new_question이란 instance가 있으므로 update 진행
serializer.save()

# validation이 통과하지 않는 상황
long_text = "abcd"*300
data = {'question_text' : long_text}
serializer = QuestionSerializer(data=data)
# False
serializer.is_valid()
# 원인 확인
serializer.errors
# field가 최대치인 200을 초과했기에 발생

ModelSerializer

  • Serializer가 아닌 ModelSerializer로 단순화

  • polls_api.serializers.py 수정

    from rest_framework import serializers
    from polls.models import Question
    
    class QuestionSerializer(serializers.ModelSerializer):
        # create, update가 자동으로 생성되어 작동
        class Meta:
            model = Question
            fields = ['id','question_text','pub_date']

HTTP Request Method

[참고 웹사이트]

https://developer.mozilla.org/ko/docs/Web/HTTP/Methods

  • CRUD 기능을 구현하는 HTTP 메서드

    • 데이터 생성(Create) : POST

    • 데이터 조회(Read) : GET

    • 데이터 업데이트(Update) : PUT

    • 데이터 삭제(Delete) : DELETE

      -> 사용자의 입장에서 생각하는 것이 바람직함
      ( POST와 PUT이 혼동될 수 있음 )

GET

  • HTTP에서 정보를 조회하는 메소드 : GET

  • 정보를 조회하지않고 새로운 데이터를 만드는 메소드 : POST

  • rest_framework 설치
    py -m pip install djangorestframework

    • settings.py에 등록
      ...
      INSTALLED_APPS = [
        'rest_framework',
        ...
      ]
  • polls_api.views.py

    from django.shortcuts import render
    # api_view : 메소드를 정의할 때 사용
    from rest_framework.decorators import api_view
    from rest_framework.response import Response
    from polls.models import Question
    from polls_api.serializers import QuestionSerializer
    
    # Question의 목록 구현
    # @api_view() : question_list가 get요청을 처리할 것이다.
    @api_view()
    def question_list(request) :
        questions = Question.objects.all()
        # 여러 개의 instance를 serializer에 줄 때는 many옵션을 사용
        serializer = QuestionSerializer(questions, many = True)
        return Response(serializer.data) 
  • polls_api.urls.py 생성

    from django.urls import path
    from .views import *
    
    urlpatterns = [
        path('question/',question_list, name='question-list')
    ]
  • urls.py에 등록

    urlpatterns = [
        path('admin/', admin.site.urls),
        path('polls/', include('polls.urls')),
        path('rest/', include('polls_api.urls')),
    ]

    -> 실제론 Json 형식으로 요청으로 이루어짐,
    Django가 api로 받은 Json을 가시화한 것

POST

  • polls_api.views.py

    from django.shortcuts import render
    from rest_framework.decorators import api_view
    from rest_framework.response import Response
    from polls.models import Question
    from polls_api.serializers import QuestionSerializer
    from rest_framework import status
    
    # @api_view(['GET','POST']) : get, post 요청을 둘다 처리함
    @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) 
    
        # post일 경우, 요청의 data를 받아와 새로운 question으로 만들어줌
        if request.method == 'POST':
            serializer = QuestionSerializer(data=request.data)
            if serializer.is_valid():
                serializer.save()
                # 보다 명확하게 생성이 되었다는 status를 전달
                return Response(serializer.data, status=status.HTTP_201_CREATED)
            else:
                # 잘못된 응답에 대해서 400 BAD request를 출력
                return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
  • CREATE TEST

  • HTTP Status Code
    • 200번대 : 정상적인 결과
      • 200 OK
      • 201 CREATED
      • 204 NO CONTENT
    • 400번대 : 사용자의 잘못된 요청
      • 400 BAD REQUEST
      • 404 NOT FOUND
    • 500번대 : 서버 내부의 오류

PUT, DELETE

  • polls_api.views.py

    from django.shortcuts import render, get_object_or_404
    from rest_framework.decorators import api_view
    from rest_framework.response import Response
    from polls.models import Question
    from polls_api.serializers import QuestionSerializer
    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 기반의 Api_view로 변환

  • 장점

    • 코드가 잘 정리됨

    • Django rest_framework에서 제공하는 다양한 class들을 활용해서 반복되는 코드를 작성할 필요 없이 쉽게 views를 만들 수 있다.

    • class는 상속을 받을 수 있음

  • polls_api.views.py

    from django.shortcuts import render, get_object_or_404
    from rest_framework.decorators import api_view
    from rest_framework.response import Response
    from polls.models import Question
    from polls_api.serializers import QuestionSerializer
    from rest_framework import status
    # class 기반 api_view
    from rest_framework.views import APIView
    
    # if 문을 사용하지 않고 method로 더 확실하게 분리
    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)
  • polls_api.urls.py

    from django.urls import path
    from .views import *
    
    urlpatterns = [
        # as_view가 요청을 받으면 QuestionList의 인스턴스를 생성
        path('question/', QuestionList.as_view() , name='question-list'),
        path('question/<int:id>/', QuestionDetail.as_view(),name="queston-detail"),
    ]

Mixin

  • Mixin을 사용하여 코드를 더욱 간단화

  • polls_api.views.py

    from django.shortcuts import render, get_object_or_404
    from rest_framework.decorators import api_view
    from rest_framework.response import Response
    from polls.models import Question
    from polls_api.serializers import QuestionSerializer
    from rest_framework import status, mixins, generics
    from rest_framework.views import APIView
    
    # ListModelMixin -> GET에서 활용
    # CreateModelMixin -> POST에서 활용
    class QuestionList(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
        queryset = Question.objects.all()
        serializer_class = QuestionSerializer
    
        def get(self, request, *args, **kwargs):
            # list 호출, list 메소드는 ListModelMixin에 정의되어있음
            # 받은 값을 그대로 전달
            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):
        # pk를 받을 때, QuestionDetail이 실행되므로
        # 단일 객체가 아닌 Question.objects.all()을 사용하더라도 내부적으로 처리가 됨
        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.destory(request, *args, **kwargs)
  • polls_api.urls.py

    from django.urls import path
    from .views import *
    
    urlpatterns = [
        path('question/', QuestionList.as_view() , name='question-list'),
        # GenericAPIView에서 primary key를 pk로 받기 때문에, pk라 해줘야함
        path('question/<int:pk>/', QuestionDetail.as_view(),name="queston-detail"),
    ]

Generic API View

  • Generic API를 사용하여 코드를 더더욱 간단화

  • polls_api.views.py

    from polls.models import Question
    from polls_api.serializers import QuestionSerializer
    from rest_framework import generics
    
    # class 안에서 기능이 전부 구현이 되어있기 때문에, 이를 상속받아 사용
    class QuestionList(generics.ListCreateAPIView):
        queryset = Question.objects.all()
        serializer_class = QuestionSerializer
    
    class QuestionDetail(generics.RetrieveUpdateDestroyAPIView):
        queryset = Question.objects.all()
        serializer_class = QuestionSerializer
    
  • class를 통해 기능을 상속 받았기에 코드가 훨씬 간결해졌음을 확인할 수 있다.

profile
데이터 엔지니어를 꿈꾸는 거북이, 한걸음 한걸음

0개의 댓글