

일반적으로 API 서버에서는 JSON 형태로 데이터를 주고 받는다
RESTful API에서 데이터를 전송할 때 자주 사용된다.

$ python [manage.py](http://manage.py/) startapp polls_api
polls_api/serializers.pyrest 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
polls_api/serializers.pyfrom 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
>>> 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'}
>>> 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"}'
>>> import json
>>> data = json.loads(json_str)
>>> data
{'id': 1, 'question_text': '휴가를 어디서 보내고 싶으세요?', 'pub_date': '2024-04-04T05:56:50Z'}
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
{}
polls_api/serializers.pyMetaSerializer 를 상속받아 보다 효율적으로 코드 작성 가능
- 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']
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>
⚠️ 학습 중 발생했던 오류
- 제대로 모든 url이 연결되고 오타도 없었으나
TemplateDoesNotExist라는 오류가 발생했다- 나는 실습하면서 템플릿을 만든 적이 없어 혼란스러웠는데, 구글링해보니 rest_framework에서 기본으로 제공하는 api.html 을 찾을 수 없어서였다. 아래 블로그를 통해 해결할 수 있었다.
Django API 기본 페이지 표출 시 오류 (TemplateDoesNotExist at /rest/question/)
polls_api/views.py@api_view() : Django REST framework에서 함수 기반의 뷰를 생성하기 위한 데코레이터
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.pyfrom django.urls import path
from .views import *
urlpatterns = [
path('question/', question_list, name='question-list')
]mysite/urls.pyfrom 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')),
]
실제로 우리가 구현한 결과
[
{
"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"
}
]
🔗 HTTP 요청 메서드
https://developer.mozilla.org/ko/docs/Web/HTTP/Methods
POST : 특정 리소스의 엔티티를 제출할 때GET :특정 리소스의 표시를 요청. 데이터를 받도록PUT : 목적 리소스의 모든 표시를 요청한 것으로 바꿀 때Delete : 특정 리소스를 삭제할 때200 OK201 CREATED400 BAD REQUEST404 NOTFOUND직접 서버에 POST 해보기
polls_api/views.py
is_valid(): serializer가 올바른 데이터 형식을 가지고 있는지 확인, 필수 필드가 존재하는지 여부를 검증save(): 데이터베이스에 새로운 질문 객체가 저장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)
201 Created 
400 Bad Request 
400 Bad Request 
하나의 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')
]
polls_api/views.pyAPIView를 상속 받는다
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.pyDjango의 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'),
]

DELETE


점점 더 간단한 코드로 지금과 같은 기능을 구현할 수 있다
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.pyfrom 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'),
]generics 클래스 안에 이미 구현이 되어 있다이 메서드를 상속받아 그대로 활용하면 더욱 심플한 코드 작성 가능

polls_api/views.pyfrom 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