MENU

Django Ninjaチュートリアル13(ページネーション)

Django Ninjaチュートリアル13(ページネーション)

前回までの記事はこちらです。

前回までの記事が読まれている前提で書いていきますので、まだ読んでいない方はこちらから読み進めて追いついてください。

それでは始めていきましょう。

目次

ページネーションの設定

Django Ninjaでは、ページネーションのサポートがあります。
これを使用することで、個々のページにリターンされたリザルトをセットすることができます。
ページネーションを適用するには、関数にpaginateデコレータを適用するだけで実現できます。
それでは、例を見てみます。

from ninja import NinjaAPI
from typing import List
from ninja.pagination import paginate

from example.Schema.api_v5_schema import UserSchema
from register.models import User

app_v5 = NinjaAPI(
  title = "Django-Ninja Sample App5",
  version = "1.0.5",
)

〜略〜
@app_v5.get("/users", response = List[UserSchema])
@paginate
def list_users(request):
  return User.objects.all()

以上です。
APIの定義をし、関数を書く前に@paginateを書くのみであるため、非常にシンプルな設定になっています。

これにより、パスパラメータで、limitoffsetを使用してusersについてGETメソッドを使用することができるようになります。

パスパラメータでlimitとoffsetを渡す場合は、以下のようになります。

http://127.0.0.1:8000/api_v5/users?limit=150&offset=2

また、自動生成ドキュメントでは、以下のように表示され、limitとoffsetを設定しexecuteを実行することで、同様の結果を得ることができます。
返されるレスポンスはリスト形式のJSONで返されます。

@paginateを設定したAPI

なお、limitはデフォルトで100に設定されています。
settings.py でNINJA_PAGINATION_PER_PAGEに値を渡すことでデフォルト値を変更することができます。

組み込みのページネーションクラスについて

組み込みのページネーションクラスを用いた設定が可能です。

LimitOffsetPagination(デフォルト)

LimitOffsetPaginationは、デフォルトのページネーションクラスです。
以下のように書くことができます。

from ninja import NinjaAPI
from typing import List
from ninja.pagination import paginate、LimitOffsetPagination

from example.Schema.api_v5_schema import UserSchema
from register.models import User

app_v5 = NinjaAPI(
  title = "Django-Ninja Sample App5",
  version = "1.0.5",
)

〜略〜
@app_v5.get("/users", response = List[UserSchema])
@paginate(LimitOffsetPagination)
def list_users(request):
  return User.objects.all()

このLimitOffsetPaginationを使用する方法は、先ほど設定した@paginateを書いただけのページネーションの設定と同義です。

このクラスには 2 つの入力パラメーターが設定されています。

  • limit: ページ毎のクエリセットの数を定義します
    →デフォルトは100で、settings.pyでNINJA_PAGINATION_PER_PAGEを設定することで変更可能
  • offset:ページウィンドウのオフセット(スタート位置)を設定します
    →デフォルトでは0となっており、インデックスは0から始まります。

PageNumberPagination

PageNumberPaginationは、ページ番号毎にクエリセットを100ずつ返すページネーションクラスです。
例を見てみます。

from ninja import NinjaAPI
from typing import List
from ninja.pagination import paginate、PageNumberPagination

from example.Schema.api_v5_schema import UserSchema
from register.models import User

app_v5 = NinjaAPI(
  title = "Django-Ninja Sample App5",
  version = "1.0.5",
)

〜略〜
@app_v5.get("/users", response = List[UserSchema])
@paginate(PageNumberPagination)
def list_users(request):
  return User.objects.all()

このページネーションクラスには1つのパラメーター(page)があり、デフォルトでページ毎に100個のクエリセットを出力します(settings.pyで変更することができます)。

また、page_sizeに値を渡すことでビューごとにクエリセットの数を個別に設定することもできます。

@app_v5.get("/users", response = List[UserSchema])
@paginate(PageNumberPagination, page_size = 1)
def list_users(request):
  return User.objects.all()

