장고 신고 기능 만들기
사용자가 부적절한 글이나 댓글을 신고할 수 있도록 하는 기능은 포럼 운영에 매우 중요하다. 이를 통해 사용자들은 안전한 커뮤니케이션 환경을 느낄 수 있고, 운영자는 문제를 조기에 인지하여 대처할 수 있다.
신고 모델 설계
사용자가 신고한 글(Post)이나 댓글(Comment)을 저장할 수 있는 Report 모델을 만들었다. 이 모델에는 신고한 사용자, 신고 대상의 타입(글 또는 댓글), 신고 사유, 상태 등을 저장할 수 있도록 했다. 특히, 신고된 글이나 댓글을 공통으로 처리할 수 있도록 GenericForeignKey를 활용하기로 했다.
from django.db import models
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
class Report(models.Model):
REPORT_STATUS_CHOICES = [
('pending', 'Pending'),
('reviewed', 'Reviewed'),
('resolved', 'Resolved'),
('rejected', 'Rejected'),
]
user = models.ForeignKey(User, on_delete=models.CASCADE) # 신고한 사용자
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) # 신고된 객체의 타입 (글 또는 댓글)
object_id = models.PositiveIntegerField() # 신고된 객체의 ID
content_object = GenericForeignKey('content_type', 'object_id') # 신고된 객체 (글 또는 댓글)
reason = models.TextField() # 신고 사유
status = models.CharField(max_length=10, choices=REPORT_STATUS_CHOICES, default='pending') # 신고 상태
created_at = models.DateTimeField(auto_now_add=True) # 신고 날짜
# 관리용 필드
is_resolved = models.BooleanField(default=False) # 관리자가 신고를 처리했는지 여부
resolved_at = models.DateTimeField(null=True, blank=True) # 신고가 처리된 시간
resolved_by = models.ForeignKey(User, null=True, blank=True, related_name='reports_resolved', on_delete=models.SET_NULL) # 처리한 관리자
def __str__(self):
return f'{self.user.username} - {self.get_status_display()}'
여기서 **GenericForeignKey**를 사용한 이유는 다수의 객체를 하나의 모델로 처리할 수 있기 때문이다. 예를 들어, `Post`와 `Comment` 같은 서로 다른 모델을 신고할 때, 각 모델을 위한 별도의 필드를 만들 필요 없이, 하나의 `Report` 모델에서 `content_type`과 `object_id`를 사용하여 신고할 수 있다. 이로 인해 코드가 훨씬 간결해지고 중복을 피할 수 있었다.
만약 각 모델에 대해 별도의 `ForeignKey`를 사용한다면 다음과 같은 문제들이 발생할 수 있다. `post`와 `comment` 필드를 모두 포함시키면, 하나는 항상 `null`이 되어야 하므로 이로 인해 복잡한 조건 처리가 필요해진다. 또한, 새로운 신고 대상 모델이 추가될 경우, 기존의 `Report` 모델에 필드를 추가해야 하고 데이터베이스 마이그레이션도 필요하다. 이렇게 되면 유지보수가 어려워질 수 있다.
is_resolved와 resolved_at, resolved_by를 추가했다. 24시간 내 처리되어야 하는데 여러 사람들이 이 필드들을 이용하면 신고들이 효율적으로 관리될 것 같다.
신고 뷰 구현
신고 기능의 뷰는 Django의 FormView를 활용하여 작성했다. 사용자가 신고 버튼을 클릭하면 신고할 대상이 글인지 댓글인지에 따라 적절한 폼이 표시되도록 설계했다. 뷰 클래스에서 dispatch 메서드를 오버라이드하여 신고할 객체를 동적으로 가져오는 로직을 구현했다.
from django.views.generic.edit import FormView
from django.contrib import messages
from django.shortcuts import get_object_or_404
from .forms import ReportForm
from django.contrib.contenttypes.models import ContentType
class ReportView(FormView):
template_name = 'report_form.html'
form_class = ReportForm
def dispatch(self, request, *args, **kwargs):
# 신고할 객체의 모델과 ID를 기반으로 대상 가져오기
model = ContentType.objects.get(app_label='posts', model=self.kwargs['model']).model_class()
self.content_object = get_object_or_404(model, id=self.kwargs['object_id'])
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form):
report = form.save(commit=False)
report.user = self.request.user
report.content_object = self.content_object # 신고 대상 객체 설정
report.save()
messages.success(self.request, '신고가 접수되었습니다.')
return super().form_valid(form)
def get_success_url(self):
# 신고된 객체가 글인지 댓글인지에 따라 리다이렉트 URL 결정
if hasattr(self.content_object, 'post'):
return reverse('post_detail', kwargs={'post_id': self.content_object.post.id})
else:
return reverse('post_detail', kwargs={'post_id': self.content_object.id})
신고가 성공적으로 처리되면 사용자는 신고가 접수되었다는 메시지를 확인할 수 있도록 했다.
신고 폼 템플릿
신고 폼을 위한 템플릿 코드는 다음과 같다. 사용자가 신고 사유를 입력할 수 있는 간단한 양식을 제공한다.
<!-- report_form.html -->
<h2>신고하기</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">신고 제출</button>
</form>
<a href="{% url 'my_reports' %}">내 신고 내역 보기</a>
신고 상태를 확인할 수 있는 페이지도 작성했다. 사용자가 자신의 신고 내역을 쉽게 확인할 수 있도록 하고, 신고 대상의 종류에 따라 링크를 다르게 표시하도록 했다.
<h2>내 신고 목록</h2>
<table>
<tr>
<th>신고 대상</th>
<th>사유</th>
<th>상태</th>
<th>신고 날짜</th>
</tr>
{% for report in reports %}
<tr>
<td>
{% if report.content_type.model == 'post' %}
<a href="{% url 'post_detail' report.object_id %}">{{ report.content_object.title }}</a>
{% elif report.content_type.model == 'comment' %}
<a href="{% url 'post_detail' report.content_object.post.id %}">댓글 보기</a>
{% endif %}
</td>
<td>{{ report.reason }}</td>
<td>{{ report.get_status_display }}</td>
<td>{{ report.created_at }}</td>
</tr>
{% endfor %}
</table>
이렇게 하면 사용자는 자신이 신고한 신고들과 결과를 쉽게 확인할 수 있을 것이다.
신고 URL 패턴
신고 요청을 처리하는 URL 패턴도 설정했다. model과 object_id를 받아서 모든 신고 요청을 하나의 뷰로 처리할 수 있게 하였다. 이로 인해 URL이 간결해지고 코드가 깔끔해졌다.
from django.urls import path
from .views import ReportView, MyReportsView
urlpatterns = [
path('report/<str:model>/<int:object_id>/', ReportView.as_view(), name='report'),
path('my-reports/', MyReportsView.as_view(), name='my_reports'),
]
신고 기능은 필수다. 신고 기능이 없다면 앱 스토어에 앱을 배포할 수 없다. 또한 Django의 강력한 기능인 GenericForeignKey와 contenttypes를 잘 활용할 수 있었던 것 같다. 이러한 설계를 통해 코드의 간결함과 유지보수의 용이성을 동시에 확보할 수 있었다. 특히, 다양한 유형의 객체를 하나의 필드에서 처리하는 방식이 매우 유용하다는 것을 깨달았다.