Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions docs/developer/other-utilities.rst
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,45 @@ Every openwisp module which has an API should use this class to configure
its own default settings, which will be merged with the settings of the
other modules.

``openwisp_utils.api.pagination.OpenWispPagination``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

A reusable pagination class for DRF views that provides consistent
pagination behavior across OpenWISP modules with sensible defaults.

- **10** items per page (``page_size``)
- **100** max items per page (``max_page_size``)
- Custom page size via ``?page_size=N`` query parameter

**Usage in Views:**

.. code-block:: python

from openwisp_utils.api.pagination import OpenWispPagination
from rest_framework.viewsets import ModelViewSet


class DeviceViewSet(ModelViewSet):
queryset = Device.objects.all()
serializer_class = DeviceSerializer
pagination_class = OpenWispPagination

**API Request Examples:**

.. code-block:: bash

# Returns first 10 items (default page size)
GET /api/v1/controller/devices/

# Returns items 11-20 (second page)
GET /api/v1/controller/devices/?page=2

# Returns first 25 items (custom page size)
GET /api/v1/controller/devices/?page_size=25

# Returns items 26-50 with custom page size
GET /api/v1/controller/devices/?page=2&page_size=25

Comment on lines +109 to +124
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These examples are good, but let's use a real URL, e.g. /api/v1/controller/device/.

Storage Utilities
-----------------

Expand Down
17 changes: 17 additions & 0 deletions openwisp_utils/api/pagination.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from django.core.exceptions import ImproperlyConfigured

try:
from rest_framework.pagination import PageNumberPagination
except ImportError: # pragma: nocover
raise ImproperlyConfigured(
"Django REST Framework is required to use "
"this feature but it is not installed"
)


class OpenWispPagination(PageNumberPagination):
"""Reusable pagination class with sensible defaults."""

page_size = 10
max_page_size = 100
page_size_query_param = "page_size"
61 changes: 61 additions & 0 deletions tests/test_project/tests/test_api.py
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we create a simple DRF view in the test project for any model and then test the pagination on that view? The current tests look very synthetic and do not model real world scenario.

I will suggest you to look for test in other modules, e.g. openwisp-notification / openwisp-radius on how we write tests for pagination. Here's one quick instance.

https://github.com/openwisp/openwisp-notifications/blob/5a54a856096695f0d46e2992f20a51a0905621a9/openwisp_notifications/tests/test_api.py#L85-L139

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pushpit-kamboj I think @pandafy's suggestion is good, let's make sure the test project has an API view which uses this pagination class so we can also test manually, make sure the tests use that view.

Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from django.conf import settings
from django.core.exceptions import ValidationError
from django.test import TestCase
from openwisp_utils.api.pagination import OpenWispPagination
from rest_framework.pagination import PageNumberPagination
from rest_framework.request import Request
from rest_framework.test import APIRequestFactory
from test_project.serializers import ShelfSerializer

from ..models import Shelf
Expand Down Expand Up @@ -64,3 +68,60 @@ def test_rest_framework_settings_override(self):
"TEST": True,
},
)


class TestOpenWispPagination(CreateMixin, TestCase):
shelf_model = Shelf

def setUp(self):
self.factory = APIRequestFactory()
self.pagination = OpenWispPagination()
for i in range(15):
self._create_shelf(name=f"shelf{i}")

def _get_request(self, path="/api/shelves/", data=None):
return Request(self.factory.get(path, data))

def test_inheritance(self):
self.assertIsInstance(self.pagination, PageNumberPagination)

def test_default_attributes(self):
self.assertEqual(self.pagination.page_size, 10)
self.assertEqual(self.pagination.max_page_size, 100)
self.assertEqual(self.pagination.page_size_query_param, "page_size")

def test_paginate_queryset(self):
request = self._get_request()
queryset = Shelf.objects.all().order_by("id")
paginated = self.pagination.paginate_queryset(queryset, request)
self.assertEqual(len(paginated), 10)

def test_paginate_queryset_second_page(self):
request = self._get_request(data={"page": 2})
queryset = Shelf.objects.all().order_by("id")
paginated = self.pagination.paginate_queryset(queryset, request)
self.assertEqual(len(paginated), 5)

def test_paginate_queryset_custom_page_size(self):
request = self._get_request(data={"page_size": 5})
queryset = Shelf.objects.all().order_by("id")
paginated = self.pagination.paginate_queryset(queryset, request)
self.assertEqual(len(paginated), 5)

def test_paginate_queryset_respects_max_page_size(self):
self.pagination.max_page_size = 10
request = self._get_request(data={"page_size": 100})
queryset = Shelf.objects.all().order_by("id")
paginated = self.pagination.paginate_queryset(queryset, request)
self.assertEqual(len(paginated), 10)

def test_get_paginated_response(self):
request = self._get_request()
queryset = Shelf.objects.all().order_by("id")
self.pagination.paginate_queryset(queryset, request)
response = self.pagination.get_paginated_response([])
self.assertIn("count", response.data)
self.assertIn("next", response.data)
self.assertIn("previous", response.data)
self.assertIn("results", response.data)
self.assertEqual(response.data["count"], 15)
Loading