🧩

020 디테일페이지

 
이번에는 디테일 페이지를 만들어 보도록 하겠습니다. 디테일 페이지는 목록이 나와있는 페이지에서 버블 버튼을 누르게 되면 디테일 페이지에 들어가서 해당 게시글만 자세하게 볼 수 있습니다.
notion imagenotion image
notion imagenotion image
 
글이 많으면 디테일 페이지가 많아집니다. 하나하나 만드는 것이 아니라 하나의 html 파일을 만들어서 재사용할 것입니다.
 
detail 페이지는 post_list.html의 축소판입니다. 필요한 기능 배치만 따로 한다고 보시면 됩니다. Ajax 통신을 하는 JavaScript파일도 새로운 파일로 만들도록 하겠습니다.
 
이제 버블 버튼을 누르면 디테일 페이지로 이동하는 기능을 작성하도록 하겠습니다. 개발자 도구를 이용해 버블 버튼이 어떤 클래스를 가지고 있는지 확인합니다.
notion imagenotion image
 
해당 클래스를 post_list.html 파일에서 검색합니다. div.sprite_bubble_icon 태그를 a태그로 감싸도록 하겠습니다. 해당 포스트의 detail 페이지로 이동하는 링크로 만드는 것입니다.
 
파일명 : post/templates/post/post_list.html
<a href="{% url 'post:post_detail' post.pk %}"> <div class="sprite_bubble_icon"></div> </a> <!-- <div class="sprite_share_icon" data-name="share"></div> -->
 
공유 아이콘인 div.sprite_share_icon 태그는 사용하지 않을 것이므로 주석처리 해줍니다.
post/urls.py에 detail 페이지로 가는 URL을 추가하겠습니다.
 
파일명 : post/urls.py
urlpatterns = [ ... path('<int:pk>/', post_detail, name='post_detail'), ]
 
post_detail이라는 뷰 함수를 views.py에다가 정의를 하도록 하겠습니다.
 
파일명 : post/views.py
def post_detail(request, pk): post = get_object_or_404(Post, pk=pk) comment_form = CommentForm() return render(request, 'post/post_detail.html', { 'comment_form': comment_form, 'post': post, })
 
post/templates/post/post_detail.html을 만들도록 하겠습니다.
 
