14
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Djangoでサイト内検索機能を追加したメモ

Last updated at Posted at 2020-12-12

はじめに

学習のアウトプットとしてのメモ
簡単なブログアプリでサイト内検索機能を実装していく

環境

macOS
Django version 2.2.16

前提

Postモデルを持つ簡単なブログアプリ
一覧画面を汎用クラスビューListviewを利用してデータを取得、一覧表示している
検索機能はPostモデルのtitleとtextに対して検索をかける仕様

検索処理の流れ

1.URLに [/?query=任意の検索ワード] を生成させるフォームを作成
2.url.pyを経由してview.pyのListviewが呼び出される
3.Listview内のget_queryset関数でQオブジェクトを利用し、渡されてきたqueryが含まれたquerysetを返す
4.querysetがfor文で1つずつ取り出され一覧表示される
5.メッセージ機能で検索結果を表示する

手順

テンプレート側で検索フォームを作成する

~
<form method="get">
  <input type="search" value="{{ request.GET.query }}" 
         name="query" type="text" 
         placeholder=" Input keywords..">
  <button>Search</button>
</form>
~

Listview内の処理を追加する。get_queryset(self)関数から下の部分

~
from django.views.generic import ListView
from django.db.models import Q # 検索機能のために追加
~

class PostList(ListView):
    context_object_name = 'post_list'
    queryset = Post.objects.order_by('-created_date')
    model = Post
    paginate_by = 7

    def get_queryset(self): # 検索機能のために追加
        queryset = Post.objects.order_by('-created_date')
        query = self.request.GET.get('query')

        if query:
            queryset = queryset.filter(
            Q(title__icontains=query) | Q(text__icontains=query)
            )
        return queryset
~

表示部分

  {% for post in post_list %}
  <div class="content">
    <div class="title">
      <a href="{% url 'post_detail' post.pk %}">{{ post.title }}</a>
    </div>
    <div class="datatime">{{ post.created_date|date:"Y-n-j" }}</div>
  </div>
  {% endfor %}

検索ワードをふくむpost_listをfor文で表示していく。
これで機能は実装できた

検索結果のメッセージ

  • "検索ワード" を含む検索結果:

のようにメッセージ機能があると親切。viewで検索ワードを取得する。

blog/view.py
from django.db.models import Q
from django.contrib import messages # 追加
~
~
        if query:
            queryset = queryset.filter(
            Q(title__icontains=query) | Q(text__icontains=query)
            )
        messages.add_message(self.request, messages.INFO, query) # 追加
        return queryset
~

以上の追加したコードでは、検索ワードをメッセージフレームワークを使ってテンプレートへ渡していく。任意でタグは指定する。

テンプレート.html
~
    {% if messages %}
      <ul class="messages">
        {% for message in messages %}
          <li {% if message.tags %} class="{{ message.tags }}"{% endif %}>
            "{{ message }}" を含む検索結果:
          </li>
        {% endfor %}
      </ul>
    {% endif %}
~

最後にテンプレート側でfor文で取り出された内容を表示していく。

paginate_by を使用している場合の検索クエリの引き継方法

views.py
class PostList(ListView):
    context_object_name = 'post_list'
    queryset = Post.objects.order_by('-created_date')
    model = Post
    paginate_by = 3 # コンテンツの表示上限の追加

    def get_queryset(self):
        queryset = Post.objects.order_by('-created_date')
        query = self.request.GET.get('query')

        if query:
            queryset = queryset.filter(
            Q(title__icontains=query) | Q(text__icontains=query)
            )
        messages.add_message(self.request, messages.INFO, query)
        return queryset
~

上記のように paginated_byを使っている場合、検索一覧ページのテンプレート側で

post_list.html
~
    {% if is_paginated %}
    <ul>
      {% if page_obj.has_previous %}
        <li id="page-number">
          <a href="?page={{ page_obj.previous_page_number }}{% if request.GET.query %}&query={{ request.GET.query }}{% endif %}">Previous</a>
        </li>
      {% endif %}
      {% for num in paginator.page_range %}
        {% if page_obj.number == num %}
        <li id="page-number">
            {{ num }}
        </li>
        {% else %}
        <li id="page-number">
          <a href="?page={{ num }}{% if request.GET.query %}&query={{ request.GET.query }}{% endif %}">{{ num }}</a>
        </li>
        {% endif %}
      {% endfor %}
      {% if page_obj.has_next %}
      <li id="page-number">
        <a href="?page={{ page_obj.next_page_number }}{% if request.GET.query %}&query={{ request.GET.query }}{% endif %}">Next</a>
      </li>
      {% endif %}
    </ul>
    {% endif %}
~

?page={{ num }}のページ数のURL生成部分に
{% if request.GET.query %}&query={{ request.GET.query }}{% endif %}
のコードを追加することで、 [?page=2&query=検索クエリ] のようなURLを生成することができる。

##検索フォームをサイドバーなどに設置する場合
検索フォームをサイドバーなど詳細画面などの画面にも表示されている場合
上記の検索フォームだと、詳細画面から検索フォームに値を入れると/post/1/?query=検索ワードとなり、検索結果にリダイレクトされない。そのため、検索フォームの

内にaction="URL"を設定して一覧画面へ自動的に送られるようにする。以下のコードではaction="{% url 'post_list' %}"として設定。
`template.html`

  <div class="title">SEARCH</div>
  <form action="{% url 'post_list' %}" method="get">
  <input type="search" value="{{ request.GET.query }}" name="query" type="text" placeholder=" Input keywords..">
  <button>Search</button>
 </form>

まとめ

一覧画面に設置した検索フォームに値を入れ検索ボタンをおすと、検索ワードが含まれたpost_listが一覧表示されるようになった。

参考資料

https://zerofromlight.com/blogs/detail/59/#_1
https://docs.djangoproject.com/ja/3.1/ref/models/querysets/#django.db.models.Q
https://docs.djangoproject.com/ja/3.1/ref/contrib/messages/#django.contrib.messages.add_message
https://medium.com/@kjmczk/django-pagination-7c497995561e

更新

2020/12/21 検索結果の部分を追記
2020/12/29 検索結果の引き継ぎ方法を追記

14
13
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
14
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?