djresttoolkit 1.0.0__tar.gz → 1.2.0__tar.gz

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.
Files changed (254) hide show
  1. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/PKG-INFO +30 -7
  2. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/README.md +29 -6
  3. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/apps/todos/models.py +20 -1
  4. djresttoolkit-1.2.0/demo/apps/todos/serializers.py +9 -0
  5. djresttoolkit-1.2.0/demo/apps/todos/urls.py +12 -0
  6. djresttoolkit-1.2.0/demo/apps/todos/views.py +43 -0
  7. djresttoolkit-1.2.0/demo/apps_config/__init__.py +3 -0
  8. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/apps_config/settings.py +6 -22
  9. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/pyproject.toml +13 -9
  10. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/pagination/_paginated_data_builder.py +6 -4
  11. djresttoolkit-1.2.0/src/djresttoolkit/serializers/__init__.py +3 -0
  12. djresttoolkit-1.2.0/src/djresttoolkit/serializers/_enhanced_model_serializer.py +59 -0
  13. djresttoolkit-1.2.0/src/djresttoolkit/views/mixins/__init__.py +3 -0
  14. djresttoolkit-1.2.0/src/djresttoolkit/views/mixins/_retrieve_object_mixin.py +59 -0
  15. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/uv.lock +1 -1
  16. djresttoolkit-1.0.0/demo/apps/todos/urls.py +0 -7
  17. djresttoolkit-1.0.0/demo/apps/todos/views.py +0 -0
  18. djresttoolkit-1.0.0/demo/env_config/__init__.py +0 -3
  19. djresttoolkit-1.0.0/demo/env_config/env_settings.py +0 -54
  20. djresttoolkit-1.0.0/src/djresttoolkit/serializers/__init__.py +0 -0
  21. djresttoolkit-1.0.0/src/djresttoolkit/views/_apiviews/__init__.py +0 -0
  22. djresttoolkit-1.0.0/src/djresttoolkit/views/mixins/__init__.py +0 -6
  23. djresttoolkit-1.0.0/src/djresttoolkit/views/mixins/_retrieve_object_mixin.py +0 -41
  24. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/.gitignore +0 -0
  25. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/.python-version +0 -0
  26. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/.vscode/settings.json +0 -0
  27. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/LICENSE +0 -0
  28. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/apps/todos/__init__.py +0 -0
  29. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/apps/todos/admin.py +0 -0
  30. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/apps/todos/apps.py +0 -0
  31. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/apps/todos/dbseed/__init__.py +0 -0
  32. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/apps/todos/dbseed/tag_seed.py +0 -0
  33. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/apps/todos/dbseed/todo_seed.py +0 -0
  34. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/apps/todos/dbseed/todo_tag_seed.py +0 -0
  35. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/apps/todos/migrations/0001_initial.py +0 -0
  36. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/apps/todos/migrations/__init__.py +0 -0
  37. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/apps/todos/tests.py +0 -0
  38. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/apps_config/asgi.py +0 -0
  39. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/apps_config/serializers.py +0 -0
  40. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/apps_config/urls.py +0 -0
  41. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/apps_config/views.py +0 -0
  42. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/apps_config/wsgi.py +0 -0
  43. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/manage.py +0 -0
  44. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/css/autocomplete.css +0 -0
  45. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/css/base.css +0 -0
  46. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/css/changelists.css +0 -0
  47. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/css/dark_mode.css +0 -0
  48. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/css/dashboard.css +0 -0
  49. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/css/forms.css +0 -0
  50. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/css/login.css +0 -0
  51. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/css/nav_sidebar.css +0 -0
  52. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/css/responsive.css +0 -0
  53. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/css/responsive_rtl.css +0 -0
  54. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/css/rtl.css +0 -0
  55. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/css/unusable_password_field.css +0 -0
  56. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/css/vendor/select2/LICENSE-SELECT2.md +0 -0
  57. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/css/vendor/select2/select2.css +0 -0
  58. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/css/vendor/select2/select2.min.css +0 -0
  59. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/css/widgets.css +0 -0
  60. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/img/LICENSE +0 -0
  61. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/img/README.txt +0 -0
  62. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/img/calendar-icons.svg +0 -0
  63. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/img/gis/move_vertex_off.svg +0 -0
  64. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/img/gis/move_vertex_on.svg +0 -0
  65. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/img/icon-addlink.svg +0 -0
  66. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/img/icon-alert.svg +0 -0
  67. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/img/icon-calendar.svg +0 -0
  68. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/img/icon-changelink.svg +0 -0
  69. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/img/icon-clock.svg +0 -0
  70. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/img/icon-deletelink.svg +0 -0
  71. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/img/icon-hidelink.svg +0 -0
  72. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/img/icon-no.svg +0 -0
  73. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/img/icon-unknown-alt.svg +0 -0
  74. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/img/icon-unknown.svg +0 -0
  75. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/img/icon-viewlink.svg +0 -0
  76. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/img/icon-yes.svg +0 -0
  77. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/img/inline-delete.svg +0 -0
  78. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/img/search.svg +0 -0
  79. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/img/selector-icons.svg +0 -0
  80. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/img/sorting-icons.svg +0 -0
  81. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/img/tooltag-add.svg +0 -0
  82. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/img/tooltag-arrowright.svg +0 -0
  83. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/SelectBox.js +0 -0
  84. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/SelectFilter2.js +0 -0
  85. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/actions.js +0 -0
  86. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/admin/DateTimeShortcuts.js +0 -0
  87. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/admin/RelatedObjectLookups.js +0 -0
  88. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/autocomplete.js +0 -0
  89. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/calendar.js +0 -0
  90. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/cancel.js +0 -0
  91. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/change_form.js +0 -0
  92. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/core.js +0 -0
  93. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/filters.js +0 -0
  94. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/inlines.js +0 -0
  95. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/jquery.init.js +0 -0
  96. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/nav_sidebar.js +0 -0
  97. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/popup_response.js +0 -0
  98. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/prepopulate.js +0 -0
  99. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/prepopulate_init.js +0 -0
  100. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/theme.js +0 -0
  101. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/unusable_password_field.js +0 -0
  102. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/urlify.js +0 -0
  103. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/jquery/LICENSE.txt +0 -0
  104. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/jquery/jquery.js +0 -0
  105. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/jquery/jquery.min.js +0 -0
  106. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/LICENSE.md +0 -0
  107. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/af.js +0 -0
  108. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/ar.js +0 -0
  109. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/az.js +0 -0
  110. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/bg.js +0 -0
  111. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/bn.js +0 -0
  112. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/bs.js +0 -0
  113. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/ca.js +0 -0
  114. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/cs.js +0 -0
  115. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/da.js +0 -0
  116. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/de.js +0 -0
  117. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/dsb.js +0 -0
  118. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/el.js +0 -0
  119. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/en.js +0 -0
  120. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/es.js +0 -0
  121. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/et.js +0 -0
  122. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/eu.js +0 -0
  123. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/fa.js +0 -0
  124. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/fi.js +0 -0
  125. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/fr.js +0 -0
  126. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/gl.js +0 -0
  127. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/he.js +0 -0
  128. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/hi.js +0 -0
  129. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/hr.js +0 -0
  130. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/hsb.js +0 -0
  131. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/hu.js +0 -0
  132. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/hy.js +0 -0
  133. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/id.js +0 -0
  134. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/is.js +0 -0
  135. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/it.js +0 -0
  136. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/ja.js +0 -0
  137. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/ka.js +0 -0
  138. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/km.js +0 -0
  139. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/ko.js +0 -0
  140. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/lt.js +0 -0
  141. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/lv.js +0 -0
  142. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/mk.js +0 -0
  143. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/ms.js +0 -0
  144. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/nb.js +0 -0
  145. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/ne.js +0 -0
  146. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/nl.js +0 -0
  147. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/pl.js +0 -0
  148. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/ps.js +0 -0
  149. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/pt-BR.js +0 -0
  150. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/pt.js +0 -0
  151. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/ro.js +0 -0
  152. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/ru.js +0 -0
  153. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/sk.js +0 -0
  154. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/sl.js +0 -0
  155. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/sq.js +0 -0
  156. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/sr-Cyrl.js +0 -0
  157. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/sr.js +0 -0
  158. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/sv.js +0 -0
  159. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/th.js +0 -0
  160. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/tk.js +0 -0
  161. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/tr.js +0 -0
  162. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/uk.js +0 -0
  163. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/vi.js +0 -0
  164. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/zh-CN.js +0 -0
  165. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/i18n/zh-TW.js +0 -0
  166. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/select2.full.js +0 -0
  167. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/select2/select2.full.min.js +0 -0
  168. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/xregexp/LICENSE.txt +0 -0
  169. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/xregexp/xregexp.js +0 -0
  170. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/admin/js/vendor/xregexp/xregexp.min.js +0 -0
  171. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/css/bootstrap-theme.min.css +0 -0
  172. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/css/bootstrap-theme.min.css.map +0 -0
  173. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/css/bootstrap-tweaks.css +0 -0
  174. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/css/bootstrap.min.css +0 -0
  175. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/css/bootstrap.min.css.map +0 -0
  176. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/css/default.css +0 -0
  177. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/css/font-awesome-4.0.3.css +0 -0
  178. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/css/prettify.css +0 -0
  179. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/docs/css/base.css +0 -0
  180. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/docs/css/highlight.css +0 -0
  181. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/docs/css/jquery.json-view.min.css +0 -0
  182. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/docs/img/favicon.ico +0 -0
  183. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/docs/img/grid.png +0 -0
  184. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/docs/js/api.js +0 -0
  185. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/docs/js/highlight.pack.js +0 -0
  186. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/docs/js/jquery.json-view.min.js +0 -0
  187. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/fonts/fontawesome-webfont.eot +0 -0
  188. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/fonts/fontawesome-webfont.svg +0 -0
  189. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/fonts/fontawesome-webfont.ttf +0 -0
  190. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/fonts/fontawesome-webfont.woff +0 -0
  191. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/fonts/glyphicons-halflings-regular.eot +0 -0
  192. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/fonts/glyphicons-halflings-regular.svg +0 -0
  193. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/fonts/glyphicons-halflings-regular.ttf +0 -0
  194. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/fonts/glyphicons-halflings-regular.woff +0 -0
  195. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/fonts/glyphicons-halflings-regular.woff2 +0 -0
  196. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/img/glyphicons-halflings-white.png +0 -0
  197. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/img/glyphicons-halflings.png +0 -0
  198. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/img/grid.png +0 -0
  199. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/js/ajax-form.js +0 -0
  200. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/js/bootstrap.min.js +0 -0
  201. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/js/coreapi-0.1.1.js +0 -0
  202. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/js/csrf.js +0 -0
  203. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/js/default.js +0 -0
  204. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/js/jquery-3.7.1.min.js +0 -0
  205. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/js/load-ajax-form.js +0 -0
  206. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/demo/staticfiles/rest_framework/js/prettify-min.js +0 -0
  207. {djresttoolkit-1.0.0/demo/apps_config → djresttoolkit-1.2.0/src/djresttoolkit}/__init__.py +0 -0
  208. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/admin.py +0 -0
  209. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/apps.py +0 -0
  210. {djresttoolkit-1.0.0/src/djresttoolkit → djresttoolkit-1.2.0/src/djresttoolkit/cache}/__init__.py +0 -0
  211. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/cache/mixins/__init__.py +0 -0
  212. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/cache/mixins/_cache_action_mixin.py +0 -0
  213. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/cache/mixins/_cache_invalidate_mixin.py +0 -0
  214. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/cache/mixins/_cache_key_mixin.py +0 -0
  215. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/cache/mixins/_cache_list_retrieve_mixin.py +0 -0
  216. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/cache/mixins/_cache_ops_mixin.py +0 -0
  217. {djresttoolkit-1.0.0/src/djresttoolkit/cache → djresttoolkit-1.2.0/src/djresttoolkit/dbseed}/__init__.py +0 -0
  218. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/dbseed/models/__init__.py +0 -0
  219. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/dbseed/models/_choice_field.py +0 -0
  220. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/dbseed/models/_gen.py +0 -0
  221. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/dbseed/models/_seed_model.py +0 -0
  222. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/envconfig/__init__.py +0 -0
  223. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/envconfig/_base_env_config.py +0 -0
  224. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/mail/__init__.py +0 -0
  225. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/mail/_email_sender.py +0 -0
  226. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/mail/_models.py +0 -0
  227. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/mail/_types.py +0 -0
  228. {djresttoolkit-1.0.0/src/djresttoolkit/dbseed → djresttoolkit-1.2.0/src/djresttoolkit/management}/__init__.py +0 -0
  229. {djresttoolkit-1.0.0/src/djresttoolkit/management → djresttoolkit-1.2.0/src/djresttoolkit/management/commands}/__init__.py +0 -0
  230. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/management/commands/dbflush.py +0 -0
  231. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/management/commands/dbseed.py +0 -0
  232. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/middlewares/__init__.py +0 -0
  233. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/middlewares/_response_time_middleware.py +0 -0
  234. {djresttoolkit-1.0.0/src/djresttoolkit/management/commands → djresttoolkit-1.2.0/src/djresttoolkit/migrations}/__init__.py +0 -0
  235. {djresttoolkit-1.0.0/src/djresttoolkit/migrations → djresttoolkit-1.2.0/src/djresttoolkit/models}/__init__.py +0 -0
  236. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/models/mixins/__init__.py +0 -0
  237. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/models/mixins/_model_choice_fields_mixin.py +0 -0
  238. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/pagination/__init__.py +0 -0
  239. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/pagination/_page_number_pagination.py +0 -0
  240. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/py.typed +0 -0
  241. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/renderers/__init__.py +0 -0
  242. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/renderers/_throttle_info_json_renderer.py +0 -0
  243. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/serializers/mixins/__init__.py +0 -0
  244. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/serializers/mixins/_absolute_url_file_mixin.py +0 -0
  245. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/serializers/mixins/_bulk_create_mixin.py +0 -0
  246. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/throttling/__init__.py +0 -0
  247. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/throttling/_throttle_inspector.py +0 -0
  248. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/urls/__init__.py +0 -0
  249. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/urls/_build_absolute_uri.py +0 -0
  250. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/views/__init__.py +0 -0
  251. {djresttoolkit-1.0.0/src/djresttoolkit/models → djresttoolkit-1.2.0/src/djresttoolkit/views/_apiviews}/__init__.py +0 -0
  252. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/views/_apiviews/_choice_fields_apiview.py +0 -0
  253. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/views/_exceptions/__init__.py +0 -0
  254. {djresttoolkit-1.0.0 → djresttoolkit-1.2.0}/src/djresttoolkit/views/_exceptions/_exception_handler.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: djresttoolkit
