Published Date : 2019年3月28日0:35

CORESERVERでDjangoのBLOGを作る
〜番外編〜



前回の記事の簡単なおさらい。

CORESERVERDjango で作ったブログをアップロードする

.htaccessdjango用の cgiファイルを設定して、 ブログを表示させる。




すっかり前回の続きをサボってしまっていました。。。

気を取り直して、今回作るのは、 Djangoで作ったブログに、 ページネーション機能を つけていきたいと思います。

Paginationとは、 いわゆるページ送り機能です。

こんな感じのやつです。


我らの味方、bootstrap様の サイトからとってきました。
https://getbootstrap.com/docs/4.0/components/pagination/




それではページネーションを作る前に、 DjangoにおけるPagination

どんな感じになっているのか軽く説明しましょう。



まず最初に、ページネーションのためには、 PaginatorというClassファイルを 呼び出す必要があります。

from django.core.paginator import Paginator



ここに前回からのブログで作った、 PostClassファイルがあります。

from django.db import models

class Post(models.Model):
    title=models.CharField(max_length=100)
    published=models.DateTimeField()
    image = models.ImageField(upload_to='media/')
    body = models.TextField()
この設計図をもとに作られたデータを データベースから取り出すと、
posts=Post.objects.all()

こんな感じにpostsオブジェクトは、 辞書型がリストととして格納されています。
print(posts)

->
[
{'id':1,'published':'記事の投稿時間','title':'記事のタイトル','body':'記事の本文'},
{'id':2,'published':'記事の投稿時間','title':'記事のタイトル','body':'記事の本文'},
{'id':3,'published':'記事の投稿時間','title':'記事のタイトル','body':'記事の本文'}
]


このpostsオブジェクトを、 さきほど呼び出した Paginatorクラスファイルの
一番目の引数に入れ、 二番目の引数に
このpostsオブジェクトを、 「何分割で表示するか」 を数字で指定します。
p = Paginator(posts, 2)
Responsive image

ページ(p)オブジェクトの countメソッドで記事の数が確認できる。
p.count

-> 3

num_pagesで、 いくつページがあるか確認できる。
p.num_pages

-> 2

page_rangeで、 上のページ数をもとに
(1から、2まで(3の手前の2)) 1ページづつ処理できる形で取り出せる。
p.page_range

-> range(1, 3)

つまり、このようなイメージ。
p.page_range

-> range(1, 3)

for page in p.page_range:

-> for page in range(1,3):

    print(page)

-> 1
-> 2

page(1)で、 1ページ目を取り出す。
page1 = p.page(1)

page1

-> ページ 2個 あるうちの 1個目

object_listで、 どの記事が1ページ目にあるか確認できる。
page1.object_list

-> [
{'id':1,'published':'記事の投稿時間','title':'記事のタイトル','body':'記事の本文'},
{'id':2,'published':'記事の投稿時間','title':'記事のタイトル','body':'記事の本文'}
]

続いてhas_next() このページに「次」があるか判定。
page1.has_next()

-> True

page1は始まりのページなので、 has_previous()つまり 前のページがあるかは「ない」と返される。
page1.has_previous()

-> False

続いて2ページ目のオブジェクトを作る。
page2 = p.page(2)

他のページがあるか調べる。 ページ1があるので、True。
page2.has_other_pages()

-> True
次のページの番号は? エラーが返される。
page2.next_page_number()

-> Traceback (most recent call last):
...
EmptyPage: That page contains no results

2個あるうちの最後のページに、 前のページは何番?と聞けば、1。
page2.previous_page_number()

-> 1

1ページ目の中にある、 アイテムの始まりの番号は?
page1.start_index()

-> 1

2ページ目の中にある、 アイテムの始まりの番号は?
page2.start_index()

-> 3

2ページ目の中にある、 アイテムの終わりの番号は?
page2.end_index()

-> 3

雑に表すとこんな感じ。
Responsive image

