EMDI는 지금도 개발중

Python with Django : 장고 웹 프로그래밍 템플릿 상속 방법(3) 본문

언어/Python

Python with Django : 장고 웹 프로그래밍 템플릿 상속 방법(3)

EMDI 2020. 6. 12. 14:25

저번 글까지는 파이썬의 구조에 대해 조금 더 배워가는 시간이었습니다. 이번 글에서는 직접적으로 클라이언트 홈페이지를 다룰 예정입니다. 

1. index.html 꾸미기

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">

클라이언트 사이트는 이미 꾸며져 있는 관리자사이트(/admin)와 다르게 저희가 직접 소스를 작성하면서 꾸며야합니다. 그렇기에 저는 부트스트랩 사이트를 이용하도록 하겠습니다. 저와 동일하게 부트스트랩 사이트를 이용하실 분들은 해당 사이트에 들어가셔서 원하는 소스를 추가해주세요.

 

<DOCTYPE html>
    <head>
        <meta charset="utf-8">
        <title>Project Management Page</title>
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">
    </head>
    <body>
         <!-- navbar 연결부분 -->
         <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
            <a class="navbar-brand">FINPM</a>
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarTogglerDemo02" aria-controls="navbarTogglerDemo02" aria-expanded="false" aria-label="Toggle navigation">
              <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarTogglerDemo02">
              <ul class="navbar-nav mr-auto mt-2 mt-lg-0">
                <li class="nav-item active">
                  <a class="nav-link" href="#">초기화면<span class="sr-only">(current)</span></a>
                </li>
                <li class="nav-item">
                  <a class="nav-link" href="#">프로그램정보</a>
                </li>
              </ul>
              <form class="form-inline my-2 my-lg-0">
                <input class="form-control mr-sm-2" type="search" placeholder="Search">
                <button class="btn btn-outline-light my-2 my-sm-0" type="submit">Search</button>
              </form>
            </div>
        </nav>
        <style>
            body {font-family: dotum;font-size: 10pt;line-height: 25px;color:#474747;}
            .tableline {border:1px solid #908d8d;display: table;width:100%;}
            .tablerow {display:table-row;}
            .tablecell_h {display: table-cell;padding:5px 20px;height:37px;background-color: #eaeaea;border-bottom:1px solid #908d8d;border-right: 1px solid #908d8d;text-align:center;font-weight: bold;}
            .tablecell_he {display: table-cell;padding:5px 20px;height:37px;background-color: #eaeaea;border-bottom:1px solid #908d8d;text-align:center;font-weight: bold;}
            .tablecell {display: table-cell;padding:5px 20px;height:37px;border-bottom:1px solid #908d8d;border-right: 1px solid #908d8d;text-align:center;}
            .tablecell_e {display: table-cell;padding:5px 20px;height:37px;border-bottom:1px solid #908d8d;text-align:center;}
            .tablecell_last {display: table-cell;padding:5px 20px;height:37px;border-right: 1px solid #908d8d;text-align:center;}
            .tablecell_laste {display: table-cell;padding:5px 20px;height:37px;text-align:center;}
            .tablecell a:visited {color:#474747;}
            .tablecell_e a:visited {color:#474747;}
            .tablecell_last a:visited {color:#474747;}
            .tablecell_laste a:visited {color:#474747;}
            .tablecell a:link {text-decoration: none;}
            .tablecell_e a:link {text-decoration: none;}
            .tablecell_last a:link {text-decoration: none;}
            .tablecell_laste a:link {text-decoration: none;}
            .tablebank {height:37px;}
            .tablemodule1 {height:37px;}
        </style>
        <div class="container" style="width:100%;">
            <div style="width:1024px;height:400px;margin:0 auto;">
                <div style="margin:0 21 0 22px">
                    <h1 style="margin-top:30px;">Project Management Page</h1>
                    <div class="toptextarea">
                        <span style="color:crimson;">* 해당 홈페이지는 파이썬 공부를 위한 테스트용 홈페이지입니다. </span>
                    </div>
                    <div style="margin-top: 20px;">
                        <!-- Table Layout -->
                        <div class="tableline">
                            <div class="tablerow">
                                <div class="tablecell_h" style="width:130px;">팀명</div>
                                <div class="tablecell_h" style="width:180px;">팀닉네임</div>
                                <div class="tablecell_h" style="width:180px;">팀멤버수</div>
                                <div class="tablecell_h" style="width:130px;">비고</div>
                            </div>
                            {% for team in teams %}
                            <div class="tablerow">
                                <div class="tablecell" style="width:130px;">{{ team.team_nm }}</div>
                                <div class="tablecell" style="width:180px;">{{ team.team_nick }}</div>
                                <div class="tablecell" style="width:180px;">{{ team.team_mems_cnt }}</div>
                                <div class="tablecell" style="width:130px;">{{ team.bigo }}</div>
                            </div>
                            {% endfor %}
                        </div>
                    </div>
                    <!-- Table Layout -->
                    <div style="margin-top:10px;text-align:right;">
                        Copyright(c) 2020 By EMDI All Right Reserved.
                    </div>
                    <iframe id="downframe" style="width:1px;height:1px;visibility: hidden;"></iframe>
                </div>
            </div>
        </div>
        <footer class="fixed-bottom bg-info">
            <div>
                Copyright(c) 2020 By EMDI All Right Reserved.
            </div>
        </footer>
    </body>
</html>

내가 마음에 든 navbar도 넣어주고, style도 바꿔주고 이것 저것 넣어본 index.html의 전체 소스입니다.

 

2. 템플릿 상속받기(base.html만들기)

만약 내가 생성할 화면이 단 하나뿐이라면 전체 소스처럼 모든 내용이 들어가도 무관합니다. 하지만 저희가 만들 프로젝트는 단 하나의 화면만 있진 않겠죠. 여기서 저희가 해야할 작업은 바로 소스 세분화 작업입니다. 소스 세분화작업을 하는 이유는 소스가 중복적으로 발생되는 것을 방지하고자 함인데 예를 들어 설명하도록 하겠습니다. 우리는 화면마다 menu, content, footer 등 여러가지 내용이 복합적으로 들어가 있을 겁니다. 근데 여기서 menu, footer와 같이 똑같은 소스를 화면(html)마다 만들게 되면 중복된 소스가 계속 발생하게 되겠죠? 이러한 중복적인 현상을 방지하기 위해 우리는 세분화작업, 바로 템플릿 상속을 다룰 것 입니다.

* 템플릿 상속 : html 중 기본 뼈대가 될 문서를 기본 템플릿으로 만들어 다른 html이 해당 소스가 필요할 때 상속하여 가져다 쓰는 것을 말합니다. 보통 기본이 되는 템플릿은 base.html로 정합니다.

 

1) base.html만들기

templates 폴더 안에 기본 뼈대가 될 base.html 문서를 생성합니다. 그리고 index.html(메인화면)에 있었던 소스들을 우선 전체 복사하여 붙여넣기 합니다.

<!DOCTYPE html>
    <head>
        <meta charset="utf-8">
        <title>Project Management Page</title>
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">
    </head>
    <body>
         <!-- navbar 연결부분 -->
         <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
            <a class="navbar-brand">FINPM</a>
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarTogglerDemo02" aria-controls="navbarTogglerDemo02" aria-expanded="false" aria-label="Toggle navigation">
              <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarTogglerDemo02">
              <ul class="navbar-nav mr-auto mt-2 mt-lg-0">
                <li class="nav-item active">
                  <a class="nav-link" href="#">초기화면<span class="sr-only"></span></a>
                </li>
                <li class="nav-item">
                  <a class="nav-link" href="#">프로그램정보</a>
                </li>
              </ul>
              <form class="form-inline my-2 my-lg-0">
                <input class="form-control mr-sm-2" type="search" placeholder="Search">
                <button class="btn btn-outline-light my-2 my-sm-0" type="submit">Search</button>
              </form>
            </div>
        </nav>
        {% block content %}
        {% endblock %}
    </body>
    <footer class="fixed-bottom bg-primary">
        <div>
            Copyright(c) 2020 By EMDI All Right Reserved.
        </div>
    </footer>
</html>

전체 붙여넣기 한 다음 이제 필요한 소스만 남겨놔야하는데 우리는 navbar만 우선 남겨놓도록 합시다. <body> 소스 중 </nav>까지 남겨놓고 나머지 content 소스들은 지웁니다. base.html문서를 상속받은 템플릿이라면 이제 <nav></nav>는 공통으로 상속받게 됩니다.

{% block content %}{% endblock %}은 base.html에서 상속받기 위해 설정했던 소스를 제외한 나머지 content 부분을 의미합니다.

 

2) index.html 정리하기

{% extends 'base.html' %}
{% block content %}
<style>
    body {font-family: dotum;font-size: 10pt;line-height: 25px;color:#474747;}
    .tableline {border:1px solid #908d8d;display: table;width:100%;}
    .tablerow {display:table-row;}
    .tablecell_h {display: table-cell;padding:5px 20px;height:37px;background-color: #eaeaea;border-bottom:1px solid #908d8d;border-right: 1px solid #908d8d;text-align:center;font-weight: bold;}
    .tablecell_he {display: table-cell;padding:5px 20px;height:37px;background-color: #eaeaea;border-bottom:1px solid #908d8d;text-align:center;font-weight: bold;}
    .tablecell {display: table-cell;padding:5px 20px;height:37px;border-bottom:1px solid #908d8d;border-right: 1px solid #908d8d;text-align:center;}
    .tablecell_e {display: table-cell;padding:5px 20px;height:37px;border-bottom:1px solid #908d8d;text-align:center;}
    .tablecell_last {display: table-cell;padding:5px 20px;height:37px;border-right: 1px solid #908d8d;text-align:center;}
    .tablecell_laste {display: table-cell;padding:5px 20px;height:37px;text-align:center;}
    .tablecell a:visited {color:#474747;}
    .tablecell_e a:visited {color:#474747;}
    .tablecell_last a:visited {color:#474747;}
    .tablecell_laste a:visited {color:#474747;}
    .tablecell a:link {text-decoration: none;}
    .tablecell_e a:link {text-decoration: none;}
    .tablecell_last a:link {text-decoration: none;}
    .tablecell_laste a:link {text-decoration: none;}
    .tablebank {height:37px;}
    .tablemodule1 {height:37px;}
</style>
<div class="container" style="width:100%;">
  <div style="width:1024px;height:400px;margin:0 auto;">
      <div style="margin:0 21 0 22px">
          <h1 style="margin-top:30px;">Project Management Page</h1>
          <div class="toptextarea">
              <span style="color:crimson;">* 해당 홈페이지는 파이썬 공부를 위한 테스트용 홈페이지입니다. </span>
          </div>
          <!-- Table Layout -->
          <div style="margin-top: 20px;">
            <div class="tableline">
                <div class="tablerow">
                    <div class="tablecell_h" style="width:130px;">팀명</div>
                    <div class="tablecell_h" style="width:180px;">팀닉네임</div>
                    <div class="tablecell_h" style="width:180px;">팀멤버수</div>
                    <div class="tablecell_h" style="width:130px;">비고</div>
                </div>
                {% for team in teams %}
                <div class="tablerow">
                    <div class="tablecell" style="width:130px;">{{ team.team_nm }}</div>
                    <div class="tablecell" style="width:180px;">{{ team.team_nick }}</div>
                    <div class="tablecell" style="width:180px;">{{ team.team_mems_cnt }}</div>
                    <div class="tablecell" style="width:130px;">{{ team.bigo }}</div>
                </div>
                {% endfor %}
            </div>
          </div>
          <!-- Table Layout -->
          <iframe id="downframe" style="width:1px;height:1px;visibility: hidden;"></iframe>
      </div>
  </div>
</div>
{% endblock %}

base.html에 nav를 설정해두었으니 이제는 index.html에 가서 넣지 않아도 될 nav를 제거하는 작업이 필요합니다. base.html에 설정했던 소스들을 다 지우고 나머지 소스들만 남깁니다. 그리고 위아래로 {% block content %}{% endblock %} 영역을 지정해줍니다.

* {% extends 'base.html' %} : 이 소스는 base.html를 상속받는다는 의미이며, 제일 상단에 붙여넣습니다.

* {% block content %}{% endblock %} : 이 소스는 아까 전 base.html에서도 봤던 소스인데 바로 index.html에서 content를 나타내는 부분을 의미합니다.

 

(dJangoVenv) C:\dev\Python\finpmProject>python manage.py runserver

그 다음 클라이언트 사이트에 들어가서 확인을 해보면 처음에 봤던 화면과 동일하지만 해당 화면은 base.html과 index.html을 활용하여 만들어진 화면입니다. 같은 index.html 창이지만 빨간색 라인은 이제 base.html에서 상속받은 소스를 보여주는 것이고 초록색 라인은 index.html의 content입니다.

 

3. 템플릿 활용하기(결과)

이번에는 여러 화면이 있다는 가정하에 작업을 해보도록 합시다.

* 필요한 화면 : 기본템플릿(base.html) / 초기화면(index.html) / 팀정보(teamInfo.html)

<!-- base.html -->
<!DOCTYPE html>
    <!-- HEAD START -->
    <head>
        <meta charset="utf-8">
        <title>Project Management Page</title>
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css">
    </head>
    <!-- HEAD END -->
    <!-- BODY START -->
    <body>
         <!-- navbar START -->
         <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
            <a class="navbar-brand">FINPM</a>
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarTogglerDemo02" aria-controls="navbarTogglerDemo02" aria-expanded="false" aria-label="Toggle navigation">
              <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarTogglerDemo02">
              <ul class="navbar-nav mr-auto mt-2 mt-lg-0">
                <li class="nav-item active">
                  <a class="nav-link" href="{% url 'index' %}">초기화면<span class="sr-only"></span></a>
                </li>
                <li class="nav-item">
                  <a class="nav-link" href="{% url 'teamInfo' %}">팀정보</a>
                </li>
              </ul>
              <form class="form-inline my-2 my-lg-0">
                <input class="form-control mr-sm-2" type="search" placeholder="Search">
                <button class="btn btn-outline-light my-2 my-sm-0" type="submit">Search</button>
              </form>
            </div>
        </nav>
        <!-- navbar END -->
        <!-- content START -->
        {% block content %}
        {% endblock %}
        <!-- content END -->
    </body>
    <!-- BODY END -->
    <!-- FOOTER START -->
    <footer class="fixed-bottom bg-primary">
        <div>
            Copyright(c) 2020 By EMDI All Right Reserved.
        </div>
    </footer>
    <!-- FOOTER END -->
</html>

참고로 여기서 href="{% url 'index' %}"은 urls.py 소스에서 path('', IndexView.as_view(), name='index')의 name을 의미합니다. 

 

<!-- index.html -->
{% extends 'base.html' %}
{% block content %}
<div class="container" style="width:100%;">
  <div style="width:1024px;height:400px;margin:0 auto;">
      <div style="margin:0 21 0 22px">
          <h1 style="margin-top:30px;">메인화면입니다!</h1>
          <div class="toptextarea">
              <span style="color:crimson;">* 해당 홈페이지는 파이썬 공부를 위한 테스트용 홈페이지입니다. </span>
          </div>
          <!-- Table Layout -->
          <h1>메인화면입니다!</h1>
          <!-- Table Layout -->
          <iframe id="downframe" style="width:1px;height:1px;visibility: hidden;"></iframe>
      </div>
  </div>
</div>
{% endblock %}

{% extends 'base.html' %}
{% block content %}
<style>
    /* body {font-family: dotum;font-size: 10pt;line-height: 25px;color:#474747;} */
    .tableline {border:1px solid #908d8d;display: table;width:100%;}
    .tablerow {display:table-row;}
    .tablecell_h {display: table-cell;padding:5px 20px;height:37px;background-color: #eaeaea;border-bottom:1px solid #908d8d;border-right: 1px solid #908d8d;text-align:center;font-weight: bold;}
    .tablecell_he {display: table-cell;padding:5px 20px;height:37px;background-color: #eaeaea;border-bottom:1px solid #908d8d;text-align:center;font-weight: bold;}
    .tablecell {display: table-cell;padding:5px 20px;height:37px;border-bottom:1px solid #908d8d;border-right: 1px solid #908d8d;text-align:center;}
    .tablecell_e {display: table-cell;padding:5px 20px;height:37px;border-bottom:1px solid #908d8d;text-align:center;}
    .tablecell_last {display: table-cell;padding:5px 20px;height:37px;border-right: 1px solid #908d8d;text-align:center;}
    .tablecell_laste {display: table-cell;padding:5px 20px;height:37px;text-align:center;}
    .tablecell a:visited {color:#474747;}
    .tablecell_e a:visited {color:#474747;}
    .tablecell_last a:visited {color:#474747;}
    .tablecell_laste a:visited {color:#474747;}
    .tablecell a:link {text-decoration: none;}
    .tablecell_e a:link {text-decoration: none;}
    .tablecell_last a:link {text-decoration: none;}
    .tablecell_laste a:link {text-decoration: none;}
    .tablebank {height:37px;}
    .tablemodule1 {height:37px;}
</style>
<!-- teamInfo.html -->
<div class="container" style="width:100%;">
  <div style="width:1024px;height:400px;margin:0 auto;">
      <div style="margin:0 21 0 22px">
          <h1 style="margin-top:30px;">Project Management Page</h1>
          <div class="toptextarea">
              <span style="color:crimson;">* 해당 홈페이지는 파이썬 공부를 위한 테스트용 홈페이지입니다. </span>
          </div>
          <!-- Table Layout -->
          <div style="margin-top: 20px;">
            <div class="tableline">
                <div class="tablerow">
                    <div class="tablecell_h" style="width:130px;">팀명</div>
                    <div class="tablecell_h" style="width:180px;">팀닉네임</div>
                    <div class="tablecell_h" style="width:180px;">팀멤버수</div>
                    <div class="tablecell_h" style="width:130px;">비고</div>
                </div>
                {% for team in teams %}
                <div class="tablerow">
                    <div class="tablecell" style="width:130px;">{{ team.team_nm }}</div>
                    <div class="tablecell" style="width:180px;">{{ team.team_nick }}</div>
                    <div class="tablecell" style="width:180px;">{{ team.team_mems_cnt }}</div>
                    <div class="tablecell" style="width:130px;">{{ team.bigo }}</div>
                </div>
                {% endfor %}
            </div>
          </div>
          <!-- Table Layout -->
          <iframe id="downframe" style="width:1px;height:1px;visibility: hidden;"></iframe>
      </div>
  </div>
</div>
{% endblock %}

위의 소스는 뼈대로 만든 화면, 메인화면, 팀정보를 나타내는 화면입니다. base.html를 제외한 나머지 화면은 {% block content %}로 묶여있는 것을 확인할 수 있습니다. 

1) views.py : teamInfoView추가 및 해당 class가 어떤 html을 사용하는지 설정

from django.shortcuts import render, redirect
from django.views.generic import TemplateView, ListView, DetailView

from .models import Team
from .forms import TeamCreate
from django.http import HttpResponse

# Create your views here.
def index(request):
    return render(request, 'index.html')

def teamInfo(request):
    teams = Team.objects.all()
    context = {'teams': teams}
    return render(request, 'content/teamInfo.html', context)

 

2) urls.py : teamInfo.html을 부르기 위해 path설정

from django.contrib import admin
from django.urls import path
from django.conf.urls import url #추가
import fin.views

app_name = 'fin'
urlpatterns = [
    path('admin/', admin.site.urls),
    # 추가로 생성한 urls
    path('', fin.views.index , name='index'),
    path('team/teamInfo/',  fin.views.teamInfo, name='teamInfo'),
]

Comments