djresttoolkit 0.14.0__py3-none-any.whl → 0.16.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
README.md CHANGED
@@ -813,6 +813,126 @@ def my_view(request: HttpRequest):
813
813
  https://example.com/api/books/123/?ref=newsletter
814
814
  ```
815
815
 
816
+ ### 14. PageNumberPagination — API Reference
817
+
818
+ ```python
819
+ from djresttoolkit.pagination import PageNumberPagination
820
+ ```
821
+
822
+ #### Description of Page Number Pagination
823
+
824
+ - Extends **DRF’s `PageNumberPagination`** with a **cleaner metadata structure**.
825
+ - Adds support for **dynamic page size** via the `page-size` query parameter.
826
+ - Returns pagination metadata inside a `"page"` object, separate from `"results"`.
827
+
828
+ #### Features of Page Number Pagination
829
+
830
+ - Clients can control items per page using `?page-size=`.
831
+ - Structured pagination metadata:
832
+
833
+ - `current` → current page number
834
+ - `total` → total number of pages
835
+ - `size` → number of items per page
836
+ - `total_items` → total number of items across all pages
837
+ - `next` → next page URL
838
+ - `previous` → previous page URL
839
+ - Standardized API response format.
840
+
841
+ ### Attributes of Page Number Pagination
842
+
843
+ - `page_size_query_param: str` → Query parameter name (`"page-size"`).
844
+
845
+ ### Page Number Pagination Methods
846
+
847
+ - `get_paginated_response(data: Any) -> Response`
848
+ Returns a JSON response with both pagination metadata and results.
849
+
850
+ ### Example Response of Page Number Pagination
851
+
852
+ ```json
853
+ {
854
+ "page": {
855
+ "current": 1,
856
+ "total": 10,
857
+ "size": 20,
858
+ "total_items": 200,
859
+ "next": "http://api.example.com/items/?page=2&page-size=20",
860
+ "previous": null
861
+ },
862
+ "results": [
863
+ { "id": 1, "name": "Item 1" },
864
+ { "id": 2, "name": "Item 2" }
865
+ ]
866
+ }
867
+ ```
868
+
869
+ ### 15. PaginatedDataBuilder — API Reference
870
+
871
+ ```python
872
+ from djresttoolkit.pagination import PaginatedDataBuilder
873
+ ```
874
+
875
+ ---
876
+
877
+ #### Description of Paginated Data Builder
878
+
879
+ - A **builder utility** to paginate and serialize Django QuerySets using DRF.
880
+ - Uses the custom **`PageNumberPagination`** class for consistent pagination responses.
881
+ - Designed for reusability inside DRF views and APIs.
882
+
883
+ #### Features of Paginated Data Builder
884
+
885
+ - Integrates with **DRF serializers**.
886
+ - Handles **invalid pages** gracefully by raising `NotFound`.
887
+ - Returns both:
888
+ - `"page"` → pagination metadata
889
+ - `"results"` → serialized data.
890
+ - Provides **structured pagination response format**.
891
+
892
+ ---
893
+
894
+ #### Initialization of Paginated Data Builder
895
+
896
+ ```python
897
+ builder = PaginatedDataBuilder(
898
+ request=request,
899
+ serializer_class=MySerializer,
900
+ queryset=MyModel.objects.all()
901
+ )
902
+ ```
903
+
904
+ - `request: Request` → DRF request object.
905
+ - `serializer_class: type[BaseSerializer]` → DRF serializer class for the model.
906
+ - `queryset: QuerySet` → Django queryset to paginate.
907
+
908
+ ### Paginated Data Builder Methods
909
+
910
+ - `get_paginated_data() -> dict[str, Any]`
911
+
912
+ - Applies pagination to the queryset.
913
+ - Serializes the paginated results.
914
+ - Returns a dictionary with `"page"` and `"results"`.
915
+ - Raises `NotFound` if no page data is found.
916
+
917
+ ### Example Response of Paginated Data Builder
918
+
919
+ ```json
920
+ {
921
+ "page": {
922
+ "current": 2,
923
+ "total": 5,
924
+ "size": 20,
925
+ "total_items": 100,
926
+ "next": "http://api.example.com/items/?page=3&page-size=20",
927
+ "previous": "http://api.example.com/items/?page=1&page-size=20"
928
+ },
929
+ "results": [
930
+ { "id": 21, "name": "Item 21" },
931
+ { "id": 22, "name": "Item 22" }
932
+ ]
933
+ }
934
+ ```
935
+
816
936
  ## 🛠️ Planned Features
817
937
 
818
938
  - Add more utils
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: djresttoolkit
3
- Version: 0.14.0
3
+ Version: 0.16.0
4
4
  Summary: A collection of Django and DRF utilities to simplify API development.
5
5
  Project-URL: Homepage, https://github.com/shaileshpandit141/djresttoolkit
6
6
  Project-URL: Documentation, https://shaileshpandit141.github.io/djresttoolkit
@@ -871,6 +871,126 @@ def my_view(request: HttpRequest):
871
871
  https://example.com/api/books/123/?ref=newsletter
872
872
  ```
