Global Navigation Bar를 만들어 볼 것이다. GNB라고도 하는데 실무에서 화면 설계서 등 GNB라고 표기되는 경우가 있으니 실무에서 처음 접할때 모르는 용어라면 검색하면서 익히도록 하자.
메인 메뉴가 들어갈 사이트의 첫 페이지를 만들어보자.
사이트의 첫 페이지는 특정 앱에 속한게 아니므로 프로젝트 디렉토리(mysite
)에 만든다.
마찬가지로 url, view, templates 순으로 코딩하면 된다.
urls.py
from django.contrib import admin
from django.urls import path, include
from .views import HomeView
urlpatterns = [
path('admin/', admin.site.urls),
path('', HomeView.as_view(), name='home'),
path('todo/', include('todo.urls')),
]
views.py
from django.views.generic import TemplateView
class HomeView(TemplateView):
template_name = 'home.html'
Root 경로에 templates 경로를 생성하여 home.html을 생성한다.
templates/home.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
Welcome to HOME!
</body>
</html>
http://127.0.0.1:8000/ url에 메인 페이지가 잘 뜨는지 확인한다.
base.html
을 만들어서 작업한다.
아래 준비물들을 검색하여 cdn 링크들을 전부 가져온다.
준비물
css
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css">
<link rel="shortcut icon" href="{% static 'img/favicon.ico' %}">
장고의 static 템플릿 태그를 사용하기 위해서 HTML 최상단에 로드시켜준다.
{% load staticfiles %}
{# Django 3.x 부터는 {% load static %} 으로 변경한다. #}
js
<!-- Bootstrap core JavaScript -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
파비콘은 아래 링크에서 다운을 받는다.
장고의 static 템플리 태그를 사용하기 위해서 로드 문법을 사용해야 한다.
Django 3.x 버전 미만
python {# {% load staticfiles %} #}
Django 3.x 부터는 {% load static %} 으로 변경한다.
{% load static %}
base.html
{#{% load staticfiles %}#}
{% load static %}
{# Django 3.x 부터는 {% load static %} 으로 변경한다. #}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css">
<link rel="shortcut icon" href="{% static 'img/favicon.ico' %}">
{# 장고의 static 템플릿 태그를 사용하기 위해서 로드를 시켜준다.#}
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="#">Navbar</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 active" aria-current="page" href="#">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Dropdown
</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
<li><a class="dropdown-item" href="#">Action</a></li>
<li><a class="dropdown-item" href="#">Another action</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#">Something else here</a></li>
</ul>
</li>
<li class="nav-item">
<a class="nav-link disabled">Disabled</a>
</li>
</ul>
<form class="d-flex">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success" type="submit">Search</button>
</form>
</div>
</div>
</nav>
<!-- Bootstrap core JavaScript -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
</body>
</html>
그리고 home.html
에서는 base.html을 상속받는다. 모든 내용을 지우고 아래 코드만 작성한다.
{% extends 'base.html' %}
이후 페이지를 확인해본다.
필자는 Custom 다크 색상을 클래스명을 넣어서 커스텀을 진행했으니 마음껏 커스텀을 해본다.
커스텀 소스
{#{% load staticfiles %}#}
{% load static %}
{# Django 3.x 부터는 {% load static %} 으로 변경한다. #}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css">
<link rel="shortcut icon" href="{% static 'img/favicon.ico' %}">
{# 장고의 static 템플릿 태그를 사용하기 위해서 로드를 시켜준다.#}
</head>
<body style="padding-top: 90px;">
<!-- Main Menu -->
<nav class="navbar navbar-expand-sm navbar-dark bg-primary fixed-top">
<span class="navbar-brand mx-5 mb-0 font-weight-bold font-italic">Vue-Django System</span>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav mr-auto">
<li class="nav-item mx-1 btn btn-primary">
<a class="nav-link text-white" href="{% url 'home' %}">Home</a></li>
<li class="nav-item mx-1 btn btn-primary">
<a class="nav-link text-white" href="{% url 'todo:vonly' %}">VueOnly</a></li>
<li class="nav-item mx-1 btn btn-primary">
<a class="nav-link text-white" href="{% url 'todo:create' %}">DjangoOnly</a></li>
<li class="nav-item dropdown mx-1 btn btn-primary">
<a class="nav-link dropdown-toggle text-white" href="#" data-toggle="dropdown">Util</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="{% url 'admin:index' %}">Admin</a>
<div class="dropdown-divider"></div>
</div>
</li>
</ul>
<form class="form-inline my-2">
<input class="form-control mr-sm-2" type="search" placeholder="Search">
</form>
<ul class="navbar-nav ml-5 mr-5">
<li class="nav-item dropdown mx-1 btn btn-primary">
<a class="nav-link dropdown-toggle text-white" href="#" data-toggle="dropdown">
<i class="fas fa-user"></i> Anonymous </a>
<div class="dropdown-menu">
<a class="dropdown-item" href="{% url 'home' %}">Login</a>
<a class="dropdown-item" href="{% url 'home' %}">Register</a>
</div>
</li>
</ul>
</div>
</nav>
<div class="container">
</div>
<!-- Bootstrap core JavaScript -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
</body>
</html>
추가로 block 태그들을 이용해 이왕같이 블럭 코드를 추가한다.
{#{% load staticfiles %}#}
{% load static %}
{# Django 3.x 부터는 {% load static %} 으로 변경한다. #}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}home.html{% endblock %}</title>
{# block 태그의 이름을 title로 정한다.#}
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css">
<link rel="shortcut icon" href="{% static 'img/favicon.ico' %}">
{# 장고의 static 템플릿 태그를 사용하기 위해서 로드를 시켜준다.#}
{% block extra-style %}{% endblock %}
{# 하위 html 파일에서 추가로 스타일을 지정할 수 있기 때문에 extra-style 블럭을 지정했다. #}
</head>
<body style="padding-top: 90px;">
<!-- Main Menu -->
<nav class="navbar navbar-expand-sm navbar-dark bg-primary fixed-top">
<span class="navbar-brand mx-5 mb-0 font-weight-bold font-italic">Vue-Django System</span>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav mr-auto">
<li class="nav-item mx-1 btn btn-primary">
<a class="nav-link text-white" href="{% url 'home' %}">Home</a></li>
<li class="nav-item mx-1 btn btn-primary">
<a class="nav-link text-white" href="{% url 'todo:vonly' %}">VueOnly</a></li>
<li class="nav-item mx-1 btn btn-primary">
<a class="nav-link text-white" href="{% url 'todo:create' %}">DjangoOnly</a></li>
<li class="nav-item dropdown mx-1 btn btn-primary">
<a class="nav-link dropdown-toggle text-white" href="#" data-toggle="dropdown">Util</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="{% url 'admin:index' %}">Admin</a>
<div class="dropdown-divider"></div>
</div>
</li>
</ul>
<form class="form-inline my-2">
<input class="form-control mr-sm-2" type="search" placeholder="Search">
</form>
<ul class="navbar-nav ml-5 mr-5">
<li class="nav-item dropdown mx-1 btn btn-primary">
<a class="nav-link dropdown-toggle text-white" href="#" data-toggle="dropdown">
<i class="fas fa-user"></i> Anonymous </a>
<div class="dropdown-menu">
<a class="dropdown-item" href="{% url 'home' %}">Login</a>
<a class="dropdown-item" href="{% url 'home' %}">Register</a>
</div>
</li>
</ul>
</div>
</nav>
<div class="container">
{# 본문이 들어갈 자리를 content block으로 지정한다.#}
{% block content %}{% endblock %}
</div>
{# 푸터 블럭 지정 #}
{% block footer %}{% endblock %}
<!-- Bootstrap core JavaScript -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
{% block extra-script %}{% endblock %}
</body>
</html>
todo_vue_only.html
{% extends 'base.html' %}
{% block title %}todo_vue_only.html{% endblock %}
{% block extra-style %}
<style>
body {
text-align: center;
background-color: #ddd;
}
.inputBox {
margin: auto;
width: 70%;
background: white;
height: 50px;
border-radius: 50px;
line-height: 50px;
}
.inputBox .name {
border-style: none;
border-bottom: 1px solid #ddd;
width: 90px;
padding-left: 20px;
line-height: 20px;
}
.inputBox .item {
border-style: none;
border-bottom: 1px solid #ddd;
width: 400px;
margin-left: 50px;
padding-left: 20px;
line-height: 20px;
}
.todoList {
list-style: none;
padding: 10px 0;
text-align: left;
}
.todoList li {
display: flex;
height: 50px;
line-height: 50px;
margin: 0.5rem 0;
padding: 0 0.9rem;
background: white;
border-radius: 5px;
}
.removeBtn {
font-size: 20px;
cursor: pointer;
height: 30px;
line-height: 0;
margin: auto 0 auto auto;
}
</style>
{% endblock %}
{% block content %}
<div id='app'>
<h1>My Todo App !</h1>
<strong>서로 할 일이나 의견을 공유해 봅시다.</strong>
<br>
<div class="inputBox">
<input class="name" type="text" placeholder="name ..." v-model.trim="name">
<input class="item" type="text" placeholder="type anything welcomed ..."
v-model.trim="newTodoItem" @keyup.enter="add_todo">
<button class="btn btn-info btn-sm" @click="add_todo">ADD</button>
</div>
<ul class="todoList">
<li v-for="(todoItem, index) in todoItems">
<span>{todoItem.name}::: {todoItem.item}</span>
<button class="removeBtn btn btn-danger btn-sm" @click.stop="remove_todo(todoItem, index)">×
</button>
</li>
</ul>
</div>
{% endblock %}
{% block extra-script %}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
let vm = new Vue({
delimiters: ['{', '}'], // 중괄호를 하나만 사용하겠다는 문법 수정
el: '#app',
data: {
name: '',
newTodoItem: '',
todoItems: [
{name: '김석훈', item: 'Django 와 Vue.js 연동 프로그램을 만들고 있습니다.'},
{name: '홍길동', item: '이름을 안쓰면 홍길동으로 나와요...'},
{name: '이순신', item: '신에게는 아직 열두 척의 배가 있사옵니다.'},
{name: '성춘향', item: '그네 타기'},
],
},
methods: {
add_todo: function () {
console.log("add_todo()...");
if (this.name === '') {
this.name = '홍길동';
}
if (this.newTodoItem === '') {
return;
}
this.todoItems.push({name: this.name, item: this.newTodoItem});
this.name = '';
this.newTodoItem = '';
},
remove_todo: function (todoItem, index) {
console.log(todoItem);
this.todoItems.splice(index, 1); /* from index, count */
// axios delete
},
clear_input: function () {
this.name = '';
this.newTodoItem = '';
},
},
})
</script>
{% endblock %}
todo_confirm_delete.html
{% extends 'base.html' %}
{% block title %}todo_confirm_delete.html{% endblock %}
{% block extra-style %}
<style>
body {
text-align: center;
background-color: #ddd;
}
.inputBox .name {
border-style: none;
border-bottom: 1px solid #ddd;
width: 70px;
padding-left: 20px;
}
.inputBox .item {
border-style: none;
border-bottom: 1px solid #ddd;
width: 400px;
margin-left: 50px;
padding-left: 20px;
}
.todoList li {
display: flex;
height: 50px;
line-height: 50px;
margin: 0.5rem 0;
padding: 0 0.9rem;
background: white;
border-radius: 5px;
}
</style>
{% endblock %}
{% block content %}
<div id='app'>
<h1>Todo Delete</h1>
<p>
Are you sure to delete {{ object }} ?
</p>
<br>
<form action="." method="post"> {% csrf_token %}
<button type="submit" class="btn btn-danger btn-sm">Confirm</button>
</form>
</div>
{% endblock %}
todo_list.html
{% extends 'base.html' %}
{% block title %}todo_list.html{% endblock %}
{% block extra-style %}
<style>
body {
text-align: center;
background-color: #ddd;
}
.todoList {
list-style: none;
padding: 10px 0;
text-align: left;
}
.todoList li {
display: flex;
height: 50px;
line-height: 50px;
margin: 0.5rem 0;
padding: 0 0.9rem;
background: white;
border-radius: 5px;
}
.removeBtn {
margin-left: auto;
font-size: 20px;
cursor: pointer;
}
</style>
{% endblock %}
{% block content %}
<div id='app'>
<h1>My Todo App !</h1>
<strong>서로 할 일이나 의견을 공유해 봅시다.</strong>
<br>
<ul class="todoList">
{# v-for문 대신에, 장고에서 제공하는 템플릿 태그로 바꾼다. #}
{% for todo in object_list %}
<li>
<span>{{ todo.name }}::: {{ todo.todo }}</span>
<span class="removeBtn"><a href="{% url 'todo:delete' todo.id %}">×</a></span>
</li>
{% endfor %}
</ul>
</div>
{% endblock %}
todo_form.html
{% extends 'base.html' %}
{% block title %}
todo_form.html
{% endblock %}
{% block extra-style %}
<style>
body {
text-align: center;
background-color: #ddd;
}
.inputBox {
margin: auto;
width: 70%;
background: white;
height: 50px;
border-radius: 50px;
line-height: 50px;
}
.inputBox .name {
border-style: none;
border-bottom: 1px solid #ddd;
width: 90px;
padding-left: 20px;
line-height: 20px;
}
.inputBox .item {
border-style: none;
border-bottom: 1px solid #ddd;
width: 400px;
margin-left: 50px;
padding-left: 20px;
line-height: 20px;
}
.todoList li {
display: flex;
height: 50px;
line-height: 50px;
margin: 0.5rem 0;
padding: 0 0.9rem;
background: white;
border-radius: 5px;
}
</style>
{% endblock %}
{% block content %}
<div id='app'>
<h1>My Todo App !</h1>
<strong>서로 할 일이나 의견을 공유해 봅시다.</strong>
<br>
{# csrf 토큰 공격을 방지하는 csrf 토큰을 넣는다. 장고에서 제공해주는 템플릿 태그이다. #}
<form class="inputBox" action="." method="post"> {% csrf_token %}
<input class="name" type="text" placeholder="name ..." name="name">
<input class="item" type="text" placeholder="type anything welcomed ..."
name="todo">
<button class="btn btn-info btn-sm" type="submit">ADD</button>
</form>
</div>
{% endblock %}
이제 모든화면에서 메뉴가 잘 나오는지 확인한다.
{% extends 'base.html' %}
{% load static %}
{% block title %}home.html{% endblock %}
{% block extra-style %}
<style type="text/css">
.home-window {
background-image: url("{% static 'img/lion.jpg' %}");
background-repeat: no-repeat;
background-position: center;
background-size: 100%;
height: 500px;
border-top: 10px solid #ccc;
border-bottom: 10px solid #ccc;
color: yellow;
padding: 20px 0 0 20px;
}
.title {
color: #c80;
font-weight: bold;
}
.powered {
position: relative;
top: 77%;
color: #cc0;
font-style: italic;
}
.footer-wrapper {
background: #456;
color: #eee;
line-height: 50px;
}
.footer-wrapper a {
color: #ff9; /* footer-right text */
}
.footer-wrapper a:hover {
color: #0ff;
}
footer ul li {
text-align: center;
}
</style>
{% endblock extra-style %}
{% block content %}
<div class="home-window">
<h2 class="title">Django - Python Web Programming</h2>
<h4 class="powered"><i class="fas fa-cog"></i> powered by django and bootstrap.</h4>
</div>
<hr style="margin: 10px 0;">
<div class="row text-center">
<div class="col-sm-6">
<h3>Todo App</h3>
<p>With Todo app, you can manage, prioritize, and complete the most important things
you need to do every day. I can focus on the most important things
and I can do my day more deliberately.
You can also share lists with colleagues, friends, and family.</p>
</div>
<div class="col-sm-6">
<h3>Blog App</h3>
<p>This application makes it possible to log daily events or write your own interests
such as hobbies, techniques, etc.
A typical blog combines text, digital images, and links to other blogs, web pages,
and other media related to its topic.</p>
</div>
</div>
{% endblock content %}
{% block footer %}
<footer class="fixed-bottom footer-wrapper">
<div class="container">
<div class="row">
<div class="col-md-4">
<p>Copyright © 2019 DjangoBook by shkim</p>
</div>
<div class="col-md-8">
<ul class="list-inline float-right">
<li class="list-inline-item">
<a href="#" data-toggle="tooltip" data-placement="top" title="준비중입니다">Introduction</a>
</li>
<li class="list-inline-item">
<a href="#" data-toggle="tooltip" data-placement="top" title="준비중입니다">Help</a>
</li>
<li class="list-inline-item">
<a href="#" data-toggle="tooltip" data-placement="top" title="준비중입니다">Policy</a>
</li>
<li class="list-inline-item">
<a href="#" data-toggle="tooltip" data-placement="top" title="준비중입니다">Something</a>
</li>
</ul>
</div>
</div>
</div>
</footer>
{% endblock footer %}
{% block extra-script %}
<script>
$(function () {
$('[data-toggle=tooltip]').tooltip();
});
</script>
{% endblock extra-script %}
메인 화면의 이미지는
해당 링크에서 자료를 받거나, 원하는 어떠한 이미지를 사용해도 좋다.
GitHub Source
👉🏻깃허브 소스