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별 개별 초기화 고려 필요 |