<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>메모장</title>
        <link>https://velog.io/</link>
        <description>드디어 1년 찍은 비전공 뉴비 개발자</description>
        <lastBuildDate>Wed, 19 Aug 2020 03:57:27 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. 메모장. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/lu_at_log" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[관계가 있는 모델의 Swagger 문서 자동화]]></title>
            <link>https://velog.io/@lu_at_log/%EA%B4%80%EA%B3%84%EA%B0%80-%EC%9E%88%EB%8A%94-%EB%AA%A8%EB%8D%B8%EC%9D%98-Swagger-%EB%AC%B8%EC%84%9C-%EC%9E%90%EB%8F%99%ED%99%94</link>
            <guid>https://velog.io/@lu_at_log/%EA%B4%80%EA%B3%84%EA%B0%80-%EC%9E%88%EB%8A%94-%EB%AA%A8%EB%8D%B8%EC%9D%98-Swagger-%EB%AC%B8%EC%84%9C-%EC%9E%90%EB%8F%99%ED%99%94</guid>
            <pubDate>Wed, 19 Aug 2020 03:57:27 GMT</pubDate>
            <description><![CDATA[<h2 id="시작에-앞서">시작에 앞서</h2>
<hr>
<p>모델 간의 관계가 있을 때 Swagger 문서를 위한 시리얼라이저를 구성하는 방법에 대해서 기록해두고자 한다. 예제는 <a href="https://velog.io/@lu_at_log/drf-yasg-and-swagger">이전 글</a>에서 만든 <a href="https://github.com/mintcc342g/swagger_study">샘플 프로젝트</a>에서 이어진다.
<br><br></p>
<h2 id="관계가-있는-모델의-시리얼라이저-작성법">관계가 있는 모델의 시리얼라이저 작성법</h2>
<hr>
<p>요약하자면, 관계가 있는 모델 끼리 시리얼라이저도 잘 묶어주면 된다.
<br><br></p>
<h3 id="모델-작성">모델 작성</h3>
<p>우선 기존의 Music 모델과 관계가 있는 새로운 모델을 선언한다.</p>
<pre><code class="language-python"># 프로젝트_루트/api/models.py
# -----------------------------------------------------

# ...

class PlayList(models.Model):
    id = models.BigAutoField(primary_key=True, verbose_name=&#39;play_list_id&#39;)
    created_at = models.DateTimeField(auto_now_add=True, verbose_name=&#39;만들어진 날짜&#39;)
    updated_at = models.DateTimeField(auto_now=True, verbose_name=&#39;업뎃 된 날짜&#39;)
    play_list_name = models.CharField(null=False, max_length=128, verbose_name=&#39;플레이리스트 이름&#39;)
    music = models.ForeignKey(Music, related_name=&#39;music&#39;, db_column=&#39;music_id&#39;,
                                    on_delete=models.CASCADE, verbose_name=&#39;곡&#39;)

    class Meta:
        managed = True
        db_table = &#39;play_lists&#39;
        app_label = &#39;api&#39;
        verbose_name_plural = &#39;나의 플레이 리스트&#39;
</code></pre>
<br>


<h3 id="시리얼라이저-작성">시리얼라이저 작성</h3>
<p>새로운 모델을 위한 시리얼라이저를 작성한다. 여기서는 response 시에 Music에서 특정 필드만 보여주고 싶어서 새로운 시리얼라이저를 만들었다. 하는 김에 request body와 query string을 위한 시리얼라이저도 만들었다.</p>
<p>Music 시리얼라이저를 PlayList의 music 변수에 선언해주는 것이 핵심이다. music이라는 변수명을 사용했는데, 모델에서 FK 필드를 선언해준 변수명과 동일하다. 변수명을 다르게 작성할 경우 인식하지 못한다.</p>
<pre><code class="language-python"># 프로젝트_루트/api/serializers.py
# -----------------------------------------------------

# ...

# PlayListSerializer에서 music 필드를 위해 사용할 새로운 시리얼라이저를 만듦.
class MusicForPlayListSerializer(serializers.ModelSerializer):
    class Meta:
        music = Music.objects.all()
        model = Music
        fields = (&#39;singer&#39;, &#39;title&#39;, &#39;category&#39;, &#39;star_rating&#39;,)


class PlayListSerializer(serializers.ModelSerializer):
    # 만약 get_queryset을 사용하고 관계가 있는 모델을 사용할 경우,
    # 시리얼라이저의 필드명은 모델에서 선언한 필드명과 동일하게 써줘야 함.
    # 안 그러면 response 값에 시리얼라이저가 안 먹혀서 해당 필드는 안 보이게 됨.
    music = MusicForPlayListSerializer(read_only=True)
    # music = MusicSerializer(read_only=True)  # Music의 모든 필드를 보이고 싶다면 이 시리얼라이저를 사용하면 됨.

    class Meta:
        play_list = PlayList.objects.all()
        model = PlayList
        fields = (&#39;play_list_name&#39;, &#39;created_at&#39;, &#39;updated_at&#39;,&#39;music&#39;,) # 원하는 필드만 선언할 때, 관계가 있는 모델의 필드명을 써준다.


class PlayListQuerySerializer(serializers.Serializer):
    title = serializers.CharField(help_text=&quot;곡 제목으로 검색&quot;, required=False)
    singer = serializers.CharField(help_text=&quot;가수명으로 검색&quot;, required=False)


class PlayListBodySerializer(serializers.Serializer):
    name = serializers.CharField(help_text=&quot;플레이 리스트 이름&quot;)
    play_list = MusicBodySerializer(read_only=True)

</code></pre>
<br>

<h3 id="view-url-등-기타-작업">view, url 등 기타 작업</h3>
<p>플레이 리스트를 위한 view를 만들자. 어노테이션 사용법은 이전 글에서 다룬대로 해주면 된다.</p>
<pre><code class="language-python"># 프로젝트_루트/api/views.py
# -----------------------------------------------------

# ...

class PlayListViewSet(viewsets.GenericViewSet,
                mixins.ListModelMixin,
                View):

    serializer_class = PlayListSerializer

    @swagger_auto_schema(query_serializer=PlayListQuerySerializer)
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)

    def get_queryset(self):
        conditions = {
            &#39;play_list_name&#39;: self.kwargs.get(&quot;play_list_name&quot;, None),
            &#39;music__title__contains&#39;: self.request.data.get(&#39;title&#39;, None),
            &#39;music__singer__contains&#39;: self.request.data.get(&#39;singer&#39;, None),
        }
        conditions = {key: val for key, val in conditions.items() if val is not None}

        play_list = PlayList.objects.filter(**conditions)
        if not play_list.exists():
            raise Http404()

        return play_list

    @swagger_auto_schema(request_body=PlayListBodySerializer)
    def add(self, request):
        conditions = request.data[&#39;play_list&#39;]
        music = Music.objects.get(**conditions)
        play_list = PlayList.objects.create(
            play_list_name=request.data[&#39;name&#39;],
            music=music  # instance를 넣어도 되고, id를 넣어도 됨.
        )

        play_list.save()

        return Response(PlayListSerializer(play_list).data, status=status.HTTP_201_CREATED)
</code></pre>
<br>

<p>새로운 View를 위한 새로운 url을 추가해주자.</p>
<pre><code class="language-python"># 프로젝트_루트/api/urls.py
# -----------------------------------------------------

# ...

from django.urls import path
from django.conf import settings

from .views import *

urlpatterns = [

    # ...

    path(&quot;v1/play_list&quot;, PlayListViewSet.as_view({&quot;get&quot;: &quot;list&quot;, &quot;post&quot;: &quot;add&quot;}), name=&quot;play_lists&quot;),
    path(&quot;v1/play_list/&lt;str:play_list_name&gt;&quot;, PlayListViewSet.as_view({&quot;get&quot;: &quot;list&quot;}), name=&quot;play_list&quot;),
]

</code></pre>
<p><br><br></p>
<p>완성이다. 서버를 켜서 Swagger 문서를 확인해보자.
<img src="https://images.velog.io/images/lu_at_log/post/a8f8d294-b04f-49e5-a859-22ba58fc2586/8.jpg" alt=""></p>
<p>response를 확인해보면 작성한대로 잘 나온다.
<img src="https://images.velog.io/images/lu_at_log/post/0d49d5d8-2d20-4bfc-8822-9cd9b4606d74/9.jpg" alt="">
<br><br></p>
<h2 id="마치며">마치며</h2>
<hr>
<p>시리얼라이저를 작성하는 것이 노다가성이 짙다는 게 약간 흠인 것 같다. 모델이 많아지면 지저분해보이기도 한다. 더 좋은 방법을 찾을 수 있길.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[drf-yasg를 이용한 Swagger 문서 자동화]]></title>
            <link>https://velog.io/@lu_at_log/drf-yasg-and-swagger</link>
            <guid>https://velog.io/@lu_at_log/drf-yasg-and-swagger</guid>
            <pubDate>Sun, 16 Aug 2020 15:08:44 GMT</pubDate>
            <description><![CDATA[<h2 id="swagger">Swagger?</h2>
<hr>
<p>API 서버를 자동으로 문서화 시켜주는 툴이다. API 주소나 각 API의 메소드를 명시해주고, query string이나 body 같은 payload도 문서 내에서 사용 가능하게 해준다.</p>
<p>Django 프레임워크를 사용한 경우, drf-yasg 라이브러리를 이용하여 python 코드로 더욱 쉽게 문서 자동화가 가능하다는 것 같다. 자세한 내용은 <a href="https://drf-yasg.readthedocs.io/en/stable/">drf-yasg 공식문서</a>, <a href="https://github.com/axnsan12/drf-yasg/tree/master/testproj">drf-yasg 공식에서 제공하는 샘플코드</a> 등을 참고하길 바란다.
<br><br></p>
<h2 id="간단한-예제">간단한 예제</h2>
<hr>
<p>여기서는 간단한 POST, GET 메소드 API를 만들어 로컬 서버에서 swagger 문서로 띄우는 과정을 설명한다.</p>
<p><a href="https://github.com/mintcc342g/swagger_study">최종 결과물 링크</a>
<br><br></p>
<h3 id="라이브러리-설치-및-환경설정">라이브러리 설치 및 환경설정</h3>
<p>drf-yasg 라이브러리를 설치한다.
간단한 API를 만들 것이니 rest-framework도 설치해주자.</p>
<pre><code class="language-bash">(.venv) $ pip install drf-yasg             # .venv는 가상환경임.
(.venv) $ pip install djangorestframework</code></pre>
<br>

<p>환경설정 파일(디폴트 settings.py)의 INSTALLED_APPS에 설치한 라이브러리의 app을 추가한다.</p>
<pre><code class="language-python"># 프로젝트_루트/프로젝트_이름/settings.py
# -----------------------------------------------------

# ...

INSTALLED_APPS = [
    &#39;django.contrib.admin&#39;,
    &#39;django.contrib.auth&#39;,
    &#39;django.contrib.contenttypes&#39;,
    &#39;django.contrib.sessions&#39;,
    &#39;django.contrib.messages&#39;,
    &#39;django.contrib.staticfiles&#39;,
    &#39;drf_yasg&#39;,        # 추가
    &#39;rest_framework&#39;,  # 추가
]

# ...</code></pre>
<br>

<p>swagger를 위한 엔드포인트를 추가한다. 이걸 명시해야 Swagger 문서를 UI로 볼 수 있다.
urls.py 파일을 app 단위로 나누었다면, 최상위 urls.py 파일에 명시하면 된다.</p>
<pre><code class="language-python"># 프로젝트_루트/프로젝트_이름/urls.py
# --------------------------------------------------

from django.contrib import admin
from django.urls import path, re_path
from django.conf import settings
from rest_framework import permissions
from drf_yasg.views import get_schema_view
from drf_yasg import openapi

schema_view = get_schema_view( 
    openapi.Info( 
        title=&quot;Swagger Study API&quot;, 
        default_version=&quot;v1&quot;, 
        description=&quot;Swagger Study를 위한 API 문서&quot;, 
        terms_of_service=&quot;https://www.google.com/policies/terms/&quot;, 
        contact=openapi.Contact(name=&quot;test&quot;, email=&quot;test@test.com&quot;), 
        license=openapi.License(name=&quot;Test License&quot;), 
    ), 
    public=True, 
    permission_classes=(permissions.AllowAny,), 
)

urlpatterns = [
    path(&#39;admin/&#39;, admin.site.urls),
]

# 이건 디버그일때만 swagger 문서가 보이도록 해주는 설정이라는 듯. urlpath도 이 안에 설정 가능해서, debug일때만 작동시킬 api도 설정할 수 있음.
if settings.DEBUG:
    urlpatterns += [
        re_path(r&#39;^swagger(?P&lt;format&gt;\.json|\.yaml)$&#39;, schema_view.without_ui(cache_timeout=0), name=&quot;schema-json&quot;),
        re_path(r&#39;^swagger/$&#39;, schema_view.with_ui(&#39;swagger&#39;, cache_timeout=0), name=&#39;schema-swagger-ui&#39;),
        re_path(r&#39;^redoc/$&#39;, schema_view.with_ui(&#39;redoc&#39;, cache_timeout=0), name=&#39;schema-redoc&#39;),    ]</code></pre>
<p><br><br></p>
<h3 id="model-생성">Model 생성</h3>
<p>api 앱 생성</p>
<pre><code class="language-bash">(.venv) $ python manage.py startapp api</code></pre>
<br>

<p>settings.py에 앱 추가</p>
<pre><code class="language-python"># 프로젝트_루트/프로젝트_이름/settings.py
# -----------------------------------------------------

# ...

INSTALLED_APPS = [
    # ...
    &#39;api&#39;,   # 새 앱 추가
]

# ...</code></pre>
<br>

<p>생성한 앱에 간단한 모델 생성</p>
<pre><code class="language-python"># 프로젝트_루트/api/models.py
# -----------------------------------------------------

from django.db import models 

# Create your models here. 

class Music(models.Model):
    ONE_STAR = 1
    TWO_STAR = 2
    THREE_STAR = 3
    STARS = (
        (ONE_STAR, &#39;좋음&#39;),
        (TWO_STAR, &#39;매우 좋음&#39;),
        (THREE_STAR, &#39;고귀함&#39;),
    )

    CATEGORY = (
        (&#39;JPOP&#39;, &#39;제이팝&#39;),
        (&#39;POP&#39;, &#39;팝송&#39;),
        (&#39;CLASSIC&#39;, &#39;클래식&#39;),
        (&#39;ETC&#39;, &#39;기타등등&#39;),
    )

    id = models.BigAutoField(primary_key=True, verbose_name=&#39;music_id&#39;)
    created_at = models.DateTimeField(auto_now_add=True, verbose_name=&#39;추가된 날짜&#39;)
    updated_at = models.DateTimeField(auto_now=True, verbose_name=&#39;업뎃 된 날짜&#39;)
    deleted_at = models.DateTimeField(blank=True, null=True, verbose_name=&#39;삭제된 날짜&#39;)
    singer = models.CharField(null=False, max_length=128, verbose_name=&#39;가수명&#39;)
    title = models.CharField(null=False, max_length=128, verbose_name=&#39;곡명&#39;)
    category = models.CharField(blank=True, null=True, max_length=32, choices=CATEGORY, verbose_name=&#39;범주&#39;)
    star_rating = models.PositiveSmallIntegerField(blank=True, null=True, choices=STARS, default=ONE_STAR, verbose_name=&#39;곡 선호도&#39;)

    class Meta:
        # abstract = True  # sqlite3 사용 시 어째선지 이게 있으면 마이그레이션이 안 됨.
        managed = True
        db_table = &#39;musics&#39;
        app_label = &#39;api&#39;
        ordering = [&#39;star_rating&#39;, ]
        verbose_name_plural = &#39;음악&#39;</code></pre>
<br>

<p>마이그레이션</p>
<pre><code class="language-bash">(.venv) $ python manage.py makemigrations  # 마이그레이션 파일 만들기
(.venv) $ python manage.py migrate         # db에 마이그레이션 파일 적용

# DB에 접속해서 확인해봄.
$ sqlite3 DB명    # 맥os 일 때
$ .\sqlite3 DB명  # 윈도우 일 때

# 접속 후
sqlite&gt; .tables
auth_group                  django_admin_log
auth_group_permissions      django_content_type       
auth_permission             django_migrations
auth_user                   django_session
auth_user_groups            musics  # 잘 추가되어 있는 것을 확인
auth_user_user_permissions</code></pre>
<p><br><br></p>
<h3 id="클래스형-view와-serializer-작성">클래스형 View와 Serializer 작성</h3>
<p>GET, POST url 추가</p>
<p>앱 내부 urls</p>
<pre><code class="language-python"># 프로젝트_루트/api/urls.py
# --------------------------------------------------

from django.urls import path 
from django.conf import settings 
from .views import MusicViewSet

urlpatterns = [ 
    path(&quot;v1/music&quot;, MusicViewSet.as_view({&quot;get&quot;: &quot;list&quot;, &quot;post&quot;: &quot;add&quot;}), name=&quot;musics&quot;),
    path(&quot;v1/music/&lt;int:music_num&gt;&quot;, MusicViewSet.as_view({&quot;get&quot;: &quot;list&quot;}), name=&quot;music&quot;),
]</code></pre>
<p>프로젝트 urls</p>
<pre><code class="language-python"># 프로젝트_루트/프로젝트_이름/urls.py
# --------------------------------------------------

# ...

urlpatterns = [
    path(&#39;admin/&#39;, admin.site.urls),
    path(&quot;api/&quot;, include((&quot;api.urls&quot;, &quot;api&quot;))), # 편의를 위해(?) api 앱의 세부 url은 api앱 내부의 urls.py 파일에서 관리할 것
]

# ...</code></pre>
<br>

<p>swagger 문서 자동화를 구현한 예제들을 보면, 다들 클래스형 View로 구현한다. 아마 클래스 변수로서 serializer_class 를 선언하여 시리얼라이저를 받아줘야 하기 때문인 것 같다.</p>
<pre><code class="language-python">from rest_framework import status, viewsets, mixins 
from rest_framework.response import Response 
from django.views import View 
from django.http import Http404

from .models import Music 
from .serializers import MusicSerializer

# Create your views here. 

class MusicViewSet(viewsets.GenericViewSet, 
                mixins.ListModelMixin, 
                View): 

    serializer_class = MusicSerializer   # 이 클래스형 view 에서 사용할 시리얼라이저를 선언

    def get_queryset(self):
        conditions = {
            &#39;id&#39;: self.kwargs.get(&quot;music_num&quot;, None),
            &#39;title__contains&#39;: self.request.GET.get(&#39;title&#39;, None),
            &#39;star_rating&#39;: self.request.GET.get(&#39;star_rating&#39;, None),
            &#39;singer__contains&#39;: self.request.GET.get(&#39;singer&#39;, None),
            &#39;category__in&#39;: [self.request.GET.get(&#39;category_&#39;+str(i+1), None) for i in range(4)],
            &#39;created_at__lte&#39;: self.request.GET.get(&#39;created_at&#39;, None),
        }
        conditions = {key: val for key, val in conditions.items() if val is not None}

        musics = Music.objects.filter(**conditions)
        if not musics.exists():
            raise Http404()

        return musics

    def add(self, request): 
        musics = Music.objects.filter(**request.data)
        if musics.exists():
            return Response(status=status.HTTP_406_NOT_ACCEPTABLE)

        music_serializer = MusicSerializer(data=request.data, partial=True)
        if not music_serializer.is_valid():
            return Response(status=status.HTTP_406_NOT_ACCEPTABLE)

        music = music_serializer.save()

        return Response(MusicSerializer(music).data, status=status.HTTP_201_CREATED)
</code></pre>
<br>


<p>시리얼라이저 작성</p>
<pre><code class="language-python">from .models import *
from rest_framework import serializers

class MusicSerializer(serializers.ModelSerializer):
    class Meta:
        music = Music.objects.all()
        model = Music
        fields = &#39;__all__&#39;  # __all__ 을 줄 경우, 모든 필드가 사용됨.
        # fields = (&#39;id&#39;, &#39;created_at&#39;, &#39;title&#39;, &#39;category&#39;, &#39;star_rating&#39;,)  # req, res 시 사용되길 원하는 필드(컬럼)만 적어줘도 됨.</code></pre>
<br>


<p>대략적인 구성은 이걸로 끝이다. 이제 서버를 켜서 실제 Swagger 문서가 잘 나오는지 확인해보자. 로컬에서 django의 기본 포트로 구동할 경우 url은 <a href="http://127.0.0.1:8000/swagger">http://127.0.0.1:8000/swagger</a> 이다.
<img src="https://images.velog.io/images/lu_at_log/post/44448cf9-7d84-42fc-ab56-4b1a9213ab41/1.jpg" alt="">
<br><br></p>
<h2 id="추가사항">추가사항</h2>
<hr>
<p>대부분의 API는 request를 받을 때 데이터를 요구한다. drf-yasg는 API 요청 시에 필요한 데이터를 Swagger 문서에서 보여주고, 사용하게 해주는 기능을 제공한다. 해당 기능은 라이브러리에서 제공하는 데코레이터에 시리얼라이저만 넘겨주면 구현된다. 정말 간단하다.</p>
<p>여기서는 POST 시의 body, GET 시의 Query String, 그리고 Header 를 설정하는 방법을 설명한다. 앞선 샘플 프로젝트에 이어서 구현한다.
<br><br></p>
<h3 id="post-메소드의-body-추가">POST 메소드의 Body 추가</h3>
<p>Swagger는 POST API에 body를 제공한다. 그리고 body에는 해당 API의 시리얼라이저에 선언된 필드가 나온다. 문제는 response에는 필요할지 몰라도, request 시에는 필요하지 않은 것도 포함되어 있다는 것이다.</p>
<p>예를 들어, 앞서 구현한 POST 메소드에서 body의 deleted_at은 response 때는 보여주고 싶어도 request 때는 받고 싶지 않다.
<img src="https://images.velog.io/images/lu_at_log/post/05ebafa8-6cbe-4b9d-9e8f-6b18c761ec2d/2.jpg" alt="필요없는 deleted_at을 없애고 싶다..."></p>
<p>deleted_at을 없애기 위한 커스텀 body를 만들어보자.<br><br><br></p>
<p>POST를 담당하는 메소드에 @swagger_auto_schema 데코레이터를 추가한다.
이 데코레이터는 파라미터로 request_body를 받는데, 해당 파라미터 값으로는 <strong>시리얼라이저 타입</strong>만 와야 한다. (혹은 Schema도 가능하다는데, 이쪽은 잘 모르겠다.)</p>
<pre><code class="language-python"># /api/views.py
# ---------------------------------------------------------------------

# ...

from drf_yasg.utils import swagger_auto_schema    # 임포트
from .serializers import MusicSerializer, MusicBodySerializer  # body를 위한 시리얼라이저 임포트. 이후에 만들거임.

# ...

class MusicViewSet(viewsets.GenericViewSet, 
                mixins.ListModelMixin, 
                View):

    serializer_class = MusicSerializer   # 원래 선언해뒀던 이 시리얼라이저 클래스와는 별개로 어노테이션 안에 request_body를 위한 시리얼라이저를 따로 추가함.

    # ...

    @swagger_auto_schema(request_body=MusicBodySerializer)  # request_body에 별도의 시리얼라이저 추가
    def add(self, request): 
        # ...
</code></pre>
<p>이렇게 할 경우, 기존에 선언해두었던 시리얼라이저는 response만 담당하고, request는 새로 추가해준 시리얼라이저가 맡게 된다.
<br></p>
<p>시리얼라이저를 작성하자. 시리얼라이저 작성 시 <strong>serializers.Serializer를 상속 받아서 구현</strong>하는 점에 주의한다.</p>
<pre><code class="language-python"># /api/serializers.py
# ---------------------------------------------------------------------

# ...

class MusicBodySerializer(serializers.Serializer):
    singer = serializers.CharField(help_text=&quot;가수명&quot;)
    title = serializers.CharField(help_text=&quot;곡 제목&quot;)
    category = serializers.ChoiceField(help_text=&quot;곡 범주&quot;, choices=(&#39;JPOP&#39;, &#39;POP&#39;, &#39;CLASSIC&#39;, &#39;ETC&#39;))
    star_rating = serializers.IntegerField(help_text=&quot;1~3 이내 지정 가능. 숫자가 클수록 좋아하는 곡&quot;)
</code></pre>
<br>

<p>이걸로 구현은 끝났다. 서버를 켜서 확인해보면, request body만 바뀌어서 나오는 걸 확인할 수 있다.
<img src="https://images.velog.io/images/lu_at_log/post/0f3ef82d-78fb-4d03-b8ce-7a6a902d105c/3.jpg" alt="try out 버튼 누르기 전"><img src="https://images.velog.io/images/lu_at_log/post/9c14f6eb-aaf8-46f0-9de8-62c582e05886/4.jpg" alt="try out 버튼 누른 후">
<br><br></p>
<h3 id="post-메소드의-body-삭제">POST 메소드의 Body 삭제</h3>
<p>body가 필요없는 경우엔 body 항목 자체가 뜨지 않도록 설정할 수 있다. 이것도 데코레이터를 사용한다.</p>
<pre><code class="language-python"># /api/views.py 
# ---------------------------------------------------------------------

from drf_yasg.utils import swagger_auto_schema, no_body   # no_body 를 import

# ...

class MusicViewSet(viewsets.GenericViewSet, 
                mixins.ListModelMixin, 
                View): 
    # ... 

    # 테스트 용도로 다른 POST 메소드를 만듦. 
    @swagger_auto_schema(request_body=no_body)  # request_body에 no_body를 넣어줌.
    def add_for_no_body(self, request):
        return Response(status=status.HTTP_201_CREATED)</code></pre>
<br>

<p>확인해보면, body가 나오지 않는다.
<img src="https://images.velog.io/images/lu_at_log/post/59eab30f-9528-4318-ad91-40684046c685/5.jpg" alt="">
<br><br></p>
<h3 id="header-추가하기">header 추가하기</h3>
<p>header는 데코레이터에 manual_parameters 인자를 이용하여 구현한다. 해당 인자는 list만 받는다. list 값으로 openapi.Parameter() 를 넣어주면 된다.</p>
<pre><code class="language-python"># /api/views.py 
# --------------------------------------------------------------------- 

from drf_yasg.utils import swagger_auto_schema    # import 잊지 말자

# ... 

class MusicViewSet(viewsets.GenericViewSet, 
                mixins.ListModelMixin, 
                View): 
    # ... 

    @swagger_auto_schema(
        request_body=MusicBodySerializer,
        manual_parameters=[openapi.Parameter(&#39;header_test&#39;, openapi.IN_HEADER, description=&quot;a header for  test&quot;, type=openapi.TYPE_STRING)]
    )
    def add(self, request): 

        # ...
</code></pre>
<p>Parameter()는 name, _in 인자는 필수값이다.(위의 예제에서 첫 번째, 두 번째 인자이다.)
name은 api 파라미터의 이름이며 문서에 표시된다.
_in에는 파라미터의 종류를 적어준다. header는 openapi.IN_HEADER 라고 선언하면 된다. 
description은 말그대로 해당 파라미터에 대한 설명을 보여준다.
type은 필수값은 아니지만, _in에 header를 지정했을 경우 반드시 선언해주어야 한다.
<br><br></p>
<p>이걸로 헤더도 완성이다. 문서를 확인해보면, data 밑에 header가 추가된 것이 보인다.
<img src="https://images.velog.io/images/lu_at_log/post/a857e79d-574f-4a14-9e23-a449035f2736/6.jpg" alt="">
<br><br></p>
<h3 id="get의-query-string-추가하기">GET의 Query String 추가하기</h3>
<p>query string도 swagger_auto_schema에 query_serializer 인자를 이용해서 구현한다. 이 인자도 값은 <strong>시리얼라이저 객체</strong> 타입을 받는다.</p>
<pre><code class="language-python"># /api/views.py 
# --------------------------------------------------------------------- 
from drf_yasg.utils import swagger_auto_schema    # import 잊지 말자 

# ... 

class MusicViewSet(viewsets.GenericViewSet, 
                mixins.ListModelMixin, 
                View): 
    serializer_class = MusicSerializer

    # get_queryset에 데코레이터를 붙이면 인식을 못 하기 때문에 list를 상속 받아서 구현했다.
    # get_queryset은 list() 에서 불리는 함수에 불과하다.
    # 실제 response 하는 메소드는 list 이기 때문에 list를 상속받아서 데코레이터를 붙여준다.
    @swagger_auto_schema(query_serializer=MusicQuerySerializer)
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)

    def get_queryset(self):

        # ...
</code></pre>
<br>

<p>시리얼라이저 작성</p>
<pre><code class="language-python"># /api/serializers.py 
# --------------------------------------------------------------------- 

# ...

class MusicQuerySerializer(serializers.Serializer):
    title = serializers.CharField(help_text=&quot;곡 제목으로 검색&quot;, required=False)
    star_rating = serializers.ChoiceField(help_text=&quot;곡 선호도로 검색&quot;, choices=(1, 2, 3), required=False)
    singer = serializers.CharField(help_text=&quot;가수명으로 검색&quot;, required=True)
    category_1 = serializers.ChoiceField(help_text=&quot;카테고리로 검색&quot;, choices=(&#39;JPOP&#39;, &#39;POP&#39;, &#39;CLASSIC&#39;, &#39;ETC&#39;), required=False)
    category_2 = serializers.ChoiceField(help_text=&quot;카테고리로 검색&quot;, choices=(&#39;JPOP&#39;, &#39;POP&#39;, &#39;CLASSIC&#39;, &#39;ETC&#39;), required=False)
    category_3 = serializers.ChoiceField(help_text=&quot;카테고리로 검색&quot;, choices=(&#39;JPOP&#39;, &#39;POP&#39;, &#39;CLASSIC&#39;, &#39;ETC&#39;), required=False)
    category_4 = serializers.ChoiceField(help_text=&quot;카테고리로 검색&quot;, choices=(&#39;JPOP&#39;, &#39;POP&#39;, &#39;CLASSIC&#39;, &#39;ETC&#39;), required=False)
    created_at = serializers.DateTimeField(help_text=&quot;입력한 날짜를 기준으로 그 이전에 추가된 곡들을 검색&quot;, required=False)

</code></pre>
<br>

<p>위에서 ChoiceField를 사용했는데, 인자인 choices에 리스트나 튜플을 넘겨주면 Swagger 문서 내에 셀렉트 박스가 생성된다. 참고로 1개 값만 주고 싶을 경우, 그냥 string으로 넘겨줘도 문제는 없었다.</p>
<p>required 인자는 Swagger 문서에서 필수값 여부를 표시해준다. 필드에 명시하지 않으면 디폴트 값인 True가 적용된다. 그러면 문서에 빨간색 글자로 reqired가 표시되고, 문서에서 해당 필드에 값을 넣어야만 api 요청이 가능해진다. 이 인자는 모든 필드에서 선언할 수 있다.
<img src="https://images.velog.io/images/lu_at_log/post/30046972-82a0-4e5a-8170-e3e5aa547717/7.jpg" alt="">
<br><br></p>
<h2 id="마치며">마치며</h2>
<hr>
<p>데코레이터를 이용하는 방법 이외에도 SwaggerAutoSchema 를 상속 받아서 직접 Scheam를 구현할수도 있다. 아무래도 API가 많아지다보면, 데코레이터가 덕지덕지 붙어 있는 것이 지저분해 보인다. 이 방법을 이용할 경우 데코레이터를 붙이지 않아 좀 더 깔끔하게 보이게 만들 수 있을 것이다. 다만 drf-yasg 라이브러리를 심도있게 파야 한다는 큰 문제가...</p>
]]></description>
        </item>
    </channel>
</rss>