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

Django
2015-10-15 10:10 (10 years ago)
複雑なクエリで検索した Django モデルインスタンスを Paginator でページングする
class Content(models.Model):
    content_name = models.Charfield(...)
    group_id = models.PositiveIntegerField(...)
    volume_number = models.PositiveIntegerField(...)
    ...

典型的な Django のモデルクラスががあるとして、そのインスタンスを複雑な SQL 1発で検索したい。 結果は Web ページに表示したいが、多くの行になることが予想されるため、パジネータを表示したい。

例えば、上記モデルでいうと、同じ group_id の中で volume_number が最大のもののみを一覧で見たい、など。

パジネータは一般的な django.core.paginator.Paginator を使うことにする。

基本的な Paginator の使い方のおさらい

contents = Content.objects.filter(...)
paginator = Paginator(contents, 100)

このように、第一引数にクエリセットを入れるとページングできる。リストでも良い。

Paginator のコードを見ると、スライスと .count() が実装されてれば動くことがわかる。 .count は __len__ でもいい。

なので、複雑な SQL で抽出したインスタンスのリスト (クエリセットにできない) を、独自のクラスインスタンスで包み、そこにスライスと .count() を実装すれば良い。スライスは、「ステップ」の機能があるが、面倒なのでステップは実装しない。 (ステップ: hoge_sequence[0:100:2] ←このスライスの3つめに数字が入るやつ)

実装コード

モデルクラス

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])

パジネータに入れる用のクラス

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()

view関数

# さきほどの LastContents のインスタンスをおもむろにぶちこむ!
paginator = Paginator(LastContents(), 100)

ページングできる。

page = paginator.page(page_number)

page.object_list ...

現在の評価: 1.7 (3)
著者は、アプリケーション開発会社 Cyberneura を運営しています。
開発相談をお待ちしています。

アーカイブ