- Serializer : 모델 인스턴스나 QuerySet과 같은 데이터를 JSON 형식의 파일로 변환하는 작업입니다.
Model -> JSON
- Descrializer : Serializer의 반대 개념으로, JSON 데이터를 정해진 포맷에 맞춰 Model로 변환하는 작업입니다.
JSON -> Model
Serializer를 코드로 구현하는 방법은 다음과 같습니다. 이전의 프로젝트 환경에서,
python manage.py startapp polls_api
명령어를 입력하여 기능을 정의하기 위한 api 앱을 만듭니다.# Django의 REST 프레임워크에서 시리얼라이저 임포트.
from rest_framework import serializers
from polls.models import Question
class QuestionSerializer(serializers.Serializer):
# 필드의 특성에 따라 read_only와 max_length 등의 옵션 입력.
id = serializers.IntegerField(read_only=True)
question_text = serializers.CharField(max_length=200)
pub_date = serializers.DateTimeField(read_only=True)
# DB에 새로운 객체 생성
def create(self, validated_data):
# validated_data를 기반으로한 새로운 Question 생성
return Question.objects.create(**validated_data)
# 해당 객체의 question_text를 수정
def update(self, instance, validated_data):
# .get(필드명, 해당 필드명이 존재하지 않을 시의 대체재)
instance.question_text = validated_data.get('question_text', instance.question_text)
instance.save()
return instance
만약 rest_framework가 없다면, pip install djangorestframework
명령어로 설치를 해 줘야 합니다.
위의 serializer 코드를 가지고 Django shell에서 코드를 실행해 봅니다.
# Django의 REST 프레임워크에서 시리얼라이저 임포트.
from rest_framework import serializers
from polls.models import Question
class QuestionSerializer(serializers.Serializer):
# 필드의 특성에 따라 read_only와 max_length 등의 옵션 입력.
id = serializers.IntegerField(read_only=True)
question_text = serializers.CharField(max_length=200)
pub_date = serializers.DateTimeField(read_only=True)
# DB에 새로운 객체 생성.
def create(self, validated_data):
# validated_data를 기반으로한 새로운 Question 생성
return Question.objects.create(**validated_data)
# 해당 객체의 question_text를 수정
def update(self, instance, validated_data):
# .get(필드명, 해당 필드명이 존재하지 않을 시의 대체재)
instance.question_text = validated_data.get('question_text', instance.question_text) + '[시리얼라이저에서 업데이트]'
instance.save()
return instance
# 모델을 임포트합니다.
>>> from polls.models import *
# Question 모델 q를 지정합니다.
>>> q = Question.objects.first()
>>> q
<Question: 제목: 휴가를 보내고 싶은 장소는?, 날짜: 2023-10-30 06:32:04+00:00>
# 시리얼라이저를 임포트합니다.
>>> from polls_api.serializers import *
# Question 모델 q에 대한 시리얼라이저를 생성합니다.
>>> serializer = QuestionSerializer(q)
>>> serializer.data
{'id': 1, 'question_text': '휴가를 보내고 싶은 장소는?', 'pub_date': '2023-10-30T06:32:04Z'}
# JSON파일로 변환하기 위해 rest_framework.renderers에서 JSONRenderer을 임포트합니다.
>>> from rest_framework.renderers import JSONRenderer
# serializer의 data를 JSON으로 렌더링합니다.
>>> json_str = JSONRenderer().render(serializer.data)
# JSON 형식의 문자열을 확인할 수가 있습니다.
>>> json_str
b'{"id":1,"question_text":"\xed\x9c\xb4\xea\xb0\x80\xeb\xa5\xbc \xeb\xb3\xb4\xeb\x82\xb4\xea\xb3\xa0 \xec\x8b\xb6\xec\x9d\x80 \xec\x9e\xa5\xec\x86\x8c\xeb\x8a\x94?","pub_date":"2023-10-30T06:32:04Z"}'
# json.loads()는 JSON 형식의 데이터를 딕셔너리 형태로 Deserialize합니다.
>>> import json
>>> data = json.loads(json_str)
>>> data
{'id': 1, 'question_text': '휴가를 보내고 싶은 장소는?', 'pub_date': '2023-10-30T06:32:04Z'}
# 위의 JSON 데이터를 시리얼라이저하는 시리얼라이저 객체를 생성합니다.
>>> serializer = QuestionSerializer(data=data)
# 해당 데이터가 valid한지 검사합니다.
>>> serializer.is_valid()
True
# 시리얼라이저의 validated_data를 확인합니다.
>>> serializer.validated_data
OrderedDict([('question_text', '휴가를 보내고 싶은 장소는?')])
# 시리얼라이저의 save 메서드를 호출합니다.
# 해당 시리얼라이저의 경우, 모델 인스턴스 없이 데이터만 존재하므로
# save() 메서드를 호출하면 create() 메서드가 호출될 것입니다.
>>> new_question = serializer.save()
>>> new_question
<Question: New!!! 제목: 휴가를 보내고 싶은 장소는?, 날짜: 2023-11-01 04:34:09.530715+00:00>
>>> new_question.id
10
# Question 모델 객체들을 보면, 마지막에 new_question 객체가 추가된 것을 확인할 수가 있습니다.
>>> Question.objects.all()
< ... , <Question: New!!! 제목: 휴가를 보내고 싶은 장소는?, 날짜: 2023-11-01 04:34:09.530715+00:00>]>
# 인스턴스와 데이터가 주어진 시리얼라이저를 확인해 봅니다.
>>> data = {'question_text' : '제목 수정'}
>>> serializer = QuestionSerializer(new_question, data=data)
# **주의 사항**
# validated_data 및 save()를 호출하기 전에 is_valid()를 호출해야 합니다.
>>> serializer.validated_data
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "C:\Users\lb948\Desktop\Programmers\django\lib\site-packages\rest_framework\serializers.py", line 271, in validated_data
raise AssertionError(msg)
AssertionError: You must call `.is_valid()` before accessing `.validated_data`.
>>> serializer.is_valid()
True
# 인스턴스와 데이터가 주어진 시리얼라이저의 save()를 호출합니다.
# 해당 시리얼라이저의 경우, 인스턴스가 주어졌기에 update 메서드가 호출될 것입니다.
>>> serializer.save()
<Question: New!!! 제목: 제목 수정[시리얼라이저에서 업데이트], 날짜: 2023-11-01 04:34:09.530715+00:00>
# not valid한 데이터에 대해서도 테스트해 봅니다.
>>> long_text = 'abcd'*300
>>> data = {'question_text' : long_text}
>>> serializer = QuestionSerializer(data=data)
>>> serializer.is_valid()
False
# 에러에 대한 로그를 확인합니다.
# question_text 필드의 길이가 200이 넘어가서 에러가 발생한 것을 확인할 수가 있습니다.
>>> serializer.errors
{'question_text': [ErrorDetail(string='Ensure this field has no more than 200 characters.', code='max_length')]}
# valid하지 않기 때문에 validated_data에는 아무 것도 들어 있지 않습니다.
>>> serializer.validated_data
{}
위의 serializer처럼 구현해도 되지만, ModelSerializer를 활용하면 더욱더 간편하게 구현할 수가 있습니다.
# Django의 REST 프레임워크에서 시리얼라이저 임포트.
from rest_framework import serializers
from polls.models import Question
# ModelSerializer를 상속받는 경우, 필드 및 메서드가 자동으로 지정됨.
class QuestionSerializer(serializers.ModelSerializer):
class Meta:
model = Question
fields = ['id', 'question_text', 'pub_date']
>>> from polls_api.serializers import *
# 시리얼라이저를 출력해 보니, 필드들에 대한 옵션이 자동으로 지정된 것을 확인할 수가 있습니다.
>>> print(QuestionSerializer())
QuestionSerializer():
id = IntegerField(read_only=True)
question_text = CharField(max_length=200)
pub_date = DateTimeField(read_only=True)
# 새로운 시리얼라이저를 생성하고, save()를 호출해 봅니다.
>>> serializer = QuestionSerializer(data={'question_text' : '모델 시리얼라이저로 만들어 봅니다.'})
>>> serializer.is_valid()
True
# create 메서드를 지정하지 않아도 자동으로 create된 것을 확인할 수가 있습니다.
>>> serializer.save()
<Question: New!!! 제목: 모델 시리얼라이저로 만들어 봅니다., 날짜: 2023-11-01 05:01:04.897276+00:00>
Http Methods에 대한 정보를 다음 웹 사이트에서 확인할 수가 있습니다.
https://developer.mozilla.org/ko/docs/Web/HTTP/Methods
장고에서 GET 메서드에 대한 코드는 다음과 같습니다.
from polls.models import Question
from polls_api.serializers import QuestionSerializer
from rest_framework.response import Response
from rest_framework.decorators import api_view
# Create your views here.
# # api_view 데코레이터의 ()가 비어있는 경우, question_list가 get 요청을 처리한다는 의미.
@api_view()
def question_list(request):
questions = Question.objects.all()
serializer = QuestionSerializer(questions, many = True) # many옵션으로 여러 개 등록 가능.
return Response(serializer.data)
from django.urls import path
from .views import *
urlpatterns = [
path('question/', question_list, name='question-list'),
]
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('polls/', include('polls.urls')),
path('rest/', include('polls_api.urls')),
]
...
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'polls.apps.PollsConfig',
# 추가된 앱
'rest_framework',
]
추가 후 이미지)
먼저 Status Code에 대해서 작성하겠습니다.
다음으로는 POST 메서드를 구현한 코드입니다.
from polls.models import Question
from polls_api.serializers import QuestionSerializer
from rest_framework.response import Response
from rest_framework.decorators import api_view
from rest_framework import status
# Create your views here.
# GET과 POST에 대해서 api_view 데코레이터 작성.
@api_view(['GET', 'POST'])
def question_list(request):
if request.method == 'GET':
questions = Question.objects.all()
serializer = QuestionSerializer(questions, many = True) # many옵션으로 여러 개 등록 가능.
return Response(serializer.data)
if request.method == 'POST':
serializer = QuestionSerializer(data=request.data)
if serializer.is_valid():
serializer.save() # 인스턴스가 주어지지 않으므로 create() 메서드가 호출됨.
# 잘된 응답에 대해서 status 201 code 리턴.
# status code가 200번대: OK
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
# 잘못된 응답에 대해서 status 400 code 리턴.
# status code가 400번대: 사용자의 잘못된 요청
# 500번대: 서버 내부의 오류
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
http://127.0.0.1:8000/rest/question/ 에서 OPTIONS를 클릭하여 데이터를 등록(POST)합니다.
세 가지의 경우에 대해서 테스트를 하였습니다.
위와 유사한 방식으로 PUT과 DELETE 메서드를 구현할 수 있습니다. 절차는 다음과 같습니다.
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.decorators import api_view
from rest_framework import status
# Create your views here.
# GET과 POST에 대해서 api_view 데코레이터 작성.
@api_view(['GET', 'POST'])
def question_list(request):
if request.method == 'GET':
questions = Question.objects.all()
serializer = QuestionSerializer(questions, many = True) # many옵션으로 여러 개 등록 가능.
return Response(serializer.data)
if request.method == 'POST':
serializer = QuestionSerializer(data=request.data)
if serializer.is_valid():
serializer.save() # 인스턴스가 주어지지 않으므로 create() 메서드가 호출됨.
# 잘된 응답에 대해서 status 201 code 리턴.
# status code가 200번대: OK
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
# 잘못된 응답에 대해서 status 400 code 리턴.
# status code가 400번대: 사용자의 잘못된 요청
# 500번대: 서버 내부의 오류
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# 하나의 Question에 대한 뷰
@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)
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'),
]
위와 같이 데코레이터를 사용하여 뷰를 지정할 수도 있지만, Class로 지정할 수도 있습니다. Class로 지정할 경우, 코드가 더욱 간편해진다는 장점이 있습니다. 코드는 다음과 같습니다.
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.decorators import api_view
from rest_framework import status
from rest_framework.views import APIView
# APIView를 상속받는 class 뷰 생성
class QuestionList(APIView):
def get(self, request):
questions = Question.objects.all()
serializer = QuestionSerializer(questions, many = True) # many옵션으로 여러 개 등록 가능.
return Response(serializer.data)
def post(self, request):
serializer = QuestionSerializer(data=request.data)
if serializer.is_valid():
serializer.save() # 인스턴스가 주어지지 않으므로 create() 메서드가 호출됨.
# 잘된 응답에 대해서 status 201 code 리턴.
# status code가 200번대: OK
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
# 잘못된 응답에 대해서 status 400 code 리턴.
# status code가 400번대: 사용자의 잘못된 요청
# 500번대: 서버 내부의 오류
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)
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'),
]
장고 프레임워크에서 제공하는 클래스로 편리한 기능을 가지고 있습니다.
위에서 보았던 클래스 뷰를 더욱더 간단하게 작성할 수가 있습니다. 코드는 다음과 같습니다.
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.decorators import api_view
from rest_framework import status, mixins, generics
from rest_framework.views import APIView
# Mixin과 Generic 뷰를 상속받는 class 뷰 생성
# 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):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
# Mixin과 Generic 뷰를 상속받는 class 뷰 생성
# RetrieveModelMixin : GET / UpdateModelMixin : UPDATE /
# DestroyModelMixin : DELETE
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)
from django.urls import path
from .views import *
urlpatterns = [
path('question/', QuestionList.as_view, name='question-list'),
# generics 뷰는 pk를 받아서 queryset을 구성함.
path('question/<int:pk>/', QuestionDetail.as_view, name='question_detail'),
]
위의 코드를 더욱더 간단하게 표현할 수가 있습니다.
이는 generics 클래스가 위의 기능을 이미 구현해 놓았기 때문에 가능한 것입니다.
코드는 다음과 같습니다.
from polls.models import Question
from polls_api.serializers import QuestionSerializer
from rest_framework import generics
# generics 클래스의 ListCreateAPIView 상속
# get, post 등의 기능이 구현되어 있음
class QuestionList(generics.ListCreateAPIView):
queryset = Question.objects.all()
serializer_class = QuestionSerializer
# generics 클래스의 RetrieveUpdateDestroyAPIView 상속
# get, put, delete 등의 기능이 구현되어 있음.
class QuestionDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Question.objects.all()
serializer_class = QuestionSerializer
매우 간편해진 것을 확인할 수가 있습니다.