django-spire 0.23.6__py3-none-any.whl → 0.23.8__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.
Files changed (541) hide show
  1. django_spire/ai/admin.py +11 -11
  2. django_spire/ai/chat/apps.py +1 -0
  3. django_spire/ai/chat/templates/django_spire/ai/chat/widget/dialog_widget.html +1 -1
  4. django_spire/ai/chat/tests/factories.py +15 -0
  5. django_spire/ai/chat/tests/test_controller.py +45 -0
  6. django_spire/ai/chat/tests/test_models.py +301 -0
  7. django_spire/ai/chat/tests/test_prompts.py +48 -0
  8. django_spire/ai/chat/tests/test_responses.py +208 -0
  9. django_spire/ai/chat/tests/test_router/test_base_chat_router.py +66 -6
  10. django_spire/ai/chat/tests/test_router/test_chat_workflow.py +73 -3
  11. django_spire/ai/chat/tests/test_router/test_integration.py +86 -6
  12. django_spire/ai/chat/tests/test_router/test_intent_decoder.py +93 -1
  13. django_spire/ai/chat/tests/test_router/test_message_intel.py +60 -1
  14. django_spire/ai/chat/tests/test_router/test_spire_chat_router.py +110 -0
  15. django_spire/ai/chat/tests/test_urls/test_json_urls.py +202 -1
  16. django_spire/ai/context/tests/__init__.py +0 -0
  17. django_spire/ai/context/tests/test_context.py +188 -0
  18. django_spire/ai/decorators.py +7 -6
  19. django_spire/ai/prompt/tests/test_bots.py +100 -10
  20. django_spire/ai/prompt/tests/test_prompt_intel.py +83 -0
  21. django_spire/ai/prompt/tests/test_prompt_tuning.py +126 -0
  22. django_spire/ai/sms/decorators.py +8 -2
  23. django_spire/ai/sms/tests/test_sms.py +240 -16
  24. django_spire/ai/sms/tests/test_sms_intel.py +42 -0
  25. django_spire/ai/sms/tests/test_webhook.py +155 -7
  26. django_spire/ai/sms/views.py +23 -24
  27. django_spire/ai/tests/test_ai.py +131 -7
  28. django_spire/auth/apps.py +4 -2
  29. django_spire/auth/controller/controller.py +36 -23
  30. django_spire/auth/controller/exceptions.py +9 -0
  31. django_spire/auth/group/admin.py +1 -0
  32. django_spire/auth/group/apps.py +2 -0
  33. django_spire/auth/group/factories.py +17 -8
  34. django_spire/auth/group/forms.py +7 -0
  35. django_spire/auth/group/tests/test_factories.py +146 -0
  36. django_spire/auth/group/tests/test_forms.py +282 -0
  37. django_spire/auth/group/tests/test_models.py +192 -0
  38. django_spire/auth/group/tests/test_querysets.py +98 -0
  39. django_spire/auth/group/tests/test_utils.py +341 -0
  40. django_spire/auth/group/tests/test_views.py +377 -0
  41. django_spire/auth/group/urls/__init__.py +3 -1
  42. django_spire/auth/group/urls/form_urls.py +2 -0
  43. django_spire/auth/group/urls/json_urls.py +3 -0
  44. django_spire/auth/group/urls/page_urls.py +2 -0
  45. django_spire/auth/group/utils.py +6 -2
  46. django_spire/auth/group/views/form_views.py +6 -3
  47. django_spire/auth/group/views/json_views.py +6 -2
  48. django_spire/auth/mfa/admin.py +2 -0
  49. django_spire/auth/mfa/apps.py +2 -0
  50. django_spire/auth/mfa/forms.py +1 -0
  51. django_spire/auth/mfa/querysets.py +9 -2
  52. django_spire/auth/mfa/tests/test_models.py +233 -0
  53. django_spire/auth/mfa/tests/test_utils.py +106 -0
  54. django_spire/auth/mfa/urls/__init__.py +2 -0
  55. django_spire/auth/mfa/urls/page_urls.py +2 -0
  56. django_spire/auth/mfa/urls/redirect_urls.py +2 -0
  57. django_spire/auth/mfa/views/page_views.py +2 -1
  58. django_spire/auth/permissions/consts.py +2 -2
  59. django_spire/auth/permissions/decorators.py +8 -8
  60. django_spire/auth/permissions/permissions.py +28 -35
  61. django_spire/auth/permissions/tests/test_decorators.py +333 -0
  62. django_spire/auth/permissions/tests/test_permissions.py +337 -0
  63. django_spire/auth/permissions/tests/test_tools.py +305 -0
  64. django_spire/auth/permissions/tools.py +21 -15
  65. django_spire/auth/seeding/seed.py +3 -0
  66. django_spire/auth/seeding/seeder.py +2 -0
  67. django_spire/auth/tests/test_controller.py +323 -0
  68. django_spire/auth/tests/test_url_endpoints.py +9 -9
  69. django_spire/auth/tests/test_views.py +406 -0
  70. django_spire/auth/urls/admin_urls.py +2 -0
  71. django_spire/auth/urls/redirect_urls.py +2 -0
  72. django_spire/auth/user/apps.py +2 -0
  73. django_spire/auth/user/forms.py +9 -0
  74. django_spire/auth/user/models.py +1 -1
  75. django_spire/auth/user/services/services.py +1 -0
  76. django_spire/auth/user/tests/factories.py +14 -13
  77. django_spire/auth/user/tests/test_factories.py +166 -2
  78. django_spire/auth/user/tests/test_forms.py +573 -0
  79. django_spire/auth/user/tests/test_models.py +257 -0
  80. django_spire/auth/user/tests/test_services.py +200 -0
  81. django_spire/auth/user/tests/test_tools.py +153 -0
  82. django_spire/auth/user/tests/test_user_factories.py +139 -0
  83. django_spire/auth/user/tests/test_views.py +363 -0
  84. django_spire/auth/user/tools.py +7 -1
  85. django_spire/auth/user/urls/form_urls.py +3 -0
  86. django_spire/auth/user/urls/page_urls.py +3 -0
  87. django_spire/auth/user/views/form_views.py +19 -10
  88. django_spire/auth/user/views/page_views.py +8 -2
  89. django_spire/auth/views/redirect_views.py +14 -9
  90. django_spire/comment/admin.py +2 -0
  91. django_spire/comment/apps.py +2 -0
  92. django_spire/comment/templatetags/comment_tags.py +1 -0
  93. django_spire/comment/tests/test_forms.py +27 -0
  94. django_spire/comment/tests/test_models.py +215 -0
  95. django_spire/comment/tests/test_querysets.py +101 -0
  96. django_spire/comment/tests/test_utils.py +90 -0
  97. django_spire/comment/urls.py +2 -0
  98. django_spire/comment/utils.py +22 -13
  99. django_spire/comment/views.py +1 -1
  100. django_spire/conf.py +8 -6
  101. django_spire/consts.py +1 -1
  102. django_spire/contrib/breadcrumb/apps.py +2 -0
  103. django_spire/contrib/breadcrumb/breadcrumbs.py +18 -18
  104. django_spire/contrib/breadcrumb/tests/test_breadcrumbs.py +198 -0
  105. django_spire/contrib/constructor/__init__.py +3 -3
  106. django_spire/contrib/constructor/constructor.py +15 -15
  107. django_spire/contrib/constructor/django_model_constructor.py +5 -4
  108. django_spire/contrib/constructor/exceptions.py +5 -3
  109. django_spire/contrib/constructor/tests/__init__.py +0 -0
  110. django_spire/contrib/constructor/tests/test_constructor.py +193 -0
  111. django_spire/contrib/form/tests/__init__.py +0 -0
  112. django_spire/contrib/form/tests/test_forms.py +203 -0
  113. django_spire/contrib/generic_views/modal_views.py +2 -1
  114. django_spire/contrib/generic_views/portal_views.py +20 -19
  115. django_spire/contrib/generic_views/tests/__init__.py +0 -0
  116. django_spire/contrib/generic_views/tests/test_views.py +459 -0
  117. django_spire/contrib/help/apps.py +2 -0
  118. django_spire/contrib/help/templatetags/help.py +1 -0
  119. django_spire/contrib/help/tests/__init__.py +0 -0
  120. django_spire/contrib/help/tests/test_templatetags.py +100 -0
  121. django_spire/contrib/options/mixins.py +6 -5
  122. django_spire/contrib/options/tests/factories.py +5 -1
  123. django_spire/contrib/options/tests/test_options.py +234 -0
  124. django_spire/contrib/ordering/exceptions.py +7 -3
  125. django_spire/contrib/ordering/mixins.py +2 -0
  126. django_spire/contrib/ordering/querysets.py +3 -1
  127. django_spire/contrib/ordering/services/processor_service.py +8 -4
  128. django_spire/contrib/ordering/services/service.py +1 -2
  129. django_spire/contrib/ordering/tests/__init__.py +0 -0
  130. django_spire/contrib/ordering/tests/test_ordering.py +165 -0
  131. django_spire/contrib/ordering/validators.py +6 -6
  132. django_spire/contrib/pagination/templatetags/pagination_tags.py +12 -5
  133. django_spire/contrib/pagination/tests/__init__.py +0 -0
  134. django_spire/contrib/pagination/tests/test_pagination.py +179 -0
  135. django_spire/contrib/performance/decorators.py +16 -6
  136. django_spire/contrib/performance/tests/__init__.py +0 -0
  137. django_spire/contrib/performance/tests/test_performance.py +107 -0
  138. django_spire/contrib/progress/__init__.py +1 -3
  139. django_spire/contrib/progress/static/django_spire/js/contrib/progress/progress.js +38 -82
  140. django_spire/contrib/queryset/enums.py +3 -1
  141. django_spire/contrib/queryset/filter_tools.py +10 -5
  142. django_spire/contrib/queryset/mixins.py +16 -16
  143. django_spire/contrib/queryset/tests/__init__.py +0 -0
  144. django_spire/contrib/queryset/tests/test_queryset.py +137 -0
  145. django_spire/contrib/seeding/field/base.py +13 -7
  146. django_spire/contrib/seeding/field/callable.py +8 -1
  147. django_spire/contrib/seeding/field/cleaners.py +5 -5
  148. django_spire/contrib/seeding/field/custom.py +20 -10
  149. django_spire/contrib/seeding/field/django/seeder.py +8 -6
  150. django_spire/contrib/seeding/field/enums.py +7 -5
  151. django_spire/contrib/seeding/field/override.py +16 -6
  152. django_spire/contrib/seeding/field/static.py +9 -2
  153. django_spire/contrib/seeding/field/tests/test_base.py +18 -14
  154. django_spire/contrib/seeding/field/tests/test_callable.py +13 -9
  155. django_spire/contrib/seeding/field/tests/test_cleaners.py +51 -38
  156. django_spire/contrib/seeding/field/tests/test_static.py +13 -9
  157. django_spire/contrib/seeding/intelligence/bots/seeder_generator_bot.py +2 -0
  158. django_spire/contrib/seeding/intelligence/intel.py +5 -1
  159. django_spire/contrib/seeding/intelligence/prompts/factory.py +6 -1
  160. django_spire/contrib/seeding/intelligence/prompts/foreign_key_selection_prompt.py +6 -1
  161. django_spire/contrib/seeding/intelligence/prompts/generate_django_model_seeder_prompts.py +2 -0
  162. django_spire/contrib/seeding/intelligence/prompts/generic_relationship_selection_prompt.py +7 -1
  163. django_spire/contrib/seeding/intelligence/prompts/hierarchical_selection_prompt.py +6 -2
  164. django_spire/contrib/seeding/intelligence/prompts/model_field_choices_prompt.py +8 -2
  165. django_spire/contrib/seeding/intelligence/prompts/negation_prompt.py +2 -0
  166. django_spire/contrib/seeding/intelligence/prompts/objective_prompt.py +6 -1
  167. django_spire/contrib/seeding/management/commands/seeding.py +9 -3
  168. django_spire/contrib/seeding/management/example.py +2 -0
  169. django_spire/contrib/seeding/model/base.py +16 -7
  170. django_spire/contrib/seeding/model/config.py +31 -15
  171. django_spire/contrib/seeding/model/django/config.py +13 -13
  172. django_spire/contrib/seeding/model/django/seeder.py +4 -4
  173. django_spire/contrib/seeding/model/django/tests/test_seeder.py +34 -23
  174. django_spire/contrib/seeding/model/enums.py +2 -0
  175. django_spire/contrib/seeding/tests/test_config.py +71 -0
  176. django_spire/contrib/seeding/tests/test_custom.py +35 -0
  177. django_spire/contrib/seeding/tests/test_enums.py +40 -0
  178. django_spire/contrib/seeding/tests/test_intel.py +32 -0
  179. django_spire/contrib/seeding/tests/test_override.py +63 -0
  180. django_spire/contrib/service/__init__.py +2 -2
  181. django_spire/contrib/service/django_model_service.py +16 -15
  182. django_spire/contrib/service/exceptions.py +5 -3
  183. django_spire/contrib/service/tests/__init__.py +0 -0
  184. django_spire/contrib/service/tests/test_service.py +153 -0
  185. django_spire/contrib/session/apps.py +2 -0
  186. django_spire/contrib/session/controller.py +48 -42
  187. django_spire/contrib/session/templatetags/session_tags.py +11 -2
  188. django_spire/contrib/session/tests/test_session_controller.py +117 -53
  189. django_spire/contrib/tests/__init__.py +0 -0
  190. django_spire/contrib/tests/test_utils.py +37 -0
  191. django_spire/contrib/utils.py +4 -1
  192. django_spire/core/apps.py +2 -0
  193. django_spire/core/converters/tests/test_to_data.py +353 -0
  194. django_spire/core/converters/tests/test_to_enums.py +61 -41
  195. django_spire/core/converters/tests/test_to_pydantic.py +138 -109
  196. django_spire/core/converters/to_data.py +29 -10
  197. django_spire/core/converters/to_enums.py +4 -2
  198. django_spire/core/converters/to_pydantic.py +22 -22
  199. django_spire/core/decorators.py +19 -6
  200. django_spire/core/forms/widgets.py +4 -0
  201. django_spire/core/maps.py +3 -1
  202. django_spire/core/middleware/maintenance.py +3 -3
  203. django_spire/core/middleware.py +8 -6
  204. django_spire/core/redirect/__init__.py +5 -0
  205. django_spire/core/redirect/generic_redirect.py +1 -2
  206. django_spire/core/redirect/tests/__init__.py +0 -0
  207. django_spire/core/redirect/tests/test_generic_redirect.py +34 -0
  208. django_spire/core/{tests/tests_redirect.py → redirect/tests/test_safe_redirect.py} +55 -81
  209. django_spire/core/shortcuts.py +3 -3
  210. django_spire/core/static/django_spire/css/app-layout.css +1 -1
  211. django_spire/core/static/django_spire/css/app-navigation.css +3 -3
  212. django_spire/core/static/django_spire/css/bootstrap-override.css +4 -0
  213. django_spire/core/tag/admin.py +12 -0
  214. django_spire/core/tag/intelligence/tag_set_bot.py +2 -0
  215. django_spire/core/tag/mixins.py +2 -0
  216. django_spire/core/tag/models.py +2 -0
  217. django_spire/core/tag/querysets.py +2 -0
  218. django_spire/core/tag/service/tag_service.py +6 -3
  219. django_spire/core/tag/tests/test_intelligence.py +9 -9
  220. django_spire/core/tag/tests/test_tags.py +44 -54
  221. django_spire/core/tag/tests/test_tools.py +191 -0
  222. django_spire/core/tag/tools.py +3 -0
  223. django_spire/core/templates/django_spire/card/card.html +5 -2
  224. django_spire/core/templates/django_spire/card/title_card.html +9 -4
  225. django_spire/core/templates/django_spire/infinite_scroll/base.html +1 -0
  226. django_spire/core/templates/django_spire/navigation/side_navigation.html +19 -24
  227. django_spire/core/templates/django_spire/page/full_page.html +46 -16
  228. django_spire/core/templates/django_spire/table/base.html +4 -2
  229. django_spire/core/templatetags/json.py +6 -2
  230. django_spire/core/templatetags/message.py +13 -8
  231. django_spire/core/templatetags/string_formating.py +8 -5
  232. django_spire/core/templatetags/tests/__init__.py +0 -0
  233. django_spire/core/templatetags/tests/test_templatetags.py +427 -0
  234. django_spire/core/templatetags/variable_types.py +17 -9
  235. django_spire/core/tests/test_cases.py +1 -1
  236. django_spire/core/tests/test_conf.py +43 -0
  237. django_spire/core/tests/test_consts.py +28 -0
  238. django_spire/core/tests/test_context_processors.py +93 -0
  239. django_spire/core/tests/test_decorators.py +95 -0
  240. django_spire/core/tests/test_django_spire_utils.py +56 -0
  241. django_spire/core/tests/test_exceptions.py +37 -0
  242. django_spire/core/tests/test_models.py +54 -0
  243. django_spire/core/tests/test_settings.py +45 -0
  244. django_spire/core/tests/test_shortcuts.py +74 -0
  245. django_spire/core/tests/test_urls.py +16 -0
  246. django_spire/core/tests/test_utils.py +58 -0
  247. django_spire/core/urls.py +4 -1
  248. django_spire/core/utils.py +12 -8
  249. django_spire/exceptions.py +16 -1
  250. django_spire/file/admin.py +4 -2
  251. django_spire/file/apps.py +8 -10
  252. django_spire/file/fields.py +7 -7
  253. django_spire/file/forms.py +1 -1
  254. django_spire/file/interfaces.py +15 -15
  255. django_spire/file/mixins.py +1 -4
  256. django_spire/file/models.py +3 -5
  257. django_spire/file/tests/factories.py +59 -0
  258. django_spire/file/tests/test_admin.py +69 -0
  259. django_spire/file/tests/test_apps.py +24 -0
  260. django_spire/file/tests/test_fields.py +114 -0
  261. django_spire/file/tests/test_forms.py +20 -0
  262. django_spire/file/tests/test_interfaces.py +183 -0
  263. django_spire/file/tests/test_models.py +82 -0
  264. django_spire/file/tests/test_querysets.py +102 -0
  265. django_spire/file/tests/test_utils.py +32 -0
  266. django_spire/file/tests/test_views.py +145 -0
  267. django_spire/file/tests/test_widgets.py +82 -0
  268. django_spire/file/tools.py +8 -2
  269. django_spire/file/views.py +7 -3
  270. django_spire/file/widgets.py +12 -12
  271. django_spire/help_desk/admin.py +15 -0
  272. django_spire/help_desk/apps.py +2 -0
  273. django_spire/help_desk/auth/controller.py +2 -0
  274. django_spire/help_desk/choices.py +2 -0
  275. django_spire/help_desk/enums.py +2 -0
  276. django_spire/help_desk/exceptions.py +31 -3
  277. django_spire/help_desk/forms.py +2 -0
  278. django_spire/help_desk/models.py +2 -0
  279. django_spire/help_desk/querysets.py +4 -1
  280. django_spire/help_desk/services/notification_service.py +26 -27
  281. django_spire/help_desk/services/service.py +2 -3
  282. django_spire/help_desk/tests/factories.py +8 -3
  283. django_spire/help_desk/tests/test_admin.py +41 -0
  284. django_spire/help_desk/tests/test_apps.py +41 -0
  285. django_spire/help_desk/tests/test_choices.py +50 -0
  286. django_spire/help_desk/tests/test_controller.py +87 -0
  287. django_spire/help_desk/tests/test_enums.py +18 -0
  288. django_spire/help_desk/tests/test_exceptions.py +37 -0
  289. django_spire/help_desk/tests/test_forms.py +89 -0
  290. django_spire/help_desk/tests/test_models.py +59 -0
  291. django_spire/help_desk/tests/test_querysets.py +38 -0
  292. django_spire/help_desk/tests/test_services/test_notification_service.py +15 -8
  293. django_spire/help_desk/tests/test_services/test_service.py +92 -0
  294. django_spire/help_desk/tests/test_urls/test_form_urls.py +6 -6
  295. django_spire/help_desk/tests/test_urls/test_page_urls.py +8 -9
  296. django_spire/help_desk/tests/test_views/test_form_views.py +46 -19
  297. django_spire/help_desk/tests/test_views/test_page_views.py +32 -9
  298. django_spire/help_desk/urls/__init__.py +4 -1
  299. django_spire/help_desk/urls/form_urls.py +3 -0
  300. django_spire/help_desk/urls/page_urls.py +3 -0
  301. django_spire/help_desk/views/form_views.py +13 -5
  302. django_spire/help_desk/views/page_views.py +11 -3
  303. django_spire/history/activity/admin.py +2 -0
  304. django_spire/history/activity/apps.py +3 -1
  305. django_spire/history/activity/mixins.py +13 -7
  306. django_spire/history/activity/models.py +6 -5
  307. django_spire/history/activity/querysets.py +2 -0
  308. django_spire/history/activity/tests/__init__.py +0 -0
  309. django_spire/history/activity/tests/test_activity.py +176 -0
  310. django_spire/history/admin.py +9 -2
  311. django_spire/history/choices.py +3 -0
  312. django_spire/history/models.py +5 -5
  313. django_spire/history/tests/test_admin.py +93 -0
  314. django_spire/history/tests/test_history.py +101 -0
  315. django_spire/history/tests/test_mixins.py +84 -0
  316. django_spire/history/viewed/admin.py +3 -1
  317. django_spire/history/viewed/apps.py +3 -1
  318. django_spire/history/viewed/models.py +2 -0
  319. django_spire/history/viewed/tests/__init__.py +0 -0
  320. django_spire/history/viewed/tests/test_viewed.py +46 -0
  321. django_spire/knowledge/auth/tests/__init__.py +0 -0
  322. django_spire/knowledge/auth/tests/test_controller.py +116 -0
  323. django_spire/knowledge/collection/admin.py +5 -1
  324. django_spire/knowledge/collection/models.py +3 -1
  325. django_spire/knowledge/collection/seeding/seed.py +1 -0
  326. django_spire/knowledge/collection/services/factory_service.py +10 -11
  327. django_spire/knowledge/collection/services/ordering_service.py +1 -2
  328. django_spire/knowledge/collection/services/service.py +5 -10
  329. django_spire/knowledge/collection/services/tag_service.py +5 -2
  330. django_spire/knowledge/collection/tests/factories.py +28 -1
  331. django_spire/knowledge/collection/tests/test_models.py +48 -0
  332. django_spire/knowledge/collection/tests/test_querysets.py +93 -0
  333. django_spire/knowledge/collection/tests/test_services/test_factory_service.py +100 -0
  334. django_spire/knowledge/collection/tests/test_services/test_services.py +160 -0
  335. django_spire/knowledge/collection/tests/test_urls/test_form_urls.py +21 -3
  336. django_spire/knowledge/collection/tests/test_urls/test_json_urls.py +39 -1
  337. django_spire/knowledge/collection/tests/test_urls/test_page_urls.py +12 -4
  338. django_spire/knowledge/collection/urls/__init__.py +3 -0
  339. django_spire/knowledge/collection/urls/form_urls.py +2 -0
  340. django_spire/knowledge/collection/urls/json_urls.py +2 -0
  341. django_spire/knowledge/collection/urls/page_urls.py +2 -0
  342. django_spire/knowledge/collection/views/form_views.py +4 -4
  343. django_spire/knowledge/collection/views/json_views.py +5 -1
  344. django_spire/knowledge/collection/views/page_views.py +5 -2
  345. django_spire/knowledge/entry/admin.py +7 -1
  346. django_spire/knowledge/entry/forms.py +2 -0
  347. django_spire/knowledge/entry/models.py +2 -0
  348. django_spire/knowledge/entry/seeding/seed.py +3 -0
  349. django_spire/knowledge/entry/services/automation_service.py +5 -4
  350. django_spire/knowledge/entry/services/factory_service.py +7 -5
  351. django_spire/knowledge/entry/services/service.py +4 -7
  352. django_spire/knowledge/entry/services/tag_service.py +0 -1
  353. django_spire/knowledge/entry/services/tool_service.py +1 -0
  354. django_spire/knowledge/entry/services/transformation_services.py +1 -5
  355. django_spire/knowledge/entry/tests/factories.py +1 -2
  356. django_spire/knowledge/entry/tests/test_factory_service.py +20 -0
  357. django_spire/knowledge/entry/tests/test_models.py +41 -0
  358. django_spire/knowledge/entry/tests/test_querysets.py +71 -0
  359. django_spire/knowledge/entry/tests/test_services.py +94 -0
  360. django_spire/knowledge/entry/tests/test_urls/test_form_urls.py +9 -14
  361. django_spire/knowledge/entry/tests/test_urls/test_json_urls.py +48 -5
  362. django_spire/knowledge/entry/tests/test_urls/test_page_urls.py +6 -8
  363. django_spire/knowledge/entry/tests/test_urls/test_template_urls.py +40 -0
  364. django_spire/knowledge/entry/urls/form_urls.py +2 -0
  365. django_spire/knowledge/entry/urls/json_urls.py +2 -0
  366. django_spire/knowledge/entry/urls/page_urls.py +2 -0
  367. django_spire/knowledge/entry/urls/template_urls.py +2 -0
  368. django_spire/knowledge/entry/version/block/choices.py +2 -0
  369. django_spire/knowledge/entry/version/block/data/data.py +1 -0
  370. django_spire/knowledge/entry/version/block/data/list/data.py +8 -13
  371. django_spire/knowledge/entry/version/block/data/list/maps.py +3 -0
  372. django_spire/knowledge/entry/version/block/data/list/meta.py +1 -2
  373. django_spire/knowledge/entry/version/block/data/list/tests/__init__.py +0 -0
  374. django_spire/knowledge/entry/version/block/data/list/tests/test_maps.py +32 -0
  375. django_spire/knowledge/entry/version/block/data/list/tests/test_meta.py +58 -0
  376. django_spire/knowledge/entry/version/block/data/maps.py +3 -6
  377. django_spire/knowledge/entry/version/block/models.py +7 -5
  378. django_spire/knowledge/entry/version/block/seeding/constants.py +5 -4
  379. django_spire/knowledge/entry/version/block/services/service.py +2 -3
  380. django_spire/knowledge/entry/version/block/tests/factories.py +4 -10
  381. django_spire/knowledge/entry/version/block/tests/test_choices.py +56 -0
  382. django_spire/knowledge/entry/version/block/tests/test_data.py +90 -0
  383. django_spire/knowledge/entry/version/block/tests/test_maps.py +37 -0
  384. django_spire/knowledge/entry/version/block/tests/test_models.py +55 -0
  385. django_spire/knowledge/entry/version/block/tests/test_querysets.py +35 -0
  386. django_spire/knowledge/entry/version/block/tests/test_services.py +65 -0
  387. django_spire/knowledge/entry/version/choices.py +2 -0
  388. django_spire/knowledge/entry/version/converters/converter.py +1 -1
  389. django_spire/knowledge/entry/version/converters/docx_converter.py +4 -7
  390. django_spire/knowledge/entry/version/converters/markdown_converter.py +20 -20
  391. django_spire/knowledge/entry/version/maps.py +4 -5
  392. django_spire/knowledge/entry/version/querysets.py +1 -1
  393. django_spire/knowledge/entry/version/seeding/seeder.py +1 -2
  394. django_spire/knowledge/entry/version/services/processor_service.py +5 -4
  395. django_spire/knowledge/entry/version/services/service.py +1 -2
  396. django_spire/knowledge/entry/version/tests/factories.py +2 -2
  397. django_spire/knowledge/entry/version/tests/test_choices.py +18 -0
  398. django_spire/knowledge/entry/version/tests/test_converters/test_docx_converter.py +56 -8
  399. django_spire/knowledge/entry/version/tests/test_converters/test_markdown_converter.py +78 -0
  400. django_spire/knowledge/entry/version/tests/test_maps.py +58 -0
  401. django_spire/knowledge/entry/version/tests/test_models.py +23 -0
  402. django_spire/knowledge/entry/version/tests/test_querysets.py +26 -0
  403. django_spire/knowledge/entry/version/tests/test_services.py +62 -0
  404. django_spire/knowledge/entry/version/tests/test_urls/test_json_urls.py +27 -8
  405. django_spire/knowledge/entry/version/tests/test_urls/test_page_urls.py +15 -8
  406. django_spire/knowledge/entry/version/tests/test_urls/test_redirect_urls.py +38 -0
  407. django_spire/knowledge/entry/version/urls/__init__.py +3 -0
  408. django_spire/knowledge/entry/version/urls/json_urls.py +2 -1
  409. django_spire/knowledge/entry/version/urls/page_urls.py +2 -0
  410. django_spire/knowledge/entry/version/urls/redirect_urls.py +2 -0
  411. django_spire/knowledge/entry/version/views/json_views.py +5 -1
  412. django_spire/knowledge/entry/version/views/page_views.py +10 -3
  413. django_spire/knowledge/entry/version/views/redirect_views.py +5 -1
  414. django_spire/knowledge/entry/views/form_views.py +16 -8
  415. django_spire/knowledge/entry/views/json_views.py +3 -1
  416. django_spire/knowledge/entry/views/page_views.py +8 -2
  417. django_spire/knowledge/entry/views/template_views.py +7 -1
  418. django_spire/knowledge/exceptions.py +2 -1
  419. django_spire/knowledge/intelligence/intel/answer_intel.py +2 -1
  420. django_spire/knowledge/intelligence/intel/entry_intel.py +0 -1
  421. django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +4 -5
  422. django_spire/knowledge/models.py +1 -2
  423. django_spire/knowledge/tests/__init__.py +0 -0
  424. django_spire/knowledge/tests/test_templatetags.py +40 -0
  425. django_spire/knowledge/tests/test_urls/__init__.py +0 -0
  426. django_spire/knowledge/tests/test_urls/test_page_urls.py +24 -0
  427. django_spire/knowledge/urls/__init__.py +2 -0
  428. django_spire/knowledge/urls/page_urls.py +2 -0
  429. django_spire/knowledge/views/page_views.py +8 -3
  430. django_spire/notification/admin.py +3 -1
  431. django_spire/notification/app/admin.py +2 -0
  432. django_spire/notification/app/apps.py +3 -1
  433. django_spire/notification/app/exceptions.py +9 -2
  434. django_spire/notification/app/models.py +8 -4
  435. django_spire/notification/app/processor.py +22 -26
  436. django_spire/notification/app/querysets.py +2 -0
  437. django_spire/notification/app/tests/__init__.py +0 -0
  438. django_spire/notification/app/tests/factories.py +34 -0
  439. django_spire/notification/app/tests/test_apps.py +24 -0
  440. django_spire/notification/app/tests/test_models.py +72 -0
  441. django_spire/notification/app/tests/test_processor.py +111 -0
  442. django_spire/notification/app/tests/test_querysets.py +90 -0
  443. django_spire/notification/app/tests/test_views/__init__.py +0 -0
  444. django_spire/notification/app/tests/test_views/test_json_views.py +48 -0
  445. django_spire/notification/app/tests/test_views/test_page_views.py +19 -0
  446. django_spire/notification/app/urls/__init__.py +3 -1
  447. django_spire/notification/app/urls/json_urls.py +6 -4
  448. django_spire/notification/app/urls/page_urls.py +4 -3
  449. django_spire/notification/app/urls/template_urls.py +4 -2
  450. django_spire/notification/apps.py +4 -1
  451. django_spire/notification/email/admin.py +5 -1
  452. django_spire/notification/email/apps.py +3 -1
  453. django_spire/notification/email/exceptions.py +4 -2
  454. django_spire/notification/email/helper.py +5 -3
  455. django_spire/notification/email/models.py +4 -0
  456. django_spire/notification/email/processor.py +19 -15
  457. django_spire/notification/email/querysets.py +3 -0
  458. django_spire/notification/email/tests/__init__.py +0 -0
  459. django_spire/notification/email/tests/factories.py +35 -0
  460. django_spire/notification/email/tests/test_apps.py +24 -0
  461. django_spire/notification/email/tests/test_models.py +52 -0
  462. django_spire/notification/email/tests/test_processor.py +92 -0
  463. django_spire/notification/email/tests/test_querysets.py +43 -0
  464. django_spire/notification/exceptions.py +17 -2
  465. django_spire/notification/managers.py +7 -1
  466. django_spire/notification/maps.py +4 -1
  467. django_spire/notification/mixins.py +2 -0
  468. django_spire/notification/models.py +3 -1
  469. django_spire/notification/processors/notification.py +12 -5
  470. django_spire/notification/processors/processor.py +2 -0
  471. django_spire/notification/processors/tests/__init__.py +0 -0
  472. django_spire/notification/processors/tests/test_notification.py +106 -0
  473. django_spire/notification/push/admin.py +10 -1
  474. django_spire/notification/push/apps.py +3 -1
  475. django_spire/notification/push/models.py +2 -3
  476. django_spire/notification/push/tests/__init__.py +0 -0
  477. django_spire/notification/push/tests/test_apps.py +24 -0
  478. django_spire/notification/push/tests/test_models.py +28 -0
  479. django_spire/notification/querysets.py +7 -1
  480. django_spire/notification/sms/admin.py +2 -0
  481. django_spire/notification/sms/apps.py +4 -1
  482. django_spire/notification/sms/automations.py +2 -0
  483. django_spire/notification/sms/choices.py +2 -0
  484. django_spire/notification/sms/exceptions.py +19 -5
  485. django_spire/notification/sms/helper.py +33 -23
  486. django_spire/notification/sms/models.py +5 -1
  487. django_spire/notification/sms/processor.py +20 -20
  488. django_spire/notification/sms/querysets.py +2 -0
  489. django_spire/notification/sms/tests/factories.py +33 -0
  490. django_spire/notification/sms/tests/test_apps.py +24 -0
  491. django_spire/notification/sms/tests/test_automation.py +38 -0
  492. django_spire/notification/sms/tests/test_choices.py +15 -0
  493. django_spire/notification/sms/tests/test_consts.py +17 -0
  494. django_spire/notification/sms/tests/test_exceptions.py +27 -0
  495. django_spire/notification/sms/tests/test_helper.py +50 -0
  496. django_spire/notification/sms/tests/test_models.py +81 -0
  497. django_spire/notification/sms/tests/test_processor.py +107 -0
  498. django_spire/notification/sms/tests/test_tools.py +25 -11
  499. django_spire/notification/sms/tools.py +16 -5
  500. django_spire/notification/sms/urls/__init__.py +3 -1
  501. django_spire/notification/sms/urls/media_urls.py +2 -0
  502. django_spire/notification/sms/views/media_views.py +14 -4
  503. django_spire/notification/tests/__init__.py +0 -0
  504. django_spire/notification/tests/factories.py +26 -0
  505. django_spire/notification/tests/test_admin.py +55 -0
  506. django_spire/notification/tests/test_apps.py +30 -0
  507. django_spire/notification/tests/test_automation.py +18 -0
  508. django_spire/notification/tests/test_choices.py +59 -0
  509. django_spire/notification/tests/test_exceptions.py +58 -0
  510. django_spire/notification/tests/test_managers.py +100 -0
  511. django_spire/notification/tests/test_maps.py +31 -0
  512. django_spire/notification/tests/test_models.py +76 -0
  513. django_spire/notification/tests/test_querysets.py +184 -0
  514. django_spire/notification/tests/test_utils.py +23 -0
  515. django_spire/notification/urls.py +3 -1
  516. django_spire/notification/utils.py +3 -1
  517. django_spire/settings.py +3 -0
  518. django_spire/theme/tests/test_context_processor.py +15 -13
  519. django_spire/theme/tests/test_enums.py +2 -2
  520. django_spire/theme/tests/test_filesystem.py +2 -5
  521. django_spire/theme/tests/test_integration.py +12 -12
  522. django_spire/theme/tests/test_model.py +40 -38
  523. django_spire/theme/tests/test_views/test_json_views.py +33 -33
  524. django_spire/theme/urls/json_urls.py +3 -0
  525. django_spire/theme/urls/page_urls.py +3 -0
  526. django_spire/urls.py +19 -15
  527. django_spire/utils.py +13 -4
  528. {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/METADATA +1 -1
  529. {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/RECORD +532 -361
  530. django_spire/contrib/options/tests/test_unit.py +0 -148
  531. django_spire/contrib/progress/views.py +0 -64
  532. django_spire/contrib/seeding/tests/test_seeding.py +0 -25
  533. django_spire/core/tests/test_templatetags.py +0 -117
  534. django_spire/core/tests/tests_shortcuts.py +0 -73
  535. django_spire/history/activity/tests.py +0 -3
  536. django_spire/history/activity/views.py +0 -3
  537. django_spire/knowledge/collection/tests/test_services/test_transformation_service.py +0 -71
  538. django_spire/notification/app/tests.py +0 -3
  539. {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/WHEEL +0 -0
  540. {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/licenses/LICENSE.md +0 -0
  541. {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from enum import Enum
2
4
  from unittest import TestCase
3
5
 
@@ -5,9 +7,9 @@ from django_spire.core.converters.to_enums import django_choices_to_enums
5
7
 
6
8
 
7
9
  class TestDjangoChoicesToEnums(TestCase):
8
-
9
- def test_basic_conversion(self):
10
+ def test_basic_conversion(self) -> None:
10
11
  """Test basic conversion with simple choices"""
12
+
11
13
  choices = [
12
14
  ('ACTIVE', 'Active'),
13
15
  ('INACTIVE', 'Inactive'),
@@ -16,60 +18,78 @@ class TestDjangoChoicesToEnums(TestCase):
16
18
 
17
19
  ResultEnum = django_choices_to_enums('Status', choices)
18
20
 
19
- # Check if enum is created correctly
20
- self.assertTrue(issubclass(ResultEnum, Enum))
21
- self.assertEqual(ResultEnum.__name__, 'Status')
21
+ assert issubclass(ResultEnum, Enum)
22
+ assert ResultEnum.__name__ == 'Status'
22
23
 
23
- # Check enum members
24
- self.assertEqual(ResultEnum.ACTIVE.value, 'ACTIVE')
25
- self.assertEqual(ResultEnum.INACTIVE.value, 'INACTIVE')
26
- self.assertEqual(ResultEnum.PENDING.value, 'PENDING')
24
+ assert ResultEnum.ACTIVE.value == 'ACTIVE'
25
+ assert ResultEnum.INACTIVE.value == 'INACTIVE'
26
+ assert ResultEnum.PENDING.value == 'PENDING'
27
27
 
28
- # Check number of members
29
- self.assertEqual(len(ResultEnum), 3)
28
+ assert len(ResultEnum) == 3
30
29
 
31
- def test_empty_choices(self):
32
- """Test function with empty choices list"""
33
- choices = []
34
- ResultEnum = django_choices_to_enums('EmptyStatus', choices)
30
+ def test_different_key_value(self) -> None:
31
+ """Test when keys and values differ"""
35
32
 
36
- self.assertTrue(issubclass(ResultEnum, Enum))
37
- self.assertEqual(ResultEnum.__name__, 'EmptyStatus')
38
- self.assertEqual(len(ResultEnum), 0)
33
+ choices = [
34
+ ('A', 'Alpha'),
35
+ ('B', 'Beta'),
36
+ ('G', 'Gamma')
37
+ ]
39
38
 
40
- def test_single_choice(self):
41
- """Test with a single choice"""
42
- choices = [('OPEN', 'Open')]
43
- ResultEnum = django_choices_to_enums('SingleStatus', choices)
39
+ ResultEnum = django_choices_to_enums('Greek', choices)
44
40
 
45
- self.assertTrue(issubclass(ResultEnum, Enum))
46
- self.assertEqual(ResultEnum.__name__, 'SingleStatus')
47
- self.assertEqual(ResultEnum.OPEN.value, 'OPEN')
48
- self.assertEqual(len(ResultEnum), 1)
41
+ assert issubclass(ResultEnum, Enum)
42
+ assert ResultEnum.A.value == 'A'
43
+ assert ResultEnum.B.value == 'B'
44
+ assert ResultEnum.G.value == 'G'
45
+ assert len(ResultEnum) == 3
49
46
 
50
- def test_duplicate_keys(self):
47
+ def test_duplicate_keys(self) -> None:
51
48
  """Test behavior with duplicate keys (should use last occurrence)"""
49
+
52
50
  choices = [
53
51
  ('STATUS', 'First'),
54
52
  ('STATUS', 'Second')
55
53
  ]
54
+
56
55
  ResultEnum = django_choices_to_enums('DuplicateStatus', choices)
57
56
 
58
- self.assertTrue(issubclass(ResultEnum, Enum))
59
- self.assertEqual(ResultEnum.STATUS.value, 'STATUS')
60
- self.assertEqual(len(ResultEnum), 1)
57
+ assert issubclass(ResultEnum, Enum)
58
+ assert ResultEnum.STATUS.value == 'STATUS'
59
+ assert len(ResultEnum) == 1
60
+
61
+ def test_empty_choices(self) -> None:
62
+ """Test function with empty choices list"""
63
+
64
+ choices = []
65
+ ResultEnum = django_choices_to_enums('EmptyStatus', choices)
66
+
67
+ assert issubclass(ResultEnum, Enum)
68
+ assert ResultEnum.__name__ == 'EmptyStatus'
69
+ assert len(ResultEnum) == 0
70
+
71
+ def test_lowercase_keys_converted_to_uppercase(self) -> None:
72
+ """Test that lowercase keys are converted to uppercase"""
61
73
 
62
- def test_different_key_value(self):
63
- """Test when keys and values differ"""
64
74
  choices = [
65
- ('A', 'Alpha'),
66
- ('B', 'Beta'),
67
- ('G', 'Gamma')
75
+ ('active', 'Active'),
76
+ ('inactive', 'Inactive')
68
77
  ]
69
- ResultEnum = django_choices_to_enums('Greek', choices)
70
78
 
71
- self.assertTrue(issubclass(ResultEnum, Enum))
72
- self.assertEqual(ResultEnum.A.value, 'A')
73
- self.assertEqual(ResultEnum.B.value, 'B')
74
- self.assertEqual(ResultEnum.G.value, 'G')
75
- self.assertEqual(len(ResultEnum), 3)
79
+ ResultEnum = django_choices_to_enums('Status', choices)
80
+
81
+ assert hasattr(ResultEnum, 'ACTIVE')
82
+ assert hasattr(ResultEnum, 'INACTIVE')
83
+ assert ResultEnum.ACTIVE.value == 'active'
84
+ assert ResultEnum.INACTIVE.value == 'inactive'
85
+
86
+ def test_single_choice(self) -> None:
87
+ """Test with a single choice"""
88
+
89
+ choices = [('OPEN', 'Open')]
90
+ ResultEnum = django_choices_to_enums('SingleStatus', choices)
91
+
92
+ assert issubclass(ResultEnum, Enum)
93
+ assert ResultEnum.__name__ == 'SingleStatus'
94
+ assert ResultEnum.OPEN.value == 'OPEN'
95
+ assert len(ResultEnum) == 1
@@ -1,42 +1,44 @@
1
+ from __future__ import annotations
2
+
3
+ import enum
4
+
5
+ from typing import get_args, get_origin, Union
1
6
  from unittest import TestCase
2
- from typing import get_origin, get_args, Union
3
7
 
4
8
  from pydantic import BaseModel
5
9
 
6
- from django.core.validators import MinValueValidator, MaxValueValidator
10
+ from django.core.validators import MaxValueValidator, MinValueValidator
7
11
  from django.db import models
8
- import enum
9
12
 
10
13
  from django_spire.core.converters.to_pydantic import DjangoToPydanticFieldConverter, django_to_pydantic_model
11
14
 
12
15
 
13
- def is_optional(typ):
14
- return get_origin(typ) is Union and type(None) in get_args(typ)
16
+ def is_optional(t: type) -> bool:
17
+ return get_origin(t) is Union and type(None) in get_args(t)
15
18
 
16
19
 
17
20
  class TestDjangoModelToPydanticModel(TestCase):
18
- def test_comprehensive_model_conversion(self):
21
+ def test_comprehensive_model_conversion(self) -> None:
19
22
  class ComprehensiveModel(models.Model):
20
- # Django auto-adds an "id" primary key field.
21
23
  char_field_with_choices = models.CharField(
22
24
  max_length=10,
23
- choices=[("A", "Active"), ("I", "Inactive")],
24
- default="A",
25
- help_text="Status of the item",
25
+ choices=[('A', 'Active'), ('I', 'Inactive')],
26
+ default='A',
27
+ help_text='Status of the item',
26
28
  unique=True,
27
29
  null=False
28
30
  )
29
31
  char_field_no_choices = models.CharField(
30
32
  max_length=20,
31
- default="default",
32
- help_text="Description",
33
+ default='default',
34
+ help_text='Description',
33
35
  unique=False,
34
36
  null=True
35
37
  )
36
38
  int_field = models.IntegerField(
37
39
  validators=[MinValueValidator(0), MaxValueValidator(100)],
38
40
  default=50,
39
- help_text="Count",
41
+ help_text='Count',
40
42
  unique=False,
41
43
  null=True
42
44
  )
@@ -44,191 +46,218 @@ class TestDjangoModelToPydanticModel(TestCase):
44
46
  max_digits=5,
45
47
  decimal_places=2,
46
48
  default=0.0,
47
- help_text="Price",
49
+ help_text='Price',
48
50
  unique=False,
49
51
  null=False
50
52
  )
51
53
  boolean_field = models.BooleanField(
52
54
  default=True,
53
- help_text="Active",
55
+ help_text='Active',
54
56
  null=False
55
57
  )
56
58
 
57
59
  class Meta:
58
60
  managed = False
59
61
 
62
+ def __str__(self) -> str:
63
+ return 'model'
64
+
60
65
  PydanticModel = django_to_pydantic_model(ComprehensiveModel)
61
- self.assertEqual(PydanticModel.__name__, "ComprehensiveModel")
66
+ assert PydanticModel.__name__ == 'ComprehensiveModel'
62
67
 
63
- def test_conversion_with_custom_base_class(self):
68
+ def test_conversion_with_custom_base_class(self) -> None:
64
69
  class CustomBase(BaseModel):
65
- extra_field: str = "extra"
70
+ extra_field: str = 'extra'
66
71
 
67
72
  class CustomModel(models.Model):
68
- char_field = models.CharField(max_length=10, default="custom")
73
+ char_field = models.CharField(max_length=10, default='custom')
74
+
69
75
  class Meta:
70
76
  managed = False
71
77
 
78
+ def __str__(self) -> str:
79
+ return 'model'
80
+
72
81
  PydanticModel = django_to_pydantic_model(CustomModel, base_class=CustomBase)
73
- instance = PydanticModel(id=1, char_field="custom", extra_field="extra")
82
+ instance = PydanticModel(id=1, char_field='custom', extra_field='extra')
74
83
 
75
- self.assertEqual(instance.char_field, "custom")
76
- self.assertEqual(instance.extra_field, "extra")
84
+ assert instance.char_field == 'custom'
85
+ assert instance.extra_field == 'extra'
77
86
 
78
- def test_include_fields_only(self):
79
- class SimpleModel1(models.Model):
80
- field1 = models.CharField(max_length=50, default="value1")
87
+ def test_exclude_fields(self) -> None:
88
+ class SimpleModel2(models.Model):
89
+ field1 = models.CharField(max_length=50, default='value1')
81
90
  field2 = models.IntegerField(default=10)
82
91
  field3 = models.BooleanField(default=True)
92
+
83
93
  class Meta:
84
94
  managed = False
85
95
 
86
- PydanticModel = django_to_pydantic_model(SimpleModel1, include_fields=["id", "field1", "field3"])
87
- fields = PydanticModel.__fields__
88
- self.assertIn("id", fields)
89
- self.assertIn("field1", fields)
90
- self.assertIn("field3", fields)
91
- self.assertNotIn("field2", fields)
96
+ def __str__(self) -> str:
97
+ return 'model'
92
98
 
93
- def test_exclude_fields(self):
94
- class SimpleModel2(models.Model):
95
- field1 = models.CharField(max_length=50, default="value1")
99
+ PydanticModel = django_to_pydantic_model(SimpleModel2, exclude_fields=['field2'])
100
+ fields = PydanticModel.model_fields
101
+ assert 'id' in fields
102
+ assert 'field1' in fields
103
+ assert 'field3' in fields
104
+ assert 'field2' not in fields
105
+
106
+ def test_include_and_exclude(self) -> None:
107
+ class SimpleModel3(models.Model):
108
+ field1 = models.CharField(max_length=50, default='value1')
96
109
  field2 = models.IntegerField(default=10)
97
110
  field3 = models.BooleanField(default=True)
111
+ field4 = models.CharField(max_length=100, default='value4')
112
+
98
113
  class Meta:
99
114
  managed = False
100
115
 
101
- PydanticModel = django_to_pydantic_model(SimpleModel2, exclude_fields=["field2"])
102
- fields = PydanticModel.__fields__
103
- self.assertIn("id", fields)
104
- self.assertIn("field1", fields)
105
- self.assertIn("field3", fields)
106
- self.assertNotIn("field2", fields)
116
+ def __str__(self) -> str:
117
+ return 'model'
107
118
 
108
- def test_include_and_exclude(self):
109
- # Define a simple model with multiple fields.
110
- class SimpleModel3(models.Model):
111
- field1 = models.CharField(max_length=50, default="value1")
119
+ PydanticModel = django_to_pydantic_model(
120
+ SimpleModel3,
121
+ include_fields=['id', 'field1', 'field2', 'field3'],
122
+ exclude_fields=['field2']
123
+ )
124
+
125
+ fields = PydanticModel.model_fields
126
+ assert 'id' in fields
127
+ assert 'field1' in fields
128
+ assert 'field3' in fields
129
+ assert 'field2' not in fields
130
+ assert 'field4' not in fields
131
+
132
+ def test_include_fields_only(self) -> None:
133
+ class SimpleModel1(models.Model):
134
+ field1 = models.CharField(max_length=50, default='value1')
112
135
  field2 = models.IntegerField(default=10)
113
136
  field3 = models.BooleanField(default=True)
114
- field4 = models.CharField(max_length=100, default="value4")
137
+
115
138
  class Meta:
116
139
  managed = False
117
140
 
118
- PydanticModel = django_to_pydantic_model(
119
- SimpleModel3,
120
- include_fields=["id", "field1", "field2", "field3"],
121
- exclude_fields=["field2"]
122
- )
123
- fields = PydanticModel.__fields__
124
- self.assertIn("id", fields)
125
- self.assertIn("field1", fields)
126
- self.assertIn("field3", fields)
127
- self.assertNotIn("field2", fields)
128
- self.assertNotIn("field4", fields)
141
+ def __str__(self) -> str:
142
+ return 'model'
143
+
144
+ PydanticModel = django_to_pydantic_model(SimpleModel1, include_fields=['id', 'field1', 'field3'])
145
+ fields = PydanticModel.model_fields
146
+ assert 'id' in fields
147
+ assert 'field1' in fields
148
+ assert 'field3' in fields
149
+ assert 'field2' not in fields
129
150
 
130
151
 
131
152
  class TestDjangoToPydanticFieldConverter(TestCase):
132
153
  # def test_nullable_field_is_optional(self):
133
154
  # class Model(models.Model):
134
155
  # char_field = models.CharField(max_length=10, null=True)
135
- #
156
+
136
157
  # class Meta:
137
158
  # managed = False
138
- #
159
+
160
+ # def __str__(self) -> str:
161
+ # return 'model'
162
+
139
163
  # field = Model._meta.get_field('char_field')
140
164
  # field_type, _ = DjangoToPydanticFieldConverter(field).build_field()
141
- #
142
- # self.assertTrue(is_optional(field_type))
143
165
 
144
- def test_field_with_no_default_sets_none(self):
145
- class Model1(models.Model):
146
- char_field = models.CharField(max_length=10, null=True)
166
+ # assert is_optional(field_type)
167
+
168
+ def test_charfield_constraints_applied(self) -> None:
169
+ class Model4(models.Model):
170
+ name = models.CharField(max_length=30)
147
171
 
148
172
  class Meta:
149
173
  managed = False
150
174
 
151
- field = Model1._meta.get_field('char_field')
152
- _, field_info = DjangoToPydanticFieldConverter(field).build_field()
175
+ def __str__(self) -> str:
176
+ return 'model'
153
177
 
154
- self.assertIsNone(field_info.default)
178
+ field = Model4._meta.get_field('name')
179
+ field_type, _ = DjangoToPydanticFieldConverter(field).build_field()
155
180
 
156
- # def test_field_with_default(self):
157
- # class Model(models.Model):
158
- # char_field = models.CharField(max_length=10, default="abc")
159
- #
160
- # class Meta:
161
- # managed = False
162
- #
163
- # field = Model._meta.get_field('char_field')
164
- # _, field_info = DjangoToPydanticFieldConverter(field).build_field()
165
- #
166
- # self.assertEqual(field_info.default, "abc")
181
+ assert 'max_length=30' in str(field_type)
167
182
 
168
- def test_help_text_becomes_description(self):
169
- class Model2(models.Model):
170
- char_field = models.CharField(max_length=10, help_text="hello")
183
+ def test_decimalfield_constraints_applied(self) -> None:
184
+ class Model6(models.Model):
185
+ amount = models.DecimalField(max_digits=5, decimal_places=2)
171
186
 
172
187
  class Meta:
173
188
  managed = False
174
189
 
175
- field = Model2._meta.get_field('char_field')
176
- _, field_info = DjangoToPydanticFieldConverter(field).build_field()
190
+ def __str__(self) -> str:
191
+ return 'model'
177
192
 
178
- self.assertEqual(field_info.description, "hello")
193
+ field = Model6._meta.get_field('amount')
194
+ field_type, _ = DjangoToPydanticFieldConverter(field).build_field()
195
+
196
+ assert 'max_digits=5' in str(field_type)
197
+ assert 'decimal_places=2' in str(field_type)
179
198
 
180
- def test_enum_created_for_choices(self):
199
+ def test_enum_created_for_choices(self) -> None:
181
200
  class Model3(models.Model):
182
201
  status = models.CharField(
183
202
  max_length=10,
184
- choices=[("A", "Active"), ("I", "Inactive")],
185
- default="A"
203
+ choices=[('A', 'Active'), ('I', 'Inactive')],
204
+ default='A'
186
205
  )
187
206
 
188
207
  class Meta:
189
208
  managed = False
190
209
 
210
+ def __str__(self) -> str:
211
+ return 'model'
212
+
191
213
  field = Model3._meta.get_field('status')
192
214
  field_type, _ = DjangoToPydanticFieldConverter(field).build_field()
193
215
 
194
- self.assertTrue(issubclass(field_type, enum.Enum))
195
- self.assertEqual(set(field_type.__members__), {"A", "I"})
216
+ assert issubclass(field_type, enum.Enum)
217
+ assert set(field_type.__members__) == {'A', 'I'}
196
218
 
197
- def test_charfield_constraints_applied(self):
198
- class Model4(models.Model):
199
- name = models.CharField(max_length=30)
219
+ def test_field_with_no_default_sets_none(self) -> None:
220
+ class Model1(models.Model):
221
+ char_field = models.CharField(max_length=10, null=True)
200
222
 
201
223
  class Meta:
202
224
  managed = False
203
225
 
204
- field = Model4._meta.get_field('name')
205
- field_type, _ = DjangoToPydanticFieldConverter(field).build_field()
226
+ def __str__(self) -> str:
227
+ return 'model'
206
228
 
207
- self.assertIn("max_length=30", str(field_type))
229
+ field = Model1._meta.get_field('char_field')
230
+ _, field_info = DjangoToPydanticFieldConverter(field).build_field()
208
231
 
209
- def test_integerfield_validators_become_constraints(self):
210
- # Todo: test needs to be corrected.
211
- from django.core.validators import MinValueValidator, MaxValueValidator
232
+ assert field_info.default is None
212
233
 
213
- class Model5(models.Model):
214
- count = models.IntegerField(validators=[MinValueValidator(1), MaxValueValidator(10)])
234
+ def test_help_text_becomes_description(self) -> None:
235
+ class Model2(models.Model):
236
+ char_field = models.CharField(max_length=10, help_text='hello')
215
237
 
216
238
  class Meta:
217
239
  managed = False
218
240
 
219
- field = Model5._meta.get_field('count')
220
- field_type, field_info = DjangoToPydanticFieldConverter(field).build_field()
221
- self.assertIn("int", str(field_type))
241
+ def __str__(self) -> str:
242
+ return 'model'
222
243
 
223
- def test_decimalfield_constraints_applied(self):
224
- class Model6(models.Model):
225
- amount = models.DecimalField(max_digits=5, decimal_places=2)
244
+ field = Model2._meta.get_field('char_field')
245
+ _, field_info = DjangoToPydanticFieldConverter(field).build_field()
246
+
247
+ assert field_info.description == 'hello'
248
+
249
+ def test_integerfield_validators_become_constraints(self) -> None:
250
+ # TODO(Nathan): test needs to be corrected.
251
+
252
+ class Model5(models.Model):
253
+ count = models.IntegerField(validators=[MinValueValidator(1), MaxValueValidator(10)])
226
254
 
227
255
  class Meta:
228
256
  managed = False
229
257
 
230
- field = Model6._meta.get_field('amount')
231
- field_type, _ = DjangoToPydanticFieldConverter(field).build_field()
258
+ def __str__(self) -> str:
259
+ return 'model'
232
260
 
233
- self.assertIn("max_digits=5", str(field_type))
234
- self.assertIn("decimal_places=2", str(field_type))
261
+ field = Model5._meta.get_field('count')
262
+ field_type, _ = DjangoToPydanticFieldConverter(field).build_field()
263
+ assert 'int' in str(field_type)
@@ -1,18 +1,28 @@
1
- from django.db import models
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import random
5
+
6
+ from typing import TYPE_CHECKING
7
+
2
8
  from django.core.validators import MinValueValidator, MaxValueValidator
3
9
  from faker import Faker
4
- import random
5
- import os
10
+
11
+ if TYPE_CHECKING:
12
+ from typing import Any
13
+
14
+ from django.db import models
6
15
 
7
16
 
8
17
  class DjangoFieldToFakerData:
9
18
  def __init__(
10
- self,
11
- model_field: models.Field,
12
- faker_method: tuple | None = None
19
+ self,
20
+ model_field: models.Field,
21
+ faker_method: tuple | None = None
13
22
  ):
14
23
  if isinstance(faker_method, str):
15
24
  faker_method = (faker_method,)
25
+
16
26
  self.model_field = model_field
17
27
  self.faker_method = faker_method
18
28
  self.faker = Faker()
@@ -40,22 +50,28 @@ class DjangoFieldToFakerData:
40
50
  def convert(self):
41
51
  if self.faker_method:
42
52
  result = self._use_faker_with_method()
53
+
43
54
  if result is not None:
44
55
  return result
45
56
 
46
57
  converter = self.field_converters.get(self.model_field.get_internal_type())
58
+
47
59
  if converter is None:
48
- raise Exception(f'No handler for {self.model_field.get_internal_type()}')
60
+ message = f'No handler for {self.model_field.get_internal_type()}'
61
+ raise Exception(message)
62
+
49
63
  return converter()
50
64
 
51
65
  def _get_min_max_from_validators(self):
52
66
  min_value = 0
53
67
  max_value = 10000
68
+
54
69
  for validator in self.model_field.validators:
55
70
  if isinstance(validator, MinValueValidator):
56
71
  min_value = validator.limit_value
57
72
  elif isinstance(validator, MaxValueValidator):
58
73
  max_value = validator.limit_value
74
+
59
75
  return min_value, max_value
60
76
 
61
77
  def _use_faker_with_method(self):
@@ -64,6 +80,7 @@ class DjangoFieldToFakerData:
64
80
  kwargs = self.faker_method[1] if len(self.faker_method) > 1 and isinstance(self.faker_method[1], dict) else {}
65
81
 
66
82
  method = getattr(self.faker, method_name, None)
83
+
67
84
  if callable(method):
68
85
  return method(**kwargs)
69
86
 
@@ -79,8 +96,6 @@ class DjangoFieldToFakerData:
79
96
  choices = [choice[0] for choice in self.model_field.choices]
80
97
  return self.faker.random_element(elements=choices)
81
98
 
82
-
83
-
84
99
  return self.faker.text(max_nb_chars=max_length)
85
100
 
86
101
  def _integer_field_data(self):
@@ -126,6 +141,10 @@ class DjangoFieldToFakerData:
126
141
  return os.urandom(16)
127
142
 
128
143
 
129
- def fake_model_field_value(model_class, field_name: str, faker_method=None):
144
+ def fake_model_field_value(
145
+ model_class: type[models.Model],
146
+ field_name: str,
147
+ faker_method: str | tuple | None = None
148
+ ) -> Any:
130
149
  field = model_class._meta.get_field(field_name)
131
150
  return DjangoFieldToFakerData(field, faker_method).convert()
@@ -1,9 +1,11 @@
1
+ from __future__ import annotations
2
+
1
3
  from enum import Enum
2
4
 
3
5
 
4
6
  def django_choices_to_enums(
5
- class_name: str,
6
- choices: list[tuple[str, str]]
7
+ class_name: str,
8
+ choices: list[tuple[str, str]]
7
9
  ):
8
10
  choices_dict = {str(k).upper(): k for k, _ in choices}
9
11
  return Enum(class_name, choices_dict)