[점프투장고] 3-16 추가기능 - 답변 페이징과 정렬
기존의 코드를 참고하여 답변 페이징 및 정렬을 완성했습니다.
다만, 실제 사용하다보니 최신순 정렬에서 2페이지 이후의 답변을 추천하거나 댓글을 달면 추천순으로 초기화된 후 1페이지로 바뀌는 문제가 발생했습니다. 이를 고치려고 우선 답변마다 달려있던 앵커들을 지우고 답변 목록 상단에 하나의 앵커(name=answer_start)만을 남겼습니다. (지금 이 사이트의 html 코드를 참고했습니다.) 그러나 이후 어떤 방식으로 구현해야되는지 모르겠어서 질문 남깁니다. 제 분석 및 문제점은 아래와 같습니다.
현재 추천 및 댓글 작동 방식:
1. question_detail.html에서 사용자가 추천 및 댓글 조작
2. 뷰에서는 각자의 컨트롤을 마친 후, pybo:detail로 리다이렉트
제 생각:
1에서, 템플릿 코드를 수정하여 GET 방식으로 추천 생성, 댓글 생성 뷰에 page와 so를 전달해야되는데 어떻게 구현할지 모르겠습니다.
2에서, 장고의 redirect 함수를 이용해 page와 so를 입력해주려면 어떻게 하는지, 가능한지 모르겠습니다.
코드는 아래와 같습니다.
question.html
{% extends 'base.html' %}
{% load pybo_filter %}
{% block content %}
<div class="container my-3">
<!-- 사용자오류 표시 -->
{% if messages %}
<div class="alert alert-danger my-3" role="alert">
{% for message in messages %}
<strong>{{ message.tags }}</strong>
<ul><li>{{ message.message }}</li></ul>
{% endfor %}
</div>
{% endif %}
<!-- 질문 제목 표시 -->
<h2 class="border-bottom py-2">{{ question.subject }}</h2>
<div class="row my-3">
<div class="col-1"> <!-- 추천 영역 -->
<div class="bg-light text-center p-3 border font-weight-bolder mb-1">{{ question.voter.count }}</div>
<a href="#" data-uri="{% url 'pybo:vote_question' question.id %}"
class="recommend btn btn-sm btn-secondary btn-block my-1">추천</a>
</div>
<div class="col-11"> <!-- 질문 영역 -->
<div class="card">
<div class="card-body">
<!-- 질문 내용 표시 -->
<div class="card-text">{{ question.content|mark }}</div>
<!-- 질문 생성&수정일시 표시 -->
<div class="d-flex justify-content-end">
{% if question.modify_date %}
<div class="badge badge-light p-2 text-left mx-3">
<div class="mb-2">modified at</div>
<div>{{ question.modify_date }}</div>
</div>
{% endif %}
<div class="badge badge-light p-2 text-left">
<div class="mb-2">{{ question.author.username }}</div>
<div>{{ question.create_date }}</div>
</div>
</div>
<!-- 질문 수정&삭제 버튼 표시 -->
{% if request.user == question.author %}
<div class="my-3">
<a href="{% url 'pybo:question_modify' question.id %}"
class="btn btn-sm btn-outline-secondary">수정</a>
<a href="#" class="delete btn btn-sm btn-outline-secondary"
data-uri="{% url 'pybo:question_delete' question.id %}">삭제</a>
</div>
{% endif %}
<!-- 질문 댓글 Start -->
{% if question.comment_set.count > 0 %}
<div class="mt-3">
{% for comment in question.comment_set.all %} <!-- 등록한 댓글을 출력 -->
<a name="comment_{{ comment.id }}"></a>
<div class="comment py-2 text-muted"> <!-- 댓글 각각에 comment 스타일 지정 -->
<span style="white-space: pre-line;">{{ comment.content }}</span>
<span
- {{ comment.author }}, {{ comment.create_date }}
{% if comment.modify_date %}
(수정:{{ comment.modify_date }})
{% endif %}
</span>
{% if request.user == comment.author %}
<a href="{% url 'pybo:comment_modify_question' comment.id %}" class="small">수정</a>,
<a href="#" class="small delete" data-uri="{% url 'pybo:comment_delete_question' comment.id %}">삭제</a>
{% endif %}
</div>
{% endfor %}
</div>
{% endif %}
<div>
<a href="{% url 'pybo:comment_create_question' question.id %}"
class="small"><small>댓글 추가 ..</small></a> <!-- 댓글 추가 링크 -->
</div>
<!-- 질문 댓글 End -->
</div>
</div>
</div>
</div>
<!-- 답변 표시 Start -->
<a name="answer_start"></a>
<div class="row justify-content-between border-bottom my-3">
<div class="col-4 py-2">
<h5>{{ answer_set.paginator.count }}개의 답변이 있습니다.</h5>
</div>
<div class="col-2">
<select class="form-control so">
<option value="recommend" {% if so == 'recommend' %}selected{% endif %}>추천순</option>
<option value="recent" {% if so == 'recent' %}selected{% endif %}>최신순</option>
</select>
</div>
</div>
{% for answer in answer_set %}
<div class="row my-3">
<div class="col-1"> <!-- 추천 영역 -->
<div class="bg-light text-center p-3 border font-weight-bolder mb-1">{{ answer.voter.count }}</div>
<a href="#" data-uri="{% url 'pybo:vote_answer' answer.id %}"
class="recommend btn btn-sm btn-secondary btn btn-block my-1">추천</a>
</div>
<div class="col-11"> <!-- 답변 영역 -->
<div class="card">
<div class="card-body">
<!-- 답변 내용 표시 -->
<div class="card-text">{{ answer.content|mark }}</div>
<!-- 답변 일시&저자 표시 -->
<div class="d-flex justify-content-end">
{% if answer.modify_date %}
<div class="badge badge-light p-2 text-left mx-3">
<div class="mb-2">modified at</div>
<div>{{ answer.modify_date }}</div>
</div>
{% endif %}
<div class="badge badge-light p-2 text-left">
<div class="mb-2">{{ answer.author.username }}</div>
<div>{{ answer.create_date }}</div>
</div>
</div>
<!-- 질문 수정&삭제 버튼 표시 -->
{% if request.user == answer.author %}
<div class="my-3">
<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>
</div>
{% endif %}
<!-- 답변 댓글 Start -->
{% if answer.comment_set.count > 0 %}
<div class="mt-3">
{% for comment in answer.comment_set.all %} <!-- 등록한 댓글을 출력 -->
<a name="comment_{{ comment.id }}"></a>
<div class="comment py-2 text-muted"> <!-- 댓글 각각에 comment 스타일 지정 -->
<span style="white-space: pre-line;">{{ comment.content }}</span>
<span
- {{ comment.author }}, {{ comment.create_date }}
{% if comment.modify_date %}
(수정:{{ comment.modify_date }})
{% endif %}
</span>
{% if request.user == coment.author %}
<a href="{% url 'pybo:comment_modify_answer' comment.id %}" class="small">수정</a>,
<a href="#" class="small delete" data-uri="{% url 'pybo:comment_delete_answer' comment.id %}">삭제</a>
{% endif %}
</div>
{% endfor %}
</div>
{% endif %}
<div>
<a href="{% url 'pybo:comment_create_answer' answer.id %}"
class="small"><small>댓글 추가 ..</small></a> <!-- 댓글 추가 링크 -->
</div>
<!-- 답변 댓글 End -->
</div>
</div>
</div>
</div>
{% endfor %}
<!-- 답변 표시 End -->
<!-- 페이징처리 시작 -->
<ul class="pagination justify-content-center">
<!-- 처음페이지 -->
<li class="page-item">
<a class="page-link" data-page="1" href="#">처음</a>
</li>
<!-- 이전페이지 -->
{% if answer_set.has_previous %}
<li class="page-item">
<a class="page-link" data-page="{{ answer_set.previous_page_number }}" href="#">이전</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link" tabindex="-1" aria-disabled="true" href="#">이전</a>
</li>
{% endif %}
<!-- 페이지리스트 -->
{% for page_number in answer_set.paginator.page_range %}
{% if page_number >= answer_set.number|add:-4 and page_number <= answer_set.number|add:4 %}
{% if page_number == answer_set.number %}
<li class="page-item active" aria-current="page">
<a class="page-link" data-page="{{ page_number }}" href="#">{{ page_number }}</a>
</li>
{% else %}
<li class="page-item">
<a class="page-link" data-page="{{ page_number }}" href="#">{{ page_number }}</a>
</li>
{% endif %}
{% endif %}
{% endfor %}
<!-- 다음페이지 -->
{% if answer_set.has_next %}
<li class="page-item">
<a class="page-link" data-page="{{ answer_set.next_page_number }}" href="#">다음</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link" tabindex="-1" aria-disabled="true" href="#">다음</a>
</li>
{% endif %}
</ul>
<!-- 페이징처리 끝 -->
<!-- 답변 등록폼 Start -->
<form action="{% url 'pybo:answer_create' question.id %}" method="post" class="my-3">
{% csrf_token %}
<!-- 오류표시 Start -->
{% if form.errors %}
<div class="alert alert-danger" role="alert">
{% for field in form %}
{% if field.errors %}
<strong>{{ field.label }}</strong>
{{ field.errors }}
{% endif %}
{% endfor %}
</div>
{% endif %}
<!-- 오류표시 End -->
<div class="form-group">
<textarea {% if not user.is_authenticated %} disabled {% endif %}
name="content" id="content" class="form-control" rows="10"></textarea>
</div>
<input type="submit" value="답변등록" class="btn btn-primary">
</form>
<!-- 답변 등록폼 End -->
</div>
<form id="searchForm" method="get" action="{% url 'pybo:detail' question.id %}">
<input type="hidden" id="page" name="page" value="{{ page }}">
<input type="hidden" id="so" name="so" value="{{ so }}">
</form>
{% endblock %}
{% block script %}
<script type='text/javascript'>
$(document).ready(function(){
$(".delete").on('click', function() {
if(confirm("정말 삭제하시겠습니까?")) {
location.href = $(this).data('uri');
}
});
$(".recommend").on('click', function() {
if(confirm("정말 추천하시겠습니까?")) {
location.href = $(this).data('uri');
}
});
$(".page-link").on('click', function() {
$("#page").val($(this).data("page"));
$("#searchForm").submit();
});
$(".so").on('change', function() {
$("#so").val($(this).val());
$("#page").val(1); // 새로운 기준으로 정렬할 경우 1페이지부터 조회한다.
$("#searchForm").submit();
});
});
</script>
{% endblock %}
vote_views.py
(...생략...)
@login_required(login_url='common:login')
def vote_answer(request, answer_id):
"""
pybo 답변추천등록
"""
answer = get_object_or_404(Answer, pk=answer_id)
if request.user == answer.author:
messages.error(request, '본인이 작성한 글을 추천할 수 없습니다.')
else:
answer.voter.add(request.user)
# todo 추천 시 답변 목록의 페이지 유지하기
return redirect(f"{resolve_url('pybo:detail', question_id=answer.question.id)}#answer_start")
steven3391 님 1196
M 2022년 1월 10일 1:20 오후
2개의 답변이 있습니다. 1 / 1 Page
안녕하세요.
pybo.kr은 모델에 get_absolute_url 을 구현하여 사용했습니다.
댓글의 경우 다음과 같이 작성했으니 참고하시기 바랍니다.
class Answer(models.Model):
author = models.ForeignKey(CustomUser, on_delete=models.CASCADE, related_name='author_answer')
question = models.ForeignKey(Question, on_delete=models.CASCADE)
content = models.TextField()
create_date = models.DateTimeField()
modify_date = models.DateTimeField(null=True, blank=True)
voter = models.ManyToManyField(CustomUser, related_name='voter_answer', blank=True)
def get_absolute_url(self):
# 현재 답변이 작성된 페이지 정보가 필요
answer_index = self.get_index()
page, r = divmod(answer_index, 10)
if r != 0:
page += 1
return reverse('pybo:question_detail', args=[self.question.id]) + "?page=%s#answer_%s" % (page, self.id)
def get_index(self):
queryset = self.question.answer_set.order_by('create_date')
numbered_qs = queryset.extra(select={
'queryset_row_number': 'ROW_NUMBER() OVER (ORDER BY "id")'
})
with connection.cursor() as cursor:
cursor.execute(
"WITH OrderedQueryset AS (" + str(numbered_qs.query) + ") "
"SELECT queryset_row_number FROM OrderedQueryset WHERE id = %s",
[self.id]
)
index = cursor.fetchall()[0][0]
# index = 0
# for _answer in self.question.answer_set.all().order_by('create_date'):
# index += 1
# if self.id == _answer.id:
# break
return index
def get_recent_comments(self):
return self.comment_set.all().order_by('-create_date')[:5]
class Comment(models.Model):
author = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
content = models.TextField()
create_date = models.DateTimeField()
modify_date = models.DateTimeField(null=True, blank=True)
question = models.ForeignKey(Question, null=True, blank=True, on_delete=models.CASCADE)
answer = models.ForeignKey(Answer, null=True, blank=True, on_delete=models.CASCADE)
voter = models.ManyToManyField(CustomUser, related_name='voter_comment')
class Meta:
ordering = ['create_date']
def get_question(self):
if self.question:
return self.question
elif self.answer:
return self.answer.question
else:
return None
def get_absolute_url(self):
if self.question:
_anchor = "#anchor_question_all_comments"
return reverse('pybo:question_detail', args=[self.question.id]) + _anchor
elif self.answer:
# 현재 답변이 작성된 페이지 정보가 필요
answer_index = self.answer.get_index()
page, r = divmod(answer_index, 10)
if r != 0:
page += 1
_anchor = "?page=%s#anchor_answer_all_comments_%s" % (page, self.answer.id)
return reverse('pybo:question_detail', args=[self.answer.question.id]) + _anchor
else:
return None
박응용 님
M 2022년 1월 10일 2:44 오후