[Django] REST API
작성:    
업데이트:
카테고리: Django
태그: BE Framework, Django
HTTP
- HyperText Transfer Protocol
- 웹 상에서 컨텐츠를 전송하기 위한 약속
- HTML 문서와 같은 리소스들을 가져올 수 있도록 하는 프로토콜(규칙, 약속)
- Stateless
-
Connectionless
- 쿠키와 세션을 통해 서버 상태를 요청과 연결하도록 함
HTTP request methods
- 자원에 대한 행위(수행하고자 하는 동작)를 정의
- 주어진 리소스(자원)에 수행하길 원하는 행동을 나타냄
- GET, POST, PUT, DELETE
HTTP response status codes
- 특정 HTTP 요청이 성공적으로 완료되었는지 여부
- (1xx) Informational responses
- (2xx) Successful responses
- (3xx) Redirection responses
- (4xx) Client error responses
- (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)
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 응답 객체
- 주어진 모델 정보를 활용하기 때문에 이전과 달리 필드를 개별적으로 직접 만들어줄 필요가 없음
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의 구조와 같다.
결과
JSON 형태가 아닌 디자인된 웹사이트가 나왔다.
- 브라우저에서 출력할 때에는 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
- 아래 핵심 기능 제공
- 모델 정보에 맞춰 자동으로 필드 생성
- serializer에 대한 유효성 검사기를 자동으로 생성
- .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',) ❓❔
댓글남기기