본문 바로가기

Django/Project

[Project] Ong's BookMark Service #5


이번에는 템플릿에 디자인을 입혀보려고 합니다.

디자이너가 어떤 디자인을 입혀달라고 요청했을 때, 그 요청을 진행할 수 있도록 디자인 파트도 공부해야 할 필요가 있을 거에요.

'부트스트랩'을 적용시켜서 해봅니다!!

그럼 나만의 디자인을 입혀보면서 개인 공부를 하며 진행합니다.

 

Code: https://github.com/ghk0409/Django_bookmark/


<디자인 입히기>

1. 템플릿 확장하기

- 여러 웹 서비스를 둘러보면 어느 페이지에서든 동일하게 보이는 메뉴바나 타이틀 같은 부분이 많습니다. 만약 이런 부분의 수정사항이 발생하면 각 템플릿들(페이지)마다 다 수정해야 할까요?? 이런 비효율을 방지하기 위해서 '템플릿 확장' 방법을 사용합니다.

 + 기준이 되는 레이아웃 부분을 담은 템플릿을 별도로 만들어둔 다음, 다른 템플릿들이 기준 템플릿을 상속받아 사용하는 것처럼 만들어주는 것이죠!!

 

- 템플릿 확장을 사용하기 위해 Project의 루트 경로에 [templates] 폴더를 추가해준 뒤, 기준 템플릿 'base.html' 파일을 만들어 봅니다.

 + 최상위 경로 bookmark에 templates 폴더를 만들어줍니다. (bookmark/templates)

# templates/base.html
<!--템플릿 확장은 block을 기준으로 동작-->
<!--다른 템플릿에서 껴넣을 공간을 block 태그를 사용해 만들어줌-->
<!--하위 템플릿에서 사용할 block에 껴넣을 내용을 결정하여 내용을 채움-->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <!--브라우저 탭에 보이는 이름 결정-->
    <title>{% block title %}{% endblock %}</title>
</head>
<body>
    <!--출력하고자 하는 내용 결정-->
    {% block content %}

    {% endblock %}
</body>
</html>
<!--여기서는 title과 content 2개의 block 생성-->

 

- 위에서 만든 기준 템플릿 파일(base.html)을 사용할 수 있도록 settings.py를 수정해봅니다.

 + settings.py의 TEMPLATES 부분을 수정 할꺼에요

# config/settings.py
# DIRS: [] 에 os.path.join(BASE_DIR, 'templates') 값을 추가
import os

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

 + 위처럼 수정하면 템플릿 검색을 할 때, 내가 지정한 'templates' 폴더를 살펴보게 될껍니다.

 

- 각 템플릿들을 base.html에 있는 [title] 블록과 [content] 블록을 적용해서 수정해봅니다.

# bookmark/templates/bookmark/bookmark_confirm_delete.html
<!--삭제할 때는 확인이 필요해서 confirm_delete라는 이름을 사용-->
<!--extends로 base.html 상속받음-->
{% extends 'base.html' %}
<!--title 블록 내용 채우기-->
{% block title %}
    Confirm Delete
{% endblock %}
<!--content 블록 내용 채우기-->
{% block content %}
    <!--form 태그와 csrf_token은 혼연일체라고 생각!!-->
    <form action="" method="post">
        {% csrf_token %}
        <div class="alert alert-danger">Do you want to delete Bookmark "{{object}}"?</div>
        <input type="submit" value="Delete" class="btn btn-danger">
    </form>
{% endblock %}


# bookmark/templates/bookmark/bookmark_create.html
{% extends 'base.html' %}

{% block title %}
    Bookmark Add
{% endblock %}

{% block content %}
    <form action="" method="post"> <!--action 메서드 비워두면 현재 페이지로 전달-->
        {% csrf_token %} <!--CSRF 공격을 막기 위한 용도-->
        {{form.as_p}} <!--클래스형 뷰의 옵션 값으로 설정한 필드를 출력할 때 P태그로 감싸줌-->
        <input type="submit" value="Add" class="btn btn-info btn-sm">
    </form>
{% endblock %}


# bookmark/templates/bookmark/bookmark_detail.html
{% extends 'base.html' %}

{% block title %}
    Bookmark Detail
{% endblock %}

