25/10/13 장고

344th·2025년 12월 11일

AWS AI

목록 보기
21/48

DOM

html 은 기본적으로 절차 지향 언어

그래서 브라우저가 이해할 수 있도록 html 이나 xml 문서를 객체 구조로 변환한 것이 dom

즉, DOM 은 웹 페이지를 스크립트 언어와 연결하는 중요한 인터페이스 역할을 한다

트리 구조의 객체 모델

브라우저가 DOM 을 생성하면 자바스크립트는 이 DOM 객체에 접근하여 요소를 찾고, 내용을 바꾸거나, 새 요소를 추가/삭제하는 등의 작업을 수행할 수 있다

점프투장고 교재 p.125

부트스트랩에 필요한 파일 추가

static\bootstrap.min.js

static\jquery-3.7.1.min.js

jquery.com/download

temlates/base.html 에 파일 추가

templates\base.html

<script src="{% static 'jquery-3.7.1.min.js' %}"></script>
<script src="{% static 'bootstrap.min.js' %}"></script>

jquery 가 무조건 먼저!

navbar.html 생성 후 코드 작성

templates\navbar.html

<!-- 내비게이션바 -->
<nav class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
    <a class="navbar-brand" href="{% url 'pybo:index' %}">Pybo</a>
    <button class="navbar-toggler ml-auto" type="button" data-toggle="collapse"
            data-target="#navbarNav" aria-controls="navbarNav"
            aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse flex-grow-0" id="navbarNav">
        <ul class="navbar-nav">
            <li class="nav-item">
                <a class="nav-link" href="#">로그인</a>
            </li>
        </ul>
    </div>
</nav>
  • data-toggle="collapse" : 버튼 클릭시 collapse 를 트리거
  • data-target="#navbarNav" : id 가 navbarNav 인 애 열고 닫힘 기능
  • aria-* : 스크린 속성 지정

base.html에 include 적용

templates\base.html

<body>
    <!-- 내비게이션바 -->
    {% include 'navbar.html' %}
    <!-- 기본 템플릿 안에 삽입될 내용 -->
    {% block content %}
    {% endblock %}
</body>
  • html 파일을 통쨰로 넣을 경우 include
  • 영역을 넣을 경우 block/endblock

게시판 페이징 기능 추가

임시 질문 데이터 300개 생성

python manage.py shell
>>> 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()

넣을 때 indentation 주의!

페이징 기능 살짝 구현

pybo\views.py

def index(request):
    """
    pybo 목록 출력
    """
    # 입력 인자
    page = request.GET.get('page', '1') #페이지
    
    # 조회
    question_list = Question.objects.order_by("-create_date")
    
    # 페이징 처리
    paginator = Paginator(question_list, 10)
    page_obj = paginator.get_page(page)
    
    context = {"question_list" : page_obj}
    return render(request, 'pybo/question_list.html', context)
  • Paginator : 페이징 기능을 담당하는 클래스 : 쿼리셋(혹은 리스트)을 받아서 페이지 단위로 나누는 역할
  • request.GET : URL 의 쿼리스트링(? 뒤에 있는 값)을 가져오는 부분
  • 페이징 객체 page_obj 속성
    항목설명
    paginator.count전체 게시물 개수
    paginator.per_page페이지당 보여줄 게시물 개수
    paginator.page_range페이지 범위
    number현재 페이지 번호
    previous_page_number이전 페이지 번호
    next_page_number다음 페이지 번호
    has_previous이전 페이지 유무
    has_next다음 페이지 유무
    start_index현재 페이지 시작 인덱스(1부터 시작)
    end_index현재 페이지의 끝 인덱스(1부터 시작)
  • get_page()
    • 유효하지 않은 번호를 넣어도 자동으로 처리(999를 넣으면 마지막 페이지 처리)

페이징 적용

templates\pybo\question_list.html

<!-- 페이징 처리 시작 -->
 <ul class="pagination justify-content-center">
    <!-- 이전 페이지 -->
    {% if question_list.has_previous %}
    <li class="page-item">
        <a class="page-link" href="?page={{ question_list.previous_page_number }}">이전</a>
    </li>
    {% else %}
    <li class="page-item disabled">
        <a class="page-link" tabindex="-1" aria-disabled="true" href="#">이전</a>
    </li>
    {% endif %}
    <!-- 페이지 리스트 -->
    {% for page_number in question_list.paginator.page_range %}
    {% if page_number >= question_list.number|add:-5 and page_number <= question_list.number|add:5 %}
        {% if page_number == question_list.number %}
        <li class="page-item active" aria-current="page">
            <a class="page-link" href="?page={{ page_number }}">{{ page_number }}</a>
        </li>
        {% else %}
        <li class="page_item">
            <a class="page-link" href="?page={{ page_number }}">{{ page_number }}</a>
        </li>
        {% endif %}
    {% endif %}
    {% endfor %}
    <!-- 다음 페이지 -->
    {% if question_list.has_next %}
    <li class="page-item">
        <a class="page-link" href="?page{{ question_list.next_page_number }}">다음</a>
    </li>
    {% else %}
    <li class="page-item disabled">
        <a class="page-link" tabindex="-1" aria-disabled="true" href="#">다음</a>
    </li>
    {% endif %}
 </ul>
 <!-- 페이징 처리 끝 -->

로그인/로그아웃

common 앱 생성 후 초기 설정 작업

django-admin startapp common

config\settings.py

INSTALLED_APPS = [
    (... 생략 ...)
    'django.contrib.auth',
    (... 생략 ...)
]
  • 장고의 로그인, 로그아웃을 도와주는 앱은 django.contrib.auth 이다. 이 앱은 장고 프로젝트 생성시 자동으로 추가됨
  • django.contrib.auth 앱을 이용하면 로그인과 로그아웃 기능을 정말 쉽게 구현할 수 있음

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

(... 생략 ...)

config\urls.py

app_name = 'common'

urlpatterns = [
]

common\urls.py 생성

app_name = 'common'

urlpatterns = [
]

로그인

templates\navbar.html

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

로그인 뷰

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 수정

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

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 %}

templates\common\form_errors.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 %}

로그인 성공 시 이동할 페이지 등록

config\settings.py

LOGIN_REDIRECT_URL = '/'

로그아웃

내비게이션바 수정

templates\navbar.html

<li class="nav-item">
    {% if user.is_authenticated %}
    <form  method="POST" action="{% url 'common:logout' %}">
        {% csrf_token %}
        <input style="background: none; border: none;" class="nav-link" type="submit" value="{{ user.username }} (로그아웃)"/>
    </form>
    {% else %}
    <a class="nav-link" href="{% url 'common:login' %}">로그인</a>
    {% endif %}
</li>

장고 5.0 버전 이후부터는 로그아웃뷰가 post 메소드 외에는 동작하지 않음

그러므로 POST 메소드를 사용할 수 있도록 form 태그 이용

URL 매핑

common\urls.py

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

LOGIN_REDIRECT_URL = '/'
LOGOUT_REDIRECT_URL = '/'
profile
새싹 개발자

0개의 댓글