파일명 : post/templates/post/post_detail.html
{% extends "post/layout.html" %} {% load static %} {% load post_extras %} <!--contents부분에 '#'이용--> {% block head %} <link rel="stylesheet" href="{% static 'css/style.css' %}"> <link rel="stylesheet" href="{% static 'css/detail-page.css' %}"> {% endblock %} {% block content %} <div id="main_container"> <section class="b_inner"> <div class="contents_box"> <article class="contents cont01"> <div class="img_section"> <div class="trans_inner"> <div><img src="{{ post.photo.url }}" alt=""></div> </div> </div> <div class="detail--right_box"> <header class="top"> <div class="user_container"> <div class="profile_img"> {% if post.author.profile.picture %}<!--사용자가 이미지가 없을 경우도 있기 때문에--> <img src="{{ post.author.profile.picture.url }}" alt=""> {% else %} <img src="{% static 'imgs/thumb.jpeg' %}" alt=""> {% endif%} </div> <div class="user_name"> <div class="nick_name">{{ post.author.profile.nickname }} {{ post.id }}</div> <div class="country">Seoul, South Korea</div> </div> </div> <div class="sprite_more_icon" data-name="more"> <!--바로 작동 안할 것입니다. ajax통신을 해야되기 때문에--> <ul class="more_detail"> <li> {% if user.profile in post.author.profile.get_follower %} <input type="submit" class="follow" value="팔로잉" data-name="follow" name="{{ post.author.profile.id }}"> {% else %} <input type="submit" class="follow" value="팔로우" data-name="follow" name="{{ post.author.profile.id }}"> {% endif %} </li> {% if post.author == user %} <li> <a class="post-edit" href="{% url 'post:post_edit' post.pk %}">수정</a> </li> <li> <form class="post-delete-form" action="{% url 'post:post_delete' post.pk %}" method="post"> {% csrf_token %} <input type="submit" class="post-delete" value="삭제"> </form> </li> {% endif %} </ul> </div> </header> <section class="scroll_section" id="comment-list-ajax-post{{post.id}}"> <!--post의 comment를 전부 가져온다. 댓글이 많아지면 늘어나고 적어지면 줄어드는 UI--> {% for comment in post.comment_set.all %} <div class="user_container-detail" id="comment{{ comment.id }}"> <div class="user"><img src="{{ comment.author.profile.picture.url }}" alt="user"></div> <div class="comment"> <span class="user_id">{{ comment.author.profile.nickname }}</span> <span>{{ comment.content }}</span> <span> {% if user == comment.author %} <input type="button" class="del-comment" data-name="comment_delete" value="삭제" name="{{ comment.id }}"> {% endif %} </span> <div class="time">{{ comment.created_at|timesince }}</div> </div> </div> {% endfor %} </section> <div class="bottom_icons"> <div class="left_icons"> <div class="heart_btn"> {% if user in post.like_user_set.all %} <div class="sprite_heart_icon_outline on" name="{{ post.id }}" data-name="heartbeat"></div> {% else %} <div class="sprite_heart_icon_outline" name="{{ post.id }}" data-name="heartbeat"></div> {% endif %} </div> <div> <div class="sprite_bubble_icon"></div> </div> <!-- <div> <div class="sprite_share_icon" data-name="share"></div> </div> --> </div> <div class="right_icon"> {% if user in post.bookmark_user_set.all%} <div class="sprite_bookmark_outline on" name="{{ post.id }}" data-name="bookmark"></div> {% else %} <div class="sprite_bookmark_outline" name="{{ post.id }}" data-name="bookmark"></div> {% endif %} </div> </div> <div class="count_likes"> <span id="like-count-{{ post.id }}" class="count">좋아요{{ post.like_count }}개</span> <span id="bookmark-count-{{ post.id }}" >북마크{{ post.bookmark_count }}개</span> </div> <div class="timer">{{ post.created_at|timesince }}</div> <div class="content">{{ post|add_link|safe|linebreaksbr }}</div> <div class="comment_field" id="add-comment-post{{post.id}}"> {% if user.is_authenticated %} {{ comment_form }} <div class="upload_btn m_text" name="{{post.id}}" data-name="comment">게시</div> {% else %} {{ comment_form }} <div class="upload_btn m_text" name="{{post.id}}" data-name="comment" onclick="alert('로그인해주세요')">게시</div> {% endif %} </div> </div> </article> </div> </section> </div> <div class="del_pop"> <div class="btn_box"> <div class="del">삭제</div> <div class="cancel">취소</div> </div> </div> {% include "post/script_ajax_detail.html" %}<!--팔로우 팔로잉 기능 ajax 작동 파일 불러오기--> {% endblock %}
 
post/templates/post/script_ajax_detail.html 파일을 만들도록 하겠습니다.
notion imagenotion image
 
script_ajax_detail.html을 다음과 같이 작성합니다. 대부분 script_ajax.html과 내용이 같습니다.
 
