개발

점프 투 장고 추가 기능(댓글)

wafla 2024. 11. 7. 00:52

댓글 기능 추가

이번엔 댓글 기능을 구현해보려합니다.

질문에 댓글 기능은 필요 없을 것 같아서 답변에만 댓글 기능을 추가해 보겠습니다.

 

먼저 models.py에서 Comment 모델을 생성해줍니다.

class Comment(models.Model):
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='author_comment')
    answer = models.ForeignKey(Answer, on_delete=models.CASCADE)
    content = models.TextField()
    create_date = models.DateTimeField()
    modify_date = models.DateTimeField(null=True, blank=True)

 

사용자, 답변, 내용, 생성 시간 정도면 충분합니다. 모델을 추가했으면 makemigrations와 migrate 명령어를 통해 실제 데이터베이스를 생성해 주는 것도 잊으면 안 됩니다.

 

forms.py도 작성해줍니다.

class CommentForm(forms.ModelForm):
    class Meta:
        model = Comment
        fields = ['content']
        labels = {
            'content' : '댓글내용',
        }

 

답변 추천 버튼 옆에 댓글을 확인할 수 있는 버튼도 추가해줬습니다.

 

question_detail.html

<div class="my-3">
    <a href="javascript:void(0)" data-uri="{% url 'pybo:answer_vote' answer.id %}"
       class="recommend btn btn-sm btn-outline-secondary">추천
        <span class="badge rounded-pill bg-success">{{answer.voter.count}}</span>
    </a>
    <a href="javascript:void(0)" class="btn btn-sm btn-outline-secondary show-comments" data-answer-id="{{ answer.id }}">
        댓글 <span class="badge rounded-pill bg-secondary">{{ answer.comment_set.count }}</span>
    </a>
    {% if request.user == answer.author %}
    <a href="{% url 'pybo:answer_modify' answer.id %}"
       class="btn btn-sm btn-outline-secondary">수정</a>
    <a href="#" class="delete btn btn-sm btn-outline-secondary"
       data-uri="{% url 'pybo:answer_delete' answer.id %}">삭제</a>
    {% endif %}
</div>

 

바로 밑에 댓글과 댓글 작성 칸을 보여주는 코드도 작성했습니다.

<!-- 댓글 표시 & 댓글 작성 영역 -->
<div id="comments_{{ answer.id }}" class="comment-section mt-3" style="display: none;">
    <!-- 댓글 표시 -->
    {% for comment in answer.comment_set.all %}
    <div class="card my-1">
        <div class="card-body">
            <p class="card-text">{{ comment.content|linebreaks }}</p>
                <small class="text-muted">{{ comment.author.username }} - {{ comment.create_date }} </small>
                {% if request.user == comment.author %}
                <a href="#" class="delete btn btn-sm btn-outline-secondary"
                    data-uri="{% url 'pybo:comment_delete' comment.id sort %}">삭제</a>
                {% endif %}
        </div>
    </div>
    {% empty %}
    <p class="text-muted">아직 댓글이 없습니다.</p>
    {% endfor %}
    <!-- 댓글 작성 -->
        <form action="{% url 'pybo:comment_create' answer.id sort %}" method="post" class="mt-2">
            {% csrf_token %}
            <div class="mb-2">
                <textarea name="content" class="form-control" rows="2" placeholder="댓글을 입력하세요."></textarea>
            </div>
            <button type="submit" class="btn btn-sm btn-primary">댓글 작성</button>
        </form>
    </div>

 

위의 코드는 버튼이 눌렸을 때만 화면에 나타나야합니다. 따라서 display: none;으로 해두고 javascript를 통해 display 값을 변경합시다. 또 댓글을 작성하고 삭제한 뒤 해당 댓글 창이 계속 열려있을 수 있는 코드도 추가했습니다.

<script type='text/javascript'>
    // 댓글 표시 & 댓글 작성 버튼 클릭
    const commentButtons = document.querySelectorAll('.show-comments');
    commentButtons.forEach(button => {
        button.addEventListener('click', function() {
            const answerId = this.getAttribute('data-answer-id');
            const commentsSection = document.getElementById(`comments_${answerId}`);
            if (commentsSection) {
                commentsSection.style.display = commentsSection.style.display === 'none' ? 'block' : 'none';
            }
        });
    });
    // 댓글 작성/삭제 후 해당 답변 댓글 창 열기
    document.addEventListener("DOMContentLoaded", function() {
        const hash = window.location.hash;
        if (hash && hash.startsWith("#answer_")) {
            const answerId = hash.replace("#answer_", "");  // 해시에서 답변 ID 추출
            const commentsSection = document.getElementById(`comments_${answerId}`);
            if (commentsSection) {
                   commentsSection.style.display = "block";
            }
        }
    });
</script>

 

삭제 버튼은

{% url 'pybo:comment_delete' comment.id sort %}

 

댓글 작성은

{% url 'pybo:comment_create' answer.id sort %}

 

이렇게 경로를 설정했습니다. 폼을 제출하고나서 정렬 방식이 바뀌는 것을 방지하기 위해 sort 값도 넘겨줬습니다.

 

urls.py에서 url 경로도 작성해줍니다.

# comment_views.py
path('comment/create/<int:answer_id>/<str:sort>',
     comment_views.comment_create, name='comment_create'),
path('comment/delete/<int:comment_id>/<str:sort>',
     comment_views.comment_delete, name='comment_delete'),

 

마지막으로 동작을 수행하는 함수도 작성해 줍니다. 댓글을 작성한 후 다시 원래 있던 답변으로 돌아올 수 있도록 답변 기능을 구현할 때 배운 앵커 기능을 사용했습니다. 이때 앵커를 의미하는 #answer_{}가 맨 뒤에 있어야 정상적으로 작동했습니다.

 

comment_views.py

from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.shortcuts import render, get_object_or_404, redirect, resolve_url
from django.utils import timezone

from ..forms import CommentForm
from ..models import Answer, Comment

@login_required(login_url='common:login')
def comment_create(request, answer_id, sort):
    answer = get_object_or_404(Answer, pk=answer_id)
    if request.method == "POST":
        form = CommentForm(request.POST)
        if form.is_valid():
            comment = form.save(commit=False)
            comment.author = request.user
            comment.create_date = timezone.now()
            comment.answer = answer
            comment.save()
            return redirect('{}?sort={}#answer_{}'.format(
                resolve_url('pybo:detail', question_id=answer.question.id), sort, answer.id))
    else:
        form = CommentForm()
    context = {'question' : answer.question, 'form' : form}
    return render(request, 'pybo:/question_detail.html', context)

@login_required(login_url='common:login')
def comment_delete(request, comment_id, sort):
    comment = get_object_or_404(Comment, pk=comment_id)
    if request.user != comment.author:
        messages.error(request, '삭제권한이 없습니다')
        return redirect('{}?sort={}#answer_{}'.format(
            resolve_url('pybo:detail', question_id=comment.answer.question.id), sort, comment.answer.id))
    comment.delete()
    return redirect('{}?sort={}#answer_{}'.format(
        resolve_url('pybo:detail', question_id=comment.answer.question.id), sort, comment.answer.id))

 

위의 코드들로 구현된 댓글 기능 사진입니다.

댓글 작성 후
2번 댓글 삭제 후