[Django REST Framework] Part 1: 시리얼라이저(Serializers)와 뷰(Views)

김서연·2024년 4월 22일

Django

목록 보기
6/8

1. Serilaizer

  • Serilaize(직렬화): 모델 인스턴스나 QuerySet과 같은 데이터를 JSON 형식의 파일로 변환하는 작업
  • Deserialize(역직렬화): JSON 형식의 데이터를 정의된 포맷에 맞추어 다시 모델 인스턴스로 변환하는 작업
  • Serializer: Serialize, Deserilaize 같은 작업을 진행한다
    • 일반적으로 API 서버에서는 JSON 형태로 데이터를 주고 받는다

    • RESTful API에서 데이터를 전송할 때 자주 사용된다.

기능 구현을 위한 polls_api app 만들기

  • mysite 프로젝트 안에서 장고 rest 프레임워크의 기능을 구현할 새로운 app
$ python [manage.py](http://manage.py/) startapp polls_api
  • polls_api/serializers.py
    • rest framework install

      $ pip install djangorestframework
    • question serializer
      - field: id, question_text, pub_date
      - method: create(), update()

      from rest_framework import serializers
      from polls.models import Question
      
      class QuestionSerializer(serializers.Serializer):
      		# id는 수정할 수 없으니 read_only
          id = serializers.IntegerField(read_only=True)
          question_text = serializers.CharField(max_length=200)
          pub_date = serializers.DateTimeField(read_only=True)
      
          # 업데이트 X, 새로 만드는 것
          def create(self, validated_data):
              # 유효성 검사를 통과해야 함. 통과한 것이 validated_data
              # validated_data 기반으로 객체 생성
              return Question.objects.create(**validated_data)
          
          # 업데이트 O, 기존 객체(instance)를 valid_data로 업데이트
          def update(self, instance, validated_data):
              # validated_data가 없을 경우 유지
              instance.question_text = validated_data.get('question_text', instance.question_text)
              instance.save()
              return instance

2. Django Shell에서 Serializer 사용하기

Serializer 수정

  • polls_api/serializers.py
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):
		    # update한 출처 명시
        instance.question_text = validated_data.get('question_text', instance.question_text) + '[시리얼라이저에서 업데이트]'
        instance.save()
        return instance

Serialize

  • Question 모델을 Serializer 객체로 만들기
>>> from polls_api.serializers import *
>>> from polls.models import *
>>> q = Question.objects.first()
>>> q
<Question: 제목: 휴가를 어디서 보내고 싶으세요?, 날짜: 2024-04-04 05:56:50+00:00>
>>> serializer = QuestionSerializer(q)
>>> serializer.data
{'id': 1, 'question_text': '휴가를 어디서 보내고 싶으세요?', 'pub_date': '2024-04-04T05:56:50Z'}
  • serializer 객체의 data를 JSONRenderer를 통해 JSON으로 변환하기
    • JSONRenderer()는 Serialize된 데이터를 JSON 형식으로 렌더링한다
>>> from rest_framework.renderers import JSONRenderer
>>> json_str = JSONRenderer().render(serializer.data)
>>> json_str
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":"2024-04-04T05:56:50Z"}'

Deserialize

  • JSON 가져오기
    • json.loads()는 JSON 형식의 데이터를 딕셔너리 형태로 Deserialize한다
