[0714] user 수정 및 park 조회

nikevapormax·2022년 7월 14일
0

TIL

목록 보기
73/116

iPark Project

user 수정

custom validator 수정

def validate(self, data):
        correct_phone = re.compile("(010)-\d{4}-\d{4}")
        correct_password = re.compile("^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$")
                
        phone_input = correct_phone.match(data.get("phone", ""))
        password_input = correct_password.match(data.get("password", ""))
        
        if data.get("username"):
            if not len(data.get("username", "")) >= 6:
                raise serializers.ValidationError(
                    detail={"error": "username의 길이는 6자리 이상이어야 합니다."})
                
        if not data.get("email", "").endswith(EMAIL):
            raise serializers.ValidationError(
                detail={"error": "네이버, 구글, 카카오, 다음, 네이트, 아웃룩 이메일만 가입할 수 있습니다."})
        
        if password_input == None:
            raise serializers.ValidationError(
                detail={"error": "비밀번호는 8 자리 이상이며 최소 하나 이상의 영문자, 숫자, 특수문자가 필요합니다."})
        
        if phone_input == None:
            raise serializers.ValidationError(
                detail={"error": "전화번호는 010-0000-0000 형식으로 작성해주시기 바랍니다."})
        
        return data
  • 어제 작성했던 코드에서 emailbirthday에 대한 정규표현식을 삭제하였다. 이유는 모델에서 정의한 필드에 의해 이미 걸러질 값들이 걸러지고 있었기 때문이다.
  • 살짝 걸리는 부분은 사용자가 생년월일을 입력할 때 MM-DD의 형식으로 쓰지 않고 3과 같이 한 자리 숫자를 입력하면 자동으로 03으로 변경되어 저장되는 것이다.
  • 이런 부분이 아니라면 알아서 다 걸러주어 위와 같이 코드를 수정했다.

비밀번호 변경

class AlterPasswordView(APIView):
    # 비밀번호를 변경할 자격이 있는지 확인
    def post(self, request):
        """
        1. 비밀번호를 변경할 사용자의 username, email을 입력받는다.
        2. 해당 값을 통해 비밀번호를 변경할 user를 찾아준다. 
        3. 만약 user가 존재한다면 user 정보를 비밀번호 수정 페이지에서도 알 수 있도록 넘겨준다.
        4. user가 존재하지 않는다면 "존재하지 않는 사용자입니다." 라는 메세지를 반환한다.
        """
        
        correct_email = re.compile("^[a-zA-Z0-9+-_.]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")
        email_input = correct_email.match(request.data["email"])
        
        if email_input == None:
            return Response({"message": "이메일 형식에 맞게 작성해주세요."})
        else:
            try: 
                user = UserModel.objects.get(Q(username=request.data["username"]) & Q(email=request.data["email"]))
                if user:
                    return Response({"message": "비밀번호 변경 페이지로 이동합니다."}, status=status.HTTP_200_OK)
            
            except UserModel.DoesNotExist:
                return Response({"message": "존재하지 않는 사용자입니다."}, status=status.HTTP_404_NOT_FOUND)
    
    # 비밀번호 변경
    def put(self, request):
        """
        1. 사용자의 정보를 그대로 가져온다. 
        2. 새롭게 세팅할 비밀번호와 중복 확인용 비밀번호를 받는다. 
        3. 이 두 비밀번호가 정규표현식을 통과하고 일치한다면, UserSerializer에 request.data를 보내 custom updator를 통해 비밀번호를 update해준다.
        """
        
        correct_password = re.compile("^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$")
        
        if request.data["new_password"] == "" or request.data["rewrite_password"] == "":
            return Response({"message": "비밀번호를 제대로 입력해주세요."}, status=status.HTTP_400_BAD_REQUEST)
        else:
            if request.data["new_password"] == request.data["rewrite_password"]:
                password_input = correct_password.match(request.data["new_password"])
                
                if password_input == None:
                    return Response({"message": "비밀번호를 양식에 맞게 작성해주세요."}, status=status.HTTP_400_BAD_REQUEST)
                else:
                    user = UserModel.objects.get(Q(username=request.data["username"]) & Q(email=request.data["email"]))
                    user.set_password(request.data["new_password"])
                    user.save()
                
                    return Response({"message": "비밀번호 변경이 완료되었습니다! 다시 로그인해주세요."}, status=status.HTTP_200_OK)
            
            return Response({"message": "두 비밀번호가 일치하지 않습니다."})
  • 비밀번호를 잊어버려 변경하고 싶어하는 사용자의 정보를 받아올 때, 존재하지 않는 값이 들어간 경우 원래 if 조건문만 사용했어서 쿼리매칭 에러가 나왔었다.
  • 따라서 try except를 사용해 쿼리매칭 에러 대신 존재하지 않는 사용자라는 메세지를 사용자에게 전달해 올바른 값을 입력하거나, 정말 존재하지 않는다면 사용할 수 없도록 조치하였다.
  • 비밀번호를 변경하는 과정에서도 아침에는 정규표현식을 제거했었는데 다시 생각해보니 비밀번호를 db를 통해 검증할 방법이 없었다. 왜냐하면 비밀번호가 양식에 맞게 쓰여야 비로소 db로 들어가 사용자의 정보를 토대로 사용자를 찾고 비밀번호를 set_password()를 통해 해싱해 저장하기 때문이다.
  • 따라서 다시 정규표현식을 사용해 사용자가 올바른 양식의 비밀번호를 입력한 경우 변경할 수 있도록 하였다.

