달나라 노트

Python django project 1 - 게시판 만들기 ch.13 : 로그인과 회원가입 기능을 form을 이용하여 구현해보기 본문

Python django/Python django project 1

Python django project 1 - 게시판 만들기 ch.13 : 로그인과 회원가입 기능을 form을 이용하여 구현해보기

CosmosProject 2020. 12. 21. 02:55
728x90
반응형

 

 

게시글 등록을 할 때 form을 이용하여 게시글 등록 기능을 만들었었습니다.

 

이번에는 form기능을 좀 더 알아보기 위해 기존에 만들었던 로그인 기능을 form을 이용하는 방식으로 변경해봅시다.

 

 

 

마찬가지로 MTV 순서대로 코딩을 할 것이고 user app model은 다 되어있으니 Template을 봅시다.

 

user_login.html 파일을 아래처럼 수정합시다.

{% extends 'base.html' %}

{% block body %}
<div class="row mt-5">
    <div class="col-12 text-center">
        <h1>User Login</h1>
    </div>
</div>

<div class="row mt-5">
    <div class="col-12">
        <form method="POST" action=".">
            {% csrf_token %}

            <div class="form-group">
                <label for=""></label>
                <input id="" class="form-control" type="" name="" placeholder=""/>
            </div>

            <div class="row mt-5">
                <div class="col-6">
                    <button type="button" class="btn btn-primary btn-block" onclick="location.href='/'">Home</button>
                </div>
                <div class="col-6">
                    <button type="submit" class="btn btn-primary btn-block">Submit</button>
                </div>
            </div>
        </form>
    </div>
</div>
{% endblock %}

마찬가지로 input 태그 부분에 아무 속성값이 없습니다.

이 속성값들은 form과 view를 완성한 후 다시 채우도록 합시다.

 

 

 

pro/app/user 디렉토리에 forms.py 파일을 생성하고 다음처럼 작성합시다.

from django import forms
from django.contrib.auth.hashers import check_password
from . import models as userapp_model

class FormUserLogin(forms.Form):
    username = forms.CharField(
        error_messages={
            'required': 'User name is required.'
        },
        max_length=32, label='User name'
    )
    password = forms.CharField(
        error_messages={
            'required': 'Password is required.'
        },
        widget=forms.PasswordInput, max_length=32, label='Password'
    )

    def clean(self):
        cleaned_data = super().clean()

        username = cleaned_data.get('username', None)
        password = cleaned_data.get('password', None)

        if not(username and password):
            pass
        else:
            try:
                modeluser = userapp_model.ModelUser.objects.get(username=username)
            except userapp_model.ModelUser.DoesNotExist:
                self.add_error('username', 'The user cannot be found.')
            else:
                if check_password(password, modeluser.password):
                    self.login_userkey = modeluser.id
                    self.login_username = modeluser.username
                else:
                    self.add_error('password', 'Password is wrong.')

 

 

 

 

 

일단 아래 부분을 봅시다.

from django import forms
from django.contrib.auth.hashers import make_password
from . import models as userapp_model

class FormUserRegister(forms.Form):
    username = forms.CharField(
        error_messages={
            'required': 'User name is required.'
        },
        max_length=32, label='User name'
    )
    password = forms.CharField(
        error_messages={
            'required': 'Password is required.'
        },
        widget=forms.PasswordInput, max_length=32, label='Password'
    )

    ...

이 부분은 저번에 게시글의 forms.py와 큰 차이가 없습니다.

로그인에서는 받아야 할 값이 username, password 2개라서 총 field가 2개이고,

password의 widget을 PasswordInput으로 적용해준 것을 알 수 있습니다.

 

 

 

 

from django import forms
from django.contrib.auth.hashers import make_password
from . import models as userapp_model

class FormUserLogin(forms.Form):
    ...

    def clean(self):
        cleaned_data = super().clean()

        username = cleaned_data.get('username', None)
        password = cleaned_data.get('password', None)

        if not(username and password):
            pass
        else:
            try:
                modeluser = userapp_model.ModelUser.objects.get(username=username)
            except userapp_model.ModelUser.DoesNotExist:
                self.add_error('username', 'The user cannot be found.')
            else:
                if check_password(password, modeluser.password):
                    self.login_userkey = modeluser.id
                    self.login_username = modeluser.username
                else:
                    self.add_error('password', 'Password is wrong.')

