장고 사용자 차단 기능 만들기

소요 시간: 10분

오늘은 Django 프로젝트에서 사용자 차단 기능을 만들기 위해 작업을 진행했다. 목표는 간단하다. 사용자가 특정 사용자를 차단했을 때, 그 사용자의 콘텐츠(글, 댓글 등)를 보지 않거나, 메시지 등 상호작용을 차단할 수 있도록 하는 것이다.


차단 정보를 저장할 모델 설계

먼저, 차단 정보를 관리할 모델을 설계했다. 사용자가 다른 사용자를 차단한 정보를 기록할 데이터베이스 테이블이 필요했는데, 이를 위해 새로운 모델을 추가했다. 모델 이름은 Block이고, 여기서 차단하는 사용자(blocker)와 차단된 사용자(blocked)의 정보를 관리할 수 있도록 설계했다.

from django.contrib.auth.models import User
from django.db import models

class Block(models.Model):
    blocker = models.ForeignKey(User, related_name='blocking', on_delete=models.CASCADE)  # 차단하는 사용자
    blocked = models.ForeignKey(User, related_name='blocked', on_delete=models.CASCADE)  # 차단된 사용자

    class Meta:
        unique_together = ('blocker', 'blocked')  # 동일한 사용자를 중복 차단하지 못하도록 설정

    def __str__(self):
        return f"{self.blocker}가 {self.blocked}를 차단함"

이 모델의 핵심은 unique_together 옵션을 설정해, 동일한 사용자를 여러 번 차단하지 않도록 한 것이다. 이를 통해 데이터베이스에 중복 차단 데이터가 쌓이는 것을 방지할 수 있다.


사용자 차단 기능을 위한 뷰 작성

모델을 만들었으니 이제 뷰를 구현할 차례. 차단 기능은 기본적으로 차단하고 싶은 사용자를 선택한 후, 해당 사용자를 차단 목록에 추가하는 방식이다. 이 과정은 두 가지 뷰로 나눴다. 하나는 사용자를 차단하는 뷰, 또 하나는 차단을 해제하는 뷰이다.

먼저, 사용자가 다른 사용자를 차단할 수 있는 뷰인 block_user 함수를 만들었다. 이 함수는 로그인한 사용자가 다른 사용자를 차단 요청했을 때, 그 요청을 처리하는 역할을 한다.

@login_required
def block_user(request, user_id):
    blocked_user = get_object_or_404(User, id=user_id)  # 차단할 사용자 검색

여기서는 get_object_or_404 함수를 사용해 차단할 사용자가 실제로 존재하는지 확인했다. 만약 해당 사용자가 없으면 404 에러를 발생시켜 적절한 오류 처리를 한다.

    block, created = Block.objects.get_or_create(blocker=request.user, blocked=blocked_user)

다음으로, Block 모델에서 get_or_create를 사용했다. 이 부분이 중요한데, 사용자가 이미 해당 유저를 차단한 기록이 있는지 확인한 뒤, 차단된 적이 없으면 새로운 차단 기록을 생성한다. 이때 created라는 플래그를 사용해 새로운 차단이 발생했는지 확인할 수 있다.

    if created:
        messages.success(request, f"{blocked_user.username}를 성공적으로 차단했습니다.")
    else:
        messages.warning(request, f"{blocked_user.username}는 이미 차단된 사용자입니다.")

여기서는 차단이 성공적으로 이루어졌다면 messages.success를 통해 사용자에게 알림을 보내고, 만약 이미 차단된 사용자라면 경고 메시지를 띄우도록 설정했다. 이 메시지를 통해 사용자 경험을 개선할 수 있다.

    return redirect('profile', user_id=user_id)  # 프로필 페이지로 리다이렉션

마지막으로, 차단을 완료한 후에는 해당 사용자의 프로필 페이지로 다시 리다이렉트한다. 이를 통해 사용자는 방금 차단한 사용자 페이지에서 차단 사실을 확인할 수 있게 된다.

차단만 할 수 있는 것이 아니라, 사용자가 차단을 해제할 수도 있어야 한다. 그래서 차단을 해제하는 unblock_user 함수를 추가로 구현했다.

@login_required
def unblock_user(request, user_id):
    blocked_user = get_object_or_404(User, id=user_id)
    block = Block.objects.filter(blocker=request.user, blocked=blocked_user).first()

