logo
Search검색어를 포함하는 게시물들이 최신순으로 표시됩니다.
    Table of Contents
    [Django] REST API

    이미지 보기

    [Django] REST API

    • 22.04.20 작성

    • 읽는 데 25

    TOC

    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이 주된 응답

    DjangoDRF
    ResponseHTMLJSON
    ModelModelFormSerializer

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

    1. 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',) ❓❔
    
    profile

    FE Developer 박승훈

    노력하는 자는 즐기는 자를 이길 수 없다