どうやら、Djangoの このページネーション機能は、 一つのページに 記事のリストをずらっーと表示させて、 その下にページ送りを つけるのが目的っぽい。
Responsive image


つまり、記事が多くないと扱いにくい 仕様になってない?と思いました。
なんか下のページ送りだけ欲しい。
そしてページは一つの記事と対応させたい。

とういうことで、 一つのページに一つの記事を表示する。
Responsive image


では、ページネーション。 作っていきます。


さて、postsviews.py 覚えていますか?
開いてみましょう。
Responsive image

中にあるpost関数に、 赤枠を付け足していきましょう。
Responsive image

Responsive image

ページ送り機能に必要な三種の神器。
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger  

ブログの全記事を DBからとってきます。
ここでポイントは、 1ページにつき、 一つの記事としたいので、
ページ指定は「1」です。
posts=Post.objects.all()
paginator = Paginator(posts, 1)

このpost関数が呼ばれるとき、 一緒に記事のIDが運ばれてきます。

それをpaginator.page() メソッドに入れてやる。 (下のコードの説明。)

そうすればこのpageオブジェクトは、 次のページと前のページと
今のページの情報を持ち、
ページ番号自体が記事のIDとなります。

tryがまず実行され、 例外(ページがないとか)が
発生すれば次の exceptの処理に移ります。
try:
    page = paginator.page(post_id)

ページがインテジャー(整数) ではないとき。
最初のページをとってくる。

記事が無いとき。
最後のページをとってくる。
except PageNotAnInteger:
    page = paginator.page(1)
except EmptyPage:
    page = paginator.page(paginator.num_pages)

renderメソッドに、 pageオブジェクトを渡す。
後は前回と同じ。
そして関数の外へ返してあげる。
return render(request,'posts/post.html',{'post':post,'post_body':post_body,'page':page})



続いて、HTMLファイル作りです。

postsにある、 templatesの、posts
の中にある、post.htmlが赤枠。

青枠が新しく作る、pagination.html
Responsive image

まず、post.htmlから。

赤枠が新たに作るところ。
Responsive image

このinclude 外から別のHTMLファイルを呼んできてくれる。

{% include 'posts/pagination.html' %}

こうすることで見た目がスッキリしたり、 他のファイルで使いまわしたりすることができる。


続いて、pagination.htmlを作る。

とりあえず全体図。
Responsive image

Bootstrap様のサイト pagination alignment からお借りした。 基本のページネーションスタイル。
<nav aria-label="Page navigation example">
<ul class="pagination justify-content-center">
    <li class="page-item disabled">
    <a class="page-link" href="#" tabindex="-1">Previous</a>
    </li>
    <li class="page-item"><a class="page-link" href="#">1</a></li>
    <li class="page-item"><a class="page-link" href="#">2</a></li>
    <li class="page-item"><a class="page-link" href="#">3</a></li>
    <li class="page-item">
    <a class="page-link" href="#">Next</a>
    </li>
</ul>
</nav>

そこにJinja2のお言葉を付け足していく。
<nav aria-label="Page navigation example">
<ul class="pagination justify-content-center">
    {% if page.has_previous %}
        <li class="page-item"><a class="page-link" 
        href="{{ page.previous_page_number }}>前へ</a></li>
    {% else %}
        <li class="page-item disabled"><span></span></li>
    {% endif %}



</ul>
</nav>

ページが前に戻れるかどうか。
{% if page.has_previous %}

戻れる(前のページがある)なら
前のページ番号をリンクに貼り付ける。
href="{{ page.previous_page_number }}"

ちなみに上のリンク先はこうなります。
http://あなたのドメイン名/posts/ページ番号

ページ番号 = 記事のIDとなるので、
index.html内の
これと同じ働きになる。
href="{% url 'post' post.id %}"

なので、post関数が 個別の記事へいざなってくれる。

そして、前のページがなければ、 空っぽな場所を作る。
{% else %}
<li class="page-item disabled"><span></span></li>

