はじめに
只今、ECサイトを作っています。
商品の検索機能を実装する際に、検索結果をページネートしようとして手間取ったので記事にしようと思います。
一足先に、、
出てきたエラーコードは
object of type 'NoneType' has no len()
でした。
まず、僕が書いた失敗例から紹介するのですが、正解のコードを見たい方はコチラからジャンプ
※商品リストなどをページネートする場合は以下の記事が分かりやすかった。
ページネーションの作り方を分かりやすく解説【原則をおさえるのがポイント】
失敗例
商品一覧を表示する時と全く同じコードをコピペした。
1ページ目はうまく表示されるが、2ページ目をクリックするとエラーが表示された。
検索ワードを入力するフォーム、ビュー、検索結果を表示する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>
検索機能とページネーションを実装したビュー
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
{% 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メソッドがイマイチ分からない時はコチラ
実際にコードを修正してみる。
改善後のビュー
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
他ページへのリンク部分だけ記載
<!--検索結果を表示する部分-->
省略
<!--ページ選択部分-->
{% 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 %}