This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

More than 3 years have passed since last update.

[Day 26]検索画面の制作

Last updated at Posted at 2021-02-04

February 4, 2021
←前回:Day 25 DjangoのAPIとAjax通信する「いいねボタン」を作成する

「Djangoを学びたい」とのことでありましたら[Day 1]Djangoの開発環境から読むことをおすすめします。

はじめに

今回はDjangoの便利機能という訳ではないんですが、クエリセットの扱いについて紹介できればと思います。

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

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


(venv)$ ./manage.py startapp search

アプリケーションを追加したら、いつもどおりmysite/settings.pyとmysite/urls.pyを変更します。
ここで本家の方では関係ないことが書いてあったのでここは私なりに修正しておきました。

mysite/settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'debug_toolbar',
    'base',
    'thread',
+   'search',
]
mysite/urls.py

urlpatterns = [
    path('admin/', admin.site.urls),
    path('accounts/', include('django.contrib.auth.urls')),
    path('', include('base.urls')),
    path('thread/', include('thread.urls')),
+   path('search/', include('search.urls')),
]

+が付いているところを加えておけば大丈夫だと思います。

テンプレートの作成

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

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

{% 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>
            {% categorytag %}
        </div>
    </div>
</div>

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

検索窓にキーワード入力
image.png
image.png

終わりに

それではまたまた

←前回:Day 25 DjangoのAPIとAjax通信する「いいねボタン」を作成する
→次回:Day 27 ページネーションを使う

0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up