본문 바로가기
WebFramework/Python Django

[Python Django] 15. Django Custom User Form (2) - Custom User Model 사용 시 유용한 Form

by Rosmary 2024. 4. 24.
728x90
반응형

 

 

 

 

 

지난 포스팅에 이어 작성하는 내용이다.

지난 포스팅 보기

 

 

 

 

1.  Custom User Model - 로그인 관련 Form:  django.contrib.auth.forms.AuthenticationForm

 

 

 

먼저 회원 로그인부터 수정해보자. 현재 필자의 회원 로그인 Form은 저 위에서 처음 만들었던 UserTestModel(사실 이거 UserTestForm으로 작성하려던 거다)와 크게 다르지 않은데, 여기서 아래와 같이 수정을 진행해보려 한다.

 

*  클래스명 변경:   UserLoginForm

*  상속받는 클래스 : forms.ModelForm -> AuthenticationForm

*  Meta.fields 속성:  전부 삭제.

*  Meta.widgets속성: 전부 삭제.

 

 

 

"이렇게 Form을 작성하면 Custom User Model이 상속하는 AbstractUser의 Fields들이 Form에서 튀어나오지 않나요?"

 

아무 문제 없다...

 

 

즉, AuthenticationForm은, AbstractUser 클래스를 상속받아 커스텀 된 User Model의 field 내용을 참조한 뒤, Authentication에 필요한 Fields만 화면에 출력되도록 Form을 작성해주고, 그와 관련된 인증 절차도 문제 없이 진행되도록 만들어 준다. 로그인을 해 보면, 

 

 

 

render 페이지가 지정되지 않아 오류는 나지만, 적어도 form 입력값에서, 이전 포스팅의 마지막 부분에서 보았던 unique 관련된 에러는 말끔하게 사라진 것이 확인된다. 이제 완전히 로그인과 로그아웃 기능이 동작하도록 만들어보려한다.

 

render templatte_name에 경로가 잘못되었다.

 

 

페이지와 로그 역시 아래와 같이 정상적으로 동작하는 것이 확인된다. 

 

 

 

 

 

2.  Custom User Model - 계정 생성 관련 Form:  django.contrib.auth.forms.UserCreationForm

 

 

다음으로 회원 가입 기능을 구현해보려한다. 회원 가입 역시, django.contrib.auth.forms에서 제공하는 UserCreationForm을 사용한다. 단, AuthenticationForm과 달리, Django가 Custom Model의 필드를 알아서 찾은 다음에 Form을 배치하지는 않기 때문에, Form에 사용할 필드를 직접 지정해주어야 한다.

 

 

 

그런데, 뭔가 이상하다. 연락처 다음으로는 나타나지 않아야 할 필드가 두 개 더 보이는데, password, password confirmation라는 label을 가진 필드다. 분명 fields 안에 password를 명시했음에도 이들이 튀어나온것은 UserCreationForm 때문이다. 보통 회원 가입 시 비밀번호는 2번 입력하여 입력값이 같은지 검증하는 절차를 거치기 때문에, password 입력창이 2번 표시되도록 만들어놓았고, 이들의 fields 명은 password가 아니라 password1, password2로 지정되어 있다. 

 

 

 

따라서, 회원 가입에서 UserCreationForm을 상속받는 Form의 경우, Custom Model에 존재하는 password 필드가 아니라, UserCreationForm의 password1과 password2가 fields에 나타나도록 만들어주어야 한다. 필자는 새로 선언할 password1과 password2 필드에 대해 한글로 전부 통일시켜보려 한다.

 

error_messages는 작성안하고 그냥 넘어간다.

 

 

이제 폼을 작성하고 views.py에서 데이터가 어떻게 나타나는지 확인해보자.

 

비밀번호가 너무 단순할 때

 

 

비밀번호가 일치하지 않을 때,

 

 

분명, 비밀번호와 관련된 validation은 form에서도, Custom model에서도 정의해주지 않았지만, 입력값에 따라 문제가 있다면 오류를 출력하고 있는 것이 확인되는데, 이것은 UserCreationForm에서 패스워드와 관련된 내역을 처리해주기 때문이다. 물론, 비밀번호 뿐만 아니라 계정명 역시 UserCreationForm과 관련이 있기 때문에 이미 존재하는 계정명을 입력하는 경우, Unique 에러가 발생한다.

 

 

 

