출처: https://meyouus.tistory.com/64 [정보 공유 - For Me For You For Us]
본문으로 바로가기

이번에는 회원탈퇴 구현하기(1) 과는 다르게, 비밀번호를 통한 본인인증 후에 회원탈퇴를 진행하게끔 구현해줄 것이다.

 

 

※ forms.py 수정하기

forms.py를 다음과 같이 수정한다:

# forms.py
from django import forms
from django.contrib.auth.hasers import check_password

class PasswordCheckForm(forms.Form):
    password = forms.Charfield(label='비밀번호', widget=forms.PasswordInput())
    
    def __init__(self, user, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.user = user

    def clean(self):
        cleaned_data = super().clean()
        password = cleaned_data.get('password')
        user_password = self.user.password
        
        if password:
            if check_password(password, user_password) == False:
                self.add_error('password', '비밀번호가 틀렸습니다.')

비밀번호를 확인해주는 PasswordCheckForm을 만들어준다. forms.Form을 상속하는 이유는 다른 모델들과 상호작용하는 것이 아니기 때문에 Modelform이 아닌 forms.Form을 사용한다.

 

password=forms.Charfield(label='비밀번호', widget=forms.PasswordInput()) 에서는 label은 비밀번호로 (밑에 html template에서 다시 설명하겠다.), widgetHTML input 변수를 정의하는 Django의 방법이다 (이 또한 밑의 html template에서 다시 설명하겠다.)

 

def __init__을 통해 password와 같은 field를 통과시킨다. 또한, user를 정의함으로써, 사용자도 또한 통과시켜준다.

 

def clean(self)을 정의하는데, 여기서 cleaned_data=super().clean()form으로 들어온 값을 읽어들이는 역할을 하고, password=cleaned_data.get('password') 를 통해 form으로 들어온 값 중 'password'를 다시 password 라는 변수에 저장한다. user_password는 사용자의 비밀번호로 정의한다. (cleaned_data=super().clean() 코드를 쓰지 않고 cleaned_data를 사용해도 상관 없으나, 정확히 정의해주기 위해 쓰면 더 좋다.)

 

마지막으로 만약 비밀번호가 입력된 것이라면, django.contrib.auth.hashers에서 import한  check_password 메소드를 통해 사용자의 비밀번호와 입력된 비밀번호가 같은지 확인해주고, 아니라면 비밀번호가 틀렸다는 에러를 password 필드에 추가해준다.

add_error()를 통해, cleaned_data에 있는 password를 제거해줌으로써, 다시 읽었을 때 생기는 충돌을 방지할 수 있다.

 

 

※ views.py 수정하기

# views.py
from .forms import PasswordCheckForm

def deleteUser(request):
    form = PasswordCheckForm(request.user)
    if request.method == 'POST':
        form = PasswordCheckForm(request.user, request.POST)
        if form.is_valid():
            request.user.delete()
            logout(request)
            return redirect('login')
    context = {'form':form}
    return render(request, 'accounts/delete_user.html', context)

우선 form이라는 변수에 PasswordCheckForm(request.user)로 정의함으로써 비밀번호 인증을 위한 사용자를 저장시켜준다.

만약 request 방식이 POST 였다면, formrequest.POST 변수도 통과시켜준다.

만약 이 form이 유효한 값이라면, 사용자를 삭제시켜주고 다시 login 페이지로 되돌려준다. (else를 정의안 한 이유는 단지 시작에 form=PasswordCheckForm(request.user) 를 통과시켜주고 시작했기에 상관 없기 때문이다.)

마지막으로 값들을 delete_user.htmlrender 시켜준다.

 

 

※ urls.py / delete_user.html 구현하기

다음은 urls.py 이다

# urls.py

urlpatterns = [
    ...
    path('delete_user/', views.deleteUser, name='delete_user'),
    ...
    ]

다음은 accounts/delete_user.html 이다:

<!-- accounts/delete_user.html -->
<form method="POST" novalidate>
    {% csrf_token %}
    {{ user.user_id }} 회원님의 계정이 삭제됩니다
    <div>
        <label name="label_password" for="{{ form.password.id_for_label }}">{{ form.password.label }}</label>
        {{ form.password }}
        {% if form.password.errors %}
            {% for error in form.password.errors %}
                {{ error }}
            {% endfor %}
        {% endif %}
    </div>   
    <button type="submit" name="bt">탈퇴하기</button>
</form>

※ 주의: 여기서 button을 눌렀을 때 탈퇴하기를 구현하려면, AJAX를 이용해야 하나, 여기선 단순히 보여주기 위해 버튼만 구현했다. 만약 버튼을 디자인하거나 하지 않을 것이라면, 단순히 <input type="submit"...> 식으로 구현해도 된다. 아래에서 보여주는 예제에서는 <input type="submit"...> 으로 구현했다.

 

여기서 method="POST" 옆에 novalidate이라 적혀져 있는 것을 볼 수 있는데, 이는 만약 입력되지 않았을 경우, 입력되지 않았다는 메세지를 띄우게끔 해주는 것이다.

이후 여기서 label name="label_password" for="{{ form.password.id_for_label }}"이 있는데, 여기서의 for 값과 form에 있는 id 값이 같으면 자동적으로 연결되게 한다. 따라서 id_for_label은 이 form.passwordid 값을 불러오게끔 하여, CheckPasswordForm과 연결되게 끔 설정한 것이다. 또한, 이 label tag는 이 label이 클릭되면 연결된 양식에 입력이 가능하게끔 하는 역할을 한다.

{{ form.password.label }}forms.py에 설정해놨던 label="비밀번호" 를 불러와준다.

 

 

※  Widget과 Label 조금 더 자세히 보기

이전에 예시를 보자:

# forms.py
password = forms.CharField(label="비밀번호", widget=forms.PasswordInput())

# accounts/delete_user.html
<label name="label_password" for="{{ form.password.id_for_label }}">{{ form.password.label }}</label>

여기서 label tag를 누르게 된다면 연결된 양식에 입력이 가능하게끔 하는 역할을 한다고 했다. 그렇다면 이 for은 무슨 역할을 하는걸까? 위의 예시를 조금 바꾸어보았다:

# forms.py
password = forms.CharField(label="비밀번호", widget=forms.PasswordInput(attrs={'id':'password_widget'}))

# accounts/delete_user.html
<label name="label_password" for="password_widget">{{ form.password.label }}</label>

이렇게 해도 label tag를 눌렀을 때, 입력 박스가 자동으로 활성화되는 것을 볼 수 있다. 첫번째 예시에서 form.password.id_for_label은 자동적으로 해당 passwordlabel을 위한 id를 입력해주었기 때문에 활성화가 된다. 하지만 만약 for의 값이나, attrs에 정의된 id의 값이 다르다면, 눌러지지 않는다:

# forms.py
password = forms.CharField(label="비밀번호", widget=forms.PasswordInput(attrs={'id':'different_id'}))

# accounts/delete_user.html
<label name="label_password" for="password_widget">{{ form.password.label }}</label>

여기서 이 labelpassword_widget으로 설정되어 있는 반면, forms.py에 정의된 passwordid'different_id'로 정의되어 있기 때문에 label을 클릭해도 비밀번호 입력 박스가 활성화 되지 않는다.

 

예시)

{{ form.password.label }} 부분인 '비밀번호'를 누르게 된다면,

이렇게 입력 박스가 활성화 되는 것이다.

 

 

※ 예제 보기

다음으로는 이를 구현한 매우 ugly한 html template을 보자:

Delete User를 누르게 된다면, /delete_user/ 로 넘어가게 된다:

이제 여기에 제대로된 비밀번호를 입력한다면 바로 로그인 페이지로 넘어가지만, 만약 다른 비밀번호를 입력하였을 경우, 다음과 같이 나타난다:

(앞서 말했듯이, 입력 박스 옆의 '비밀번호'를 누른다면 입력박스가 활성화되면서 입력이 가능해진다)