3
- Version: 1.0.0
3
+ Version: 1.2.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
@@ -240,7 +240,7 @@ or
240
240
  Flushed 120 records from all models and reset IDs.
241
241
  ```
242
242
 
243
- ### 3. BaseEnvConfig — API Reference
243
+ ### 3. BaseEnvConfig — API Reference
244
244
 
245
245
  ```python
246
246
  from djresttoolkit.envconfig import BaseEnvConfig
@@ -801,6 +801,7 @@ Retrieve a single model object using the provided filter criteria.
801
801
 
802
802
  ```python
803
803
  from rest_framework.views import APIView
804
+ from rest_framework.response import Respone
804
805
  from django.http import JsonResponse
805
806
  from myapp.models import Book
806
807
  from djresttoolkit.mixins import RetrieveObjectMixin
@@ -808,17 +809,18 @@ from djresttoolkit.mixins import RetrieveObjectMixin
808
809
  class BookDetailView(RetrieveObjectMixin[Book], APIView):
809
810
  queryset = Book.objects.all()
810
811
 
811
- def get(self, request, *args, **kwargs):
812
+ def not_found_detail(self) -> dict[str, str] | str:
813
+ return "The requested Book was not found."
814
+
815
+ def get(self, request, *args, **kwargs) -> Respone:
812
816
  book = self.get_object(id=kwargs["id"])
813
- if book:
814
- return JsonResponse({"title": book.title, "author": book.author})
815
- return JsonResponse({"detail": "Not found"}, status=404)
817
+ return Respone({"title": book.title, "author": book.author})
816
818
  ```
