설표의 장고




장고) 게시물 검색기능 추가방법





( 수정됨)


환경

OS : Windows 10
python : 3.10.11
Django : 5.0.7

주요 파일 경로

mysite/
|- board/
|   |- forms.py
|   |- veiws.py
|   |- urls.py
|- templates/
    |- list.html

읽기 전에

이 글을 보고 따라하는 것이 목적이라면 이전 글을 먼저 읽고 동일한 환경을 갖춘 상태에서 시도하시길 바랍니다.

장고에 검색기능을 추가하는 방법

게시물의 작성과 조회까지 구현을 했으니, 이번에는 게시물을 검색하는 기능을 구현할 차례입니다.

forms.py 작성하기

board app에 다음과 같은 내용으로 forms.py를 저장합니다.

# board/forms.py
# code by 하얀설표(django.seolpyo.com/)

from django import forms


class SearchForm(forms.Form):
    field = forms.ChoiceField( # 검색 영역 선택
        label='', # 표기되는 이름이 없도록 설정
        choices=( # 선택 가능한 영역
            ('title', '제목'),
            ('content', '내용'),
        ),
        initial='title', # 검색 영역 기본값
    )
    keyword = forms.CharField( # 검색어
        label='',
        max_length=19, # 최대 글자수
        min_length=2, # 최소 글자수
        widget=forms.TextInput(attrs={'placeholder': '검색어를 입력하세요.'}), # "place-holder 설정"
    )

views.py 수정하기

veiws.py의 List class가 context에 SearchForm을 포함하게 하고, List class를 상속받는 Search class를 만듭니다.
검색에는 "title__icontains"와 같은 명령어가 사용되는데, title__icontains는 title 영역에 대소문자 구분없이 검색어가 포함되는 게시물을 가져오라는 명령입니다.
대소문자를 구분하고 싶다면 "title__contains"를 사용하면 됩니다.

# board/views.py
# code by 하얀설표(django.seolpyo.com/)

from django.db.models import Count
from django import forms # 추가
from django.shortcuts import get_object_or_404, redirect, resolve_url
from django.views.generic import ListView, CreateView, UpdateView, DeleteView, DetailView

from django_summernote.widgets import SummernoteWidget

from .models import Article, Comment
from .forms import SearchForm # 추가


form_article = ...

class PostMixin:
    ...
class EditMixin:
    ...
class DeleteMixin:
    ...
class List(ListView):
    "글 목록 페이지 요청에 사용되는 작업"
    template_name = 'list.html'
    paginate_by = 8
    queryset = Article.objects.annotate(num_comments=Count('comment')).order_by(*Article._meta.ordering))
    form_search = SearchForm() # 검색을 위한 form 추가
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['form_search'] = self.form_search # context에 검색창 추가
        return context

class Search(List):
    "게시글 검색 요청에 사용되는 작업"
    def get_queryset(self):
        queryset = super().get_queryset()
        self.form_search = SearchForm(self.request.GET) # 검색정보 가져오기
        if self.form_search.is_valid(): # 검색 유효성 검사
            # 요청이 유효하면 검색
            field = self.form_search.cleaned_data['field'] # 검색 영역
            keyword = self.form_search.cleaned_data['keyword'] # 검색어
            if 'title' == field: query = {'title__icontains': keyword} # 제목 검색
            elif 'content' == field: query = {'content__icontains': keyword} # 내용 검색
            queryset = queryset.filter(**query)
        else: queryset = queryset.none() # 유효하지 않으면 빈 검색결과
        return queryset

urls.py 수정하기

urls.py에 있는 urlpatterns에 검색 기능을 위한 url을 추가합니다.

# board/urls.py

from django.urls import path
from django.contrib.auth.decorators import login_required

from . import views


app_name = 'board'

urlpatterns = [
    path('', views.List.as_view(), name='list'),
    path('search/', views.Search.as_view(), name='search'), # 검색에 사용하는 url
    ...
]

템플릿 수정하기

이전에 작성한 list.html에 검색창 영역을 추가하고, 페이지 url과 검색결과가 없을 때에 사용할 코드를 추가해줍니다.

# templates/list.html

