Pythonメモ torinaブログ

このブログはDjangoとBootstrap4で作成されました
ソースコード

Djangoで、前のデータと次のデータ

プログラミング関連 Django 約19日前
2017年4月6日11:04
つい先日、ブログにこのような機能を実装しました。次の記事、前の記事、です。今回はこれを作成してみます。


models.py


まず、このようなモデルがあったとします。
class Post(models.Model):

    title = models.CharField('タイトル', max_length=255)
    created_at = models.DateTimeField('作成日', auto_now_add=True)

    def __str__(self):
        return f'pk:{self.pk} title:{self.title} created_at:{self.created_at}'


python manage.py shell で、ちょっと見てみましょう。
データを4件足しました。
>>> from app.models import Post
>>> for post in Post.objects.all():
...     print(post)
...
pk:137 title:おはよう created_at:2017-04-07 14:01:28.330511+00:00
pk:138 title:こんにちは created_at:2017-04-07 14:01:31.704052+00:00
pk:139 title:こんばんは created_at:2017-04-07 14:01:35.191542+00:00
pk:140 title:おやすみ created_at:2017-04-07 14:01:37.929762+00:00
>>>


「こんにちは」のPostから見ると、前の記事は「おはよう」で、次の記事は「こんばんは」になりそうですね。
どう取得するかですが、スタックオーバーフローにいいのがありました。
http://stackoverflow.com/questions/2214852/next-previous-links-from-a-query-set-generic-views

models.pyのPostに、以下のメソッドを足します。
    def get_next(self):
        """次のデータを取得する"""

        next_post = Post.objects.filter(pk__gt=self.pk)
        if next_post:
            return next_post.first()
        return False

    def get_prev(self):
        """前のデータを取得する"""

        prev_post = Post.objects.filter(pk__lt=self.pk).order_by('-pk')
        if prev_post:
            return prev_post.first()
        return False


まずはget_nextですが、内部のコードを実際に試してみましょう。「こんにちは」基準ということで、pkには138を入れます。
>>> next_post = Post.objects.filter(pk__gt=138)
>>> for post in next_post:
...     print(post)
...


Post.objects.filter(pk__gt=138)は、pkが138より大きいものを取得します。
結果もちゃんと取れてますね。
pk:139 title:こんばんは created_at:2017-04-07 14:01:35.191542+00:00
pk:140 title:おやすみ created_at:2017-04-07 14:01:37.929762+00:00


これにfirst()を呼ぶと、クエリセットの最初のオブジェクトを返します。なので、「こんばんは」が取得できるわけです。
>>> next_post.first()
<Post: pk:139 title:こんばんは created_at:2017-04-07 14:01:35.191542+00:00>



次はprev_postです。これも内容は大体同じです...諸事情で、「こんばんは」を基準に行います。
>>> prev_post = Post.objects.filter(pk__lt=139)
>>> for post in prev_post:
...     print(post)



Post.objects.filter(pk__lt=138)は、pkが139より小さい、です。
pk:137 title:おはよう created_at:2017-04-07 14:01:28.330511+00:00
pk:138 title:こんにちは created_at:2017-04-07 14:01:31.704052+00:00


これにfirst()を呼ぶと、ちょっとよろしくない動作になりますね。欲しい結果はpk138の「こんにちは」です。
>>> prev_post.first()
<Post: pk:137 title:おはよう created_at:2017-04-07 14:01:28.330511+00:00>


last()を使ってしまえば大丈夫です。
スタックオーバーフローでは、先に.order_by('-pk')で逆順に並び替えていましたね。
>>> prev_post.last()
<Post: pk:138 title:こんにちは created_at:2017-04-07 14:01:31.704052+00:00>


テンプレートでは、例えば以下のように利用できます!
    {% if post.get_prev %} 
    <a href="{% url 'detail' post.get_prev.pk %}">Prev {{ post.get_prev.title }}</a> 
    {% endif %}

    {% if post.get_next %} 
    <a href="{% url 'detail' post.get_next.pk %}">Next {{ post.get_next.title }}</a> 
    {% endif %}



今回の動作はpkの値ですが、日付にしたいならこんな感じです。
    def get_next(self):
        """次のデータを取得する"""

        next_post = Post.objects.filter(created_at__gt=self.created_at)
        if next_post:
            return next_post.first()
        return False

    def get_prev(self):
        """前のデータを取得する"""

        prev_post = Post.objects.filter(created_at__lt=self.created_at)
        if prev_post:
            return prev_post.last()
        return False