[Django] 2주차_3_파이보 서비스 개발2
이 글은 점프 투 장고를 참고하여 작성하였습니다.
작성자 : 정현민
개발환경은 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 페이지에 접속하면 가입된 사용자 목록을 확인할 수 있다.