이제 정상적인 회원 가입이 이루어지도록 코드를 작성해보자. 코드 작성은 회원 로그인과 유사하기 때문에 어렵지 않지만, cp_num의 경우 포맷을 검증해야하기 때문에 form 내부에 clean() 관련 매서드를 선언해주어야 한다.

 

 

 

참고로 UserCreationForm을 상속하는 Form의 경우, save() 매서드를 상속받기가 어려운데, 신규 가입자 정보를 토대로 새로운 User Account를 만들려고 할 때 반드시 ID 값이 요구되기 때문이다. 하지만, ID 값은 사용자가 직접 입력하지 않고 UserCreationForm의 save() 매서드에서 처리하기 때문에, save() 매서드를 오버라이드해서 계정 정보를 저장하려고 시도해도, 에러가 발생하게 된다.

 

 

 

결과적으로  views.py에서 form에 대해 save()만 진행하더라도 정상적으로 새 계정이 등록되는 것을 확인할 수 있다.

 

 

 

 

 

3.  Custom User Model - 계정정보 수정 관련 Form:  django.contrib.auth.forms.UserChangeForm

 

 

마지막으로 회원 정보의 수정과 관련된 내용을 진행하려 한다. 마찬가지로 django.contrib.auth.forms에서 제공하는 UserChangeForm을 사용하면 회원 정보 수정도 손쉽게 진행할 수 있다. 필자는 어차피 수정해야 하는 정보가 email과 cp_num 둘 뿐이지만, 계정명도 비활성화 한 채로 나타나도록 만들어보려한다. 

 

두 가지 방식이 있는데, username field를 아예 새로 정의하던가, 아니면 UserChangeForm의 field를 __init__ 매서드 내에서 속성을 변경하는 방법이 있다. 

 

 

 

좌측 방법은 UserChangeForm에서 제공하는 username field를 직접 사용하지 않고 다시 재정의 한 것이며, 우측은 반대로 UserChangeForm에서 제공하는 fields 중 "username" 필드만 추출하여 disabled 속성을 걸어준 것이다. 결과는 둘 다 동일하게 나타난다.

 

 

 

다른 것들은 둘째 치고, 계정 정보의 변경인데 기존 계정에 대한 정보가 미리 폼에 입력되어 있으면 좋을 듯 하다. 필자가 한창 UserForm에서 해멜 때, 아래와 같이 코드를 작성했었다.

 

 

좌측이 forms.py, 우측이 views.py 다.

 

 

그런데, 지금이야 Accounts 테이블에 넣어야 할 데이터가 꼴랑 3개니까 이런 방식도 상관없지만, 입력 Field가 많아진다면 스스로를 고문하는 아주 훌륭한 방법(?)이 된다는게 문제다. 

 

위의 방식도 되기는 된다. 안되는게 문제가 아니라 힘든게 문제다.

 

 

문제를 해결할 수 있는 방법은 소스코드에 숨어있다. 상속받은 Class부터 하나씩 소스코드를 열어보자.

UserChangeForm의 소스를 열어 상속 클래스의 최상위로 올라가다보면, BaseModelForm이라는 클래스를 마주하게 된다. 

 

 

 

이 클래스의 소스를 열어보면 클래스 호출 시 kwargs를 구성하는 값들이 등장하는데, 

 

 

 

BaseModelForm에 instance 값이 존재하는 경우 해당 instance의 내역을 dict 형태로 변환하는 연산이 존재한다. 연산 결과를 저장하는 변수명이 object_data 인 것으로 보아 이는 특정 Model의 row에 대한 정보를 dictionary 데이터로 저장한다는 뜻이 된다. 

 

바로 아래의 코드를 보면 object_data가 수행할 내용이 조금 더 적나라하게 드러나는데,

 

 

 

UserChangeForm에서 initial이 지정되지 않은 경우, object_data 값을 각 필드에 맞춰 initial 값으로 지정해주는 부분이 존재하는 것을 확인할 수 있다. 즉, UserInfoChangeForm이라는 CustomForm에서 instance로 현재 User에 대한 Accounts 정보를 넘기면, UserChangeForm은 자동으로 이 정보를 각 폼의 initial 값으로 지정해준다는 의미다.

 

 

 

결과는 위에서 작성한 효율성 떨어지는 코드와 유사하게 - label을 추가했다 - 나타난다.

 

 

 

 

3-1.  Custom User Model - 계정 비밀번호 변경 관련 Form:  django.contrib.auth.forms.PasswordChangeForm

 

 

