달나라 노트

Python django project 1 - 게시판 만들기 ch.7 : 회원가입 본문

Python django/Python django project 1

Python django project 1 - 게시판 만들기 ch.7 : 회원가입

CosmosProject 2020. 12. 13. 04:09
728x90
반응형



이번엔 회원가입 기능을 만들어보겠습니다.

 

저는 코딩하는 순서를 MTV 패턴에 맞게 Model -> Template -> View 순서로 한다고 했습니다.

일단 회원가입을 위해선 user app의 model부터 작성해야하는데 model은 이미 작성을 해놨죠?

그러면 회원가입을 위한 template을 만들겠습니다.

 

pro/app/user/templates directory에 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="username">User id</label>
                <input id="username" class="form-control" type="text" name="username" placeholder="User id"/>
            </div>

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

            <div class="form-group">
                <label for="re_password">Re-password</label>
                <input id="re_password" class="form-control" type="password" name="re_password" placeholder="Re-password"/>
            </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 %}

일단 회원가입 과정을 생각해봅시다.

유저가 회원가입 화면을 보고 user id와 password 등을 입력하고 그 정보대로 회원이 등록됩니다.

그러면 일단 유저가 입력한 값을 받아와서 뭔가 조작을 해야하는데 이때 사용하는 태그가 form태그입니다.

 

그리고 이 form 태그 안에 값을 입력할 칸들을 html 코드로 구성해주게됩니다.

form 태그를 보면 POST 방식으로 값을 전달하기 위해 method를 POST로 설정하였으며, form 태그의 동작이 현재 url에서 이뤄지므로 action은 .을 적어 현재 위치에서 작동됨을 표시하였습니다.

 

 

 

form 태그 안에 보면 {% csrf_token %}이라는 것이 있습니다.

이에 대해 간단하게 설명하면 다음과 같습니다.

 

인터넷에서 누군가가 로그인을해서 어떤 행동을 했습니다.

근데 CSRF 공격이 들어오면 이 사용자가 직접 웹에서 한 행동이 아님에도 불구하고 누군가가 악의적으로 사용자의 권한을 이용해 어떤 행동을 웹에 요청할 수 있습니다.

예를들어 어떤 사용자의 권한으로 광고성 글을 올리는거죠. 이 사용자가 직접 올린게 아니라 다른 제 3자가요.

이런 것을 막기 위한 기능을 django에서 제공하는데 이를 이용하기 위해선 form태그의 시작부분에 csrf_token을 적어주면 됩니다.

만약 이것을 누락할 경우 에러가 발생합니다.

 

 

 

 

form 태그 안을 보면 입력받을 값이 총 3개입니다.

 

username(=userid), password, re_password

 

왜 3개를 입력받는지는 어느정도 감이 오실겁니다.

일단 user app의 model에는 username, password, registered_dttm 세 가지 field가 있었는데, 이를 채우기 위해선 일단 username, password값을 user로부터 받아야합니다.

그리고 re_password는 user가 password를 정확히 입력했는지 확인하기 위해 추가로 입력받아야 하는 값이죠.

 

 

 

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

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

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

위 부분을 봅시다.

 

label 태그의 for와 input태그의 id는 서로 동일하게 입력하여 두 태그를 연결해줍니다.

 

form-group과 form-control은 bootstrap에서 제공하는 태그입니다.

 

type태그는 입력받을 값이 어떤 종류인지를 나타내는데 user id는 그냥 문자이며, password, re_password는 type을 password라고 적어주었습니다.

이 둘의 차이는 실제 브라우저에서 값을 입력할 때 type이 password인 경우 입력 문자 자체가 보이지 않고 점으로 암호화되어 보여지게 됩니다.

 

name 태그는 유저가 입력한 값을 view로 전달해서 여러 기능을 하게되는데 이 값에 붙어있는 key를 설정해주는 부분입니다.

 

{'username': 'user_input', 'password': 'password_input', 're_password': 'repassword_input'}

마치 위처럼 username: value(user가 입력한 값)의 쌍을 가진 dictionary의 형태라고 이해하면 편합니다.

 