{% block content %}
    <!--북마크 하나의 정보만 출력-->
    <!--DetailView가 object라는 이름으로 북마크 값을 전달함-->
    <!--object변수를 이용해 값을 하나씩 출력-->
    {{object.site_name}}<br/>
    {{object.url}}
{% endblock %}


# bookmark/templates/bookmark/bookmark_update.html
{% extends 'base.html' %}

{% block title %}
    Bookmark Update
{% endblock %}

{% block content %}
    <form action="" method="post">
        {% csrf_token %}
        {{form.as_p}}
        <input type="submit" value="Update" class="btn btn-info btn-sm">
    </form>
{% endblock %}}


# bookmark/templates/bookmark/bookmark_list.html
{% extends 'base.html' %}

{% block title %}
    Bookmark List
{% endblock %}

{% block content %}
    <!--북마크 추가하기 링크-->
    <div class="btn-group">
        <a href="{% url 'add' %}" class="btn btn-info">Add Bookmark</a>
    </div>
    <p></p>
    <!--북마크 목록 출력-->
    <table class="table">
        <thead>
            <tr>
                <th scope="col">#</th>
                <th scope="col">Site</th>
                <th scope="col">URL</th>
                <th scope="col">Modify</th>
                <th scope="col">Delete</th>
            </tr>
        </thead>
        <tbody>
        <!--제네릭 뷰에서는 모델의 오브젝트가 여러 개일 경우 object_list 변수로 전달함!!-->
        <!--object_list에서 bookmark 이름으로 북마크를 하나씩 꺼내 한 줄씩 출력-->
            {% for bookmark in object_list %}
            <tr>
                <td>{{forloop.counter}}</td>
                <!--pk값에 bookmark의 id값을 담아서 detail 페이지로 전달-->
                <td><a href="{% url 'detail' pk=bookmark.id %}">{{bookmark.site_name}}</a></td>
                <td><a href="{{bookmark.url}}" target="_blank">{{bookmark.url}}</a></td>
                <td><a href="{% url 'update' pk=bookmark.id %}" class="btn btn-success btn-sm">Modify</a></td>
                <td><a href="{% url 'delete' pk=bookmark.id %}" class="btn btn-danger btn-sm">Delete</a></td>
            </tr>
            {% endfor %}
        </tbody>
    </table>
{% endblock %}

 + [title] 블록과 [content] 블록 안에 넣을 코드만 제외하고 나머지는 전부 깔끔하게 지워줍니다.

 + 'base.html'로부터 나머지 html 코드를 전부 상속받기 때문에 문제없이 동일하게 구동될 수 있습니다!!


2. 부트스트랩 적용하기

- 템플릿을 분리/확장까지 끝낸 후, 디자인을 입혀봅니다.

 + CSS 프레임워크 중 하나인 부트스트랩을 적용해보겠습니다.

 + 부트스트랩에 접속해서 부트스트랩을 사용할 수 있도록 css 파일과 js 파일들을 불러옵니다.

 + 위 링크로 접속하면 Quick Start에서 CSS와 JS의 'Bundle', 'Seperate' 부분에 있는 코드를 복사해서 base.html의 <head> 태그에 넣어줍니다.

# templates/base.html
<!--head 태그 안에 부트스트랩 적용 코드 복붙!!-->
<head>
    <meta charset="UTF-8">
    <!--브라우저 탭에 보이는 이름 결정-->
    <title>{% block title %}{% endblock %}</title>

    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css"
          rel="stylesheet"
          integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x"
          crossorigin="anonymous">

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.bundle.min.js"
            integrity="sha384-gtEjrD/SeCtmISkJkNUaaKMoLD0//ElJ19smozuHV6z3Iehds+3Ulb9Bn9Plx0x4"
            crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js"
            integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p"
            crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.min.js"
            integrity="sha384-Atwg2Pkwv9vp0ygtn1JAojH0nYbwNJLPhwyoVbhoPwBhjQPR5VtM2+xf0Uwh9KtT"
            crossorigin="anonymous"></script>
</head>
<!--기존 html 코드에 각 태그 속 class에 css를 미리 지정해놔서 이쁘게 바로 변신함-->

 

- 위 부트스트랩을 적용한 후, 목록 페이지를 확인해봅니다.