>>> import json
>>> data = json.loads(json_str)
>>> data
{'id': 1, 'question_text': '휴가를 어디서 보내고 싶으세요?', 'pub_date': '2024-04-04T05:56:50Z'}
  • Question 모델 객체로 변환하기
    • is_valid(), 즉 검증 후에 save()
      - serializer.save()의 결과로 데이터베이스에 새롭게 생성된 Question 인스턴스가 저장된다

      >>> serializer = QuestionSerializer(data=data)
      >>> serializer.is_valid()
      True
      >>> serializer.validated_data
      {'question_text': '휴가를 어디서 보내고 싶으세요?'}
      >>> new_question = serializer.save()
      >>> new_question
      <Question: 제목: 휴가를 어디서 보내고 싶으세요?, 날짜: 2024-04-12 04:03:32.097166+00:00>
      >>> new_question.id
      8
      >>> Question.objects.all()
      <QuerySet [<Question: 제목: 휴가를 어디서 보내고 싶으세요?, 날짜: 2024-04-04 05:56:50+00:00>, <Question: 제목: 가장 좋아하는 디저트는?, 날짜: 2024-04-08 05:57:13+00:00>, <Question: 제목: 커피 vs 녹차, 날짜: 2024-04-08 07:16:07.468897+00:00>, <Question: 제목: abc???, 날짜: 2024-04-08 07:19:16.996914+00:00>, <Question: 제목: 휴가를 가실 계획인가요?, 날짜: 2024-04-08 08:21:04.019412+00:00>, <Question: 제목: 휴가 계획이 있나요?, 날짜: 2024-04-08 08:31:23.274211+00:00>, <Question: 제목: 잘 지내셨나요?, 날짜: 2024-04-11 14:34:16.549989+00:00>, <Question: 제목: 휴가를 어디서 보내고 싶으세요?, 날짜: 2024-04-12 04:03:32.097166+00:00>]>
      
      >>> data = {'question_text': '제목수정'}
      >>> serializer = QuestionSerializer(new_question, data=data)
      >>> serializer.is_valid()
      True
      >>> serializer.save() # update
      <Question: 제목: 제목수정[시리얼라이저에서 업데이트], 날짜: 2024-04-12 04:03:32.097166+00:00>
    • Validation이 통과하지 않는 경우
      - 필드의 조건과 맞지 않는 경우
      - valid를 통과하지 못해 validated_data는 빈 상태

      >>> 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')]}
      >>> serializer.validated_data
      {}

3. ModelSerializer

  • polls_api/serializers.py
    • MetaSerializer상속받아 보다 효율적으로 코드 작성 가능
      - Meta 클래스 내부의 model 속성을 통해 Serialize할 대상 모델을 지정 가능하다
      - Meta 클래스 내부의 fields 속성을 통해 Serialize할 대상 필드들을 리스트 형태로 나열하여 지정 가능하다
      - 기본적으로 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']
  • Django Shell
    • Meta 클래스를 상속받아 정상적으로 serializer가 작동함을 알 수 있다

      >>> from polls_api.serializers import QuestionSerializer
      >>> print(QuestionSerializer())
      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()
      True
      >>> serializer.save()
      <Question: 제목: 모델시리얼라이저로 만들어 봅니다., 날짜: 2023-02-14 19:41:081444+00:00>

4. GET

  • question에 대해 json 형태로 데이터를 제공하는 api 서버 기능 구현
    • serializer로 json 형태 데이터 활용 가능!
  • get: 정보 획득할 때
  • post: 정보를 서버에 써주세요 요청할 때

⚠️ 학습 중 발생했던 오류

  • 제대로 모든 url이 연결되고 오타도 없었으나 TemplateDoesNotExist 라는 오류가 발생했다
  • 나는 실습하면서 템플릿을 만든 적이 없어 혼란스러웠는데, 구글링해보니 rest_framework에서 기본으로 제공하는 api.html 을 찾을 수 없어서였다. 아래 블로그를 통해 해결할 수 있었다.
    Django API 기본 페이지 표출 시 오류 (TemplateDoesNotExist at /rest/question/)
  • polls_api/views.py
    • @api_view() : Django REST framework에서 함수 기반의 뷰를 생성하기 위한 데코레이터

      • 데코레이터의 ()가 비어있는 경우에는, GET 요청을 처리한다는 의미 → question_list() 에서 HTTP GET 요청에 대한 응답을 처리하도록 함
    • question_list 뷰는 HTTP 요청(request) 객체를 인자로 받는다

    • Response()는 데이터를 HTTP 응답으로 반환한다

      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
    from django.urls import path
    from .views import *
    
    urlpatterns = [
        path('question/', question_list, name='question-list')
    ]
  • mysite/urls.py
    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')),
    ]
  • 장고가 기본적으로 제공하는 템플릿

  • 전달한 json
    • 실제로 우리가 구현한 결과

      [
         {
            "id":1,
            "question_text":"휴가를 어디서 보내고 싶으세요?",
            "pub_date":"2024-04-04T05:56:50Z"
         },
         {
            "id":2,
            "question_text":"가장 좋아하는 디저트는?",
            "pub_date":"2024-04-08T05:57:13Z"
         },
         {
            "id":3,
            "question_text":"커피 vs 녹차",
            "pub_date":"2024-04-08T07:16:07.468897Z"
         },
         {
            "id":4,
            "question_text":"abc???",
            "pub_date":"2024-04-08T07:19:16.996914Z"
         },
         {
            "id":5,
            "question_text":"휴가를 가실 계획인가요?",
            "pub_date":"2024-04-08T08:21:04.019412Z"
         },
         {
            "id":6,
            "question_text":"휴가 계획이 있나요?",
            "pub_date":"2024-04-08T08:31:23.274211Z"
         },
         {
            "id":7,
            "question_text":"잘 지내셨나요?",
            "pub_date":"2024-04-11T14:34:16.549989Z"
         },
         {
            "id":8,
            "question_text":"제목수정[시리얼라이저에서 업데이트]",
            "pub_date":"2024-04-12T04:03:32.097166Z"
         },
         {
            "id":9,
            "question_text":"모델시리얼라이저로",
            "pub_date":"2024-04-12T04:17:30.762598Z"
         }
      ]

