Skip to main content

REST API

목차
  • RESTful API
  • HTTP
  • Response
  • Single Model
  • 1:N Relation

HTTP

  • HyperText Transfer Protocol
  • 웹 상에서 컨텐츠를 전송하기 위한 약속
  • HTML 문서와 같은 리소스들을 가져올 수 있도록 하는 프로토콜 (규칙, 약속)
  • 웹에서 이루어지는 모든 데이터 교환의 기초
    • 요청 (request)
      • 클라이언트에 의해 전송되는 메세지
    • 응답 (response)
      • 서버에서 응답으로 전송되는 메세지

  • 기본 특성
    • Stateless
    • Connectless
  • 쿠키와 세션을 통해 서버 상태를 요청과 연결하도록 함

HTTP 메세지

  • 요청 예시

image-20211227174912939

  • 응답 예시

image-20211227174934892


HTTP request methods

  • 자원에 대한 행위 (수행하고자 하는 동작)을 정의
  • 주어진 리소스(자원)에 수행하길 원하는 행동을 나타냄
  • HTTP Method 예시
    • GET, POST, PUT, DELETE

HTTP response status codes

  • 특정 HTTP 요청이 성공적으로 완료되었는지 여부를 나타냄
  • 응답은 5개의 그룹으로 나뉘어짐
    1. Informational responses (1xx)
    2. Successful responses (2xx)
    3. Redirection messages (3xx)
    4. Client error responses (4xx)
    5. Server error responses (5xx)

웹에서의 리소스 식별

  • HTTP 요청의 대상을 리소스 (resource, 자원)라고 함
  • 리소스는 문서, 사진 또는 기타 어떤 것이든 될 수 있음
  • 각 리소스는 리소스 식별을 위해 HTTP 전체에서 사용되는 URI (Uniform Resource Identifier)로 식별됨.

URL, URN

  • URL (Uniform Resource Locator)
    • 통합 자원 위치
    • 네트워크 상에 자원이 어디 있는지 알려주기 위한 약속
    • 과거에는 실제 자원의 위치를 나타냈지만 현재는 추상화된 의미론적인 구성
    • '웹 주소', '링크'라고도 불림

  • URN (Uniform Resource Name)
    • 통합 자원 이름
    • URL과 달리 자원의 위치에 영향을 받지 않는 유일한 이름 역할을 함
    • 예시
      • ISBN (국제표준도서번호)

URI

  • URI (Uniform Resource Identifier)
    • 통합 자원 식별자
    • 인터넷의 자원을 식별하는 유일한 주소 (정보의 자원을 표현)
    • 인터넷에서 자원을 식별하거나 이름을 지정하는데 사용되는 간단한 문자열
    • 하위 개념
      • URL, URN

  • URI는 크게 URL과 URN으로 나눌 수 있지만, URN을 사용하는 비중이 매우 적기 떄문에 일반적으로 URL은 URI와 같은 의미처럼 사용하기도 함.

URI의 구조

image-20211227174943335


  • Scheme (protocol)

    • 브라우저가 사용해야 하는 프로토콜
    • http(s), data, file, ftp, malito

  • Host (Domain name)

    • 요청을 받는 웹 서버의 이름
    • IP address를 직접 사용할 수도 있지만, 실 사용시 불편하므로 웹에서 그리 자주 사용되지는 않음. (google의 IP address - 142.251.42.142)

  • Port

    • 웹 서버 상의 리소스에 접근하는데 사용되는 기술적인 '문(gate)'
    • HTTP 프로토콜의 표준 포트
      • HTTP 80
      • HTTPS 443

  • Path

    • 웹 서버 상의 리소스 경로
    • 초기에는 실제 파일이 위치한 물리적 위치를 나타냈지만, 오늘날은 물리적인 실제 위치가 아닌 추상화 형태의 구조로 표현

  • Query (Identifier)

    • Query String Parameters
    • 웹 서버에 제공되는 추가적인 매개 변수
    • &로 구분되는 key-value 목록

  • Fragment

    • Anchor
    • 자원 안에서의 북마크의 한 종류를 나타냄
    • 브라우저에게 해당 문서 (HTML)의 특정 부분을 보여주기 위한 방법
    • 브라우저에게 알려주는 요소이기 때문에 fragment identifier(부분 식별자)라고 부르며 '#' 뒤의 부분은 요청이 서버에 보내지지 않음.

