シンプルブログ、記事一覧ページの作成③

Django REST framework Vue.js

概要

DRFとVue.jsで、シンプルブログを作るシリーズの一つです。記事一覧ページを作成する続きです。

REST frameworkでページネーションを実装する

ページネーションを実装していきます。Django REST frameworkでのページネーション実装は簡単で、例えばビューにて次のように指定します。

# 追加
from rest_framework import generics, pagination


class StandardResultsSetPagination(pagination.PageNumberPagination):
    page_size = 10


class PostList(generics.ListAPIView):
    queryset = Post.objects.all()
    serializer_class = SimplePostSerializer
    pagination_class = StandardResultsSetPagination

ページネーションクラス(StandardResultsSetPagination)を作成し、それを各ビューのクラス属性に指定(pagination_class = StandardResultsSetPagination)します。この結果、返されるJSONは次のようになります(わかりやすいように、page_sizeを1にして実行)。

{
    "count": 2,
    "next": "http://127.0.0.1:8000/design-note/api/posts/?page=2",
    "previous": null,
    "results": [
        {
            "id": 2,
            "category": {
                "id": 1,
                "name": "カテゴリA",
                "color": "#333"
            },
            "title": "記事B",
            "thumbnail": "http://127.0.0.1:8000/media/%E5%90%8D%E5%88%BA%E3%82%B5%E3%83%A0%E3%83%8D_jLXQJEw.png",
            "lead_text": "紹介文"
        }
    ]
}

countに全件数が、nextには次のページ取得のためのURL、previousは前ページがないのでnull、resultsにはデータの一覧があります。簡単に実装できましたが、今回はもう少し手を加えようと思います。次のようにします。

# 追加
from rest_framework import generics, pagination, response

class StandardResultsSetPagination(pagination.PageNumberPagination):
    page_size = 1

    def get_paginated_response(self, data):
        return response.Response({
            'next': self.get_next_link(),
            'previous': self.get_previous_link(),
            'count': self.page.paginator.count,
            'total_pages': self.page.paginator.num_pages,
            'current_page': self.page.number,
            'results': data,
            'page_size': self.page_size,
            'range_first': (self.page.number * self.page_size) - (self.page_size) + 1,
            'range_last': min((self.page.number * self.page_size), self.page.paginator.count),
        })

get_paginated_responseを上書きすると、返されるJSONの内容に手を加えることができます。今回は次のようなJSONとなります。

{
    "next": "http://127.0.0.1:8000/design-note/api/posts/?page=2",
    "previous": null,
    "count": 5,
    "total_pages": 5,
    "current_page": 1,
    "results": [
        {
            "id": 5,
            "category": {
                "id": 1,
                "name": "カテゴリA",
                "color": "#333"
            },
            "title": "記事E",
            "thumbnail": "http://127.0.0.1:8000/media/%E5%90%8D%E5%88%BA%E3%82%B5%E3%83%A0%E3%83%8D_0te5Mqa.png",
            "lead_text": "記事E"
        }
    ],
    "page_size": 1,
    "range_first": 1,
    "range_last": 1
}

幾つか増えました。total_pagesは一番最後のページ番号です。current_pageは今のページ番号。page_sizeは何件ずつの表示か、range_firstrange_lastは今、何番目から何番目の記事を表示しているかを表しています。

Vue側にも反映する

このJSONを使って、Vue側のページネーション処理を実装しましょう。まずはstore/index.js

    getters: {
        getPreviousURL(state) {
            return state.posts.previous
        },

        getNextURL(state) {
            return state.posts.next
        },

        hasPrevious(state) {
            return !!state.posts.previous
        },

        hasNext(state) {
            return !!state.posts.next
        },

        postRangeFirst(state) {
            return state.posts.range_first
        },

        postRangeLast(state) {
            return state.posts.range_last
        },

        postCurrentPageNumber(state) {
            return state.posts.current_page
        },

        postCount(state) {
            return state.posts.count
        },

        postList(state) {
            return state.posts.results
        },
    },

JSONに対応する各種ゲッターを定義しました。postListゲッターは前からありましたが、state.postsからstate.posts.resultに変わることに注意しましょう。

幾つかアイコンが増えるので、こちらの内容をダウンロードして、中身のファイルをsrc/assets内に配置しておいてください。

PostList.vueを変更します。まずはtemplate部分です。

<template>
    <main class="container">
        <p id="lead">{{postCount}}件中 {{postRangeFirst}}~{{postRangeLast}}件を一覧表示</p>
        <section>
            <article class="post" v-for="post of postList" :key="post.id">
                <figure>
                    <img :src="post.thumbnail" :alt="post.title" class="thumbnail">
                </figure>
                <p class="post-category" :style="{'color': post.category.color}">{{post.category.name}}</p>
                <h2 class="post-title">{{post.title}}</h2>
                <p class="post-lead">{{post.lead_text}}</p>
            </article>
        </section>
        <hr class="divider">
        <nav id="page">
            <a v-if="hasPrevious" @click="getPostPrevious" id="back"><img src="@/assets/back.png"></a>
            <span>Page {{postCurrentPageNumber}}</span>
            <a v-if="hasNext" @click="getPostNext" id="next"><img src="@/assets/next.png"></a>
        </nav>
    </main>
</template>

表示件数や、ページ番号、前ページ次ページのリンクを表示するようにしました。参照しているプロパティやメソッドは、次のscript部分です。

<script>
    import {mapGetters, mapActions} from 'vuex'
    import {UPDATE_POSTS} from "../store/mutation-types";

    export default {
        name: 'post-list',
        computed: {
            ...mapGetters([
                'postList', 'postCount', 'postRangeFirst', 'postRangeLast',
                'postCurrentPageNumber', 'hasPrevious', 'hasNext', 'getPreviousURL', 'getNextURL'
            ]),
        },
        methods: {
            ...mapActions([UPDATE_POSTS]),
            getPostPrevious() {
                this.$http(this.getPreviousURL)
                    .then(response => {
                        return response.json()
                    })
                    .then(data => {
                        this[UPDATE_POSTS](data)
                    })
            },
            getPostNext() {
                this.$http(this.getNextURL)
                    .then(response => {
                        return response.json()
                    })
                    .then(data => {
                        this[UPDATE_POSTS](data)
                    })
            }
        },
        created() {
            this.$http(this.$httpPosts)
                .then(response => {
                    return response.json()
                })
                .then(data => {
                    this[UPDATE_POSTS](data)
                })
        }
    }
</script>

mapGettersで利用するゲッターを登録します。前ページ・次ページリンクを押したときのgetPostPreviousgetPostNextメソッドでは、次や前のページデータを取得して、一覧データを更新するようにしました。

これで前ページや次ページの移動ができるようになります。次の画像は、分かりやすいようにpage_sizeを1にしたときの動作確認です。

Relation Posts

Comment

記事にコメントする

まだコメントはありません。