Django

Chapter 6-2 Bootstrap 적용으로 블로그 UI 개선하기

Chansman 2025. 5. 9. 10:05

💻 Chapter 06. Bootstrap 적용으로 블로그 UI 개선하기


🎯 목표

Django 블로그 프로젝트에 Bootstrap 5를 적용하여 UI/UX를 대폭 개선합니다. 네비게이션 바, 버튼, 리스트, 폼 등 전체적인 구조를 리팩토링합니다.


1️⃣ base.html에 Bootstrap 적용

✅ 흐름 설명

  1. 모든 페이지의 기본 레이아웃이 되는 base.html에서 Bootstrap CSS와 JS를 불러옵니다.
  2. 네비게이션 바에 Bootstrap 클래스(d-flex, bg-black, text-white)를 적용하여 스타일을 통일합니다.
  3. 인증 상태에 따라 로그인/로그아웃 버튼을 다르게 표시하며, 버튼에 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)

✅ 흐름 설명

  1. 블로그 목록을 object_list 반복문으로 출력
  2. 각 글 제목을 link-primary 스타일로 링크
  3. 생성 버튼은 btn btn-sm btn-primary, 검색은 form-control-sm 사용
  4. 페이지네이션에 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)

✅ 흐름 설명

  1. 제목과 작성자 정보 출력
  2. 작성자 본인 또는 관리자일 경우 수정/삭제 버튼 노출
  3. 삭제 버튼은 자바스크립트 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)

✅ 흐름 설명

  1. blog_create.html과 blog_update.html을 통합하여 blog_form.html로 재구성
  2. 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 리팩토링
  • 통합 폼으로 재사용성과 유지보수성 강화
  • 다음 단계에서 댓글 모델과 템플릿을 연결하여 블로그 완성도를 높입니다 ✅