또한 User 정보를 instance로 전달하는 것이 password 변경 시에도 상당히 유용한데, 현재 페이지 우측 하단의 "This Form"이라는 링크를 클릭하면, url의 마지막이 /계정정보의 PK값/password로 끝나는 것을 확인할 수 있다.

 

 

 

 

 

즉, 해당 URL에 대한 정의만 urls.py에 되어 있다면 비밀번호의 변경도 간편하게 진행할 수 있다는 의미가 된다. 이 때 사용하는 Form은 지금까지 본 Class와 동일한 모듈에 존재하는 PasswordChangeForm이다. 

 

좌측부터 urls.py, forms.py, views.py다.

 

사진이 작아 잘 보이지 않지만, 우선 UserChangeForm의 안내대로 코드를 작성한 뒤, 링크를 다시 접속해보면 아래와 같이 비밀번호 변경과 관련된 입력 폼이 나타난다. 참고로 PasswordChangeForm을 상속받은 코드는 instance 매개변수로 로그인 사용자의 정보를 받지 않고, user라는 매개변수명을 사용한다. 

 

 

 

지금까지 진행한 다른 Form과 마찬가지로, Custom Form에서 PasswordChangeForm을 상속받아, 아래와 같이 입력 폼을 변경하는 것도 가능하다.

 

old_password, new_password1, new_password2는 PassswordChangeForm에 정의된 필드를 재정의한 것이다.

 


 

 

그럼, 회원 정보 수정과 비밀번호 수정 내역 페이지를 조금만 더 꾸며보자. 우선은 기존의 방식대로, 회원 정보 수정 페이지에서 '비밀번호 변경'을 클릭하면 비밀번호 변경 페이지로 이동하도록 만들어보려한다. 

 

현재 UserChangeForm은 password라는 field가 별도의 input 태그없이 HTML에 적용되도록 설정된 것이 확인되는데, 

 

 

 

이 부분이 영 지저분하니 화면에 출력되지 않도록 설정하고 버튼을 추가하려한다. 화면에 출력되지 않도록 만드는 방법은 HTML에서 form DTL을 사용하던지, 아니면 forms.py의 UserChangeForm에서 password 필드를 None으로 지정해주면 된다.

 

 

 

최종적인 화면은 아래와 같이 나타난다.

비밀번호 변경 버튼 부분은 <a> 링크를 사용해도 문제없이 동작한다.

 

 

 

만약 별도의 페이지 이동 없이, 회원 정보 수정 페이지에서 버튼 클릭 시 비밀번호 변경 폼이 나타나게 만들려면 Javascript를 사용해야한다. 본 포스팅의 주제를 벗어나는 내용이니 우선 넘어가고...

 

최종적으로 form과 view에서 사용자 입력값을 출력하여, 제대로 동작하는지 확인해보자. 먼저, PasswordChangeForm부터. 

 

이전의 계정 비밀번호가 일치하고, 새 비밀번호 입력값 2개가 모두 일치하면 form에서 에러 없이 정상적으로 출력되는 것을 확인할 수 있다. 만약, 이전 비밀번호가 일치하지 않거나 새 비밀번호가 비밀번호 생성 규칙을 벗어나거나 입력값 두 개가 일치하지 않는다면 아래와 같이 에러가 발생한다.

 

 

 

이제 form.save()와 UserPwChangeForm에서 직접 구현한 login() 매서드를 사용하여, 사용자가 비밀번호 변경 후, 변경 비밀번호로 로그인까지 되도록 만들어보려한다. 로그인까지 완료되면 기존의 정보 변경 페이지로 이동하도록 Redirection도 진행한다.

 

 

 

 

마지막으로 회원 정보 수정 내역도 마무리해보자. 회원 정보는views.py에서 POST로 호출되는 경우만 추가로 아래와 같이 작성해주면 된다.

 

 

 

크게 어렵지는 않지만 한 가지 주의해야 할 사항이 있는데, POST 내에서 Form 호출 시, 기존 정보에 대한 값을 disabled 된 Field에서도 참조하도록 만들기 위해서는 반드시 instance 매개변수를 현재 사용자 정보로 추가해주어야 한다. 만약 instance를 누락한채로 코드를 실행하면, form 내부에 username의 초기값이 전달되지 않아 is_valid() 조건문을 통과하지 못하게 된다.

 

 

 

 


 

 

End.

 

 

 

반응형

댓글