새로 추가된 건 clean 함수 부분이 생겼음을 알 수 있습니다.

이 부분을 간단하게 말하면 다음과 같습니다.

지난 게시글 등록에서 forms.py의 역할은 단순히 사용자 입력값을 받는 것이었습니다.

이 값을 받아 form class에 담고 그것을 view에 전달하죠.

그러면 view에서 이 받은 값을 가지고 원하는 로직(게시글 값 테스트, 게시글 등록 등)을 구현하였습니다.

 

하지만 이번 forms.py에선 form이 사용자 입력값도 받으며, 이 값을 이용하여 처리할 로직까지 담고있습니다.

이렇게 forms.py에서 어떤 로직을 추가하고싶으면 clean 함수를 변경하면 됩니다.

 

cleaned_data = super().clean() -> 이 부분은 사용자 입력 값을 cleaned_data에 저장하는 단계입니다. 원래 django forms.Form에는 clean 함수가 있는데 이것을 def clean(self): 부분을 통해 새로 정의함으로서 clean 함수의 내용이 바뀌게됩니다. 여기서 원래 clean 함수의 기능(사용자 입력값을 받는 기능)을 사용하기 위해 super 함수와 함께 clean 함수를 호출한 것이죠.

 

 

username = cleaned_data.get('username', None)
password = cleaned_data.get('password', None)

-> cleaned_data에서 입력값을 가져옵니다. cleaned_data에는 입력값이 field name과 key: value 쌍으로 연결된 dictionary 형태로 되어있다고 했었죠.

 

 

        if not(username and password):
            pass

만약 위 값중 하나라도 존재하지 않으면 그냥 pass를 합니다.

왜냐하면 form에서 누락값은 자동으로 검사해주니까요.

 

 

 

 

        if not(username and password):
            pass
        else:
            try:
                modeluser = userapp_model.ModelUser.objects.get(username=username)
            except userapp_model.ModelUser.DoesNotExist:
                self.add_error('username', 'The user cannot be found.')
            else:
                if check_password(password, modeluser.password):
                    self.login_userkey = modeluser.id
                    self.login_username = modeluser.username
                else:
                    self.add_error('password', 'Password is wrong.')

값이 다 존재하면 위 코드블록이 실행됩니다.

기본적인 로직은 기존 로그인에 구현했던 것과 동일합니다.

 

다만 self.add_error라는 부분이 추가되었습니다.

원래 로그인에선 res_data라는 dictionary를 만들어 이 dictionary에 에러메세지를 담아 template으로 전달하였는데, form에서 에러를 추가할 때에는 위처럼 add_error(필드명, 에러메세지)를 사용합니다.

self.add_error('username', 'The user cannot be found.')라는 것은 username field에 해당 에러메세지를 추가하겠다는 뜻입니다.

이 에러메세지는 나중에 template에서 표시될겁니다.

 

 

 

 

            else:
                if check_password(password, modeluser.password):
                    self.login_userkey = modeluser.id
                    self.login_username = modeluser.username
                else:
                    self.add_error('password', 'Password is wrong.')

만약 입력한 username이 model에 등록되어있으면서, 입력한 password가 model의 password와 일치하면 로그인을 해야하는데 이 때 session에 login user 정보를 저장해야 합니다.

session에 대한 접근은 view에서 가능하므로 view에서 사용할 login user 정보를 form의 class 변수로 등록해주는 과정이 바로 self.login_userkey = ~~, self.login_username 부분입니다.

 

 

 

 

 

이제 pro/app/user/views.py의 view_user_login 함수를 아래처럼 수정합시다.

from django.shortcuts import render, redirect
from django.contrib.auth.hashers import make_password, check_password
from . import models as userapp_model
from . import forms as userapp_form

def view_user_login(request):
    if request.method == 'GET':
        formuserlogin = userapp_form.FormUserLogin()
        dict_render = {
            'formuserlogin': formuserlogin
        }
        return render(request, 'user_login.html', dict_render)

    elif request.method == 'POST':
        formuserlogin = userapp_form.FormUserLogin(request.POST)

        if formuserlogin.is_valid():
            request.session['login_userkey'] = formuserlogin.login_userkey
            request.session['login_username'] = formuserlogin.login_username
            
            return redirect('/')

        dict_render = {
            'formuserlogin': formuserlogin
        }

        return render(request, 'user_login.html', dict_render)