873
873
 
874
+ ### 14. PageNumberPagination — API Reference
875
+
876
+ ```python
877
+ from djresttoolkit.pagination import PageNumberPagination
878
+ ```
879
+
880
+ #### Description of Page Number Pagination
881
+
882
+ - Extends **DRF’s `PageNumberPagination`** with a **cleaner metadata structure**.
883
+ - Adds support for **dynamic page size** via the `page-size` query parameter.
884
+ - Returns pagination metadata inside a `"page"` object, separate from `"results"`.
885
+
886
+ #### Features of Page Number Pagination
887
+
888
+ - Clients can control items per page using `?page-size=`.
889
+ - Structured pagination metadata:
890
+
891
+ - `current` → current page number
892
+ - `total` → total number of pages
893
+ - `size` → number of items per page
894
+ - `total_items` → total number of items across all pages
895
+ - `next` → next page URL
896
+ - `previous` → previous page URL
897
+ - Standardized API response format.
898
+
899
+ ### Attributes of Page Number Pagination
900
+
901
+ - `page_size_query_param: str` → Query parameter name (`"page-size"`).
902
+
903
+ ### Page Number Pagination Methods
904
+
905
+ - `get_paginated_response(data: Any) -> Response`
906
+ Returns a JSON response with both pagination metadata and results.
907
+
908
+ ### Example Response of Page Number Pagination
909
+
910
+ ```json
911
+ {
912
+ "page": {
913
+ "current": 1,
914
+ "total": 10,
915
+ "size": 20,
916
+ "total_items": 200,
917
+ "next": "http://api.example.com/items/?page=2&page-size=20",
918
+ "previous": null
919
+ },
920
+ "results": [
921
+ { "id": 1, "name": "Item 1" },
922
+ { "id": 2, "name": "Item 2" }
923
+ ]
924
+ }
925
+ ```
926
+
927
+ ### 15. PaginatedDataBuilder — API Reference
928
+
929
+ ```python
930
+ from djresttoolkit.pagination import PaginatedDataBuilder
931
+ ```
932
+
933
+ ---
934
+
935
+ #### Description of Paginated Data Builder
936
+
937
+ - A **builder utility** to paginate and serialize Django QuerySets using DRF.
938
+ - Uses the custom **`PageNumberPagination`** class for consistent pagination responses.
939
+ - Designed for reusability inside DRF views and APIs.
940
+
941
+ #### Features of Paginated Data Builder
942
+
943
+ - Integrates with **DRF serializers**.
944
+ - Handles **invalid pages** gracefully by raising `NotFound`.
945
+ - Returns both:
946
+ - `"page"` → pagination metadata
947
+ - `"results"` → serialized data.
948
+ - Provides **structured pagination response format**.
949
+
950
+ ---
951
+
952
+ #### Initialization of Paginated Data Builder
953
+
954
+ ```python
955
+ builder = PaginatedDataBuilder(
956
+ request=request,
957
+ serializer_class=MySerializer,
958
+ queryset=MyModel.objects.all()
959
+ )
960
+ ```
961
+
962
+ - `request: Request` → DRF request object.
963
+ - `serializer_class: type[BaseSerializer]` → DRF serializer class for the model.
964
+ - `queryset: QuerySet` → Django queryset to paginate.
965
+
966
+ ### Paginated Data Builder Methods
967
+
968
+ - `get_paginated_data() -> dict[str, Any]`
969
+
970
+ - Applies pagination to the queryset.
971
+ - Serializes the paginated results.
972
+ - Returns a dictionary with `"page"` and `"results"`.
973
+ - Raises `NotFound` if no page data is found.
974
+
975
+ ### Example Response of Paginated Data Builder
976
+
977
+ ```json
978
+ {
979
+ "page": {
980
+ "current": 2,
981
+ "total": 5,
982
+ "size": 20,
983
+ "total_items": 100,
984
+ "next": "http://api.example.com/items/?page=3&page-size=20",
985
+ "previous": "http://api.example.com/items/?page=1&page-size=20"
986
+ },
987
+ "results": [
988
+ { "id": 21, "name": "Item 21" },
989
+ { "id": 22, "name": "Item 22" }
990
+ ]
991
+ }
992
+ ```
993
+
874
994
  ## 🛠️ Planned Features
875
995
 
876
996
  - Add more utils