따라서 view에서 우리는 여기서 설정한 key를 이용하여 사용자가 입력한 값을 불러올 수 있게 됩니다.

 

placeholder는 입력 칸에 사용자가 값 입력 전 떠있게 할 글자를 의미합니다.

 

 

 

 

 

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

			...

            <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>

위 부분은 버튼 부분입니다.

 

일단 submit버튼을 보면 이는 입력받은 값을 전달하는 버튼입니다. 그래서 type을 submit으로 하였죠.

 

그리고 Home버튼을 봅시다.

Home 버튼은 home화면으로 이동하기 위한 버튼입니다. 이건 입력받은 값을 전달하는 것이 아니기 때문에 type이 button으로 되어있습니다.

그리고 onclick="location.href='/'" 부분이 있습니다.

이것은 이 버튼이 클릭되었을 때 지정된 url로 이동하라는 내용입니다.

/라고 적혀있는건 그냥 기본url로 이동하라는 것이므로 home으로 이동하라는 뜻이 되겠죠.

 

 

 

 

여기까지 하면 template 구성은 끝났습니다.

MTV에서 M과 T가 끝났으니 V로 가봅시다.

 

pro/app/user/views.py를 아래처럼 수정합시다.

 

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

# Create your views here.

def home(request):
    return render(request, 'home.html')

def view_user_register(request):
    if request.method == 'GET':
        return render(request, 'user_register.html')

    elif request.method == 'POST':
        username = request.POST.get('username', None)
        password = request.POST.get('password', None)
        re_password = request.POST.get('re_password', None)

        res_data = {}
        if not(username and password and re_password):
            res_data['errmsg_values'] = 'All values are required.'
        else:
            try:
                modeluser = userapp_model.ModelUser.objects.get(username=username)
            except userapp_model.ModelUser.DoesNotExist:
                if password != re_password:
                    res_data['errmsg_password'] = 'Passwords are different.'
                else:
                    modeluser = userapp_model.ModelUser()
                    modeluser.username = username
                    modeluser.password = make_password(password)
                    modeluser.save()

                    return redirect('/')
            else:
                res_data['errmsg_username'] = 'The user is already registered.'

        dict_render = {
            'res_data': res_data
        }

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

 

view_user_register 함수를 추가했습니다.

이 함수의 내용에 대해 알아보기 전에 먼저 pro/pro/urls.py를 아래처럼 수정합시다.

 

from django.contrib import admin
from django.urls import path, include
from app.user import views as userapp_view

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', userapp_view.home),
    path('user/', include('app.user.urls'))
]

url patterns에 세 번째 줄을 추가했습니다.

이것의 뜻은 요청한 url의 패턴이 기본주소/user/~~~라면 user app에 있는 urls.py를 참조하라는 뜻입니다.

그러면 user app에도 urls.py가 있어야겠죠?

pro/app/user에 urls.py를 생성한 후 아래처럼 내용을 적어줍시다.

from django.contrib import admin
from django.urls import path, include
from . import views as userapp_view

urlpatterns = [
    path('user_register/', userapp_view.view_user_register)
]

위 내용은 url 패턴이 user_register라면 userapp view의 view_user_register함수를 실행시키라는 뜻입니다.

두 urls.py를 연결해보면 요청한 주소가 기본주소/user/user_register/ 라면 userapp_view 파일에 있는 view_user_register 함수를 실행시키라는 뜻이죠.

 

 

자 이제 views.py를 한번 들여다봅시다.

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

# Create your views here.

def home(request):
    return render(request, 'home.html')

def view_user_register(request):
    if request.method == 'GET':
        return render(request, 'user_register.html')

    elif request.method == 'POST':
        username = request.POST.get('username', None)
        password = request.POST.get('password', None)
        re_password = request.POST.get('re_password', None)

        res_data = {}
        if not(username and password and re_password):
            res_data['errmsg_values'] = 'All values are required.'
        else:
            try:
                modeluser = userapp_model.ModelUser.objects.get(username=username)
            except userapp_model.ModelUser.DoesNotExist:
                if password != re_password:
                    res_data['errmsg_password'] = 'Passwords are different.'
                else:
                    modeluser = userapp_model.ModelUser()
                    modeluser.username = username
                    modeluser.password = make_password(password)
                    modeluser.save()

                    return redirect('/')
            else:
                res_data['errmsg_username'] = 'The user is already registered.'

        dict_render = {
            'res_data': res_data
        }

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

