[Feature] Server pagination implementation (#243)

## Major Changes
- Implement a REST style API app in epapi
- Currently implements a GET method for all entity types in the browse menu (both package level and global)
- Provides paginated results per default with query style filtering for reviewed vs unreviewed.
- Provides new paginated templates with thin wrappers per entity types for easier maintainability
- Implements e2e tests for the API

## Minor changes
- Added more comprehensive gitignore to cover coverage reports and other test/node.js etc. data.
- Add additional CI file for API tests that only gets triggered on API relevant changes.

## ⚠️ Currently only works with session-based authentication. Token based will be added in new PR.

Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Co-authored-by: jebus <lorsbach@envipath.com>
Reviewed-on: enviPath/enviPy#243
Co-authored-by: Tobias O <tobias.olenyi@envipath.com>
Co-committed-by: Tobias O <tobias.olenyi@envipath.com>
This commit is contained in:
2025-12-15 11:34:53 +13:00
committed by jebus
parent d2d475b990
commit 8adb93012a
59 changed files with 3101 additions and 620 deletions

60
epapi/v1/pagination.py Normal file
View File

@ -0,0 +1,60 @@
import math
from typing import Any, Generic, List, TypeVar
from django.db.models import QuerySet
from ninja import Schema
from ninja.pagination import PageNumberPagination
T = TypeVar("T")
class EnhancedPageNumberPagination(PageNumberPagination):
class Output(Schema, Generic[T]):
items: List[T]
page: int
page_size: int
total_items: int
total_pages: int
def paginate_queryset(
self,
queryset: QuerySet,
pagination: PageNumberPagination.Input,
**params: Any,
) -> Any:
page_size = self._get_page_size(pagination.page_size)
offset = (pagination.page - 1) * page_size
total_items = self._items_count(queryset)
total_pages = math.ceil(total_items / page_size) if page_size > 0 else 0
return {
"items": queryset[offset : offset + page_size],
"page": pagination.page,
"page_size": page_size,
"total_items": total_items,
"total_pages": total_pages,
}
async def apaginate_queryset(
self,
queryset: QuerySet,
pagination: PageNumberPagination.Input,
**params: Any,
) -> Any:
page_size = self._get_page_size(pagination.page_size)
offset = (pagination.page - 1) * page_size
total_items = await self._aitems_count(queryset)
total_pages = math.ceil(total_items / page_size) if page_size > 0 else 0
if isinstance(queryset, QuerySet):
items = [obj async for obj in queryset[offset : offset + page_size]]
else:
items = queryset[offset : offset + page_size]
return {
"items": items,
"page": pagination.page,
"page_size": page_size,
"total_items": total_items,
"total_pages": total_pages,
}