파일명 : post/templates/post/script_ajax_detail.html
<script type="text/javascript"> (function(){ const delegation = document.querySelector('.contents_box') function delegationFunc(e) { let elem = e.target; while (!elem.getAttribute('data-name')){ elem = elem.parentNode; if (elem.nodeName === 'BODY'){ elem = null; return; } } if (elem.matches('[data-name="follow"]')){ console.log('팔로우'); var pk = elem.getAttribute('name') $.ajax({ type: "POST", url: "{% url 'accounts:follow' %}", data: { 'pk': pk, 'csrfmiddlewaretoken': '{{ csrf_token }}', }, dataType: "json", success: function(response){ // alert('성공!') if(response.status){ document.querySelector('input.follow[name=\''+pk+'\']').value="팔로잉"; }else{ document.querySelector('input.follow[name=\''+pk+'\']').value="팔로우"; } }, error: function(request, status, error){ alert("code:"+request.status+"\n"+"message:"+request.responseText+"\n"+"error:"+error); }, }) }else if (elem.matches('[data-name="heartbeat"]')){ var pk = elem.getAttribute('name') $.ajax({ type: "POST", url: "{% url 'post:post_like' %}", data: { 'pk': pk, 'csrfmiddlewaretoken': '{{ csrf_token }}', }, dataType: "json", success: function(response){ // alert('성공!') var likeCount = document.querySelector('#like-count-'+pk); likeCount.innerHTML = '좋아요' + response.like_count + '개'; }, error: function(request, status, error){ alert("code:"+request.status+"\n"+"message:"+request.responseText+"\n"+"error:"+error); }, }) }else if (elem.matches('[data-name="bookmark"]')){ var pk = elem.getAttribute('name') $.ajax({ type: "POST", url: "{% url 'post:post_bookmark' %}", data: { 'pk': pk, 'csrfmiddlewaretoken': '{{ csrf_token }}', }, dataType: "json", success: function (response){ // alert('성공!'); var bookmarkCount = document.querySelector('#bookmark-count-'+pk); bookmarkCount.innerHTML = '북마크' + response.bookmark_count + '개'; }, error: function(request, status, error){ alert("code:"+request.status+"\n"+"message:"+request.responseText+"\n"+"error:"+error); }, }) } elem.classList.toggle('on'); } delegation.addEventListener('click', delegationFunc); })(); </script>
 
메인 페이지에서처럼 팔로우, 수정, 삭제, 좋아요, 북마크가 잘 작동합니다. 이제 댓글 기능만 추가하면 됩니다. detail 페이지에서 댓글은 메인 페이지에서의 댓글과 약간의 차이점이 있습니다. 댓글 작성자의 프로필 사진이 보여야 합니다.
 
comment_new_ajax.html와 똑같은 위치에 detail 페이지의 댓글 템플릿으로 comment_new_detail_ajax.html을 만듭니다. 댓글 작성자의 프로필 사진 부분만 다릅니다.
 
파일명 : post/templates/post/comment_new_detail_ajax.html
<div class="user_container-detail" id="comment{{ comment.id }}"> <div class="user"><img src="{{ comment.author.profile.picture.url }}" alt="user"></div> <div class="comment"> <span class="user_id">{{ comment.author.profile.nickname }}</span> <span>{{ comment.content }}</span> <span> {% if user == comment.author %} <input type="button" class="del-comment" data-name="comment_delete" value="삭제" name="{{ comment.id }}"> {% endif %} </span> <div class="time">{{ comment.created_at|timesince }}</div> </div> </div>
 
이제 urls.py에 detail 페이지의 댓글 작성 요청 URL을 추가합니다.
 
파일명 : post/urls.py
urlpatterns = [ ... path('comment_detail/new/', comment_new_detail, name='comment_new_detail'), ]
 
views.pycomment_new_detail 뷰 함수를 추가합니다.
 
파일명 : post/views.py
@login_required def comment_new_detail(request): pk = request.POST.get('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 render(request, 'post/comment_new_detail_ajax.html', { 'comment': comment, })
 
Ajax로 comment_detail/new/로 요청을 보내면 comment_new_detail_ajax.html을 응답합니다. 이제 댓글 게시 버튼을 누르면 Ajax 요청을 보내도록 script_ajax_detail.html을 수정합니다.
 
파일명 : post/templates/post/script_ajax_detail.html
<script type="text/javascript"> (function(){ const delegation = document.querySelector('.contents_box') function delegationFunc(e) { ... if (elem.matches('[data-name="follow"]')){ ... }else if (elem.matches('[data-name="heartbeat"]')){ ... }else if (elem.matches('[data-name="bookmark"]')){ ... }else if (elem.matches('[data-name="comment"]')){ var pk = elem.getAttribute('name'); var content = document.querySelector('#add-comment-post'+pk+'>input[type=text]').value; if(content.length > 140) { alert("댓글은 최대 140자 입력 가능합니다. 현재 글자수 :"+content.length); } if(content.length == 0) { alert("한글자 이상 입력해 주세요. 현재 글자수 :"+content.length); } $.ajax({ type: "POST", url: "{% url 'post:comment_new_detail' %}", data: { 'pk':pk, 'content': content, 'csrfmiddlewaretoken': '{{ csrf_token}}', }, dataType: "html", success: function(data, textStatus, jqXHR){ document.querySelector("#comment-list-ajax-post"+pk).insertAdjacentHTML("afterbegin", data); }, error: function(reqeust, status, error){ alert("code:"+request.status+"\n"+"message:"+request.responseText+"\n"+"error:"+error); }, }); }else if(elem.matches('[data-name="comment_delete"]')){ var pk = elem.getAttribute('name'); $.ajax({ type: "POST", url: "{% url 'post:comment_delete' %}", data: { 'pk': pk, 'csrfmiddlewaretoken': '{{ csrf_token }}', }, dataType: "json", success: function(response){ if(response.status){ document.querySelector('#comment'+pk).remove(); } }, error: function(reqeust, status, error){ alert("code:"+request.status+"\n"+"message:"+request.responseText+"\n"+"error:"+error); }, }) {% endcomment %} } elem.classList.toggle('on'); } delegation.addEventListener('click', delegationFunc); })(); </script>
 
notion imagenotion image
 
이제 댓글 작성과 댓글 삭제가 잘 작동합니다.