@@ -1,5 +1,5 @@
1
1
  LICENSE,sha256=8-oZM3yuuTRjySMbVKX9YXYA7Y4M_KhQNBYXPFjeWUo,1074
2
- README.md,sha256=VZKLjTbak58sMkj073lXbWJVrxZk3VqPS1wIhuqmu7U,21729
2
+ README.md,sha256=wdjszjenN8a6p1wcJSfaIbhjlZgretrF_Y3XSMwyQsQ,24856
3
3
  demo/staticfiles/admin/img/LICENSE,sha256=0RT6_zSIwWwxmzI13EH5AjnT1j2YU3MwM9j3U19cAAQ,1081
4
4
  src/djresttoolkit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  src/djresttoolkit/admin.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -26,6 +26,9 @@ src/djresttoolkit/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
26
26
  src/djresttoolkit/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
27
  src/djresttoolkit/models/mixins/__init__.py,sha256=MHwv36f3nHwI0bXeejuO7MTYuV93ln2tSyCCipS2xVw,223
28
28
  src/djresttoolkit/models/mixins/_model_choice_fields_mixin.py,sha256=9FZbe3PwrtIUZYGQh1gcOix5bfeyvKEOaNmkemvZX8E,2843
29
+ src/djresttoolkit/pagination/__init__.py,sha256=lQhyyX381RbWBsYV9Os3OQIbY7Z6aouL0QE5kI_u5SU,176
30
+ src/djresttoolkit/pagination/_page_number_pagination.py,sha256=NHPdMZfmTurKLdgpMBT2usTiGAoZMyA3dYXq_n11y34,2358
31
+ src/djresttoolkit/pagination/_paginated_data_builder.py,sha256=N4JaJwmfmC2NiC8MqOpkxV8-itKTueaOdawnv_it4bU,2239
29
32
  src/djresttoolkit/renderers/__init__.py,sha256=kmFMPRiMfD8CuJTN1_-6Z_Hqil3x8GBM0IN1roZESm0,107
30
33
  src/djresttoolkit/renderers/_throttle_info_json_renderer.py,sha256=aP2cN4cB_Imcpy732zsPBQrMQqcKEs5R3dld5Y_4AMU,1089
31
34
  src/djresttoolkit/serializers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -43,8 +46,8 @@ src/djresttoolkit/views/_exceptions/__init__.py,sha256=DrCUxuPNyBR4WhzNutn5HDxLa
43
46
  src/djresttoolkit/views/_exceptions/_exception_handler.py,sha256=_o7If47bzWLl57LeSXSWsIDsJGo2RIpwYAwNQ-hsHVY,2839
44
47
  src/djresttoolkit/views/mixins/__init__.py,sha256=K-1tk5d8tCVViMynw5DdffJ3Oo5uHpEx32E3_4X2UxM,154
45
48
  src/djresttoolkit/views/mixins/_retrieve_object_mixin.py,sha256=Q9znYPb07YXXUhsL7VIrk3BC-zDwjOhwLJKe2GPJ-k0,1155
