Programming

; develop a program

Framework/Django

[Django] 장고(Django) 서비스 개발 - 로그인과 로그아웃

Clloud_ 2022. 12. 14. 17:14
반응형

이번 포스팅에서는 장고를 사용하여 게시판 서비스 개발에 필요한 로그인과 로그아웃에 대하여 공부를 해보고자 한다.

 


로그인과 로그아웃

현재 여러 사람이 사용하는 질문 답변 게시판을 만들고 있다.

하지만 지금까지 회원가입 기능도 로그인, 로그아웃 기능도 없었다.

질문을 올린 사람, 답변을 올린 사람을 구별하기 위해서는 로그인과 로그아웃 기능이 필수이다.

 

장고의 로그인, 로그아웃을 도와주는 앱은  django.contrib.auth 이다.

이 앱은 장고 프로젝트 생성시 다음처럼 자동으로 추가된다.

 

[projects\mysite\config\settings.py]

INSTALLED_APPS = [
    (... 생략 ...)
    'django.contrib.auth',
    (... 생략 ...)
]

 

django.contrib.auth 을 이용하면 로그인과 로그아웃 기능을 정말 쉽게 구현할 수 있다. 

 


common 앱

로그인 · 로그아웃은 pybo 앱에 구현해야 옳을까? 라는 생각에 아니라고 말할 수 있다.

하나의 웹 사이트에는 파이보와 같은 게시판 서비스 외에도 블로그나 쇼핑몰과 같은 굵직한 단위의 앱들이 함께 있을 수 있기 때문에 공통으로 사용되는 기능인 로그인이나 로그아웃을 이 중의 하나의 앱에 종속시키는 것은 좋지 않기 때문이다.

이러한 이유로 여기서는 로그인 · 로그아웃을 "공통 기능을 가진 앱"이라는 의미의 common 앱에 구현할 것이다.

 

다음처럼 common 앱을 신규로 생성한다.

(mysite) c:\projects\mysite>django-admin startapp common

 

common앱을 생성하면 pybo앱과 동일한 구조의 디렉터리와 파일들이 자동으로 생성된다.

 

 

그리고 config/settings.py 파일에 생성한 common 앱을 등록한다.

 

[파일명: projects\mysite\config\settings.py]

(... 생략 ...)

