Django REST Framework/Django Third Party Lib for REST

[DRF] drf-spectacular API 문서 자동화 라이브러리 정리

bluebamus 2024. 3. 17. 23:34

 1. drf-spectacular 설치 방법

$ pip install drf-spectacular  # pip을 사용하면 이렇게 설치
$ poetry add drf-spectacular  # poetry를 사용하면 이렇게 설치

 

 2. settings.py 설정

https://drf-spectacular.readthedocs.io/en/latest/settings.html#settings

 

Settings — drf-spectacular documentation

© Copyright 2020, T. Franzel. Revision 4d6c93f0.

drf-spectacular.readthedocs.io

   - 기본 설정

# settings.py

INSTALLED_APPS = [
    # ...
    'rest_framework',
    'drf_spectacular',
    # ...
]

REST_FRAMEWORK = {
    # YOUR SETTINGS  drf의 schema 클래스를 drf-specacular의 AutoSchema로 교체해줍니다.
    'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}

 

   - 타이틀, 버전 및 설몆 설정

SPECTACULAR_SETTINGS = {
    'TITLE': 'My API Service',
    'DESCRIPTION': 'A detailed description of my API.',
    'VERSION': '1.0.0',
}

 

   - 스키마 노출 설정 

      - OAS3 Meta정보 API를 비노출 처리한다.

SPECTACULAR_SETTINGS = {
    'SERVE_INCLUDE_SCHEMA': False,
}

 

   - 웹 인터페이스에서 파일 업로드 기능 제공 설정

SPECTACULAR_SETTINGS = {
    'COMPONENT_SPLIT_REQUEST': True,
}

 

   - API 문서에 표시될 연락처 정보를 설정

      - 일반적으로 개발자나 팀의 연락처 정보를 입력한다.

SPECTACULAR_SETTINGS = {
    'CONTACT': {
        'name': 'John Doe',
        'email': 'johndoe@example.com',
        'url': 'https://www.example.com',
    }
}

 

   - Swagger UI에 대한 설정을 관리하는 옵션

      - 이 옵션을 통해 Swagger UI의 테마, URL 등을 설정할 수 있다.

      - SWAGGER_UI_SETTINGS 하위 옵션들은 Swagger-UI(JavaScript)에 반영되는 옵션들이기 때문에 카멜표기법에 기반하여 작성하여야 한다.

SPECTACULAR_SETTINGS = {
    'SWAGGER_UI_SETTINGS': {
        'deepLinking': True,
        'theme': 'dark',
        'validatorUrl': None,
    }
}

# https://pypi.org/project/form-schema-generator/ 에서 참조함
'SWAGGER_UI_SETTINGS': {
        # https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/  <- 여기 들어가면 어떤 옵션들이 더 있는지 알수있습니다.
        'dom_id': '#swagger-ui',  # required(default)
        'layout': 'BaseLayout',  # required(default)
        'deepLinking': True,  # API를 클릭할때 마다 SwaggerUI의 url이 변경됩니다. (특정 API url 공유시 유용하기때문에 True설정을 사용합니다)
        'persistAuthorization': True,  # True 이면 SwaggerUI상 Authorize에 입력된 정보가 새로고침을 하더라도 초기화되지 않습니다.
        'displayOperationId': True,  # True이면 API의 urlId 값을 노출합니다. 대체로 DRF api name둘과 일치하기때문에 api를 찾을때 유용합니다.
        'filter': True,  # True 이면 Swagger UI에서 'Filter by Tag' 검색이 가능합니다
    },

 

   - API 문서에 표시될 라이선스 정보를 설정

SPECTACULAR_SETTINGS = {
    'LICENSE': {
        'name': 'MIT License',
        'url': 'https://opensource.org/licenses/MIT',
        # github의 LICENSE 주소도 가능
    }
}

 

   - Swagger UI의 정적 파일 경로를 설정하는 옵션

      - Swagger UI의 정적 파일을 원하는 경로에 저장하거나 다른 서버에서 호스팅할 수 있다.

SPECTACULAR_SETTINGS = {
    'SWAGGER_UI_DIST': '/static/swagger-ui/',
}

# https://pypi.org/project/form-schema-generator/ 에서 참조

SPECTACULAR_SETTINGS = {
# https://www.npmjs.com/package/swagger-ui-dist 해당 링크에서 최신버전을 확인후 취향에 따라 version을 수정해서 사용하세요.
    'SWAGGER_UI_DIST': '//unpkg.com/swagger-ui-dist@3.38.0',
}

SPECTACULAR_SETTINGS = {
     # .....
  
     # drf-spectacular 라이브러리 version up이 없이도 자신의 원하는 swagger-ui의 version을 사용할수있다.
     # swagger-ui version 정보는 여기서 확인 https://www.npmjs.com/package/swagger-ui
    'SWAGGER_UI_DIST': '//unpkg.com/swagger-ui-dist@3.44.0', 
    'SWAGGER_UI_FAVICON_HREF': '//unpkg.com/swagger-ui-dist@3.44.0/favicon-32x32.png',
    
     # ....
}

 

 3. urls.py 설정

   - 예전 자료를 보면 json, yaml과 관련된 path를 정의해 줘야 했다. 하지만, 최신 버전에서는 SpectacularAPIView만 정의해주면 된다.

# 이전 방식
from drf_spectacular.views import SpectacularJSONAPIView
from drf_spectacular.views import SpectacularYAMLAPIView

urlpatterns = [ 
    path("docs/json/", SpectacularJSONAPIView.as_view(), name="schema-json"),
    path("docs/yaml/", SpectacularYAMLAPIView.as_view(), name="swagger-yaml"),
    ...
    ]
    
# 최신 방식 
from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView

urlpatterns = [
    # YOUR PATTERNS
    path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
    # Optional UI:
    path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
    path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
]

 

 4. 기본 사용 방법 

   1) @extend_schema

      - 메소드 단위의 데코레이터 , 하나의 메소드(path)에 해당하는 문서화를 커스터마이징 할때 사용하고, 가장 핵심이 되는 데코레이터이다.

      - 사용 가능한 파라메터는 아래 함수 정의를 참고할 수 있다.

