# account/forms.py
class CustomUserCreationForm(UserCreationForm): # 기존 ModelForm인 UserCreationForm을 상속받아 새로운 폼을 만든다.
class Meta:
model = User # User Model의 model field를 이용해서 Form field를 구성.
# fields = "__all__" # 모델의 모든 field들을 이용해서 구성.
fields = ["username", "password1", "password2", "name", "email", "birthday"] # 특정 model field들을 선택해서 구성.
# exclude = ["name"] # 지정한 model field를 제외한 나머지를 이용해서 구성. (fields와 exclude는 같이 사용 못한다.)
# Form Field들의 input type을 변경.
## birthday input type=text ==> input type=date
# key: field 이름 value: Widget 객체
widgets = {
"birthday":forms.DateInput(attrs={"type":"date"}),
# "name":forms.PasswordInput()
}
fields에 이름, 이메일, 생일 추가

# account/forms.py
## Form이나 ModelForm 클래스들을 정의하는 모듈
## 보통 Form은 등록/수정 폼 각각 하나씩 정의.
## User 등록 폼, User 수정 폼 ==> ModelForm
from django import forms
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from .models import User
# ModelForm: ModelForm을 상속, Form: forms.Form을 상속
# ModelForm: 모델을 이용해 입력 양식 구성
# Form: 사용자가 직접 입력 양식 구성
# UserCreationForm에 정의된 Form필드: username, password1, password2
# CustomUserCreationFrom: UserCreationForm의 form필드 + name, email, birthday
class CustomUserCreationForm(UserCreationForm): # 기존 ModelForm인 UserCreationForm을 상속받아 새로운 폼을 만든다.
class Meta:
model = User # User Model의 model field를 이용해서 Form field를 구성.
# fields = "__all__" # 모델의 모든 field들을 이용해서 구성.
fields = ["username", "password1", "password2", "name", "email", "birthday"] # 특정 model field들을 선택해서 구성.
# exclude = ["name"] # 지정한 model field를 제외한 나머지를 이용해서 구성. (fields와 exclude는 같이 사용 못한다.)
# Form Field들의 input type을 변경.
## birthday input type=text ==> input type=date
# key: field 이름 value: Widget 객체
widgets = {
"birthday":forms.DateInput(attrs={"type":"date"}),
# "name":forms.PasswordInput()
}
#########################
# 검증
# Form/ModelForm에서 하는 기본 검증
# - blank=False: required 검증
# - 숫자 입력: 숫자인지 검증
# - Email/일시 입력: email/일시 형식을 검증.
#
# 검증 메소드를 추가. - 도메인 특화 검증을 할 경우 정의
# - clean(): 모든 필드들을 한 번에 검증
# - clean_field이름(): 개별 field를 검증
# - 검증시 문제가 발생하면 forms.ValidationError("에러이유메시지") 발생.
# - 검증시 문제가 없으면 검증한 값(요청파라미터)를 반환.
# name: 두 글자 이상 입력.
def clean_name(self):
# self.cleaned_data : dictionary - 기본 검증을 통과한 요청파라미터들을 조회.
name = self.cleaned_data['name']
if len(name) < 2: # 한 글자만 입력
raise forms.ValidationError("사용자 이름은 두 글자 이상 입력하세요.")
return name # 검증 통과시 name 리턴.
# account/views.py
from django.shortcuts import render, redirect
from django.urls import reverse
from .forms import CustomUserCreationForm
#########################
# 사용자 가입 처리
# 요청 url: account/create
# 가입이나 수정은 항상 두 단계로 나뉜다.
# GET - 가입 폼 양식을 응답
# POST - 가입 처리
# view 함수: create
# 응답: GET - templates/account.create.html
# POST - home으로 이동. redirect 방식으로 (새로고침해도 votes 추가 되지 않게 하기 위해서)
def create(request):
if request.method == "GET":
return render(request, "account/create.html", {"form":CustomUserCreationForm()})
else: # POST
# 가입처리
# 1. 요청파라미터 조회.
form = CustomUserCreationForm(request.POST, request.FILES)
# 요청파라미터로 넘어온 값들을 Form의 instance 변수(attribute)에 저장.
# 2. 요청파라미터 검증
# form.is_valid(): bool -> True: 검증 성공, False: 검증 실패
if form.is_valid():
# 검증 성공 -> 가입 처리
# form: ModelForm은 save() 기능 제공. -> DB insert.
# 반환: insert 처리한 결과를 가진 Model을 반환.
user = form.save()
print("---- 가입: user:", type(user), user)
return redirect(reverse("home"))
else:
# 검증 실패 -> 실패 처리 -> 가입화면으로 이동.
pass
사용자 인증에는 아래 세 가지가 있다.
1. 로그인
2. 로그인 상태 유지
3. 로그아웃 (로그인 상태 해제)
로그인 구현 -> 로그인 모델 폼. AuthenticationForm
# account/views.py
from django.shortcuts import render, redirect
from django.urls import reverse
from django.contrib.auth.forms import(
AuthenticationForm, # 로그인 폼
PasswordChangeForm # 비밀번호 변경 폼
)
from django.contrib.auth import (
login, # 로그인 처리 함수. 로그인한 사용자의 User Model 객체를 session에 저장해서 로그인상태를 유지하도록 한다.
logout, # 로그아웃 처리 함수. 로그인 상태를 종료.
authenticate # 인증 확인 함수. (username, password를 DB에서 확인)
)
from .forms import CustomUserCreationForm
#########################
# 사용자 가입 처리
# 요청 url: account/create
# 가입이나 수정은 항상 두 단계로 나뉜다.
# GET - 가입 폼 양식을 응답
# POST - 가입 처리
# view 함수: create
# 응답: GET - templates/account.create.html
# POST - home으로 이동. redirect 방식으로 (새로고침해도 votes 추가 되지 않게 하기 위해서)
def create(request):
if request.method == "GET":
return render(request, "account/create.html", {"form":CustomUserCreationForm()})
else: # POST
# 가입처리
# 1. 요청파라미터 조회.
form = CustomUserCreationForm(request.POST, request.FILES)
# 요청파라미터로 넘어온 값들을 Form의 instance 변수(attribute)에 저장. + 검증 작업
# 2. 요청파라미터 검증
# form.is_valid(): bool -> True: 검증 성공, False: 검증 실패
if form.is_valid():
# 검증 성공 -> 가입 처리
# form: ModelForm은 save() 기능 제공. -> DB insert.
# 반환: insert 처리한 결과를 가진 Model을 반환.
user = form.save()
print("---- 가입: user:", type(user), user)
return redirect(reverse("home"))
else:
# 검증 실패 -> 실패 처리 -> 가입화면으로 이동.
return render(
request, "account/create.html", {"form": form} # 요청파라미터와 검증결과를 가진 form을 전달.
)
########################################
# Loging 처리 View
# 요청 URL: /account/login
# view: user_login
# - GET: Login form 제공
# - POST: Login 처리
# template
# - GET: templates/account/login.html, POST: home (redirect)
def user_login(request):
if request.method == "GET":
# 로그인 폼을 응답
return render(request, "account/login.html", {"form":AuthenticationForm()})
else:
pass
<!-- account/templates/account/login.html -->
{% extends "layouts/main_layout.html" %}
{% load django_bootstrap5 %}
{% block title %}로그인{% endblock title %}
{% block contents %}
<h1>로그인</h1>
<form method="post">
{% bootstrap_form form %}
<div class="mt-3">
<button type="submit" class="btn btn-primary">로그인</button>
<button type="reset" class="btn btn-primary">초기화</button>
</div>
</form>
{% endblock contents %}
path("login", views.user_login, name="login")

