---
slug: "複雑なクエリで検索したDjangoモデルインスタンスをPaginatorでページングする"
title: "複雑なクエリで検索した Django モデルインスタンスを Paginator でページングする"
description: "Django で複雑なクエリ (raw SQL や Q オブジェクトの組み合わせ) で取得したモデルインスタンスを Paginator でページングする実装パターン。"
url: "https://www.ytyng.com/blog/複雑なクエリで検索したDjangoモデルインスタンスをPaginatorでページングする"
publish_date: "2015-10-15T10:10:34Z"
created: "2015-10-15T10:10:34Z"
updated: "2026-05-11T13:05:50.875Z"
categories: ["Django"]
keywords: ""
featured_image_url: "https://media.ytyng.com/resize/20230812/29a0edf0c96b4f4890196c7eb0ff1a88.png.webp?width=768"
has_video: false
has_music: false
video_urls: []
music_urls: []
lang: "ja"
---

# 複雑なクエリで検索した Django モデルインスタンスを Paginator でページングする

<div class="document">


<pre class="literal-block">class Content(models.Model):
    content_name = models.Charfield(...)
    group_id = models.PositiveIntegerField(...)
    volume_number = models.PositiveIntegerField(...)
    ...
</pre>
<p>典型的な Django のモデルクラスががあるとして、そのインスタンスを複雑な SQL 1発で検索したい。
結果は Web ページに表示したいが、多くの行になることが予想されるため、パジネータを表示したい。</p>
<p>例えば、上記モデルでいうと、同じ group_id の中で volume_number が最大のもののみを一覧で見たい、など。</p>
<p>パジネータは一般的な django.core.paginator.Paginator を使うことにする。</p>
<div class="section" id="paginator">
<h3>基本的な Paginator の使い方のおさらい</h3>
<pre class="literal-block">contents = Content.objects.filter(...)
paginator = Paginator(contents, 100)
</pre>
<p>このように、第一引数にクエリセットを入れるとページングできる。リストでも良い。</p>
<p>Paginator のコードを見ると、スライスと .count() が実装されてれば動くことがわかる。 .count は __len__ でもいい。</p>
<p>なので、複雑な SQL で抽出したインスタンスのリスト (クエリセットにできない) を、独自のクラスインスタンスで包み、そこにスライスと .count() を実装すれば良い。スライスは、「ステップ」の機能があるが、面倒なのでステップは実装しない。
(ステップ: hoge_sequence[0:100:2] ←このスライスの3つめに数字が入るやつ)</p>
</div>
<div class="section" id="id1">
<h3>実装コード</h3>
<p>モデルクラス</p>
<pre class="literal-block">class Content(models.Model):
    content_name = models.Charfield(...)
    group_id = models.PositiveIntegerField(...)
    volume_number = models.PositiveIntegerField(...)
    ...

    @classmethod
    def last_volumes(cls, limit=100, offset=0):
        """
        どうしても生のSQLで検索したい場合のメソッド
        objects.raw() を使って、モデルインスタンスにする。
        SQL ちょっとアレだけど、気にしないでください
        """
        sql = """SELECT t1.*
FROM (
  SELECT *
  FROM content_content
  ORDER BY volume_number DESC
) t1
GROUP BY group_id
LIMIT %s OFFSET %s
"""
        return cls.objects.raw(sql, params=[limit, offset])
</pre>
<p>パジネータに入れる用のクラス</p>
<pre class="literal-block">class LastContents(object):
    """
    Paginator に食わせる用のクラス。
    スライスと、count() を実装する
    """

    def __getitem__(self, key):
        if isinstance(key, slice):
            # スライスの場合 (例: instance[0:100])
            # step は無視!
            limit = key.stop - key.start
            return Content.last_volumes(
                limit=limit, offset=key.start)
       # 要素を1つ get するケース (例: instance[50])
       # 今回は使わないけど
       return Content.last_volumes(limit=1, offset=key)[0]

    def count(self):
        # 全件数が取得できるメソッドを実装しておく
        return Content.objects.filter(volume_number=1).count()
</pre>
<p>view関数</p>
<pre class="literal-block"># さきほどの LastContents のインスタンスをおもむろにぶちこむ!
paginator = Paginator(LastContents(), 100)

# ページングできる。
page = paginator.page(page_number)

page.object_list ...
</pre>
</div>
</div>