def extend_schema(
        operation_id: Optional[str] = None,
        parameters: Optional[Sequence[Union[OpenApiParameter, _SerializerType]]] = None,
        request: Any = empty,
        responses: Any = empty,
        auth: Optional[Sequence[str]] = None,
        description: Optional[_StrOrPromise] = None,
        summary: Optional[_StrOrPromise] = None,
        deprecated: Optional[bool] = None,
        tags: Optional[Sequence[str]] = None,
        filters: Optional[bool] = None,
        exclude: Optional[bool] = None,
        operation: Optional[_SchemaType] = None,
        methods: Optional[Sequence[str]] = None,
        versions: Optional[Sequence[str]] = None,
        examples: Optional[Sequence[OpenApiExample]] = None,
        extensions: Optional[Dict[str, Any]] = None,
        callbacks: Optional[Sequence[OpenApiCallback]] = None,
        external_docs: Optional[Union[Dict[str, str], str]] = None,
) -> Callable[[F], F]:
    """
    Decorator mainly for the "view" method kind. Partially or completely overrides
    what would be otherwise generated by drf-spectacular.

    :param operation_id: replaces the auto-generated operation_id. make sure there
        are no naming collisions.
    :param parameters: list of additional or replacement parameters added to the
        auto-discovered fields.
    :param responses: replaces the discovered Serializer. Takes a variety of
        inputs that can be used individually or combined

        - ``Serializer`` class
        - ``Serializer`` instance (e.g. ``Serializer(many=True)`` for listings)
        - basic types or instances of ``OpenApiTypes``
        - :class:`.OpenApiResponse` for bundling any of the other choices together with
          either a dedicated response description and/or examples.
        - :class:`.PolymorphicProxySerializer` for signaling that
          the operation may yield data from different serializers depending
          on the circumstances.
        - ``dict`` with status codes as keys and one of the above as values.
          Additionally in this case, it is also possible to provide a raw schema dict
          as value.
        - ``dict`` with tuples (status_code, media_type) as keys and one of the above
          as values. Additionally in this case, it is also possible to provide a raw
          schema dict as value.
    :param request: replaces the discovered ``Serializer``. Takes a variety of inputs

        - ``Serializer`` class/instance
        - basic types or instances of ``OpenApiTypes``
        - :class:`.PolymorphicProxySerializer` for signaling that the operation
          accepts a set of different types of objects.
        - ``dict`` with media_type as keys and one of the above as values. Additionally, in
          this case, it is also possible to provide a raw schema dict as value.
    :param auth: replace discovered auth with explicit list of auth methods
    :param description: replaces discovered doc strings
    :param summary: an optional short summary of the description
    :param deprecated: mark operation as deprecated
    :param tags: override default list of tags
    :param filters: ignore list detection and forcefully enable/disable filter discovery
    :param exclude: set True to exclude operation from schema
    :param operation: manually override what auto-discovery would generate. you must
        provide a OpenAPI3-compliant dictionary that gets directly translated to YAML.
    :param methods: scope extend_schema to specific methods. matches all by default.
    :param versions: scope extend_schema to specific API version. matches all by default.
    :param examples: attach request/response examples to the operation
    :param extensions: specification extensions, e.g. ``x-badges``, ``x-code-samples``, etc.
    :param callbacks: associate callbacks with this endpoint
    :param external_docs: Link external documentation. Provide a dict with an "url" key and
        optionally a "description" key. For convenience, if only a string is given it is
        treated as the URL.
    :return:
    """
    
    # operation_id : 자동으로 설정되는 id 값, 대체로 수동할당하여 쓰진 않음
    # parameters : 해당 path로 받기로 예상된 파라미터 값 (Serializer or OpenApiParameter 사용)
    # request : 요청시 전달될 content의 형태
    # responses : 응답시 전달될 content의 형태
    # auth : 해당 method에 접근하기 위한 인증방법
    # description: 해당 method 설명
    # summary : 해당 method 요약
    # deprecated : 해당 method 사용여부 
    # tags : 문서상 보여줄 묶음의 단위
    # exclude : 문서에서 제외여부  
    # operation : ??? json -> yaml 하기위한 dictionary??? 
    # methods : 요청 받을 Http method 목록
    # versions : 문서화 할때 사용할 openAPI 버전
    # examples : 요청/응답에 대한 예시
    # ...methods...

 

      -  사용 예시 :

from django.http import JsonResponse
from rest_framework.decorators import api_view
from drf_spectacular.utils import extend_schema
from .serializers import ExampleModelSerializer

# 함수형 뷰
@extend_schema(
    methods=['GET'], # 이 데코레이터를 적용할 HTTP 메서드
    summary='ExampleModel 리스트 조회', # API 요약 설명
    description='ExampleModel의 리스트를 조회하는 API입니다.', # API 상세 설명
    responses={200: ExampleModelSerializer(many=True)}, # 응답 스키마
)
@api_view(['GET'])
def example_model_list(request):
    if request.method == 'GET':
        # 데이터 조회 로직
        data = {'message': 'ExampleModel 리스트 조회 성공'}
        return JsonResponse(data, safe=False)

@extend_schema(
    methods=['POST'], # 이 데코레이터를 적용할 HTTP 메서드
    summary='ExampleModel 생성', # API 요약 설명
    description='새로운 ExampleModel을 생성하는 API입니다.', # API 상세 설명
    request=ExampleModelSerializer, # 요청 스키마
    responses={201: ExampleModelSerializer}, # 응답 스키마
)
@api_view(['POST'])
def example_model_create(request):
    if request.method == 'POST':
        # 데이터 생성 로직
        data = {'message': 'ExampleModel 생성 성공'}
        return JsonResponse(data, safe=False, status=201)

 

   2) @extend_schema_view

      - 하나의 ViewSet에 속한 method들의 문서화를 커스터마이징 할 때 사용할 수 있고 APIView, ViewSet, 그리고 일반 View 클래스 등 다양한 종류의 뷰에서 사용이 가능하다.

      - 만약, @extend_schema 와 동시에 쓰인다면 @extend_schema가 우선순위를 가진다.

      - view_name은 기본적으로 list,retrieve,create,update,delete가 있고, @action을 통해 만든 커스텀 메소드도 메소드 명을 사용해 커스텀 할 수 있다.

def extend_schema_view(**kwargs) -> Callable[[F], F]:
    """
    Convenience decorator for the "view" kind. Intended for annotating derived view methods that
    are are not directly present in the view (usually methods like ``list`` or ``retrieve``).
    Spares you from overriding methods like ``list``, only to perform a super call in the body
    so that you have have something to attach :func:`@extend_schema <.extend_schema>` to.

    This decorator also takes care of safely attaching annotations to derived view methods,
    preventing leakage into unrelated views.

    :param kwargs: method names as argument names and :func:`@extend_schema <.extend_schema>`
      calls as values
    """

 

      - APIView 클래스에서 extend_schema_view 적용

from django.http import JsonResponse
from rest_framework.views import APIView
from drf_spectacular.utils import extend_schema, extend_schema_view
from .serializers import ExampleModelSerializer

@extend_schema_view(
    get=extend_schema(
        summary='ExampleModel 조회',
        description='ExampleModel의 세부 정보를 조회하는 API입니다.',
        responses={200: ExampleModelSerializer},
    ),
    post=extend_schema(
        summary='ExampleModel 생성',
        description='새로운 ExampleModel을 생성하는 API입니다.',
        request=ExampleModelSerializer,
        responses={201: ExampleModelSerializer},
    )
)
class ExampleModelAPIView(APIView):
    def get(self, request, *args, **kwargs):
        # 데이터 조회 로직
        return JsonResponse({'message': '조회 성공'}, status=200)
    
    def post(self, request, *args, **kwargs):
        # 데이터 생성 로직
        return JsonResponse({'message': '생성 성공'}, status=201)

 

      - Mixin과 Generic Views에 extend_schema_view 적용

from rest_framework import mixins, viewsets
from rest_framework.generics import GenericAPIView
from .models import ExampleModel
from .serializers import ExampleModelSerializer
from drf_spectacular.utils import extend_schema_view, extend_schema

# 리스트 생성 및 조회 API
@extend_schema_view(
    get=extend_schema(description="ExampleModel 리스트를 조회합니다."),
    post=extend_schema(description="새로운 ExampleModel을 생성합니다.")
)
class ExampleModelList(mixins.ListModelMixin,
                       mixins.CreateModelMixin,
                       GenericAPIView):
    queryset = ExampleModel.objects.all()
    serializer_class = ExampleModelSerializer

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)
    
    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

# 단일 객체 조회, 업데이트 및 삭제 API
@extend_schema_view(
    get=extend_schema(description="ExampleModel의 상세 정보를 조회합니다."),
    put=extend_schema(description="ExampleModel을 업데이트합니다."),
    delete=extend_schema(description="ExampleModel을 삭제합니다.")
)
class ExampleModelDetail(mixins.RetrieveModelMixin,
                         mixins.UpdateModelMixin,
                         mixins.DestroyModelMixin,
                         GenericAPIView):
    queryset = ExampleModel.objects.all()
    serializer_class = ExampleModelSerializer

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)
    
    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)
    
    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

 

      - ListCreateAPIView, RetrieveUpdateDestroyAPIView에 extend_schema_view 적용

