💻 Chapter 06. Bootstrap 적용으로 블로그 UI 개선하기
🎯 목표
Django 블로그 프로젝트에 Bootstrap 5를 적용하여 UI/UX를 대폭 개선합니다. 네비게이션 바, 버튼, 리스트, 폼 등 전체적인 구조를 리팩토링합니다.
1️⃣ base.html에 Bootstrap 적용
✅ 흐름 설명
- 모든 페이지의 기본 레이아웃이 되는 base.html에서 Bootstrap CSS와 JS를 불러옵니다.
- 네비게이션 바에 Bootstrap 클래스(d-flex, bg-black, text-white)를 적용하여 스타일을 통일합니다.
- 인증 상태에 따라 로그인/로그아웃 버튼을 다르게 표시하며, 버튼에 btn btn-sm 클래스를 지정합니다.
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>블로그 프로젝트</title>
<link href="{% static 'css/bootstrap.css' %}" rel="stylesheet">
</head>
<body>
<nav class="d-flex justify-content-between py-2 px-4 bg-black text-white">
<div>
<a class="text-decoration-none text-white" href="{% url 'blog:list' %}">홈</a>
</div>
<div style="text-align: right">
{% if request.user.is_authenticated %}
<form action="{% url 'logout' %}" method="POST" class="d-inline">
{% csrf_token %}
<button class="btn btn-danger btn-sm">로그아웃</button>
</form>
{{ request.user.username }}
{% else %}
<a href="{% url 'signup' %}">회원가입</a>
<a href="{% url 'login' %}">로그인</a>
{% endif %}
</div>
</nav>
<div class="container">
{% block content %}{% endblock %}
</div>
<script src="{% static 'js/bootstrap.bundle.js' %}"></script>
{% block js %}{% endblock %}
</body>
</html>
2️⃣ 블로그 목록 페이지 (blog_list.html)
✅ 흐름 설명
- 블로그 목록을 object_list 반복문으로 출력
- 각 글 제목을 link-primary 스타일로 링크
- 생성 버튼은 btn btn-sm btn-primary, 검색은 form-control-sm 사용
- 페이지네이션에 Bootstrap의 pagination, page-item, page-link 적용
<div class="mt-2">
<h1 class="d-inline">블로그 목록</h1>
<a href="{% url 'blog:create' %}" class="float-end btn btn-sm btn-primary">생성</a>
</div>
<hr>
{% for blog in object_list %}
<div class="my-1">
<a href="{% url 'blog:detail' blog.pk %}" class="link-primary link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover">
[{{ blog.get_category_display }}] {{ blog.title }} - <small>{{ blog.created_at|date:"Y-m-d" }}</small>
</a>
</div>
{% endfor %}
<form method="get" class="my-3 d-flex">
<input class="form-control-sm" name="q" type="text" placeholder="검색어 입력" value="{{ request.GET.q }}">
<button class="btn btn-sm btn-info ms-2">검색</button>
</form>
<ul class="pagination">
<!-- 페이지네이션 버튼 구성 (이전/다음/현재 페이지) -->
</ul>
3️⃣ 블로그 상세 페이지 (blog_detail.html)
✅ 흐름 설명
- 제목과 작성자 정보 출력
- 작성자 본인 또는 관리자일 경우 수정/삭제 버튼 노출
- 삭제 버튼은 자바스크립트 confirm() 확인창을 통해 제출
<div class="mt-2 d-flex justify-content-between">
<h1>{{ blog.title }}</h1>
{% if request.user == blog.author or request.user.is_superuser %}
<div>
<a class="btn btn-sm btn-warning" href="{% url 'blog:update' blog.pk %}">수정</a>
<form id="delete_form" action="{% url 'blog:delete' blog.pk %}" method="POST" style="display: inline">
{% csrf_token %}
<button type="button" id="delete_btn" class="btn btn-sm btn-danger">삭제</button>
</form>
</div>
{% endif %}
</div>
<hr>
<p>{{ blog.content }}</p>
<a class="btn btn-sm btn-info" href="{% url 'blog:list' %}">목록</a>
<script>
document.querySelector('#delete_btn').addEventListener('click', function() {
if(confirm('삭제하시겠습니까?')) {
document.querySelector('#delete_form').submit();
}
})
</script>
4️⃣ 블로그 작성/수정 통합 (blog_form.html)
✅ 흐름 설명
- blog_create.html과 blog_update.html을 통합하여 blog_form.html로 재구성
- BlogCreateView, BlogUpdateView에서 get_context_data()를 활용해 버튼 라벨 등 구분
<h1 class="mt-2">블로그 {{ sub_title }}</h1>
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<button class="btn btn-primary">{{ btn_name|default:"저장" }}</button>
</form>
3. views.py ( 생성, 업데이트 에 함수추가 )
class BlogCreateView(...):
...
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['sub_title'] = '작성'
context['btn_name'] = '생성'
return context
class BlogUpdateView(...):
...
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['sub_title'] = '수정'
context['btn_name'] = '수정'
return context
4. blog_form.html body부분 JS block 추가
{% block js %}
<script>
document.querySelectorAll('select, input, textarea').forEach(function(ele){
ele.classList.add('form-control');
})
</script>
{% endblock %}
5️⃣ 로그인 및 회원가입 페이지 (registration/login.html, signup.html)
1. form_js.html 를 새로만들어서 다른곳에서도 사용할수있게 만든다. base.html같이
<script>
document.querySelectorAll('select, input, textarea').forEach(function(ele){
ele.classList.add('form-control');
})
</script>
2. blog_form.html 부분에서 form_js 를 불러온다
{% block js %}
{% include 'form-js.html' %}
{% endblock %}
✅ 흐름 설명
- Django 기본 Form 출력 방식에 Bootstrap 버튼만 추가하여 UI 개선 및 바디부분 연결 추가
1. login.html
{% extends 'base.html' %}
{% block content %}
<h1 class="mt-2">로그인</h1>
<form method="POST">
{% csrf_token %}
{{ form.as_p }} <!-- 폼 데이터를 p 태그로 출력 -->
<button class="btn btn-primary">로그인</button>
</form>
{% endblock %}
{% block js %}
{% include 'form-js.html' %}
{% endblock %}
2.signup.html
{% extends 'base.html' %}
{% block content %}
<h1 class="mt-2">회원가입</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button class="btn btn-primary">가입하기</button>
</form>
{% endblock %}
{% block js %}
{% include 'form-js.html' %}
{% endblock %}
✅ 최종 요약
- base.html에서 전체 Bootstrap 적용을 통해 디자인 통일
- CRUD, 인증, 상세 페이지까지 일괄 UI 리팩토링
- 통합 폼으로 재사용성과 유지보수성 강화
- 다음 단계에서 댓글 모델과 템플릿을 연결하여 블로그 완성도를 높입니다 ✅
'Django' 카테고리의 다른 글
Chapter 6-4 Django 블로그에 댓글 기능 추가하기 (0) | 2025.05.09 |
---|---|
Chapter 6-3 댓글 기능 시작하기 - 모델과 관리자 설정 (0) | 2025.05.09 |
Chapter 6-1 [블로그] 댓글 기능 만들기 - Static Files 설정과 Bootstrap 적용 (0) | 2025.05.09 |
Chapter 5-8 📌 Mini Project - Admin 권한으로 모든 게시글 수정/삭제 가능하게 만들기 (0) | 2025.05.08 |
Chapter 5-7 📌 Django URL 구조 정리 - include와 앱 네임스페이스로 FBV/CBV 분리하기 (0) | 2025.05.08 |