만약 회원가입을 하기위해 유저가 주소창에 기본주소/user/user_register를 입력하여 접속을 했다고 가정합시다.

이 경우 유저는 그냥 url을 요청했으므로 GET방식으로 회원가입 화면의 url을 요청한 것입니다.

 

그리고 이 유저가 회원가입을 위해 정보를 입력하고 submit 버튼을 눌렀다면 우리가 form태그에 method를 POST로 지정했으므로 이는 POST 형식으로 회원가입 화면의 url을 요청한 것이 되겠죠.

 

그래서 일단 views.py에서 유저의 요청 method(=request.method)가 GET인지 POST인지에 따라 로직을 나눴습니다.

 

GET일 경우 user가 그냥 회원가입 화면에 접속만 한 것이므로 user_register.html을 유저에게 보여주면 됩니다.

그러나 POST일 경우 user가 회원가입을 위해 어떤 데이터를 입력한 것이므로 뭔가 이 데이터가 제대로 입력되었는지 검사하고 model에 유저가 입력한 데이터를 저장하는 로직이 필요합니다.

 

 

 

    elif request.method == 'POST':
        username = request.POST.get('username', None)
        password = request.POST.get('password', None)
        re_password = request.POST.get('re_password', None)

이 부분을 봅시다.

request method가 POST인 경우에는 user가 입력한 값을 먼저 받아와야합니다.

user가 입력한 값은 html의 POST 형식으로 전달되므로 request의 POST에서 얻어올 수 있습니다.

이것은 마치 python dictionary처럼 접근이 가능하여 get함수를 사용하였습니다.

그리고 html의 input 태그에서 name 속성에 적었던 글자가 dictionary의 key가 되고 user가 입력한 값이 value가 되어 dictionary의 형태로 전달되기 때문에 이를 이용하여 username, password, re_password값을 받아왔습니다.

(POST는 dictionary의 형태라고 생각하면 편합니다.)

 

 

 

        res_data = {}

		...
        
        dict_render = {
            'res_data': res_data
        }
        
        return render(request, 'user_register.html', dict_render)

이 부분을 봅시다.

중간 코드는 생략을 했는데 일단 res_data라는 dictionary를 생성하고, 이걸 dict_render라는 dictionary에 담아서 render의 인자로 담아 보내고있습니다.

render는 위처럼 request, template 파일 외에 별도로 dictionary를 추가로 인자로서 받을 수 있습니다.

이렇게 제공된 dictionary는 연결된 html 파일에서 참조하여 dictionary 정보를 표시할 수 있습니다.

 

예를 들어 사용자가 모든 값을 입력하지 않은 경우 이에 대한 에러메세지를 res_data라는 dictionary에 담아서 html파일에 보냅니다.

html 파일에 미리 이 dictionary를 읽는 코드를 작성해두면 html 파일이 user에게 적용될 때 전달된 error 메세지를 포함하여 보여주게 됩니다.

 

이런 식으로 에러 메세지 뿐만 아니라 html 파일에 표시하고 싶은 모든 것을 dictoinary를 통해 전달할 수 있습니다.

 

 

 

        res_data = {}
        if not(username and password and re_password):
            res_data['errmsg_values'] = 'All values are required.'
        else:
            try:
                modeluser = userapp_model.ModelUser.objects.get(username=username)
            except userapp_model.ModelUser.DoesNotExist:
                if password != re_password:
                    res_data['errmsg_password'] = 'Passwords are different.'
                else:
                    modeluser = userapp_model.ModelUser()
                    modeluser.username = username
                    modeluser.password = make_password(password)
                    modeluser.save()

                    return redirect('/')
            else:
                res_data['errmsg_username'] = 'The user is already registered.'
        
        dict_render = {
            'res_data': res_data
        }
        
        return render(request, 'user_register.html', dict_render)

위 부분을 봅시다.

 

가장 먼저 값을 모두 입력했는지 체크합니다.

