본문 바로가기

웹 개발

[Django] DRF Project - 4

오늘 구현해볼 부분은 회원 정보 수정과 탈퇴에 관한 부분이다.

  • 본인 정보 수정
    • 조건: 이메일, 이름, 닉네임, 생일 입력 필요하며, 성별, 자기소개 생략 가능
    • 검증: 로그인 한 사용자만 본인 프로필 수정 가능. 수정된 이메일은 기존 다른 사용자의 이메일과 username은 중복되면 안 됨.
    • 구현: 입력된 정보를 검증 후 데이터베이스를 업데이트.
  • 회원 탈퇴
    • 조건: 로그인 상태, 비밀번호 재입력 필요.
    • 검증: 입력된 비밀번호가 기존 비밀번호와 일치해야 함.
    • 구현: 비밀번호 확인 후 계정 삭제.

정보 수정은 프로필 확인과 똑같은 url로 요청이 들어오기 때문에 같은 클래스에서 작업했다.

class UserProfileView(APIView):
    permission_classes = [IsAuthenticated]
    
    def put(self, request, username):
        user = get_object_or_404(User, username=username)
        if user == request.user:
            is_valid, error_message = validate_update_user(request.data)
            if not is_valid:
                return Response({"error": error_message}, status=status.HTTP_400_BAD_REQUEST)
            serializer = UserSerializer(user, data=request.data, partial=True)
            if serializer.is_valid():
                serializer.save()
            return Response(serializer.data)

        else:
            return Response({"message": "수정 권한이 없습니다."}, status=status.HTTP_401_UNAUTHORIZED)
def validate_update_user(user_data):
    username = user_data.get("username")
    first_name = user_data.get("first_name")
    last_name = user_data.get("last_name")
    email = user_data.get("email")
    nickname = user_data.get("nickname")
    birthday = user_data.get("birthday")
    gender = user_data.get("gender")
    self_introduction = user_data.get("self_introduction")

    error_messages = []

    if username and User.objects.filter(username=username).exists():
        error_messages.append({"username": "이미 존재하는 username입니다."})

    if nickname and len(nickname) > 50:
        error_messages.append({"nickname": "닉네임의 길이는 50자 이하여야 합니다."})

    if first_name and len(first_name) > 50:
        error_messages.append({"first_name": "이름이 너무 깁니다."})
    if last_name and len(last_name) > 50:
        error_messages.append({"last_name": "성이 너무 깁니다."})

    if email:
        try:
            validate_email(email)
        except:
            error_messages.append({"email": "email 형식이 잘못되었습니다."})
        if User.objects.filter(email=email).exists():
            error_messages.append({"email": "이미 존재하는 email입니다."})

    if birthday:
        try:
            datetime.strptime(birthday, '%Y-%m-%d')
        except:
            error_messages.append({"birthday": "생일 형식은 YYYY-MM-DD여야 합니다."})

    return not bool(error_messages), error_messages

사실 회원가입을 만들었다면 회원 정보를 수정하는 로직을 구현하는 것은 어렵지 않다고 생각한다.

 

우선 로그인한 유저가 자신의 정보를 수정하는 것인지 확인하고

맞다면 validate_update_user 함수를 불러와서 검증을 시작한다.

받은 정보에 에러가 없다면 데이터베이스에 반영하고 업데이트된 유저 정보를 돌려주면 되는 것이다.

여기서 의문점이 생겼는데 내가 직접 검증 함수를 만들어서 유효성을 검증했어도,

직렬화 이후에 데이터베이스에 반영하기 위해서는 is_valid()를 이용해서 다시 검증을 해야한다는 점이다.

이거는 에러가 나서 이렇게 구현했지만 다른 방법이 있는지 찾아보면 좋을 것 같다.

 

 

회원 탈퇴는 회원 가입과 같은 url로 요청이 들어온다.

def delete(self, request):
        password = request.data.get('password')
        user = request.user
        if not user.check_password(password):
            return Response({"error": "비밀번호가 일치하지 않습니다."}, status=status.HTTP_400_BAD_REQUEST)

        user.soft_delete()
        return Response({"message": "회원탈퇴에 성공했습니다."})
class User(AbstractUser):

    def soft_delete(self):
        self.is_active = False
        self.save()

회원 탈퇴를 구현하는데 있어서 두 가지 방법이 있다.

  1. 유저의 정보를 데이터베이스에서 삭제한다.
  2. 유저의 계정을 비활성화시켜서 로그인을 막는다.

이번에는 2번째 방법을 썼는데, 내가 만든 상품 기능에서는 유저가 데이터베이스에서 삭제가 된다면

그 유저가 쓴 게시물도 전부 삭제가 되도록 CASCADE를 on_delete 속성에 넣었다.

회원 탈퇴를 한다고 해서 바로 관련된 모든 것들을 삭제하면 불편한 점들이 많다고 한다.

유저의 정보를 삭제하는 것을 hard delete라고 하는데 soft delete는 삭제가 아니라 사용 불가능한 상태로 만드는 것이다.

 

우선 회원 탈퇴 기능은 먼저 비밀번호를 받아서 장고의 user가 제공하는

check_password 메소드를 이용해서 비밀번호가 맞는지 체크한다.

비밀번호가 유저의 비밀번호가 맞다면 User 모델에서 새로 정의한 soft_delete 메소드를 실행시키는데

Django가 기본적으로 제공하는 is_active 필드의 값을 False로 바꿔서 유저의 계정 사용을 막는 것이다.

실무에서는 유저의 정보를 직접 삭제하는 일은 SQL을 이용해서 데이터베이스에 직접 접근하는 일이 많으므로

회원 탈퇴 기능을 구현할 때는 위와 같이 계정의 사용을 막는 방향으로 구현하도록 해보자.

'웹 개발' 카테고리의 다른 글

[Django] Django 팀 프로젝트 - 프로젝트 설계  (2) 2024.09.11
[Django] Pagination  (1) 2024.09.11
[Django] DRF Project - 3  (0) 2024.09.05
[Django] DRF Project - 2  (0) 2024.09.05
[Django] DRF Project - 1  (0) 2024.09.03