2020-04-02

Django 18. 게시글 검색 기능 구현

Django Query Filter를 통해 전체, 작성자, 제목, 내용별 검색을 구현합니다.


1. views.py 수정

게시글의 검색기능 구현은 Django의 쿼리 필터를 통해 쉽게 구현할 수 있습니다. 템플릿에 select 태그로 전체, 제목+내용, 제목, 내용, 작성자별 검색 타입과 검색어를 입력받아 form GET 메소드로 요청을 받습니다. view에서 form의 값들이 GET으로 넘어와 url 뒤에 /?type=’검색타입’&q=’검색어’&page=’페이지’ 와 같은 형식으로 파라미터들을 받게 되면 request 객체에 있는 get은 딕셔너리 형으로 변환하여 저장하게 됩니다. 따라서 request.GET.get(‘파라미터값’, ‘ ‘) 과 같은 형식으로 파라미터를 전달받고 쿼리 필터를 적용하여 반환하는 소스를 이전 포스팅에서 구현한 NoticeListViewget_queryset에 아래와 같이 추가합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# notice/views.py

from django.contrib import messages
from django.db.models import Q

def get_queryset(self):
search_keyword = self.request.GET.get('q', '')
search_type = self.request.GET.get('type', '')
notice_list = Notice.objects.order_by('-id')

if search_keyword :
if len(search_keyword) > 1 :
if search_type == 'all':
search_notice_list = notice_list.filter(Q (title__icontains=search_keyword) | Q (content__icontains=search_keyword) | Q (writer__user_id__icontains=search_keyword))
elif search_type == 'title_content':
search_notice_list = notice_list.filter(Q (title__icontains=search_keyword) | Q (content__icontains=search_keyword))
elif search_type == 'title':
search_notice_list = notice_list.filter(title__icontains=search_keyword)
elif search_type == 'content':
search_notice_list = notice_list.filter(content__icontains=search_keyword)
elif search_type == 'writer':
search_notice_list = notice_list.filter(writer__user_id__icontains=search_keyword)

return search_notice_list
else:
messages.error(self.request, '검색어는 2글자 이상 입력해주세요.')
return notice_list

get_queryset에 추가된 소스를 보시면 search_keyword, search_type의 변수명으로 파라미터의 값들을 저장합니다. 그 후 검색어인 search_keyword의 유무와 검색어의 길이를 판별해 검색타입인 search_type으로 다시 구분하여 각각의 필터를 적용합니다.

전체타입과 제목+내용 타입과 같이 두가지 이상의 필터 조건을 적용하기 위하여 django.db.modelsQ를 import합니다. 그리고 전체 쿼리셋에서 search_keyword가 포함되어있는 쿼리셋만 가져오기 위해 ‘필드명’__icontains = ‘조건값’ 형식으로 필터를 적용합니다. __icontains는 대소문자를 구분하지 않고 조건값이 포함되어 있는 데이터를 가져옵니다.

그리고 템플릿에서도 사용자가 검색한 검색결과를 ‘<검색어> 검색결과 입니다.’ 와 같이 표시하고, 선택한 검색타입을 계속 유지하기 위해 contextsearch_keywordsearch_type을 넘겨주는 소스를 NoticeListViewget_context_data에 추가합니다.

1
2
3
4
5
6
7
8
9
10
11
# notice/views.py

def get_context_data(self, **kwargs):
search_keyword = self.request.GET.get('q', '')
search_type = self.request.GET.get('type', '')

if len(search_keyword) > 1 :
context['q'] = search_keyword
context['type'] = search_type

return context

마찬가지로 request.GET.get 형식으로 파라미터값을 받고, 템플릿에 넘겨줄 q와 type을 딕셔너리형인 context에 추가합니다.

2. templates 수정

사용자가 검색한 검색어를 표시하기 위해 상단부에 context로 전달받은 검색어인 q를 표시합니다.

1
2
3
4
5
<!-- templates/notice/notice_list.html -->

{% if q %}
<h5>'{{ q }}' 검색 결과입니다.</h5>
{% endif %}

검색어를 포함하는 게시글이 없어 검색결과에러를 표시하기 위해 context의 q를 사용하여 { % if notice_list % }else 처리 부분에 아래와 같이 소스를 추가합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!-- templates/notice/notice_list.html -->

{% if notice_list %}
{% for notice in notice_list %}
<tr class="text-center">
<td>{{ notice.id }}</td>
<td>{{ notice.title|truncatechars:30 }}</td>
<td>{{ notice.writer }}</td>
<td>{{ notice.registered_date|date:'Y. m. d' }}</td>
<td>{{ notice.hits }}</td>
</tr>
{% endfor %}
{% else %} <!-- 게시글 쿼리셋이 존재하지 않을 때 -->
{% if q %} <!-- 검색어가 있어 q가 context로 넘어오면 검색결과가 없음 -->
<tr class="text-center">
<td colspan="5">
일치하는 검색 결과가 없습니다.
</td>
</tr>
{% else %} <!-- q가 없으면 검색어가 아니며 게시글이 존재하지 않으므로 게시글 미작성 -->
<tr class="text-center">
<td colspan="5">
작성된 게시글이 없습니다.
</td>
</tr>
{% endif %}
{% endif %}

검색결과 수가 많아 페이지네이션이 적용되었을 때 페이지를 넘기면 검색결과가 풀려버리는 버그가 발생합니다. 이 프로젝트에서는 따로 검색 View를 구현하지 않고 ListView로 묶어 구현했기 때문에 페이지네이션 부분의 href 소스도 파라미터를 전달 할 수 있도록 아래와 같이 수정해줍니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!-- templates/notice/notice_list.html -->

{% if is_paginated %}
<ul class="pagination">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?type={{ type }}&q={{ q }}&page={{ page_obj.previous_page_number }}"
tabindex="-1">이전</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link" href="#" tabindex="-1">이전</a>
</li>
{% endif %}

{% for page in page_range %}
<li class="page-item {% if page == page_obj.number %} activate {% endif %}">
<a class="page-link" href="?type={{ type }}&q={{ q }}&page={{ page }}">{{ page }}</a>
</li>
{% endfor %}

{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link"
href="?type={{ type }}&q={{ q }}&page={{ page_obj.next_page_number }}">다음</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link" href="#">다음</a>
</li>
{% endif %}
</ul>
{% endif %}

3. 결과

django-project-18

*전체 html, css 등은 자세하게 포스팅하지 않습니다. 제 Github에서 소스를 확인하실 수 있습니다.