LoginSignup
6
6

More than 3 years have passed since last update.

検索結果をページネーションする時の注意点【Django】【paginator】

Last updated at Posted at 2019-09-05

はじめに

只今、ECサイトを作っています。
商品の検索機能を実装する際に、検索結果をページネートしようとして手間取ったので記事にしようと思います。

一足先に、、
出てきたエラーコードは
object of type 'NoneType' has no len()
でした。

まず、僕が書いた失敗例から紹介するのですが、正解のコードを見たい方はコチラからジャンプ

※商品リストなどをページネートする場合は以下の記事が分かりやすかった。
ページネーションの作り方を分かりやすく解説【原則をおさえるのがポイント】

失敗例

商品一覧を表示する時と全く同じコードをコピペした。
1ページ目はうまく表示されるが、2ページ目をクリックするとエラーが表示された。

検索ワードを入力するフォーム、ビュー、検索結果を表示するHTMLを示す。

フォーム部分.html
<form action="{% url 'search_app:searchResult' %}" method="get">
{% csrf_token %}
  <input name="q" type="search" placeholder="Search" aria-label="Search">
  <button type="submit">検索</button>
</form>

検索機能とページネーションを実装したビュー

search_app.views.py
from django.shortcuts import render
from shop.models import Product
from django.db.models import Q
from django.core.paginator import Paginator, EmptyPage, InvalidPage

def searchResult(request):
    products = None
    query = None
    #検索機能の実装
    if 'q' in request.GET:
        query = request.GET.get('q')
        products = Product.objects.all().filter(Q(name__contains=query)|Q(description__contains=query))
    #ページネーションの実装
    paginator = Paginator(products, 6)
    try:
        page = int(request.GET.get('page','1'))
    except:
        page = 1
    try:
        products = paginator.page(page)
    except (EmptyPage, InvalidPage):
        products = paginator.page(paginator.num_pages)

    return render(request, 'search.html', {'query':query, 'products':products})

検索結果を表示するHTML

検索結果を表示する部分.html
{% block content %}

#検索結果を表示する部分
{% for product in products.object_list %}
  <h4>{{product.name}}</h4>
  <p>{{product.price}}円(+税)</p>
{% empty %}
  <p class="text-center my_search_text">検索結果がありません</p>
{% endfor %}

#ページ選択部分
{% if products.paginator.num_pages > 1 %}
  {% for pg in products.paginator.page_range %}
    <a href="?page={{pg}}">{{pg}}</a>
  {% endfor %}
{% endif %}

{% endblock %}

試したこと

検索結果の1ページ目は正常に表示されていた。
2ページ目を表示するために、ページ番号をクリックするとエラーが表示された。

その時のURLを観察したり、いじったりしてみた。
検索結果1ページ目のURL(検索ワードを入れてエンターを押した時に表示されるURL)は、
http://127.0.0.1:8000/search/?csrfmiddlewaretoken=eYAHucE7ZSzpJe5lxEdKspkRf7NzFxngZuPJ8wonT1XMTcf08Wjz5WHXW0QJu7c4&q=検索ワード

このURLを見やすくするために、csrfmiddlewaretoken=以下をAAAで置換すると
検索結果1ページ目のURLは、
http://127.0.0.1:8000/search/?csrfmiddlewaretoken=AAA&q=検索ワード

エラーとなる検索結果2ページ目のURLは、
http://127.0.0.1:8000/search/?page=2

ここまでで、エラーが出た原因を推測すると、
以下のページリンクの部分が原因

{% for pg in products.paginator.page_range %}
  <a href="?page={{pg}}">{{pg}}</a>
{% endfor %}

もっと言うと、
csrf_tokenがURLで指定されていない。(csrfmiddlewaretoken=AAAの部分)
そして、検索ワードがURLで指定されていない。(q=検索ワードの部分)

改善

2ページ目のリンクが
http://127.0.0.1:8000/search/?csrfmiddlewaretoken=AAA&q=検索ワード&page=2
となるようにリンクを設定すればよい。

具体的には、
ビュー部分でrequest.GETメソッドを用いてURLからAAAと検索ワードの値を取得
そして、
ページのリンク部分に取得した値を埋め込み、正しいURLに飛ばす。

request.GETメソッドがイマイチ分からない時はコチラ

実際にコードを修正してみる。

改善後のビュー

search_app.views.py
def searchResult(request):
    products = None
    query = None
    #検索機能の実装
    if 'q' in request.GET:
        query = request.GET.get('q')
        products = Product.objects.all().filter(Q(name__contains=query)|Q(description__contains=query))
    #ページネーションの実装
    paginator = Paginator(products, 6)
    try:
        page = int(request.GET.get('page','1'))
    except:
        page = 1
    try:
        products = paginator.page(page)
    except (EmptyPage, InvalidPage):
        products = paginator.page(paginator.num_pages)
    #追加
    csrf_token = request.GET.get('csrfmiddlewaretoken')
    #修正
    return render(request, 'search.html', {'query':query, 'products':products, 'csrf_token':csrf_token})

改善後の検索結果のHTML
他ページへのリンク部分だけ記載

検索結果を表示するページ.html
<!--検索結果を表示する部分-->
省略

<!--ページ選択部分-->
{% if products.paginator.num_pages > 1 %}
  {% for pg in products.paginator.page_range %}
    <!--修正-->
    <a href="?csrfmiddlewaretoken={{csrf_token}}&q={{query}}&page={{pg}}">{{pg}}</a>
  {% endfor %}
{% endif %}
6
6
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
6
6