[0809] iPark project 피드백 수정

nikevapormax·2022년 8월 9일
0

TIL

목록 보기
87/116

iPark Project

피드백 사항 반영

  • 어제 하던 것들에 이어 피드백을 수정하고 있다.
  • 어제 하던 것들을 완벽하지 않지만 조금씩 수정한 부분이 있다.

회원가입 에러 메세지 반영 부분 수정

  • 어제 넘어왔던 에러 메세지는 django의 기본 validator에서 보내주던 에러 메세지였다. 따라서 그냥 빈 값을 백앤드로 보내면 django가 빈값 싫어 값 내놔하면서 자동으로 key값을 붙여 메세지를 보내주던 것이었다.
  • 따라서 내가 직접 키값을 작성하지 않아도 {"키값" : "에러 메세지"}의 형태로 데이터를 보낸 것이다.
  • 하지만 오늘 수정한 부분은 custom validator의 에러 메세지의 키값이며, 지금까지 나는 error라는 키값으로 메세지들을 보내고 있었다.
  • 이 부분을 메세지에 맞는 키값의 이름으로 변경해 주었다.
def validate(self, data):
        correct_password = re.compile("^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$")
        password_input = correct_password.match(data.get("password", ""))
        
        if data.get("username"):
            try:
                if UserModel.objects.get(username=data.get("username")):
                    raise serializers.ValidationError(
                        detail={"username": "이미 존재하는 username입니다."}
                    )
            except:
                if not len(data.get("username", "")) >= 6:
                    raise serializers.ValidationError(
                    detail={"username": "username의 길이는 6자리 이상이어야 합니다."})
        
        if password_input == None:
            raise serializers.ValidationError(
                detail={"password": "비밀번호는 8 자리 이상이며 최소 하나 이상의 영문자, 숫자, 특수문자가 필요합니다."})
        
        return data
  • 또한 javascript에 아래 부분을 추가하였다.
    • 어제 작성했던 코드는 switch문의 모든 case마다 차일드 노드를 삭제하는 코드를 작성했었다. 그렇게 했더니 에러 메세지가 없는 경우에는 차례대로 마지막에 위치한 다른 노드들까지 전부 지워버렸기 때문이다.
    • 각 case마다 작성하는 것은 반복이기 때문에 좋지 않아 일단 이 부분이라도 반복을 줄여보았다.
var error_node = document.querySelectorAll("#error")
  error_node.forEach(
    error =>
      error.parentNode.removeChild(error)
  )
  • 다음과 같이 작동한다.

회원 정보 수정 코드 피드백 반영

  • 내가 만든 회원정보 수정 페이지는 회원가입과 똑같은 폼을 사용하고 있다.
  • 별 생각없이 비슷한 폼을 유지하는 것이 좋을 거 같아 만들게 되었고, 회원가입의 html과 js를 변경하게 되면서 이 부분에 대한 피드백도 같이 반영해 수정해 보았다.
  • 이 페이지의 피드백은 다음과 같다.
    • 사용자 인증을 하는데 아이디와 비밀번호를 제대로 쳐도 존재하지 않는 사용자라는 에러가 난다. (이거는 왜 그런지 모르겠다.)
    • 로그인을 두 번 하는 거 같아 불편하다.
      • 아이디 부분을 백앤드에서 불러와 붙여줄 예정
    • 회원탈퇴를 할 때 정말 탈퇴하겠습니까? 라고 한 번 더 물어보는 것이 좋을 것 같다.
    • 에러 메세지의 세분화
      • 어떤 부분에 대한 에러 메세지인지
  • 계정관리를 할 때 페이지를 변경하는 것이 아니라 팝업을 사용하는 것도 좋을 것 같다는 피드백도 주셨었는데, 나는 페이지를 변경해 계정관리를 하는 곳이라는 것을 보여주고 싶었기 때문에 이 부분은 반영하지 않았다.
  • 위에서 3번째와 4번째 부분을 일단 수정해 보았다.
  • 먼저 계정 관리를 할 수 있는지 권한을 확인하는 함수이다.
    • 이메일을 직접 입력하는 것이 아닌 select 태그를 사용하고 있기 때문에 이메일의 앞 부분과 도메인 부분을 따로 받아왔다. (split 사용)
