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 적용 결과
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을 블랙리스트에 추가한다.