{% block main %}
...
        {# 게시글 list loop #}
        {% for obj in object_list %}
            ...
            {% empty %}
            {# list가 빈 경우 #}
            {% if '/search/' not in request.path %}
            <td colspan="5">게시글이 없습니다.</td>
            {% else %}
            <td colspan="5">검색결과가 없습니다.</td>
            {% endif %}
        </tr>
        {# loop 종료 명령어 #}
        {% endfor %}
...

{% if is_paginated %}
<div class="page" style="display: flex;">
    {% for i in paginator.page_range %}
        {# 페이지 버튼 간 간격 띄우기 #}
        {% if not forloop.first %}&nbsp;&nbsp;{% endif %}

        {% if i == page_obj.number %}
        <p><b>{{ i }}</b></p>
        {% elif page_obj.number|add:-5 <= i and i <= page_obj.number|add:5 %}
            {% if '/search/' not in request.path %}
            {# 게시글 목록에서 사용하는 페이지 url #}
            <p><a href="{% url 'board:list' %}?page={{ i }}">{{ i }}</a></p>
            {% else %}
            {# 게시글 검색에서 사용하는 페이지 url #}
            <p><a href="{% url 'board:search' %}?field={{ request.GET.field}}&keyword={{ request.GET.keyword }}&page={{ i }}">{{ i }}</a></p>
            {% endif %}
        {% endif %}
    {% endfor %}
</div>
{% endif %}

{% if form_search %}
<div style="text-align: center; margin-top: 20px; margin-bottom: 40px;">
    <form action="{% url 'board:search' %}" style="display: ruby;">
        {{ form_search }}
        <input type="submit" value="검색">
    </form>
</div>
{% endif %}

{% endblock %}

변경된 list.html은 다음과 같이 작성됩니다.

list.html 확인하기
# templates/list.html

{% extends "base.html" %}


{% block main %}
<form action="{% url 'board:post' %}">
    <input type="submit" value="글 작성하기">
</form>
<table class="list">
    <thead>
        <tr>
            <th>번호</th>
            <th>제목</th>
            <th>작성자</th>
            <th>작성일</th>
            <th>조회수</th>
        </tr>
    </thead>
    <tbody>
        {# 게시글 list loop #}
        {% for obj in object_list %}
        <tr>
            <td>{{ obj.pk }}</td>
            <td>
                {# 게시글 절대경로 #}
                <a href="{{ obj.get_absolute_url }}">
                    {# 제목과 댓글 수 표시 #}
                    {{ obj.title }} {% if obj.num_comments %}({{ obj.num_comments  }}){% endif %}
                </a>
            </td>
            {# 작성자 #}
            <td>{{ obj.author }}</td>
            {# 작성시간 #}
            <td>{{ obj.date_post|date:'Y.m.d.' }}</td>
            {# 조회수 #}
            <td>{{ obj.hits }}</td>
            {% empty %}
            {# list가 빈 경우 #}
            {% if '/search/' not in request.path %}
            <td colspan="5">게시글이 없습니다.</td>
            {% else %}
            <td colspan="5">검색결과가 없습니다.</td>
            {% endif %}
        </tr>
        {# loop 종료 명령어 #}
        {% endfor %}
    </tbody>
</table>

{% if is_paginated %}
<div class="page" style="display: flex;">
    {% for i in paginator.page_range %}
        {# 페이지 버튼 간 간격 띄우기 #}
        {% if not forloop.first %}&nbsp;&nbsp;{% endif %}

        {% if i == page_obj.number %}
        <p><b>{{ i }}</b></p>
        {% elif page_obj.number|add:-5 <= i and i <= page_obj.number|add:5 %}
        {% if '/search/' not in request.path %}
        <p><a href="{% url 'board:list' %}?page={{ i }}">{{ i }}</a></p>
        {% else %}
        <p><a href="{% url 'board:search' %}?field={{ request.GET.field}}&keyword={{ request.GET.keyword }}&page={{ i }}">{{ i }}</a></p>
        {% endif %}
        {% endif %}
    {% endfor %}
</div>
{% endif %}

{% if form_search %}
// code by 하얀설표(https://django.seolpyo.com/)
<div style="text-align: center; margin-top: 20px;">
    <form action="{% url 'board:search' %}" style="display: ruby;">
        {{ form_search }}
        <input type="submit" value="검색">
    </form>
</div>
{% endif %}

{% endblock %}

검색기능 사용해보기

이제 검색창을 통해 게시물을 검색할 수 있게 되었습니다.

장고 검색기능 구현



이 글의 댓글 기능은 일부러 막아놓았습니다. 궁금한 내용이 있다면 게시판을 이용해주세요!


공감 : 0