5. HTTP Methods

🔗 HTTP 요청 메서드
https://developer.mozilla.org/ko/docs/Web/HTTP/Methods

  • HTTP는 요청 메서드를 정의하여, 주어진 리소스에 수행하길 원하는 행동을 나타낸다
    • 리소스: polls에서 Question에 대해 Choice를 vote하는 것같은 기능

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

  • Create → POST : 특정 리소스의 엔티티를 제출할 때
  • Read → GET :특정 리소스의 표시를 요청. 데이터를 받도록
  • Update → PUT : 목적 리소스의 모든 표시를 요청한 것으로 바꿀 때
  • Delete → Delete : 특정 리소스를 삭제할 때

HTTP Status Code

  • 200번대: 정상적인 결과
    • 200 OK
    • 201 CREATED
  • 400번대: 사용자의 잘못된 요청
    • 400 BAD REQUEST
    • 404 NOTFOUND
  • 500번대: 서버 내부의 오류

6. POST

  • 직접 서버에 POST 해보기

  • polls_api/views.py

    • is_valid(): serializer가 올바른 데이터 형식을 가지고 있는지 확인, 필수 필드가 존재하는지 여부를 검증
    • save(): 데이터베이스에 새로운 질문 객체가 저장
    • serializer가 유효하지 않은 경우, 에러메시지와 함께 HTTP 응답이 반환
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

# 괄호에 뭘 넣지 않아도 question_list 가 요청을 처리할 것이다
@api_view(['GET', 'POST'])
def question_list(request):
    if request.method == 'GET': # 읽기
        questions = Question.objects.all()
        # 여러 개 전달 (many = True)
        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)
  • 잘된 경우 → Status code: 201 Created
  • 너무 길게 보내 조건(max_length)을 어기는 경우 → Status code: 400 Bad Request
  • 빈 값을 보낼 경우 → Status code: 400 Bad Request

7. PUT / DELETE

  • 하나의 question에 대해 put, delete 처리 해보기

  • views.py

    • Question detail 페이지 구현

    • GET, PUT, DELETE를 처리할 수 있다

    • 각 요청이 유효한 경우 200대 응답을 반환, 유효하지 않은 경우 400대 응답을 반환

      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)
      

  • polls_api/urls.py

    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')
    ]

8. Class 기반의 뷰(Views)

  • 지금까지는 메소드를 선언한, 메소드 기반 구현

APIView

  • polls_api/views.py
    • APIView를 상속 받는다

    • if문이 아닌 메서드로 HTTP 메서드를 구별해 처리할 수 있다

      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)
              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
    • Django의 url 패턴과 연결하기 위해 .as_view()를 사용해 클래스 기반 view를 함수 기반 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'),
      ]
  • Question List, Question Detail에서 잘 작동하는 것 확인
    • DELETE

Mixin

  • 점점 더 간단한 코드로 지금과 같은 기능을 구현할 수 있다

  • polls_api/views.py

    • mixins 의 클래스를 상속받음으로써 보다 간단하게 구현할 수 있다

      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)
      
  • polls_api/urls.py
    from django.urls import path
    from . import views
    
    urlpatterns = [
        path('question/', views.QuestionList.as_view(), name = 'question-list'),
        path('question/<int:pk>/', views.QuestionDetail.as_view(), name = 'question-detail'),
    ]

Generic API View

  • generics 클래스 안에 이미 구현이 되어 있다
    • 이 메서드를 상속받아 그대로 활용하면 더욱 심플한 코드 작성 가능

  • polls_api/views.py
    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
profile
가보자고! 🔥

0개의 댓글