Djangoで作る初めてのウェブアプリケーション(番外編)検索とページネーション
こ本記事は、マガジン(『超入門』Djangoで作る初めてのウェブアプリケーション)で作成した、Djangoアプリを使用します。
- 検索関連の記事 -
Djangoで作る初めてのウェブアプリケーション(番外編)検索機能
Djangoで作る初めてのウェブアプリケーション(番外編)お問い合わせ機能
本日はデータベース内の検索(絞り込み)にページネーションを加えていきます。
完成図
Djangoでページ ネーションを実現する方法は関数ベースやクラスベースで行う方法など複数あります。公式の方に記載されています。
Django pagenation
検索用とページネーション用のHTMLを作ろう
まずはtemplates直下に検索用のフォームsearch.htmlを作ります。
/templates/search.html
<form class="form-inline my-2 my-lg-0" action="{% url 'blog_app:index' %}" method="GET">
<input class="form-control mr-sm-2" type="text" name="q" value="{{ request.GET.q }}" placeholder="検索" aria-label="Search">
<button class="btn btn-outline-success my-2 my-sm-0" type="submit"><i class="fas fa-search saerch"></i></button>
</form>
formクラスのaction属性にはblog_appのindex関数に検索内容を送信するようにします。
methodはデータベースを更新したり削除したりしないのでGETメソッドにします。
inputタグのname属性のqはqueryを表していてこの検索フォームで入力されたqをviews.pyで絞り込んでいきます。value属性は{{ request.GET.q }}とする事によって検索バーに検索内容(検索した値)が残るようにしています。
最後のbuttonタグの虫眼鏡アイコンはFont Awesomeを使用しています。
searchクラスを追加してCSSで少しだけサイズを変えています。
これで検索フォームが完成しました。
header.html内にincludeしましょう。
/templates/header.html
<nav class="navbar fixed-top navbar-expand-lg navbar-light bg-light">
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse" data-target="#navbarTogglerDemo02" aria-controls="navbarTogglerDemo02" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<a class="navbar-brand" href="{% url 'blog_app:index' %}">ブログサイト</a>
<div class="collapse navbar-collapse" id="navbarTogglerDemo02">
<ul class="navbar-nav mr-auto mt-2 mt-md-0">
{% if request.user.is_authenticated %}
<li class="nav-item">
<a class="nav-link" href="{% url 'accounts:detail' request.user.pk %}">{{request.user.username}}</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'blog_app:add' %}">投稿</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'account_logout' %}">ログアウト</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{% url 'account_signup' %}">新規登録</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'account_login' %}">ログイン</a>
</li>
{% endif %}
<li class="nav-item">
<a class="nav-link js-scroll-trigger" href="{% url 'blog_app:contact' %}">お問い合わせ</a>
</li>
</ul>
<!-- 追加 -->
{% include 'search.html' %}
<!-- -->
</div>
</nav>
続いてページ ネーション用のHTMLもtemplates直下に作ります。
ページ ネーションはBootstrapで見た目を整えています。
/templates/page.html
{% if posts.has_other_pages %}
<nav class="d-flex justify-content-center">
<ul class="pagination">
{% if posts.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ posts.previous_page_number }}{% if request.GET.q %}&q={{ request.GET.q }}{% endif %}" tabindex="-1">Previous</a>
</li>
{% else %}
<li class="page-item disabled"><a class="page-link" href="#!">Previous</a></li>
{% endif %}
{% for num in posts.paginator.page_range %}
{% if posts.number == num %}
<li class="page-item active">
<a class="page-link" href="#!">{{ num }}<span class="sr-only">(current)</span></a>
</li>
{% else %}
<li class="page-item"><a class="page-link" href="?page={{ num }}{% if request.GET.q %}&q={{ request.GET.q }}{% endif %}">{{ num }}</a></li>
{% endif %}
{% endfor %}
{% if posts.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ posts.next_page_number }}{% if request.GET.q %}&q={{ request.GET.q }}{% endif %}">Next</a>
</li>
{% else %}
<li class="page-item disabled"><a class="page-link" href="#!">Next</a></li>
{% endif %}
</ul>
</nav>
{% endif %}
page.htmlに見慣れないif文などが存在します。
{% if posts.has_other_pages %}
{% if posts.has_previous %}
{{ posts.previous_page_number }}
その他、、、
これらはviews.pyのindex関数内で後に使用する、Paginator, EmptyPage, PageNotAnIntegerによって生成されたものです。
page.htmlをindex.html内にincludeします。
/templates/blog_app/index.html
<!-- 省略 -->
<div class="d-flex justify-content-center flex-wrap">
{% for post in posts%}
<a href="{% url 'blog_app:detail' post_id=post.id %}">
<div class="card m-4" style="min-width: 300; max-height: 490;">
{% if post.image %}
<img style="max-width: 300; max-height: 300;" class="card-img-top" src="{{ post.image.url }}">
{% else %}
<div class="p-1 border border-secondary text-center pt-5" style="min-width: 300; min-height: 225;">No Image</div>
{% endif %}
<div class="card-body">
<h4 class="card-title">{{post.title|truncatechars:10 | linebreaksbr}}</h4>
<p class="card-text">
{{ post.text|truncatechars:17 }}
</p>
</a>
<p></p>
<p>#{{post.tag}}</p>
<p>{{post.created_at}} |
<a href="{% url 'accounts:detail' post.user.pk %}">{{post.user.username}}</a></p>
</div>
</div>
{% endfor %}
</div>
<!-- 追加 -->
{% include 'page.html' %}
<!-- -->
{% endblock %}
index関数の編集
検索バーで入力された値を絞り込み、それにページネーションを追加していいましょう。
検索バーのformでの送信先は、action="{% url 'blog_app:index' %}"なのでindex関数を編集します。
/blog_app/views.py
from django.db.models import Q
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
def index(request):
posts = Post.objects.all().order_by('-created_at')
query = request.GET.get('q')
if query:
posts = Post.objects.filter(
Q(title__icontains=query) |
Q(text__icontains=query) |
Q(user__username__icontains=query)
).distinct()
paginator = Paginator(posts, 2)
page = request.GET.get('page')
try:
posts = paginator.page(page)
except PageNotAnInteger:
posts = paginator.page(1)
except EmptyPage:
posts = paginator.page(paginator.num_pages)
return render(request, 'blog_app/index.html', {'posts': posts})
index関数内の重要な部分の説明です。
検索ワードを絞り込むのに必要なfrom django.db.models import Qを使用し、ページ ネーションに必要なfrom django.core.paginator import Paginator, EmptyPage, PageNotAnIntegerを使用します。
まずはquery = request.GET.get('q')のGETメソッドで検索バーの値を取得し、それをquery関数に代入します。
if文を使用して、もしquery変数に値があれば(Trueならば)
if query:
filter関数を使い絞り込んでいきます。
posts = Post.objects.filter
ここでQ関数の引数にtitle__icontains=queryを渡す事で、query変数の値のtitle、text、usernameを |(パイプ)を使用する事で複数条件で絞り込みます。
title、text、usernameはmodels.pyで作ったデーターベース上のカラムを検索しています。
Q(title__icontains=query) |
Q(text__icontains=query) |
Q(user__username__icontains=query)
)
distinct関数は重複を防ぎます。
distinct()
続いてページ ネーション関連です。
Paginator関数の引数に絞り込まれたpostsもしくはデータ検索を行わずに単にデータを取得した場合のpostsを渡し、第二引数に何個データがあればページ ネーションを表示させるかの数字を渡します。
今回はブログ記事が少ないので分かり易いように2にしています。
paginator = Paginator(posts, 2)
page = request.GET.get('page')
そしてtry文とexceptを使用して、pageが存在するときしないときの処理をします。
try:
posts = paginator.page(page)
except PageNotAnInteger:
posts = paginator.page(1)
except EmptyPage:
posts = paginator.page(paginator.num_pages)
最後にrender関数でretuenします。
return render(request, 'blog_app/index.html', {'posts': posts})
完成
以上で以下のように絞り込みが可能になり、ページネーションも使用できるようになりました。
この記事が気に入ったらサポートをしてみませんか?