INSTALLED_APPS = [
    'common.apps.CommonConfig',  
    'pybo.apps.PyboConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

(... 생략 ...)

 

이어서 common 앱의 urls.py 파일을 사용하기 위해 config/urls.py 파일을 다음과 같이 수정한다.

 

[파일명: projects\mysite\config\urls.py]

from django.contrib import admin
from django.urls import path, include


urlpatterns = [
    path('admin/', admin.site.urls),
    path('pybo/', include('pybo.urls')),
    path('common/', include('common.urls')),
]

 

이제 http://localhost:8000/common/ 으로 시작하는 URL은 모두 common/urls.py 파일을 참조할 것이다.

 

그리고 common/urls.py 파일을 신규로 작성한다.

 

[파일명: projects\mysite\common\urls.py]

app_name = 'common'

urlpatterns = [
]

 

아직 common 앱에 어떤 기능도 구현하지 않았으므로 urlpatterns는 빈 상태로 둔다.

 


로그인

로그인의 시작은 로그인 화면이다.

로그인 화면으로 진입할 수 있도록 templates/navbar.html 파일의 '로그인' 링크를 다음처럼 수정한다.

 

[파일명: projects\mysite\templates\navbar.html]

(... 생략 ...)
<ul class="navbar-nav">
    <li class="nav-item ">
        <a class="nav-link" href="{% url 'common:login' %}">로그인</a>
    </li>
</ul>
(... 생략 ...)

 


로그인 뷰

navbar.html 파일에서 템플릿 태그로 {% url 'common:login' %} 를 사용했으므로 common/urls.py 파일에 다음과 같은 URL 매핑 규칙을 추가한다.

 

[파일명: projects\mysite\common\urls.py]

from django.urls import path
from django.contrib.auth import views as auth_views

app_name = 'common'

urlpatterns = [
    path('login/', auth_views.LoginView.as_view(), name='login'),
]

 

로그인 뷰는 따로 만들 필요없이 위 코드처럼 django.contrib.auth 의 LoginView를 사용하도록 설정한다.

 


로그인 템플릿

여기까지 수정하고 브라우저에서 내비게이션바의 '로그인' 링크를 누르면 다음과 같은 오류 페이지가 나타날 것이다.

 

 

위 오류는 registration 디렉터리login.html 파일이 없음을 의미한다.

앞에서 사용한 LoginView는 registration이라는 템플릿 디렉터리에서 login.html 파일을 찾는다.

그런데 이 파일을 찾지 못해 오류가 발생한 것이다.

이 오류를 해결하려면 registration/login.html 템플릿 파일을 작성해야 한다.

 

하지만 로그인은 common 앱에 구현할 것이므로 오류 메시지에 표시한 것처럼 registration 디렉터리에 템플릿 파일을 생성하기보다는 common 디렉터리에 템플릿을 생성하는 것이 좋다.

이를 위해 LoginView가 common 디렉터리의 템플릿을 참조할 수 있도록 common/urls.py 파일을 다음과 같이 수정하자.

 

[파일명: projects\mysite\common\urls.py]

from django.urls import path
from django.contrib.auth import views as auth_views

app_name = 'common'

urlpatterns = [
    path('login/', auth_views.LoginView.as_view(template_name='common/login.html'), name='login'),
]

 

위와 같이 수정하면 이제 registration 디렉터리가 아닌 common 디렉터리에서 login.html 파일을 참조하게 된다.

코드 수정 후 로그인 링크를 누르면 여전히 오류가 발생한다.

하지만 조금만 더 자세히 보면 메시지가 변경되었음을 알 수 있다.

 

 

registration/login.html이 아닌 common/login.html이 없다는 오류로 변경되었다.

 

이제 common/login.html 파일을 생성하기 위해 common 템플릿 디렉터리를 다음과 같이 생성한다.

(mysite) c:\projects\mysite>cd templates
(mysite) c:\projects\mysite\templates>mkdir common

 

그리고 common 템플릿 디렉터리에 login.html 템플릿을 다음과 같이 작성한다.

 

[파일명: projects\mysite\templates\common\login.html]

{% extends "base.html" %}
{% block content %}
<div class="container my-3">
    <form method="post" action="{% url 'common:login' %}">
        {% csrf_token %}
        {% include "form_errors.html" %}
        <div class="mb-3">
            <label for="username">사용자ID</label>
            <input type="text" class="form-control" name="username" id="username"
                   value="{{ form.username.value|default_if_none:'' }}">
        </div>
        <div class="mb-3">
            <label for="password">비밀번호</label>
            <input type="password" class="form-control" name="password" id="password"
                   value="{{ form.password.value|default_if_none:'' }}">
        </div>
        <button type="submit" class="btn btn-primary">로그인</button>
    </form>
</div>
{% endblock %}

 

사용자ID와 비밀번호를 입력으로 받아 로그인하는 간단한 템플릿이다.

로그인에 사용되는 사용자ID를 의미하는 username과 비밀번호를 의미하는 password 항목은 django.contrib.auth 앱이 요구하는 하는 필수항목이다.

 

[django.contrib.auth 인증시 다음 2개의 항목은 반드시 필요하다]

  • username - 사용자명
  • password - 비밀번호

그리고 {% csrf_token %} 바로 밑에 include 태그로 포함된 form_errors.html 템플릿 파일은 다음과 같이 작성한다.

 

[파일명: projects\mysite\templates\form_errors.html]

<!-- 필드 오류와 넌필드 오류를 출력한다. -->
{% if form.errors %}
<div class="alert alert-danger">
    {% for field in form %}
    <!-- 필드 오류 -->
    {% if field.errors %}
    <div>
        <strong>{{ field.label }}</strong>
        {{ field.errors }}
    </div>
    {% endif %}
    {% endfor %}
    <!-- 넌필드 오류 -->
    {% for error in form.non_field_errors %}
    <div>
        <strong>{{ error }}</strong>
    </div>
    {% endfor %}
</div>
{% endif %}

 

form_errors.html 템플릿은 로그인 실패시 로그인이 왜 실패했는지 알려주는 역할을 한다.

폼 오류에는 다음과 같이 두 가지 종류의 오류가 있다.

  • 필드 오류 (field.errors)
  • 넌필드 오류 (form.non_field_errors)

 

필드 오류는 사용자가 입력한 필드 값에 대한 오류로 값이 누락되었거나 필드의 형식이 일치하지 않는 경우에 발생하는 오류이다.

넌필드 오류는 필드의 값과는 상관없이 다른 이유로 발생하는 오류이다.

form_errors.html 템플릿은 필드 오류와 넌필드 오류 모두를 표시하기 위해 삽입되는 템플릿이다.

 

question_form.html, question_detail.html 템플릿에서 오류를 표시하기 위해 추가했던 HTML코드를 {% include "form_errors.html" %} 으로 대체해도 좋다.

 


로그인 수행

이제 내비게이션바의 로그인 링크를 클릭하면 다음과 같은 화면을 볼 수 있다.

 

 

입력값을 누락하거나 엉뚱한 값으로 로그인하려고 시도하면 다음처럼 그에 해당하는 오류메시지가 표시될 것이다.

 

 

현재 로그인이 가능한 사용자는 슈퍼유저로 생성한 "admin" 뿐이다.

사용자 ID에 admin을 입력하고 비밀번호에 1111을 입력하여 로그인을 수행한다.

 

그러면 아마도 다음과 같은 오류를 볼 수 있을 것이다.

 

 

오류가 발생한 이유는 로그인이 성공하면  django.contrib.auth 패키지는 디폴트로 /accounts/profile/ 이라는 URL로 이동시키기 때문이다. 

로그인은 성공한 것이 맞다.

 

다만 /accounts/profile/ URL은 현재 우리가 파이보에 구성한 URL 구조와 맞지 않으므로 로그인 성공 시 '/' 페이지로 이동할 수 있도록 config/settings.py 파일을 수정한다.

마지막 줄에 LOGIN_REDIRECT_URL을 추가하면 된다.

 

[파일명: projects\mysite\config\settings.py]

(... 생략 ...)

# 로그인 성공후 이동하는 URL
LOGIN_REDIRECT_URL = '/'

 

이렇게 수정하고 다시 로그인을 수행하면 이번에는 다음과 같은 오류가 발생한다.

 

 

왜냐하면 '/' 를 의미하는 http://localhost:8000/ 페이지에 대한 URL 매핑 규칙을 작성하지 않았기 때문이다.

 

이제 config/urls.py 파일에 '/' 페이지에 대응하는 URL 매핑 규칙을 추가한다.

 

[파일명: C:\projects\mysite\config\urls.py]

from django.contrib import admin
from django.urls import path, include
from pybo import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('pybo/', include('pybo.urls')),
    path('common/', include('common.urls')),
    path('', views.index, name='index'),  # '/' 에 해당되는 path
]

 