이렇게 이전보다 더 나은 디자인을 볼 수 있습니다!!

 

- 부트스트랩을 적용해봤으니 좀 더 수정해서 메뉴바도 넣어봅니다.

# templates/base.html
<!--body 태그를 아래처럼 수정-->

<body>
    <div class="container">
        <nav class="navbar navbar-expand-lg navbar-light bg-light">
            <a class="navbar-brand" href="#">Django Bookmark</a>
            <button class="navbar-toggler" type="button" data-toggle="collapse"
                    data-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 mr-auto">
                    <li class="nav-item active">
                        <a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
                    </li>
                </ul>
            </div>
        </nav>
        <p></p>
        <div class="row">
            <div class="col">
                <!--출력하고자 하는 내용 결정-->
                {% block content %}

                {% endblock %}

                <!--새로 추가된 block!!-->
                {% block pagination %}
                {% endblock %}
            </div>
        </div>
    </div>
</body>

 

- 그리고나서 목록 페이지를 다시 새로고침 후 확인하면 짜잔~

비슷해 보이지만 나름 조금 바뀐 부분들이 있습니다!!ㅎㅎ


3. 페이징 기능 만들기

- 위에서 새로 추가된 [pagination] 블록을 이용해서 페이징 기능을 만들어 넣어 봅니다.

 + 클래스형 뷰에서는 간단하게 페이징 기능을 구현할 수 있습니다.

 + paginate_by = N을 추가해서 목록 페이지에 북마크 목록을 N개까지만 출력하게 만들어줍니다.

# bookmark/views.py

class BookmarkListView(ListView):
    model = Bookmark
    paginate_by = 5 # 이 코드를 추가하기

 

- 수정 후, 목록 페이지를 확인 해봅니다.

좌) 수정 전 // 우) 수정 후, 5개만 출력하게끔 하긴 했는데 나머지 3개는 어딨음...??

 + paginate_by 값을 5로 지정해서 5개만 출력되는 것으로 보아 정상적으로 동작 성공!!

 

- 목록 아래쪽에 페이지 목록을 출력해서 제대로 된 페이징 기능을 사용할 수 있게끔 만들어 봅니다.

 + bookmark_list.html 파일을 열어 가장 하단부에 아래 코드처럼 수정해봅니다.

# bookmark/templates/bookmark/bookmark_list.html

<!--[pagination] 블록을 적용하기-->
{% block pagination %}
    {% if is_paginated %}
        <ul class="pagination justify-content-center pagination-sm">
            <!--출력 페이지보다 이전 목록을 가지고 있을 경우-->
            {% if page_obj.has_previous %}
                <li class="page-item">
                    <a class="page-link" href="{% url 'list' %}?page={{ page_obj.previous_page_number }}" tabindex="-1">Previous</a>
                </li>
            <!--이전 목록이 없을 경우, Previous 버튼 비활성화-->
            {% else %}
                <li class="page-item disabled">
                    <a class="page-link" href="#" tabindex="-1">Previous</a>
                </li>
            {% endif %}

            <!--페이지 수만큼 페이지 버튼 넣기-->
            {% for object in page_obj.paginator.page_range %}
            	<!--해당 페이지로 이동하면 해당 페이지 버튼은 비활성화-->
                <li class="page-item {% if page_obj.number == forloop.counter %} disabled{% endif %}">
                    <a class="page-link" href="{{ request.path }}?page={{ forloop.counter }}">{{ forloop.counter }}</a>
                </li>
            {% endfor %}

            <!--출력 페이지보다 다음 목록을 가지고 있을 경우-->
            {% if page_obj.has_next %}
                <li class="page-item">
                    <a class="page-link" href="{% url 'list' %}?page={{ page_obj.next_page_number }}">Next</a>
                </li>
            <!--다음 목록이 없으면 Next 버튼 비활성화-->
            {% else %}
                <li class="page-item disabled">
                    <a class="page-link" href="#" tabindex="-1">Next</a>
                </li>
            {% endif %}
        </ul>
    {% endif %}
{% endblock %}

 + paginate_by 값을 사용하면 자동으로 Page 객체를 생성합니다. 이 객체를 이용해 이전 페이지, 다음 페이지, 현재 페이지를 알 수 있고 페이지의 범위도 알 수 있습니다.

 + 이를 이용해 템플릿 문법을 통해 각각의 값들을 출력해서 조건에 따라 페이징 버튼들을 설정해줍니다.

 + 참고 문서 : https://docs.djangoproject.com/en/3.1/topics/pagination/#django.core.paginator.Paginator

 