게시글 등록에서와 마찬가지로 method가 GET일때와 POST일때를 구분하였고, 각각 FormUserLogin class를 받아오고 있습니다.

또한 method == 'POST'일 때 is_valid 함수를 이용하여 만약 입력값이 valid할 경우(=누락값도 없고 조건에 맞는 값이 입력되었을 경우) session에 로그인 유저 정보를 저장하고 home으로 redirect하게되죠.

 

만약 값에 에러가 있을 경우 에러값을 담은 formuserlogin class를 render 함수에 담아 template에 전달하게 됩니다.

 

 

 

 

 

이제 다시 user_login.html을 수정해봅시다.

{% extends 'base.html' %}

{% block body %}
<div class="row mt-5">
    <div class="col-12 text-center">
        <h1>User Login</h1>
    </div>
</div>

<div class="row mt-5">
    <div class="col-12">
        <form method="POST" action=".">
            {% csrf_token %}

            {% for field in formuserlogin %}
            <div class="form-group">
                <label for="{{ field.id_for_label }}">{{ field.label }}</label>
                <input id="{{ field.id_for_label }}" class="form-control" type="{{ field.field.widget.input_type }}" name="{{ field.name }}" placeholder="{{ field.label }}"/>
            </div>

            <span style="color: orange">{{ field.errors }}</span>

            {% endfor %}

            <div class="row mt-5">
                <div class="col-6">
                    <button type="button" class="btn btn-primary btn-block" onclick="location.href='/'">Home</button>
                </div>
                <div class="col-6">
                    <button type="submit" class="btn btn-primary btn-block">Submit</button>
                </div>
            </div>
        </form>
    </div>
</div>
{% endblock %}

게시글 등록에서 만든 template과 유사합니다.

다만 게시글 등록에서는 contents를 받아오기 위해 textarea 태그를 사용했지만 로그인에선 그럴 필요가 없으니 모두 input 태그를 사용하고있죠.

 

 

 

 

여기까지 하고 127.0.0.1:8000/user/user_login url로 이동해서 보면

위처럼 잘 나오는것을 알 수 있죠.

 

 

 

값을 입력하지 않았을 때의 에러가 잘 출력되네요.

 

 

 

 

등록되지 않은 유저이름으로 로그인 시도를 했을 때의 에러메세지도 잘 출력되구요.

 

 

 

 

패스워드를 틀렸을 때의 에러메세지도 출력됩니다.

 

 

 

 

로그인까지 정상적으로 되는군요.

 

 

 

 

 

 

 

 

 

 

그러면 회원가입도 form을 이용해서 바꿔봅시다.

로그인과 굉장히 유사합니다.

 

마찬가지로 MTV 순서대로 코딩할것이며 model은 스킵하고 Template을 봅시다.

user_register.html을 아래처럼 수정합시다.

{% extends 'base.html' %}

{% block body %}
<div class="row mt-5">
    <div class="col-12 text-center">
        <h1>User Register</h1>
    </div>
</div>

<div class="row mt-5">
    <div class="col-12">
        <form method="POST" action=".">
            {% csrf_token %}

            <div class="form-group">
                <label for=""></label>
                <input id="" class="form-control" type="" name="" placeholder=""/>
            </div>

            <div class="row mt-5">
                <div class="col-6">
                    <button type="button" class="btn btn-primary btn-block" onclick="location.href='/'">Home</button>
                </div>
                <div class="col-6">
                    <button type="submit" class="btn btn-primary btn-block">Submit</button>
                </div>
            </div>
        </form>
    </div>
</div>
{% endblock %}

 

 

 

그리고 pro/app/user/forms.py에 회원가입 class를 추가합시다.

from django import forms
from django.contrib.auth.hashers import make_password, check_password
from . import models as userapp_model