REST API

API

  • Application Programming Interface
  • 프로그래밍 언어가 제공하는 기능을 수행할 수 있게 만든 인터페이스
    • 애플리케이션과 프로그래밍으로 소통하는 방법
    • CLI는 명령줄, GUI는 그래픽(아이콘), API는 프로그래밍을 통해 특정한 기능 수행
  • Web API
    • 웹 애플리케이션 개발에서 다른 서비스에 요청을 보내고 응답을 받기 위해 정의된 명세
    • 현재 웹 개발은 모든 것을 직접 개발하기보다 여러 Open API를 활용하는 추세
  • 응답 데이터 타입
    • HTML, XML, JSON 등

  • 대표적인 API 서비스 목록
    • Youtube API, Naver Papage API, Kakao Map Api, ...

REST

  • REpresentational State Transfer
  • API Server를 개발하기 위한 일종의 소프트웨어 설계 방법론
    • 2000년 로이 필딩의 박사학위 논문에서 처음으로 소개된 후 네트워킹 문화에 널리 퍼짐
  • 네트워크 구조 (Netword Architecture) 원리의 모음
    • 자원을 정의하고 자원에 대한 주소를 지정하는 전반적인 방법
  • REST 원리를 따르는 시스템을 RESTful 이란 용어로 지칭함.
  • 자원을 정의하는 방법에 대한 고민
    • ex) 정의된 자원을 어디에 위치시킬 것인가

  • REST의 자원과 주소의 지정 방법
    1. 자원
      • URI
    2. 행위
      • HTTP Method
    3. 표현
      • 자원과 행위를 통해 궁극적으로 표현되는 (추상화된) 결과물
      • JSON으로 표현된 데이터를 제공

JSON
  • JSON (JavaScript Object Notation)
    • JSON is a lightweight data-interchange format
    • JavaScript의 표기법을 따른 단순 문자열

  • 특징
    • 사람이 읽거나 쓰기 쉽고 기계가 파싱(해석, 분석)하고 만들어내기 쉬움
    • 파이썬의 dictionary, 자바스크립트의 object처럼 C계열의 언어가 갖고있는 자료구조로 쉽게 변화할 수 있는 key-value 형태의 구조를 갖고 있음

  • REST의 핵심 규칙
    1. '정보'는 URI로 표현
    2. 자원에 대한 '행위'는 HTTP Method로 표현 (GET, POST, PUT, DELETE)

  • 설계 방법론은 지키지 않았을 때 잃는 것보다 지켰을 때 얻는 것이 훨씬 많음
    • 단, 설계 방법론을 지키지 않더라도 동작 여부에 큰 영향을 미치지는 않음

RESTful API

  • REST 원리를 따라 설계한 API
  • RESTfulr services, 혹은 simply REST services라고도 부름
  • 프로그래밍을 통해 클라이언트의 요청에 JSON을 응답하는 서버를 구성
    • 지금까지 사용자의 입장에서 썼던 API를 제공자의 입장이 되어 개발해보기

Response

Init Project

  • 제공된 00_json_response 프로젝트로 진행

# settings.py

INSTALLED_APPS = [
'articles',
'django_seed',
...,
]
# my_api/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/', include('articles.urls')),
]
# articles/urls.py

from django.urls import path
from . import views


urlpatterns = [
path('html/', views.article_html),
path('json-1/', views.article_json_1),
path('json-2/', views.article_json_2),
path('json-3/', views.article_json_3),
]
# articles/models.py

from django.db import models

# Create your models here.
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

Create Dummy Data

  • django-seed 라이브러리를 사용해 모델 구조에 맞는 데이터 생성
$ python manage.py migrate
$ python manage.py seed articles --number=20

Response - HTML

  • HTML을 응답하는 서버
# articles/urls.py

urlpatterns = [
path('html/', views.article_html),
...
]
# articles/views.py

from django.shortcuts import render
from .models import Article


def article_html(request):
articles = Article.objects.all()
context = {
'articles': articles,
}
return render(request, 'articles/article.html', context)

image


Response - JsonResponse

  • JsonResponse 객체를 활용한 JSON 데이터 응답