from rest_framework.generics import (CreateAPIView, ListAPIView, 
                                     RetrieveAPIView, ListCreateAPIView, 
                                     RetrieveUpdateDestroyAPIView)
from .models import YourModel
from .serializers import YourModelSerializer
from drf_spectacular.utils import extend_schema_view, extend_schema

@extend_schema_view(
    post=extend_schema(description="새로운 YourModel 인스턴스를 생성합니다."),
    get=extend_schema(description="YourModel 인스턴스 목록을 가져옵니다.")
)
class YourModelListCreateAPIView(ListCreateAPIView):
    queryset = YourModel.objects.all()
    serializer_class = YourModelSerializer

@extend_schema_view(
    get=extend_schema(description="YourModel 인스턴스의 상세 정보를 가져옵니다."),
    put=extend_schema(description="YourModel 인스턴스를 업데이트합니다."),
    delete=extend_schema(description="YourModel 인스턴스를 삭제합니다.")
)
class YourModelRetrieveUpdateDestroyAPIView(RetrieveUpdateDestroyAPIView):
    queryset = YourModel.objects.all()
    serializer_class = YourModelSerializer

 

      - ViewSet에 extend_schema_view 적용

from rest_framework import viewsets, mixins
from drf_spectacular.utils import extend_schema_view, extend_schema
from .models import User
from .serializers import UserSerializer

@extend_schema_view(
    list=extend_schema(description="사용자 목록을 조회합니다."),
    retrieve=extend_schema(description="특정 사용자의 상세 정보를 조회합니다.")
)
class UserViewSet(viewsets.GenericViewSet, mixins.ListModelMixin, mixins.RetrieveModelMixin):
    queryset = User.objects.all()
    serializer_class = UserSerializer
from rest_framework.viewsets import ModelViewSet
from drf_spectacular.utils import extend_schema_view, extend_schema
from .models import ExampleModel
from .serializers import ExampleModelSerializer

# extend_schema_view를 사용해 개별 액션에 대한 스키마 정보 추가
@extend_schema_view(
    list=extend_schema(description="모든 예시 항목의 리스트를 반환합니다."),
    create=extend_schema(description="새로운 예시 항목을 생성합니다."),
    retrieve=extend_schema(description="단일 예시 항목의 상세 정보를 반환합니다."),
    update=extend_schema(description="기존 예시 항목을 업데이트합니다."),
    partial_update=extend_schema(description="기존 예시 항목의 일부를 업데이트합니다."),
    destroy=extend_schema(description="기존 예시 항목을 삭제합니다.")
)
class ExampleModelViewSet(ModelViewSet):
    queryset = ExampleModel.objects.all()
    serializer_class = ExampleModelSerializer

 

   3) @extend_schema_serializer

      - 직렬화 클래스에 대한 스키마 정보를 명시적으로 정의할 때 사용된다. 주로 스키마에서 특정 필드를 제외하거나, 요청과 응답 스키마에 추가 정보를 부착하는 데 유용하다.

      - 적용 우선순위는 method > viewset > serializer 이다.

