[17. Django] 사용자 생성/인증 (250711 수업)

해피해피슈크림·2025년 7월 11일

1. 사용자 생성

사용자 생성 폼

# 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

2. 사용자 인증

사용자 인증에는 아래 세 가지가 있다.
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 %}

account/urls.py에 path 추가

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>


가입, 로그인 버튼이 생김~!

로그인 정보가 다를시 로그인 다시!

views.py

# 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를 다시 확인하세요."})

login.html

에러메세지 관련 수정

<!-- 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 %}

views.py 로그아웃 처리

#################################
# Logout 처리
# url: /account/logout
# view: user_logout
# template: redirect 방식 - home
def user_logout(request):
    logout(request) # 로그아웃 처리. 로그인 상태유지 종료.
    return redirect(reverse("home"))

urls.py에 로그아웃 path 추가

# 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을 변경했는데 ...

-> main layout이 아니라 home.html에 main

로그인 여부에 따라 메뉴 다르게 보이게 하기

main_layout.html

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

이렇게 하면 로그인 했을 때랑 아닐 때 메뉴가 다르게 보인다.

vote_form.html

{# 로그인 안 한 경우 투표를 못 하도록 처리 #}
        {% if not user.is_authenticated %}
            <button type="submit">투표</button>
            <button type="reset">선택해제</button>
        {% endif %}

회원 이름 클릭했을 때 디테일 보여주기

views.py

#################################
# 사용자 정보를 조회하는 View
#   - 단순히 template만 실행해서 응답하는 View
#   - TemplateView.as_view(template_name="template경로") ==> urls.py
# def detail(request):
#   return 
# /account/urls.py

account/urls.py에 detail.html path 추가

path("detail", TemplateView.as_view(template_name="account/detail.html"), name="detail"),

main_layout.html에 detail.html 추가 -> 회원 정보 조회 레이아웃 설정

{% 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 %}

결과


views.py (polls, account)

로그인을 해야 볼 수 있는 화면을 구축하기 위해 아래 코드를 필요한 위치에 적절히 삽입한다.

@login_required

예를 들어, 회원이 로그인을 한 상태일 때만 로그아웃이 보여야 하므로 아래와 같이 코드를 쓴다.

@login_required
def user_logout(request):
    logout(request) # 로그아웃 처리. 로그인 상태유지 종료.
    return redirect(reverse("home"))

로그인 하지 않은 사용자가 @login_required인 View를 호출했을 때

# 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를 다시 확인하세요."})

0개의 댓글