4. 좋아요(추천) 기능 (M:N 관계) 구현

[STEP 4] 좋아요(추천) 기능 (M:N 관계)

이번 단계에서는 사용자가 게시글에 '좋아요(추천)'를 누를 수 있는 기능을 버튼을 한 번 누르면 추천이 되고, 다시 누르면 취소되는 토글(Toggle) 방식으로  구현합니다.

이 기능의 핵심은 데이터베이스의 M:N (다대다) 관계를 이해하는 것입니다.

  • 1:N 관계 (댓글): 댓글은 오직 하나의 게시글에만 속할 수 있습니다.
  • M:N 관계 (좋아요): 한 명의 유저가 여러 글에 좋아요를 누를 수 있고, 하나의 글도 여러 유저에게 좋아요를 받을 수 있습니다.

자바(Spring/MyBatis)에서는 이를 구현하려면 중간 테이블(Mapping Table)을 직접 만들고, 복잡한 조인 쿼리를 짜야 했지만, Django는 ManyToManyField 한 줄이면 중간 테이블 생성부터 관리까지 알아서 처리해 줍니다.

1. 모델 수정 (boards/models.py)

Post 모델에 좋아요를 누른 사람들의 목록을 저장할 필드를 추가합니다.

boards/models.py 파일을 열고 Post 클래스를 수정하세요.

# ... 기존 imports ...
from django.conf import settings # User 모델 참조용

class Post(models.Model):
    # ... 기존 필드 (author, title, content, image, views 등) ...

    # [추가] 좋아요 (Many-to-Many)
    # User 모델과 다대다 관계를 맺습니다.
    # related_name='like_posts': 나중에 유저 입장에서 "내가 좋아요 누른 글들"을 가져올 때 사용할 이름입니다.
    # (주의: related_name을 안 쓰면 author 필드와 충돌이 날 수 있습니다.)
    likes = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='like_posts', blank=True, verbose_name="좋아요")

    def __str__(self):
        return f"[{self.board.title}] {self.title}"

2. DB 반영 (마이그레이션)

Django가 자동으로 boards_post_likes 라는 중간 테이블을 만들어줍니다.

[Bash]

python manage.py makemigrations
python manage.py migrate

3. URL 설정 (boards/urls.py)

좋아요 요청을 처리할 URL을 만듭니다.

urlpatterns = [
    # ... 기존 url들 ...
    
    # [추가] 좋아요 토글 URL
    path('<str:board_code>/<int:pk>/like/', views.post_like, name='post_like'),
]

4. 로직 작성 (boards/views.py)

View에서는 아주 간단한 로직만 있으면 됩니다. "이 사람이 이미 좋아요 명단에 있나? 있으면 빼고, 없으면 넣자!"

boards/views.py 하단에 함수를 추가하세요.

@login_required
def post_like(request, board_code, pk):
    post = get_object_or_404(Post, pk=pk)
    
    # [로직] 좋아요 토글 (Toggle)
    # filter(id=...): 현재 게시글의 좋아요 목록에 내 아이디가 있는지 확인
    if post.likes.filter(id=request.user.id).exists():
        # 이미 눌렀다면 -> 취소 (remove)
        post.likes.remove(request.user)
    else:
        # 안 눌렀다면 -> 추가 (add)
        post.likes.add(request.user)
        
    # 처리가 끝나면 상세 페이지로 다시 이동
    return redirect('boards:board_detail', board_code=board_code, pk=pk)

5. 아이콘 라이브러리 추가 (templates/base.html)

하트 아이콘(❤️)을 사용하기 위해 Bootstrap Icons 라이브러리를 연결해야 합니다.

(Bootstrap 5부터는 아이콘이 별도로 분리되었습니다.)

templates/base.html의 <head> 태그 안에 아래 링크를 추가해 주세요.

<head>
    <meta charset="UTF-8">
    <title>My Django Board</title>
    
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
    
    </head>

6. 화면 수정 (boards/templates/boards/board_detail.html)

아래 코드를 복사해서 board_detail.html본문 영역 아래, 댓글 영역 위쪽에 붙여넣으세요.

        <div class="text-center my-4">
            {% if user.is_authenticated %}
                <a href="{% url 'boards:post_like' board.code post.pk %}" class="text-decoration-none">
                    
                    {% if user in post.likes.all %}
                        <button type="button" class="btn btn-danger">
                            <i class="bi bi-heart-fill"></i> 좋아요 취소
                            <span class="badge bg-light text-dark ms-1">{{ post.likes.count }}</span>
                        </button>
                    {% else %}
                        <button type="button" class="btn btn-outline-danger">
                            <i class="bi bi-heart"></i> 좋아요
                            <span class="badge bg-danger ms-1">{{ post.likes.count }}</span>
                        </button>
                    {% endif %}
                    
                </a>
            {% else %}
                <button type="button" class="btn btn-outline-secondary" disabled>
                    좋아요 <span class="badge bg-secondary ms-1">{{ post.likes.count }}</span>
                </button>
                <div class="small text-muted mt-1">로그인 후 추천 가능합니다.</div>
            {% endif %}
        </div>
        
        <hr class="my-4">

7. 테스트 (확인 리스트)

  1. 로그인 상태에서 글 상세 페이지로 들어갑니다.
  2. [좋아요] 버튼에 빈 하트 아이콘이 잘 나오나요?
  3. 버튼을 누르면 화면이 새로고침 되면서 [좋아요 취소](빨간색 버튼 + 꽉 찬 하트)로 바뀌고 숫자가 1 올라가는지 확인합니다.
  4. 다시 누르면 취소되고 숫자가 내려가는지 확인합니다.
  5. 다른 아이디로 로그인해서 같은 글을 보면 숫자가 유지되어 있고, 내 기준으로는 아직 안 누른 상태로 나오는지 확인합니다.