diff --git a/users/serializers.py b/users/serializers.py index c7bc7e9..d7bef42 100644 --- a/users/serializers.py +++ b/users/serializers.py @@ -1,6 +1,11 @@ +import random +import string + from django.contrib.auth import get_user_model, password_validation from rest_framework import serializers +from users.models import UserConfirmCode + class UserSerializer(serializers.ModelSerializer): class Meta: @@ -15,3 +20,37 @@ def validate_password(self, data): def create(self, validated_data): user = get_user_model().objects.create_user(validated_data["email"], validated_data["username"], validated_data["password"]) return user + + +class UserConfirmCodeSerializer(UserSerializer): + def create(self, validated_data): + user = super().create(validated_data) + + confirm_code = "".join(random.choice(string.ascii_letters + string.digits) for i in range(6)) + user_confirm_code = UserConfirmCode.objects.create(code=confirm_code, user=user) + return user_confirm_code + + +class UserConfirmSerializer(serializers.Serializer): + username = serializers.CharField(max_length=128) + password = serializers.CharField(max_length=128, write_only=True) + code = serializers.CharField(max_length=32) + + def validate(self, data): + user = self.instance + if not user.check_password(data["password"]): + raise serializers.ValidationError("Password is incorrect") + + if user.is_confirmed: + raise serializers.ValidationError("User is already confirmed") + + confirm_code = UserConfirmCode.objects.get(user=user).code + if confirm_code != data["code"]: + raise serializers.ValidationError("Confirmation code is incorrect") + + return data + + def update(self, user, validated_data): + user.is_confirmed = True + user.save() + return user diff --git a/users/tests/views/test_confirm_user_view.py b/users/tests/views/test_confirm_user_view.py new file mode 100644 index 0000000..251ffa8 --- /dev/null +++ b/users/tests/views/test_confirm_user_view.py @@ -0,0 +1,93 @@ +import json + +from django.urls import reverse +from rest_framework import status +from rest_framework.test import APITestCase + +from users.models import User, UserConfirmCode + + +class SignupViewTest(APITestCase): + @classmethod + def setUpTestData(cls): + cls.user = User.objects.create_user( + email="testuser1@example.com", + username="testusername1", + password="testpassword", + ) + + cls.userconfirmcode = UserConfirmCode.objects.create( + code="abcdef", + user=cls.user, + ) + + def test_post_signup_success(self): + response = self.client.post( + path=reverse("confirm"), + data=json.dumps( + { + "username": "testusername1", + "password": "testpassword", + "code": "abcdef", + } + ), + content_type="application/json", + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_post_signup_fail_user_not_found(self): + response = self.client.post( + path=reverse("confirm"), + data=json.dumps( + { + "username": "testusername2", + "password": "testpassword", + "code": "abcdef", + } + ), + content_type="application/json", + ) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + def test_post_signup_fail_invalid_password(self): + response = self.client.post( + path=reverse("signup"), + data=json.dumps( + { + "username": "testusername1", + "password": "testpassword2", + "code": "abcdef", + } + ), + content_type="application/json", + ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_post_signup_fail_invalid_code(self): + response = self.client.post( + path=reverse("signup"), + data=json.dumps( + { + "username": "testusername1", + "password": "testpassword", + "code": "aaaaaa", + } + ), + content_type="application/json", + ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + + def test_post_signup_fail_already_confirmed(self): + self.user.is_confirmed = True + response = self.client.post( + path=reverse("signup"), + data=json.dumps( + { + "username": "testusername1", + "password": "testpassword", + "code": "abcdef", + } + ), + content_type="application/json", + ) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) diff --git a/users/urls.py b/users/urls.py index 5c2df0d..4b70f0f 100644 --- a/users/urls.py +++ b/users/urls.py @@ -1,7 +1,5 @@ from django.urls import path -from users.views import SignupView +from users.views import ConfirmUserView, SignupView -urlpatterns = [ - path("signup/", SignupView.as_view(), name="signup"), -] +urlpatterns = [path("signup/", SignupView.as_view(), name="signup"), path("confirm/", ConfirmUserView.as_view(), name="confirm")] diff --git a/users/views.py b/users/views.py index 5be3e58..46c04ec 100644 --- a/users/views.py +++ b/users/views.py @@ -1,23 +1,29 @@ +from django.contrib.auth import get_user_model +from django.shortcuts import get_object_or_404 from drf_yasg.utils import swagger_auto_schema from rest_framework import status from rest_framework.request import Request from rest_framework.response import Response from rest_framework.views import APIView -from users.serializers import UserSerializer +from users.serializers import ( + UserConfirmCodeSerializer, + UserConfirmSerializer, + UserSerializer, +) class SignupView(APIView): @swagger_auto_schema( operation_summary="유저 회원가입", - request_body=UserSerializer, + request_body=UserConfirmCodeSerializer, responses={ - status.HTTP_201_CREATED: UserSerializer, + status.HTTP_201_CREATED: UserConfirmCodeSerializer, }, ) def post(self, request: Request) -> Response: """ - username, email, paswword를 받아 유저 계정을 생성합니다. + username, email, paswword를 받아 유저 계정과 인증 코드를 생성합니다. Args: email: 이메일 username: 이름 @@ -25,8 +31,43 @@ def post(self, request: Request) -> Response: Returns: email: 생성된 계정 이메일 username: 생성된 계정 이름 + code: 생성된 인증 코드 """ - serializer = UserSerializer(data=request.data) - serializer.is_valid(raise_exception=True) - serializer.save() - return Response(serializer.data, status=status.HTTP_201_CREATED) + user_confirm_code_serializer = UserConfirmCodeSerializer(data=request.data) + user_confirm_code_serializer.is_valid(raise_exception=True) + user_confirm_code = user_confirm_code_serializer.save() + + response_data = UserSerializer(user_confirm_code.user).data + response_data["confirm_code"] = user_confirm_code.code + return Response(response_data, status=status.HTTP_201_CREATED) + + +class ConfirmUserView(APIView): + @swagger_auto_schema( + operation_summary="유저 가입 승인", + request_body=UserConfirmSerializer, + responses={ + status.HTTP_200_OK: UserConfirmSerializer, + }, + ) + def post(self, request: Request) -> Response: + """ + username, paswword, code를 받아 code가 user의 인증코드와 같을 경우 회원가입을 승인합니다. + Args: + username: 이름 + password: 비밀번호 + code: 인증 코드 + Returns: + username: 이름 + is_confirmed: 인증 여부 + """ + user = get_object_or_404(get_user_model(), username=request.data["username"]) + user_confirm_serializer = UserConfirmSerializer(user, data=request.data) + user_confirm_serializer.is_valid(raise_exception=True) + user_confirm_serializer.save() + + response_data = {} + response_data["username"] = user.username + response_data["is_confirmed"] = user.is_confirmed + + return Response(response_data, status=status.HTTP_200_OK)