게시판에서 검색기능은 필수이다. 검색의 대상은 제목, 질문의 내용, 질문의 작성자, 답변의 내용, 답변의 작성자가 있다.
이런 조건으로 검색하려면 질문 목록을 조회하는 부분을 다음처럼 수정해야 한다. 일단 눈으로만 보자.
from django.db.models. import Q
kw = request.GET.get('kw', '') # 검색어
if kw:
question_list = question_list.filter(
Q(subject__icontains=kw) | # 제목 검색
Q(content__icontains=kw) | # 내용 검색
Q(answer__content__icontains=kw) | # 답변 내용 검색
Q(author__username__icontains=kw) | # 질문 글쓴이 검색
Q(answer__author__username__icontains=kw) # 답변 글쓴이 검색
).distinct()
Q 함수는 OR 조건으로 데이터를 조회하기 위해 사용하는 함수이다. 제목,내용,글쓴이를 or 조건으로 검색하기위해 사용했다. 그리고 filter 함수 뒤에서 사용된 distinct 조회 결과에 중복이 있을 경우 중복을 제거하여 리턴하는 함수이다.
위 코드에서 kw 는 화면으로 부터 전달받은 검색어이다. 그리고 kw 값은 POST 가 아닌 GET 으로 읽도록 했다. GET 은 가져온다는 개념이고, POST 는 수행한다는 개념이라고 볼수있다. GET 은 글늬 내용에 대한 목록을 보여주는 경우나, 글의 내용을 보는 경우이고, POST 는 글의 내용을 저장하고, 수정할 때 사용한다.
kw = request.GET.get('kw', '') # 검색어
이처럼 작성되었기 때문에 kw 는 다음처럼 GET 방식으로 전달되어야 index 함수에서 읽을 수 있다.
http://localhost:8000/?kw=파이썬&page=1
만약 kw 를 GET이 아닌 POST 방식으로 전달하는 방법으로 한다면 page 파라미터도 POST 방식으로 전달되어야 한다. 하지만 그렇게 한다면 새로고침이나 뒤로가기 등을 했을 때 만료된 페이지라는 오류를 만난다. POST 방식은 동일한 POST 요청이 발생할 경우 오류를 발생시키기 때문이다. 그러므로 게시물 목록을 조회할 때는 GET 방식을 사용하는 것이 좋다.
이제 화면에서 kw 와 page 를 동시에 GET 방식으로 호출하는 방법에 대해서 알아보자.question_list.html 템플릿에 검색어를 입력할 수 있는 텍스트 창을 추가하자.
projects\mysite\templates\pybo\question_list.html 에 추가하자.
<table>태그 상단 우측에 검색어를 입력할 수 있는 텍스트창을 생성하였다. 맨 밑에 있던 질문 등록하기 버튼은 검색창의 좌측으로 이동했다. 그리고 자바스크림트에서 이 텍스트창에 입력된 값을 읽기 위해 다음처럼 id 속성을 추가했다.
<input type="text"
id="search_kw"
class="form-control" value="{{ kw|default_if_none:'' }}">빨간 줄로 그은 부분은 삭제해주자.
그리고 page 와 kw 를 동시에 GET 으로 요청할 수 있는 searchForm 을 다음과 같이 추가하자.projects\mysite\templates\pybo\question_list.html 에 추가하자.
GET 방식으로 요청해야 하므로 method 속성에 "get" 을 설정했다. kw 와 page 는 이전에 요청했던 값을 기억하고 있어야 하므로 value에 값을 대입했다. 이전에 요청했던 kw와 page의 값은 index 함수로부터 전달될 것이다. action 속성은 '폼이 전송되는 URL'이므로 질문 목록 URL인 {% url 'index' %} 를 지정했다.
기존 페이징 처리하는 부분도 ?page=1 처럼 직접 파라미터를 코딩하는 방식에서 값을 읽어 폼에 설정할 수 있도록 변경해야한다.
projects\mysite\templates\pybo\question_list.html 을 변경하자.
모든 페이지 링크를 href 속성에 직접 입력하는 대신 data-page 속성으로 값을 읽을 수 있도록 변경했다.
그리고 page, kw 파라미터를 동시에 요청할 수 있는 자바스크립트를 다음과 같이 추가하자.
projects\mysite\templates\pybo\question_list.html 에 추가하자.
작성한 자바스크립트 코드를 살펴보자면,
만약 다음과 같이 class 속성 값으로 'page-link' 라는 값을 가지고 있는 링크를 클릭하면
<a class="page-link" data-page="{{ question_list.previous_page_number }}"
href="javascript:void(0)">이전</a>
이 링크의 data-page 속성값을 읽어 searchForm 의 page 필드에 설정하여 searchForm 을 요청하도록 다음과 같은 스크립트를 추가했다.
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();
});
});
그리고 검색버튼을 클릭하면 검색어 텍스트창에 입력된 값을 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을 설정하여 요청하도록 했다.
마지막으로 요청한 검색어가 질문 목록에 조회되도록 수정하자.
projects\mysite\pybo\views\base_views.py 의 index 함수를 수정하자.
Q 함수내에 사용된 subject__icontains=kw 의 의미는 제목에 kw 문자열이 포함되어있는지를 의미한다.
answer__author__username__icontains 는 답변을 작성한 사람의 이름이 포함되는가? 라는 의미이다. filter 함수에서 모델 속성에 접근하기 위해서는 __ (언더스크롤 두개) 를 이용하여 위 속성에 접근할 수 있다.
subject__contains=kw 대신 subject__icontains=kw 를 하면 대소문자 가리지 않고 찾아 준다.
그리고 page와 kw 를 템플릿에 전달하기 위해 context 딕셔너리에 추가했다.
테스트를 해보자.
검색어에 마크다운이라고 입력한 후 찾기를 눌러보면 마크다운 이라는 제목을 가진 질문만 나온다.