# articles/urls.py

urlpatterns = [
...,
path('json-1/', views.article_json_1),
...,
]
# articles/views.py

def article_json_1(request):
articles = Article.objects.all()
articles_json = []

for article in articles:
articles_json.append(
{
'id': article.pk,
'title': article.title,
'content': article.content,
'created_at': article.created_at,
'updated_at': article.updated_at,
}
)
return JsonResponse(articles_json, safe=False)

image-20211027231615238


  • Content-Type entity header
    • 데이터의 media type(MIME type, content type)을 나타내기 위해 사용됨
    • 응답 내에 있는 컨텐츠의 컨텐츠 유형이 실제로 무엇인지 클라이언트에게 알려줌

  • JsonResponse objects
    • JSON-encoded response를 만드는 HttpResponse의 서브 클래스
    • "safe" parameter
      • True (기본값)
      • dict 이외의 객체를 직렬화 (Serialization) 하려면 False로 설정해야 함
# JsonResponse 예시

response = JsonResponse({'foo': 'bar'})
response = JsonResponse([1, 2, 3], safe=False)

Serialization

  • "직렬화"

  • 데이터 구조나 객체 상태를 동일하거나 다른 컴퓨터 환경에 저장하고, 나중에 재구성할 수 있는 포맷으로 변환하는 과정

  • Serializers in Django

    • Queryset 및 Model Instance와 같은 복잡한 데이터를 JSON, XML 등의 유형으로 쉽게 변환할 수 있는 Python 데이터 타입으로 만들어 줌

Response - Django Serializer

  • Django의 내장 HttpResponse를 활용한 JSON 응답 객체
  • 주어진 모델 정보를 활용하기 떄문에 이전과 달리 필드를 개별적으로 직접 만들어 줄 필요 없음
# articles/urls.py

urlpatterns = [
...,
path('json-2/', views.article_json_2),
...,
]
# articles/views.py

from django.http.response import JsonResponse, HttpResponse
from django.core import serializers

def article_json_2(request):
articles = Article.objects.all()
data = serializers.serialize('json', articles)
return HttpResponse(data, content_type='application/json')

image-20211027232216324


Response - Django REST Framework

  • Django REST framework(DRF) 라이브러리를 사용한 JSON 응답
  • 설치 과정
$ pip install djangorestframework
# settings.py

INSTALLED_APPS = [
...,
'rest_framework',
...,
]

  • DRF 라이브러리를 사용한 JSON 응답 url 확인
# articles/urls.py

urlpatterns = [
...,
path('json-3/', views.article_json_3),
]

  • Article 모델에 맞춰 자동으로 필드를 생성해 serialize 해주는 Model Serializer 확인
# articles/serializers.py

from rest_framework import serializers
from .models import Article


class ArticleSerializer(serializers.ModelSerializer):

class Meta:
model = Article
fields = '__all__'

  • DRF의 Response()를 활용해 Serialize 된 JSON 객체 응답 (DRF를 이용한 view 함수 작성은 다음 챕터에서 진행)
# articles/views.py

from rest_framework.decorators import api_view
from rest_framework.response import Response
from .serializers import ArticleSerializer


# @api_view(['GET'])
@api_view()
def article_json_3(request):
articles = Article.objects.all()
serializer = ArticleSerializer(articles, many=True)
return Response(serializer.data)

image-20211027232657837


  • python 파일을 통해 직접 요청 보낸 후 응답 확인해보기 (requests 라이브러리를 활용)
import requests
from pprint import pprint

response = requests.get('http://127.0.0.1:8000/api/v1/json-3/')
pprint(response.json())
[{'content': 'Whole skin structure hand response home. Check wide play book '
'remember enough.\n'
'Who behind half give low. Low story garden rest anyone why. '
'Everybody fact just drop poor key receive.',
'created_at': '2009-05-02T10:51:02Z',
'id': 1,
'title': 'Father hope west especially let.',
'updated_at': '2016-11-06T17:06:39Z'},
....
]
[27/Oct/2021 23:30:43] "GET /api/v1/json-3/ HTTP/1.1" 200 5922

  • Web API 구축을 위한 강력한 Toolkit을 제공하는 라이브러리
  • DRF의 Serializer는 Django의 Form 및 ModelForm 클래스와 매우 유사하게 구성되고 작동함
  • Web API
    • 웹 애플리케이션 개발에서 다른 서비스에 요청을 보내고 응답을 받기 위해 정의된 명세

