[Django] REST API

작성:    

업데이트:

카테고리:

태그: ,

HTTP

  • HyperText Transfer Protocol
  • 웹 상에서 컨텐츠를 전송하기 위한 약속
  • HTML 문서와 같은 리소스들을 가져올 수 있도록 하는 프로토콜(규칙, 약속)


  • Stateless
  • Connectionless

  • 쿠키와 세션을 통해 서버 상태를 요청과 연결하도록 함


HTTP request methods

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


HTTP response status codes

  • 특정 HTTP 요청이 성공적으로 완료되었는지 여부
  1. (1xx) Informational responses
  2. (2xx) Successful responses
  3. (3xx) Redirection responses
  4. (4xx) Client error responses
  5. (5xx) Server error responses


웹에서의 리소스 식별

  • 리소스(자원) : HTTP 요청의 대상
  • 문서, 사진 등 어떤 것이든 될 수 있다.
  • 각 리소스는 리소스 식별을 위해 HTTP 전체에서 사용되는 URI(Uniform Resource Identifier)로 식별


URL, URN, URI

URL(Uniform Resource Locator)

  • 통합 자원 위치
  • 네트워크 상에 자원이 어디 있는지 알려주기 위한 약속
  • 과거에는 실제 자원 위치를 표시, 현재는 추상화된 의미론적 구성
  • ‘웹 주소’, ‘링크’ 등으로 불림


URN(Uniform Resource Name)

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


URI(Uniform Resource Identifier)

  • 통합 자원 식별자
  • 인터넷의 자원을 식별하는 유일한 주소
  • 인터넷에서 자원을 식별하거나 이름을 지정하는 데에 사용되는 간단한 문자열
  • URL, URN의 큰 범위
  • URN을 사용하는 비중이 적어, 일반적으로 URL은 URI와 같은 의미로 사용


URI의 구조

예시 URI : https://www.example.com:80/path/to/myfile.html/?key=value#quick-start

Scheme : https://

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


Host(Domain name) : www.example.com

  • 요청을 받는 웹 서버의 이름
  • IP address를 직접 사용할 수 있으나, 실 사용시 불편하여 웹에서는 지양


Port : :80

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


Path : /path/to/myfile.html

  • 웹 서버 상의 리소스 경로
  • 초기에는 실제 파일이 위치한 물리적 위치
  • 오늘날은 물리적 실제 위치가 아닌 추상적 구조로 표현


Query(Identifier) : ?key=value

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


Fragment : #quick-start

  • Anchor
  • 자원 안에서의 북마크의 한 종류
  • 브라우저에게 해당 문서(HTML)의 특정 부분을 보여주기 위한 방법
  • fragment identifier(부분 식별자)라고 부른다.
  • # 뒤의 부분은 요청이 서버에 보내지지 않는다.


RESTful API

API

  • Application Programming Interface
  • 프로그래밍 언어가 제공하는 기능을 수행할 수 있게 만든 인터페이스
  • 어플리케이션과 프로그래밍으로 소통하는 방법


Web API

  • 웹 애플리케이션 개발에서 다른 서비스에 요청을 보내고 응답을 받기 위해 정의된 명세
  • 현재 웹 개발은 여러 Open API를 활용


REST

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


REST의 자원과 주소의 지정 방법

  • 자원 : URI
  • 행위 : HTTP method
  • 표현 : 결과물 JSON


핵심 규칙

  • ‘정보’는 URI로 표현
  • 자원에 대한 ‘행위’ 는 HTTP method로 표현(GET, POST, PUT, DELETE)
  • 설계 방법론을 지키지 않더라도 동작 여부에 큰 영향을 미치지 않지만, 얻는 것이 더 많음


JSON

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


특징

  • 사람이 읽거나 쓰기 쉬움
  • 기계가 파싱(해석, 분석)하고 만들어내기 쉬움
  • C 계열의 언어가 가지는 key-value 형태의 자료구조로 변화하기 쉬움


RESTful API

  • REST 원리를 따라 설계한 API
  • (simply) RESTful services라고도 부름
  • 프로그래밍을 통해 클라이언트의 요청에 따라 JSON을 응답하는 서버 구성


Response

코드를 통해 알아보자.


초기 설정

# 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)


응답 코드

HTML 응답

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


JSON 응답 A : for문 이용

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


Content Type entity header

  • 데이터의 media type(MIME type, content type)을 나타내기 위해 사용
  • 응답 내에 있는 컨텐츠의 유형이 실제로 무엇인지 클라이언트에게 전달


JSON 응답 B : Serialization 이용

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


JsonResponse objects

  • JSON-encoded response를 만드는 HttpResponse의 서브 클래스


“safe” parameter

  • 기본값은 True
  • dict 이외의 객체를 직렬화(Serializaton)하려면 False로 설정해야 함
