Django

Chapter 9-2 Django Post 목록 페이지 만들기

Chansman 2025. 5. 14. 15:11

 

📸 Django + Swiper.js 슬라이더 구현

이미지가 여러 장인 포스트를 사용자에게 직관적으로 보여주기 위해 Swiper.js를 활용한 슬라이더 UI를 구현할 수 있습니다. 아래는 그 핵심 내용과 적용 방법입니다.

1️⃣ Swiper.js란? https://swiperjs.com/get-started#use-swiper-from-cdn

  • Swiper.js는 모바일과 데스크탑 환경 모두에서 강력한 반응형 이미지 슬라이더를 지원하는 JavaScript 라이브러리입니다.
  • 다양한 커스터마이징 옵션 제공 (loop, pagination, navigation 등)
  • swiper-pagination을 설정하면, 하단에 도트 형태의 페이지네이션이 표시되어 UX를 향상시킵니다.

2️⃣ 기본 아이디어 💡

  • Django 템플릿에서 for 루프를 사용해 여러 포스트를 렌더링하고, 각 포스트에 연결된 여러 이미지를 슬라이드로 구성
  • 이미지 슬라이드는 .swiper 클래스로 감싸고, 슬라이드 내부는 .swiper-wrapper와 .swiper-slide로 구성
  • 각 포스트마다 Swiper 슬라이더를 독립적으로 렌더링

3️⃣ 코드 구조

<!-- templates/post/list.html -->

{% extends 'base.html' %}

{% block style %}
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css"/>
  <style>
    .post-image {
      aspect-ratio: 1 / 1;
      object-fit: cover;
    }
  </style>
{% endblock %}

{% block content %}
<div class="row">
  <div class="col-10 offset-1 col-lg-6 offset-lg-3">
    {% for post in object_list %}
      <div class="swiper" style="max-height: 500px;">
        <div class="border-1 swiper-wrapper">
          {% for post_image in post.images.all %}
            <div class="swiper-slide">
              <img class="img-fluid post-image" src="{{ post_image.image.url }}" alt="">
            </div>
          {% endfor %}
        </div>
        <div class="swiper-pagination"></div>
      </div>
    {% endfor %}
  </div>
</div>
{% endblock %}

{% block js %}
  <script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>
  <script>
    const swipers = document.querySelectorAll('.swiper');
    swipers.forEach((el) => {
      new Swiper(el, {
        direction: 'horizontal',
        loop: false,
        pagination: {
          el: el.querySelector('.swiper-pagination'),
        },
      });
    });
  </script>
{% endblock %}

base.html 수정

<!-- templates/base.html -->
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Pystagram</title>
  <link rel="stylesheet" href="{% static 'css/bootstrap.css' %}">
    {% block style %}{% endblock %}
</head>
<body>
<nav class="d-flex justify-content-between py-2 px-4 bg-black text-white">
    <div>
        <a href="{% url 'main' %}" class="text-decoration-none text-white">Pystagram</a>
    </div>
    <div class="text-end">
        {% if request.user.is_authenticated %}
          {{ request.user.nickname }}
          <form action="{% url 'logout' %}" method="post" class="d-inline ms-2">
            {% csrf_token %}
            <button class="btn btn-dark btn-sm">로그아웃</button>
          </form>
        {% else %}
          <a class="btn btn-dark btn-sm" href="{% url 'signup' %}">회원가입</a>
          <a class="btn btn-dark btn-sm" href="{% url 'login' %}">로그인</a>
        {% endif %}
    </div>
</nav>
  <div class="container pt-3">
    {% block content %}{% endblock %}
  </div>
  <script src="{% static 'js/bootstrap.bundle.js' %}"></script>
{% block js %}{% endblock %}
</body>
</html>

post/views.py (정참조,역참조 구현까지)

class PostListView(ListView):
    queryset = Post.objects.all().select_related('user').prefetch_related('images')
    template_name = 'post/list.html'
    paginate_by = 5
    ordering = ('-created_at', )

post/models.py relate_name 을 image로 변경  (아닐경우 post.postimage_set을써야됨)

class PostImage(TimestampModel):
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='images')
    image = models.ImageField('이미지', upload_to='post/%Y/%m/%d')

 

static/font-awesome/ folder에 삽입 https://fontawesome.com/

 

base.html 에 추가 (font awesome->docs->getting start에서 복사해서 붙여넣고 내 경로로 설정)

<link rel="stylesheet" href="{% static 'css/bootstrap.css' %}">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link href="{% static 'font-awesome/css/fontawesome.css' %}" rel="stylesheet" />
  <link href="{% static 'font-awesome/css/brands.css' %}" rel="stylesheet" />
  <link href="{% static 'font-awesome/css/solid.css' %}" rel="stylesheet" />

 

list.html 에 추가 (font awesome->docs->getting start에서 복사해서 붙여넣기 <i class="fa-solid fa-user"></i>

{% block content %}
  <div class="row">
    <div class="col-10 offset-1 col-lg-6 offset-lg-3">
      {% for post in object_list %}
      <div class="border-bottom my-4">
        <div class="mb-2">
            <span class="p-2 border rounded-circle me-2">
                <i class="fa-solid fa-user" style="width:16px; padding-left: 1px;"></i>
            </span>
            {{ post.user.nickname }}
        </div>
        <div class="swiper" style="max-height: 500px;">
        <div class="border-1 swiper-wrapper">
        {% for post_image in post.images.all %}
          <div class="swiper-slide">
            <img class="img-fluid post-image" src="{{ post_image.image.url }}" alt="">
          </div>
        {% endfor %}
        </div>
        <div class="swiper-pagination"></div>
        </div>
{#          TODO: LIKE#}
        <div class="my-2">
            {{ post.content | linebreaksbr}}
        </div>
      {% endfor %}
      </div>


    </div>
  </div>
{% endblock %}

 

4️⃣ 실무 팁 🔥

  • swiper 클래스를 여러 개 사용할 경우 각각 초기화가 필요함 → Swiper instance를 각각 생성하는 루프 필요
  • 이미지 경로는 MEDIA_URL 설정과 모델의 ImageField 설정이 잘 연결되어 있어야 정상 출력됨
  • swiper-bundle.min.css/js는 CDN으로도 가능하지만, 자체 호스팅을 고려하면 성능 및 버전 관리에 유리함
💬 여러 슬라이더를 개별로 초기화하려면:

{% for post in object_list %}
  <script>
    new Swiper('#swiper-{{ post.id }}', { pagination: { el: '#pagination-{{ post.id }}' } });
  </script>
{% endfor %}

✅ 기대 효과

  • 사용자에게 여러 장의 이미지를 깔끔하게 제공
  • 모바일에서도 스와이프 UX로 사용성 향상
  • 반응형 디자인 + 페이지네이션 도트로 가시성 증가

 

요약

항목 내용

사용 라이브러리 Swiper.js (v11 CDN 사용)
주요 기능 이미지 슬라이더 + pagination UI
템플릿 활용 방식 {% for %} 루프를 통한 반복 렌더링
초기화 방식 .swiper 선택자 기반 JS 초기화
실무 확장 포인트 각 슬라이더 ID별 개별 초기화 고려 필요