회원가입 뷰 생성
이미 회원가입을 위한 모델은 만들어졌으니 먼저 뷰를 만들어봅니다. 이번에는 TemplateView대신 CreateView를 이용할 예정입니다. CreateView는 TemplateView보다 많은 믹스인들이 추가되어 훨씬 다양한 기능을 제공합니다. 이 기능들을 모두 사용하려면 규칙에 따라 설정해야 할 것들이 몇 가지 있습니다.
# user/views.py
from django.views.generic import CreateView
from user.models import User
class UserRegistrationView(CreateView):
template_name = 'user_model.html'
model = User # 자동생성 폼에서 사용할 모델
fields = ('email', 'name', 'password') # 자동생성 폼에서 사용할 필드
Template를 상속받아 정의하던 때랑 달라진 부분이 크게 model, fields 클래스 변수가 추가되고, template_name이라는 클래스 변수가 사라진 점 입니다. CreateView의 model클래스 변수에 참조할 모델 클래스를 정의하면 앞으로 이 뷰에서는 데이터 관련된 부분은 이 모델을 이용할 것이라는 의미입니다.( 폼을 만들 때 model 변수에 정의한 클래스를 참조하고, 폼의 필드들은 모델의 필드의 속성을 참조합니다.)
model이 정의되면 내부적으로 Form객체를 자동으로 생성하는데 이 때 모델의 모든 필드를 이용해서 폼을 만드는 것이 아니라 fields라는 클래스 변수를 참조해서 정의되어 있는 필드만 이용합니다. Form클래스도 커스터마이징이 가능합니다.
django.forms.forms.ModelForm 클래스를 상속받아 정의한 클래스는 form_class라는 클래스 변수에 정의해서 CreateView가 새로 만들지 않고 우리가 Form클래스를 사용하도록 설정할 수 있습니다. UserRegistrationView에서는 기본 생성 폼을 이용할 예정입니다.
template_name이라는 클래스 변수를 정의하지않으면 CreateView변수에서 자동으로 해당 앱의 templates디렉토리에서 앱 이름의 디렉토리 하위의 모델명_form.html파일을 템플릿으로 사용합니다. template_name에 user_model.html을 적용시켜 줬으니 user/template/user_model.html파일이 템플릿이 됩니다.
get, post 요청을 처리할 핸들러 메소드도 정의하지 않았지만 이것 또한 Createview에서 기본적인 것들은 처리해 줍니다.
이제 url이 UserRegistrationView를 라이팅 할 수 있도록 urls.py에 추가해 주고 user_model.html이라는 템플릿도 생성해 봅시다.
# djangotutorial/urls.py
from django.contrib import admin
from django.urls import path
from bbs.views import hello, ArticleListView, ArticleCreateUpdateView, ArticleDetailView
from user.views import UserRegistrationView
urlpatterns = [
path('hello/<to>', hello),
path('admin/', admin.site.urls),
path('article/', ArticleListView.as_view()),
path('article/create/', ArticleCreateUpdateView.as_view()),
path('article/<article_id>/', ArticleDetailView.as_view()),
path('article/<article_id>/update/', ArticleCreateUpdateView.as_view()),
path('user/create/', UserRegistrationView.as_view()),
]
이후 runserver를 통해 서버를 열어주고 user/create주소로 접속하게 되면 아래와 같이 뜹니다.
회원가입 템플릿 생성
이제 템플릿에 form태그와 input태그들을 입력하면 됩니다. 이전 Template 만들기에서 이런 방식은 해봤으니 번은 조금 다르게 장고의 기본 form을 이용해서 template을 만들어 보겠습니다.
<!-- user/template/user/user_model.html -->
{% extends 'base.html' %}
{% block title %}<title>회원 가입</title>{% endblock %}
{% block css %}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
{% endblock css %}
{% block content %}
<div class="panel panel-default registration">
<div class="panel-heading">
가입하기
</div>
<div class="panel-body">
<form action="." method="post">
{% csrf_token %}
{{ form.as_p }}
<div class="form-actions">
<button class="btn btn-primary btn-large" type="submit">가입하기</button>
</div>
</form>
</div>
</div>
{% endblock content %}
장고의 기본 생성 Form으로 템플릿을 만들어 봤습니다. 버튼과 부트스트랩 wrapper컴포넌트를 제외하면 코드는 몇 줄 안 되지만 화면이 생성되었습니다.
아직은 너무 딱딱하기만 한 화면이지만 css를 조금만 추가하면 조금 더 괜찮아집니다. 먼저 렌더링 된 html을 확인 후 style을 맞추도록 하겠습니다.
렌더링
렌더링은 사전적으로 번역이라는 의미도 가지고 있는데, form데이터를 html데이터로 번역한다. 즉, 장고가 이해하는 데이터를 브라우저가 이해할 수 있는 데이터 형태로 변환하는 작업을 장고 템플릿에서 렌더링이라고 합니다. 실제로는 폼이 렌더링 역할을 하지는 않고 각 필드들에게 랜더링을 위임하고 각 필드들을 관리합니다.
</head>
<body>
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="/article/">게시글 목록</a>
</div>
</div>
</nav>
<!-- 추가된 부분 끝 -->
<div class="panel panel-default registration">
<div class="panel-heading">
가입하기
</div>
<div class="panel-body">
<form action="." method="post">
<input type="hidden" name="csrfmiddlewaretoken" value="s2Pn1LZHdUXz3bo6ipYu6NHD4o2c9DsqyiEje8Qgta9oU0wqGJ3YPIUxw6ARWQjj">
<p>
<label for="id_email">Email:</label>
<input type="email" name="email" maxlength="254" required id="id_email">
</p>
<p>
<label for="id_name">이름:</label>
<input type="text" name="name" maxlength="30" id="id_name">
</p>
<p>
<label for="id_password">Password:</label>
<input type="text" name="password" maxlength="128" required id="id_password">
</p>
<div class="form-actions">
<button class="btn btn-primary btn-large" type="submit">가입하기</button>
</div>
</form>
</div>
</div>
</body>
</html>
각 필드마다 p태그로 둘러싸였고 각 input들은 label태그와 짝을 이루었습니다. 각 태그들은 관련된 필드의 이름으로 name 속성과 id속성이 설정되어 있습니다. 좀 더 정확하게는 id는 "id_" + field.name으로 되어 있습니다.
좀 보기 좋게 하려면 모든 label의 너비를 동일하게 하고, 모든 input들의 너비를 동일하게 맞추면 좀 더 깔끔해 보일 듯합니다.
<!-- user/template/user/user_model.html -->
{% extends 'base.html' %}
{% block title %}<title>회원 가입</title>{% endblock %}
{% block css %}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<style>
.registration {
width: 360px;
margin: 0 auto;
}
p {
text-align: center;
}
label {
width: 50%;
text-align: left;
}
.form-action {
text-align: center;
}
</style>
{% endblock css %}
{% block content %}
<div class="panel panel-default registration">
<div class="panel-heading">
가입하기
</div>
<div class="panel-body">
<form action="." method="post">
{% csrf_token %}
{{ form.as_p }}
<div class="form-actions">
<button class="btn btn-primary btn-large" type="submit">가입하기</button>
</div>
</form>
</div>
</div>
{% endblock content %}
완성된 화면의 모습입니다.
이후 제 email과 이름 패스워드를 입력 후 가입하기를 눌러보겠습니다.
정보를 입력후 가입하기를 누르니 email주소가 중복되었다는 메시지와 함께 가입이 되지 않았습니다. 저 email 주소로는 superuser 계정은 만들었지만 사용자 계정은 만든 적이 없습니다. 실제로 DB파일을 확인해 보도록 합시다.
장고 기본폼은 모델폼을 상속받아 생성하는데, 모델폼은 각 필드들이 정상적인 값들인지 검증하기도 하고 오류가 있을 경우 message프레임워크를 통해 각 필드에 메시지를 전달합니다. 또한 폼 객체를 처리할 때 오류가 발생하는 경우에는 오류가 발생한 폼의 필드에 오류 메시지를 전달합니다.
확인해 보니 auth_user와 user_user 두 개의 DB모두에 값이 있는 것을 확인하였습니다.
위 문제보다 거 큰 문제가 있습니다. 바로 제가 입력한 비밀번호가 암호화가 되지 않고 화면에 바로 출력이 됩니다.
또한 장고의 기본폼은 비밀번호 필드를 일반 CharField와 구분할 수 없습니다.
회원가입 폼 생성
가입 폼을 새롭게 만들기 위해서 장고 auth프레임 워크에서 기본적으로 제공하는 UserCreationForm을 가져다 사용합니다. 추가로 우리도 User모델을 직접 임포트 해서 model 변수에 정의했는데 좀 더 유연한 방법으로 가지고 오도록 수정합니다.
# user/views.py
from django.contrib.auth.forms import UserCreationForm
from django.views.generic import CreateView
from user.models import User
class UserRegistrationView(CreateView):
model = User
form_class = UserCreationForm
이후 새로고침을 해주면 위 사진처럼 지저분하게 각 필드마다 무언가를 설명해 주는 문구들이 있는데 UserCreationForm내부에서 폼 생성의 기준이 되는 모델을 auth의 모델로 하드코딩이 되어 있기 때문입니다.
settings.py에서 설정한 AUTH_USER_MODEL로 되어있다면 이런 일이 없겠지만 이 기회에 UserCreateionForm을 상속받아 새로운 폼을 만들어 보겠습니다.
우선 폼 클래스는 보통 forms.py파일에 구현합니다. 새로운 forms.py 파일을 생성 후 UserCreationForm클래스를 상속받아 새로운 폼을 정의합니다.
# user/forms.py
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import UserCreationForm
class UserRegistrationForm(UserCreationForm):
class Meta:
model = get_user_model()
fields = ('email', 'name')
모델 변수를 get_user_model이라는 함수를 호출해서 정의했는데, settings 파일에서 AUTH_USER_MODEL이 가리키는 모델을 자동으로 찾아주는 함수입니다. Meta클래스의 fields라는 변수는 정의된 모델에서 폼에 보여줄 필드들을 정의하는 변수입니다. password는 자동으로 UserCreationForm에서 생성하기 때문에 email과 name 필드만 추가하면 됩니다.
이제 뷰에서도 새로 생성된 폼을 사용하도록 변경합니다. 뷰에서도 마찬가지로 get_user_model 함수를 이용해서 model 변수를 지정하도록 합시다.
# user/views.py
from django.contrib.auth import get_user_model
from django.views.generic import CreateView
from user.forms import UserRegistrationForm
class UserRegistrationView(CreateView):
model = get_user_model()
form_class = UserRegistrationForm
이제 다시 접속해서 어떻게 달라졌는지 확인해 봅시다.
두 가지 정도 더 수정해야 할 화면이 보이네요.
- 일단 Email, 이름, password, password confirmation 부분이랑 입력창을 한 줄로 두고 싶습니다.
- 그리고 비밀번호에 여전히 지저분한 텍스트들이 눈에 안보였으면 좋겠습니다.
1번의 경우는 label 너비를 50%로 주고 입력창을 50프로 줬기 때문에 margin값을 생각하면 label이나 입력창의 너비를 조금 줄여주면 해결이 될 것 같습니다.
2번의 경우는 UserCreateForm의 필드들에 정의되어 있는 help_text 속성이 출력되기 때문에 UserCreateForm이 상속받는 ModelForm은 as_p (as_table, as_ul) 함수 호출 시 폼 객체 자신 또는 폼 객체가 참조하는 모델의 필드에 정의된 help_text속성을 설명문구로써 출력해 줍니다.
위 수정사항들을 수정하기 위해 템플릿을 수정해 주겠습니다.
{% extends 'base.html' %}
{% block title %}<title>회원 가입</title>{% endblock %}
{% block css %}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<style>
.registration {
width: 360px;
margin: 0 auto;
}
label {
width: 43%;
text-align: left;
}
.registration .form-actions > button {
width: 100%;
}
</style>
{% endblock css %}
{% block content %}
<div class="panel panel-default registration">
<div class="panel-heading">
가입하기
</div>
<div class="panel-body">
<form action="." method="post">
{% csrf_token %}
{% for field in form %}
<div class="form-group">
<label for="id_{{ field.html_name}}">{{ field.label }}</label>
{{ field }}
</div>
{% endfor %}
<div class="form-actions">
<button class="btn btn-primary btn-large" type="submit">가입하기</button>
</div>
</form>
</div>
</div>
{% endblock content %}
form객체를 iterate 시키면 각 필드들이 출력이 됩니다. field들을 출력하면 각 필드에 맞는 태그로 렌더링 됩니다. 필드의 레이블도 field.lable_tag값으로 렌더링 할 수 있으나 이렇게 하면 필드 레이블 뒤에 콜론이 자동으로 붙게 돼서 어쩔 수 없이 원초적으로 태그를 직접 하드코딩해 줬습니다. 각 필드들의 name속성의 field.html_name으로 접근이 가능하고 레이블은 field.label으로 접근할 수 있습니다.
이렇게 해주면 거의 완성되었습니다. 그런데 label의 값들이 영어를 한국어로 바꾸어주고 싶습니다. email의 경우는 name과 마찬가지로 모델에서 수정해 주면 되는데 비밀번호의 경우는 UserCreationForm에 정의되어 있는 부분이어서 override 해줘야 합니다. ugettext_lazy라는 함수를 통해서 언어 설정에 따라 출력되는 문자열을 변환해 주는 기능을 사용해 주겠습니다.
# djangotutorial/settings.py
# 생략
LANGUAGE_CODE = 'ko-KR'
TIME_ZONE = 'Asia/Seoul'
# 생략
이후 새로고침을 하면 아래와 같은 화면을 보실 수 있으실 겁니다.
회원가입 한글화
이제 화면은 우리가 원하는 대로 출력이 됩니다. 실제 가입하기가 정상적으로 확인하는지 다시 확인해 봅시다. 간단하게 인증정보를 입력하여 회원가입을 해봤습니다. 그랬더니 여러 증상등이 나왔습니다.
- 화면만 깜빡 거림
- 비밀번호가 지워진 상태로 화면이 다시나 옴
- 오류 발생
화면만 깜빡이는 케이스는 db를 확인해 보니 아무것도 저장이 되지 않았고 ImproperlyConfirue오류가 발생한 경우에는 데이터가 정상적으로 저장된 것으로 확인되었습니다.
화면이 깜빡이는 원인은 폼에서 입력된 값의 유효성 검사를 통과하지 못했다는 의미인데, 어떤 문제가 있었는지 화면에 출력하지 않아서 알 수가 없는 상황입니다.
각 필드마다 errors속성이 있는데 이 속성을 출력하면 해달 필드에서 발생한 문제들이 줄줄이 출력이 됩니다.
<!-- user/template/user/user_model.html -->
<!-- 생략 -->
<form action="." method="post">
{% csrf_token %}
{% for field in form %}
{{ field.errors }} <! -- 필드 상단에 오류 메시지 출력 -->
<div class="form-group">
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
{{ field }}
</div>
{% endfor %}
<div class="form-actions">
<button class="btn btn-primary btn-large" type="submit">가입하기</button>
</div>
</form>
<!-- 생략 -->
다시 가입하기를 통해 확인해 보면 오류메시지가 잘 나타나는 것을 확인할 수 있습니다.
UserCreationForm에서 password2의 필드에 비밀번호를 검증하는 루틴이 추가되어 있는데 내부를 살펴보면 설정파일의 AUTH_PASSWORD_VALIDATORS리스트에 정의된 validator을 모두 통과해야 됩니다. 기본적으로 4개의 validator들이 정의되어 있습니다.
4가지 중 오류메시지가 발생된 3가지와는 다르게 UserAttibuteSimarityValidator의 기능
사용자의 모델 (필드)와 비교해서 유사한 경우 오류를 발생시키는 유틸리티입니다. 기본적으로 비교하는 필드는 정해져 있지만 수정이 가능합니다. userwname, first_name, last_name, email을 비교하는데 email을 제외하고 나머지 3가지는 새로운 사용자 모델을 만들면서 제거한 내용이기 때문에 email과 name 두 가지 필드를 user_attributes이라는 이름으로 옵션으로 전달해 주면 됩니다. 또한 유사도는 max_similarity의 값을 바꿔서 변경해줄수있습니다. validator에 옵션을 전달하는 방법은 설정파일의 AUTH_PASSWORD_VALIDATORS변수에 옵션을 전달해주면 됩니다.
ImproperlyConfirue오류
이 오류는 redirect 될 URL이 정의되지 않아서 발생하는 오류입니다. CreateView에서 내부적으로 폼을 처리한 이후 get_success_url() 함수를 호출해서 이동할 페이지의 주소를 결정하는 뷰의 클래스 변수인 success_url을 참조합니다. 간단히 말하면 success_url의 클래스 변수를 정의해 주면 되는 것입니다.
# user/views.py
from django.contrib.auth import get_user_model
from django.views.generic import CreateView
from user.forms import UserRegistrationForm
class UserRegistrationView(CreateView):
template_name = 'user_model.html'
model = get_user_model()
form_class = UserRegistrationForm
success_url = '/article/'
페이지를 새로고침하고 회원가입을 해보면 이번에는 회원가입 후 정상적으로 게시글 목록 화면으로 이동합니다.
마지막으로 각 필드들의 errors출력에 style을 입히는 작업을 하겠습니다.
<!-- user/template/user/user_model.html -->
{% extends 'base.html' %}
{% load i18n %}
{% block title %}<title>회원 가입</title>{% endblock %}
{% block css %}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<style>
.registration {
width: 360px;
margin: 0 auto;
}
p {
text-align: center;
}
label {
width: 50%;
text-align: left;
}
.control-label {
width: 100%;
}
.registration .form-actions > button {
width: 100%;
}
</style>
{% endblock css %}
{% block content %}
<div class="panel panel-default registration">
<div class="panel-heading">
가입하기
</div>
<div class="panel-body">
<form action="." method="post">
{% csrf_token %}
{% for field in form %}
<div class="form-group {% if field.errors|length > 0 %}has-error{%endif %}">
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
<input name="{{ field.html_name }}" id="{{ field.id_for_lable }}" class="form-control" type="{{ field.field.widget.input_type }}" value="{{ field.value|default_if_none:'' }}">
{% for error in field.errors %}
<label class="control-label" for="{{ field.id_for_label }}">{{ error }}</label>
{% endfor %}
</div>
{% endfor %}
<div class="form-actions">
<button class="btn btn-primary btn-large" type="submit">가입하기</button>
</div>
</form>
</div>
</div>
{% endblock content %}
이제 에러가 발생하면 에러가 발생한 필드 아래에 빨간 메시지가 표시되고 해당 필드도 빨간색으로 표시되도록 변경했습니다.
'BackEnd > Django, DRF' 카테고리의 다른 글
[Django Tutorial] 사용자 인증 - 세션, 인증 관리 (0) | 2023.01.15 |
---|---|
[Django Tutorial] 사용자 인증 - 로그인 기능 생성 (0) | 2023.01.15 |
[Django Tutorial] 사용자 인증 - auth프레임워크, 커스텀 사용자 모델(User) (0) | 2023.01.11 |
[Django Tutorial] Template 만들기 (0) | 2023.01.10 |
[Django Tutorial] 뷰 만들기 (0) | 2023.01.09 |