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
- 어제 작성했던 코드에서
email
과 birthday
에 대한 정규표현식을 삭제하였다. 이유는 모델에서 정의한 필드에 의해 이미 걸러질 값들이 걸러지고 있었기 때문이다.
- 살짝 걸리는 부분은 사용자가 생년월일을 입력할 때 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을 보내도 모든 공원이 다 보인다. 해당 부분같은 경우는 하나의 공원만 보여주고 싶은데 아직 구현하지 못했다.