# 딕셔너리이므로 safe 옵션 default(True)
response = JsonResponse({'foo':'bar'})

# 딕셔너리 형태가 아니므로 safe 옵션 False
response = JsonResponse([1, 2, 3], safe=False)


Serialization

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


장고에서의 직렬화?

  • QuerySet 및 Model Instance와 같은 복잡한 데이터를 JSON이나 XML 등의 유형으로 바로 변환할 수 없기 때문에 Python 데이터 타입으로 바뀐 뒤 변환하는 것

  • 즉, 중간 과정!!


아까 JSON 반환 직렬화 코드를 다시 보자

def article_json_2(request):
    # articles는 쿼리셋 → 바로 json으로 바꿀 수 없다.
    articles = Article.objects.all()

    # 직렬화된 객체
    data = serializers.serialize('json', articles)
    return HttpResponse(data, content_type='application/json')
  • Django의 내장 HttpResponse를 활용한 JSON 응답 객체
  • 주어진 모델 정보를 활용하기 때문에 이전과 달리 필드를 개별적으로 직접 만들어줄 필요가 없음


image

A와 달리 내장된 모델을 사용하므로 pk가 표시되는 등 일부가 다르지만 JSON 형태로 rendering되는 것을 볼 수 있다.


DRF

  • Django REST framework(DRF)
  • Web API 구축을 위한 강력한 Toolkit을 제공하는 라이브러리
  • DRF의 Serializer는 Django의 Form 및 ModelForm 클래스와 매우 유사하게 구성 및 작동
  • JSON이 주된 응답


  Django DRF
Response HTML JSON
Model ModelForm Serializer


JSON 응답 C : DRF 이용

# articles/views.py
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)
  • many는 단일 객체가 아닐 때 True로 명시하여 사용
  • default는 False


ArticleSerializer 정의

어? ArticleSerializer는 정의한 적이 없는데요?

# articles/serializers.py
from rest_framework import serializers
from .models import Article


class ArticleSerializer(serializers.ModelSerializer):

    class Meta:
        model = Article
        fields = '__all__'
  • ArticleSerializer : Article 모델의 쿼리셋을 JSON 형태로 변환하는 데에 도움을 주는 도구
  • ModelForm의 구조와 같다.


결과

image

JSON 형태가 아닌 디자인된 웹사이트가 나왔다.


image

  • 브라우저에서 출력할 때에는 HTML문서로 응답을 받음
  • API로써 사용하려 할 때에는 JSON으로 사용 가능


requests 라이브러리를 이용해 DRF URL로 요청을 보내보자

import requests
from pprint import pprint

response = requests.get('http://127.0.0.1:8000/api/v1/json-3/')
pprint(response)
pprint(response.json())

# [응답]
# <Response [200]>
# 이후 json 문서 줄줄줄


Single Model

DRF with Single Model

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


ModelSerializer

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


단일 객체가 아닌 여러 queryset 결과를 serializing하는 ModelSeiralizer를 만들어보자

# articles/serializers.py
from rest_framework import serializers
from .models import Article

class ArticleListSerializer(serializers.ModelSerializer):
    
    class Meta:
        model = Article
        fields = ('id', 'title',)


ModelSerializer 사용

ipython과 shell_plus를 이용해 ModelSerializer를 추가해보자

# ModelSerializer import 
In [1]: from articles.serializers import ArticleListSerializer

# 단일 객체 생성 및 확인
In [3]: serializer = ArticleListSerializer()

In [4]: serializer
Out[4]: 
ArticleListSerializer():
    id = IntegerField(label='ID', read_only=True)
    title = CharField(max_length=100)
    content = CharField(style={'base_template': 'textarea.html'})
    created_at = DateTimeField(read_only=True)
    updated_at = DateTimeField(read_only=True)

In [5]: article = Article.objects.get(pk=1)

In [6]: article
Out[6]: <Article: Article object (1)>

In [7]: serializer = ArticleListSerializer(article)

In [8]: serializer
Out[8]: 
ArticleListSerializer(<Article: Article object (1)>):
    id = IntegerField(label='ID', read_only=True)
    title = CharField(max_length=100)
    content = CharField(style={'base_template': 'textarea.html'})
    created_at = DateTimeField(read_only=True)
    updated_at = DateTimeField(read_only=True)

# JSON 형태로 변환된 데이터를 serializer 객체가 가짐
In [9]: serializer.data
Out[9]: {'id': 1, 'title': 'Without different those candidate.', 'content': 'Speak painting matter family direction always like. Yard threat sometimes how.', 'created_at': '1981-02-22T16:42:43Z', 'updated_at': '1973-03-08T03:07:57Z'}


