[Python] django 게시판 검색 기능 구현
검색기능
게시판 검색 기능입니다.
검색 대상
- 제목
- 질문 본문 내용
- 질문 작성자
- 답변 본문 내용
- 답변 작성자
1. 검색 화면
맨 밑에 있던 기존 "질문 등록하기" 버튼을 상단 오른쪽으로 옮기고 줄에 검색창을 만들려고 합니다.
question_list.html 템플릿 페이징처리 하단에 기존 작성된 질문 등록하기 코드를 삭제합니다.
<a href="{% url 'pybo:question_create' %}" class="btn btn-primary">질문 등록하기</a>
상단에 검색어를 입력할 수 있는 텍스트창을 다음과 같이 추가합니다.
{% extends 'base.html' %}
{% load pybo_filter %} <!-- 템플릿 필터 함수를 사용하기 위한 파일 로드 -->
{% block content %}
<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>
<!-- 페이징처리 시작 -->
<ul class="pagination justify-content-center">
...생략...
</ul>
<!-- 페이징처리 끝 -->
</div>
{% endblock %}
<table> 태그 상단 우측에 검색어를 입력할 수 있는 텍스트창을 생성하였습니다.
자바스크립트에서 이 텍스트창에 입력된 값을 읽기 위해 다음처럼 id 속성을 추가하였습니다.
<input type="text" id="search_kw" class="form-control" value="{{ kw|default_if_none:'' }}">
여기서 kw가 검색어입니다.
2. 검색 폼
page와 kw를 동시에 GET으로 요청할 수 있는 searchForm을 다음과 같이 추가합니다.
<ul class="pagination justify-content-center">
... 생략 ...
</ul>
<!-- 페이징처리 끝 -->
</div>
<!-- searchForm 추가 -->
<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>
<!-- searchForm 추가 끝 -->
{% endblock %}
GET방식으로 요청해야 하므로 method 속성에 "get"을 설정합니다. action 속성은 '폼이 전송되는 URL' 이므로 질문 목록 URL인 {% url 'index' %} 를 지정했습니다.
kw와 page는 이전에 요청했던 값을 기억하고 있어야 하므로 value에 값을 대입했습니다.
kw(검색어)를 GET이 아닌 POST 방식으로 전달하게 된다면 웹 브라우저에서 "새로고침" 또는 "뒤로가기"를 했을 때 "만료된 페이지입니다."라는 오류가 발생하게 됩니다.
이유는 POST방식은 동일한 POST 요청이 발생할 경우 중복 요청을 방지하기 위해 "만료된 페이지입니다."라는 오류를 발생시키기 때문입니다. 즉, 2페이지에서 3페이지로 갔다가 뒤로가기를 했을 때 2페이지로 가는 것이 아니라 "만료된 페이지입니다."라고 화면에 보여지게 됩니다.
이러한 이유로 여러 파라미터를 조합하여 게시물 목록을 조회할 때는 GET 방식을 사용하는 것이 좋습니다.
3. 페이징
기존 페이징 처리하는 부분도 ?page=1 처럼 직접 파라미터를 코딩하는 방식에서 값을 읽어 폼에 설정할 수 있도록 다음처럼 변경해야 합니다.
모든 페이지 링크를 href 속성에 직접 입력하는 대신 data-page 속성으로 값을 읽을 수 있도록 변경합니다.
즉, 다음과 같은 링크를
<a class="page-link" href="?page={{ question_list.previous_page_number }}">이전</a>
다음처럼 수정합니다.
<a class="page-link" data-page="{{ question_list.previous_page_number }}" href="javascript:void(0)">이전</a>
<!-- 페이징처리 시작 -->
<ul class="pagination justify-content-center">
...생략...
<a class="page-link" data-page="{{ question_list.previous_page_number }}"
href="javascript:void(0)">이전</a>
...생략...
<a class="page-link" data-page="{{ page_number }}" href="javascript:void(0)">{{ page_number }}</a>
...생략...
<a class="page-link" data-page="{{ page_number }}" href="javascript:void(0)">{{ page_number }}</a>
...생략...
<a class="page-link" data-page="{{ question_list.next_page_number }}"
href="javascript:void(0)">다음</a>
...생략...
<!-- 페이징처리 끝 -->
4. 검색 스크립트
page. kw 파라미터를 동시에 요청할 수 있는 자바스크립트를 하단에 다음과 같이 추가합니다.
{% 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 %}
5. 검색 함수
이제 화면에서 요청한 검색어가 질문 목록 조회에 적용될 수 있도록 base_views.py의 index 함수를 다음처럼 수정합니다.
...생략 ...
from django.db.models import Q
def index(request):
page = request.GET.get('page', '1') # 페이지
kw = request.GET.get('kw', '') # 검색어
question_list = Question.objects.order_by('-create_date')
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()
paginator = Paginator(question_list, 10) # 페이지당 10개씩 보여주기
page_obj = paginator.get_page(page)
context = {'question_list': page_obj, 'page': page, 'kw': kw}
return render(request, 'pybo/question_list.html', context)
...생략...
filter 함수에서 모델 속성에 접근하기 위해서는 __ (언더바 두개)를 사용하여 하위 속성에 접근할 수 있습니다.
Q함수내에 사용된 subject__icontains=kw의 의미는 제목에 kw 문자열이 포함되었는지를 의미합니다.
subject__contains=kw 대신 subject__icontains=kw를 사용하면 대소문자를 가리지 않고 검색합니다.
answer__author__username__icontains는 "답변을 작성한 사람의 이름에 포함되는가"? 라는 의미를 갖습니다.
그리고 page와 kw를 템플릿에 전달하기 위해 context 딕셔너리에 추가합니다.
6. 검색 확인
'IT > Linux' 카테고리의 다른 글
[Linux] 우분투 Mysql 계정 생성 및 삭제 방법 (0) | 2023.05.11 |
---|---|
[Linux] Ubuntu 동작중인 service 확인하는 방 (0) | 2023.05.10 |
[Python] django 마크다운 설치 (0) | 2022.12.08 |
[Python] django 앵커(anchor) 기능 구현하기 (0) | 2022.12.08 |
[Python] django 게시판 추천, 좋아요 기능 구현하기 (0) | 2022.12.07 |
댓글