비밀번호 변경 테스트 코드

# 비밀번호 변경 테스트
class AlterPasswordTest(APITestCase):
    @classmethod
    def setUpTestData(cls):
        user_data = {
            "username" : "user10",
            "password" : "1010abc!",
            "fullname" : "user10",
            "email" : "user10@gmail.com",
            "phone" : "010-1010-1010",
            "birthday" : "2022-07-13",
        }
        cls.user = UserModel.objects.create(**user_data)
    
    # 비밀번호를 변경할 자격이 있는지 확인
    def test_post_user_info(self):
        url = reverse("alter_password_view")
        user_data = {
            "username" : "user10",
            "email" : "user10@gmail.com"
        }
        
        response = self.client.post(url, user_data)
        
        self.assertEqual(response.data["message"], "비밀번호 변경 페이지로 이동합니다.")
    
    # 비밀번호 변경    
    def test_alter_password(self):
        url = reverse("alter_password_view")
        password_data = {
            "username" : "user10",
            "email" : "user10@gmail.com",
            "new_password" : "abcde10!",
            "rewrite_password" : "abcde10!"
        }
        
        response = self.client.put(url, password_data)
        
        self.assertEqual(response.data["message"], "비밀번호 변경이 완료되었습니다! 다시 로그인해주세요.")
  • 먼저 테스트 db에 테스트 user를 생성해주었다.
  • 이를 바탕으로 사용자를 검증하고, 검증된 사용자가 비밀번호를 바꿀 수 있는지 검사하였다.

park 조회

공원 검색 페이지

  • 검색 페이지에서 필요한 일반 필터링 검색에 대해 구현하였다.
  • 또한 조회수가 많은 공원을 순서대로 보여주었고, 조회수가 아예 없는 공원은 보여지지 않도록 조치하였다.
# 검색 페이지
class ParkSearchView(APIView):
    def get(self, request):
        options = request.query_params.getlist("option", "")

        query = Q()  
        for option in options:
            query.add(Q(option__option_name=option), Q.OR)
            
        results = ParkModel.objects.filter(query)

        if results.exists():
            serializer = ParkSerializer(results, many=True)
            return Response(serializer.data, status=status.HTTP_200_OK)

        return Response({"message": "공원을 찾을 수 없습니다."}, status=status.HTTP_404_NOT_FOUND)
    
    
# 공원 인기순 검색
class ParkPopularityView(APIView):
    def get(self, request):
        popular_park = ParkModel.objects.filter(check_count__gte=1).order_by("-check_count")
        popular_serializer = ParkSerializer(popular_park, many=True)
        
        return Response(popular_serializer.data)
  • 현재 공원을 검색하는 부분에 문제가 조금 있다.
    • option 값이 하나만 들어올 경우 조회가 되지 않는다.
    • 예를 들어, 서울대공원의 경우 3번 옵션인 놀이공원을 가지고 있는데, 서울대공원에서 갖고 있지 않는 운동 옵션과 같이 옵션이 백앤드로 날아오면 조회가 되지 않는다.
    • 특정 옵션들을 검색하고 싶어 특정 옵션을 선택해 넣으면 조회가 되지 않는다. (심지어 db에 있는 값임)
  • 조회수가 많은 순으로 공원을 보여주는 부분은 잘 된다.
  • check_count 데이터가 0인 공원들은 보이지 않도록 조치하였다.
  • 위의 공원 검색 부분을 아래와 같이 수정하였다.
# 검색 페이지
class ParkSearchView(APIView):
    def get(self, request):
        options = request.query_params.getlist("option", "")
        
        if len(options) == 1:
            results = ParkModel.objects.filter(option__option_name__contains=request.query_params.get("option", "")).distinct()
        else:
            results = ParkModel.objects.filter(option__option_name__in=options).distinct()
            print(results)

        if results.exists():
            serializer = ParkSerializer(results, many=True)
            return Response(serializer.data, status=status.HTTP_200_OK)

        return Response({"message": "공원을 찾을 수 없습니다."}, status=status.HTTP_404_NOT_FOUND)
  • __contains를 사용해 하나의 option이 들어오더라도 그것을 포함한 공원들을 보여주도록 하였다.
  • __in을 사용해 여러 개의 option이 들어오면 그것들이 들어가 있는 공원들을 보여주도록 하였다.
  • 하지만 모든 옵션 중 놀이공원을 빼고 모든 것을 가지고 있는 남산공원이 가진 option을 보내도 모든 공원이 다 보인다. 해당 부분같은 경우는 하나의 공원만 보여주고 싶은데 아직 구현하지 못했다.
profile
https://github.com/nikevapormax

0개의 댓글