Pagination | Django documentation | Django

Django The web framework for perfectionists with deadlines. Overview Download Documentation News Community Code Issues About ♥ Donate

docs.djangoproject.com

 + 이제 다시 목록 페이지를 확인 해보겠습니다!!

이제 페이지 번호랑 Previous, Next 버튼도 잘 작동하네요. 휴~


4. 정적(Static) 파일 사용하기

- 부트스트랩을 이용해 온라인 상으로 css파일과 js파일을 불러와서 사용해 봤습니다. 그럼 로컬 서버에 있는 정적 파일을 사용해보도록 하겠습니다.

 + 로컬에 있는 이미지 파일이나 css, js파일들을 이용해서 디자인을 입힐 수도 있습니다!!

 + 정적 파일도 템플릿 파일처럼 정해진 위치가 있습니다. 보통은 각 앱 폴더 밑으로 'static' 폴더를 만들어 사용하고 'templates' 폴더처럼 별도의 폴더를 사용하려 하기 때문에 settings.py에서 설정을 해주도록 합니다.

 + 그렇긴 하지만 여기서는 Project 루트에 static 폴더를 만들어서 사용해볼께요!! (경로 설정만 다를 뿐이죠^^)

# 최상위 bookmark 폴더에 static 폴더를 먼저 추가해줍니다!!
# config/settings.py
# settings.py 가장 하단부에 아래 변수를 추가해줍니다.
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]

 

- static 폴더에 'style.css' 파일을 생성해줍니다.

 + 우클릭 -> New -> File 선택해서 style.css 그대로 파일명 입력 후 만들기

 + css파일 만들고 아래 코드를 입력!!

# static/style.css
body {
    width:100%;
}

 

- base.html에서 style.css를 사용할 수 있도록 base.html 코드를 수정해줍니다.

+ base.html 파일에서 <head> 태그 안쪽에 아래 코드를 추가합니다.

# templates/base.html
<!--static 폴더의 style.css 파일 불러오기-->
{% load static %}
    <link rel="stylesheet" href="{% static 'style.css' %}">

 

- 북마크 서비스 페이지를 새로고침하고 css파일이 잘 불러와졌는지 개발자 도구를 이용해 확인해봅니다.

Sources 탭에서 static 폴더의 style.css 파일을 확인해서 내가 입력한 코드와 동일하게 뜨면 성공!!


여기까지 북마크 웹 서비스를 완성 해봤습니다!! 기본적인 기능 외에 디자인적인 측면은 css 파일을 추가로 수정해서 더욱 예쁘게 입힐 수 있습니다. 이 부분은 내가 하고 싶은대로!! 꾸며보면 더 좋을거에요. css 공부도 간단하게 해볼겸!!

 + 현재 본인이 css를 잘 몰라서 추가를 더 안했다는건 안 비밀....;;;;;;

 + html 코드에 넣은 태그들이 몇 개 없어서 꾸미는건 나중으로 미뤄미뤄...ㅎ

 

물론 매우 간단하게 보이는 웹 페이지 수준이겠지만 Django를 사용했기 때문에 더 적은 양의 코드??와 시간으로 완성할 수 있었다고 봅니다ㅎㅎ

 

나만의 웹페이지를 잘 만들어보고 서버에 업로드 시켜서 다른 사람들도 사용할 수 있도록 '배포'를 해보면 좋을 것 같아요~

 

해당 프로젝트는 "베프의 오지랖 파이썬 웹 프로그래밍(디지털북스)"를 참고합니다.

'Django > Project' 카테고리의 다른 글

[Project] Ongstagram Service #2  (0) 2021.06.02
[Project] Ongstagram Service #1  (0) 2021.06.01
[Project] Ong's BookMark Service #4  (0) 2021.05.29
[Project] Ong's BookMark Service #3  (0) 2021.05.29
[Project] Ong's BookMark Service #2  (0) 2021.05.27