kw = request.GET.get('kw', '') # 검색어
즉, kw는 다음처럼 GET 방식으로 전달되어야 index 함수에서 읽을 수 있다.
kw를 GET이 아닌 POST 방식으로 전달하는 방법은 추천하고 싶지 않다. kw를 POST 방식으로 전달한다면 page 파라미터도 역시 POST방식으로 전달해야 한다.
kw는 POST로 전달하고 page는 GET으로 전달하는 방법은 존재하지 않는다.
만약 GET이 아닌 POST 방식으로 검색과 페이징을 처리한다면 웹 브라우저에서 "새로고침" 또는 "뒤로가기"를 했을 때 "만료된 페이지입니다."라는 오류를 종종 만나게 된다
왜냐하면 POST 방식은 동일한 POST 요청이 발생할 경우 중복 요청을 방지하기 위해 "만료된 페이지입니다." 라는 오류를 발생시키기 때문이다. 2페이지에서 3페이지로 갔다가 뒤로가기를 했을 때 2페이지로 가는것이 아니라 오류가 발생한다면 엉망이 될 것이다.
이러한 이유로 여러 파라미터를 조합하여 게시물 목록을 조회할 때는 GET 방식을 사용하는 것이 좋다.
templates\boards\post_list.html
<div class="container my-3">
<div class="row my-3">
<div class="col-6">
<a href="{% url 'pybo:question_create' %}" class="btn btn-primary">질문 등록하기</a>
</div>
<div class="col-6">
<div class="input-group">
<input type="text" id="search_kw" class="form-control" value="{{ kw|default_if_none:'' }}">
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="button" id="btn_search">찾기</button>
</div>
</div>
</div>
</div>
<table class="table">
(... 생략 ...)
</table>
<!-- 페이징처리 시작 -->
(... 생략 ...)
<!-- 페이징처리 끝 -->
<a href="{% url 'pybo:question_create' %}" class="btn btn-primary">질문 등록하기</a>
그리고 자바 스크립트에서 이 텍스트창에 입력된 값을 읽기 위해 다음처럼 id 속성을 추가한 점에 주목하자
그리고 page와 kw를 동시에 GET으로 요청할 수 있는 searchForm을 다음과 같이 추가하자.
templates\boards\post_list.html
(... 생략 ...)
<!-- 페이징처리 끝 -->
</div>
<form id="searchForm" method="get" action="{% url 'index' %}">
<input type="hidden" id="kw" name="kw" value="{{ kw|default_if_none:'' }}">
<input type="hidden" id="page" name="page" value="{{ page }}">
</form>
{% endblock %}
GET 방식으로 요청해야 하므로 method 속성에 "get"을 설정했다. kw와 page는 이전에 요청했던 값을 기억하고 있어야 하므로 value에 값을 대입했다. 이전에 요청했던 kw와 page의 값은 index 함수로부터 전달될 것이다. action 속성은 '폼이 전송되는 URL'이므로 질문 목록 URL인 {% url 'index' %}를 지정했다.
그리고 기존 페이징 처리하는 부분도 ?page=1 처럼 직접 파라미터를 코딩하는 방식에서 값을 읽어 폼에 설정할 수 있도록 다음처럼 변경해야 한다.
templates\boards\post_list.html
(... 생략 ...)
<!-- 페이징처리 시작 -->
<ul class="pagination justify-content-center">
<!-- 이전페이지 -->
{% if question_list.has_previous %}
<li class="page-item">
<a class="page-link" data-page="{{ question_list.previous_page_number }}"
href="javascript:void(0)">이전</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link" tabindex="-1" aria-disabled="true"
href="javascript:void(0)">이전</a>
</li>
{% endif %}
<!-- 페이지리스트 -->
{% for page_number in question_list.paginator.page_range %}
{% if page_number >= question_list.number|add:-5 and page_number <= question_list.number|add:5 %}
{% if page_number == question_list.number %}
<li class="page-item active" aria-current="page">
<a class="page-link" data-page="{{ page_number }}"
href="javascript:void(0)">{{ page_number }}</a>
</li>
{% else %}
<li class="page-item">
<a class="page-link" data-page="{{ page_number }}"
href="javascript:void(0)">{{ page_number }}</a>
</li>
{% endif %}
{% endif %}
{% endfor %}
<!-- 다음페이지 -->
{% if question_list.has_next %}
<li class="page-item">
<a class="page-link" data-page="{{ question_list.next_page_number }}"
href="javascript:void(0)">다음</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link" tabindex="-1" aria-disabled="true"
href="javascript:void(0)">다음</a>
</li>
{% endif %}
</ul>
<!-- 페이징처리 끝 -->
(... 생략 ...)
###모든 페이지 링크를 href 속성에 직접 입력하는 대신 data-page 속성으로 값을 읽을 수 있도록 변경했다.
templates\boards\post_list.html
(... 생략 ...)
{% endblock %}
{% block script %}
<script type='text/javascript'>
const page_elements = document.getElementsByClassName("page-link");
Array.from(page_elements).forEach(function(element) {
element.addEventListener('click', function() {
document.getElementById('page').value = this.dataset.page;
document.getElementById('searchForm').submit();
});
});
const btn_search = document.getElementById("btn_search");
btn_search.addEventListener('click', function() {
document.getElementById('kw').value = document.getElementById('search_kw').value;
document.getElementById('page').value = 1; // 검색버튼을 클릭할 경우 1페이지부터 조회한다.
document.getElementById('searchForm').submit();
});
</script>
{% endblock %}
검색버튼을 클릭하면 검색어 텍스트창에 입력된 값을 searchForm의 kw 필드에 설정하여 searchForm을 요청하도록 다음과 같은 스크립트를 추가했다.
const btn_search = document.getElementById("btn_search");
btn_search.addEventListener('click', function() {
document.getElementById('kw').value = document.getElementById('search_kw').value;
document.getElementById('page').value = 1; // 검색버튼을 클릭할 경우 1페이지부터 조회한다.
document.getElementById('searchForm').submit();
});
검색버튼을 클릭하는 경우는 새로운 검색에 해당되므로 page에 항상 1을 설정하여 요청
(... 생략 ...)
from django.db.models import Q
(... 생략 ...)
def index(request):
page = request.GET.get('page', '1') # 페이지 만약 넘어온값이 없으면 1페이지
kw = request.GET.get('kw', '') # 검색어 넘어온값이 없으면 ''(빈값으로)
post_list = Post.objects.order_by('-created_at')
if kw:
post_list = post_list.filter(
Q(title__icontains=kw) | # 제목 검색
Q(content__icontains=kw)| # 내용 검색
Q(comment__content__icontains=kw) | # 답변 내용 검색
Q(user__username__icontains=kw) | # 게시글 글쓴이 검색
Q(comment__user__username__icontains=kw) # 답변 글쓴이 검색
).distinct() # 중복 제거
paginator = Paginator(post_list, 10) # 페이지당 10개씩 보여주기
page_obj = paginator.get_page(page)
context = {'post_list': page_obj,'page': page, 'kw': kw}
return render(request, 'boards/post_list.html', context)
Q함수내에 사용된 title__icontains=kw의 의미는 제목에 kw 문자열이 포함되었는지를 의미한다.
comment__user__username__icontains 은 좀 복잡해 보이는데 "답변을 작성한 사람의 이름에 포함되는가?" 라는 의미를 갖는다.
filter 함수에서 모델 속성에 접근하기 위해서는 이처럼 __ (언더바 두개) 를 이용하여 하위 속성에 접근할 수 있다.
※ subjectcontains=kw 대신 subjecticontains=kw을 사용하면 대소문자를 가리지 않고 찾아 준다.
그리고 page와 kw를 템플릿에 전달하기 위해 context 딕셔너리에 추가했다.