Django ModelForm vs. DRF Serializers

DjangoDRF
ResponseHTMLJSON
ModelModelFormserializers

Single Model

DRF with Single Model

  • 단일 모델의 data를 직렬화(serialization)하여 JSON으로 변환하는 방법에 대한 학습
  • 단일 모델을 두고 CRUD 로직을 수행 가능하도록 설계
  • API 개발을 위한 핵심 기능을 제공하는 도구 활용
    • DRF built-in form
    • Postman

[참고] Postman
  • API를 구축하고 사용하기 위해 여러 도구를 제공하는 API 플랫폼
  • 설계, 테스트, 문서화 등의 도구를 제공함으로써 API를 더 빠르게 개발 및 생성할 수 있도록 도움

Init Project

# settings.py

INSTALLED_APPS = [
'articles',
'django_seed',
'django_extensions',
'rest_framework',
...
]
# my_api/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/', include('articles.urls')),
]
# articles/urls.py

from django.urls import path
from . import views


urlpatterns = [
]
# models.py

from django.db import models


class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

Create Dummy Data

  • django-seed 라이브러리를 사용해 모델 구조에 맞는 데이터 생성
$ python manage.py migrate
$ python manage.py seed articles --number=20

ModelSerializer

  • 모델 필드에 해당하는 필드가 있는 Serializer 클래스를 자동으로 만들 수 있는 shortcut
  • 아래 핵심 기능을 제공
    1. 모델 정보에 맞춰 자동으로 필드 생성
    2. serializer에 대한 유효성 검사기를 자동으로 생성
    3. .create() & .update()의 간단한 기본 구현이 포함됨

  • Model의 필드를 어떻게 '직렬화'할지 설정하는 것이 핵심
  • 이 과정은 Django에서 Model의 필드를 설정하는 것과 동일함
# articles/serializers.py

from rest_framework import serializers
from .models import Article, Comment


class ArticleListSerializer(serializers.ModelSerializer):

class Meta:
model = Article
fields = ('id', 'title',)

Serializer in Shell

  • shell_plus에서 serializer 사용해보기
#1. 작성한 Serializer import
>>> from articles.serializers import ArticleListSerializer


#2. 기본 인스턴스 구조 확인
>>> serializer = ArticleListSerializer()
>>> serializer
ArticleListSerializer():
id = IntegerField(label='ID', read_only=True)
title = CharField(max_length=100)


#3. Model instance 객체
>>> article = Article.objects.get(pk=1)
>>> article
<Article: Article object (1)>

>>> serializer = ArticleListSerializer(article)
>>> serializer
ArticleListSerializer(<Article: Article object (1)>):
id = IntegerField(label='ID', read_only=True)
title = CharField(max_length=100)

>>> serializer.data
{'id': 1, 'title': 'Site economic if two country science.'}


#4. QuerySet 객체
>>> articles = Article.objects.all()


