From bd660354622d7666d5535c1c40e925c3591bb57f Mon Sep 17 00:00:00 2001 From: ybkang1108 Date: Fri, 9 Aug 2024 14:25:35 +0900 Subject: [PATCH 1/5] =?UTF-8?q?fix:=20=EA=B6=8C=ED=95=9C=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- restaurants/models.py | 4 +++- restaurants/views.py | 14 ++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/restaurants/models.py b/restaurants/models.py index b010332..8b98886 100644 --- a/restaurants/models.py +++ b/restaurants/models.py @@ -23,7 +23,9 @@ class Restaurant(models.Model): def rating_average(self): ratings = [self.rating_naver, self.rating_kakao, self.rating_google] - valid_ratings = [rating for rating in ratings if rating is not None] + valid_ratings = [ + rating for rating in ratings if rating is not None and rating > 0 + ] if valid_ratings: return sum(valid_ratings) / len(valid_ratings) return 0 diff --git a/restaurants/views.py b/restaurants/views.py index 42251dc..1446944 100644 --- a/restaurants/views.py +++ b/restaurants/views.py @@ -12,8 +12,8 @@ ) # from rest_framework.authentication import TokenAuthentication -# from rest_framework.permissions import IsAuthenticated -# from django.contrib.auth.decorators import login_required +from rest_framework.decorators import permission_classes +from rest_framework.permissions import IsAuthenticated from django.views.decorators.csrf import csrf_exempt import logging from accounts.models import User # 임시 유저 지정을 위한 임포트, 추후 삭제 @@ -31,7 +31,7 @@ def restaurant_list(request): @api_view(["GET", "POST", "DELETE"]) # @authentication_classes([TokenAuthentication]) -# @permission_classes([IsAuthenticated]) +@permission_classes([IsAuthenticated]) def search(request): user = User.objects.get(id=21) # 임시 유저 지정, 추후 삭제 if request.method == "GET": @@ -102,7 +102,8 @@ def search(request): @csrf_exempt @api_view(["GET"]) -# @login_required +# @authentication_classes([TokenAuthentication]) +@permission_classes([IsAuthenticated]) def user_restaurant_list(request): try: user = User.objects.get(id=21) # 임시 유저 지정, 추후 삭제 @@ -120,7 +121,8 @@ def user_restaurant_list(request): @csrf_exempt @api_view(["POST", "DELETE"]) -# @login_required +# @authentication_classes([TokenAuthentication]) +@permission_classes([IsAuthenticated]) def add_remove_restaurant(request, pk): user = User.objects.get(id=21) # 임시 유저 지정, 추후 삭제 try: @@ -158,7 +160,7 @@ def add_remove_restaurant(request, pk): @api_view(["GET"]) # @authentication_classes([TokenAuthentication]) -# @permission_classes([IsAuthenticated]) +@permission_classes([IsAuthenticated]) def restaurant_detail(request, pk): try: restaurant = Restaurant.objects.prefetch_related("reviews").get(pk=pk) From 4bab18aef907f2d0693ada34bd96eadc6fbae3d3 Mon Sep 17 00:00:00 2001 From: ybkang1108 Date: Fri, 9 Aug 2024 14:28:22 +0900 Subject: [PATCH 2/5] =?UTF-8?q?fix:=20settings.py=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mustgou/settings.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/mustgou/settings.py b/mustgou/settings.py index ba164c0..3d6dc56 100644 --- a/mustgou/settings.py +++ b/mustgou/settings.py @@ -12,6 +12,7 @@ from pathlib import Path, os from decouple import config +import datetime # Build paths inside the project like this: BASE_DIR / 'subdir'. @@ -27,7 +28,7 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = ["*", ".pythonanywhere.com"] +ALLOWED_HOSTS = ["*"] # Application definition @@ -40,6 +41,7 @@ "django.contrib.messages", "django.contrib.staticfiles", "rest_framework", + "rest_framework_simplejwt", "rest_framework.authtoken", "corsheaders", "accounts", @@ -123,7 +125,29 @@ # } +REST_FRAMEWORK = { + # "DEFAULT_AUTHENTICATION_CLASSES": [ + # "rest_framework_simplejwt.authentication.JWTAuthentication", + # ], + "DEFAULT_PERMISSION_CLASSES": ( + "rest_framework.permissions.IsAuthenticated", + "rest_framework.permissions.IsAdminUser", + "rest_framework.permissions.AllowAny", + ), +} + + +JWT_AUTH = { + "JWT_SECRET_KEY": SECRET_KEY, + "JWT_ALGORITHM": "HS256", # 암호화 알고리즘 + "JWT_ALLOW_REFRESH": True, # refresh 사용 여부 + "JWT_EXPIRATION_DELTA": datetime.timedelta(days=7), # 유효기간 설정 + "JWT_REFRESH_EXPIRATION_DELTA": datetime.timedelta(days=28), # JWT 토큰 갱신 유효기간 +} + + CORS_ALLOWED_ORIGINS = config("CORS_ALLOWED_ORIGINS", default="").split(",") +CORS_ALLOW_CREDENTIALS = True CORS_ALLOW_METHODS = ( "DELETE", From 8854ebb98b53f3ed5fd34b050d26637a55a36352 Mon Sep 17 00:00:00 2001 From: ybkang1108 Date: Fri, 9 Aug 2024 14:33:49 +0900 Subject: [PATCH 3/5] =?UTF-8?q?fix:=20friends=EC=97=90=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=ED=86=B5=ED=95=A9=20friend=5Flist,=20friend=5Frequ?= =?UTF-8?q?est,=20friend-delete=EB=A5=BC=20friends=EC=97=90=20=ED=95=A9?= =?UTF-8?q?=EC=B9=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- friends/urls.py | 5 +- friends/views.py | 377 ++++++++++++++++++++++++----------------------- 2 files changed, 194 insertions(+), 188 deletions(-) diff --git a/friends/urls.py b/friends/urls.py index c853796..486e1c3 100644 --- a/friends/urls.py +++ b/friends/urls.py @@ -7,9 +7,6 @@ views.friend_restaurant_list, name="friend-restaurant-list", ), - # 추가 기능을 테스트하기 위한 url으로 실제로는 /friend 안에서 모두 진행됨 - path("friend-request/", views.FriendRequestView.as_view(), name="friend-request"), - path("friends/", views.delete_friend, name="delete-friend"), - path("friends/", views.friend_list, name="friend-list"), + path("friends/", views.friends, name="friends"), path("friend-recommend/", views.friend_recommend, name="friend-recommend"), ] diff --git a/friends/views.py b/friends/views.py index 5e893b9..adfc53e 100644 --- a/friends/views.py +++ b/friends/views.py @@ -1,9 +1,9 @@ # from django.shortcuts import render from rest_framework.decorators import api_view -from rest_framework.views import APIView # from rest_framework.authentication import TokenAuthentication -# from rest_framework.permissions import IsAuthenticated +from rest_framework.permissions import IsAuthenticated +from rest_framework.decorators import permission_classes from rest_framework.response import Response from rest_framework import status @@ -30,7 +30,7 @@ @api_view(["GET", "POST"]) # @authentication_classes([TokenAuthentication]) -# @permission_classes([IsAuthenticated]) +@permission_classes([IsAuthenticated]) def friend_restaurant_list(request, pk): try: friend = User.objects.get(pk=pk) @@ -75,62 +75,212 @@ def friend_restaurant_list(request, pk): ) -@api_view(["GET"]) +@api_view(["GET", "POST", "DELETE"]) # @authentication_classes([TokenAuthentication]) -# @permission_classes([IsAuthenticated]) -def friend_list(request): - try: - # user = request.user - user = User.objects.get(id=21) +@permission_classes([IsAuthenticated]) +def friends(request): + # friends_list + if request.method == "GET": + try: + # user = request.user + user = User.objects.get(id=21) + + friend_request = FriendRequest.objects.filter(to_user=user, state="pending") + friend_request_serialized = FriendRequestSerializer( + friend_request, context={"request": request}, many=True + ).data - friend_request = FriendRequest.objects.filter(to_user=user, state="pending") - friend_request_serialized = FriendRequestSerializer( - friend_request, context={"request": request}, many=True - ).data + friends = Friend.objects.filter(user=user) + friends_serialized = FriendSerializer(friends, many=True).data - friends = Friend.objects.filter(user=user) - friends_serialized = FriendSerializer(friends, many=True).data + user_restaurants = set( + UserRestaurantsList.objects.filter(user=user).values_list( + "restaurant_id", flat=True + ) + ) - user_restaurants = set( - UserRestaurantsList.objects.filter(user=user).values_list( - "restaurant_id", flat=True + friend_ids = friends.values_list("friend_id", flat=True) + potential_friends = ( + User.objects.exclude(id=user.id) + .exclude(id__in=friend_ids) + .annotate( + common_restaurant_count=Count( + "userrestaurantslist__restaurant_id", + filter=Q( + userrestaurantslist__restaurant_id__in=user_restaurants + ), + ) + ) + .order_by("-common_restaurant_count")[:7] ) - ) - friend_ids = friends.values_list("friend_id", flat=True) - potential_friends = ( - User.objects.exclude(id=user.id) - .exclude(id__in=friend_ids) - .annotate( - common_restaurant_count=Count( - "userrestaurantslist__restaurant_id", - filter=Q(userrestaurantslist__restaurant_id__in=user_restaurants), + friend_recommend_serialized = FriendRecommendSerializer( + potential_friends, + many=True, + context={ + "request": request, + "user": user, + "include_restaurants": False, + }, + ).data + + data = { + "friend_request": friend_request_serialized, + "friends": friends_serialized, + "friend_recommend": friend_recommend_serialized, + } + + return Response(data) + + except User.DoesNotExist: + return Response( + {"message": "User not found"}, status=status.HTTP_404_NOT_FOUND + ) + + # friend_request + elif request.method == "POST": + action = request.data.get("action") + friend_id = request.data.get("friend_id") + + # action의 대상이 자기 자신인 경우 + if friend_id == request.user.id: + return Response( + {"message": "자기 자신에게는 요청을 보낼 수 없습니다."}, + status=status.HTTP_400_BAD_REQUEST, + ) + + # 친구 신청 + elif action == "send": + to_user = get_object_or_404(User, id=friend_id) + from_user = request.user + + # 이미 요청을 보냈는지 확인 + if FriendRequest.objects.filter( + from_user=from_user, to_user=to_user, state="pending" + ).exists(): + return Response( + {"message": "이미 친구 요청을 보냈습니다."}, status=status.HTTP_400_BAD_REQUEST ) + # 이미 친구 관계인지 확인, Friend는 양방향이므로 한쪽만 확인해도 됨 + if Friend.objects.filter(user=from_user, friend=to_user).exists(): + return Response( + {"message": f"이미 {to_user.name}님과 친구입니다."}, + status=status.HTTP_400_BAD_REQUEST, + ) + + # 거절된 요청이 있다면 지우고 다시 요청 + declined_request = FriendRequest.objects.filter( + from_user=from_user, to_user=to_user, state="declined" ) - .order_by("-common_restaurant_count")[:7] - ) + if declined_request.exists(): + declined_request[0].delete() - friend_recommend_serialized = FriendRecommendSerializer( - potential_friends, - many=True, - context={"request": request, "user": user, "include_restaurants": False}, - ).data + friend_request = FriendRequest( + from_user=from_user, to_user=to_user, state="pending" + ) + friend_request.save() - data = { - "friend_request": friend_request_serialized, - "friends": friends_serialized, - "friend_recommend": friend_recommend_serialized, - } + return Response( + {"message": "친구 요청을 보냈습니다."}, status=status.HTTP_201_CREATED + ) - return Response(data) + # 친구 수락 + elif action == "accept": + # 친구 요청을 보낸 사용자의 id와 일치하는 요청이 있는지 확인 + friend_request = get_object_or_404( + FriendRequest, from_user__id=friend_id, to_user=request.user + ) - except User.DoesNotExist: - return Response({"message": "User not found"}, status=status.HTTP_404_NOT_FOUND) + if friend_request.to_user != request.user: + return Response( + {"message": "권한이 없습니다."}, status=status.HTTP_403_FORBIDDEN + ) + if friend_request.state != "pending": + return Response( + {"message": "이미 처리된 요청입니다."}, status=status.HTTP_400_BAD_REQUEST + ) + + friend_request.state = "accepted" + friend_request.save() + + # 내가 상대방에게 보낸 친구요청이 있다면 state를 accepted로 변경 + reverse_friend_request = FriendRequest.objects.filter( + from_user=request.user, to_user__id=friend_id + ) + if reverse_friend_request.exists(): + reverse_friend_request.update(state="accepted") + + # 양방향 친구 관계 설정 + from_user = friend_request.from_user + to_user = friend_request.to_user + + friend_relation = Friend.objects.create(user=from_user, friend=to_user) + friend_relation.save() + reverse_friend_relation = Friend.objects.create( + user=to_user, friend=from_user + ) + reverse_friend_relation.save() + + return Response({"message": "친구 신청을 수락했습니다."}, status=status.HTTP_200_OK) + + # 친구 거절 + elif action == "decline": + friend_request = get_object_or_404( + FriendRequest, from_user__id=friend_id, to_user=request.user + ) + + if friend_request.to_user != request.user: + return Response( + {"message": "권한이 없습니다."}, status=status.HTTP_403_FORBIDDEN + ) + if friend_request.state != "pending": + return Response( + {"message": "이미 처리된 요청입니다."}, status=status.HTTP_400_BAD_REQUEST + ) + + friend_request.state = "declined" + friend_request.save() + + return Response({"message": "친구 신청을 거절했습니다."}, status=status.HTTP_200_OK) + + else: + return Response( + {"message": "올바르지 않은 요청입니다."}, status=status.HTTP_400_BAD_REQUEST + ) + + # delete_friend + elif request.method == "DELETE": + friend_id = request.data.get("friend_id") + # 친구 객체 삭제 + friend = get_object_or_404(Friend, user=request.user, friend__id=friend_id) + friend.delete() + reverse_friend = get_object_or_404( + Friend, user__id=friend_id, friend=request.user + ) + reverse_friend.delete() + + # 친구 신청 객체 삭제 + friend_request = FriendRequest.objects.filter( + from_user=request.user, to_user__id=friend_id + ) + if friend_request.exists(): + friend_request[0].delete() + reverse_friend_request = FriendRequest.objects.filter( + from_user__id=friend_id, to_user=request.user + ) + if reverse_friend_request.exists(): + reverse_friend_request[0].delete() + + friend_name = get_object_or_404(User, id=friend_id).name + + return Response( + {"message": f"{friend_name}님이 친구목록에서 삭제되었습니다."}, status=status.HTTP_200_OK + ) @api_view(["GET"]) # @authentication_classes([TokenAuthentication]) -# @permission_classes([IsAuthenticated]) +@permission_classes([IsAuthenticated]) def friend_recommend(request): try: # user = request.user @@ -172,144 +322,3 @@ def friend_recommend(request): except User.DoesNotExist: return Response({"message": "User not found"}, status=status.HTTP_404_NOT_FOUND) - - -# 친구신청 -class FriendRequestView(APIView): - # authentication_classes = [TokenAuthentication] - # permission_classes = [IsAuthenticated] - - def post(self, request): - action = request.data.get("action") - friend_id = request.data.get("friend_id") - - if friend_id == request.user.id: - return Response( - {"message": "자기 자신에게는 요청을 보낼 수 없습니다."}, - status=status.HTTP_400_BAD_REQUEST, - ) - if action == "send": - return self.send_request(request, friend_id) - elif action == "accept": - return self.accept_request(request, friend_id) - elif action == "decline": - return self.decline_request(request, friend_id) - else: - return Response( - {"message": "올바르지 않은 요청입니다."}, status=status.HTTP_400_BAD_REQUEST - ) - - # 친구 요청 - def send_request(self, request, friend_id): - to_user = get_object_or_404(User, id=friend_id) - from_user = request.user - - # 이미 요청을 보냈는지 확인 - if FriendRequest.objects.filter( - from_user=from_user, to_user=to_user, state="pending" - ).exists(): - return Response( - {"message": "이미 친구 요청을 보냈습니다."}, status=status.HTTP_400_BAD_REQUEST - ) - # 이미 친구 관계인지 확인, Friend는 양방향이므로 한쪽만 확인해도 됨 - if Friend.objects.filter(user=from_user, friend=to_user).exists(): - return Response( - {"message": f"이미 {to_user.name}님과 친구입니다."}, - status=status.HTTP_400_BAD_REQUEST, - ) - - # 거절된 요청이 있다면 지우고 다시 요청 - declined_request = FriendRequest.objects.filter( - from_user=from_user, to_user=to_user, state="declined" - ) - if declined_request.exists(): - declined_request[0].delete() - - friend_request = FriendRequest( - from_user=from_user, to_user=to_user, state="pending" - ) - friend_request.save() - - return Response({"message": "친구 요청을 보냈습니다."}, status=status.HTTP_201_CREATED) - - # 친구 수락 - def accept_request(self, request, friend_id): - # 친구 요청을 보낸 사용자의 id와 일치하는 요청이 있는지 확인 - friend_request = get_object_or_404( - FriendRequest, from_user__id=friend_id, to_user=request.user - ) - - if friend_request.to_user != request.user: - return Response({"message": "권한이 없습니다."}, status=status.HTTP_403_FORBIDDEN) - if friend_request.state != "pending": - return Response( - {"message": "이미 처리된 요청입니다."}, status=status.HTTP_400_BAD_REQUEST - ) - - friend_request.state = "accepted" - friend_request.save() - - # 내가 상대방에게 보낸 친구요청이 있다면 state를 accepted로 변경 - reverse_friend_request = FriendRequest.objects.filter( - from_user=request.user, to_user__id=friend_id - ) - if reverse_friend_request.exists(): - reverse_friend_request.update(state="accepted") - - # 양방향 친구 관계 설정 - from_user = friend_request.from_user - to_user = friend_request.to_user - - friend_relation = Friend.objects.create(user=from_user, friend=to_user) - friend_relation.save() - reverse_friend_relation = Friend.objects.create(user=to_user, friend=from_user) - reverse_friend_relation.save() - - return Response({"message": "친구 신청을 수락했습니다."}, status=status.HTTP_200_OK) - - # 친구 거절 - def decline_request(self, request, friend_id): - friend_request = get_object_or_404( - FriendRequest, from_user__id=friend_id, to_user=request.user - ) - - if friend_request.to_user != request.user: - return Response({"message": "권한이 없습니다."}, status=status.HTTP_403_FORBIDDEN) - if friend_request.state != "pending": - return Response( - {"message": "이미 처리된 요청입니다."}, status=status.HTTP_400_BAD_REQUEST - ) - - friend_request.state = "declined" - friend_request.save() - - return Response({"message": "친구 신청을 거절했습니다."}, status=status.HTTP_200_OK) - - -@api_view(["DELETE"]) -# @authentication_classes([TokenAuthentication]) -# @permission_classes([IsAuthenticated]) -def delete_friend(request, friend_id): - # 친구 객체 삭제 - friend = get_object_or_404(Friend, user=request.user, friend__id=friend_id) - friend.delete() - reverse_friend = get_object_or_404(Friend, user__id=friend_id, friend=request.user) - reverse_friend.delete() - - # 친구 신청 객체 삭제 - friend_request = FriendRequest.objects.filter( - from_user=request.user, to_user__id=friend_id - ) - if friend_request.exists(): - friend_request[0].delete() - reverse_friend_request = FriendRequest.objects.filter( - from_user__id=friend_id, to_user=request.user - ) - if reverse_friend_request.exists(): - reverse_friend_request[0].delete() - - friend_name = get_object_or_404(User, id=friend_id).name - - return Response( - {"message": f"{friend_name}님이 친구목록에서 삭제되었습니다."}, status=status.HTTP_200_OK - ) From 94eba284b6d171798570899436983391b455ab11 Mon Sep 17 00:00:00 2001 From: ybkang1108 Date: Fri, 9 Aug 2024 14:34:18 +0900 Subject: [PATCH 4/5] =?UTF-8?q?fix:=20=EC=A4=91=EB=B3=B5=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- accounts/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accounts/admin.py b/accounts/admin.py index 03e4a1a..8f8ef1f 100644 --- a/accounts/admin.py +++ b/accounts/admin.py @@ -1,6 +1,5 @@ from django.contrib import admin from django.contrib.auth.admin import UserAdmin -from django.contrib.auth.admin import UserAdmin from .models import User @@ -15,4 +14,5 @@ class CustomUserAdmin(UserAdmin): "last_login", ) + admin.site.register(User, CustomUserAdmin) From b2996e831b6854a5bf92aa9bf2dffd928d12ef90 Mon Sep 17 00:00:00 2001 From: ybkang1108 Date: Fri, 9 Aug 2024 14:41:14 +0900 Subject: [PATCH 5/5] =?UTF-8?q?feat:=20jwt=20authentication=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- accounts/serializers.py | 35 ++-------- accounts/urls.py | 26 +++++-- accounts/views.py | 149 ++++++++++++++++++++++++++++++++++------ accounts/views.txt | 31 +++++++++ 4 files changed, 186 insertions(+), 55 deletions(-) create mode 100644 accounts/views.txt diff --git a/accounts/serializers.py b/accounts/serializers.py index 6accfde..ba96be2 100644 --- a/accounts/serializers.py +++ b/accounts/serializers.py @@ -1,15 +1,12 @@ from .models import User from friends.models import Friend -from django.contrib.auth.password_validation import validate_password # pw 검증 -from django.contrib.auth import authenticate from rest_framework import serializers -from rest_framework.authtoken.models import Token -from rest_framework.validators import UniqueValidator # 이메일 중복 방지 +from rest_framework.validators import UniqueValidator +from django.contrib.auth.password_validation import validate_password -# 회원가입 시리얼라이저 -class RegisterSerializer(serializers.ModelSerializer): +class UserSerializer(serializers.ModelSerializer): email = serializers.EmailField( required=True, validators=[UniqueValidator(queryset=User.objects.all())], # 이메일 중복 검증 @@ -34,39 +31,15 @@ def validate(self, data): # pw와 checkPw 확인 return data def create(self, validated_data): - # CREATE 요청이 들어오면 create 매서드를 오버라이딩하여 유저와 토큰 생성 user = User.objects.create_user( username=validated_data["email"], email=validated_data["email"], ) - user.name = validated_data["name"] user.set_password(validated_data["password"]) user.save() - Token.objects.create(user=user) - # token = Token.objects.create(user=user) - return user - -# 로그인 시리얼라이저 -class LoginSerializer(serializers.Serializer): - email = serializers.EmailField(required=True) - # write_only=True 를 통해 클라이언트->서버 만 가능하도록 설정 - password = serializers.CharField(required=True, write_only=True) - - def validate(self, data): - user = authenticate(**data) - if user: - token = Token.objects.get(user=user) - return { - "token": token.key, - "user_id": user.id, - "reliability": user.reliability, - "profile_img_url": user.profile_img.url, - } - - # 가입된 유저가 없을 경우 - raise serializers.ValidationError({"error": "유저 정보가 존재하지 않습니다."}) + return user class ProfileSerializer(serializers.ModelSerializer): diff --git a/accounts/urls.py b/accounts/urls.py index 4f59980..5caa6d6 100644 --- a/accounts/urls.py +++ b/accounts/urls.py @@ -1,8 +1,26 @@ -from django.urls import path -from .views import RegisterView, LoginView, profile +from django.urls import path, include +from .views import ( + RegisterView, + LoginView, + LogoutView, + UserViewSet, + DeleteUserView, + profile, +) + +# 뷰셋 +from rest_framework import routers +from rest_framework_simplejwt.views import TokenRefreshView + +router = routers.DefaultRouter() +router.register("list", UserViewSet) urlpatterns = [ - path("auth/register/", RegisterView.as_view()), - path("auth/login/", LoginView.as_view()), + path("register/", RegisterView.as_view()), + path("login/", LoginView.as_view()), + path("logout/", LogoutView.as_view()), + path("test/", include(router.urls)), + path("delete/", DeleteUserView.as_view()), + path("refresh/", TokenRefreshView.as_view()), # jwt 토큰 재발급 path("profile/", profile, name="profile"), ] diff --git a/accounts/views.py b/accounts/views.py index d138aa3..84163cd 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -1,38 +1,147 @@ -# from django.shortcuts import render -from .models import User -from rest_framework import generics, status +from rest_framework.views import APIView +from .serializers import UserSerializer, ProfileSerializer +from rest_framework_simplejwt.serializers import ( + TokenObtainPairSerializer, +) +from rest_framework import status from rest_framework.response import Response - -from .serializers import RegisterSerializer, LoginSerializer, ProfileSerializer from rest_framework.decorators import api_view +from django.contrib.auth.hashers import check_password +from rest_framework.permissions import IsAuthenticated, AllowAny +from rest_framework.decorators import permission_classes -# from rest_framework.authentication import TokenAuthentication -# from rest_framework.permissions import IsAuthenticated +from friends.models import Friend, FriendRequest +from .models import User +# 테스트용 +from rest_framework import viewsets -class RegisterView(generics.CreateAPIView): - queryset = User.objects.all() - serializer_class = RegisterSerializer +class RegisterView(APIView): + def post(self, request): + serializer = UserSerializer(data=request.data) + if serializer.is_valid(): + user = serializer.save() + + # jwt token 접근 + token = TokenObtainPairSerializer.get_token(user) + refresh_token = str(token) + access_token = str(token.access_token) + res = Response( + { + "user": serializer.data, + "message": "회원가입을 완료했습니다.", + "token": { + "access": access_token, + "refresh": refresh_token, + }, + }, + status=status.HTTP_200_OK, + ) + # jwt 토큰 => 쿠키에 저장 + res.set_cookie("access", access_token, httponly=True) + res.set_cookie("refresh", refresh_token, httponly=True) + + return res + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) -class LoginView(generics.GenericAPIView): - serializer_class = LoginSerializer +@permission_classes([AllowAny]) +class LoginView(APIView): def post(self, request): - serializer = self.get_serializer(data=request.data) - if serializer.is_valid(): - validated_data = serializer.validated_data + email = request.data.get("email") + password = request.data.get("password") + + if not email: + return Response({"error": "이메일을 입력하세요"}, status=status.HTTP_400_BAD_REQUEST) + if not password: return Response( + {"error": "비밀번호를 입력하세요"}, status=status.HTTP_400_BAD_REQUEST + ) + + user = User.objects.filter(email=email).first() + + # username에 맞는 user가 존재하지 않는 경우 + if user is None: + return Response( + {"message": "존재하지 않는 아이디입니다."}, status=status.HTTP_400_BAD_REQUEST + ) + + # 비밀번호가 틀린 경우, + if not check_password(password, user.password): + return Response( + {"message": "비밀번호가 틀렸습니다."}, status=status.HTTP_400_BAD_REQUEST + ) + + # user 정보가 일치 + if user is not None: + token = TokenObtainPairSerializer.get_token(user) # refresh 토큰 생성 + refresh_token = str(token) # refresh 토큰 문자열화 + access_token = str(token.access_token) # access 토큰 문자열화 + response = Response( { - "token": validated_data["token"], - "user_id": validated_data["user_id"], - "reliability": validated_data["reliability"], - "profile_img_url": validated_data["profile_img_url"], + "message": f"{user.name}님 안녕하세요!", + "user_id": user.id, + "reliability": user.reliability, + "profile_img_url": user.profile_img.url, + "jwt_token": { + "access_token": access_token, + "refresh_token": refresh_token, + }, }, status=status.HTTP_200_OK, ) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + response.set_cookie("access_token", access_token, httponly=True) + response.set_cookie("refresh_token", refresh_token, httponly=True) + return response + else: + return Response( + {"message": "로그인에 실패하였습니다."}, status=status.HTTP_400_BAD_REQUEST + ) + + +class LogoutView(APIView): + # 로그아웃 + def delete(self, request): + # 쿠키에 저장된 토큰 삭제 => 로그아웃 처리 + response = Response( + {"message": "Logout success"}, status=status.HTTP_202_ACCEPTED + ) + response.delete_cookie("access") + response.delete_cookie("refresh") + return response + + +# jwt 토근 인증 확인용 뷰셋 +# Header - Authorization : Bearer <발급받은토큰> +class UserViewSet(viewsets.ModelViewSet): + permission_classes = [IsAuthenticated] + queryset = User.objects.all() + serializer_class = UserSerializer + + +@permission_classes([IsAuthenticated]) +class DeleteUserView(APIView): + # 회원삭제 + def delete(self, request): + # 친구 삭제 + friend = Friend.objects.filter(user=request.user) + if friend.exists(): + friend.delete() + reverse_friend = Friend.objects.filter(friend=request.user) + if reverse_friend.exists(): + reverse_friend.delete() + # 친구 요청 삭제 + friend_request = FriendRequest.objects.filter(from_user=request.user) + if friend_request.exists(): + friend_request.delete() + reverse_friend_request = FriendRequest.objects.filter(to_user=request.user) + if reverse_friend_request.exists(): + reverse_friend_request.delete() + + request.user.delete() + return Response({"message": "회원탈퇴가 완료되었습니다."}, status=status.HTTP_202_ACCEPTED) @api_view(["GET", "PATCH"]) diff --git a/accounts/views.txt b/accounts/views.txt new file mode 100644 index 0000000..397678b --- /dev/null +++ b/accounts/views.txt @@ -0,0 +1,31 @@ +# from django.shortcuts import render +from .models import User +from rest_framework import generics, status +from rest_framework.response import Response + +from .serializers import RegisterSerializer, LoginSerializer + + +class RegisterView(generics.CreateAPIView): + queryset = User.objects.all() + serializer_class = RegisterSerializer + + +class LoginView(generics.GenericAPIView): + serializer_class = LoginSerializer + + def post(self, request): + serializer = self.get_serializer(data=request.data) + if serializer.is_valid(): + validated_data = serializer.validated_data + return Response( + { + "token": validated_data["token"], + "user_id": validated_data["user_id"], + "reliability": validated_data["reliability"], + "profile_img_url": validated_data["profile_img_url"], + }, + status=status.HTTP_200_OK, + ) + + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)