GDSC HUFS 3기/Backend - Django

[Django] 2주차_3_파이보 서비스 개발2

gusals42 2022. 5. 15. 22:35

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

작성자 : 정현민

개발환경은 Python, PyCharm입니다. 

 

 

1. 답변 개수 표시

질문 목록에 "해당 질문에 달린 답변 개수"를 표시할 수 있는 기능을 추가한다. 게시물 제목 바로 오른쪽에 표시하도록 한다. templates/pybo/question_list.html

<td>
    <a href="{% url 'pybo:detail' question.id %}">{{ question.subject }}</a>
    {% if question.answer_set.count > 0 %}
    <span class="text-danger small mx-2">{{ question.answer_set.count }}</span>
    {% endif %}
</td>

답변개수 표시 템플릿이 적용된 이미지

 

2. 로그인과 로그아웃

파이보는 질문답변게시판이므로 여러 회원이 사용하기 위해서는 로그인/로그아웃이 필요하다.

장고의 로그인/로그아웃을 도와주는 앱은 django.contrib.auth이며 장고 프로젝트 생성 시 자동으로 추가된다. 

cmd 창에서 로그인 · 로그아웃을 "공통 기능을 가진 앱"이라는 의미의 common 앱을 신규로 생성한다.

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

config/settings.py에 생성한 common앱을 등록한다.

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

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')), # common앱 경로
]

2-1. 로그인

로그인 화면으로 진입할 수 있도록 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 매핑 규칙을 추가한다.

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'),
]

브라우저에서 내비게이션바의 '로그인' 링크를 눌러 보면 아래의 오류페이지가 나타난다.

login.html에 파일이 없다는 것이므로 common/urls.py를 auth_views.LoginView.as_view(template_name='common/login.html')로 수정해준다.

그렇지만 아직도 오류페이지가 나타난다.

위의 이미지와 다르게 common/login.html이 없다는 오류이다.

common/login.html 파일 생성을 위해 common 디렉터리를 cmd 창에서 생성하고 login.html 파일을 만들어준다.

그리고 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 %}

include안의 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 %}

폼 오류에는 필드 오류(field.errors)넌필드 오류(form.non_field_errors)가 있는데 필드 오류는 사용자가 입력한 필드 값에 대한 오류로 값이 누락되었거나 필드의 형식이 일치하지 않는 경우에 발생하는 오류이다. 넌필드 오류는 필드의 값과는 상관없이 다른 이유로 발생하는 오류이다. form_errors.html 템플릿은 필드 오류와 넌필드 오류 모두를 표시하기 위해 삽입되는 템플릿이다. 

 

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

 

로그인 후 이동하는 페이지는 config/settings.py에  LOGIN_REDIRECT_URL = '/'를 추가하고

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
]

2-2. 로그아웃

로그인 후에는 네비게이션바의 "로그인" 링크가 "로그아웃" 링크로 바뀌도록 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>

로그아웃 링크를 추가했기 때문에 "{% url 'common:logout' %}"에 대응하는 매핑을 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'), # 추가된 매핑
]

이후 로그아웃 시 redirect할 위치도 config/urls.py에 LOGOUT_REDIRECT_URL ='/'를 추가해준다.

 

3. 회원가입

회원가입을 위한 링크를 templates/navbar.html에 추가한다.

                <li>
                    {% if not user.is_authenticated %}
                    <a class="nav-link" href="{% url 'common:signup' %}">회원가입</a>
                    {% endif %}
                </li>

{% url 'common:signup' %} 에 대응하는 url 매핑 규칙을 common/urls.py에 추가한다.

from django.urls import path
from django.contrib.auth import views as auth_views
from . import 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'),
    path('signup/', views.signup, name='signup'),
]

common/forms.py에 계정생성 시 사용할 userform을 작성한다.

from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User


class UserForm(UserCreationForm):
    email = forms.EmailField(label="이메일")

    class Meta:
        model = User
        fields = ("username", "password1", "password2", "email")

common/views.py에 signup함수를 정의해준다.

from django.contrib.auth import authenticate, login
from django.shortcuts import render, redirect
from common.forms import UserForm


def signup(request):
    if request.method == "POST":
        form = UserForm(request.POST)
        if form.is_valid():
            form.save()
            username = form.cleaned_data.get('username')
            raw_password = form.cleaned_data.get('password1')
            user = authenticate(username=username, password=raw_password)  # 사용자 인증
            login(request, user)  # 로그인
            return redirect('index')
    else:
        form = UserForm()
    return render(request, 'common/signup.html', {'form': form})

회원가입 화면을 구성해주는 common/signup.html 템플릿을 작성하면 끝이다.

{% extends "base.html" %}
{% block content %}
<div class="container my-3">
    <form method="post" action="{% url 'common:signup' %}">
        {% csrf_token %}
        {% include "form_errors.html" %}
        <div class="mb-3">
            <label for="username">사용자 이름</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="password1">비밀번호</label>
            <input type="password" class="form-control" name="password1" id="password1"
                   value="{{ form.password1.value|default_if_none:'' }}">
        </div>
        <div class="mb-3">
            <label for="password2">비밀번호 확인</label>
            <input type="password" class="form-control" name="password2" id="password2"
                   value="{{ form.password2.value|default_if_none:'' }}">
        </div>
        <div class="mb-3">
            <label for="email">이메일</label>
            <input type="text" class="form-control" name="email" id="email"
                   value="{{ form.email.value|default_if_none:'' }}">
        </div>
        <button type="submit" class="btn btn-primary">생성하기</button>
    </form>
</div>
{% endblock %}

이후 회원가입을 테스트해보면 회원가입을 할 수 있는 페이지가 나타난다.

이후 슈퍼유저 계정으로 admin 페이지에 접속하면 가입된 사용자 목록을 확인할 수 있다.