817
819
 
818
820
  #### Features of Retrieve Object Mixin
819
821
 
820
822
  - Simplifies object retrieval in class-based views or DRF views.
821
- - Returns `None` instead of raising `DoesNotExist`, making error handling easier.
823
+ - Raise `http404` if requested resource does not extst.
822
824
  - Works with any Django model and queryset.
823
825
 
824
826
  ### 13. build_absolute_uri — API Reference
@@ -1079,6 +1081,27 @@ class BookViewSet(CacheInvalidateMixin, ModelViewSet):
1079
1081
  - Invalidates caches when books are created, updated, or deleted.
1080
1082
  - Supports custom cache keys per action.
1081
1083
 
1084
+ ### 17. EnhancedModelSerializer — API Reference
1085
+
1086
+ A subclass of Django REST Framework’s `ModelSerializer` that automatically merges Django model field `error_messages` into the serializer field, unless explicitly overridden.
1087
+ This helps maintain consistent validation messages between the model and the serializer.
1088
+
1089
+ #### Type Parameters
1090
+
1091
+ - `T` (`Model`): The Django model type that the serializer corresponds to.
1092
+
1093
+ #### Example of EnhancedModelSerializer
1094
+
1095
+ ```python
1096
+ from myapp.models import Book
1097
+ from myapp.serializers import EnhancedModelSerializer
1098
+
1099
+ class BookSerializer(EnhancedModelSerializer[Book]):
1100
+ class Meta:
1101
+ model = Book
1102
+ fields = "__all__"
1103
+ ```
1104
+
1082
1105
  ## 🛠️ Planned Features
