DjangoでQiita風ページネーションの実装

初投稿で拙い部分も多いと思いますがよろしくおねがいします.

今回はDjangoフレームワークを使って以下のようなページネーションを作成しました.
後々気づきましたがQiitaのページネーションとほとんど同じ感じに仕上がります.全く同じようにも出来るのでやってみてください.
ページネーションに関する便利なパッケージもあるようですが,今回は使わずに実装してみたいと思います.

![スクリーンショット 2019-01-25 7.28.09.png](https://qiita-image-store.s3.amazonaws.com/0/344098/df0f81bd-6de7-5d49-aca4-1060dd76b1d1.png)
![スクリーンショット 2019-01-25 7.30.08.png](https://qiita-image-store.s3.amazonaws.com/0/344098/87586f05-25da-2032-50dc-253f69732980.png)
5ページ目以降は以下のようになる.
![スクリーンショット 2019-01-25 7.29.54.png](https://qiita-image-store.s3.amazonaws.com/0/344098/76eee167-b833-84f4-c7dd-9c3838593da9.png)


## 構造
- 両端の山括弧は前後のページ移動に使います.
- [...]のところはクリックしても何も起きません.

## バージョン
- Django2.1
- Bootstrap4

## コード
基本的な骨組みは[Bootstrap4チートシート](https://hackerthemes.com/bootstrap-cheatsheet/)から拝借しました.

```python:pagination.html
<nav aria-label="Page navigation">
<ul class="pagination">
  {% if page_obj.has_previous %}
  <li class="page-item">
    <a class="page-link" href="?page={{ page_obj.previous_page_number }}" aria-label="Previous">
      <span aria-hidden="true">&lt;</span>
    </a>
  </li>
  {% endif %}

  {% if page_obj.number > 3 %}
  <li class="page-item">
    <a class="page-link" href="?page=1" aria-label="First">1</a>
  </li>
  {% endif %}

  {% if page_obj.number > 4 %}
  <li class="page-item"><span class="page-link" aria-hidden="true">...</span></li>
  {% endif %}

  {% for link_page in page_obj.paginator.page_range %}
  {% if link_page == page_obj.number %}
  <li class="page-item active">
    <a class="page-link" href="?page={{ link_page }}">
      {{ link_page }}
    </a>
  </li>
  {% elif link_page < page_obj.number|add:3 and link_page > page_obj.number|add:-3 %}
  <li class="page-item">
    <a class="page-link" href="?page={{ link_page }}">
      {{ link_page }}
    </a>
  </li>
  {% endif %}
  {% endfor %}

  {% if page_obj.number < page_obj.paginator.num_pages|add:-3 %}
  <li class="page-item"><span class="page-link" aria-hidden="true">...</span></li>
  {% endif %}

  {% if page_obj.number < page_obj.paginator.num_pages|add:-2 %}
  <li class="page-item">
    <a class="page-link" href="?page={{ page_obj.paginator.num_pages }}" aria-label="Last">{{ page_obj.paginator.num_pages}}</a>
  </li>
  {% endif%}

  {% if page_obj.has_next %}
  <li class="page-item">
    <a class="page-link" href="?page={{page_obj.next_page_number }}" aria-label="Next">
      <span aria-hidden="true">&gt;</span>
    </a>
  </li>
  {% endif %}
</ul>
</nav>
```

## 今開いてるページの前後部分の表示

流れはシンプルです.組み込みタグをうまく使えばできます.
1. for文でページ番号を1から順番に取り出す.要素はlink_pageに格納される.
2. link_pageが現在のページ番号(page_obj.number)と同じ場合class="page-item active"でリンクを表示
3. page_obj.number-3<link_page<page_obj.number+3となる場合は通常通りリンクを表示


```python
  {% for link_page in page_obj.paginator.page_range %}
  {% if link_page == page_obj.number %}
  <li class="page-item active">
    <a class="page-link" href="?page={{ link_page }}">
      {{ link_page }}
    </a>
  </li>
  {% elif link_page < page_obj.number|add:3 and link_page > page_obj.number|add:-3 %}
  <li class="page-item">
    <a class="page-link" href="?page={{ link_page }}">
      {{ link_page }}
    </a>
  </li>
  {% endif %}
  {% endfor %}
```

### 組み込みタグ if
組み込みタグのif文ではelifが使えます.また条件式の部分でand or notはもちろん不等号も使えます.
しかし条件式に足し算,引き算を使うには+や-では動きません.
そこでフィルタaddを使うことで今回のページネーションを実現しました.

ちなみに掛け算や割り算のフィルタははないので自作すると良いです.

>#### 組み込みフィルタadd
>https://docs.djangoproject.com/ja/2.1/ref/templates/builtins/#add
>入力値に対して引数の値を加算します。
>
>```python:ex
>{{ value|add:"2" }}
>```

## まとめ
本実装はほぼ組み込みフィルタのおかげでできました.
リファレンスの検索性の悪さは有名ですので
「site:https://docs.djangoproject.com/ リレーション」
みたいな感じでググると良いと思います.
ページネーションの両端部分の条件分岐を考慮するとやや煩雑なコードになってます.もう少し良い書き方を考えようと思います.
グーグル検索風なページネーションも今回のやり方ですぐ作れます.

以上です.ありがとうございました.