# 다중 객체 쿼리셋 생성 및 저장
In [10]: articles = Article.objects.all()

# serialization 1 : 쿼리셋만
In [11]: serializer = ArticleListSerializer(articles)

# 오류 발생
In [12]: serializer.data

AttributeError: Got AttributeError when attempting to get a value for field `title` on serializer `ArticleListSerializer`.

# serialization 2 : 쿼리셋과 many=True 옵션
In [13]: serializer = ArticleListSerializer(articles, many=True)

In [14]: serializer.data
Out[14]: [OrderedDict([('id', 1), ('title', 'Without different those candidate.'), ('content', 
'Speak painting matter family direction always like. Yard threat sometimes how.'), ('created_at', '1981-02-22T16:42:43Z'), ...]


many argument

many=True

  • Serializing multiple objects
  • 단일 인스턴스 대신 QuerySet 등을 직렬화하기 위해서 serializer를 인스턴스화할 때 many=True를 키워드 인자로 전달


가. GET - Article List

urls

# articles/urls.py
from django.urls import path
from . import views


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


views

# articles/views.py
from django.shortcuts import render
from rest_framework.response import Response
from articles import serializers
from .serializers import ArticleListSerializer
from .models import Article

# Create your views here.
def article_list(request):
    articles = Article.objects.all()

    # articles queryset을 직렬화
    serializer = ArticleListSerializer(articles, many=True)
    return Response(serializer.data)


api_view decorator

  • 기본적으로 GET method만 허용
  • 다른 method에 대해서는 405 Method Not Allowed로 응답
  • View 함수가 응답해야 하는 HTTP method 목록을 리스트의 인자로 받음

  • ⭐ DRF에서는 api_view decorator를 선택이 아닌 반드시 작성
  • 작성하지 않으면 view 함수가 작동하지 않는다.


detail을 위한 serializer 추가 정의

기존 serializers.py

# articles/serializers.py
from rest_framework import serializers
from .models import Article

class ArticleListSerializer(serializers.ModelSerializer):
    
    class Meta:
        model = Article
        fields = ('id', 'title',)
  • Article queryset에 대해 직렬화하는 serializer
  • 모든 articles를 나열하는 index에 사용하므로, 자세한 정보는 불필요
  • field를 기존 ‘__all__‘에서 id와 title만 나오도록 변경


Detail serializer 추가 정의

class ArticleSerializer(serializers.ModelSerializer):
    
    class Meta:
        model = Article
        fields = '__all__'
  • 새롭게 정의해준다.


detail url

from django.urls import path
from . import views


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


detail view 함수

# articles/views.py

from django.shortcuts import render, 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)


나. POST - Create Article

  • 201 Created 상태 코드 및 메시지 응답
  • RESTful 구조에 맞게 작성
  • article_list 함수로 게시글 조회, 생성 모두 처리


else로 하는 것이 아니라 method마다 구조를 elif로 나누어 정확히 가시적으로 보일 수 있도록 한다.


# 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)
        
        # articles queryset을 직렬화
        serializer = ArticleListSerializer(articles, many=True)
        return Response(serializer.data)
    
    elif request.method=='POST':
        serializer = ArticleSerializer(data=request.data) # ⭐request.POST가 아님에 주의
        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 옵션

  • DRF에는 status code를 보다 명확하고 읽기 쉽게 만드는 데 사용할 수 있는 정의된 상수 집합 제공
  • status 모듈에 HTTP status code 집합이 모두 포함
  • 단순히 status=201 처럼 표현도 가능하지만 DRF에서는 권장하지 않음


raise_exception

  • 400 status code를 응답하는 return 구문 삭제용
  • is_valid()에서 유효성 검사 오류 시 400_BAD_REQUEST를 발생
@api_view(['GET', 'POST'])
def article_list(request):
    if request.method=='GET':
        articles = get_list_or_404(Article)
        
        # articles queryset을 직렬화
        serializer = ArticleListSerializer(articles, many=True)
        return Response(serializer.data)
    
    elif request.method=='POST':
        serializer = ArticleSerializer(data=request.data) 
        if serializer.is_valid(raise_exception=True): # ⭐is_valid의 인자로 들어감
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        # return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 생략 가능


다. DELETE - Delete Article

  • 204 No Content 상태 코드 및 메시지 응답
  • article_detail 함수로 상세 게시글 조회 및 삭제 행위 모두 처리 가능


기존 코드

# articles/views.py

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


DELETE 추가

# articles/views.py
@api_view(['GET', 'DELETE'])
def article_detail(request, article_pk):
    # ⭐ article은 공유하므로 method 판단 전에 저장
    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)
  • 삭제되는 것은 객체가 남는 작업이 아니다.
  • 그래서 삭제 안내 메시지를 delete key로 data에 담아 Response에 전달
  • 204_NO_CONTENT 상태 코드 응답


