bong-u/til

[모각코24하계] 01 : 결과

수정일 : 2024-07-02

django에서 swagger 문서화 구현하기

개요

장고 프레임워크를 사용하여 기본적인 CRUD 기능과, REST API를 구현하는 방법을 알아보자.

  • 프로젝트 구조
 1.
 2├── db.sqlite3
 3├── djtest (메인 앱)
 4│   ├── __init__.py
 5│   ├── asgi.py
 6│   ├── settings.py
 7│   ├── urls.py
 8│   ├── views.py
 9│   └── wsgi.py
10├── manage.py
11├── paste (생성한 앱)
12│   ├── __init__.py
13│   ├── admin.py
14│   ├── apps.py
15│   ├── migrations
16│   ├── models.py
17│   ├── serializers.py
18│   ├── tests.py
19│   ├── urls.py
20│   └── views.py
21└── requirements.txt
  • 자주 사용하는 Django 명령자
 1# 새로운 Django 프로젝트를 생성
 2python manage.py startproject
 3# 새로운 Django 앱을 생성
 4python manage.py startapp
 5# 데이터베이스에 적용할 마이그레이션 파일을 생성
 6python manage.py makemigrations
 7# 마이그레이션 파일을 실제 데이터베이스에 적용
 8python manage.py migrate
 9# 관리자(superuser) 계정을 생성
10python manage.py createsuperuser
11# 프로젝트의 테스트 케이스를 실행
12python manage.py test
13# 테스트용 서버를 특정 설정으로 실행
14python manage.py testserver

CRUD 구현

Paste 모델 정의

 1from django.db import models
 2
 3class Paste(models.Model):
 4    title = models.CharField(max_length=100)
 5    content = models.TextField()
 6    # auto_now_add : 객체가 처음 생성될 때만 현재 날짜와 시간을 자동으로 설정
 7    created_at = models.DateTimeField(auto_now_add=True)
 8    # auto_now : 객체가 저장될 때마다 현재 날짜와 시간을 자동으로 설정
 9    updated_at = models.DateTimeField(auto_now=True)
10    class Meta:
11        ordering = ['-created_at']
12    def __str__(self):
13        return self.title

Serializer 정의

1from rest_framework import serializers
2from .models import Paste
3
4class PasteSerializer(serializers.ModelSerializer):
5    class Meta:
6        model = Paste
7        fields = '__all__'
8        # 직접 지정하는 방법
9        # fields = ['title', 'content']

View 구현

  • PasteView

     1class PasteView(APIView):
     2    def get(self, _):
     3        pastes = Paste.objects.all()
     4        serializer = PasteSerializer(pastes, many=True)
     5        return Response(serializer.data, status=status.HTTP_200_OK)
     6
     7    def post(self, request):
     8        serializer = PasteSerializer(data=request.data)
     9        if serializer.is_valid():
    10            serializer.save(user=request.user)
    11            return Response(serializer.data, status=status.HTTP_201_CREATED)
    12        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    
  • PasteDetailView

     1class PasteDetailView(APIView):
     2    def get(self, _, pk):
     3        try:
     4            paste = Paste.objects.get(pk=pk)
     5            serializer = PasteSerializer(paste)
     6            return Response(serializer.data, status=status.HTTP_200_OK)
     7        except Paste.DoesNotExist:
     8            return Response(status=status.HTTP_404_NOT_FOUND)
     9
    10    def put(self, request, pk):
    11        try:
    12            paste = Paste.objects.get(pk=pk)
    13        except Paste.DoesNotExist:
    14            return Response(status=status.HTTP_404_NOT_FOUND)
    15
    16        if paste.user != request.user:
    17            return Response(status=status.HTTP_403_FORBIDDEN)
    18
    19        serializer = PasteSerializer(paste, data=request.data)
    20        if serializer.is_valid():
    21            serializer.save()
    22            return Response(serializer.data, status=status.HTTP_200_OK)
    23        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    24
    25    def delete(self, request, pk):
    26        try:
    27            paste = Paste.objects.get(pk=pk)
    28        except Paste.DoesNotExist:
    29            return Response(status=status.HTTP_404_NOT_FOUND)
    30
    31        paste.delete()
    32        return Response(status=status.HTTP_204_NO_CONTENT)
    

urls.py

1from django.urls import path
2from paste.views import *
3
4urlpatterns = [
5    path('', PasteView.as_view(), name='paste_list_create'),
6    path('<int:pk>', PasteDetailView.as_view(), name='paste_get_update_delete'),
7]

Swagger 적용

PasteView

  • PasteView

     1class PasteView(APIView):
     2    @swagger_auto_schema(
     3        operation_description="Get list of pastes",
     4        operation_summary="Get list of pastes",
     5        responses={200: PasteSerializer(many=True)},
     6    )
     7    def get(self, _):
     8        ...
     9
    10    @swagger_auto_schema(
    11        operation_description="Create a new paste",
    12        operation_summary="Create a new paste",
    13        request_body=PasteSerializer,
    14        responses={201: PasteSerializer, 400: "Bad Request"},
    15    )
    16    def post(self, request):
    17        ...
    
  • PasteDetailView

     1class PasteDetailView(APIView):
     2    @swagger_auto_schema(
     3        operation_description="Get a paste by ID",
     4        operation_summary="Get a paste by ID",
     5        responses={200: PasteSerializer, 404: "Not Found"},
     6    )
     7    def get(self, _, pk):
     8        ...
     9
    10    @swagger_auto_schema(
    11        operation_description="Update a paste by ID",
    12        operation_summary="Update a paste by ID",
    13        request_body=PasteSerializer,
    14        responses={
    15            200: PasteSerializer,
    16            400: "Bad Request",
    17            403: "Forbidden",
    18            404: "Not Found",
    19        },
    20    )
    21    def put(self, request, pk):
    22        ...
    23
    24    @swagger_auto_schema(
    25        operation_description="Delete a paste by ID",
    26        operation_summary="Delete a paste by ID",
    27        responses={204: "No Content", 404: "Not Found"},
    28    )
    29    def delete(self, request, pk):
    30        ...
    

