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

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

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

まず、僕が書いた失敗例から紹介するのですが、正解のコードを見たい方は[コチラからジャンプ](https://qiita.com/mamorous3578/items/1d62878daa4216d79f39#%E6%94%B9%E5%96%84)


※商品リストなどをページネートする場合は以下の記事が分かりやすかった。
[ページネーションの作り方を分かりやすく解説【原則をおさえるのがポイント】](https://codor.co.jp/django/how-to-make-pagenation)

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

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

```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>
```

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

```python: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:検索結果を表示する部分.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`

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

```html
{% 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メソッドがイマイチ分からない時は[コチラ](https://codor.co.jp/django/difference-request-get)

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

改善後のビュー

```python: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:検索結果を表示するページ.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 %}
```