그러면 주소에 /account/login을 입력했을 때 위와 같이 로그인 화면이 뜬다.
<a href="{% url 'account:create' %}" class="nav-link">가입</a>
<a href="{% url 'account:login' %}" class="nav-link">로그인</a>

가입, 로그인 버튼이 생김~!
# account/views.py
from django.shortcuts import render, redirect
from django.urls import reverse
from django.contrib.auth.forms import(
AuthenticationForm, # 로그인 폼
PasswordChangeForm # 비밀번호 변경 폼
)
from django.contrib.auth import (
login, # 로그인 처리 함수. 로그인한 사용자의 User Model 객체를 session에 저장해서 로그인상태를 유지하도록 한다.
logout, # 로그아웃 처리 함수. 로그인 상태를 종료.
authenticate # 인증 확인 함수. (username, password를 DB에서 확인)
)
from .forms import CustomUserCreationForm
#########################
# 사용자 가입 처리
# 요청 url: account/create
# 가입이나 수정은 항상 두 단계로 나뉜다.
# GET - 가입 폼 양식을 응답
# POST - 가입 처리
# view 함수: create
# 응답: GET - templates/account.create.html
# POST - home으로 이동. redirect 방식으로 (새로고침해도 votes 추가 되지 않게 하기 위해서)
def create(request):
if request.method == "GET":
return render(request, "account/create.html", {"form":CustomUserCreationForm()})
else: # POST
# 가입처리
# 1. 요청파라미터 조회.
form = CustomUserCreationForm(request.POST, request.FILES)
# 요청파라미터로 넘어온 값들을 Form의 instance 변수(attribute)에 저장. + 검증 작업
# 2. 요청파라미터 검증
# form.is_valid(): bool -> True: 검증 성공, False: 검증 실패
if form.is_valid():
# 검증 성공 -> 가입 처리
# form: ModelForm은 save() 기능 제공. -> DB insert.
# 반환: insert 처리한 결과를 가진 Model을 반환.
user = form.save()
print("---- 가입: user:", type(user), user)
return redirect(reverse("home"))
else:
# 검증 실패 -> 실패 처리 -> 가입화면으로 이동.
return render(
request, "account/create.html", {"form": form} # 요청파라미터와 검증결과를 가진 form을 전달.
)
########################################
# Loging 처리 View
# 요청 URL: /account/login
# view: user_login
# - GET: Login form 제공
# - POST: Login 처리
# template
# - GET: templates/account/login.html, POST: home (redirect)
def user_login(request):
if request.method == "GET":
# 로그인 폼을 응답
return render(request, "account/login.html", {"form":AuthenticationForm()})
else:
# 로그인 처리 -> username/password 확인 -> 로그인 상태 유지 처리
username = request.POST['username']
password = request.POST['password']
# User모델(settings.AUTH_USER_MODEL)을 기반으로 사용자 인증 처리. (DB로 부터 username, password를 확인)
## 유효한 username/password이면 User 모델 객체 반환.
## 유효하지 않은 경우 None을 반환.
user = authenticate(request, username=username, password=password)
if user is not None:
# 유효한 사용자 계정
login(request, user)
return redirect(reverse("home"))
else: # 유효하지 않은 사용자 계정 (None이 반환됨.)
return render(request, "account/login.html",
{"form":AuthenticationForm(),
"error_message":"username 또는 password를 다시 확인하세요."})
에러메세지 관련 수정
<!-- account/templates/account/login.html -->
{% extends "layouts/main_layout.html" %}
{% load django_bootstrap5 %}
{% block title %}로그인{% endblock title %}
{% block contents %}
<h1>로그인</h1>
{% if error_message %}
<div style="color:red;font-size: 0.8em;">{{error_message}}</div>
{% endif %}
<form method="post">
{% csrf_token %} <!-- post 방식일 때 꼭 넣어주기 -->
{% bootstrap_form form %}
<div class="mt-3">
<button type="submit" class="btn btn-primary">로그인</button>
<button type="reset" class="btn btn-primary">초기화</button>
</div>
</form>
{% endblock contents %}
#################################
# Logout 처리
# url: /account/logout
# view: user_logout
# template: redirect 방식 - home
def user_logout(request):
logout(request) # 로그아웃 처리. 로그인 상태유지 종료.
return redirect(reverse("home"))
# account/urls.py
from django.urls import path
from . import views
app_name="account"
urlpatterns = [
path("create", views.create, name="create"),
path("login", views.user_login, name="login"),
path("logout", views.user_logout, name="logout")
]
<!-- 메뉴 - nav -->
<nav class="navbar navbar-expand-sm bg-success bg-opacity-20">
<!-- bg-success: 배경 초록색 / bg-opacity-10: 투명도 조절 (숫자 작을수록 투명) -->
<div class="container">
<div class="navbar-nav">
<a href="{% url 'polls:vote_create' %}" class="nav-link">설문 등록</a>
<a href="{% url 'polls:list' %}" class="nav-link">설문 목록</a>
<a href="{% url 'account:create' %}" class="nav-link">가입</a>
<a href="{% url 'account:login' %}" class="nav-link">로그인</a>
<a href="{% url 'account:logout' %}" class="nav-link">로그아웃</a>
<a class="nav-link">{{user.name}}</a>
</div>
</div>
</nav>