swagger 적용 결과

swagger

django에서 JWT 인증 구현하기

장고에서는 djangorestframework-simplejwt 패키지를 사용하여 JWT 인증을 구현할 수 있다.

requirements

1pip install djangorestframework-simplejwt

settings.py

1INSTALLED_APPS = [
2    ...
3    'rest_framework',
4    'rest_framework_simplejwt',
5]
 1REST_FRAMEWORK = {
 2    # 기본 인증 클래스를 설정
 3    'DEFAULT_AUTHENTICATION_CLASSES': (
 4        'rest_framework_simplejwt.authentication.JWTAuthentication',
 5    ),
 6    # 기본 스키마 클래스를 설정, CoreAPI를 사용하여 자동으로 API 문서화를 생성
 7    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
 8    # 기본 권한 클래스를 설정, AllowAny를 사용하여 모든 요청을 허용
 9    'DEFAULT_PERMISSION_CLASSES': (
10        'rest_framework.permissions.AllowAny',
11    ),
12}
 1from datetime import timedelta
 2
 3SIMPLE_JWT = {
 4    # 액세스 토큰의 유효 기간을 설정
 5    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30),
 6    # 리프레시 토큰의 유효 기간을 설정
 7    'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
 8    # 리프레시 토큰이 갱신될 때마다 새로운 리프레시 토큰을 발급할지 여부를 설정
 9    'ROTATE_REFRESH_TOKENS': False,
10    # 리프레시 토큰이 갱신된 후 이전 토큰을 블랙리스트에 추가할지 여부를 설정
11    'BLACKLIST_AFTER_ROTATION': True,
12
13    # JWT 토큰의 암호화 알고리즘을 설정
14    'ALGORITHM': 'HS256',
15    # JWT 토큰을 서명할 때 사용할 키를 설정
16    'SIGNING_KEY': SECRET_KEY,
17    # 토큰 검증에 사용할 공개 키를 설정
18    'VERIFYING_KEY': None,
19    # 토큰의 대상자(aud) 클레임을 설정
20    'AUDIENCE': None,
21    # 토큰의 발급자(iss) 클레임을 설정
22    'ISSUER': None,
23
24    # 인증 헤더 타입을 설정
25    'AUTH_HEADER_TYPES': ('Bearer',),
26    # 인증 헤더의 이름을 설정
27    'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
28    # 사용자 모델에서 사용자 ID 필드를 설정
29    'USER_ID_FIELD': 'id',
30    # JWT 토큰에서 사용자 ID를 저장할 클레임을 설정
31    'USER_ID_CLAIM': 'user_id',
32
33    # 인증에 사용할 토큰 클래스들을 설정
34    'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
35    # 토큰의 유형을 저장할 클레임을 설정
36    'TOKEN_TYPE_CLAIM': 'token_type',
37}

urls.py

 1from django.urls import path
 2from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
 3from rest_framework_simplejwt.authentication import JWTAuthentication
 4
 5urlpatterns = [
 6    # JWT 토큰을 발급하는 뷰
 7   path("token/", TokenObtainPairView.as_view(), name="token_obtain_pair"),
 8    # JWT 토큰을 갱신하는 뷰
 9   path("token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
10]

views.py

 1from rest_framework import permissions
 2from rest_framework_simplejwt.authentication import JWTAuthentication
 3from drf_yasg.utils import swagger_auto_schema
 4
 5
 6class PasteView(APIView):
 7
 8    def get(self, request):
 9        ...
10
11    def post(self, request):
12        ...
13
14   def get_permissions(self):
15        # SAFE_METHODS : GET, HEAD, OPTIONS
16        if self.request.method in permissions.SAFE_METHODS:
17            self.permission_classes = [permissions.AllowAny]
18        else:
19            self.authentication_classes = [JWTAuthentication]
20            self.permission_classes = [permissions.IsAuthenticated]
21        return super().get_permissions()
22
23
24class PasteDetailView(APIView):
25
26    def get(self, request, pk):
27        ...
28
29    def put(self, request, pk):
30        ...
31
32    def delete(self, request, pk):
33        ...
34
35    def get_permissions(self):
36        # SAFE_METHODS : GET, HEAD, OPTIONS
37        if self.request.method in permissions.SAFE_METHODS:
38            self.permission_classes = [permissions.AllowAny]
39        else:
40            self.authentication_classes = [JWTAuthentication]
41            self.permission_classes = [permissions.IsAuthenticated]
42        return super().get_permissions()
43

결과

  • TokenObtainPairView를 통해 access token과 refresh token을 발급받을 수 있다.
  • TokenRefreshView를 통해 refresh token을 사용하여 access token을 갱신할 수 있다.
  • access token과 refresh token은 settings.py에서 설정한 유효 기간에 따라 만료된다.

Blacklist 적용

settings.py

1INSTALLED_APPS = [
2    ...
3    'rest_framework_simplejwt.token_blacklist',
4]
1SIMPLE_JWT = {
2    ...
3    # 블랙리스트에 토큰을 추가할 때 사용할 모델을 설정
4    'BLACKLIST_AFTER_ROTATION': True,
5}

결과

  • TokenRefreshView를 통해 토큰이 재발급될 때, 이전 refresh token을 블랙리스트에 추가한다.