처음엔 장고에 익숙하지 않아서 우왕좌왕했는데 이제 익숙해지니 작업의 순서를 기억하며 작업하니 쉽게 느껴진다. 장고에서 작업할 때 기억해야 하는 것

  1. 모델
  2. URL
  3. 탬플릿

모델 만들고 모델 사용할 폼 만들고 폼을 사용할 URL을 만들고 URL에서 사용할 뷰를 만들어 준다고 머릿속에 각인해두자! 만들어진 뷰는 탬플릿에서 호출한다. 먼저 밝히는 것은 이 글에서 댓글은 회원에게만 받을 것이다.


MODEL

1
2
3
4
5
6
7
8
class Comment(models.Model):
    author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
    post = models.ForeignKey('board.Post', related_name='comments', on_delete = models.CASCADE)
    text = models.TextField()
    created_date = models.DateTimeField(default=timezone.now)

    def __str__(self):
        return self.text

이전에 공부할때 참고한 글에서는 author를 CharField로 구현하는 것을 봤었는데 그때는 잘 몰라서 그대로 따라했지만 두가지 문제가 있었다.

  • 작성자의 아바타를 불러오는게 복잡함
  • 닉네임이 변경됐을때 조치하기가 어려움

이젠 어느정도 흐름도 알게되어 User의 ForeignKey로 사용하기로 했다. 물론 위 모델을 만드신 분께선 비회원의 댓글을 만들기 위해서 사용한 것으로 생각된다.


FROM

만약 비회원이라면 닉네임, 이메일, 패스워드, 패스워드 확인, 텍스트 필드 이렇게 5가지의 폼이 필요할 것이지만 필자는 회원에게만 댓글 작성의 권한을 주므로 텍스트 필드만 사용하면 된다.

1
2
3
4
5
6
7
8
9
10
11
class CommentForm(forms.ModelForm):
    class Meta:
        model = Comment
        fields = ('text',)

        widgets={
            "text":forms.Textarea(attrs={'placeholder':'배려와 매너가 밝은 커뮤니티를 만듭니다.','class':'form-control','rows':5}),
        }
        labels={
            "text":""
        }

처음 장고를 접할때 자동으로 생성되는 레이블을 지우는 법과 클래스를 삽입하는 방법을 잘 몰랐는데 위와같이 구현하면 된다. 필자는 부트스트랩을 사용하므로 form-control 클래스를 추가했다.


URL/VIEW

먼저 댓글이라는 것은 글 안에서 작성할 수 있게 되어있다. 따라서 우리는 URL을 별도로 만들 필요가 없이 글 본문을 활용하면 된다.

1
path('/posts/<int:pk>', views.post_detail, name='post_detail'),
1
2
3
4
5
6
7
8
9
10
11
12
13
def post_detail(request, pk):
    post = get_object_or_404(Post, pk=pk)
    if request.method == "POST":
        form = CommentForm(request.POST)
        if form.is_valid():
            comment = form.save(commit=False)
            comment.author = request.user
            comment.post = post
            comment.save()
            return redirect('post_detail', pk=pk)
    else:
        form = CommentForm()
    return render(request, 'board/post_detail.html', {'post': post, 'form': form})

자신이 사용하던 나머지 부분은 그대로 두고 추가해 줄 부분은 3행 if 부터 else까지다. POST 방식으로 요청(댓글을 작성)했으면 댓글을 추가해주고 보던 글로 리디렉션을 해주고 아니면 코맨트폼을 불러와서 랜더링을 해주면 된다.

회원에게만 받을거라고 했는데? 이 부분은 탬플릿에서 구현할 것이다. 일부 개발자들이 이는 매우 좋지않은 습관이라고 했지만… 이건 개인 프로젝트이므로;


TEMPLATE

나머지 부분은 그대로두고 댓글을 작성할 부분(컨텐츠 하단)에 해당 태그를 추가하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div id="comment">
    {% for comment in post.comments.all %}
    <div class="comment-list">
        <a href="#">{{ comment.author }}</a>
        <small>{{ comment.created_date }}</small>
        <p>{{ comment.text|linebreaks }}</p>
    </div>
    {% empty %}
    <div class="comment-list">
        <p>작성된 댓글이 없습니다!</p>
    </div>
    {% endfor %}
</div>

{% if request.user.is_active %}
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit" class="btn btn-primary">댓글 작성</button>
</form>
{% else %}
<div class="alert alert-warning">로그인된 사용자만 댓글을 작성할 수 있습니다.</div>
{% endif %}

탬플릿에선 request.user.is_active를 통해서 간단하게 로그인된 사용자인지 판단할 수 있다.

대체 이미지

이상적인 포스트의 삭제 방법에 대해서는 잘 모르겠지만 우선 정상적으로 동작하는데 초점을 맞추었다. URL을 요청하면 적절한 작업을 해주는 뷰를 호출할 수 있으므로 이 방법을 토대로 작업하였다.


URL

1
path('posts/<int:pk>/comment/<int:cpk>/remove', views.comment_remove, name='comment_remove'),


댓글 삭제

우선 절대로 중복될 수 없고 사용자가 쉽게 짐작할 수 없게 URL을 만들기 위해서 포스트의 pk와 코멘트의 pk인 cpk를 받아오도록 하였다. 하지만 댓글 자체도 pk는 중복되지 않으므로 굳이 이렇게 길게 할 필요는 없겠다.


VIEW

1
2
3
4
5
6
7
8
def comment_remove(request, pk, cpk):
    post = get_object_or_404(Post, pk=pk)
    comment = Comment.objects.get(pk=cpk)
    if not comment.author == request.user:
        return redirect('post_detail', post.author, post.url)
    else:
        comment.delete()
        return redirect('post_detail', post.author, post.url)

댓글 삭제라는 버튼은 댓글 작성자에게만 표출할 것이다. 하지만 위 URL을 이용하면 타인의 댓글을 손댈 수 있게된다. 댓글 삭제를 요청했을 때 작성자인지 판단을하고 작성자인 경우 삭제후 리디렉션, 아니면 그냥 리디렉션을 해준다.


TEMPLATE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div id="comment">
    {% for comment in post.comments.all %}
    <div class="comment-list">
        <a href="#">{{ comment.author }}</a>
        <small>{{ comment.created_date }}</small>
        {% if comment.author == request.user %}<a href="{% url 'comment_remove' pk=post.pk cpk=comment.pk%}">삭제</a>{% endif %}
        <p>{{ comment.text|linebreaks }}</p>
    </div>
    {% empty %}
    <div class="comment-list">
        <p>작성된 댓글이 없습니다!</p>
    </div>
    {% endfor %}
</div>

지난번에 작업했던 댓글에서 수정된 부분은 6행의 삭제 부분이다. 댓글 작성자인 경우 삭제 링크를 제공한다. 링크를 감추는 방법에 대해서는 좀 더 고민을 해봐야 할 듯하다.

WRITTEN BY

배진오

하고싶은 건 다 하면서 사는게 목표
im@baejino.com

comments powered by Disqus