[Django] 파이보 서비스 개발 - 로그인과 로그아웃

싱숭생숭어·2023년 5월 9일
0

Django

목록 보기
19/19
post-thumbnail

위 글은 점프 투 장고를 참고해 작성하였습니다.

지금까지 이 시리즈에서 파이보라는 앱은 여러 사람이 사용하는 질문 답변 게시판이다. 하지만 현재까지 파이보에는 회원가입 기능 및 로그인, 로그아웃 기능이 없었다. 질문을 올린 사람, 답변을 올린 사람을 구별하기 위해서는 로그인과 로그아웃 기능이 필수! 따라서 이 포스팅에서는 파이보의 로그인, 로그아웃 기능을 구현해보자 !!

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

이 앱은 장고 프로젝트 생성시 아래처럼 config\settings.py의 INSTALLED_APPS 부분에 기본으로 추가되어있음

INSTALLED_APPS = [
    'pybo.apps.PyboConfig',
    'django.contrib.admin',
    'django.contrib.auth', #이 부분 !!
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

이 앱을 이용하면 로그인과 로그아웃 기능을 정말 쉽게 구현할 수 있다 한다(책에서)

과연 정말 쉬운지 !! 아래에서 알아보자 😶‍🌫️


common 앱

로그인, 로그아웃은 pybo 게시판 뿐만 아니라 다른 앱들에서도 동일하게 구현해야 하는 기능이다. 따라서 하나의 앱에 로그인 기능을 종속시키는 것 대신 "공통 기능을 가진 앱" 인 common 앱을 신규로 생성하여 common 앱에 기능을 구현하도록 하자.

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

(mysite) gyu@DESKTOP-4OUGKIK:~/workspace/projects/mysite$ django-admin startapp common

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

이후 mysite\config\settings.py파일에 생성한 common 앱을 등록

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 파일을 다음과 같이 수정

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')), #이 부분 추가 !!
]
  • 위의 path를 지정해줌으로써 이제 http://localhost:8000/common/으로 시작하는 URL은 모두 common/urls.py 파일을 참조할 것 !

그리고 참조할 common/urls.py파일을 신규로 생성하자(아직 common앱에 어떤 기능도 구현하지 않았으므로 urlpatterns는 빈 상태로)


로그인

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

(... 생략 ...)
<ul class="navbar-nav">
    <li class="nav-item ">
        <a class="nav-link" href="{% url 'common:login' %}">로그인</a> <!--이 부분 추가-->
    </li>
</ul>
(... 생략 ...)
  • <a class="nav-link" href="#">로그인</a> -> <a class="nav-link" href="{% url 'common:login' %}">로그인</a>

로그인 뷰

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

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 파일을 찾음
    • 이 오류를 해결하려면 로그인 템플릿 파일을 작성해야 함

하지만 로그인은 common 앱에 구현할 것이므로 오류페이지에 뜬 것처럼 registration 디렉터리에 템플릿 파일을 생성하기보다 common 디렉터리에 템플릿을 생성해 이를 참조하도록 하는 것이 좋다. 이를 위해 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안의 LoginView.as_view가 template 이름이 common/login.html인 템플릿을 참조하도록 수정하였음

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

수정 후 다시 오류 페이지를 보면 registration -> common으로 오류 메시지가 변경된 것을 볼 수 있다.

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

(mysite) gyu@DESKTOP-4OUGKIK:~/workspace/projects/mysite$ cd templates
(mysite) gyu@DESKTOP-4OUGKIK:~/workspace/projects/mysite/templates$ mkdir common

방금 생성한 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 앱이 요구하는 필수항목 2가지

{% 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)는 필드의 값과 상관없이 다른 이유로 발생하는 오류

      • 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 구조와 맞지 않으므로 로그인 성공 시 /페이지(= 현재 기본 URL인 http://localhost:8000/ 페이지)로 이동할 수 있도록 config/settings.py 파일을 수정하자. 마지막 줄에 LOGIN_REDIRECT_URL을 추가하면 됨 !

    • 로그인은 성공했지만 그 이후 이동한 페이지에서 오류가 발생하는 것

config/settings.pyLOGIN_REDIRECT_URL = '/' 한줄만 추가해주기 !!

그럼 이제는 404 에러가 발생한다.

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

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

from django.contrib import admin
from django.urls import path, include
from pybo import views #이 부분 추가 = pybo의 views를 import


urlpatterns = [
    path('admin/', admin.site.urls),
    path('pybo/', include('pybo.urls')),
    path('common/', include('common.urls')),
    path('', views.index, name='index'),  # '/' 에 해당되는 path 추가
]
  • 이렇게 URL 매핑 규칙을 추가하면 /페이지 요청에 대해 path('', views.index, name='index')가 작동하여 pybo/views.py 파일의 index 함수가 실행됨.

이후 다시 네비게이션바의 로그인 링크를 클릭해 admin으로 로그인을 시도하면

제대로 로그인이 되는 것을 확인할 수 있다 !!!


로그아웃

사진처럼 로그인에 성공했지만, 네비게이션바에는 여전히 "로그인" 링크가 보인다. 로그인 후에는 "로그인" -> "로그아웃" 링크로 바뀌어야 한다.

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 %} 은 현재 사용자가 로그인 되었는지를 판별한다. 따라서 로그인이 되어 있으면 username과 "로그아웃" 링크를 표시하고, 로그인이 되어 있지 않다면 "로그인" 링크를 표시할 것이다.

템플릿에서 User 사용하기
뷰 함수에서 템플릿에 User 객체를 전달하지 않아도 템플릿에서는 django.contrib.auth 기능으로 인해 User 객체를 사용 가능함(아래가 대표적)

  • user.is_authenticated - 현재 사용자가 인증되었는지 여부 (로그인한 상태라면 true, 로그아웃 상태라면 false)
  • user.is_anonymous - is_authenticated의 반대 경우 (로그인한 상태라면 false, 로그아웃 상태라면 true)
  • user.username - 사용자명 (사용자 ID)
  • user.is_superuser - 사용자가 슈퍼유저인지 여부
  • 더 자세한 내용은 위 링크 참고: https://docs.djangoproject.com/en/4.0/ref/contrib/auth/

위의 템플릿에서 로그아웃 링크를 추가했으므로, {% url 'common:logout' %}에 대응하는 URL 매핑을 common/urls.py 파일에 추가해야 함 !

urlpatterns에 path('logout/', auth_views.LogoutView.as_view(), name='logout'), 추가

이제 다시 admin으로 로그인을 해보면

네비게이션바의 로그인 링크가 위 사진과 같이 username과 함께 "로그아웃"으로 변경되는 것을 볼 수 있다 !!!

아, 그리고 로그아웃을 했을 때 리다이렉트할 위치도 config/settings.py 파일에 추가해야함

(... 생략 ...)

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

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

이제 로그아웃을 했을때에도 로그인과 동일하게 기본 페이지인 /으로 이동하도록 URL을 지정한 것 !~~~

profile
공부합시당

0개의 댓글