ページの改良

はじめに

前回に引き続きトップページを見やすい形にしていきます。やることは、本を登録した新着順、レビューの平均点順に並び替えをします。それからページネーションをつけて1ページに2冊ずつ本を見れるようにします。


トップページのデータの並び替え

新着順に並び替え

新着順に並び替えるにはbook/views.pyを以下のように編集します。

def index_view(request):
    object_list = Book.objects.order_by('-id') # 修正


    return render( request, 'book/index.html', {'object_list': object_list})

修正した-idとすることで新着順にしています。本の追加した順にidは1,2,3...と増えていくのでidの古いものからとすると、新しい本からというようになります。

レビューの平均点順に並び替え

平均点順にはbook/views.pyを以下のようにします。

from django.db.models import Avg

def index_view(request):
    object_list = Book.objects.all
    ranking_list = Book.objects.annotate(avg_rating=Avg('review__rate')).order_by('-avg_rating')

    return render(
        request,
        'book/index.html',
        {'object_list': object_list, 'ranking_list': ranking_list},
    )

追加したAvg('review__rate')を使うことでデータの平均値を取ります。どのデータの平均値をとるのかは、'review__rate'で指定します。つまりreviewが対象とするモデル、rateがそのフィールドを示しています。

できればbook/templates/book/index.htmlを修正し、投稿順とランキング順に並び替えます。

{% extends 'base.html' %}

{% block title %}本棚アプリケーション{% endblock %}
{% block h1 %}本棚アプリケーション{% endblock %}