로그인 하면 사용자 이름이 안 뜨고

로그인 하면 갑자기 왕창 보이지?
main_layout을 변경했는데 ...
<nav class="navbar navbar-expand-sm bg-success bg-opacity-20">
<!-- bg-success: 배경 초록색 / bg-opacity-10: 투명도 조절 (숫자 작을수록 투명) -->
<div class="container">
<div class="navbar-nav">
<a href="{% url 'polls:list' %}" class="nav-link">설문 목록</a>
{% if user.is_authenticated %}
<a href="{% url 'polls:vote_create' %}" class="nav-link">설문 등록</a>
<a href="{% url 'account:logout' %}" class="nav-link">로그아웃</a>
{% endif %}
{% if not user.is_authenticated %}
<a href="{% url 'account:create' %}" class="nav-link">가입</a>
<a href="{% url 'account:login' %}" class="nav-link">로그인</a>
{% endif %}
<a class="nav-link">{{user.name}}</a>
</div>
</div>
</nav>
이렇게 하면 로그인 했을 때랑 아닐 때 메뉴가 다르게 보인다.
{# 로그인 안 한 경우 투표를 못 하도록 처리 #}
{% if not user.is_authenticated %}
<button type="submit">투표</button>
<button type="reset">선택해제</button>
{% endif %}
#################################
# 사용자 정보를 조회하는 View
# - 단순히 template만 실행해서 응답하는 View
# - TemplateView.as_view(template_name="template경로") ==> urls.py
# def detail(request):
# return
# /account/urls.py
path("detail", TemplateView.as_view(template_name="account/detail.html"), name="detail"),
{% if user.is_authenticated %}
<a href="{% url 'polls:vote_create' %}" class="nav-link">설문 등록</a>
<a href="{% url 'account:logout' %}" class="nav-link">로그아웃</a>
<a href="{% url 'account:detail' %}" class="nav-link">{{user.name}}</a> <!-- 여기!! -->
{% endif %}

로그인을 해야 볼 수 있는 화면을 구축하기 위해 아래 코드를 필요한 위치에 적절히 삽입한다.
@login_required
예를 들어, 회원이 로그인을 한 상태일 때만 로그아웃이 보여야 하므로 아래와 같이 코드를 쓴다.
@login_required
def user_logout(request):
logout(request) # 로그아웃 처리. 로그인 상태유지 종료.
return redirect(reverse("home"))
# settings.py
##############################
# 로그인 관련 설정
##############################
# 로그인 하지 않은 사용자가 @login_required인 View를 호출 했을 때 이동할 url을 지정.
LOGIN_URL = "/account/login"
# account/views.py
user = authenticate(request, username=username, password=password)
if user is not None:
# 유효한 사용자 계정
login(request, user)
next_url = request.GET.get("next")
if next_url is not None: # 여기! if문 추가
return redirect(next_url)
return redirect(reverse("home"))
else: # 유효하지 않은 사용자 계정 (None이 반환됨.)
return render(request, "account/login.html",
{"form":AuthenticationForm(),
"error_message":"username 또는 password를 다시 확인하세요."})