본문 바로가기
django

[django]drf-yasg로 swagger 적용하기

by 전봇대파괴자 2022. 2. 13.

다음은 django로 어플리케이션을 생성할 때 swagger를 적용, 활용하는 방법을 정리한 문서입니다. 

swagger는 함께 협업하는 개발자들을 위해 최대한 구체적인 설명을 적고, 보기 쉽게 만드는 게 좋습니다. 보통 swagger는 개발/테스트 서버에서는 배포를 진행하나, 실서버에서는 보안 문제로 배포를 진행하지 않습니다.

 

0. 준비

먼저 django와 django를 설치할 가상환경을 구성해야 합니다. 가상환경은 conda, virtualenv 등 여러 개의 옵션을 선택할 수 있지만, 여기서는 python 3.4 이상에 기본적으로 제공되는 pyenv를 사용하겠습니다.

 

가상환경 구성

$ python -m venv env

 

django 설치

django의 버전은 어떤 버전을 선택하든 큰 상관은 없습니다. 본인의 기준이나 프로젝트에서 합의된 버전을 설치하면 됩니다.

pip install django

 

django project 생성

swagger를 적용하기 위해서는 사전에 swagger를 적용할 프로젝트를 생성해야 합니다. 프로젝트를 생성하기 전 앞에서 구성해두었던 가상환경을 활성화시킵니다.

source env/bin/activate # 가상환경 활성화
django-admin startproject "프로젝트 이름" # 프로젝트 생성
cd "프로젝트 이름" # 문서로 이동
django-admin startapp "app 이름" # 앱 생성

 

DB 만들기

그 다음은 프로젝트와 연동할 데이터베이스를 선택하고, 생성합니다. django에서는 기본적으로 sqlite3을 제공합니다. 하지만 본인의 필요에 따라 MySQL DB, postgres DB 등을 선택할 수 있습니다. 어떤 DB를 선택하느냐에 따라 연동하는 방법도 달라지기 때문에 여기서는 이 정도만 설명하겠습니다.

 

 

1. swagger 적용

여기서는 drf-yasg를 사용해 swagger를 적용하겠습니다. 이하의 내용은 공식 문서를 참고했습니다.

drf-yasg install

pip install -U drf-yasg

 

settings.py 수정

INSTALL_APPS=[
...
'django.contrib.staticfiles',
'drf_yasg'
...
]

 

urls.py 수정

...
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="Snippets API",
      default_version='v1',
      description="Test description",
      terms_of_service="https://www.google.com/policies/terms/",
      contact=openapi.Contact(email="contact@snippets.local"),
      license=openapi.License(name="BSD License"),
   ),
   public=True,
   permission_classes=(permissions.AllowAny,),
)

