Serializer 구성
from rest_framework.serializers import ModelSerializer
from django.contrib.auth.models import User
from .models import UserProfile
class UserIdUsernameSerializer(ModelSerializer):
class Meta:
model = User
fields = ["id", "username"]
class UserSerializer(ModelSerializer):
class Meta:
model = User
fields = ["id", "username", "password", "email"]
class UserProfileSerializer(ModelSerializer):
user = UserSerializer(read_only=True)
class Meta:
model = UserProfile
fields = "__all__"
account/serializers.py
from rest_framework import serializers
class SignUpRequestSerializer(serializers.Serializer):
email = serializers.EmailField()
password = serializers.CharField()
username = serializers.CharField()
college = serializers.CharField()
major = serializers.CharField()
class SignInRequestSerializer(serializers.Serializer):
email = serializers.EmailField()
username = serializers.CharField()
password = serializers.CharField()
account/request_serializers.py (회원가입 시, 로그인 시)
회원가입 API
def set_token_on_response_cookie(user, status_code):
token = RefreshToken.for_user(user)
user_profile = UserProfile.objects.get(user=user)
serialized_data = UserProfileSerializer(user_profile).data
res = Response(serialized_data, status=status_code)
res.set_cookie("refresh_token", value=str(token), httponly=True)
res.set_cookie("access_token", value=str(token.access_token), httponly=True)
return res
#### view
class SignUpView(APIView):
@swagger_auto_schema(
operation_id="회원가입",
operation_description="회원가입을 진행합니다.",
request_body=SignUpRequestSerializer,
responses={201: UserProfileSerializer, 400: "Bad Request"},
)
def post(self, request):
user_serializer = UserSerializer(data=request.data)
if user_serializer.is_valid(raise_exception=True):
user = user_serializer.save()
user.set_password(user.password)
user.save()
college=request.data.get('college')
major=request.data.get('major')
UserProfile.objects.create(user=user, college=college, major=major)
return set_token_on_response_cookie(user, status.HTTP_201_CREATED)
accounts/views.py
- simple JWT 의 RefreshToken 이라는 class는 user object 를 받아가서, refresh token 과 access token 을 token 안에 담아서 발급, .for_user() 함수 사용
- Response 클래스의 set_cookie 메서드 이용해 쿠키값으로 전달
- Response body에 담는 대신 쿠키에 담고, httponly=True 옵션 세팅을 통해 보안성 확보
로그인 API
class SignInView(APIView):
@swagger_auto_schema(
operation_id="로그인",
operation_description="로그인을 진행합니다.",
request_body=SignInRequestSerializer,
responses={200: UserSerializer, 404: "Not Found", 400: "Bad Request"},
)
def post(self, request):
username = request.data.get("username")
password = request.data.get("password")
if username is None or password is None:
return Response(
{"message": "missing fields ['username', 'password'] in query_params"},
status=status.HTTP_400_BAD_REQUEST,
)
try:
user = User.objects.get(username=username)
if not user.check_password(password):
return Response(
{"message": "Password is incorrect"},
status=status.HTTP_400_BAD_REQUEST,
)
return set_token_on_response_cookie(user, status.HTTP_200_OK)
except User.DoesNotExist:
return Response(
{"message": "User does not exist"}, status=status.HTTP_404_NOT_FOUND
)
로그인 성공 시 JWT 토큰 포함한 serialized data를 리턴해줌
회원가입, 그리고 로그인 시도마다 cookie 값이 이렇게 세팅되어 오는 것을 볼 수 있다
- 글 작성 권한 => 글을 작성한 사람이 나라는 사실을 서버가 기억했으면 좋겠어요!
- 글 삭제/수정 권한 => 다른 사람이 제 글을 함부로 삭제하거나 수정할 수 없었으면 좋겠어요.
- 좋아요 => 좋아요를 누른 사람이 누구인지 서버가 기억하면 좋을 것 같아요.
- 댓글 작성 권한 => 게시글과 마찬가지로 댓글도 작성자가 누구인지를 서버측에서 기억할 필요가 있겠죠!
- 댓글 삭제/수정 권한 => 이것도 게시글과 마찬가지로, 타인이 함부로 누군가의 댓글을 삭제할 수 없게 해야해요.
JWT token 인증을 swagger에서 테스트하려면
SWAGGER_SETTINGS = {
'USE_SESSION_AUTH': False, # swagger가 기본으로 사용하는 session auth를 사용하지 않음
'SECURITY_DEFINITIONS': {
'BearerAuth': { # bearer 토큰을 헤더의 Authorization에 담아서 보냄
'type': 'apiKey',
'name': 'Authorization',
'in': 'header',
'description': "JWT Token"
}
},
'SECURITY_REQUIREMENTS': [{
'BearerAuth': []
}]
}
위 코드를 seminar/settings.py 맨 아래에 추가한다
게시글 작성 API
...
class PostListView(APIView):
@swagger_auto_schema(
operation_id="게시글 생성",
operation_description="게시글을 생성합니다.",
request_body=PostListRequestSerializer,
responses={201: PostSerializer, 404: "Not Found", 400: "Bad Request", 401: "Unauthorized"},
manual_parameters=[openapi.Parameter("Authorization", openapi.IN_HEADER, description="access token", type=openapi.TYPE_STRING)]
)
def post(self, request):
title = request.data.get("title")
content = request.data.get("content")
tag_contents = request.data.get("tags")
author = request.user
if not author.is_authenticated:
return Response(
{"detail": "please signin"},
status=status.HTTP_401_UNAUTHORIZED,
)
if not title or not content:
return Response(
{"detail": "[title, content] fields missing."},
status=status.HTTP_400_BAD_REQUEST,
)
post = Post.objects.create(title=title, content=content, author=author)
if tag_contents is not None:
for tag_content in tag_contents:
if not Tag.objects.filter(content=tag_content).exists():
post.tags.create(content=tag_content)
else:
post.tags.add(Tag.objects.get(content=tag_content))
serializer = PostSerializer(post)
return Response(serializer.data, status=status.HTTP_201_CREATED)
- reqeust.user : 헤더에 bearer 토큰을 담아 보내면 요청 내부에 유저의 정보가 담기게 되고 해당 정보를 가져옴
- author.is_authenticated : Django는 user 인스턴스의 is_authenticated 속성을 통해 유저의 인증 여부를 간단하게 확인할 수 있음
- manual_parameters 줄을 추가해야 함
게시글 삭제 API
class PostDetailView(APIView):
...
@swagger_auto_schema(
operation_id="게시글 삭제",
operation_description="게시글을 삭제합니다.",
request_body=SignInRequestSerializer,
responses={204: "No Content", 404: "Not Found", 400: "Bad Request"},
manual_parameters=[openapi.Parameter("Authorization", openapi.IN_HEADER, description="access token", type=openapi.TYPE_STRING)]
)
def delete(self, request, post_id):
try:
post = Post.objects.get(id=post_id)
except:
return Response(
{"detail": "Post Not found."}, status=status.HTTP_404_NOT_FOUND
)
author = request.user
if not author.is_authenticated:
return Response(
{"detail": "please signin"},
status=status.HTTP_401_UNAUTHORIZED,
)
if post.author != author:
return Response(
{"detail": "You are not the author of this post."},
status=status.HTTP_403_FORBIDDEN,
)
post.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
게시글 수정 API
@swagger_auto_schema(
operation_id="게시글 수정",
operation_description="게시글을 수정합니다.",
request_body=PostDetailRequestSerializer,
responses={200: PostSerializer, 404: "Not Found", 400: "Bad Request"},
manual_parameters=[openapi.Parameter('Authorization', openapi.IN_HEADER, description="access token", type=openapi.TYPE_STRING)]
)
def put(self, request, post_id):
try:
post = Post.objects.get(id=post_id)
except:
return Response(
{"detail": "Post not found."}, status=status.HTTP_404_NOT_FOUND
)
author = request.user
if not author.is_authenticated:
return Response(
{"detail": "please signin"},
status=status.HTTP_401_UNAUTHORIZED,
)
if post.author != author:
return Response(
{"detail": "You are not the author of this post."},
status=status.HTTP_403_FORBIDDEN,
)
title = request.data.get("title")
content = request.data.get("content")
if not title or not content:
return Response(
{"detail": "[title, content] fields missing."},
status=status.HTTP_400_BAD_REQUEST,
)
post.title = title
post.content = content
tag_contents = request.data.get("tags")
if tag_contents is not None:
post.tags.clear()
for tag_content in tag_contents:
if not Tag.objects.filter(content=tag_content).exists():
post.tags.create(content=tag_content)
else:
post.tags.add(Tag.objects.get(content=tag_content))
post.save()
serializer = PostSerializer(instance=post)
return Response(serializer.data, status=status.HTTP_200_OK)
좋아요 기능
class LikeView(APIView):
@swagger_auto_schema(
operation_id="좋아요 토글",
operation_description="좋아요를 토글합니다. 이미 좋아요가 눌려있으면 취소합니다.",
request_body=SignInRequestSerializer,
responses={200: PostSerializer, 404: "Not Found", 400: "Bad Request"},
manual_parameters=[openapi.Parameter("Authorization", openapi.IN_HEADER, description="access token", type=openapi.TYPE_STRING)]
)
def post(self, request, post_id):
try:
post = Post.objects.get(id=post_id)
except:
return Response(
{"detail": "Post not found."}, status=status.HTTP_404_NOT_FOUND
)
author = request.user
if not author.is_authenticated:
return Response(
{"detail": "please signin"},
status=status.HTTP_401_UNAUTHORIZED,
)
is_liked = post.like_set.filter(user=author).count() > 0
if is_liked == True:
post.like_set.get(user=author).delete()
print("좋아요 취소")
else:
Like.objects.create(user=author, post=post)
print("좋아요 누름")
serializer = PostSerializer(instance=post)
return Response(serializer.data, status=status.HTTP_200_OK)
댓글 작성 기능
class CommentListView(APIView):
```
@swagger_auto_schema(
operation_id="댓글 생성",
operation_description="특정 게시글에 댓글을 생성합니다.",
request_body=ComentListRequestSerializer,
responses={
201: CommentSerializer,
400: "Bad Request",
404: "Not Found",
403: "Forbidden",
},
manual_parameters=[openapi.Parameter("Authorization", openapi.IN_HEADER, description="access token", type=openapi.TYPE_STRING)]
)
def post(self, request):
author = request.user
if not author.is_authenticated:
return Response(
{"detail": "please signin"}, status=status.HTTP_401_UNAUTHORIZED
)
post_id = request.data.get("post")
content = request.data.get("content")
if not post_id or not content:
return Response(
{"detail": "missing fields ['post', 'content']"},
status=status.HTTP_400_BAD_REQUEST,
)
if not Post.objects.filter(id=post_id).exists():
return Response(
{"detail": "Post not found."}, status=status.HTTP_404_NOT_FOUND
)
comment = Comment.objects.create(
post_id=post_id, author=author, content=content
)
serializer = CommentSerializer(comment)
return Response(serializer.data, status=status.HTTP_201_CREATED)
댓글 삭제 및 수정
class CommentDetailView(APIView):
@swagger_auto_schema(
operation_id="댓글 수정",
operation_description="특정 댓글을 수정합니다.",
request_body=ComentDetailRequestSerializer,
responses={
200: CommentSerializer,
400: "Bad Request",
404: "Not Found",
401: "Unauthorized",
},
manual_parameters=[openapi.Parameter("Authorization", openapi.IN_HEADER, description="access token", type=openapi.TYPE_STRING)]
)
def put(self, request, comment_id):
author = request.user
if not author.is_authenticated:
return Response(
{"detail": "please signin"}, status=status.HTTP_401_UNAUTHORIZED
)
content = request.data.get("content")
try:
comment = Comment.objects.get(id=comment_id)
except:
return Response(
{"detail": "Comment not found."}, status=status.HTTP_404_NOT_FOUND
)
if author != comment.author:
return Response(
{"detail": "You are not the author of this comment."},
status=status.HTTP_403_FORBIDDEN,
)
comment.content = content
serializer = CommentSerializer(comment, data=request.data, partial=True)
if not serializer.is_valid():
return Response(
{"detail": "data validation error"}, status=status.HTTP_400_BAD_REQUEST
)
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
@swagger_auto_schema(
operation_id="댓글 삭제",
operation_description="특정 댓글을 삭제합니다.",
request_body=SignInRequestSerializer,
responses={
204: "No Content",
400: "Bad Request",
404: "Not Found",
401: "Unauthorized",
},
manual_parameters=[openapi.Parameter("Authorization", openapi.IN_HEADER, description="access token", type=openapi.TYPE_STRING)]
)
def delete(self, request, comment_id):
author = request.user
if not author.is_authenticated:
return Response(
{"detail": "please signin"}, status=status.HTTP_401_UNAUTHORIZED
)
try:
comment = Comment.objects.get(id=comment_id)
except:
return Response({"detail": "Not found."}, status=status.HTTP_404_NOT_FOUND)
if author != comment.author:
return Response(
{"detail": "You are not the author of this comment."},
status=status.HTTP_403_FORBIDDEN,
)
comment.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
정리하자면 아래 코드로 인가 연부를 계속 확인한다
if not request.user.is_authenticated:
return Response({"detail": "please signin"}, status=status.HTTP_401_UNAUTHORIZED)
'web > django' 카테고리의 다른 글
GraduArt backend 구현 중에 겪은 문제 및 trouble shooting (2) | 2024.11.06 |
---|---|
Django Access Token 재발급 API (0) | 2024.05.20 |
Django JWT 토큰 기반의 인증 방식 환경 세팅하기 (0) | 2024.05.20 |
Django Cookie, Session, 그리고 JWT (0) | 2024.05.20 |
Django User 모델과 admin page (0) | 2024.05.20 |