class FormUserRegister(forms.Form):
    username = forms.CharField(
        error_messages={
            'required': 'User name is required.'
        },
        max_length=32, label='User name'
    )
    password = forms.CharField(
        error_messages={
            'required': 'Password is required.'
        },
        widget=forms.PasswordInput, max_length=32, label='Password'
    )
    re_password = forms.CharField(
        error_messages={
            'required': 'Re-Password is required.'
        },
        widget=forms.PasswordInput, max_length=32, label='Re-Password'
    )

    def clean(self):
        cleaned_data = super().clean()

        username = cleaned_data.get('username', None)
        password = cleaned_data.get('password', None)
        re_password = cleaned_data.get('re_password', None)

        if not(username and password and re_password):
            pass
        else:
            try:
                modeluser = userapp_model.ModelUser.objects.get(username=username)
            except userapp_model.ModelUser.DoesNotExist:
                if password != re_password:
                    self.add_error('password', 'Passwords are different.')
                    self.add_error('re_password', 'Passwords are different.')
                else:
                    modeluser = userapp_model.ModelUser()
                    modeluser.username = username
                    modeluser.password = make_password(password)
                    modeluser.save()
            else:
                self.add_error('username', 'The user is already registered.')

로그인 form과 굉장히 유사하며 def clean(self): 부분에 명시된 로직만 회원가입용 로직으로 변경되었음을 알 수 있습니다.

 

 

 

 

from django import forms
from django.contrib.auth.hashers import make_password, check_password
from . import models as userapp_model

class FormUserRegister(forms.Form):
    ...

    def clean(self):
        ...
                else:
                    modeluser = userapp_model.ModelUser()
                    modeluser.username = username
                    modeluser.password = make_password(password)
                    modeluser.save()
        ...

또한 위 부분을 보면 form에서 model에 정보를 저장하는 것도 가능한 것을 알 수 있죠.

 

 

 

 

 

 

이제 pro/app/user/views.py의 view_user_register 함수를 아래처럼 수정합시다.

from django.shortcuts import render, redirect
from django.contrib.auth.hashers import make_password, check_password
from . import models as userapp_model
from . import forms as userapp_form

# Create your views here.

...

def view_user_register(request):
    if request.method == 'GET':
        formuserregister = userapp_form.FormUserRegister()
        dict_render = {
            'formuserregister': formuserregister
        }
        return render(request, 'user_register.html', dict_render)

    elif request.method == 'POST':
        formuserregister = userapp_form.FormUserRegister(request.POST)

        if formuserregister.is_valid():
            return redirect('/')

        dict_render = {
            'formuserregister': formuserregister
        }

        return render(request, 'user_register.html', dict_render)

상당히 비슷하죠?

다만 login view와 다른 점은 form이 valid할 경우 따로 session에 값을 저장할 필요 없이 바로 home으로 redirect해주면 된다는 것입니다.

 

 

 

 

 

여기까지 하고 user_register.html을 수정합시다.

{% extends 'base.html' %}

{% block body %}
<div class="row mt-5">
    <div class="col-12 text-center">
        <h1>User Register</h1>
    </div>
</div>

<div class="row mt-5">
    <div class="col-12">
        <form method="POST" action=".">
            {% csrf_token %}

            {% for field in formuserregister %}
            <div class="form-group">
                <label for="{{ field.id_for_label }}">{{ field.label }}</label>
                <input id="{{ field.id_for_label }}" class="form-control" type="{{ field.field.widget.input_type }}" name="{{ field.name }}" placeholder="{{ field.label }}"/>
            </div>

            <span style="color: orange">{{ field.errors }}</span>

            {% endfor %}

            <div class="row mt-5">
                <div class="col-6">
                    <button type="button" class="btn btn-primary btn-block" onclick="location.href='/'">Home</button>
                </div>
                <div class="col-6">
                    <button type="submit" class="btn btn-primary btn-block">Submit</button>
                </div>
            </div>
        </form>
    </div>
</div>
{% endblock %}

로그인 템플릿과 거의 똑같습니다.

제목 부분만 User Register로 바꾸고 전달받은 form dictionary 이름이 formuserregister이므로 이 부분만 바꿔줬습니다.

 

 

 

 

여기까지 하고 http://127.0.0.1:8000/user/user_register/ 주소로 진입해봅시다.

 

기본 화면은 잘 뜹니다.

 

 

 

 

 

값 누락 에러메세지도 잘 뜨네요.

 

 

 

 

비밀번호를 다르게 입력했을 때의 에러도 잘 뜹니다.

 

 

 

 

이미 등록된 유저이름으로 회원가입 시도했을 때의 에러메세지.

 

 

 

 

 

회원가입 성공 후 Home으로 돌아오는 기능도 잘 되네요.

 

 

 

728x90
반응형
Comments