// 계정관리 페이지 사용 권한 확인
async function searchUser() {
  const userData = {
    username: document.getElementById("checkUsername").value,
    password: document.getElementById("checkPassword").value
  }

  const response = await fetch(`${backendBaseUrl}/user/verification/`, {
    method: "POST",
    headers: {
      Accept: "application/json",
      "Content-type": "application/json",
      "Authorization": "Bearer " + localStorage.getItem("access")
    },
    body: JSON.stringify(userData)
  })

  verification_json = await response.json()
  if (response.status == 200) {
    const popup = document.getElementById("popup")
    popup.style.visibility = "visible"
    document.getElementById("accountUsername").value = verification_json.username
    document.getElementById("accountFullname").value = verification_json.fullname
    document.getElementById("accountEmail").value = verification_json.email.split("@")[0]
    document.querySelector("#email-field select option").value = "@" + verification_json.email.split("@")[1]
    document.querySelector("#email-field select option").innerHTML = "@" + verification_json.email.split("@")[1]
    document.getElementById("accountPhone").value = verification_json.phone
    document.getElementById("accountRegion").value = verification_json.region

  } else {
    alert(verification_json["message"])
    const popup = document.getElementById("popup")
    popup.style.visibility = "hidden"
  }
}
  • 계정 관리 페이지에서 수정한 정보를 백앤드로 보내는 함수이다.
    • switch를 통해 각 상황에 맞는 에러 메세지를 보여줄 수 있도록 했다.
    • 물론 회원가입과 다르게 이미 회원가입할 때 작성한 데이터가 이미 들어가 있어 많은 에러가 나지는 않을 것이라 생각한다.
// 계정확인 페이지 : 수정된 데이터 송신
async function changeAccount() {
  var error_node = document.querySelectorAll("#error")
  error_node.forEach(
    error =>
      error.parentNode.removeChild(error)
  )

  let changedData

  if (document.getElementById("accountPassword").value) {
    changedData = {
      username: document.getElementById("accountUsername").value,
      password: document.getElementById("accountPassword").value,
      fullname: document.getElementById("accountFullname").value,
      email: document.getElementById("accountEmail").value + document.querySelector("#email-field select option").value,
      phone: document.getElementById("accountPhone").value,
      region: document.getElementById("accountRegion").value
    }
  } else {
    changedData = {
      username: document.getElementById("accountUsername").value,
      fullname: document.getElementById("accountFullname").value,
      email: document.getElementById("accountEmail").value + document.querySelector("#email-field select option").value,
      phone: document.getElementById("accountPhone").value,
      region: document.getElementById("accountRegion").value
    }
  }

  const response = await fetch(`${backendBaseUrl}/user/`, {
    method: "PUT",
    headers: {
      Accept: "application/json",
      "Content-type": "application/json",
      "Authorization": "Bearer " + localStorage.getItem("access")
    },
    body: JSON.stringify(changedData)
  })

  account_response = await response.json()
  if (response.status == 201) {
    alert("회원정보 수정이 완료되었습니다.")
    window.location.replace(`${frontendBaseUrl}/index.html`)

  } else {
    const key = Object.keys(account_response)
    const error = Object.values(account_response)

    for (let i = 0; i < key.length; i++) {
      switch (key[i]) {
        case "username":
          const err_username = document.getElementById("username-field")
          var new_span = document.createElement("span")
          new_span.setAttribute("id", "error")
          new_span.innerText = error[i]
          err_username.appendChild(new_span)
          break
        case "email":
          const err_email = document.getElementById("email-field")
          var new_span = document.createElement("span")
          new_span.setAttribute("id", "error")
          new_span.innerText = error[i]
          err_email.appendChild(new_span)
          break
        case "fullname":
          const err_fullname = document.getElementById("fullname-field")
          var new_span = document.createElement("span")
          new_span.setAttribute("id", "error")
          new_span.innerText = error[i]
          err_fullname.appendChild(new_span)
          break
        case "password":
          const err_password = document.getElementById("password-field")
          var new_span = document.createElement("span")
          new_span.setAttribute("id", "error")
          new_span.innerText = error[i]
          err_password.appendChild(new_span)
          break
        case "phone":
          const err_phone = document.getElementById("phone-field")
          var new_span = document.createElement("span")
          new_span.setAttribute("id", "error")
          new_span.innerText = error[i]
          err_phone.appendChild(new_span)
          break
      }
    }
  }
}