1083
1106
 
1084
1107
  - Add more utils
@@ -184,7 +184,7 @@ or
184
184
  Flushed 120 records from all models and reset IDs.
185
185
  ```
186
186
 
187
- ### 3. BaseEnvConfig — API Reference
187
+ ### 3. BaseEnvConfig — API Reference
188
188
 
189
189
  ```python
190
190
  from djresttoolkit.envconfig import BaseEnvConfig
@@ -745,6 +745,7 @@ Retrieve a single model object using the provided filter criteria.
745
745
 
746
746
  ```python
747
747
  from rest_framework.views import APIView
748
+ from rest_framework.response import Respone
748
749
  from django.http import JsonResponse
749
750
  from myapp.models import Book
750
751
  from djresttoolkit.mixins import RetrieveObjectMixin
@@ -752,17 +753,18 @@ from djresttoolkit.mixins import RetrieveObjectMixin
752
753
  class BookDetailView(RetrieveObjectMixin[Book], APIView):
753
754
  queryset = Book.objects.all()
754
755
 
755
- def get(self, request, *args, **kwargs):
756
+ def not_found_detail(self) -> dict[str, str] | str:
757
+ return "The requested Book was not found."
758
+
759
+ def get(self, request, *args, **kwargs) -> Respone:
756
760
  book = self.get_object(id=kwargs["id"])
757
- if book:
758
- return JsonResponse({"title": book.title, "author": book.author})
759
- return JsonResponse({"detail": "Not found"}, status=404)
761
+ return Respone({"title": book.title, "author": book.author})
760
762
  ```
