어느 사이트든 접속을 하게 되면 기본적으로 항상 존재하는 공간이 공지사항일 것이다.
오늘은 공지사항을 추가하면서 기존의 프로젝트에 적용시켜두었던 JWT를 활용해 CRUD API를 구현해볼 것이다.
공지사항의 경우 Admin 유저만 작성, 수정 및 삭제가 가능하며,
조회는 아무나 가능하게끔 설정해볼 것이다.
JWT 초기 설정은 이전 글을 참고하면 된다.
Notice 앱 생성
python manage.py startapp notice
위 명령어를 통해 기존 프로젝트에 notice 앱을 생성한다.
앱을 생성하면 settings.py 에도 추가해주자 !
그리고 글 조회 시 페이징 처리도 같이 해주기 위해서 필요한 내용도 미리 추가한다.
# settings.py
INSTALLED_APPS = [
...
'notice',
...
]
# 페이징처리에 PageNumberPagination을 사용할 것이고 한 페이지에 표시할 객체는 3개로 지정했다.
REST_FRAMEWORK = {
...
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 3,
...
}
Model 작성
공지사항 모델에 세부사항을 작성해보도록 하자 !
먼저 어떤 어드민 유저가 글을 작성했는지 알아야 하니 유저 모델을 외래 키로 사용해서 작성자 정보를 남겨주어야 하고,
공지사항의 제목과 내용, 작성 시간, 수정했다면 수정 시간까지 기록되게끔 설정하였다.
외래 키로 설정한 user 항목은 db_column을 따로 지정하지 않으면 user_id 로 자동 생성된다.
# notice/models.py
from django.conf import settings
from django.db import models
class Notice(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, db_column='author') # 작성자는 유저 !
title = models.CharField(max_length=50)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True) # 생성시 자동으로 시간저장
updated_at = models.DateTimeField(auto_now=True) # 수정시 자동으로 시간저장
# 테이블명을 따로 지정
class Meta:
db_table = 'notice'
ordering = ['-id'] # 정렬기준 최신순(늦게 작성된 글이 최신글임)
serializers.py 작성
정보를 직렬화 하기 위한 serializers를 작성한다.
이때 공지사항 정보를 받아왔을 때 유저 상세 정보도 같이 받아올 수 있게 설정해둔다.
이전 글에서 추가로 생성해놓은 UserInfoSerializer를 이용해 필요한 컬럼만 가져온다.
# notice/serializers.py
from rest_framework import serializers
from .models import Notice
from accounts.serializers import UserInfoSerializer
class NoticeSerializer(serializers.ModelSerializer):
user = UserInfoSerializer(read_only=True) # 유저 정보를 가져온다 !
class Meta:
model = Notice
fields = '__all__'
views.py 작성
이제 준비가 다 되었으니 CRUD를 작성해보도록 하자 !
상세 내용은 주석을 달아 두었다.
# notice/views.py
from django.shortcuts import get_object_or_404
from rest_framework import status
from rest_framework.response import Response
from rest_framework.decorators import api_view, permission_classes, authentication_classes
from rest_framework.permissions import IsAuthenticated, IsAdminUser, AllowAny
from rest_framework.pagination import PageNumberPagination
from rest_framework_simplejwt.authentication import JWTAuthentication
from .models import Notice
from notice.serializers import NoticeSerializer
@api_view(['POST'])
@permission_classes([IsAuthenticated, IsAdminUser]) # 어드민 유저만 공지사항 작성 가능
@authentication_classes([JWTAuthentication]) # JWT 토큰 확인
def notice_create(request):
serializer = NoticeSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
# JWT 인증 사용시 인증객체를 통해 인증을 진행하고 사용자 정보를 request.user 객체에 저장
# 인증정보가 없거나 일치하지않으면 AnonymousUser를 저장
serializer.save(user=request.user)
return Response(status=status.HTTP_201_CREATED)
@api_view(['GET'])
@permission_classes([AllowAny]) # 글 확인은 로그인 없이 가능
def notice_list(request):
notice_list = Notice.objects.all()
paginator = PageNumberPagination()
# 페이지 사이즈를 주면 해당 사이즈로 지정
# 값이 없으면 기본 사이즈로 설정(settings.py안에)
page_size = request.GET.get('size') # request.GET['size']를 써도 되지만 size가 없다면 에러를 발생시킴
if not page_size == None: # request.GET.get('size')로 작성시 size가 없으면 None을 반환
paginator.page_size = page_size
result = paginator.paginate_queryset(notice_list, request)
serializers = NoticeSerializer(result, many=True)
return paginator.get_paginated_response(serializers.data)
@api_view(['GET'])
@permission_classes([AllowAny]) # 글 확인은 로그인 없이 가능
def notice_detail(request, pk):
notice = get_object_or_404(Notice, pk=pk)
serializer = NoticeSerializer(notice)
return Response(serializer.data, status=status.HTTP_200_OK)
@api_view(['PUT'])
@permission_classes([IsAuthenticated, IsAdminUser]) # 어드민 유저만 공지사항 수정 가능
@authentication_classes([JWTAuthentication]) # JWT 토큰 확인
def notice_update(request, pk):
notice = get_object_or_404(Notice, pk=pk)
# instance를 지정해줘야 수정될 때 해당 정보가 먼저 들어간 뒤 수정(안정적이다)
serializer = NoticeSerializer(instance=notice, data=request.data)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(status=status.HTTP_200_OK)
@api_view(['DELETE'])
@permission_classes([IsAuthenticated, IsAdminUser]) # 어드민 유저만 공지사항 삭제 가능
@authentication_classes([JWTAuthentication]) # JWT 토큰 확인
def notice_delete(request, pk):
notice = get_object_or_404(Notice, pk=pk)
notice.delete()
return Response(status=status.HTTP_200_OK)
위에서부터 글 작성, 글 전체 조회 + 페이징 처리, 글 상세 조회, 수정, 삭제 순으로 되어있다.
IsAuthenticated을 통해 토큰의 유효성을 확인한다.
JWTAuthentication은 토큰은 JWT 방식을 사용한다는 것을 명시해주는 역할을 한다.
urls.py 작성
url 연결은 상위와 하위 둘 다 해주어야 한다.
# urls.py
from django.contrib import admin
from django.urls import include, path
from django.conf.urls.static import static
from django.conf import settings
urlpatterns = [
path('admin/', admin.site.urls),
path('api/user/', include('accounts.urls')),
path('api/notice/', include('notice.urls')),
]
# notice/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('create/', views.notice_create),
path('', views.notice_list, name='notice_list'),
path('<int:pk>', views.notice_detail, name='notice_detail'),
path('update/<int:pk>', views.notice_update, name='notice_update'),
path('delete/<int:pk>', views.notice_delete, name='notice_delete')
]
테스트
여기까지 작성했다면 테스트를 해보자 !
python manage.py makemigrations
python manage.py migrate
python manage.py runserver
먼저 위 명령어를 통해 작성한 모델을 DB에 넣어준 다음 서버를 실행한다.
테스트는 Postman으로 진행해볼 것이다.
먼저 어드민 계정으로 로그인 후 토큰을 받는다.
다음 access_token을 복사해서 Authorization에 Bearer Token을 선택 후 복사한 토큰을 넣어준다.
그 뒤 title과 content를 작성하고 글을 생성하는 API를 호출해보자
201을 반환하는 것을 보니 생성이 잘 된 것 같다.
만약 어드민이 아닌 다른 계정으로 로그인한 토큰을 사용한다면 어떻게 될까?
403을 반환하며 권한이 없다고 나온다.
글을 여러 개 생성한 다음 페이징 처리가 잘 되었는지도 확인해보자.
page는 1번, size는 2로 설정해서 호출해보면 아래와 같이
count에 전체 글 수, next의 다음 글을 조회할 수 있는 링크
글 내용과 연결되어있는 유저 정보까지 확인할 수 있다.
여기까지 공지사항을 만들고 JWT 토큰을 이용해 API 인가 처리까지 해보았다.
간단하게 데코레이션을 사용하는 것 만으로 인가처리를 할 수 있어서 상당히 편했다.
또 JWT 토큰으로 인증을 처리하면 자동으로 해당 토큰의 유저 정보를 request.user에 담아주는데 여러모로 편리했다.
다음에는 지금까지 작성한 API를 Swagger를 통해 간단하게 문서화하는 법을 배워보자 !
'Backend > Django' 카테고리의 다른 글
[Django-DRF] drf-yasg 를 활용한 Swagger 적용하기 (0) | 2022.03.13 |
---|---|
[Django-DRF] 커스텀 유저 모델 생성 및 회원가입 로그인 + JWT 적용 (0) | 2022.03.02 |