#5-1. many=True 옵션 X
>>> serializer = ArticleListSerializer(articles)
>>> serializer.data
---------------------------------------------------------------------------
AttributeError: Got AttributeError when attempting to get a value for field `title` on serializer `ArticleListSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `QuerySet` instance.
Original exception text was: 'QuerySet' object has no attribute 'title'.


#5-2. many=True 옵션 O
>>> serializer = ArticleListSerializer(articles, many=True)
>>> serializer.data
[OrderedDict([('id', 1), ('title', 'Site economic if two country science.'), ('content', 'Small')])...

many argument

  • many=True
    • Serializing multiple objects
    • 단일 인스턴스 대신 QuerySet 등을 직렬화하기 위해서는 serializer를 인스턴스화 할 때, many=True를 키워드 인자로 전달해야 함
@api_view()
def article_json_3(request):
articles = Article.objects.all()
serializer = ArticleSerializer(articles, many=True)
return Response(serializer.data)

Build RESTful API

GETPOSTPUTDELETE
articles/전체 글 조회글 작성
articles/1/1번 글 조회1번 글 수정1번 글 삭제

1. GET - Article List

  • url 및 view 함수 작성
# articles/urls.py

urlpatterns = [
path('articles/', views.article_list),
]
# articles/views.py

from rest_framework.response import Response
from rest_framework.decorators import api_view

from django.shortcuts import render, get_list_or_404

from .serializers import ArticleListSerializer
from .models import Article


@api_view(['GET'])
def article_list(request):
articles = get_list_or_404(Article)
serializer = ArticleListSerializer(articles, many=True)
return Response(serializer.data)

  • api_view decorator

    • 기본적으로 GET 메서드만 허용되며 다른 메서드 요청에 대해서는 405 Method Not Allowed로 응답
    • View 함수가 응답해야 하는 HTTP 메서드의 목록을 리스트의 인자로 받음
    • DRF에서는 선택이 아닌 필수적으로 작성해야 해당 view 함수가 정상적으로 동작함.
  • http://127.0.0.1:8000/api/v1/articles/ 로 GET 요청 후 응답 확인


2. GET - Article Detail

  • Article List와 Article Detail을 구분하기 위해 추가 Serializer 정의
  • 모든 필드를 직렬화하기 위해 fields 옵션을 __all__로 설정
# articles/serializers.py

class ArticleSerializer(serializers.ModelSerializer):

class Meta:
model = Article
fields = '__all__'
# articles/urls.py

urlpatterns = [
...
path('articles/<int:article_pk>/', views.article_detail),
]
# articles/views.py

from django.shortcuts import get_object_or_404
from .serializers import ArticleListSerializer, ArticleSerializer


@api_view(['GET'])
def article_detail(request, article_pk):
article = get_object_or_404(Article, pk=article_pk)
serializer = ArticleSerializer(article)
return Response(serializer.data)


3. POST - Create Article

  • 201 Create 상태 코드 및 메세지 응답
  • RESTful 구조에 맞게 작성
    1. URI는 자원을 표현
    2. 자원을 조작하는 행위는 HTTP Method
  • article_list 함수로 게시글을 조회하거나 생성하는 행위를 모두 처리 가능

  • view 함수 수정
# articles/views.py

from rest_framework import status


@api_view(['GET', 'POST'])
def article_list(request):
if request.method == 'GET':
articles = get_list_or_404(Article)
serializer = ArticleListSerializer(articles, many=True)
return Response(serializer.data)

elif request.method == 'POST':
serializer = ArticleSerializer(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)

Status Codes in DRF

  • DRF에는 status code를 보다 명확하고 읽기 쉽게 만드는 데 사용할 수 있는 정의된 상수 집합을 제공
  • status 모듈에 HTTP status code 집합이 모두 포함되어 있음
  • 단순히 status=201 같은 표현으로도 사용할 수 있지만 DRF는 권장하지 않음
Response(serializer.data, status=201)


raise_exception argument

  • Raising an exception on invalid data
  • is_valid()는 유효성 검사 오류가 있는 경우 serializers.ValidationError 예외를 발생시키는 선택적 raise_exception 인자를 사용할 수 있음.
  • DRF에서 제공하는 기본 예외 처리기에 자동으로 처리되며, 기본적으로 HTTP status code 400을 응답으로 반환함
# articles/views.py

@api_view(['GET', 'POST'])
def article_list(request):
if request.method == 'GET':
articles = get_list_or_404(Article)
serializer = ArticleListSerializer(articles, many=True)
return Response(serializer.data)

elif request.method == 'POST':
serializer = ArticleSerializer(data=request.data)

# Return a 400 response if the data was invalid
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
# return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

4. DELETE - Delete Article

  • 204 No Content 상태 코드 및 메세지 응답
  • article_detail 함수로 상세 게시글을 조회하거나 삭제하는 행위 모두 처리 가능
# articles/views.py

@api_view(['GET', 'DELETE'])
def article_detail(request, article_pk):

article = get_object_or_404(Article, pk=article_pk)

if request.method == 'GET':
serializer = ArticleSerializer(article)
return Response(serializer.data)

elif request.method == 'DELETE':
article.delete()
data = {
'delete': f'데이터 {article_pk}번이 삭제되었습니다.',
}
return Response(data, status=status.HTTP_204_NO_CONTENT)


5. PUT - Update Article

  • article_detail 함수로 상세 게시글을 조회하거나 삭제, 수정하는 행위 모두 처리 가능
# articles/views.py

@api_view(['GET', 'DELETE', 'PUT'])
def article_detail(request, article_pk):

article = get_object_or_404(Article, pk=article_pk)

if request.method == 'GET':
serializer = ArticleSerializer(article)
return Response(serializer.data)

elif request.method == 'DELETE':
article.delete()
data = {
'delete': f'데이터 {article_pk}번이 삭제되었습니다.',
}
return Response(data, status=status.HTTP_204_NO_CONTENT)

elif request.method == 'PUT':
serializer = ArticleSerializer(article, data=request.data)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data)


1:N Relation

DRF with 1:N Relation

  • 1:N 관계에서의 모델 data를 직렬화(serialization)하여 JSON으로 변환하는 방법에 대한 학습
  • 2개 이상의 1:N 관계를 맺는 모델을 두고 CRUD 로직을 수행 가능하도록 설계하기

  • 데이터베이스 초기화 후 Comment 모델 작성
# articles/models.py

class Comment(models.Model):
article = models.ForeignKey(Article, on_delete=models.CASCADE)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

  • migration 후 data seed 진행
$ python manage.py makemigrations
$ python manage.py migrate
$ python manage.py seed articles --number=20

1. GET - Comment List

  • CommentSerializer 작성
# articles/serializers.py

class CommentSerializer(serializers.ModelSerializer):

class Meta:
model = Comment
fields = '__all__'
# articles/urls.py

urlpatterns = [
...
path('comments/', views.comment_list),
]
# articles/views.py

from .serializers import ArticleListSerializer, ArticleSerializer, CommentSerializer
from .models import Article, Comment


@api_view(['GET'])
def comment_list(request):
comments = get_list_or_404(Comment)
serializer = CommentSerializer(comments, many=True)
return Response(serializer.data)


2. GET - Comment Detail

# articles/urls.py

urlpatterns = [
...
path('comments/<int:comment_pk>/', views.comment_detail),
]
# articles/views.py

@api_view(['GET'])
def comment_detail(request, comment_pk):
comment = get_object_or_404(Comment, pk=comment_pk)
serializer = CommentSerializer(comment)
return Response(serializer.data)


3. POST - Create Comment

# articles/urls.py

urlpatterns = [
...
path('articles/<int:article_pk>/comments/', views.comment_create),
]
# articles/views.py

@api_view(['POST'])
def comment_create(request, article_pk):
article = get_object_or_404(Article, pk=article_pk)
serializer = CommentSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)

  • http://127.0.0.1:8000/api/v1/articles/1/comments 로 POST 요청 시도

  • IntegrityError : NOT NULL constraint failed

  • Article 생성과 달리 Comment 생성은 생성 시에 참조하는 모델의 객체 정보가 필요

    • 1:N 관계에서 N은 어떤 1을 참조하는지에 대한 정보가 필요하기 때문 (외래 키)

Passing Additional attributes to .save()

  • .save() 메서드는 특정 Serializer 인스턴스를 저장하는 과정에서 추가적인 데이터를 받을 수 있음.
    • 인스턴스를 저장하는 시점에 추가 데이터 삽입이 필요한 경우
# articles/views.py

@api_view(['POST'])
def comment_create(request, article_pk):
article = get_object_or_404(Article, pk=article_pk)
serializer = CommentSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
serializer.save(article=article)
return Response(serializer.data, status=status.HTTP_201_CREATED)


Read Only Field (읽기 전용 필드)

  • 어떤 게시글에 작성하는 댓글인지에 대한 정보를 form-data로 넘겨주지 않았기 때문에 직렬화하는 과정에서 article 필드가 유효성 검사(is_valid)를 통과하지 못함.
    • CommentSerializer에서 article field에 해당하는 데이터 또한 요청으로부터 받아서 직렬화하는 것으로 설정되었기 때문
  • 이때는 읽기 전용 필드(read_only_fields) 설정을 통해 직렬화하지 않고 반환 값에만 해당 필드가 포함되도록 설정할 수 있음.
# articles/serializers.py

class CommentSerializer(serializers.ModelSerializer):

class Meta:
model = Comment
fields = '__all__'
read_only_fields = ('article',)


4. DELETE & Put - Delete, Update Comment

  • Article 생성 로직에서와 마찬가지로 Comment_detail 함수가 모두 처리할 수 있도록 작성
# articles/views.py

@api_view(['GET', 'DELETE', 'PUT'])
def comment_detail(request, comment_pk):

comment = get_object_or_404(Comment, pk=comment_pk)

if request.method == 'GET':
serializer = CommentSerializer(comment)
return Response(serializer.data)

elif request.method == 'DELETE':
comment.delete()
data = {
'message': f'댓글 {comment_pk}번이 삭제되었습니다.',
}
return Response(data, status=status.HTTP_204_NO_CONTENT)

elif request.method == 'PUT':
serializer = CommentSerializer(comment, data=request.data)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data)


1:N Serializer

  1. 특정 게시글에 작성된 댓글 목록 출력하기
    • 기존 필드 overide
  2. 특정 게시글에 작성된 댓글의 개수 구하기
    • 새로운 필드 추가

1. 특정 게시글에 작성된 댓글 목록 출력하기

  • serializer는 기존 필드를 override 하거나 추가 필드를 구성할 수 있음
  • 우리가 작성한 로직에서는 크게 2가지 형태로 구성할 수 있음
    1. PrimaryKeyRelatedField
    2. Nested relationships

(1) PrimaryKeyRelatedField
  • pk를 사용하여 관계된 대상을 나타내는 데 사용할 수 있음
  • 필드가 to many relationships(N)를 나타내는데 사용되는 경우 many=True 속성 필요
  • comment_set 필드 값을 form-data로 받지 않으므로 read_only=True설정 필요
# articles/serializers.py

class ArticleSerializer(serializers.ModelSerializer):
comment_set = serializers.PrimaryKeyRelatedField(many=True, read_only=True)

class Meta:
model = Article
fields = '__all__'

  • 역참조 시 생성되는 comment_set을 override 할 수 있음
# articles/models.py

class Comment(models.Model):
article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='comments')
...

(2) Nested relationships
  • 모델 관계상으로 참조된 대상은 참조하는 대상의 표현(응답)에 포함되거나 중첩(nested)될 수 있음
  • 이러한 중첩된 관계는 serializers를 필드로 사용하여 포현할 수 있음.
  • 두 클래스의 상하위치 변경
# articles/serializers.py

class CommentSerializer(serializers.ModelSerializer):

class Meta:
model = Comment
fields = '__all__'
read_only_fields = ('article',)


class ArticleSerializer(serializers.ModelSerializer):
comment_set = CommentSerializer(many=True, read_only=True)

class Meta:
model = Article
fields = '__all__'

2. 특정 게시글에 작성된 댓글의 개수 구하기

  • comment_set 매니저는 모델 관계로 인해 자동으로 구성되기 때문에 커스텀 필드를 구성하지 않아도 comment_set이라는 필드명을 fields 옵션에 작성만 해도 사용할 수 있었음.
  • 하지만 지금처럼 별도의 값을 위한 필드를 사용하려는 경우 자동으로 구성되는 매니저가 아니기 때문에 직접 필드를 작성해야 함.
# articles/serializers.py

class ArticleSerializer(serializers.ModelSerializer):
comment_set = CommentSerializer(many=True, read_only=True)
comment_count = serializers.IntegerField(source='comment_set.count', read_only=True)

class Meta:
model = Article
fields = '__all__'

source arguments
  • 필드를 채우는 데 사용할 속성의 이름
  • 점 표기법(dot notation)을 사용하여 속성을 탐색할 수 있음
  • comment_set 이라는 필드에 .(dot)을 통해 전체 댓글의 개수 확인 가능
  • .count()는 built-in Queryset API 중 하나

[주의] read_only_fields shorcut issue
  • 특정 필드를 override 혹은 추가한 경우 read_only_fields으로 사용할 수 없음.
class ArticleSerializer(serializers.ModelSerializer):
comment_set = CommentSerializer(many=True, read_only=True)
comment_count = serializers.IntegerField(source='comment_set.count', read_only=True)

class Meta:
model = Article
fields = '__all__'
read_only_fields = ('comment_set', 'comment_count') # X

마무리

  • RESTful API
  • HTTP
  • Response
  • Single Model
  • 1:N Relation