761
763
 
762
764
  #### Features of Retrieve Object Mixin
763
765
 
764
766
  - Simplifies object retrieval in class-based views or DRF views.
765
- - Returns `None` instead of raising `DoesNotExist`, making error handling easier.
767
+ - Raise `http404` if requested resource does not extst.
766
768
  - Works with any Django model and queryset.
767
769
 
768
770
  ### 13. build_absolute_uri — API Reference
@@ -1023,6 +1025,27 @@ class BookViewSet(CacheInvalidateMixin, ModelViewSet):
1023
1025
  - Invalidates caches when books are created, updated, or deleted.
1024
1026
  - Supports custom cache keys per action.
1025
1027
 
1028
+ ### 17. EnhancedModelSerializer — API Reference
1029
+
1030
+ A subclass of Django REST Framework’s `ModelSerializer` that automatically merges Django model field `error_messages` into the serializer field, unless explicitly overridden.
1031
+ This helps maintain consistent validation messages between the model and the serializer.
1032
+
1033
+ #### Type Parameters
1034
+
1035
+ - `T` (`Model`): The Django model type that the serializer corresponds to.
1036
+
1037
+ #### Example of EnhancedModelSerializer
1038
+
1039
+ ```python
1040
+ from myapp.models import Book
1041
+ from myapp.serializers import EnhancedModelSerializer
1042
+
1043
+ class BookSerializer(EnhancedModelSerializer[Book]):
1044
+ class Meta:
1045
+ model = Book
1046
+ fields = "__all__"
1047
+ ```
1048
+
1026
1049
  ## 🛠️ Planned Features
1027
1050
 
1028
1051
  - Add more utils
@@ -57,8 +57,18 @@ class Todo(BaseModel):
57
57
  on_delete=CASCADE,
58
58
  related_name="todos",
59
59
  help_text="Owner of this todo",
60
+ error_messages={
61
+ "null": "Every todo must belong to a user.",
62
+ "invalid": "Invalid user reference.",
63
+ },
64
+ )
65
+ title: CharField[str, str] = CharField(
66
+ max_length=255,
67
+ error_messages={
68
+ "blank": "Please give your todo a title.",
69
+ "max_length": "The title cannot exceed 255 characters.",
70
+ },
60
71
  )
61
- title: CharField[str, str] = CharField(max_length=255)
62
72
  description: TextField[str, str] = TextField(blank=True, default="")
63
73
  due_date: DateTimeField[str | None, str | None] = DateTimeField(
64
74
  null=True,
@@ -69,16 +79,25 @@ class Todo(BaseModel):
69
79
  max_length=20,
70
80
  choices=TodoPriority.choices,
71
81
  default=TodoPriority.MEDIUM,
82
+ error_messages={
83
+ "invalid_choice": "Priority must be one of: low, medium, high, critical.",
84
+ },
72
85
  )
73
86
  status: CharField[str, str] = CharField(
74
87
  max_length=20,
75
88
  choices=TodoStatus.choices,
76
89
  default=TodoStatus.PENDING,
90
+ error_messages={
91
+ "invalid_choice": "Status must be one of: pending, in_progress, completed, archived.",
92
+ },
77
93
  )
78
94
 
79
95
  completed_at: DateTimeField[str | None, str | None] = DateTimeField(
80
96
  null=True,
81
97
  blank=True,
98
+ error_messages={
99
+ "invalid": "Completed at must be a valid datetime.",
100
+ },
82
101
  )