自動生成ドキュメントを見てみます。

page_sizeを設定していない場合

page_sizeを設定していない場合は、以下のように表示されます。
page(=ページ番号)に値を渡すと、該当するページに対するリザルトが確認できます。
デフォルトでは100件のクエリセットが返されます(100件なければ、すべてのクエリが返される)。

PageNumberPaginationのデフォルト設定

page_sizeを設定した場合

先ほど設定したpage_sizeの例で見てみます。

@app_v5.get("/users", response = List[UserSchema])
@paginate(PageNumberPagination, page_size = 1)
def list_users(request):
  return User.objects.all()

page_size1に設定しています。
これにより、ページ毎に表示されるクエリの数は、1件に制限されることになります。
自動生成ドキュメントで試してみます。
page_size1にしてexecuteしてみます。

page_sizeを設定した場合の自動生成ドキュメント

先ほどは2件表示されましたが、今回はページを指定しても、1件ずつしか表示されなくなりました。
このように、page_sizeを指定することで、各ページの表示件数を変えることが可能です。

view function内でのpaginatorパラメータへのアクセス

ページビューでページネーションを使用してInputパラメータにアクセスする必要がある場合は、pass_parameter引数を使用します。
また、この場合において、インプットデータとして、**kwargsを使用することができます。
使用例を見てみます。

@app_v5.get("/someview")
@paginate(pass_parameter = "pagination_info")
def someview(request, **kwargs):
  page = kwargs["pagination_info"].page
  return ...

上記の例は、公式ドキュメントから引用しています。
このまま実行しても機能しませんので、ご注意ください。

カスタムページネーションクラスの作成

カスタムのページネーションクラスを作成するには、ninja.pagination.PaginationBaseをサブクラス化し、InputスキーマクラスとOutputスキーマクラス、そしてpaginate_queryset(self, queryset, request, **params)メソッドをオーバーライドする必要があります。

from ninja.pagination import PaginationBase
from typing import List, Any

# CustomPaginationクラスの作成
class CustomPagination(PaginationBase): # ninjaのpaginationからPaginationBaseを継承
  
  # Inputスキーマを作成(paginatorに渡すパラメータを記述)
  class Input(Schema):
    skip: int # ○件目から表示させるかを設定するパラメータ
  
  # Outputスキーマを作成(ページ出力用のスキーマ)
  class Output(Schema):
    items: List[Any] # List型を使用(itemsはデフォルトの属性)
    total: int
    par_page: int
  
  
  def paginate_queryset(self, queryset, pagination: Input, **params):
    skip = pagination.skip
    return {
      "items": queryset[skip: skip + 5],
      "total": queryset.count(),
      "par_page": 5
    }
from ninja import NinjaAPI
from typing import List
from ninja.pagination import paginate, PageNumberPagination # add

from django.http import HttpRequest, HttpResponse
from example.Schema.api_v5_schema import UserSchema, ErrorSchema, TaskSchema, CustomPagination # add
from example.models import Task
from register.models import User

app_v5 = NinjaAPI(
  title = "Django-Ninja Sample App5",
  version = "1.0.5",
)

〜略〜

@app_v5.get("/custom_paginations_users", response = List[UserSchema])
@paginate(CustomPagination)
def list_users(request):
  return User.objects.all()

つまり、

  • Inputスキーマは、ページネーターに渡す必要があるパラメーターを記述するスキーマクラス(ページ番号、制限、オフセット値など)です。
  • Output schema は、ページ出力用のスキーマ(count/next-page/items/etc) を記述します。
  • このpaginate_querysetメソッドは、最初のクエリセットが渡され、そして要求されたページのデータのみを含む繰り返し使用することができるオブジェクトを返す必要があります。
    このメソッドは次の引数を受け入れます。
    • queryset: API関数によって返されるクエリセット(またはiterable)
    • paginationpaginator.Inputパラメーター(解析および検証済みであるもの)
    • **params: デコレータが付された関数が受け取ったすべての引数を含むkwargs

