djresttoolkit 1.1.0__py3-none-any.whl → 1.2.1__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.
@@ -1,11 +1,13 @@
1
1
  import logging
2
2
  from typing import Any
3
3
 
4
- from django.db.models import QuerySet
4
+ from django.db.models import Model, QuerySet
5
5
  from rest_framework.exceptions import NotFound
6
6
  from rest_framework.request import Request
7
7
  from rest_framework.serializers import BaseSerializer
8
- from django.db.models import Model
8
+
9
+ from djresttoolkit.serializers import EnhancedModelSerializer
10
+
9
11
  from ._page_number_pagination import PageNumberPagination
10
12
 
11
13
  # Get logger from logging.
@@ -18,7 +20,7 @@ class PaginatedDataBuilder[T: Model]:
18
20
  def __init__(
19
21
  self,
20
22
  request: Request,
21
- serializer_class: type[BaseSerializer[T]],
23
+ serializer_class: type[BaseSerializer[T] | EnhancedModelSerializer[T]],
22
24
  queryset: QuerySet[T],
23
25
  ) -> None:
24
26
  """Initilize the PaginatedDataBuilder class."""
@@ -49,7 +51,7 @@ class PaginatedDataBuilder[T: Model]:
49
51
  )
50
52
 
51
53
  # Construct the paginated response