username, password, re_password중 하나라도 없게되면 not(~~~) 부분이 False가 될 것입니다.

따라서 이 경우 res_data에 모든 값이 필요하다는 메세지를 저장합니다.

그리고 가장 아래 return render(request, 'user_register.html', dict_render)가 실행되어 회원가입 화면 그대로 보여주되 에러메세지를 추가해서 보여주게되죠.

 

 

만약 모든 값을 입력했을 경우, 바로 회원가입을 해주면될까요?

아닙니다. 두 가지를 먼저 체크해야합니다.

1. 입력한 userid가 이미 등록되어있는지

2. 입력한 password, re_password가 동일한지.

 

일단 먼저 try: 부분을 보면 user app model에서 데이터를 불러오고있습니다.

                modeluser = userapp_model.ModelUser.objects.get(username=username)

어떤 model로부터 내가 원하는 조건의 값을 가져올 때는 위처럼 하면 됩니다.

먼저 user app model(models.py)를 import한 후 해당 model파일에 존재하는 model class(ModelUser)의 objects를 불러오는데 그 조건을 get에 명시합니다.

get(username=username)이라고 적은 이유는 user app model에 존재하는 username 컬럼의 데이터가 유저로부터 입력받은 username과 동일한 것을 가져오라는 의미이죠.

 

            try:
                modeluser = userapp_model.ModelUser.objects.get(username=username)
            except userapp_model.ModelUser.DoesNotExist:
				...
            else:
                res_data['errmsg_username'] = 'The user is already registered.'
        
        dict_render = {
            'res_data': res_data
        }
        
        return render(request, 'user_register.html', dict_render)

만약에 조건을 만족하는 데이터가 없으면 DoesNotExist 에러가 발생하여 except 블록이 실행될 것이고 조건을 만족하는 데이터가 있으면 else: 블록이 실행될 것입니다.

 

기존에 동일한 user가 있으면 회원가입을 해주면 안되겠죠?

따라서 else 코드 블록에서 res_data에 해당 유저가 이미 등록되어있다는 에러메세지를 추가한 후에 가장 아래에 있는 return의 render 속 인자로서 에러메세지를 포함하여 제공합니다.

 

 

            try:
                modeluser = userapp_model.ModelUser.objects.get(username=username)
            except userapp_model.ModelUser.DoesNotExist:
                if password != re_password:
                    res_data['errmsg_password'] = 'Passwords are different.'
                else:
                    modeluser = userapp_model.ModelUser()
                    modeluser.username = username
                    modeluser.password = make_password(password)
                    modeluser.save()

                    return redirect('/')
            else:
                ...
        
        dict_render = {
            'res_data': res_data
        }
        
        return render(request, 'user_register.html', dict_render)

그리고 만약 사용자가 입력한 username이 기존 model에 존재하지 않는다면 userapp_model.ModelUser에 DoesNotExist error가 뜨고 우리는 이제 두 번째 검사기준인 password와 re_password가 동일한지를 체크하게됩니다.

만약 password != re_password 인 경우 res_data에 입력한 비밀번호가 다르다는 error message를 담고, 마지막 return render의 인자로서 에러메세지를 포함하여 회원가입 html을 다시 보여줍니다.

 

만약 입력한 비밀번호가 같을 경우 입력한 값을 model에 저장합니다.

model에 저장할 땐 아래처럼 model class만을 불러온 후, model class에 존재하는 각 컬럼에 알맞는 데이터를 넣고 save()로 저장을 해줍니다.

                    modeluser = userapp_model.ModelUser()
                    modeluser.username = username
                    modeluser.password = make_password(password)
                    modeluser.save()

여기서 한 가지 주의할 것은 password의 경우 django에서 제공하는 make_password method를 이용하여 입력받은 password를 암호화하여 저장합니다.

이렇게 암호화하게되면 password를 1234로 입력했을 때 django admin에서 저장된 비밀번호를 보면 1234라는 입력값 그 자체가 보이는게 아니라 암호화된 일련의 문자로 보이게됩니다.

 

그리고나서 redirect를 return 하는데 redirect는 어떤 url로 다시 연결해주는 기능을 합니다.