83
102
 
84
103
  def mark_completed(self) -> None:
@@ -0,0 +1,9 @@
1
+ from djresttoolkit.serializers import EnhancedModelSerializer
2
+
3
+ from .models import Todo
4
+
5
+
6
+ class TodoSerializer(EnhancedModelSerializer[Todo]):
7
+ class Meta:
8
+ model = Todo
9
+ exclude = ["created_at", "updated_at"]
@@ -0,0 +1,12 @@
1
+ """
2
+ URL configuration for todos app.
3
+ """
4
+
5
+ from django.urls import path
6
+
7
+ from .views import TodoDetailView, TodoListView
8
+
9
+ urlpatterns = [
10
+ path("", TodoListView.as_view(), name="todo-list"),
11
+ path("<int:id>/", TodoDetailView.as_view(), name="todo-detail"),
12
+ ]
@@ -0,0 +1,43 @@
1
+ from rest_framework.permissions import AllowAny
2
+ from rest_framework.request import Request
3
+ from rest_framework.response import Response
4
+ from rest_framework.views import APIView
5
+
6
+ from djresttoolkit.pagination import PaginatedDataBuilder
7
+ from djresttoolkit.views.mixins import RetrieveObjectMixin
8
+
9
+ from .models import Todo
10
+ from .serializers import TodoSerializer
11
+
12
+
13
+ class TodoListView(APIView):
14
+ permission_classes = [AllowAny]
15
+
16
+ def get(self, request: Request) -> Response:
17
+ pagination = PaginatedDataBuilder[Todo](
18
+ request=request,
19
+ serializer_class=TodoSerializer,
20
+ queryset=Todo.objects.all(),
21
+ )
22
+ return Response(data=pagination.get_paginated_data())
23
+
24
+ def post(self, request: Request) -> Response:
25
+ serializer = TodoSerializer(
26
+ data=request.data,
27
+ many=isinstance(request.data, list),
28
+ )
29
+
30
+ if serializer.is_valid():
31
+ serializer.save(user=request.user)
32
+
33
+ return Response(data=serializer.data)
34
+
35
+
36
+ class TodoDetailView(RetrieveObjectMixin[Todo], APIView):
37
+ permission_classes = [AllowAny]
38
+ queryset = Todo.objects.all()
39
+
40
+ def get(self, request: Request, id: int) -> Response:
41
+ todo = self.get_object(id=id)
42
+ serializer = TodoSerializer(instance=todo, many=False)
43
+ return Response(data=serializer.data)
@@ -0,0 +1,3 @@
1
+ from dotenv import load_dotenv
2
+
3
+ load_dotenv()
@@ -1,8 +1,7 @@
1
1
  import sys
2
+ from os import getenv
2
3
  from pathlib import Path
3
4
 
4
- from env_config import env_settings
5
-
6
5
  # Configuration Settings File for the django backend
7
6
  # --------------------------------------------------
8
7
  BASE_DIR = Path(__file__).resolve().parent.parent.parent
@@ -14,7 +13,7 @@ sys.path.insert(0, str(SRC_DIR))
14
13
 
15
14
  # Security Configuration Settings
16
15
  # -------------------------------
17
- SECRET_KEY = env_settings.secret_key
16
+ SECRET_KEY = getenv("SECRET_KEY")
18
17
 
19
18
  # DEBUG Configuration Settings
20
19
  # ----------------------------
@@ -22,11 +21,11 @@ DEBUG = True
22
21
 
23
22
  # Allowed Host Configuration Settings
24
23
  # -----------------------------------
25
- ALLOWED_HOSTS = env_settings.allowed_hosts
24
+ ALLOWED_HOSTS = ["*"]
26
25
 
27
26
  # Configure CORS Settings
28
27
  # -----------------------
29
- CORS_ALLOWED_ORIGINS = env_settings.cors_allowed_origins
28
+ # CORS_ALLOWED_ORIGINS = []
30
29
 
31
30
  # Login Redirect URL Configuration Setting
32
31
  # ----------------------------------------
@@ -166,6 +165,7 @@ MEDIA_URL = "/media/"
166
165
  # -------------------------------------