52
- paginated_data = {
54
+ paginated_data: dict[str, Any] = {
53
55
  "page": {
54
56
  "current": paginator.page.number, # type: ignore
55
57
  "total": paginator.page.paginator.num_pages, # type: ignore
@@ -63,3 +65,7 @@ class PaginatedDataBuilder[T: Model]:
63
65
 
64
66
  logger.debug(f"Pagination result: {paginated_data}")
65
67
  return paginated_data
68
+
69
+ @property
70
+ def paginated_data(self) -> dict[str, Any]:
71
+ return self.get_paginated_data()
@@ -0,0 +1,3 @@
1
+ from ._enhanced_model_serializer import EnhancedModelSerializer
2
+
3
+ __all__ = ["EnhancedModelSerializer"]
@@ -0,0 +1,59 @@
1
+ from __future__ import annotations
2
+
3
+ from copy import deepcopy
4
+ from typing import Any
5
+
6
+ from django.db.models import Field as DjangoField
7
+ from django.db.models import Model
8
+ from rest_framework.serializers import Field as DrfField
9
+ from rest_framework.serializers import ModelSerializer
10
+ from rest_framework.utils.model_meta import RelationInfo
11
+
12
+
13
+ class EnhancedModelSerializer[T: Model](ModelSerializer[Model]):
14
+ """
15
+ A DRF ModelSerializer that automatically applies Django model field
16
+ `error_messages` unless explicitly overridden in the serializer.
17
+ """
18
+
19
+ def _merge_error_messages(
20
+ self,
21
+ field_kwargs: dict[str, Any],
22
+ model_field: DjangoField[Any, Any] | None,
23
+ ) -> dict[str, Any]:
24
+ """Safely merge model field error_messages with serializer kwargs."""
25
+ model_errors: dict[str, str] | None = getattr(
26
+ model_field, "error_messages", None
27
+ )
28
+ if model_errors:
29
+ existing: dict[str, str] = field_kwargs.get("error_messages", {})
30
+ field_kwargs["error_messages"] = {**deepcopy(model_errors), **existing}
31
+ return field_kwargs
32
+
33
+ def build_standard_field(
34
+ self,
35
+ field_name: str,
36
+ model_field: DjangoField[Any, Any],
37
+ ) -> tuple[type[DrfField[Any, Any, Any, Any]], dict[str, Any]]:
38
+ field_class, field_kwargs = super().build_standard_field( # type: ignore
39
+ field_name,
40
+ model_field,
41
+ )
42
+ return field_class, self._merge_error_messages(
43
+ field_kwargs,
44
+ model_field,
45
+ ) # type: ignore
46
+
47
+ def build_relational_field(
48
+ self,
49
+ field_name: str,
50
+ relation_info: RelationInfo,
51
+ ) -> tuple[type[DrfField[Any, Any, Any, Any]], dict[str, Any]]:
52
+ field_class, field_kwargs = super().build_relational_field( # type: ignore
53
+ field_name,
54
+ relation_info,
55
+ )
56
+ return field_class, self._merge_error_messages(
57
+ field_kwargs,
58
+ relation_info.model_field, # type: ignore
59
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: djresttoolkit
3
- Version: 1.1.0
3
+ Version: 1.2.1
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
@@ -970,7 +970,7 @@ from djresttoolkit.pagination import PaginatedDataBuilder
970
970
  ```python
971
971
  builder = PaginatedDataBuilder(
972
972
  request=request,
973
- serializer_class=MySerializer,
973
+ serializer_class=BookSerializer,
974
974
  queryset=MyModel.objects.all()
975
975
  )
976
976
  ```
@@ -979,7 +979,7 @@ builder = PaginatedDataBuilder(
979
979
  - `serializer_class: type[BaseSerializer]`:- DRF serializer class for the model.
980
980
  - `queryset: QuerySet`:- Django queryset to paginate.
981
981
 
982
- ### Paginated Data Builder Methods
982
+ ### Paginated Data Builder Methods and Property
983
983
 
984
984
  - `get_paginated_data() -> dict[str, Any]`
985
985
 
@@ -987,6 +987,10 @@ builder = PaginatedDataBuilder(
987
987
  - Serializes the paginated results.
988
988
  - Returns a dictionary with `"page"` and `"results"`.
989
989
  - Raises `NotFound` if no page data is found.
990
+
991
+ - `paginated_data -> dict[str, Any]`
992
+
993
+ - Call `paginated_data` property that internaly call `get_paginated_data()` method
990
994
 
991
995
  ### Example Response of Paginated Data Builder
992
996
 
@@ -1001,8 +1005,8 @@ builder = PaginatedDataBuilder(
1001
1005
  "previous": "http://api.example.com/items/?page=1&page-size=20"
1002
1006
  },
1003
1007
  "results": [
1004
- { "id": 21, "name": "Item 21" },
1005
- { "id": 22, "name": "Item 22" }
1008
+ { "id": 21, "title": "Title 21" },
1009
+ { "id": 22, "title": "Title 22" }
1006
1010
  ]
1007
1011
  }
1008
1012
  ```
@@ -1081,6 +1085,27 @@ class BookViewSet(CacheInvalidateMixin, ModelViewSet):
1081
1085
  - Invalidates caches when books are created, updated, or deleted.
1082
1086
  - Supports custom cache keys per action.
1083
1087
 
1088
+ ### 17. EnhancedModelSerializer — API Reference
1089
+
1090
+ A subclass of Django REST Framework’s `ModelSerializer` that automatically merges Django model field `error_messages` into the serializer field, unless explicitly overridden.
1091
+ This helps maintain consistent validation messages between the model and the serializer.
1092
+
1093
+ #### Type Parameters
1094
+
1095
+ - `T` (`Model`): The Django model type that the serializer corresponds to.
1096
+
1097
+ #### Example of EnhancedModelSerializer
1098
+
1099
+ ```python
1100
+ from myapp.models import Book
1101
+ from myapp.serializers import EnhancedModelSerializer
1102
+
1103
+ class BookSerializer(EnhancedModelSerializer[Book]):
1104
+ class Meta:
1105
+ model = Book
1106
+ fields = "__all__"
1107
+ ```
1108
+
1084
1109
  ## 🛠️ Planned Features
1085
1110
 
1086
1111
  - Add more utils
@@ -32,10 +32,11 @@ djresttoolkit/models/mixins/__init__.py,sha256=MHwv36f3nHwI0bXeejuO7MTYuV93ln2tS
32
32
  djresttoolkit/models/mixins/_model_choice_fields_mixin.py,sha256=9FZbe3PwrtIUZYGQh1gcOix5bfeyvKEOaNmkemvZX8E,2843
33
33
  djresttoolkit/pagination/__init__.py,sha256=lQhyyX381RbWBsYV9Os3OQIbY7Z6aouL0QE5kI_u5SU,176
34
34
  djresttoolkit/pagination/_page_number_pagination.py,sha256=NHPdMZfmTurKLdgpMBT2usTiGAoZMyA3dYXq_n11y34,2358
35
- djresttoolkit/pagination/_paginated_data_builder.py,sha256=N4JaJwmfmC2NiC8MqOpkxV8-itKTueaOdawnv_it4bU,2239
35
+ djresttoolkit/pagination/_paginated_data_builder.py,sha256=oASu4wA8hN8vaPlh4rx0HM600CUVO179M25JEr9pRx4,2424
36
36
  djresttoolkit/renderers/__init__.py,sha256=kmFMPRiMfD8CuJTN1_-6Z_Hqil3x8GBM0IN1roZESm0,107
37
37
  djresttoolkit/renderers/_throttle_info_json_renderer.py,sha256=aP2cN4cB_Imcpy732zsPBQrMQqcKEs5R3dld5Y_4AMU,1089
38
- djresttoolkit/serializers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
+ djresttoolkit/serializers/__init__.py,sha256=367CLluj8C15Zpr7BD-euP4DxKbwepm58gnGGcuYOJU,103
39
+ djresttoolkit/serializers/_enhanced_model_serializer.py,sha256=XNYM-zy5_ecfUNRtmBfaDKSuxEDhmoBV9HNx8iSaDpE,2055
39
40
  djresttoolkit/serializers/mixins/__init__.py,sha256=dRT0kXDckOkZo1RQHrT1gXbGFMIv5M8TBHGF2uF-81Q,225
40
41
  djresttoolkit/serializers/mixins/_absolute_url_file_mixin.py,sha256=5ewael0_RsJZ9b36IfXacxjb-Vx1eQ9Dk6dWuj5D_dc,3261
41
42
  djresttoolkit/serializers/mixins/_bulk_create_mixin.py,sha256=9ZWm2MNaZOhmhKlWOu6VECtlDbUtaPeceGHmivDYwYQ,3248
@@ -50,8 +51,8 @@ djresttoolkit/views/_exceptions/__init__.py,sha256=DrCUxuPNyBR4WhzNutn5HDxLa--q5
50
51
  djresttoolkit/views/_exceptions/_exception_handler.py,sha256=_o7If47bzWLl57LeSXSWsIDsJGo2RIpwYAwNQ-hsHVY,2839
51
52
  djresttoolkit/views/mixins/__init__.py,sha256=mHD49OUxuJ9v81tGfM0hLnUJuJlYi7E-5cTVdplh-vs,91
52
53
  djresttoolkit/views/mixins/_retrieve_object_mixin.py,sha256=v7CQDUkRWjtevFZnAYRBdDl7wcfYWF3evWoKWHAcckA,1749
53
- djresttoolkit-1.1.0.dist-info/METADATA,sha256=0Ln7HqEDc0-fHHiJZhZv0uipTH5TC9IsoWH9dWCswvI,31878
54
- djresttoolkit-1.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
55
- djresttoolkit-1.1.0.dist-info/entry_points.txt,sha256=YMhfTF-7mYppO8QqqWnvR_hyMWvoYxD6XI94_ViFu3k,60
56
- djresttoolkit-1.1.0.dist-info/licenses/LICENSE,sha256=8-oZM3yuuTRjySMbVKX9YXYA7Y4M_KhQNBYXPFjeWUo,1074
57
- djresttoolkit-1.1.0.dist-info/RECORD,,
54
+ djresttoolkit-1.2.1.dist-info/METADATA,sha256=tfUlUP1aTjciJVuxWBmGMr_pJngjAOKwihneOuThlUI,32697
55
+ djresttoolkit-1.2.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
56
+ djresttoolkit-1.2.1.dist-info/entry_points.txt,sha256=YMhfTF-7mYppO8QqqWnvR_hyMWvoYxD6XI94_ViFu3k,60
57
+ djresttoolkit-1.2.1.dist-info/licenses/LICENSE,sha256=8-oZM3yuuTRjySMbVKX9YXYA7Y4M_KhQNBYXPFjeWUo,1074
58
+ djresttoolkit-1.2.1.dist-info/RECORD,,