차단 해제도 기본적인 구조는 비슷하다. 먼저 차단할 사용자를 찾고, 이번에는 Block 모델에서 차단 기록이 있는지 필터링한다. 여기서 first()를 사용해 필터링된 결과가 있으면 첫 번째 차단 기록을 가져오도록 했다.

    if block:
        block.delete()
        messages.success(request, f"{blocked_user.username}의 차단을 해제했습니다.")
    else:
        messages.warning(request, f"{blocked_user.username}는 차단된 사용자가 아닙니다.")

차단 기록이 있으면 해당 차단 기록을 삭제해 차단을 해제하고, 성공 메시지를 띄운다. 만약 차단된 기록이 없을 경우, 차단된 사용자가 아니라는 경고 메시지를 띄워준다. 이 역시 사용자에게 명확한 피드백을 제공하기 위해 꼭 필요한 부분이다.

    return redirect('profile', user_id=user_id)

차단 해제 후에도 동일하게 프로필 페이지로 리다이렉트되도록 설정했다. 차단 기능과 마찬가지로 사용자가 현재 상호작용 중인 페이지로 돌아가게끔 한다.

전체 뷰:

from django.shortcuts import redirect, get_object_or_404
from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from .models import Block

@login_required
def block_user(request, user_id):
    blocked_user = get_object_or_404(User, id=user_id)  # 차단할 사용자 검색
    block, created = Block.objects.get_or_create(blocker=request.user, blocked=blocked_user)
    
    if created:
        messages.success(request, f"{blocked_user.username}를 성공적으로 차단했습니다.")
    else:
        messages.warning(request, f"{blocked_user.username}는 이미 차단된 사용자입니다.")
    
    return redirect('profile', user_id=user_id)  # 프로필 페이지로 리다이렉션
    
@login_required
def unblock_user(request, user_id):
    blocked_user = get_object_or_404(User, id=user_id)
    block = Block.objects.filter(blocker=request.user, blocked=blocked_user).first()
    
    if block:
        block.delete()
        messages.success(request, f"{blocked_user.username}의 차단을 해제했습니다.")
    else:
        messages.warning(request, f"{blocked_user.username}는 차단된 사용자가 아닙니다.")

    return redirect('profile', user_id=user_id)


템플릿에서 차단 버튼 추가

이제 뷰가 준비됐으니, 사용자가 이를 쉽게 사용할 수 있도록 템플릿에 차단 및 차단 해제 버튼을 추가했다. 이 버튼은 사용자 프로필 페이지나 상세 페이지에서 동적으로 표시되도록 설정했다. 사용자가 이미 차단한 경우 해제 버튼을, 그렇지 않은 경우 차단 버튼을 보여준다.

{% if not request.user.blocking.filter(blocked=user).exists %}
    <form method="post" action="{% url 'block_user' user.id %}">
        {% csrf_token %}
        <button type="submit" class="btn btn-danger">차단하기</button>
    </form>
{% else %}
    <form method="post" action="{% url 'unblock_user' user.id %}">
        {% csrf_token %}
        <button type="submit" class="btn btn-warning">차단 해제</button>
    </form>
{% endif %}


차단된 사용자의 콘텐츠 필터링

차단된 사용자의 게시물이나 댓글을 사용자에게 보이지 않도록 하려면, 이들을 필터링하는 기능이 필요하다. 이를 위해 차단된 사용자의 콘텐츠를 제외하는 쿼리를 작성했다.

from .models import Block

def get_filtered_posts(user):
    blocked_users = Block.objects.filter(blocker=user).values_list('blocked', flat=True)
    return Post.objects.exclude(author__in=blocked_users)

이 코드 덕분에 차단된 사용자의 게시물이 현재 로그인한 사용자에게 보이지 않게 된다. 나중에 댓글이나 다른 콘텐츠에도 이 필터링을 적용하면 더 안전하게 차단 기능을 확장할 수 있다.


추가적으로 고려할 사항

이번 작업을 하면서 몇 가지 추가적인 고민거리가 생겼다.

오늘 작업한 기능 덕분에 사용자는 더 이상 원치 않는 사람과의 상호작용을 최소화할 수 있게 됐다. 차단 기능은 간단하지만, 사용자 경험을 크게 향상시키는 중요한 기능임을 다시 한 번 느꼈다.

장고 리스트