46
- djresttoolkit-0.14.0.dist-info/METADATA,sha256=rYNwwhrKT4i3RuKPMFC6l1xU8yzJkcd7vC8rkYeeOsc,24739
47
- djresttoolkit-0.14.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
48
- djresttoolkit-0.14.0.dist-info/entry_points.txt,sha256=YMhfTF-7mYppO8QqqWnvR_hyMWvoYxD6XI94_ViFu3k,60
49
- djresttoolkit-0.14.0.dist-info/licenses/LICENSE,sha256=8-oZM3yuuTRjySMbVKX9YXYA7Y4M_KhQNBYXPFjeWUo,1074
50
- djresttoolkit-0.14.0.dist-info/RECORD,,
49
+ djresttoolkit-0.16.0.dist-info/METADATA,sha256=wHoVJ4XBAJVLoZYMPC-2Q7QrlwXuguGbnHvbmIG9iMM,27866
50
+ djresttoolkit-0.16.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
51
+ djresttoolkit-0.16.0.dist-info/entry_points.txt,sha256=YMhfTF-7mYppO8QqqWnvR_hyMWvoYxD6XI94_ViFu3k,60
52
+ djresttoolkit-0.16.0.dist-info/licenses/LICENSE,sha256=8-oZM3yuuTRjySMbVKX9YXYA7Y4M_KhQNBYXPFjeWUo,1074
53
+ djresttoolkit-0.16.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ from ._page_number_pagination import PageNumberPagination
2
+ from ._paginated_data_builder import PaginatedDataBuilder
3
+
4
+ __all__ = ["PageNumberPagination", "PaginatedDataBuilder"]
@@ -0,0 +1,69 @@
1
+ from typing import Any
2
+
3
+ from rest_framework.exceptions import ValidationError
4
+ from rest_framework.pagination import PageNumberPagination as DrfPageNumberPagination
5
+ from rest_framework.response import Response
6
+
7
+
8
+ class PageNumberPagination(DrfPageNumberPagination):
9
+ """
10
+ Custom PageNumberPagination for Django REST Framework.
11
+
12
+ This pagination class extends DRF's PageNumberPagination to provide:
13
+ - Dynamic page size support via the "page-size" query parameter.
14
+ - A streamlined, structured pagination metadata format in API responses.
15
+
16
+ Features:
17
+ - Clients can control the number of items per page using the "page-size" query parameter.
18
+ - The paginated response includes a "page" object with detailed pagination metadata and a "results" list.
19
+
20
+ Paginated Response Example:
21
+ ```
22
+ {
23
+ "page": {
24
+ "current": 1,
25
+ "total": 10,
26
+ "size": 20,
27
+ "total_items": 200,
28
+ "next": "http://api.example.com/items/?page=2&page-size=20",
29
+ "previous": null
30
+ },
31
+ "results": [ ... ]
32
+ }
33
+ ```
34
+
35
+ Attributes:
36
+ page_size_query_param (str): Query parameter name for dynamic page size ("page-size").
37
+
38
+ Methods:
39
+ get_paginated_response(data: Any) -> Response:
40
+ Returns a standardized paginated response with metadata and results.
41
+
42
+ """
43
+
44
+ # Allow clients to set page size via ?page-size=
45
+ page_size_query_param = "page-size"
46
+
47
+ def get_paginated_response(self, data: Any) -> Response:
48
+ page = getattr(self, "page", None)
49
+ paginator = getattr(self, "paginator", None)
50
+ request = getattr(self, "request", None)
51
+ if page is None or paginator is None or request is None:
52
+ raise ValidationError(
53
+ {"detail": "Pagination has not been properly configured."}
54
+ )
55
+
56
+ items_per_page = self.get_page_size(request)
57
+ return Response(
58
+ {
59
+ "page": {
60
+ "current": page.number,
61
+ "total": paginator.num_pages,
62
+ "size": items_per_page,
63
+ "total_items": paginator.count,
64
+ "next": self.get_next_link(),
65
+ "previous": self.get_previous_link(),
66
+ },
67
+ "results": data,
68
+ }
69
+ )
@@ -0,0 +1,65 @@
1
+ import logging
2
+ from typing import Any
3
+
4
+ from django.db.models import QuerySet
5
+ from rest_framework.exceptions import NotFound
6
+ from rest_framework.request import Request
7
+ from rest_framework.serializers import BaseSerializer
8
+ from django.db.models import Model
9
+ from ._page_number_pagination import PageNumberPagination
10
+
11
+ # Get logger from logging.
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class PaginatedDataBuilder[T: Model]:
16
+ """Builder class to handle pagination and serialization."""
17
+
18
+ def __init__(
19
+ self,
20
+ request: Request,
21
+ serializer_class: type[BaseSerializer[T]],
22
+ queryset: QuerySet[T],
23
+ ) -> None:
24
+ """Initilize the PaginatedDataBuilder class."""
25
+ self.request = request
26
+ self.serializer_class = serializer_class
27
+ self.queryset = queryset
28
+
29
+ def get_paginated_data(self) -> dict[str, Any]:
30
+ """Paginate and serialize the queryset."""
31
+
32
+ logger.debug("Starting pagination with custom PageNumberPagination.")
33
+ paginator = PageNumberPagination()
34
+ page = paginator.paginate_queryset(
35
+ self.queryset,
36
+ self.request,
37
+ )
38
+
39
+ # If no data is returned from pagination, raise NotFound
40
+ if page is None:
41
+ logger.warning("No data returned from pagination. Possibly invalid page.")
42
+ raise NotFound("The requested records were not found.")
43
+
44
+ # Serialize the paginated data
45
+ serializer = self.serializer_class(
46
+ instance=page, # type: ignore
47
+ many=True,
48
+ context={"request": self.request},
49
+ )
50
+
51
+ # Construct the paginated response
52
+ paginated_data = {
53
+ "page": {
54
+ "current": paginator.page.number, # type: ignore
55
+ "total": paginator.page.paginator.num_pages, # type: ignore
56
+ "size": paginator.get_page_size(self.request),
57
+ "total_items": paginator.page.paginator.count, # type: ignore
58
+ "next": paginator.get_next_link(),
59
+ "previous": paginator.get_previous_link(),
60
+ },
61
+ "results": serializer.data,
62
+ }
63
+
64
+ logger.debug(f"Pagination result: {paginated_data}")
65
+ return paginated_data