회원탈퇴 시 탈퇴 여부를 물어보도록 수정

  • 기존에는 회원탈퇴 버튼을 누르면 바로 회원탈퇴가 진행되었다.
  • 왜 만들때는 알아차리지 못했는지 모르겠지만, 굉장히 이상한 방식이었다.
  • 따라서 탈퇴 여부를 물을 수 있도록 confirm을 사용해 보았다.
  • Window.confirm() 메서드는 확인과 취소 두 버튼을 가진다.
    • 확인과 취소 버튼을 통해 각각 truefalse를 인자값으로 받아 조건문에 있는 것을 실행해준다.
    • 나의 경우 true일 때 회원탈퇴를 진행하도록 하고, false일 때 아무 값도 넣지 않아 취소하게 되면 아무런 일이 일어나지 않고 해당 페이지에 머무르도록 했다.
  • 코드는 아래와 같다.
// 회원 탈퇴
async function withdrawal() {
  var delConfirm = confirm("정말 회원 탈퇴를 진행하시겠습니까?")
  if (delConfirm) {
    const response = await fetch(`${backendBaseUrl}/user/`, {
      method: "DELETE",
      headers: {
        Accept: "application/json",
        "Content-type": "application/json",
        "Authorization": "Bearer " + localStorage.getItem("access")
      }
    })

    withdrawal_json = await response.json()
    if (response.status == 200) {
      alert(withdrawal_json["message"])
      localStorage.removeItem("payload")
      localStorage.removeItem("access")
      localStorage.removeItem("refresh")
      window.location.replace(`${frontendBaseUrl}/index.html`)

    } else {
      alert(withdrawal_json["message"])
    }
  }
}
  • 아래와 같이 작동한다.

계정 관리 페이지에 들어가면 아이디 보이도록 조치

  • 기존에는 아이디와 비밀번호를 모두 입력해야 계정관리 페이지로 들어갈 수 있도록 로직을 작성했었다.
  • 이에 대해 사용자 피드백으로 두 번 로그인하는 것 같아 불편하다라는 피드백이 왔었고, 이를 반영해 보완해보았다.
  • 기존의 틀은 유지하되, 내가 필요한 부분은 현재 로그인한 사용자의 아이디를 보여주는 것이었다.
  • 이를 위해 먼저 jwt 토큰을 custom하였다.
  • user/jwt_claim_serializer.py
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer


class iParkTokenObtainPairSerializer(TokenObtainPairSerializer):
    @classmethod
    def get_token(cls, user):
        token = super().get_token(user)

        # 로그인한 사용자의 클레임 설정하기.
        token['id'] = user.id
        token['username'] = user.username

        return token
  • user/views.py
from rest_framework_simplejwt.views import TokenObtainPairView
from user.jwt_claim_serializer import iParkTokenObtainPairSerializer


class iParkTokenObtainPairView(TokenObtainPairView):
    serializer_class = iParkTokenObtainPairSerializer
  • user/urls.py
from django.urls import path
from user import views
from rest_framework_simplejwt.views import (
    TokenRefreshView
)

