2-5. 検索画面を作る

今回のテーマは「検索画面を作る」です。今回はDjangoの便利機能という訳ではないんですが、クエリセットの扱いについて紹介できればと思います。

※本ページはDjangoのAPIとAjax通信する「いいねボタン」を作成するまで読まれた方を対象としています。そのためサンプルソースコードが省略されている場合があります。

検索アプリケーションの作成

まずは検索を担当するアプリケーションを追加しましょう。Djangoは機能別のアプリケーションを集めてWebアプリケーションを構築します。もちろん、threadアプリケーション内に検索機能を持たせることもできますが、将来別のアプリケーションが追加された場合にはサイト全体の検索を誰が担うのか所在がぼやける恐れがあります。検索を専門に行うsearchアプリケーションを作ることにします。


(venv)$ ./manage.py startapp search

アプリケーションを追加したら、いつもどおりmysite/settings.pyとmysite/urls.pyを変更します。

mysite/settings.py


(venv)$ ./manage.py startapp search

mysite/urls.py


(venv)$ ./manage.py startapp search

テンプレートの作成

ではテンプレートを作っていきましょう。検索結果を表示するテンプレートはこのようになります。

templates/search/result.html



{% extends 'base/base.html' %}
{% block title %}検索結果 - {{ block.super }}{% endblock %}
{% block content %}
<div class="ui grid stackable">
    <div class="eleven wide column">
        <div class="ui breadcrumb">
            <a href="{% url 'base:top' %}" class="section">TOP</a>
            <i class="right angle icon divider"></i>
            <a class="active section">検索結果</a>
        </div>
        <div class="ui segment">
            <div class="content">
                <div class="header"><h3>検索結果</h3></div>
                <form action="{% url 'search:result' %}" method="GET">
                    <div class="ui action input" style="width: 100%;">
                        <input type="text" placeholder="検索" value="{{query}}" name="q">
                        <button class="ui button"><i class="search icon"></i></button>
                    </div>               
                </form>
                {% if result_list %}
                {% for result in result_list %}
                <div class="ui segment message">
                    <h3><a href="{% url 'thread:topic' pk=result.id %}">{{result.title}}</a></h3>
                    <p>{{result.message | truncatewords:30}}</p>
                </div>
                {% endfor %}
                {% else %}
                <div class="ui segment warning message">
                    <p>検索結果はありません。</p>
                </div>
                {% endif %}
            </div>
        </div>
    </div>
    {% include 'base/sidebar.html' %}
</div>
{% endblock %}

特に問題ないと思います。今回はGETでアクセスするのでcsrfについては気にしなくてOKです。

ビューの作成

次にビューを作ります。ビューにはthreadアプリケーションのモデルをインポートします。
search/views.py


from django.shortcuts import render
from django.db.models import Q
from django.views.generic import ListView
from functools import reduce
from operator import and_
from thread.models import Topic

class SearchResultView(ListView):
    template_name = 'search/result.html'
    context_object_name = 'result_list'

    def get_queryset(self):
        if self.request.GET.get('q', ''):
            params = self.parse_search_params(self.request.GET['q'])
            query = reduce(
                lambda x,y : x & y,
                list(map(lambda z: Q(title__icontains=z) | Q(message__icontains=z), params))
            )
            # 下記でもOK
            # query = reduce(and_, [Q(title__icontains=p) | Q(message__icontains=p) for p in params])
            return Topic.objects.filter(query)
        else:
            return None
    
    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx['query'] = self.request.GET.get('q', '')
        return ctx
        
    def parse_search_params(self, words: str):
        search_words = words.replace(' ', ' ').split()
        return search_words

今回はQオブジェクトが出てきました。Djangoでは複雑なクエリセットを組み立てる時にはQオブジェクトを使用します。Qオブジェクトを使用すると”|”演算子でOR条件、”&”演算子でAND条件を表現できます。Qオブジェクトはビット演算子によって新たなQオブジェクトを生成します。よってQ(title__icontains=z)|Q(message_icontains=z)は1つのQオブジェクトとなります。icontainsでは大文字、小文字を区別せず検索します。

それを念頭に置いた上で上記のソースコードを見ると分かりやすいと思います。見やすいようにlambdaとmapで書きましたが、and_関数とリスト内包表記でも同じことができます。

このビューにアクセスするURLを作成します。
search/urls.py


from django.urls import path
from . import views

app_name = 'search'

urlpatterns = [
    path('', views.SearchResultView.as_view(), name='result'),
]

サイドバーの修正

次にサイドバーの検索バーから検索できるようにtemplates/base/sidebar.htmlを修正しましょう。
templates/base/sidebar.html



{% load threadtags %}
<div class="five wide column">
    <form action="{% url 'search:result' %}" method="GET">
        <div class="ui action input" style="width: 100%;">
            <input type="text" placeholder="検索" name="q">
            <button type="submit" class="ui button"><i class="search icon"></i></button>
        </div>
    </form> 
    <div class="ui items">
        <div class="item">
            <a href="{% url 'thread:create_topic' %}" class="ui fluid teal button">トピックを作成</a>
        </div>
    </div>
    <div class="ui segment">
        <div class="content">
            <div class="header"><h4>カテゴリー</h4></div>
            {% category_tag %}
        </div>
    </div>
</div>

では確認してみましょう。 サイドバーの検索窓から検索を行って検索結果画面に検索結果が表示されればOKです。

検索窓にキーワード入力

検索結果画面

最後に

今回のポイントはQオブジェクトの使い方ですね。ビット演算子で連結することで複雑なクエリを表現できますので、活用してみて下さい。次回はページネーションを扱っていきます。だんだん掲示板っぽくなってきましたね。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です