bootstrap様が用意してくれた、 ページネーションの見た目を 変えるHTMLクラス名達。
page-item disabled page-item active 

if文をちゃんと閉じる。
{% endif %}

続いて、
<nav aria-label="Page navigation example">
<ul class="pagination justify-content-center">
    {% if page.has_previous %}
        <li class="page-item"><a class="page-link" 
        href="{{ page.previous_page_number }}>前へ</a></li>
    {% else %}
        <li class="page-item disabled"><span></span></li>
    {% endif %}

    どんどん付け足していこう。

    {% for i in page.paginator.page_range %}
        {% if page.number == i %}
            <li class="page-item active">
            <span class="page-link">{{ i }}
            <span class="sr-only">(current)
            </span></span></li>              
        {% else %}
            <li class="page-item">
            <a class="page-link" 
            href="{{ i }}">{{ i }}</a></li>
        {% endif %}
    {% endfor %}

</ul>
</nav>

全部のページの数だけぐるぐる回す。
{% for i in page.paginator.page_range %}   
{% if page.number == i %}
{% else %}
{% endif %}
{% endfor %}

今表示してるページ番号 と同じ番号がきたら
{% if page.number == i %}

アクティブにして、
現在表示中のページ番号だとわかるようにする。

<li class="page-item active">
<span class="page-link">{{ i }}

<span class="sr-only">(current)
</span></span></li>

sr-onlyは、Screen Reader Only の略。
簡単に雑に話すと、

目が不自由な方々への配慮です。

音声ソフト(スクリーンリーダー)が
このページネーションの場所は(current)だよ
と音声で読み上げてくれる。
画面上からは見えない。


    もしそうでないなら

    {% else %}

        ページ番号を表示。

        <li class="page-item">
        <a class="page-link" 
        href="{{ i }}">{{ i }}</a></li>

        リンク先も番号(記事のID番号と同じ)にする。

        IF文終了

    {% endif %}

    FOR文終了

{% endfor %}

お次は、

もし、次のページがあるなら、

{% if page.has_next %}

    次のページのリンク先をつけて「次へ」を表示。

    <li class="page-item">
    <a class="page-link" 
    href="{{ page.next_page_number }}">次へ</a></li>

そうではないなら、

{% else %}

    空っぽの表示。

    <li class="page-item disabled">
    <span></span></li>

IF文終了。

{% endif %}

最終形態。
<nav aria-label="Page navigation example">
<ul class="pagination justify-content-center">

    前への処理。

    {% if page.has_previous %}
        <li class="page-item"><a class="page-link" 
        href="{{ page.previous_page_number }}>前へ</a></li>
    {% else %}
        <li class="page-item disabled"><span></span></li>
    {% endif %}

    ページの番号表示とリンクへ飛ばす処理。

    {% for i in page.paginator.page_range %}
        {% if page.number == i %}
            <li class="page-item active">
            <span class="page-link">{{ i }}
            <span class="sr-only">(current)
            </span></span></li>              
        {% else %}
            <li class="page-item">
            <a class="page-link" 
            href="{{ i }}">{{ i }}</a></li>
        {% endif %}
    {% endfor %}

    次への処理。

    {% if page.has_next %}
        <li class="page-item">
        <a class="page-link" 
        href="{{ page.next_page_number }}">次へ</a></li>
    {% else %}
        <li class="page-item disabled">
        <span></span></li>
    {% endif %}

</ul>
</nav>

そしてこのまま保存する。

そしてサーバーを起こしてあげる。
python manage.py runserver

ブラウザも起こして、 検索窓に入力。
localhost:8000/posts

Responsive image

そしてトップ画面から、 個別ページへ飛んでみる。

赤い矢印が合っていれば成功。 3番目の記事は最後のページなので、 次へが表示されていない。
Responsive image


Responsive image


Responsive image

以上です。
お疲れさまでした。

See You Next Page !