이렇게 수정하면 '/' 페이지 요청에 대해 path('', views.index, name='index')가 작동하여 pybo/views.py 파일의 index 함수가 실행된다.

 


로그아웃

로그인에 성공했지만, 내비게이션바에는 여전히 "로그인" 링크가 보인다.

로그인 후에는 "로그인" 링크가 "로그아웃" 링크로 바뀌어야 할 것이다. 

반대로 로그아웃 상태에서는 "로그인" 링크로 바뀌어야 한다.

 

navbar.html 템플릿 파일에서 로그인 링크 부분을 다음과 같이 수정한다.

 

[파일명: projects\mysite\templates\navbar.html]

(... 생략 ...)
<li class="nav-item">
    {% if user.is_authenticated %}
    <a class="nav-link" href="{% url 'common:logout' %}">{{ user.username }} (로그아웃)</a>
    {% else %}
    <a class="nav-link" href="{% url 'common:login' %}">로그인</a>
    {% endif %}
</li>
(... 생략 ...)

 

{% if user.is_authenticated %} 은 현재 사용자가 로그인 되었는지를 판별한다.

따라서 로그인이 되어 있으면 "로그아웃" 링크를 표시하고 로그인이 되어 있지 않다면 "로그인" 링크를 표시할 것이다.

로그인 상태에서는 로그아웃 링크에 사용자명 {{ user.username }} 도 추가로 표시한다.

 

로그아웃 링크가 추가되었으므로 {% url 'common:logout' %} 에 대응하는 URL 매핑을 common/urls.py 파일에 추가해야 한다.

 

[파일명: projects\mysite\common\urls.py]

from django.urls import path
from django.contrib.auth import views as auth_views

app_name = 'common'

urlpatterns = [
    path('login/', auth_views.LoginView.as_view(template_name='common/login.html'), name='login'),
    path('logout/', auth_views.LogoutView.as_view(), name='logout'),
]

 

이제 슈퍼 유저로 로그인하면 로그인 링크가 다음과 같이 변경될 것이다.

 

 

그리고 로그아웃 시 리다이렉트할 위치도 config/settings.py 파일에 추가한다.

 

[파일명: projects\mysite\config\settings.py]

(... 생략 ...)

# 로그인 성공후 이동하는 URL
LOGIN_REDIRECT_URL = '/'

# 로그아웃시 이동하는 URL
LOGOUT_REDIRECT_URL = '/'

 

로그아웃 시 '/' 페이지로 이동하기 위해 LOGOUT_REDIRECT_URL을 설정했다.

코드 수정 후 로그인 · 로그아웃을 해보면 잘 작동하는 것을 볼 수 있다.

 


반응형