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 を使うことにする。
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 ...