urlpatterns = [
   url(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
   url(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
   url(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
   ...
]
  • urlpatterns를 바꿔줌으로써 swagger의 URL을 변경할 수 있습니다.
  • get_schema_view의 파라미터를 재설정하여 기본 swagger 문서의 내용을 변경할 수 있습니다. 문서에는 사용자가 views.py에서 생성해 놓은 api들이 나열됩니다. api에 대한 설명을 더 덧붙이고 싶거나 수정하고 싶다면 method_decorator를 활용해 커스텀할 수 있습니다.

 

2. swagger custom

swagger 문서를 가독성 좋게 커스텀하는 과정을 알아봅니다.

  • django 프로젝트에서 swagger 문서를 커스텀하는 데 가장 기본적인 라이브러리는 method_decorator입니다.
from django.utils.decorators import method_decorator # 라이브러리 불러오기

method_decorator 사용하기

GET

@method_decorator(name='list', # GET API
    decorator=swagger_auto_schema(
        manual_parameters=[openapi.Parameter('query/path 이름', in_=openapi.IN_QUERY/IN_PATH, description='query/path에 대한 설명', type=openapi.TYPE_STRING)],
        tags=['API에 지정할 태그'],
        operation_summary="API 표시줄에 들어갈 간략한 설명을 적습니다."
        operation_description="API에 대한 보다 자세한 설명을 넣습니다.",
        responses={
            200: openapi.Response('응답에 대한 설명', mySerializer), # API serializer
            401: 'Authentication Failed(40100)', # 이하 error 처리
            403: 'Permission denied(403)',
            404: 'Not found(404)'
        }
    )
)

 

POST

@method_decorator(name='list', # GET API
    decorator=swagger_auto_schema(
        tags=['API에 지정할 태그'],
        operation_summary="API 표시줄에 들어갈 간략한 설명을 적습니다."
        operation_description="API에 대한 보다 자세한 설명을 넣습니다.",
        request_body=openapi.Schema(
        	type=openapi.TYPE_OBJECT,
            properties={
                'request 필수값 1': openapi.Schema(type=openapi.TYPE_STRING, description='값에 대한 설명'),
                'request 필수값 2': openapi.Schema(type=openapi.TYPE_INTEGER, description='값에 대한 설명')
            }
        )
        responses={
            200: openapi.Response('응답에 대한 설명', mySerializer), # API serializer
            401: 'Authentication Failed(40100)', # 이하 error 처리
            403: 'Permission denied(403)',
            404: 'Not found(404)'
        }
    )
)

 

3. 배포 시 swagger 적용하기

django project 개발 서버 배포 시 swagger를 적용할 수 있는 방법 및 이슈를 기록한 부분입니다.

 

필요한 값들만 입력하도록 입력 parameter 수정하기

POST 요청 시 꼭 필요한 값들이 있고, 그렇지 않은 값들도 있습니다. 하지만 초기에는 모든 값을 입력하도록 raw data가 swagger 문서에 떠 있는 것을 볼 수 있습니다. method_decorator를 활용하여 이 중 굳이 입력할 필요가 없는 값들을 지우고, 입력이 필요한 값들만 남깁니다.

from django.utils.decorators import method_decorator

@method_decorator(name='create',
                 decorator=swagger_auto_schema(
                 request_body=openapi.Schema(
                 type=openapi.TYPE_OBJECT,
                 properties={
                     '필요한 입력값 1': openapi.Schema(type=openapi.TYPE_STRING, description='입력값에 대한 설명'),
                     '필요한 입력값 2': openapi.Schema(type=openapi.TYPE_STRING, description='입력값에 대한 설명'),
                     ...
                 })))

 

특정한 조건으로 데이터 필터링하기, API 함수 만들기

django filter

django_filters를 통해 기존의 데이터베이스에 있는 정보들 중 특정한 조건에 맞는 데이터만을 가져올 수 있도록 합니다.

  • django-filter 설치
$ pip install django-filter
  • settings.py 수정
...
INSTALLED_APPS=[
    'django.contrib.admin',
    'django.contrib.auth',
    ...
    'django_filters',
]
...
  • views.py
...
from django_filters.rest_framework import DjangoFilterBackend
...

class MyViewSet(viewsets.GenericViewSet, mixins.ListModelMixin):
    queryset = '데이터를 가져올 table 이름'.objects.all()
    serializer_class = MySerializer
    permission_class = []
    filter_backends = (DjangoFilterBackend,)
    filterset_fields = ('필터링하고 싶은 column명')

위와 같이 작성해주기만 해도 GET api를 생성했을 때 원하는 대로 필터링된 데이터를 받을 수 있습니다. 하지만 여기서 한 발 더 나아가, 저렇게 필터링한 데이터를 가져와 그 안에서 다시 조건을 걸어 한 번 더 데이터를 거르고 싶을 때가 있습니다. 그 때는 아래와 같은 코드를 사용합니다.

...
filterset_fields = ('필터링하고 싶은 column명')

def list(self, request, *args, **kwargs):
    filtered_queryset = self.filter_queryset(self.get_queryset()) # 한 번 필터링된 데이터
    '''
    위의 데이터를 다시 필터링할 조건 적기
    '''

query parameter

특정 값만 url에 인자로 포함시키고, 해당 값만 가져와 데이터 필터링에 사용할 수 있습니다.

  • urls.py
...
urlpatterns = [
    path('myproject/<str:column 명>'),
    path('myproject/<int:column 명'),
    ...
]
...

models.py에서 설정한 타입에 맞게 url을 설정하여 위처럼 넣어주면, swagger에서 query parameter로 해당 값들을 입력하고 views.py에서 request와 함께 사용할 수 있습니다. 이 방법 외에도 아래와 같이 사용할 수 있습니다.

from django.urls import paht, include
from rest_framework.routers import DefaultRouter

...
router = DefaultRouter()
router.register('api/<column 명>', views.MyApiViewSet)
router.register('api2/<column 명>/<column 명>', views.MyApiViewSet) # 여러 개의 query parameter도 줄 수 있다!
...

위와 같이 query parameter를 지정할 경우 swagger에서 해당 파라미터가 필수값(required)로 표시됩니다. 위에서 filterset_fields를 사용해 query parameter를 줄 경우, 해당 파라미터는 필수값으로 표시되지 않습니다.

  • views.py
...
class MyFirstViewSet(viewsets.GenericViewSet, mixins.ListModelMixin):
    queryset = MyModel.objects.all()
    serializer_class = MySerializer
    permission_classes = [] # 로그인 설정 안할 경우
    
    def list(self, request, column 명):
    # do something...
...

위와 같이 쓰는 것도 가능하지만 아래와 같이 쓸 수도 있습니다.

...
class MyFirstViewSet(viewsets.GenericViewSet, mixins.ListModelMixin):
    queryset = MyModel.objects.all()
    serializer_class = MySerializer
    permission_classes = [] # 로그인 설정 안할 경우
    
    def list(self, request):
        query_param = request.GET['column 명'] # query parameter 얻기
    # do something...
...