def extend_schema_serializer(
        many: Optional[bool] = None,
        exclude_fields: Optional[Sequence[str]] = None,
        deprecate_fields: Optional[Sequence[str]] = None,
        examples: Optional[Sequence[OpenApiExample]] = None,
        extensions: Optional[Dict[str, Any]] = None,
        component_name: Optional[str] = None,
) -> Callable[[F], F]:
    """
    Decorator for the "serializer" kind. Intended for overriding default serializer behaviour that
    cannot be influenced through :func:`@extend_schema <.extend_schema>`.

    :param many: override how serializer is initialized. Mainly used to coerce the list view detection
        heuristic to acknowledge a non-list serializer.
    :param exclude_fields: fields to ignore while processing the serializer. only affects the
        schema. fields will still be exposed through the API.
    :param deprecate_fields: fields to mark as deprecated while processing the serializer.
    :param examples: define example data to serializer.
    :param extensions: specification extensions, e.g. ``x-is-dynamic``, etc.
    :param component_name: override default class name extraction.

 

      - 사용 방법

from drf_spectacular.utils import extend_schema_serializer, OpenApiExample
from rest_framework import serializers

@extend_schema_serializer(
    # 스키마에서 제외하고 싶은 필드 목록
    exclude_fields=('internal_field',),
    # 직렬화 클래스에 대한 추가 설명
    description='이것은 예시 직렬화 클래스입니다.'
)
class ExampleSerializer(serializers.ModelSerializer):
    class Meta:
        model = ExampleModel
        fields = '__all__'
        # 내부 용도로만 사용되는 필드
        internal_field = serializers.CharField()
@extend_schema_serializer(
    exclude_fields=["password"],
    examples=[
        OpenApiExample(
            "Valid example 1",
            summary="short summary",
            description="longer description",
            value={
                "is_superuser": True,
                "username": "string",
                "first_name": "string",
                "last_name": "string",
                "email": "user@example.com",
                "is_staff": False,
                "is_active": True,
                "date_joined": "2021-04-18 04:14:30",
                "user_type": "customer",
            },
            request_only=True,   # view Layer에서 사용한 Example Object와 동일한 동작을 한다.
            response_only=False,
        ),
    ],
)
class UserCustomSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = User
        fields = '__all__'

 

   4) @extend_schema_field

      - SerializerMethodField( 커스텀 필드) 혹은 기본 필드 형식이 명확하게 추론되지 않을 때 정의한 Field의 메타정보를 알 수 없다. 이때에는 extend_schema_field를 사용해 스키마에 사용될 필드의 타입힌트를 직접 정의할 수 있다.

      - 드의 데이터 형식을 직접 지정하거나, 더 복잡한 구조를 가진 커스텀 Serializer를 사용할 때 명확하게 스키마에 정보를 표현할 수 있다.

from rest_framework import serializers
from drf_spectacular.utils import extend_schema_field
from drf_spectacular.types import OpenApiTypes

class UserSerializer(serializers.Serializer):
    name = serializers.CharField(max_length=100)
    # 커스텀 필드에 대해 OpenAPI 타입을 명시적으로 지정
    @extend_schema_field(OpenApiTypes.UUID)
    def get_custom_field(self):
        pass
class CustomUserSerializer(serializers.HyperlinkedModelSerializer):
    field_custom = serializers.SerializerMethodField(method_name="get_field_custom")

    class Meta:
        model = User
        fields = '__all__'

    @extend_schema_field(OpenApiTypes.DATETIME)
    def get_field_custom(self, data):
        return '2021-03-06 20:54:00'

 

   5) 터미널에서 schema.yml 만들기

      - 다른 개발자에게 오프라인으로 api의 정보에 대해 전달하기 위해서 schema.yml을 생성해 보낼 수 있다.

      - 명령어 :

./manage.py spectacular --file schema.yml

 

      - 실행시 아래와 같은 결과를 얻을 수 있다.

 

 5. 상세 사용 방법 

   1) tag, summary, description

      - tags : 

         - swagger에 어떤 부분에 tag 되어있을건지 정한다. 기본적으로 django의 app name에따라 default로 정해진다.

         - API를 그룹핑하는데 사용합니다. 리스트 형태로 여러 태그를 지정할 수 있다.

      - summary :

         - API의 간략한 요약 정보를 제공한다.

      - description :

         - API의 상세 설명을 제공합니다. 여러 줄에 걸쳐 상세 정보를 기술할 수 있다.

class TistoryView(APIView):

    @extend_schema(
        tags=['files'],
        summary="요약줄",
        description="""설명란<br/>
            html 태그를 사용할 수도 있습니다. """,
    )
    def get(self, request):
        ...
from django.urls import path
from drf_spectacular.utils import extend_schema
from rest_framework import serializers, views, viewsets
from rest_framework.response import Response

class IssueSerializer(serializers.Serializer):
    issue_id = serializers.IntegerField()
    description = serializers.CharField()

@extend_schema(tags=['고객 서비스'], summary='이슈 해결 요청', description='고객이 겪고 있는 문제를 해결하기 위한 상세 액션을 처리합니다.')
class IssueResolutionView(viewsets.GenericViewSet):
    serializer_class = IssueSerializer

    @extend_schema(tags=['이슈 해결'], responses={200: IssueSerializer})
    def resolve_issue(self, request, *args, **kwargs):
        # 가상의 로직으로 이슈 해결 프로세스를 표현
        return Response({'issue_id': 123, 'description': '고객 문제 해결 완료'})

urlpatterns = [
    path('resolve/', IssueResolutionView.as_view({'get': 'resolve_issue'})),
]

 

   2) parameters

      - API 엔드포인트의 입력값을 정의할 때 핵심적으로 활용되는 요소이다. 정확히 말하자면, parameters는 API 호출 시 요청에 포함되는 쿼리 파라미터, 헤더, 경로 변수 등을 명시하는 데 사용된다.

      - get, post, put, fetch, delete 등 요청에 요구되는 입력 변수들을 명시할 수 있으며, 부가적인 설명과 필수 요소 등을 설정할 수 있다.

# models.py
from django.db import models


class Issue(models.Model):
    description = models.TextField()
    customer_id = models.CharField(max_length=100)
    issue_type = models.CharField(max_length=100)

    def __str__(self):
        return f"Issue {self.id}: {self.description[:50]}"

# views.py
from rest_framework import serializers, viewsets
from rest_framework.response import Response
from drf_spectacular.utils import extend_schema, OpenApiParameter
from .models import Issue


class IssueSerializer(serializers.ModelSerializer):
    class Meta:
        model = Issue
        fields = ["id", "description", "customer_id", "issue_type"]


@extend_schema(
    tags=["고객 서비스"],
    parameters=[
        OpenApiParameter(
            name="customer_id", description="고객 ID", required=True, type=str
        ),
        OpenApiParameter(
            name="issue_type", description="이슈 타입", required=True, type=str
        ),
    ],
)
class IssueResolutionViewSet(viewsets.ModelViewSet):
    queryset = Issue.objects.all()
    serializer_class = IssueSerializer

 

      - 위와 같은 코드를 실행할 경우, 아래와 같은 화면을 확인할 수 있다.

 

   3) request

      - 해당 api에서 받을 request 데이터를 처리할 serializer를 정할 수 있다.

      - 주로 복잡하거나 커스텀된 직렬화 형태를 가진 API 엔드포인트에서 요구될 수 있다.

      - 그러나 이런 방법은 serialier_class를 사용 할 수 있는 GenericAPIView 이상부터 가능하다. 즉 일반 APIView는 사용할 수 없다.

class TistoryView(GenericAPIView):
    serializer_class = TistorySerializer

    @extend_schema(
        request=TistorySerializer,
    )
    def post(self, request, *args, **kwargs):
        pass

 

      - 하지만, inline_serializer를 사용하면 정의할 수 있다.

      - 예제 1 : 사용자 회원가입 API 사용자가 회원가입할 때, 이름, 이메일, 비밀번호 등을 입력해야 한다. 그러나, 관리자가 직접 사용자를 생성할 경우 추가로 role 필드를 지정할 수 있다.

from drf_spectacular.utils import extend_schema, inline_serializer
from rest_framework import serializers, views

@extend_schema(request=inline_serializer(
    name='SignUpSerializer',
    fields={
        'name': serializers.CharField(),
        'email': serializers.EmailField(),
        'password': serializers.CharField(),
        'role': serializers.ChoiceField(choices=['user', 'admin'], default='user', required=False)
    }
))
class SignUpView(views.APIView):
    pass

 

      - 예제 2 : 장바구니 아이템 추가 API 사용자가 장바구니에 상품을 추가할 때, 상품 ID와 구매 수량을 요청 본문에 포함해야 한다. 여기서는 복수의 상품을 한 번에 추가하는 경우를 고려하여 many=True 옵션을 사용한다.

@extend_schema(request=inline_serializer(
    name='CartItemAddSerializer',
    fields={
        'product_id': serializers.IntegerField(),
        'quantity': serializers.IntegerField()
    },
    many=True  # 여러 개의 상품 입력 허용
))
class CartItemAddView(views.APIView):
    pass

 

   4) responses

      - api의 response를 정할 수 있다. 

      - dictionary형태로 status_code: response 형태로 적용한다.

      - 그러나 중복된 status_code의 여러 가지 형태의 response는 보여줄 수 없다.

      - 특정 HTTP 상태 코드에 대한 응답을 커스텀할 수 있으며, 에러 응답 또는 성공 응답에 대한 세부 정보를 명시한다.

      - responses 옵션의 핵심 파라미터 :

 

         1. 상태 코드

            - Key 값으로 사용 : 응답 옵션에서 상태 코드(예: 200, 404, 400 등)는 사전 키(Dictionary Key)로 사용되어 해당 HTTP 응답 코드에 대응하는 응답의 내용을 정의한다.


         2. 응답 시리얼라이저 또는 스키마

            - 스키마 해석 : 각 상태 코드에 해당하는 응답을 설명하기 위해 사용되는 시리얼라이저 또는 스키마입니다. 이를 통해 응답 데이터의 구조와 예상되는 데이터 유형을 명확히 할 수 있다.


         3. OpenApiResponse

            - 응답 커스텀 : OpenApiResponse 객체를 사용함으로써 응답에 대한 사전 정의된 텍스트 설명 또는 추가 메타데이터를 제공할 수 있다. 이는 API 사용자에게 요청의 결과를 더욱 명확하게 이해시키는 데 도움이 된다.
            - 사용 예 : OpenApiResponse(response=YourSerializer, description='성공적으로 수행됨')과 같이 사용할 수 있다.

 

      - 예시 : 

         - 아래 예제에서는 200, 400 상태 코드에 대한 응답 설정을 보여준다. 200의 경우 ExampleSerializer가 응답 구조를 정의하고, 추가적인 설명 '성공 응답'을 제공한다. 400의 경우는 특정 시리얼라이저 없이 설명만을 제공한다.

from drf_spectacular.utils import extend_schema, OpenApiResponse
from rest_framework import serializers, views

class ExampleSerializer(serializers.Serializer):
    message = serializers.CharField()

@extend_schema(
    responses={
        200: OpenApiResponse(response=ExampleSerializer, description='성공 응답'),
        400: OpenApiResponse(description='잘못된 요청 처리')
    }
)
class ExampleView(views.APIView):
    pass

 

      - 사용 방법 : 

class TistoryView(GenericAPIView):
    serializer_class = TistorySerializer

    @extend_schema(
        request=None,
        responses={
            200: TistorySerializer,
            400: None
        }
    )
    def post(self, request, *args, **kwargs):
        pass

 

      - 이 예제에서는 responses 옵션을 사용하여 HTTP 200 상태 코드에 대한 응답을 YourSerializer로 지정한다.

from drf_spectacular.utils import extend_schema
from rest_framework.views import APIView
from .serializers import YourSerializer

class YourAPIView(APIView):
    
    @extend_schema(responses={200: YourSerializer})
    def get(self, request, *args, **kwargs):
        # 여기에 HTTP GET 요청 로직 구현
        pass

 

      - 이 예제에서는 200과 400 상태 코드에 대한 응답으로 SuccessSerializer와 ErrorSerializer를 각각 지정하고, 응답에 대한 설명을 추가한다.

from drf_spectacular.utils import extend_schema, OpenApiResponse
from rest_framework.views import APIView
from .serializers import SuccessSerializer, ErrorSerializer

class YourAPIView(APIView):
    
    @extend_schema(
        responses={
            200: OpenApiResponse(response=SuccessSerializer, description='성공 응답'),
            400: OpenApiResponse(response=ErrorSerializer, description='잘못된 요청')
        }
    )
    def post(self, request, *args, **kwargs):
        # 여기에 HTTP POST 요청 로직 구현
        pass

 

   5) examples

      - examples를 사용하여 API의 예시를 정의할 수 있다.

      - OpenAPiExaple을 이용해 상세 내용을 정의할 수 있다.

      - OpenApiExample 클래스의 파라메터 :

         - name: 키값(중복된 값이 있을시, 마지막으로 정의된 값으로 고정),  예제의 고유 이름이다. 이를 통해 예제를 식별할 수 있다.
         - summary: 예제에 대해 짧게 설명한다.
         - description: 예제에 대한 자세한 설명이다.
         - value: 예제의 실제 값이다. 대부분의 경우, dict 형태로 입력된다.
         - request_only: 이 예제가 요청에만 사용될지 여부를 나타낸다. (True / False)
         - response_only: 이 예제가 응답에만 사용될지 여부를 나타낸다. (True / False)
         - status_code: 예제가 표시될 때 HTTP 상태 코드를 명시할 수 있다. 이는 주로 응답 예제에 유용하다.

from drf_spectacular.utils import extend_schema, OpenApiExample
from rest_framework.decorators import api_view
from rest_framework.response import Response

@api_view(['POST'])
@extend_schema(
    examples=[
        OpenApiExample(
            name='주문 생성 요청 예시',
            summary='주문 생성 요청',
            description='새로운 주문을 생성하기 위한 요청의 예시입니다.',
            value={
                'item': '에스프레소',
                'quantity': 1
            },
            request_only=True,  # 요청 예시에만 사용됨
        ),
        OpenApiExample(
            name='성공적인 주문 생성',
            summary='주문 생성 성공 예시',
            description='새로운 주문이 성공적으로 생성되었을 때의 예시 응답입니다.',
            value={
                'order_id': 124,
                'item': '에스프레소',
                'quantity': 1,
                'price': '4000원',
                'ordered_at': '2023-04-01T11:30:00Z'
            },
            response_only=True,
            status_codes=['201'],  # HTTP 상태 코드를 배열 형태로 수정
        )
    ]
)
def create_order(request):
    # 실제 주문 생성 로직
    data = {
        'order_id': 124,
        'item': '에스프레소',
        'quantity': 1,
        'price': '4000원',
        'ordered_at': '2023-04-01T11:30:00Z'
    }
    return Response(data, status=201)  # 예제와 일치하는 실제 응답 반환

 

   6) 파일 업로드

      - spectacular settings에 COMPONENT_SPLIT_REQUEST: True 를 설정한다.

      - APIView에 parser_classes를 추가하면 파일 업로드가 가능하다.

SPECTACULAR_SETTINGS = {
    ...
    'COMPONENT_SPLIT_REQUEST': True
}
class TistoryView(APIView):
    parser_classes = (MultiPartParser,)
    @extend_schema(
        request=inline_serializer(
            name="upload example",
            fields={
                "file": serializers.FileField(),
            },
        )
    )
    def post(self, request, *args, **kwargs):
        pass

 

   7) preprocessing_hooks

      - API 스키마 생성 전 데이터를 전처리하는 데 사용되는 옵션 중 하나이다.

      - 예시 : 특정 API 경로를 스키마에서 제외하기

         - 프로젝트에는 여러 API 엔드포인트가 있고, 일부 엔드포인트를 API 문서에서 제외하고 싶다고 가정한다.

         - 이 때 PREPROCESSING_HOOKS를 이용해 특정 조건에 맞는 엔드포인트를 자동으로 스키마에서 제외할 수 있다.

      1. hook 함수 구현

# preprocessing_hook.py 파일에 아래 내용 코딩

def remove_private_paths(schema):
    """
    API 스키마에서 특정 경로를 제외하는 hook function입니다.
    """

    private_paths = ['/api/private/', '/api/internal/']  # 제외할 경로 목록
    paths = dict(schema['paths'])

    for path in private_paths:
        paths.pop(path, None)  # 경로가 존재하면 제거

    schema['paths'] = paths
    return schema

 

      2. 설정에 hook 추가

         - settings.py 파일에 SPECTACULAR_SETTINGS 딕셔너리 내에 PREPROCESSING_HOOKS 키를 설정하여 만든 hook을 연결한다.

# settings.py에 아래의 설정 추가

SPECTACULAR_SETTINGS = {
    'PREPROCESSING_HOOKS': [
        'your_project.preprocessing_hook.remove_private_paths',
    ],
}

 

      - 예시 : api/v1 및 api/v2 경로 제외하기

         - 어떤 웹 어플리케이션에서 다양한 버전의 API를 운영하고 있으며, 새로 출시되는 drf-spectacular 기반의 API 문서에서는 오직 최신 버전인 api/v3만을 노출하고 싶다고 가정 한다.

# preprocessing_hooks.py 파일에 구현
from drf_spectacular.plumbing import preprocess_schema

def exclude_old_api_versions(schema):
    """
    'api/v1' 및 'api/v2' 경로를 스키마에서 제외하는 프리프로세싱 훅
    """

    # 스키마에서 'paths' 키를 가져옵니다.
    paths = schema.get('paths', {})
    
    # 제외할 경로 목록을 정의합니다.
    excluded_paths = ['api/v1/', 'api/v2/']
    
    # 제외할 경로가 포함된 모든 엔트리를 삭제합니다.
    for path in list(paths.keys()):
        for excluded_path in excluded_paths:
            if path.startswith(excluded_path):
                del paths[path]
    
    # 수정된 'paths'를 스키마에 적용합니다.
    schema['paths'] = paths
    
    return schema
# settings.py 파일에 추가

SPECTACULAR_SETTINGS = {
    'PREPROCESSING_HOOKS': [
        'my_project.preprocessing_hooks.exclude_old_api_versions'  # 경로는 실제 프로젝트의 구조에 맞게 조정해야 합니다.
    ],
}

 

   8) 인증 방식 설정하기

      1. 기본 설정 : 

         - settings.py에 SPECTACULAR_SETTINGS 딕셔너리를 정의하여 drf-spectacular의 기본 설정을 조정할 수 있다.

         - 아래 예시에서 'SECURITY' 설정은 Bearer 토큰을 사용한 인증 방식을 문서에 포함시키도록 한다.

SPECTACULAR_SETTINGS = {
    ...
    'SECURITY': [{
        'BearerAuth': [],
    }]
    ...
}

 

      2. 사용자 정의 토큰 타입 명시하기 :

         - 이 설정은 Bearer Authentication이 JWT를 사용함을 명시한다. bearerFormat 필드를 통해 API 소비자에게 사용되는 토큰의 형식(JWT)을 명확하게 알려준다.

# settings.py
SPECTACULAR_SETTINGS = {
    ...
    'SECURITY': [{
        'BearerAuth': {
            'type': 'http',
            'scheme': 'bearer',
            'bearerFormat': 'JWT',  # JWT 토큰 사용 명시
        }
    }]
    ...
}

 

      3. 여러 인증 방식 지정하기 :

         - 이 설정은 시스템이 Bearer Token과 별도의 API 키를 통해 인증을 지원한다.

# settings.py
SPECTACULAR_SETTINGS = {
    ...
    'SECURITY': [
        {'BearerAuth': []},  # 기본 Bearer Token 인증
        {
            'ApiKeyAuth': {  # API 키를 통한 추가 인증 방식
                'type': 'apiKey',
                'in': 'header',
                'name': 'X-API-KEY'
            }
        }
    ]
    ...
}

 

      4. OAuth2 인증 정보 추가하기 :

         - 이 설정을 통해 OAuth2 authorizationCode 방식을 사용하는 인증 방식을 문서화할 수 있다. 사용자는 authorizationUrl을 통해 인증을 받고, tokenUrl에서 토큰을 받아 해당 API 시스템에 접근할 수 있는 권한(읽기, 쓰기)을 부여받는다.

# settings.py
SPECTACULAR_SETTINGS = {
    ...
    'SECURITY': [{
        'OAuth2': {
            'type': 'oauth2',
            'flows': {
                'authorizationCode': {
                    'authorizationUrl': 'https://example.com/oauth/authorize',
                    'tokenUrl': 'https://example.com/oauth/token',
                    'scopes': {
                        'read': '읽기 권한',
                        'write': '쓰기 권한'
                    }
                }
            }
        }
    }]
    ...
}

 

      2. OpenApiAuthenticationExtension :

         - 공식 문서에는 아래와 같이 정의되어 있다.

         - 구현하기 어려운 복잡한 문제들을 내포하고 있기 때문에, 사용하지 않을 것을 권장한다.

        - 하지만 대안으로 OpenApiAuthenticationExtension 클래스를 활용하는 방식이 제시되고 있다.

# STRONGLY DISCOURAGED (with the exception for the djangorestframework-api-key library)
    # please don't use this anymore as it has tricky implications that
    # are hard to get right. For authentication, OpenApiAuthenticationExtension are
    # strongly preferred because they are more robust and easy to write.
    # However if used, the list of methods is appended to every endpoint in the schema!
    'SECURITY': [],

 

       - spectacular은 다양한 DRF 인증 클래스를 OpenAPI 3 스키마에 맞게 변환하기 위해 OpenApiAuthenticationExtension 확장 기능을 제공한다. 커스텀 인증 방식 혹은 타사 인증 패키지를 사용하는 경우, 이 클래스를 상속받아 구현할 수 있다.

         - 위의 코드는 path.to.CustomAuthenticationClass에 정의된 커스텀 인증 방식을 문서화하는 확장 클래스의 예를 보여준다.

from drf_spectacular.extensions import OpenApiAuthenticationExtension

class CustomAuthScheme(OpenApiAuthenticationExtension):
    target_class = 'path.to.CustomAuthenticationClass'  # 커스텀 인증 클래스 경로
    name = 'CustomAuth'  # 스키마에 표시될 인증 이름

    def get_security_definition(self, auto_schema):
        return {
            "type": "http",
            "scheme": "bearer",
            "bearerFormat": "JWT"  # JWT 토큰에 대한 설명
        }

 

         - JWT 토큰을 사용하여 사용자를 인증하는 API를 개발하고 있고, 이를 drf-spectacular를 이용해 문서화하고 싶다고 가정 한다면 위와 동일한 settings.py 설정에 아래와 같이 정의 할 수 있다.

# custom_auth_scheme.py
from drf_spectacular.extensions import OpenApiAuthenticationExtension

class JWTAuthScheme(OpenApiAuthenticationExtension):
    target_class = 'django.contrib.auth.backends.ModelBackend'  # 사용 중인 인증 백엔드
    name = 'JWTAuth'

    def get_security_definition(self, auto_schema):
        return {
            "type": "http",
            "scheme": "bearer",
            "bearerFormat": "JWT"
        }

 

   8) Permission

      - SPECTACULAR_SETTINGS의 SERVE_PERMISSIONS와 SERVE_AUTHENTICATION 설정을 조정함으로써 API 접근 권한을 설정해 일부 사용자에게만 문서를 노출하거나, 특정 조건에서만 접근을 허용하는 기능을 제공할 수 있다.

      - 예시 : 

         1. 관리자만 API 문서 접근

# settings.py
SPECTACULAR_SETTINGS = {
    'SERVE_PERMISSIONS': ['rest_framework.permissions.IsAdminUser'],
}

 

         2. 커스텀 Permission 사용 - 1

            - 슈퍼유저만 접근 가능하다.

# permissions.py
from rest_framework import permissions

class IsSuperUser(permissions.BasePermission):
    """
    슈퍼유저만 접근 가능한 Permission
    """

    def has_permission(self, request, view):
        return bool(request.user and request.user.is_superuser)

# settings.py
# 맞춤 Permission 클래스를 SERVE_PERMISSIONS에 추가합니다.
SPECTACULAR_SETTINGS = {
    'SERVE_PERMISSIONS': ['path.to.permissions.IsSuperUser'],
}

# urls.py
from django.urls import path
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView

urlpatterns = [
    path('schema/', SpectacularAPIView.as_view(), name='schema'),
    # 슈퍼유저만 API 문서를 볼 수 있습니다.
    path('docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='docs'),
]

 

         3. 커스텀 Permission 사용 - 2

            - 디버그 모드에만 접근이 가능하게 한다.

from django.conf import settings
from rest_framework import permissions

class IsDebugMode(permissions.BasePermission):
    """
    settings.DEBUG가 True일 때만 API 문서에 접근을 허용합니다.
    """
    def has_permission(self, request, view):
        return settings.DEBUG
SPECTACULAR_SETTINGS = {
    'TITLE': '마이쉽단 API',
    ...
    'DISABLE_ERRORS_AND_WARNINGS': True, # warning과 error 안뜨게 설정
    'SERVE_PERMISSIONS': ['core.permissions.IsDebugMode'],
}

 

 6. ModelViewSet의 기본 action과 custom action을 문서화 하는 방법 

   - ModelViewSet에 정의된 action들을 문서화 하고, custom action을 문서화 하는 방법

from django.contrib.auth.models import User
from django.http import HttpResponse
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiExample, OpenApiParameter, extend_schema_view
from drf_spectacular.utils import extend_schema
from rest_framework.decorators import action
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from rest_framework import serializers, status


class UserSerializer(serializers.ModelSerializer):
    user_type = serializers.CharField(help_text="회원의 유형값을 받습니다.", default="customer")
    class Meta:
        model = User
        depth = 1
        fields = "__all__"

@extend_schema_view(
    list=extend_schema(summary="이런식으로 class레벨 데코레이터로 문서 커스터마이징 가능하다.", tags=["User"]),
    retrieve=extend_schema(summary="사용자 세부 정보 조회", tags=["User"]),
    update=extend_schema(summary="사용자 정보 갱신", tags=["User"]),
    partial_update=extend_schema(summary="사용자 정보 부분 갱신", tags=["User"]),
    i_am_custom_api=extend_schema(
        summary="@action API도 마찬가지로 class 데코레이터로 문서 커스터마이징 가능하다.",
        tags=["User"],
        request=UserSerializer,
        responses={status.HTTP_200_OK: UserSerializer},
    ),
    manage_likes=extend_schema(
        summary="사용자 좋아요 관리",
        description="특정 사용자의 좋아요 수 증감",
        request=None,
        responses={status.HTTP_200_OK: UserSerializer},
        parameters=[
            OpenApiParameter(name="action", description="좋아요 또는 좋아요 취소 액션", required=True, type=OpenApiTypes.STR, examples=[]),
        ],
        tags=["User"]
    ),
)
class UserViewSet(ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer

    @extend_schema(
        tags=["User"],
        summary="method레벨 데코레이터도 가능",
        parameters=[
            OpenApiParameter(name="a_param", description="QueryParam1 입니다.", required=False, type=str),
            OpenApiParameter(
                name="date_param",
                type=OpenApiTypes.DATE,
                location=OpenApiParameter.QUERY,
                description="Filter by release date",
                examples=[
                    OpenApiExample(
                        "이것은 Query Parameter Example입니다.",
                        summary="short optional summary",
                        description="longer description",
                        value="1993-08-23",
                    ),
                ],
            ),
        ],
        examples=[
            OpenApiExample(
                request_only=True,
                summary="이거는 Request Body Example입니다.",
                name="success_example",
                value={
                    "username": "root",
                    "password": "django_1234",
                    "first_name": "성렬",
                    "last_name": "김",
                    "email": "user@example.com",
                },
            ),
        ],
    )
    def create(self, request: Request, *args, **kwargs) -> Response:
        response: HttpResponse = super().create(request, *args, **kwargs)

        return response

    @action(
        detail=False, url_path="custom-action-api",
    )
    def i_am_custom_api(self, request: Request, *args, **kwargs):

        return Response(data={"hi": "i am custom api"})

    @action(detail=True, methods=['post'], url_path='manage-likes')
    def manage_likes(self, request, pk=None):
        user = self.get_object()
        action_type = request.data.get("action")
        if action_type == "like":
            user.likes += 1
        elif action_type == "unlike":
            user.likes -= 1
        user.save()
        return Response({"likes": user.likes})

 

 7. request_only, response_only를 사용한 문서 정의 방법

   - 옵션 설명 :

      - responses를 통해 예상되는 HTTP 응답 상태 코드에 대한 상세 설명과 함께 해당 응답의 형식을 보여준다.

      - parameters를 이용해 API에 추가적인 쿼리 파라미터를 문서화하고, 이 파라미터가 필수 항목인지, 타입은 무엇인지 정의한다.

      - description으로 메서드의 상세한 설명을 추가한다.

      - request 옵션으로 요청 시 기대되는 스키마 형태를 지정한다.

      - methods를 사용하여 이 API가 지원되는 HTTP 메소드를 제한한다.

      - examples를 통해 요청과 응답에 대한 구체적인 예시를 제공하여 일반적인 사용 사례를 이해하기 쉽도록 돕는다.

from drf_spectacular.utils import extend_schema, OpenApiExample, OpenApiParameter, OpenApiResponse
from drf_spectacular.types import OpenApiTypes
from rest_framework.request import Request
from rest_framework.response import Response

@extend_schema(
    tags=["User"],  # 이 API에 적용할 태그 정보입니다.
    summary="method레벨 데코레이터도 가능",  # 보다 상세한 요약설명을 제공합니다.
    responses={  # 기대되는 응답 형상에 대한 정보입니다.  
        200: OpenApiResponse(description='성공적인 사용자 생성', response=OpenApiTypes.OBJECT),
        400: OpenApiResponse(description='잘못된 요청', response=OpenApiTypes.OBJECT),
        401: OpenApiResponse(description='인증 실패', response=OpenApiTypes.OBJECT)
    },
    # API 호출에 사용될 추가적인 쿼리 파라미터를 정의합니다.
    parameters=[
        OpenApiParameter(name='sample_param', description='샘플 추가 파라미터', required=False, type=OpenApiTypes.STR),
    ],
    description="API를 통해 새로운 사용자를 생성합니다. 성공, 실패, 요청 사항에 대한 예시가 포함됩니다.",
    request=OpenApiTypes.OBJECT,  # 요청 시에 기대되는 스키마를 단순화하여 표시합니다.
    methods=['POST'],  # 해당 API의 HTTP 메소드를 제한합니다.
    examples=[  # 요청과 응답의 예시를 제공합니다.
        OpenApiExample(
            name="success_example",
            value={
                "username": "root",
                "password": "django_1234",
                "first_name": "성렬",
                "last_name": "김",
                "email": "user@example.com",
            },
        ),
        OpenApiExample(
            request_only=True, # 요청 예제 명시
            name="invalid_example1",
            summary="비밀번호 너무 쉬움 예제",
            description="비밀번호가 보안 기준에 미치지 못합니다.",
            value={
                "username": "root23",
                "password": "1234",
                "first_name": "성렬",
                "last_name": "김",
                "email": "user@example.com",
            },
        ),
        OpenApiExample(
            request_only=True,
            name="invalid_example2",
            summary="이름 필수 입력 예제",
            description="첫 번째 이름 입력이 누락되었습니다.",
            value={
                "username": "root434",
                "password": "django_1234",
                "first_name": "",
                "last_name": "김",
                "email": "user@example.com",
            },
        ),
        OpenApiExample(
            response_only=True, # 응답 예제 명시
            name="success_example2",
            summary="사용자 생성 성공 예제",
            description="성공적으로 사용자가 생성된 경우의 예제 응답입니다.",
            value={
                "username": "root434",
                "password": "django_1234",
                "first_name": "성렬",
                "last_name": "김",
                "email": "user@example.com",
            },
        ),
    ],
)
def create(self, request: Request, *args, **kwargs) -> Response:
    # API 구현 로직
    pass

 

 8. Query Param에 Example 목록 정의하는 방법

   - 주요 옵션 설명 :

      1) deprecated: 이를 True로 설정하면 해당 파라미터가 더 이상 사용되지 않음을 나타내어, API 사용자가 새로운 대체 파라미터나 방법을 사용하도록 유도할 수 있다.

      2) style 및 explode: 이 설정들은 주로 파라미터가 어떻게 serialize되고 API에 전달되는지를 제어한다. 예를 들어, 배열이나 객체를 여러 파라미터로 분해해서 전달할 수 있다.

      3) allowEmptyValue: API 사용 시 특정 파라미터가 값을 가지지 않는 경우를 허용할지 여부를 결정한다. 예를 들어, 값을 설정하지 않고 파라미터만 전달하여 기본 동작을 유도할 수 있다.

@extend_schema(
    tags=["사용자"],
    summary="method레벨 데코레이터도 가능",
    parameters=[
        OpenApiParameter(
            name="a_param", 
            description="QueryParam1 입니다.", 
            required=False, 
            type=str,
            # 추가 옵션
            deprecated=False, # 이 파라미터가 더 이상 사용되지 않음을 나타냄
            style='form', # 이 파라미터가 serialize되는 스타일을 정의 (예: "form", "deepObject")
            explode=True, # 스타일이 'form'이고, 'array' 또는 'object' 타입인 경우, 개별 요소를 별도 파라미터로 전달할지 여부
            allowEmptyValue=False, # 파라미터 값이 비어 있어도 되는지 여부
        ),
        OpenApiParameter(
            name="date_param",
            type=OpenApiTypes.DATE,
            location=OpenApiParameter.QUERY,
            description="Filter by release date",
            examples=[
                OpenApiExample(
                    "이것은 Query Parameter Example입니다.",
                    summary="short optional summary1",
                    description="longer description",
                    value="1993-08-23",
                ),
                OpenApiExample(
                    "이것은 Query Parameter Example2입니다.",
                    summary="short optional summary2",
                    description="longer description",
                    value="1993-08-23",
                ),
                OpenApiExample(
                    "이것은 Query Parameter Example3입니다.",
                    summary="short optional summary3",
                    description="longer description",
                    value="1993-08-23",
                ),
            ],
            # 여기에 추가 옵션을 넣을 수 있지만 위 예제를 참고해 주세요.
        ),
    ],
)
def create(self, request: Request, *args, **kwargs) -> Response:
    # 함수 내용
    ...

 

 9. 다른 파일로 분리하여 사용하는 방법

   - 문서화 코드를 작성하다보면, 실재 동작하는 코드보다 더 많은 코드가 생성될 수 있다. 

   - 이런 경우, 새로운 파일에 데코레이터로 새로 만들어 사용하거나, example을 정의하고 views.py에서 쉽게 호출하는 방식으로 사용할 수 있다.

   - example 분리하는 방법 :

# example_objects.py 파일

from drf_spectacular.utils import OpenApiExample

example1 = OpenApiExample(
    name="Query Parameter Example 1",
    description="이것은 Query Parameter Example1입니다.",
    value="1993-08-23",
)

example2 = OpenApiExample(
    name="Query Parameter Example 2",
    description="이것은 Query Parameter Example2입니다.",
    value="1994-09-24",
)

example3 = OpenApiExample(
    name="Query Parameter Example 3",
    description="이것은 Query Parameter Example3입니다.",
    value="1995-10-25",
)
from drf_spectacular.utils import extend_schema, OpenApiParameter
from .example_objects import example1, example2, example3  # 예제 객체 import

@extend_schema(
    tags=["사용자"],
    summary="method레벨 데코레이터도 가능",
    parameters=[
        OpenApiParameter(
            name="a_param",
            description="QueryParam1 입니다.",
            required=False,
            type=str,
            # 여기에 추가 옵션들...
        ),
        OpenApiParameter(
            name="date_param",
            type=OpenApiTypes.DATE,
            location=OpenApiParameter.QUERY,
            description="Filter by release date",
            examples=[example1, example2, example3],  # 사용 예제 객체들
            # 추가 옵션들...
        ),
    ],
)
def create(self, request, *args, **kwargs):
    # 함수 내용

 

      - 데코레이터로 분리하는 방법

 

# schema_decorators.py

from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiTypes, OpenApiExample

def custom_extend_schema(_func=None, **kwargs):
    """사용자 정의 extend_schema 데코레이터"""
    def decorator(func):
        return extend_schema(
            # 사용자 맞춤 설정을 여기에 추가
            tags=["사용자"],
            summary="method레벨 데코레이터도 가능",
            parameters=[
                OpenApiParameter(
                    name="a_param", 
                    description="QueryParam1 입니다.", 
                    required=False, 
                    type=str,
                    # 추가 옵션
                    deprecated=False,
                    style='form',
                    explode=True,
                    allowEmptyValue=False,
                ),
                OpenApiParameter(
                    name="date_param",
                    type=OpenApiTypes.DATE,
                    location=OpenApiParameter.QUERY,
                    description="Filter by release date",
                    examples=[
                        OpenApiExample(
                            "이것은 Query Parameter Example입니다.",
                            summary="short optional summary1",
                            description="longer description",
                            value="1993-08-23",
                        ),
                        # 추가 예시
                    ],
                    # 추가 옵션
                ),
            ],
            **kwargs  # 추가적인 데코레이터 설정을 위한 가변 인자
        )(func)
    return decorator if _func is None else decorator(_func)
# views.py

from rest_framework.views import APIView
from rest_framework.response import Response
from .schema_decorators import custom_extend_schema  # custom_extend_schema import

@custom_extend_schema
class ExampleView(APIView):
    
    def get(self, request, *args, **kwargs):
        # Your view logic here
        return Response({"message": "This is an example response!"})

 

 

 - reference :

https://drf-spectacular.readthedocs.io/en/latest/customization.html#step-1-queryset-and-serializer-class

 

Workflow & schema customization — drf-spectacular documentation

You are not satisfied with your generated schema? Follow these steps in order to get your schema closer to your API. Note The warnings emitted by ./manage.py spectacular --file schema.yaml --validate are intended as an indicator to where drf-spectacular di

drf-spectacular.readthedocs.io

https://gruuuuu.github.io/programming/openapi/

 

OpenAPI 란? (feat. Swagger)

Overview 이 문서에서는 API의 기본적인 정의는 알고 있다는 전제하에 OpenAPI와 Swagger의 개념, 차이점, 비교적 최근(2017-07-26) 업데이트한 OpenAPI 3.0에 대해서 알아보도록 하겠습니다. 1. OpenAPI? Open API?

gruuuuu.github.io

https://drf-spectacular.readthedocs.io/en/latest/

 

drf-spectacular — drf-spectacular documentation

© Copyright 2020, T. Franzel. Revision 4d6c93f0.

drf-spectacular.readthedocs.io

https://techblog.yogiyo.co.kr/django-rest-framework-api-document-generator-feat-drf-spectacular-585fcabec404

 

Django Rest Framework API Document Generator (feat. drf-spectacular)

drf-yasg의 단점 그리고 Open API Specification 3.0

techblog.yogiyo.co.kr

https://medium.com/@heeee/django-drf-spectacular%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-api-%EB%AC%B8%EC%84%9C-%EC%9E%90%EB%8F%99%ED%99%94-with-dto-91fbf4af6781

 

[Django] drf-spectacular를 이용한 API 문서 자동화(With DTO)

사내에 도입한 API 자동 문서화에 대한 내용들을 정리했습니다.

medium.com

https://velog.io/@catveloper/DRF-Spectacular-%EC%82%AC%EC%9A%A9%EB%B2%95

 

DRF-Spectacular 사용법

Open API Spec 3.0에 맞추어 문서화를 도와주는 라이브러리 drf-spectacular 사용법입니다.

velog.io

https://mixedprograming.tistory.com/36

 

drf_spectacular를 사용한 api docs 만들기

목차 서론 필자가 drf_spectacularf를 사용하여 만들었던 docs 페이지를 기억하기 위해 작성하는 페이지입니다. 찾으시는 내용이 없을 수 있습니다. 또한 정확히 알지 않고 필자의 경험으로만 이루어

mixedprograming.tistory.com

https://velog.io/@catveloper/DRF-Spectacular-%EC%82%AC%EC%9A%A9%EB%B2%95

 

DRF-Spectacular 사용법

Open API Spec 3.0에 맞추어 문서화를 도와주는 라이브러리 drf-spectacular 사용법입니다.

velog.io

https://whitepro.tistory.com/650

 

OpenAPI 명세, Swagger 기본 개념 : Django restframework, drf-spectacular로 swagger split

OpenAPI 명세 개념 개요 여기서 말하는 것은 Open API가 아니라 붙여쓰는 OpenAPI이다. 참조 1에 따르면 Open API는 말 그대로 개방된 API로 날씨, 주소 정보 등 공개된 API를 의미한다. OpenAPI는 Restful API의

whitepro.tistory.com

https://www.slideshare.net/ssuserc158fd/api-pdf-259231365

 

API 자동화 문서 도입기.pdf

API 자동화 문서 도입기.pdf - Download as a PDF or view online for free

www.slideshare.net