redirect('/')로 적은 것은 기본 url로 이동하라는 뜻입니다.

(저는 기본 url을 home으로 지정해두었으므로 회원가입이 성공한 경우 model에 정보를 저장한 후 home으로 이동하라는 뜻이 되겠죠.)

 

 

 

 

한번 위까지 완료한 후에 127.0.0.1:8000/user/user_register라는 주소를 브라우저에 입력해봅시다. (당연히 runserver를 한 상태여야합니다.)

 

 

자 그러면 화면이 잘 보이는 것을 알 수 있죠.

Home버튼을 눌러보면 home화면으로 잘 이동됩니다.

 

그리고 실제 테스트 전에 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 %}

            <span>{{ res_data.errmsg_values }}</span>
            <span>{{ res_data.errmsg_password }}</span>
            <span>{{ res_data.errmsg_username }}</span>

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

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

            <div class="form-group">
                <label for="re_password">Re-password</label>
                <input id="re_password" class="form-control" type="password" name="re_password" placeholder="Re-password"/>
            </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 %}

잘 보시면 span태그에 res_data.errmsg_values 등을 추가한 걸 볼 수 있습니다.

이것은 회원가입을 위해 입력한 데이터 중 에러가 있을 때 그 에러메세지를 views.py에서 html파일로 전달한다고 했는데 이 메세지를 표시하기 위한 것입니다.

 

전달된 dict_render의 구조를 보면 아래와 같은 형식이 될겁니다.

dict_render = {
    'res_data':{
    	'errmsg_values': 'All values are required.'
    }
}

 

django html에서 이걸 참조하기 위해선 아래와 같은 형식으로 양쪽에 중괄호 2개씩을 해준 후 전달된 dictionary에서 참조하고싶은 key값을 적어주면 됩니다.

 

{{ res_data.errmsg_values }}

 

우리는 res_data의 errmsg_values에 해당되는 value(All values are required.)를 출력할 것이므로 {{ res_data.errmsg_values }} 라고 적었습니다.

다른 에러메세지들도 동일한 맥락입니다.

 

여기까지 한 후 다시 user_register url을 입력하여 회원가입을 진행해봅시다.

 

 

 

 

 

 

값을 누락하고 submit을 누르면 가장 위에 All values are required.라는 에러메세지가 표시됩니다.

 

 

 

 

비밀번호를 다르게 입력한 경우 아래와 같은 에러메세지가 뜸.

 

 

 

 

정상적으로 회원가입을 했을 경우 Home화면으로 이동됨.

 

 

 

 

이미 등록되어있는 user id를 입력하여 다시 회원가입 시도했을 때 이미 등록되어있다는 에러메세지 출력됨.

 

 

 

 

 

그러면 django admin의 user model에 내가 등록한 내용이 잘 들어갔는지 볼까요?

 

django admin의 user model을 보면 제가 등록한 test2라는 이름의 유저 정보가 잘 등록되어있음을 알 수 있습니다.

username이 test인 것을 보면 처음에 django admin에서 직접 등록한거라 비밀번호가 1234로 보이는데 test2 user는 make_password로 인해 비밀번호가 암호화된 채로 보여지는 것을 알 수 있죠.

 

 

 

 

 

 

자 이제 마지막으로 한가지만 더 해봅시다.

보통 회원가입을 할 때 유저가 직접 회원가입 화면의 url을 입력하나요? 아닙니다.

보통 회원가입 버튼을 누르면 회원가입 화면으로 이동이되죠.

 

이를 위해 home.html을 아래처럼 수정합시다.

{% extends 'base.html' %}

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

<div class="row mt-5">
    <div class="col-6">
        <button type="button" class="btn btn-primary btn-block" onclick="location.href='/user/user_register/'">Register</button>
    </div>
</div>

{% endblock %}

보면 register 버튼을 추가했습니다.

그리고 onclick 속성에 회원가입 url을 적어줌으로써 해당 버튼이 클릭되면 회원가입 페이지로 이동하도록 합니다.

 

그러면 아래처럼 home 화면에 Register 버튼이 등록된 것을 알 수 있습니다.

 

 

 

 

 

 

728x90
반응형
Comments