167
166
  REST_FRAMEWORK = {
168
167
  "NON_FIELD_ERRORS_KEY": "non_field",
168
+ "DATETIME_FORMAT": "%Y-%m-%d %H:%M",
169
169
  "DEFAULT_AUTHENTICATION_CLASSES": [
170
170
  "rest_framework.authentication.TokenAuthentication",
171
171
  ],
@@ -200,29 +200,13 @@ REST_FRAMEWORK = {
200
200
  # -------------------------------------
201
201
  AUTHENTICATION_BACKENDS = ["django.contrib.auth.backends.ModelBackend"]
202
202
 
203
- # EMAIL Configuration Settings
204
- # ----------------------------
205
- EMAIL_BACKEND = env_settings.email.backend # type: ignore[]
206
- EMAIL_HOST = env_settings.email.host
207
- EMAIL_PORT = env_settings.email.port
208
- EMAIL_USE_TLS = env_settings.email.use_tls
209
- EMAIL_USE_SSL = env_settings.email.use_ssl
210
- EMAIL_HOST_USER = env_settings.email.host_user
211
- EMAIL_HOST_PASSWORD = env_settings.email.host_password
212
- DEFAULT_FROM_EMAIL = env_settings.email.default_from_email
213
-
214
- # Google OAuth2 Configuration Settings
215
- # ------------------------------------
216
- GOOGLE_CLIENT_ID = env_settings.google.client_id
217
- GOOGLE_CLIENT_SECRET = env_settings.google.client_secret
218
- GOOGLE_REDIRECT_URI = env_settings.google.redirect_url
219
203
 
220
204
  # Redis configuration for production
221
205
  # ----------------------------------
222
206
  CACHES = {
223
207
  "default": {
224
208
  "BACKEND": "django_redis.cache.RedisCache",
225
- "LOCATION": env_settings.redis.cache_location,
209
+ "LOCATION": "redis://localhost:6379/1",
226
210
  "OPTIONS": {
227
211
  "CLIENT_CLASS": "django_redis.client.DefaultClient",
228
212
  },
@@ -4,7 +4,7 @@
4
4
 
5
5
  [project]
6
6
  name = "djresttoolkit"
7
- version = "1.0.0"
7
+ version = "1.2.0"
8
8
  description = "A collection of Django and DRF utilities to simplify API development."
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  license = { file = "LICENSE" }
@@ -45,10 +45,7 @@ keywords = [
45
45
  "python-package",
46
46
  ]
47
47
  requires-python = ">=3.13"
48
- dependencies = [
49
- "faker>=37.5.3",
50
- "pydantic>=2.11.7",
51
- ]
48
+ dependencies = ["faker>=37.5.3", "pydantic>=2.11.7"]
52
49
 
53
50
  # CLI scripts entry point configuration
54
51
  # -------------------------------------
@@ -109,12 +106,19 @@ Source = "https://github.com/shaileshpandit141/djresttoolkit"
109
106
  Issues = "https://github.com/shaileshpandit141/djresttoolkit/issues"
110
107
  License = "https://github.com/shaileshpandit141/djresttoolkit/blob/main/LICENSE"
111
108
 
112
- # ===================
113
- # Tool Configurations
114
- # ===================
115
109
 
110
+ # ==========================
111
+ # Add pyright Configurations
112
+ # ==========================
113
+ [tool.pyright]
114
+ typeCheckingMode = "strict"
115
+ reportMissingTypeStubs = false
116
+ reportUnknownMemberType = false
117
+ reportIncompatibleVariableOverride = false
118
+
119
+ # =========================================
116
120
  # Disable ruff S101 rule for test casecases
117
- # -----------------------------------------
121
+ # =========================================
118
122
  [tool.ruff.lint.per-file-ignores]
119
123
  "tests/*.py" = ["S101"]
120
124
 
@@ -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
@@ -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
+ )
@@ -0,0 +1,3 @@
1
+ from ._retrieve_object_mixin import RetrieveObjectMixin
2
+
3
+ __all__ = ["RetrieveObjectMixin"]
@@ -0,0 +1,59 @@
1
+ from typing import Any
2
+
3
+ from django.core.exceptions import ImproperlyConfigured
4
+ from django.db.models import Model, QuerySet
5
+ from django.http import Http404
6
+
7
+
8
+ class RetrieveObjectMixin[T: Model]:
9
+ """
10
+ Retrieve a single model object by filters.
11
+
12
+ Requires the `queryset` attribute to be set in the class that inherits this mixin.
13
+
14
+ Raises `Http404` when the object is missing.
15
+
16
+ This works in both Django views and DRF views.
17
+
18
+ Example:
19
+ ```
20
+ class MyView(RetrieveModelMixin[Book], APIView):
21
+ queryset = Book.objects.all()
22
+
23
+ def get(self, request, *args, **kwargs):
24
+ obj = self.get_object(id=1)
25
+ return JsonResponse(obj.to_dict())
26
+ ```
27
+ """
28
+
29
+ queryset: QuerySet[T] | None = None
30
+
31
+ def get_object(self, **filters: Any) -> T:
32
+ """Retrieve a model object based on provided filters."""
33
+
34
+ if self.queryset is None:
35
+ raise ImproperlyConfigured(
36
+ "Queryset attribute is not set in the class.",
37
+ )
38
+
39
+ try:
40
+ return self.queryset.get(**filters)
41
+ except self.queryset.model.DoesNotExist:
42
+ raise Http404(self.not_found_detail())
43
+
44
+ def not_found_detail(self) -> dict[str, str] | str:
45
+ """
46
+ Hook for customizing the 404 message.
47
+ Can be overridden per view.
48
+ """
49
+
50
+ if self.queryset is None:
51
+ raise ImproperlyConfigured(
52
+ "Queryset attribute is not set in the class.",
53
+ )
54
+
55
+ verbose_name = self.queryset.model._meta.verbose_name
56
+ model_name = (
57
+ verbose_name.title() if verbose_name else self.queryset.model.__name__
58
+ )
59
+ return f"The requested {model_name} was not found."
@@ -342,7 +342,7 @@ wheels = [
342
342
 
343
343
  [[package]]
344
344
  name = "djresttoolkit"
345
- version = "1.0.0"
345
+ version = "1.1.0"
346
346
  source = { editable = "." }
347
347
  dependencies = [
348
348
  { name = "faker" },
@@ -1,7 +0,0 @@
1
- """
2
- URL configuration for todos app.
3
- """
4
-
5
- from django.urls import path
6
-
7
- urlpatterns = []
File without changes
@@ -1,3 +0,0 @@
1
- from .env_settings import env_settings
2
-
3
- __all__ = ["env_settings"]
@@ -1,54 +0,0 @@
1
- from typing import Literal
2
-
3
- from pydantic import BaseModel
4
-
5
- from src.djresttoolkit.envconfig import EnvBaseSettings
6
-
7
-
8
- class DatabaseConfig(BaseModel):
9
- engine: str
10
- name: str
11
- user: str
12
- password: str
13
- host: str
14
- port: int
15
-
16
-
17
- class RedisConfig(BaseModel):
18
- cache_location: str
19
-
20
-
21
- class EmailConfig(BaseModel):
22
- backend: str
23
- host: str
24
- port: int
25
- use_tls: bool
26
- use_ssl: bool
27
- host_user: str
28
- host_password: str
29
- default_from_email: str
30
-
31
-
32
- class GoogleOAuth2Config(BaseModel):
33
- client_id: str
34
- client_secret: str
35
- redirect_url: str
36
-
37
-
38
- class EnvSettings(EnvBaseSettings["EnvSettings"]):
39
- """Env Settings class to handle env loads."""
40
-
41
- secret_key: str
42
- host: str
43
- port: int
44
- environ: Literal["dev", "prod"]
45
- allowed_hosts: list[str]
46
- cors_allowed_origins: list[str]
47
- database: DatabaseConfig
48
- redis: RedisConfig
49
- email: EmailConfig
50
- google: GoogleOAuth2Config
51
-
52
-
53
- env_settings = EnvSettings.load(warning=False)
54
-
@@ -1,6 +0,0 @@
1
- from ._retrieve_object_mixin import RetrieveObjectMixin, QuerysetNotDefinedError
2
-
3
- __all__ = [
4
- "RetrieveObjectMixin",
5
- "QuerysetNotDefinedError",
6
- ]
@@ -1,41 +0,0 @@
1
- from typing import Any
2
- from django.db.models import Model, QuerySet
3
-
4
-
5
- class QuerysetNotDefinedError(Exception):
6
- """Exception raised when the `queryset` attribute is not set in the class."""
7
-
8
- pass
9
-
10
-
11
- class RetrieveObjectMixin[T: Model]:
12
- """
13
- Mixin to provide a method for retrieving a single model object by filters.
14
-
15
- Requires the `queryset` attribute to be set in the class that inherits this mixin.
16
-
17
- Example:
18
- ```
19
- class MyView(RetrieveModelMixin[Book], APIView):
20
- queryset = Book.objects.all()
21
-
22
- def get(self, request, *args, **kwargs):
23
- obj = self.get_object(id=1)
24
- return JsonResponse(obj.to_dict())
25
- ```
26
- """
27
-
28
- queryset: QuerySet[T] | None = None
29
-
30
- def get_object(self, **filters: Any) -> T | None:
31
- """Retrieve a model object based on provided filters."""
32
-
33
- if self.queryset is None:
34
- raise QuerysetNotDefinedError(
35
- "Queryset attribute is not set in the class.",
36
- )
37
-
38
- try:
39
- return self.queryset.get(**filters)
40
- except self.queryset.model.DoesNotExist:
41
- return None
File without changes