댓글 삭제 기능 추가하기
Posted on 2021-12-29 by GKSRUDTN99
Django로 웹사이트 만들기
Django
Comment
1. 댓글 삭제 기능을 위한 테스트 코드 작성하기
delete 버튼 모양 구상하기
- 댓글의 delete 버튼도 자신이 작성한 댓글에만 나타나야 한다.
- delete 버튼을 클릭했을 때는, 바로 삭제되는 것 보다 정말로 삭제하고 싶은지 물어보는 과정이 필요하다.
- 위 상황에 맞게 테스트코드를 작성한다.
def test_delete_comment(self):
# Trump로 로그인하여 댓글을 작성한다.
comment_by_trump = Comment.objects.create(
post=self.post_001,
author=self.user_trump,
content='트럼프의 댓글입니다.'
)
# setUp()에서 obama 계정으로 댓글을 하나 만들었으니, 총 댓글은 2개이며 post_001의 댓글 수도 2개이다.
self.assertEqual(Comment.objects.count(), 2)
self.assertEqual(self.post_001.comment_set.count(), 2)
# 로그인하지 않은 상태
response = self.client.get(self.post_001.get_absolute_url())
self.assertEqual(response.status_code, 200)
soup = BeautifulSoup(response.content, 'html.parser')
# 로그인하지 않은 상태에서는 삭제 버튼이 보이면 안된다.
# 버튼의 id는 'comment-pk-delete-btn'으로 한다.
comment_area = soup.find('div', id='comment-area')
self.assertFalse(comment_area.find('a', id='comment-1-delete-btn'))
self.assertFalse(comment_area.find('a', id='comment-2-delete-btn'))
# trump로 로그인한 상태
self.client.login(username='trump', password='somepassword')
response = self.client.get(self.post_001.get_absolute_url())
self.assertEqual(response.status_code, 200)
soup = BeautifulSoup(response.content, 'html.parser')
# trump로 로그인한 상태이므로 obama가 작성한 댓글에 대한 삭제 버튼은 보이지 않아야한다.
comment_area = soup.find('div', id='comment-area')
self.assertFalse(comment_area.find('a', id='comment-1-delete-btn'))
# 반면 pk=2에 대한 삭제 버튼은 있어야 한다.
comment_002_delete_modal_btn = comment_area.find(
'a', id='comment-2-delete-modal-btn'
)
self.assertIn('delete', comment_002_delete_modal_btn)
self.assertEqual(
comment_002_delete_modal_btn.attrs['data-target'],
'#deleteCommentModal-2'
)
# 삭제를 할지 물어보는 모달에는 'Are You Sure?'이라는 문구와 함께 delete 버튼이 있어야 한다.
# 이 버튼의 링크는 '/blog/delete_comment/삭제할 comment의 pk'로 되어 있어야 한다.
delete_comment_modal_002 = soup.find('div', id='deleteCommentModal-2')
self.assertIn('Are You Sure?', delete_comment_modal_002.text)
really_delete_btn_002 = delete_comment_modal_002.find('a')
self.assertIn('Delete', really_delete_btn_002.text)
self.assertEqual(
really_delete_btn_002.attrs['href'],
'/blog/delete_comment/2/'
)
# delete 버튼을 클릭하면 댓글이 삭제되고 post_001의 페이지로 리다이렉트된다.
# 해당 페이지에는 더이상 트럼프의 댓글이 존재하지 않고, 댓글의 개수도 1로 줄어든다.
response = self.client.get('/blog/delete_comment/2/', follow=True)
self.assertEqual(response.status_code, 200)
soup = BeautifulSoup(response.content, 'html.parser')
self.assertIn(self.post_001.title, soup.title.text)
comment_area = soup.find('div', id='comment-area')
self.assertNotIn('트럼프의 댓글입니다.', comment_area.text)
self.assertEqual(Comment.objects.count(), 1)
self.assertEqual(self.post_001.comment_set.count(), 1)
- 현재 상태로 테스트를 진행해보면 'comment를 삭제할지 물어보는 모달을 나타내기 위한 버튼이 없다'는 오류가 발생한다.
2. 댓글 삭제 버튼과 모달 만들기
post_detail.html 수정하기
- 앞의 오류를 해결하기 위해 post_detail.html을 다음과 같이 수정한다.
<!-- /blog/templates/post_detail.html -->
<!-- ... 생략 ... -->
{% if user.is_authenticated and comment.author == user %}
<div class="float-end">
<a role="button" class="btn btn-sm btn-info float-end" id="comment-{{ comment.pk }}-update-btn" href="/blog/update_comment/{{ comment.pk }}/">
edit
</a>
<a role="button" class="btn btn-sm btn-danger" id="comment-{{ comment.pk }}-delete-modal-btn" href="#" data-toggle="modal" data-target="#deleteCommentModal-{{ comment.pk }}">
delete
</a>
</div>
<!-- Modal -->
<div class="modal fade" id="deleteCommentModal-{{ comment.pk }}" tabindex="-1" role="dialog" aria-labelledby="deleteCommentModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="deleteModalLabel">Are You Sure?</h5>
<button type="button" class="btn-close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<del>{{ comment | linebreaks }}</del>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<a role="button" class="btn btn-danger" href="/blog/delete_comment/{{ comment.pk }}/">Delete</a>
</div>
</div>
</div>
</div>
{% endif %}
-
모달을 만드는데 사용된 속성들
- data-toggle="modal": modal 계층에 data-target을 통해 지정된 부분을 보이게 만들 것이다.
- data-target="#deleteCommentModal": #(id Selector)+deleteCommentModal, 즉 id가 deleteCommentModal인 태그를 지정한다.
- data-dismiss="modal": modal 계층에 보이고 있는 부분들을 없앤다.
- tabindex="-1": 탭을 눌러서 태그들을 순회할 때 포함되지 않도록 한다.
- aria-: 장애가 있는 사용자들이 사용하는 스크린 리더등을 사용할 때 접근성을 높이는 속성들이다.
<del>
: 취소선을 추가하는 태그이다.
-
다시 테스트를 수행해보면 '페이지를 찾을 수 없음'을 의미하는 404오류가 발생한다.
- 아직 /blog/delete_comment/{{ comment.pk }}에 해당하는 url처리를 추가하지 않았기 때문이다.
urls.py와 views.py 수정하기
- urls.py에 URL을 추가하고, views.py에 FBV 스타일로 뷰를 추가한다.
#/blog/urls.py
from django.contrib import admin
from django.urls import path
from . import views
urlpatterns = [
path('delete_comment/<int:pk>/', views.delete_comment),
path('update_comment/<int:pk>/', views.CommentUpdate.as_view()),
path('update_post/<int:pk>/', views.PostUpdate.as_view()),
path('create_post/', views.PostCreate.as_view()),
path('tag/<str:slug>/', views.tag_page),
path('category/<str:slug>/', views.category_page),
path('<int:pk>/new_comment/', views.new_comment),
path('<int:pk>/', views.PostDetail.as_view()),
path('', views.PostList.as_view()),
]
# /blog/views.py
# (...생략...)
def delete_comment(request, pk):
comment = get_object_or_404(Comment, pk=pk)
post = comment.post
if request.user.is_authenticated and request.user == comment.author:
comment.delete()
return redirect(post.get_absolute_url())
else:
raise PermissionDenied
- 이제 테스트를 실행해보면 OK가 나온다.
- 사실, 장고에서 기본적으로 제공하는 DeleteView가 있긴 하지만, 해당 View를 사용하면 '정말 사용자가 삭제할 것인지' 확인하는 페이지로 이동한 뒤 삭제가 이루어지는 방식이다.
- 사용자 입장에서는 모달을 통한 삭제가 더 편할 것이라 생각하여 이렇게 구현한 것이다.