[참고] 3장 파이보 서비스 개발! - 점프 투 장고 (wikidocs.net)
점프 투 장고 3장에서는 파이보 서비스를 본격적으로 개발한다.
게시판 CRUD 기능, 로그인/로그아웃, 게시판 페이징, 회원가입, 검색 기능을 추가한다.
그리고 부트스트랩을 적용하여 웹 페이지의 심미성을 높인다.
일단 이 글에서는 네비게이션 바, 페이징, 템플릿 필터, 답변 개수 표시, 로그인/로그아웃을 먼저 살펴보자.
기능을 추가하고 화면을 바꾸는 걸 반복하면서 위 사진에서 보이는 장고 아키텍쳐에 대한 감을 잡게 된다.
클라이언트의 요청을 받으면, urls.py에서 views.py로 넘겨주고, views.py에서 template을 불러와 화면을 보여준다.
내비게이션 바
파이보 메인 페이지에 햄버거 모양의 네비게이션 바를 만들고자 한다.
네비게이션 바는 장고 부트스트랩 컴포넌트로 제공되는 것이다.
모든 화면에서 보여주어야 하는 기능이므로 base.html 템플릿에 추가해줄 것이다.
</templates/base.html>
{% load static %}
<!doctype html>
<html lang="ko">
<head>
<!--메타 태그-->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!--부트스트랩 CSS-->
<link rel="stylesheet" type="text/css" href="{%static 'bootstrap.min.css'%}">
<!--파이보 CSS-->
<link rel="stylesheet" type="text/css" href="{%static 'style.css'%}">
<title>Hello, pybo!</title>
</head>
<body>
<!--내비게이션 바-->
<nav class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
<div class="container-fluid">
<a class="navbar-brand" href="{%url 'pybo:index'%}">Pybo</a>
<button class="navbar-toggler" type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent"
aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="#">로그인</a>
</li>
</ul>
</div>
</div>
</nav>
<!--기본 템플릿을 상속받아 삽입될 내용-->
{%block content%}
{%endblock%}
</body>
</html>
이렇게 <nav> 태그 시작부터 </nav> 태그 끝까지 네비게이션에 관한 내용이다.
네비게이션 바에 햄버거 버튼과 함께 로그인 링크를 추가해주었다.
이렇게 햄버거 버튼이 생겼으나 버튼을 누른다고 해서 반응하지는 않는다.
반응형 웹이기 때문에 웹 브라우저의 크기를 줄이면 어느 순간 네비게이션 메뉴 버튼이 생기고, 로그인 링크는 사라진다.
이렇게 반응형 웹으로 웹이 반응하게 하려면, 부트스트랩 자바스크립트 파일인 bootstrap.min.js를 포함시켜야 한다.
<!--기본 템플릿을 상속받아 삽입될 내용-->
{%block content%}
{%endblock%}
<!--부트스트랩-->
<script src="{%static 'bootstrap.min.js' %}"></script>
</body>
static 폴더에 bootstrap.min.js를 복사한 뒤에,
base.html의 맨 아래에 bootstrap.min.js를 추가해주었더니 변화가 생긴 걸 확인할 수 있다.
짠! 로그인 링크가 생겼다.
- include 태그
include 태그는 다른 템플릿 파일을 포함해서 재사용할 수 있게 해주는 기능이다.
특정 영역이 반복적으로 등장할 때 코드의 중복을 없애기 위해 사용한다.
만약에 재사용하고자 하는 부분을 abc.html로 분리했을 때,
다른 파일에서 넣으려면, {%include "abc.html" %}만 넣어주면 된다.
우리는 네비게이션 바에 필요한 내용을 navbar.html 파일에 분리해두고자 한다.
<navbar.html>
<!--네비게이션 바에만 필요한 내용 -->
<nav class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
<div class="container-fluid">
<a class="navbar-brand" href="{%url 'pybo:index'%}">Pybo</a>
<button class="navbar-toggler" type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent"
aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="#">로그인</a>
</li>
</ul>
</div>
</div>
</nav>
navbar.html 템플릿을 생성해주고,
이 템플릿을 base.html에 {%include "navbar.html"%}로 추가해준다.
<base.html> 중
<!--내비게이션 바-->
{% include "navber.html" %}
페이징
이제 pybo 게시판에 페이징 기능을 추가하고자 한다.
페이징 기능이라는 건, 모든 게시물을 한 화면에 보여주는 게 아니라 10개, 20개씩 끊어서 한 페이지에 보여주는 기능을 말한다.
페이지 기능을 테스트하기 위해 일단 대량 데이터를 생성해보자.
이를 위해 장고 셸이 필요하다.
python manage.py shell
Python 3.8.10 (default, Jun 22 2022, 20:18:18)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>
이렇게 셸을 실행하고,
>>> from pybo.models import Question
>>> from django.utils import timezone
>>> for i in range(300):
... q = Question(subject='테스트데이터입니다:[%03d]' %i, content='내용없음',
create_date=timezone.now())
... q.save()
테스트 데이터를 생성해준다.
페이징 기능을 해주는 건 django.core의 Paginator 클래스다.
views.py 함수에서 paginator 클래스를 이용해 페이징을 해주자.
from django.core.paginator import Paginator
# Create your views here.
def index(request):
page = request.GET.get('page','1') # 페이지
question_list = Question.objects.order_by('-create_date')
paginator = Paginator(question_list, 10) # 페이지당 10개씩 보여주기
page_obj = paginator.get_page(page)
context = {'question_list' : page_obj}
return render(request, 'pybo/question_list.html', context)
하나씩 보면,
page = request.GET.get('page', '1')은 GET 방식으로 호출된 URL에서 page 값을 가져올 때 사용한다.
page 값 없이 호출될 때는 디폴트 1이다.
paginator 클래스를 (quesiton_list, 10)으로 사용한다.
question_list는 게시물 전체 데이터, 두번째 파라미터 10은 페이지당 보여줄 글의 수다.
page_obj = paginator.get_page(page)
에서는 page에 해당하는 페이징 객체 page_obj를 생성해준 것이다.
짠! 페이지가 생겼다.
템플릿 필터
템플릿 필터란 | 문자 뒤에 사용하는 필터를 의미한다.
from django import template
register = template.Library()
@register.filter
def sub(value, arg):
return value - arg
이렇게 만든 템플릿 필터를 이용해서,
게시판 글의 번호를 정확하게 보여줄 수 있다.
답변 개수 표시
<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>
로그인과 로그아웃
장고의 django.contrib.auth를 이용해서 로그인, 로그아웃을 구현할 수 있다.
로그인, 로그아웃 기능은 pybo가 아니라 common 앱에서 구현해준다.
회원가입과 로그인/로그아웃 기능은 다른 프로젝트에서도 필요하기 때문이다.
common 앱을 시작해준다.
$ django-admin startapp common
common 앱이 생긴걸 볼 수 있다.
이제 pybo를 만들 때 했던 것처럼 settings.py에 common 앱을 등록하자.
<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의 url을 사용하기 위해 config/urls.py에 common의 urls.py 파일을 등록해준다.
urlpatterns = [
path('admin/', admin.site.urls),
path('pybo/', include('pybo.urls')),
path('common/', include('common.urls')),
]
<common/urls.py>
app_name = 'common'
urlpatterns = [
]
<navbar.html>에서
<a class="nav-link" href="{% url 'common:login' %}">로그인</a>
이렇게 로그인 링크를 common:login으로 바꾸어준다.
로그인 뷰
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')
]
URL 매핑 규칙 common/urls.py는 login 링크를 받으면, django.contrib.auth 앱이 가지고 있는 auth_views.LoginView를 보여준다.
로그인 템플릿
{%extends "base.html"%}
{%block content%}
<div class="container my-3">
<form method="post" action="{% url 'common:lobin'%}">
{%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%}
이 페이지에서 받는 username과 password 항목은 필수 항목이다.
<!-- 필드 오류, 넌필드 오류를 출력 -->
{% 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 태그 파일을 생성해준다.
form_errors에서 필드 에러랑 논필드 에러를 표시해준다고 했다. 필드 에러는 필드 형식이 맞지 않거나 누락되었거나 해서 발생하는 에러다. 넌필드 에러는 그것과 상관없이 발생하는 에러다.
이렇게 화면이 생성되었다.
#로그인 후에 이동하는 URL
LOGIN_REDIRECT_URL='/'
settings.py에서 설정해준다.
이제 이것에 맞게 URL 매핑 규칙을 추가해주자.
<urls.py>
urlpatterns = [
path('admin/', admin.site.urls),
path('pybo/', include('pybo.urls')),
path('common/', include('common.urls')),
path('', views.index, name='index'),
]
로그아웃
로그인된 상태에서는 로그인 버튼을 로그아웃 버튼으로 바꾸어주어야 한다.
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<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>
</ul>
</div>
</div>
</nav>
navbar.html 템플릿에서 if문을 추가해준다.
user.is_authenticated으로 유저가 로그인된 상태를 확인한다면,
로그아웃 링크를 표시하고 이를 common:logout과 연결하는 것이다.
이제 이것에 맞게 common:logout을 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(template_name='common/login.html'), name='login')
path('logout/', auth_views.LogoutView.as_view(), name='logout'),
]
매핑을 추가해주었다.
'Computer Science > BackEnd' 카테고리의 다른 글
점프 투 장고 #3-3 | 파이보 서비스 개발하기(views.py 분리, 추천, 앵커, 마크다운, 검색, 파이보 추가 기능) (0) | 2022.08.23 |
---|---|
점프 투 장고 #3-2 | 파이보 서비스 개발하기(회원가입, 모델 변경, 글쓴이 표시, 수정과 삭제) (2) | 2022.08.22 |
점프 투 장고 #2-3 | 장고 기본 요소(데이터 저장, 스태틱, 부트스트랩, 템플릿 상속, 폼) 익히기 (0) | 2022.08.16 |
점프 투 장고 #2-2 | 장고 관리자, 조회와 템플릿, URL 별칭 익히기 (0) | 2022.08.15 |
점프 투 장고 #2 | 장고 URL, 뷰, ORM, 모델 익히기 (0) | 2022.08.13 |