urlpatterns = [
    path("", views.UserView.as_view(), name="user_view"),
    path("api/token/refresh/", TokenRefreshView.as_view(), name="token_refresh_pair"),
    path('api/ipark/token/', views.iParkTokenObtainPairView.as_view(), name='ipark_token'),
    path("kakao/", views.KakaoLoginView.as_view(), name="kakao"),
    path("myid/", views.FindUserInfoView.as_view(), name="myid_view"),
    path("alterpassword/", views.AlterPasswordView.as_view(), name="alter_password_view"),
    path("verification/", views.UserVerifyView.as_view(), name="user_verification_view"),
]
  • 프론트의 코드는 아래와 같다.
    • payload에 담겨있는 username을 가져와 input 창에 넣어 자동으로 보이게 하였고, 해당 값을 username.value를 통해 백앤드로 보내주어 계정관리를 할 수 있게 하였다.
// 계정관리 페이지에 로그인한 사용자의 username 미리 표시하기 위한 값
const username = document.getElementById("checkUsername")
username.value = JSON.parse(localStorage.getItem("payload"))["username"]


// 계정관리 페이지 사용 권한 확인
async function searchUser() {
  const userData = {
    username: username.value,
    password: document.getElementById("checkPassword").value
  }

  const response = await fetch(`${backendBaseUrl}/user/verification/`, {
    method: "POST",
    headers: {
      Accept: "application/json",
      "Content-type": "application/json",
      "Authorization": "Bearer " + localStorage.getItem("access")
    },
    body: JSON.stringify(userData)
  })

  verification_json = await response.json()
  if (response.status == 200) {
    const popup = document.getElementById("popup")
    popup.style.visibility = "visible"
    document.getElementById("accountUsername").value = verification_json.username
    document.getElementById("accountFullname").value = verification_json.fullname
    document.getElementById("accountEmail").value = verification_json.email.split("@")[0]
    document.querySelector("#email-field select option").value = "@" + verification_json.email.split("@")[1]
    document.querySelector("#email-field select option").innerHTML = "@" + verification_json.email.split("@")[1]
    document.getElementById("accountPhone").value = verification_json.phone
    document.getElementById("accountRegion").value = verification_json.region

  } else {
    alert(verification_json["message"])
    const popup = document.getElementById("popup")
    popup.style.visibility = "hidden"
  }
}
  • 다음과 같은 화면이다.

계정관리 페이지 권한 인증 실패 시 에러 메세지 수정

  • 원래는 정보를 잘못 입력하면 존재하지 않는 사용자입니다.를 메세지로 사용하고 있었다.
  • 정보가 잘못 입력되면 당연히 존재하지 않는 사용자아닌가? 라고 생각하고 작성했지만, 피드백에 다시 로그인을 시도하셨다는 분이 계셔서 다른 사람들이 봤을 때 내 계정 자체가 없다고?라고 생각할 수도 있다는 생각이 들었다.
  • 어짜피 계정관리 페이지가 변경되어 사용자 아이디가 자동으로 들어가 있는 것도 있어 메세지를 비밀번호가 일치하지 않습니다.로 변경해 오해를 줄일 수 있도록 하였다.

회원정보 수정 시 동일 비밀번호 사용 못하도록 조치

  • 회원정보를 수정할 때 비밀번호는 바꿔도 되고, 바꾸지 않아도 된다.
  • 처음에는 생각하지 않고 있었는데, 비밀번호에 대한 정규 표현식도 이 부분에 필요하고 동일한 비밀번호에 대한 처리도 필요하다는 생각이 이미 배포를 하고 들었다!
  • 이 부분에 대한 피드백은 없었지만 운 좋게 생각이 들어 미래의 피드백을 방어한 것 같다.
  • 코드는 아래와 같다. custom updater를 통해 사용자의 정보를 수정하기 때문에 custom updater를 아래와 같이 수정하였다.
def update(self, instance, validated_data):
        for key, value in validated_data.items():
            if key == "password":
                user = UserModel.objects.get(Q(username=validated_data["username"]) & Q(email=validated_data["email"]))
                if check_password(value, user.password):
                    raise serializers.ValidationError(
                        detail={"password": "현재 사용중인 비밀번호와 동일한 비밀번호는 입력할 수 없습니다."})
                else:
                    instance.set_password(value)
                continue
            setattr(instance, key, value)
        instance.save()

        return instance
  • 작동 화면은 아래와 같다.
profile
https://github.com/nikevapormax

0개의 댓글