라. 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)
    
    ...

    elif request.method=='PUT':
        serializer = ArticleSerializer(article, data=request.data)
        # serializer = ArticleSerializer(instance=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를 직렬화하여, JSON으로 변환
  • 2개 이상의 1:N 관계를 맺는 모델을 두고 CRUD 로직을 수행 가능하도록 설계


가-1. 단일 댓글

Comment model 추가

# 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)


Comment에 대한 serializers 추가

# articles/serializers.py
from .models import Article, Comment

...

class CommentSerializer(serializers.ModelSerializer):
    
    class Meta:
        model = Comment
        fields = '__all__'


url

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


view

# articles/views.py
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. 댓글 detail

url

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


view

@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)


나. POST - Create Comment

url

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


view

@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)

IntegrityError 오류 발생

  • Article 생성과 달리 Comment의 생성은 생성 시에 참조하는 모델의 객체 정보가 필요
  • 1:N 관계에서 N은 어떤 1을 참조하는지에 대한 정보가 필요하기 때문(외래 키)


수정 코드

  • .save() method는 특정 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)


require 에러 발생

article 필드에 대해 이 필드는 필수 항목이라는 응답 수신


Read Only Field(읽기 전용 필드)

  • 어떤 게시글에 작성하는 댓글인지에 대한 정보를 form-data로 넘겨주지 않았기 때문에 직렬화하는 과정에서 article 필드가 유효성 검사(is_valid)를 통과하지 못함
  • CommentSerializer에서 article field에 해당하는 데이터 또한 요청으로부터 받아서 직렬화하는 것으로 설정되었기 때문

  • 이때는 읽기 전용 필드(read_only_fields) 설정을 통해 직렬화하지 않고 반환 값에만 해당 필드가 포함되도록 설정할 수 있음
  • 하지만 응답에는 포함


serializers.py

# articles/serializers.py
class CommentSerializer(serializers.ModelSerializer):
    
    class Meta:
        model = Comment
        fields = '__all__'
        read_only_fields = ('article',) 🔅


DELETE & PUT - delete, update Comment

Article 생성 로직과 같다.

@api_view(['GET', 'POST', '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 = {
            'delete' : f'데이터 {comment_pk}번이 삭제되었습니다.'
        }
        return Response(data, status=status.HTTP_204_NO_CONTENT)
    
    elif request.method=='PUT':
        serializer = CommentSerializer(comment, data=request.data)
        # serializer = CommentSerializer(instance=comment, data=request.data)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response(serializer.data)


1:N Serializer 가. 특정 게시글에 작성된 댓글 목록 출력

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


1) PrimaryKeyRelatedField

  • pk를 사용하여 관계된 대상을 나타내는 데 사용 가능
  • 필드가 to many relationships(N)를 나타내는데 사용되는 경우 many=True 속성 필요
  • comment_set 필드 값을 form-data로 받지 않으므로 read_only=True 설정 필요


serializers.py

class ArticleSerializer(serializers.ModelSerializer):
    comment_set = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
    
    class Meta:
        model = Article
        fields = '__all__'
  • Article 인스턴스에 있는 comment_set이 이미 있다고 생각
  • article:comment가 1:N 관계이므로 many=True 설정
  • 사용자로부터 form-data를 받는 것이 아닌, 조회만 하는 것이므로 read_only를 속성값으로 넣는다.


  • 역참조 시 생성되는 comment_set을 다른 매니저 이름으로 override 가능
  • 단, 이전 serializers.py에서의 클래스 변수명도 일치하도록 수정해야 함
# articles/models.py
class Comment(models.Model):
    article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='comments')


2) Nested relationships

  • 모델 관계상으로 참조된 대상은 참조하는 대상의 표현(응답)에 포함되거나 중첩(nested) 가능
  • 이러한 중첩된 관계는 serializers를 필드로 사용하여 표현 가능
  • 두 클래스의 상하위치를 변경해주어야 함
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__'
  • comment 각각의 상세 내용을 모두 들고 옴


1:N Serializer 나. 특정 게시글에 작성된 댓글의 개수 구하기

  • comment_set 매니저는 모델 관계로 인해 자동으로 구성 → custom field를 구성하지 않아도 comment_set이라는 필드명을 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__'


‘source’ arguments

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


주의사항 : read_only_fields 문제

  • 특정 필드를 override 혹은 추가한 경우 read_only_fields shortcut으로 사용 불가능
  • 사용하고 싶다면 필드에 속성으로 넣어주어야 한다!
# articles/serializers.py
class ArticleSerializer(serializers.ModelSerializer):
    # comment_set = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
    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',) ❓❔

댓글남기기