{% block content %}
<div class="row">
    <div class="col-9">
    {% for item in object_list %}
        <div class="p-4 m-4 bg-light border border-success rounded">
            <h2 class="text-success">{{ item.title }}</h2>
            <img src="{{ item.thumbnail.url }}" class="img-thumbnail" />
            <h6>カテゴリー: {{ item.category }}</h6>
            <div class="mt-3">
                <a href="{% url 'detail-book' item.pk %}">詳細へ</a>
            </div>
        </div>
    {% endfor %}
    </div>
    <div class="col-3">
        {% for ranking_book in ranking_list %}
            </div class="p-4 m-4 bg-light border border-success rounded">
                <h3 class="text-success h5">{{ ranking_book.title }}</h3>
                <img src="{{ ranking_book.thumbnail.url }}" class="img-thumbnail" />
                <h6>評価:{{ ranking_book.avg_rating}点</h6>
                <a href="{% url 'detail-book' ranking_book.id %}">評価を見る</a>
            </div>
        {% endfor %}
    </div>
</div>
</div>
{% endblock content %}

これで新着順、評価順に並び替えることができました。少し見た目を整えるために評価の小数点を修正します。
book/templates/book/index.htmlを以下のように修正します。

        {% for ranking_book in page_obj %}
            </div class="p-4 m-4 bg-light border border-success rounded">
                <h3 class="text-success h5">{{ ranking_book.title }}</h3>
                <img src="{{ ranking_book.thumbnail.url }}" class="img-thumbnail" />
                <h6>評価:{{ ranking_book.avg_rating|floatformat:2 }}点</h6> # 修正
                <a href="{% url 'detail-book' ranking_book.id %}">評価を見る</a>
            </div>
        {% endfor %}

追加した|floatformat:で小数点以下を四捨五入することができます。その後ろに2とすることで小数点以下第2位まで見れるようにしました。


書籍の詳細にレビューを追加する

ここでは複数のレビューを追加、閲覧できるようにします。やりかたはbook/templates/book/detail.htmlに以下を追加します。

<p>{{ object.text }}</p>
            <div class="border p-4 mb-2"> # 追加
                {% for review in object.review_set.all %} # 追加
                <div> # 追加
                    <h3 class="h4">{{ review.title }}</h3> # 追加
                    <div class="px-2"> # 追加
                        <span>(投稿ユーザー: {{ review.user.username }})</span> # 追加
                        <h6>評価: {{ review.rate }}点</h6> # 追加
                        <p>{{ review.text }}</p> # 追加
                    </div> # 追加
                </div> # 追加
                {% endfor %} # 追加
            </div> # 追加
            <a href="{% url 'list-book' %}" class="btn btn-primary">一覧へ</a>

これで一度に複数のレビューに対応することができます。
以下のように複数のレビューが表示されるでしょう。

ページネーションの作成

本棚アプリケーションで本が何百冊となった場合、10冊ずつの閲覧ができたほうが見栄えが良いでしょう。ここでは2冊ずつでページネーションを作成していきます。
今回はfunciton-based viewとclass-based viewの場合の実装をしていきます。

funciton-based viewでのページネーションの作成

まずはbook/views.pyとbook/consts.pyに以下を追加します。

from django.core.paginator import Paginator # 追加
from .consts import ITEM_PER_PAGE # 追加

def index_view(request):
    object_list = Book.objects.all
    ranking_list = Book.objects.annotate(avg_rating=Avg('review__rate')).order_by('-avg_rating')

    paginator = Paginator(ranking_list, ITEM_PER_PAGE) # 追加
    page_number = request.GET.get('page',1) # 追加
    page_obj = paginator.page(page_number) # 追加

    return render(
        request,
        'book/index.html',
        {'object_list': object_list, 'ranking_list': ranking_list, 'page_obj': page_obj}, # 追加
    )
MAX_RATE = 5
ITEM_PER_PAGE = 2 # 追加

from .consts import ITEM_PER_PAGEで一つのページに表示する本の数を指定するために定義しておき、ITEM_PER_PAGE = 2と2つずつ表示させるようにしています。ここを違う数にすると10冊ずつのように変更ができます。


次はhtmlファイルの中にページネーションの仕組みをいれていきます。そのため、以下のコマンドで新たなファイルを作成しましょう。

mkdir book/templates/book/components
mkdir book/templates/book/components/paginations.html

作成したファイルに以下の記述をします。

{% if page_obj.has_other_pages %}
    <ul class="list-unstyled m-0 d-flex justify-content-between">
        {% if page_obj.has_previous %}
            <li><a href="?page={{ page_obj.previous_page_number }}">&lt;&lt;前へ</a></li>
        {% else %}
            <li class="text-muted">&lt;&lt;前へ</li>
        {% endif %}
        {% if page_obj.has_next %}
                <li><a href="?page={{ page_obj.next_page_number }}">次へ&gt;&gt;</a></li>
        {% else %}
            <li class="text-muted">次へ&gt;&gt;</li>
        {% endif %}
    </ul>
{% endif %}

基本的にはpage_objを使って条件分岐を行っています。

<a href="?page={{ page_obj.previous_page_number }}">&lt;&lt;前へ</a>

の部分で前のページがある場合は"前へ"と表示し、前のページのリンクを挿入することでページネーションの実装を行っています。
次への部分も基本的には同じです。

あとは記述したcomponentsファイルをhtmlファイルに組み込みます。book/templates/book/index.htmlを以下のように変更します。

<div class="col-3">
        <h2>評価順TOP2</h2> # 変更
        {% for ranking_book in page_obj %} # 変更
            </div class="p-4 m-4 bg-light border border-success rounded">
                <h3 class="text-success h5">{{ ranking_book.title }}</h3>
                <img src="{{ ranking_book.thumbnail.url }}" class="img-thumbnail" />
                <h6>評価:{{ ranking_book.avg_rating|floatformat:2 }}点</h6>
                <a href="{% url 'detail-book' ranking_book.id %}">評価を見る</a>
            </div>
        {% endfor %}
        {% include 'book/components/pagination.html' %} # 変更
    </div>

これで一度確認してみましょう。

前へのリンクははじめのページなのでありませんが、次へはあります。これでページネーションの作成ができました。

class-based viewでのページネーションの作成

次は、class-based viewでページネーションの作成をします。
book/views.pyに以下の記述を追加します。

class ListBookView(LoginRequiredMixin, ListView):
    template_name = 'book/book_list.html'
    model = Book
    paginate_by = ITEM_PER_PAGE # 追加

実はこれだけでできます。あとはhtmlにページネーションに関する記述を追加します。book/templates/book/book_list.htmlに以下の記述を追加します。

{% extends 'base.html' %}

{% block title %}書籍一覧{% endblock %}
{% block h1 %}書籍一覧{% endblock %}

{% block content %}
    {% for item in object_list %}
    <div class="p-4 m-4 bg-light border border-success rounded">
        <h2 class="text-success">{{ item.title }}</h2>
        <h6>カテゴリー:{{ item.category }}</h6>
        <div class="mt-3">
            <a href="{% url 'detail-book' item.pk %}">詳細へ</a>
        </div>
    </div>
    {% endfor %}
    {% include 'book/components/pagination.html' %} # 追加
{% endblock content %}

これで同じように確認してみると良いでしょう。class-based viewでもページネーションの作成ができました。


おわりに

今回は新しい順、評価順に並び替えて表示するような変更を行いました。また、2冊ずつでページネーションをつけて見やすいページになるようにしました。

これで本棚アプリケーションの作成はおおよそ終了しました。
次回は作成した本棚アプリケーションをHerokuで公開します。