.. _pagination:
Pagination
==========
Description
-----------
**Pagination** is used to split data across several pages. It is a well known pattern to optimise performance on large dataset.
DSG works with pagination the same way `DRF Pagination `_ does. It uses the `Django Pagination system `_ and its `Paginator` class.
That means DSG supports the default pagination class from DRF like `PageNumberPagination `_, `LimitOffsetPagination `_ and `CursorPagination `_. It also supports creating your `own Pagination `_ for advanced pagination functionalities.
Pagination can be :ref:`enabled globally` for the all the project or for :ref:`only some services `.
To use pagination you may use metadata or request depending on the :ref:`PAGINATION_BEHAVIOR setting`, See :ref:`example for how to use it`.
.. _pagination-using-it-globally:
Using it globally on all List and Stream actions
------------------------------------------------
To enable pagination globally you need to set the :ref:`DEFAULT_PAGINATION_CLASS settings` to the pagination class you want to use.
.. code-block:: python
# settings.py
GRPC_FRAMEWORK = {
...
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination"
...
}
.. _pagination-using-it-by-service:
Using it by service on List action
----------------------------------
To enable pagination only for one or some service you can override the :func:`pagination_class ` attribute.
.. code-block:: python
# server
# quickstart/services.py
from django_socio_grpc import generics
from quickstart.models import Post
from quickstart.serializer import PostProtoSerializer
from rest_framework.pagination import PageNumberPagination
# This service will have all the CRUD actions
class PostService(generics.AsyncModelService):
queryset = Post.objects.all()
serializer_class = PostProtoSerializer
pagination_class = PageNumberPagination
.. _pagination-using-it-by-action:
Using it on specific action when using filter in message
--------------------------------------------------------
If your :ref:`PAGINATION_BEHAVIOR setting` is set to ``REQUEST_STRUCT_STRICT`` or ``METADATA_AND_REQUEST_STRUCT``
and you want to use filtering for your custom action by message and not metadata (:ref:`See Using It section `)
you need to use the :func:`PaginationGenerationPlugin `
as demonstrated below (:ref:`See Generation Plugin documentation `):
.. code-block:: python
# server
# quickstart/services.py
from django_socio_grpc import generics
from quickstart.models import Post
from quickstart.serializer import PostProtoSerializer
from rest_framework.pagination import PageNumberPagination
from django_socio_grpc.decorators import grpc_action
from django_socio_grpc.protobuf.generation_plugin import ListGenerationPlugin, PaginationGenerationPlugin
# This service will have all the CRUD actions
class PostService(generics.GenericService):
queryset = Post.objects.all()
serializer_class = PostProtoSerializer
pagination_class = PageNumberPagination
@grpc_action(
request=[],
response=PostProtoSerializer,
use_generation_plugins=[ListGenerationPlugin(request=True), PaginationGenerationPlugin()],
)
async def CustomListWithPagination(self, request, context):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
if hasattr(serializer.message, "count"):
serializer.message.count = self.paginator.page.paginator.count
return serializer.message
else:
serializer = self.get_serializer(queryset, many=True)
return serializer.message
.. _pagination-using-it:
Using it
--------
You can use metadata or ``_pagination`` request field to make the filters work out of the box.
For more example you can see the `client in DSG example repo `_
.. code-block:: python
# client
import asyncio
import grpc
import json
async def main():
##########################################################################################################
# Working if PAGINATION_BEHAVIOR settings is equal to "METADATA_STRICT" or "METADATA_AND_REQUEST_STRUCT" #
##########################################################################################################
async with grpc.aio.insecure_channel("localhost:50051") as channel:
quickstart_client = quickstart_pb2_grpc.PostControllerStub(channel)
request = quickstart_pb2.PostListRequest()
# Getting the 11 to 20 elements following backend ordering
pagination_as_dict = {"page": 2, "page_size": 10}
metadata = (("pagination", (json.dumps(pagination_as_dict))),)
response = await quickstart_client.List(request, metadata=metadata)
################################################################################################################
# Working if PAGINATION_BEHAVIOR settings is equal to "REQUEST_STRUCT_STRICT" or "METADATA_AND_REQUEST_STRUCT" #
################################################################################################################
async with grpc.aio.insecure_channel("localhost:50051") as channel:
quickstart_client = quickstart_pb2_grpc.PostControllerStub(channel)
# Getting the 11 to 20 elements following backend ordering
pagination_as_dict = {"page": 2, "page_size": 6}
pagination_as_struct = struct_pb2.Struct()
pagination_as_struct.update(pagination_as_dict)
# _pagination field is only generated if you set PAGINATION_BEHAVIOR to the correct options. Think to regenerate proto after changing it.
request = quickstart_pb2.PostListRequest(_pagination=pagination_as_struct)
response = await quickstart_client.List(request)
if __name__ == "__main__":
asyncio.run(main())
.. _pagination-with-page-size:
Using page size
---------------
As specified in the `DRF Pagination `_ documentation, you need to override the ``PageNumberPagination`` class to use page size.
.. code-block:: python
# server
# quickstart/pagination.py
from rest_framework.pagination import PageNumberPagination
class CustomPageNumberPagination(PageNumberPagination):
page_size = 10
page_size_query_param = 'page_size'
max_page_size = 1000
# quickstart/services.py
...
from quickstart.pagination import CustomPageNumberPagination
...
class PostService(generics.GenericService):
...
pagination_class = CustomPageNumberPagination
...
.. _pagination-web-usage:
Web Example
-----------
For web usage of the client see :ref:`How to web: Using JS client`
.. code-block:: javascript
import { Struct } from "@bufbuild/protobuf";
// See web usage to understand how to use the client.
const postClient = createPromiseClient(PostController, transport);
const paginationStruct = Struct.fromJson({page: 2});
const res = await postClient.list({ Pagination: paginationStruct }); // _pagination is transformed to Pagination in buf build used by connect
console.log(res)
.. warning::
The following example is the deprecated way of using pagination. Please use the example above.
Note that the examples work depending on the :ref:`PAGINATION_BEHAVIOR setting` settings.
.. code-block:: javascript
// See web usage to understand how to use the client.
const postClient = createPromiseClient(PostController, transport);
headers = {pagination: JSON.stringify({page: 2})}
const res = await postClient.list({}, {headers})
console.log(res)