となります。

CustomPaginationの出力結果

上記のようにskipパラメータのみを設定しexecuteすると、結果が返されます。
ページ毎のitemstotalの件数、ページ毎の表示件数(par_page)が返されますので、これをフロントエンドに渡し、表示の切り替えを行います。

ちなみにOutput属性はデフォルトでitemsとなりますが、これを任意の値(例えばresultsなど)に変えたい場合は、その後にitems_attributeを使用し、オーバーライドします。

class Output(Schema):
  results: List[Any] # デフォルトはitemsだが、resultsを使用
  total: int
  per_page: int

  items_attribute: str = "results" # resultsを指定し、items_attributeでオーバーライドする

複数のAPIにページネーションを設定する

アプリ開発をする場合、クエリセットやリストを返すすべてのビューにページネーションを追加する必要がある場合があるかもしれません。
この場合、組み込みルータークラスであるRouterPaginatedを使用し、response = List[SomeSchema]というように定義されたすべての操作に対して、ページネーションを自動的に挿入することができます。

from ninja import NinjaAPI
from typing import List
from ninja.pagination import paginate, PageNumberPagination, RouterPaginated

from django.http import HttpRequest, HttpResponse
from example.Schema.api_v5_schema import UserSchema, ErrorSchema, TaskSchema, CustomPagination
from example.models import Task
from register.models import User

app_v5 = NinjaAPI(
  title = "Django-Ninja Sample App5",
  version = "1.0.5",
)

router = RouterPaginated()

app_v5.add_router("/router_example/", router)

〜略〜

@router.get("/users", response = List[UserSchema])
@paginate(PageNumberPagination, page_size = 1)
def list_users(request):
  return User.objects.all()

@router.get("/custom_paginations_users", response = List[UserSchema])
@paginate(CustomPagination)
def list_custom_users(request):
  return User.objects.all()

これまでに作ったuserをリスト形式で返すAPIを変更します。
まず、ninja.paginationからRouterPaginatedをインポートします。
そしてインポートしたRouterPaginatedからrouterを生成します。
さらに、すでに定義しているapp_v5add_routerrouterから生成されるAPIを認識させます。
元々、app_v5としていた箇所をrouterに置き換えると、これらのAPIではページネーションが適用されるようになります。

既存のappメインインスタンス等を利用する場合

また、routerを使用せずに、既存のappメインインスタンス等に対してページネーションを設定することもできます。
設定は以下のとおりです。

from ninja import NinjaAPI
from typing import List
from ninja.pagination import paginate, PageNumberPagination, RouterPaginated

from django.http import HttpRequest, HttpResponse
from example.Schema.api_v5_schema import UserSchema, ErrorSchema, TaskSchema, CustomPagination
from example.models import Task
from register.models import User

app_v5 = NinjaAPI(
  title = "Django-Ninja Sample App5",
  version = "1.0.5",
  default_router = RouterPaginated()
)


〜略〜

@app_v5.get("/users", response = List[UserSchema])
@paginate(PageNumberPagination, page_size = 1)
def list_users(request):
  return User.objects.all()

@app_v5.get("/custom_paginations_users", response = List[UserSchema])
@paginate(CustomPagination)
def list_custom_users(request):
  return User.objects.all()

結果は、先ほどのrouterの部分がapp_v5に戻っただけで、同じ結果が得られます。
ケースに応じて使い分ける必要があると思います。

最後に

今回は、Django Ninjaにおけるページネーションの設定をみてきました。
Webサイトなどでは、表示件数のコントロールは欠かせませんので、ページネーションの知識は必要になるでしょう。

次は、Django Ninjaにおけるカスタムレンダラーについてみていきます。

よければ次回もご覧下さい。
今回はここまでです。ありがとうございました。

Django Ninjaチュートリアル13(ページネーション)

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

はじめまして、ふじです。
Python、Django、FastAPI、React.js、Next.jsを学習している、ずっと文系のプログラミング独学者です。

目次