TIL

23.05.10 TIL

best_spear_man 2023. 5. 11. 11:42

오늘은...

팀 프로젝트를 진행하였다. 팀프로젝트에서 회원탈퇴와 내 회원정보 조회, 내 회원 정보 수정(비밀번호 수정)기능을 구현하고 그 테스트 코드를 작성하였다.

 

1. 탈퇴 구현

def put(self, request):
        user = request.user
        user = get_object_or_404(User, id=user.id)
        serializer = UserSignOutSerializer(user, request.data)
        if serializer.is_valid():
            user.is_active = False
            user.save()
            return Response({"message": "signout_success"}, status=status.HTTP_200_OK)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

유저 데이터를 삭제하지 않고, 로그인이 불가능하도록 is_active를 false로 바꿈으로서 탈퇴를 구현하였다. 이렇게 하면 이미 탈퇴한 유저가 자신의 회원정보를 요청하거나 계정 복구를 요청할 때 원활한 처리가 가능하다는 장점이 있다.

 

2. 유저 수정

def patch(self, request):
        """
        회원정보 변경을 위해 current_passoword를 입력받아 현재 로그인 중인 유저(request.user)와 비교 후
        일치하면 해당 유저의 정보(password...)를 수정한다.
        """
        user = get_object_or_404(User, id=request.user.id)
        serialized = UserEditSerializer(user, request.data, partial=True)
        if serialized.is_valid():
            serialized.save()
            return Response({"message": "변경성공"}, status=status.HTTP_200_OK)
        return Response(serialized.errors, status=status.HTTP_400_BAD_REQUEST)
class UserEditSerializer(serializers.ModelSerializer):

    current_password = serializers.CharField(
        style={"input_type": "password"}, write_only=True
    )
    password2 = serializers.CharField(style={"input_type": "password"}, write_only=True)

    class Meta:
        model = User
        # 이 두가지는 변경하면 안됩니다.
        exclude = (
            "email",
            "username",
        )

    def update(self, instance, validated_data):
        """
        오버라이딩 하였습니다.
        불필요한 데이터를 제거해 오류를 제거한 뒤 비밀번호 부터 지정 하고 저장을 계속합니다.
        """
        validated_data.pop("password2", None)
        validated_data.pop("current_password", None)
        if validated_data.get("password"):
            instance.set_password(validated_data["password"])
            validated_data.pop("password", None)
        user = super().update(instance, validated_data)
        return user

    def validate(self, attrs):
        """
        입력된 현재 비밀번호의 유효성부터 검사합니다.
        """
        if not self.instance.check_password(attrs.get("current_password")):
            raise serializers.ValidationError(
                {"current password": "current password wrong."}
            )
        password_validation(attrs.get("password"), attrs.get("password2"))
        return super().validate(attrs)
def password_validation(password, password2):
    """
    비밀번호와 비밀번호 확인 입력을 검증하기 위한 코드입니다.
    둘의 일치를 먼저 확인하고 입력값 자체가 유효한지 regex를 이용해 확인합니다.
    """
    if password != password2:
        raise serializers.ValidationError({"password": "두 비밀번호가 일치하지 않습니다."})

    if password != None and not re.match(PASSWORD_REGEX, password):
        raise serializers.ValidationError(
            {
                "password": "비밀번호는 8자리~32자리, 한개 이상의 숫자/알파벳/특수문자(@,$,!,%,*,#,?,&)로 이루어져야합니다."
            }
        )

두 비밀번호 입력의 불일치과 값 자체의 유효성 검사를 하는 코드가 회원가입과 정보수정 둘에서 중복되어 따로 함수로 분리하였다. 상속과 오버라이딩으로 해결해보고 싶었으나, 둘중 하나가 부모가 되면 자식이 조부모의 메서드를 실행하기위해 부모의 메서드를 불러와야하므로 그냥 따로 분리하였다.

 

3. permission 설정

class SignOutAuthenticatedOnly(permissions.BasePermission):
    def has_permission(self, request, view):
        """
        POST 요청(회원가입)만은 비회원이라도 가능해야하므로 무조건 True
        이외에는 로그인한 유저 본인의 정보에 대한 것이므로 로그인 여부를 확인한다.
        """
        if request.method == "POST":
            return True
        return bool(request.user and request.user.is_authenticated)