django-spire 0.23.7__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 (538) 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/queryset/enums.py +3 -1
  139. django_spire/contrib/queryset/filter_tools.py +10 -5
  140. django_spire/contrib/queryset/mixins.py +16 -16
  141. django_spire/contrib/queryset/tests/__init__.py +0 -0
  142. django_spire/contrib/queryset/tests/test_queryset.py +137 -0
  143. django_spire/contrib/seeding/field/base.py +13 -7
  144. django_spire/contrib/seeding/field/callable.py +8 -1
  145. django_spire/contrib/seeding/field/cleaners.py +5 -5
  146. django_spire/contrib/seeding/field/custom.py +20 -10
  147. django_spire/contrib/seeding/field/django/seeder.py +8 -6
  148. django_spire/contrib/seeding/field/enums.py +7 -5
  149. django_spire/contrib/seeding/field/override.py +16 -6
  150. django_spire/contrib/seeding/field/static.py +9 -2
  151. django_spire/contrib/seeding/field/tests/test_base.py +18 -14
  152. django_spire/contrib/seeding/field/tests/test_callable.py +13 -9
  153. django_spire/contrib/seeding/field/tests/test_cleaners.py +51 -38
  154. django_spire/contrib/seeding/field/tests/test_static.py +13 -9
  155. django_spire/contrib/seeding/intelligence/bots/seeder_generator_bot.py +2 -0
  156. django_spire/contrib/seeding/intelligence/intel.py +5 -1
  157. django_spire/contrib/seeding/intelligence/prompts/factory.py +6 -1
  158. django_spire/contrib/seeding/intelligence/prompts/foreign_key_selection_prompt.py +6 -1
  159. django_spire/contrib/seeding/intelligence/prompts/generate_django_model_seeder_prompts.py +2 -0
  160. django_spire/contrib/seeding/intelligence/prompts/generic_relationship_selection_prompt.py +7 -1
  161. django_spire/contrib/seeding/intelligence/prompts/hierarchical_selection_prompt.py +6 -2
  162. django_spire/contrib/seeding/intelligence/prompts/model_field_choices_prompt.py +8 -2
  163. django_spire/contrib/seeding/intelligence/prompts/negation_prompt.py +2 -0
  164. django_spire/contrib/seeding/intelligence/prompts/objective_prompt.py +6 -1
  165. django_spire/contrib/seeding/management/commands/seeding.py +9 -3
  166. django_spire/contrib/seeding/management/example.py +2 -0
  167. django_spire/contrib/seeding/model/base.py +16 -7
  168. django_spire/contrib/seeding/model/config.py +31 -15
  169. django_spire/contrib/seeding/model/django/config.py +13 -13
  170. django_spire/contrib/seeding/model/django/seeder.py +4 -4
  171. django_spire/contrib/seeding/model/django/tests/test_seeder.py +34 -23
  172. django_spire/contrib/seeding/model/enums.py +2 -0
  173. django_spire/contrib/seeding/tests/test_config.py +71 -0
  174. django_spire/contrib/seeding/tests/test_custom.py +35 -0
  175. django_spire/contrib/seeding/tests/test_enums.py +40 -0
  176. django_spire/contrib/seeding/tests/test_intel.py +32 -0
  177. django_spire/contrib/seeding/tests/test_override.py +63 -0
  178. django_spire/contrib/service/__init__.py +2 -2
  179. django_spire/contrib/service/django_model_service.py +16 -15
  180. django_spire/contrib/service/exceptions.py +5 -3
  181. django_spire/contrib/service/tests/__init__.py +0 -0
  182. django_spire/contrib/service/tests/test_service.py +153 -0
  183. django_spire/contrib/session/apps.py +2 -0
  184. django_spire/contrib/session/controller.py +48 -42
  185. django_spire/contrib/session/templatetags/session_tags.py +11 -2
  186. django_spire/contrib/session/tests/test_session_controller.py +117 -53
  187. django_spire/contrib/tests/__init__.py +0 -0
  188. django_spire/contrib/tests/test_utils.py +37 -0
  189. django_spire/contrib/utils.py +4 -1
  190. django_spire/core/apps.py +2 -0
  191. django_spire/core/converters/tests/test_to_data.py +353 -0
  192. django_spire/core/converters/tests/test_to_enums.py +61 -41
  193. django_spire/core/converters/tests/test_to_pydantic.py +138 -109
  194. django_spire/core/converters/to_data.py +29 -10
  195. django_spire/core/converters/to_enums.py +4 -2
  196. django_spire/core/converters/to_pydantic.py +22 -22
  197. django_spire/core/decorators.py +19 -6
  198. django_spire/core/forms/widgets.py +4 -0
  199. django_spire/core/maps.py +3 -1
  200. django_spire/core/middleware/maintenance.py +3 -3
  201. django_spire/core/middleware.py +8 -6
  202. django_spire/core/redirect/__init__.py +5 -0
  203. django_spire/core/redirect/generic_redirect.py +1 -2
  204. django_spire/core/redirect/tests/__init__.py +0 -0
  205. django_spire/core/redirect/tests/test_generic_redirect.py +34 -0
  206. django_spire/core/{tests/tests_redirect.py → redirect/tests/test_safe_redirect.py} +55 -81
  207. django_spire/core/shortcuts.py +3 -3
  208. django_spire/core/static/django_spire/css/app-layout.css +1 -1
  209. django_spire/core/static/django_spire/css/app-navigation.css +3 -3
  210. django_spire/core/static/django_spire/css/bootstrap-override.css +4 -0
  211. django_spire/core/tag/admin.py +12 -0
  212. django_spire/core/tag/intelligence/tag_set_bot.py +2 -0
  213. django_spire/core/tag/mixins.py +2 -0
  214. django_spire/core/tag/models.py +2 -0
  215. django_spire/core/tag/querysets.py +2 -0
  216. django_spire/core/tag/service/tag_service.py +6 -3
  217. django_spire/core/tag/tests/test_intelligence.py +9 -9
  218. django_spire/core/tag/tests/test_tags.py +44 -54
  219. django_spire/core/tag/tests/test_tools.py +191 -0
  220. django_spire/core/tag/tools.py +3 -0
  221. django_spire/core/templates/django_spire/card/card.html +5 -2
  222. django_spire/core/templates/django_spire/card/title_card.html +9 -4
  223. django_spire/core/templates/django_spire/infinite_scroll/base.html +1 -0
  224. django_spire/core/templates/django_spire/navigation/side_navigation.html +19 -24
  225. django_spire/core/templates/django_spire/page/full_page.html +46 -16
  226. django_spire/core/templates/django_spire/table/base.html +4 -2
  227. django_spire/core/templatetags/json.py +6 -2
  228. django_spire/core/templatetags/message.py +13 -8
  229. django_spire/core/templatetags/string_formating.py +8 -5
  230. django_spire/core/templatetags/tests/__init__.py +0 -0
  231. django_spire/core/templatetags/tests/test_templatetags.py +427 -0
  232. django_spire/core/templatetags/variable_types.py +17 -9
  233. django_spire/core/tests/test_cases.py +1 -1
  234. django_spire/core/tests/test_conf.py +43 -0
  235. django_spire/core/tests/test_consts.py +28 -0
  236. django_spire/core/tests/test_context_processors.py +93 -0
  237. django_spire/core/tests/test_decorators.py +95 -0
  238. django_spire/core/tests/test_django_spire_utils.py +56 -0
  239. django_spire/core/tests/test_exceptions.py +37 -0
  240. django_spire/core/tests/test_models.py +54 -0
  241. django_spire/core/tests/test_settings.py +45 -0
  242. django_spire/core/tests/test_shortcuts.py +74 -0
  243. django_spire/core/tests/test_urls.py +16 -0
  244. django_spire/core/tests/test_utils.py +58 -0
  245. django_spire/core/urls.py +4 -1
  246. django_spire/core/utils.py +12 -8
  247. django_spire/exceptions.py +16 -1
  248. django_spire/file/admin.py +4 -2
  249. django_spire/file/apps.py +8 -10
  250. django_spire/file/fields.py +7 -7
  251. django_spire/file/forms.py +1 -1
  252. django_spire/file/interfaces.py +15 -15
  253. django_spire/file/mixins.py +1 -4
  254. django_spire/file/models.py +3 -5
  255. django_spire/file/tests/factories.py +59 -0
  256. django_spire/file/tests/test_admin.py +69 -0
  257. django_spire/file/tests/test_apps.py +24 -0
  258. django_spire/file/tests/test_fields.py +114 -0
  259. django_spire/file/tests/test_forms.py +20 -0
  260. django_spire/file/tests/test_interfaces.py +183 -0
  261. django_spire/file/tests/test_models.py +82 -0
  262. django_spire/file/tests/test_querysets.py +102 -0
  263. django_spire/file/tests/test_utils.py +32 -0
  264. django_spire/file/tests/test_views.py +145 -0
  265. django_spire/file/tests/test_widgets.py +82 -0
  266. django_spire/file/tools.py +8 -2
  267. django_spire/file/views.py +7 -3
  268. django_spire/file/widgets.py +12 -12
  269. django_spire/help_desk/admin.py +15 -0
  270. django_spire/help_desk/apps.py +2 -0
  271. django_spire/help_desk/auth/controller.py +2 -0
  272. django_spire/help_desk/choices.py +2 -0
  273. django_spire/help_desk/enums.py +2 -0
  274. django_spire/help_desk/exceptions.py +31 -3
  275. django_spire/help_desk/forms.py +2 -0
  276. django_spire/help_desk/models.py +2 -0
  277. django_spire/help_desk/querysets.py +4 -1
  278. django_spire/help_desk/services/notification_service.py +26 -27
  279. django_spire/help_desk/services/service.py +2 -3
  280. django_spire/help_desk/tests/factories.py +8 -3
  281. django_spire/help_desk/tests/test_admin.py +41 -0
  282. django_spire/help_desk/tests/test_apps.py +41 -0
  283. django_spire/help_desk/tests/test_choices.py +50 -0
  284. django_spire/help_desk/tests/test_controller.py +87 -0
  285. django_spire/help_desk/tests/test_enums.py +18 -0
  286. django_spire/help_desk/tests/test_exceptions.py +37 -0
  287. django_spire/help_desk/tests/test_forms.py +89 -0
  288. django_spire/help_desk/tests/test_models.py +59 -0
  289. django_spire/help_desk/tests/test_querysets.py +38 -0
  290. django_spire/help_desk/tests/test_services/test_notification_service.py +15 -8
  291. django_spire/help_desk/tests/test_services/test_service.py +92 -0
  292. django_spire/help_desk/tests/test_urls/test_form_urls.py +6 -6
  293. django_spire/help_desk/tests/test_urls/test_page_urls.py +8 -9
  294. django_spire/help_desk/tests/test_views/test_form_views.py +46 -19
  295. django_spire/help_desk/tests/test_views/test_page_views.py +32 -9
  296. django_spire/help_desk/urls/__init__.py +4 -1
  297. django_spire/help_desk/urls/form_urls.py +3 -0
  298. django_spire/help_desk/urls/page_urls.py +3 -0
  299. django_spire/help_desk/views/form_views.py +13 -5
  300. django_spire/help_desk/views/page_views.py +11 -3
  301. django_spire/history/activity/admin.py +2 -0
  302. django_spire/history/activity/apps.py +3 -1
  303. django_spire/history/activity/mixins.py +13 -7
  304. django_spire/history/activity/models.py +6 -5
  305. django_spire/history/activity/querysets.py +2 -0
  306. django_spire/history/activity/tests/__init__.py +0 -0
  307. django_spire/history/activity/tests/test_activity.py +176 -0
  308. django_spire/history/admin.py +9 -2
  309. django_spire/history/choices.py +3 -0
  310. django_spire/history/models.py +5 -5
  311. django_spire/history/tests/test_admin.py +93 -0
  312. django_spire/history/tests/test_history.py +101 -0
  313. django_spire/history/tests/test_mixins.py +84 -0
  314. django_spire/history/viewed/admin.py +3 -1
  315. django_spire/history/viewed/apps.py +3 -1
  316. django_spire/history/viewed/models.py +2 -0
  317. django_spire/history/viewed/tests/__init__.py +0 -0
  318. django_spire/history/viewed/tests/test_viewed.py +46 -0
  319. django_spire/knowledge/auth/tests/__init__.py +0 -0
  320. django_spire/knowledge/auth/tests/test_controller.py +116 -0
  321. django_spire/knowledge/collection/admin.py +5 -1
  322. django_spire/knowledge/collection/models.py +3 -1
  323. django_spire/knowledge/collection/seeding/seed.py +1 -0
  324. django_spire/knowledge/collection/services/factory_service.py +10 -11
  325. django_spire/knowledge/collection/services/ordering_service.py +1 -2
  326. django_spire/knowledge/collection/services/service.py +5 -10
  327. django_spire/knowledge/collection/services/tag_service.py +5 -2
  328. django_spire/knowledge/collection/tests/factories.py +28 -1
  329. django_spire/knowledge/collection/tests/test_models.py +48 -0
  330. django_spire/knowledge/collection/tests/test_querysets.py +93 -0
  331. django_spire/knowledge/collection/tests/test_services/test_factory_service.py +100 -0
  332. django_spire/knowledge/collection/tests/test_services/test_services.py +160 -0
  333. django_spire/knowledge/collection/tests/test_urls/test_form_urls.py +21 -3
  334. django_spire/knowledge/collection/tests/test_urls/test_json_urls.py +39 -1
  335. django_spire/knowledge/collection/tests/test_urls/test_page_urls.py +12 -4
  336. django_spire/knowledge/collection/urls/__init__.py +3 -0
  337. django_spire/knowledge/collection/urls/form_urls.py +2 -0
  338. django_spire/knowledge/collection/urls/json_urls.py +2 -0
  339. django_spire/knowledge/collection/urls/page_urls.py +2 -0
  340. django_spire/knowledge/collection/views/form_views.py +4 -4
  341. django_spire/knowledge/collection/views/json_views.py +5 -1
  342. django_spire/knowledge/collection/views/page_views.py +5 -2
  343. django_spire/knowledge/entry/admin.py +7 -1
  344. django_spire/knowledge/entry/forms.py +2 -0
  345. django_spire/knowledge/entry/models.py +2 -0
  346. django_spire/knowledge/entry/seeding/seed.py +3 -0
  347. django_spire/knowledge/entry/services/automation_service.py +5 -4
  348. django_spire/knowledge/entry/services/factory_service.py +7 -5
  349. django_spire/knowledge/entry/services/service.py +4 -7
  350. django_spire/knowledge/entry/services/tag_service.py +0 -1
  351. django_spire/knowledge/entry/services/tool_service.py +1 -0
  352. django_spire/knowledge/entry/services/transformation_services.py +1 -5
  353. django_spire/knowledge/entry/tests/factories.py +1 -2
  354. django_spire/knowledge/entry/tests/test_factory_service.py +20 -0
  355. django_spire/knowledge/entry/tests/test_models.py +41 -0
  356. django_spire/knowledge/entry/tests/test_querysets.py +71 -0
  357. django_spire/knowledge/entry/tests/test_services.py +94 -0
  358. django_spire/knowledge/entry/tests/test_urls/test_form_urls.py +9 -14
  359. django_spire/knowledge/entry/tests/test_urls/test_json_urls.py +48 -5
  360. django_spire/knowledge/entry/tests/test_urls/test_page_urls.py +6 -8
  361. django_spire/knowledge/entry/tests/test_urls/test_template_urls.py +40 -0
  362. django_spire/knowledge/entry/urls/form_urls.py +2 -0
  363. django_spire/knowledge/entry/urls/json_urls.py +2 -0
  364. django_spire/knowledge/entry/urls/page_urls.py +2 -0
  365. django_spire/knowledge/entry/urls/template_urls.py +2 -0
  366. django_spire/knowledge/entry/version/block/choices.py +2 -0
  367. django_spire/knowledge/entry/version/block/data/data.py +1 -0
  368. django_spire/knowledge/entry/version/block/data/list/data.py +8 -13
  369. django_spire/knowledge/entry/version/block/data/list/maps.py +3 -0
  370. django_spire/knowledge/entry/version/block/data/list/meta.py +1 -2
  371. django_spire/knowledge/entry/version/block/data/list/tests/__init__.py +0 -0
  372. django_spire/knowledge/entry/version/block/data/list/tests/test_maps.py +32 -0
  373. django_spire/knowledge/entry/version/block/data/list/tests/test_meta.py +58 -0
  374. django_spire/knowledge/entry/version/block/data/maps.py +3 -6
  375. django_spire/knowledge/entry/version/block/models.py +7 -5
  376. django_spire/knowledge/entry/version/block/seeding/constants.py +5 -4
  377. django_spire/knowledge/entry/version/block/services/service.py +2 -3
  378. django_spire/knowledge/entry/version/block/tests/factories.py +4 -10
  379. django_spire/knowledge/entry/version/block/tests/test_choices.py +56 -0
  380. django_spire/knowledge/entry/version/block/tests/test_data.py +90 -0
  381. django_spire/knowledge/entry/version/block/tests/test_maps.py +37 -0
  382. django_spire/knowledge/entry/version/block/tests/test_models.py +55 -0
  383. django_spire/knowledge/entry/version/block/tests/test_querysets.py +35 -0
  384. django_spire/knowledge/entry/version/block/tests/test_services.py +65 -0
  385. django_spire/knowledge/entry/version/choices.py +2 -0
  386. django_spire/knowledge/entry/version/converters/converter.py +1 -1
  387. django_spire/knowledge/entry/version/converters/docx_converter.py +4 -7
  388. django_spire/knowledge/entry/version/converters/markdown_converter.py +20 -20
  389. django_spire/knowledge/entry/version/maps.py +4 -5
  390. django_spire/knowledge/entry/version/querysets.py +1 -1
  391. django_spire/knowledge/entry/version/seeding/seeder.py +1 -2
  392. django_spire/knowledge/entry/version/services/processor_service.py +5 -4
  393. django_spire/knowledge/entry/version/services/service.py +1 -2
  394. django_spire/knowledge/entry/version/tests/factories.py +2 -2
  395. django_spire/knowledge/entry/version/tests/test_choices.py +18 -0
  396. django_spire/knowledge/entry/version/tests/test_converters/test_docx_converter.py +56 -8
  397. django_spire/knowledge/entry/version/tests/test_converters/test_markdown_converter.py +78 -0
  398. django_spire/knowledge/entry/version/tests/test_maps.py +58 -0
  399. django_spire/knowledge/entry/version/tests/test_models.py +23 -0
  400. django_spire/knowledge/entry/version/tests/test_querysets.py +26 -0
  401. django_spire/knowledge/entry/version/tests/test_services.py +62 -0
  402. django_spire/knowledge/entry/version/tests/test_urls/test_json_urls.py +27 -8
  403. django_spire/knowledge/entry/version/tests/test_urls/test_page_urls.py +15 -8
  404. django_spire/knowledge/entry/version/tests/test_urls/test_redirect_urls.py +38 -0
  405. django_spire/knowledge/entry/version/urls/__init__.py +3 -0
  406. django_spire/knowledge/entry/version/urls/json_urls.py +2 -1
  407. django_spire/knowledge/entry/version/urls/page_urls.py +2 -0
  408. django_spire/knowledge/entry/version/urls/redirect_urls.py +2 -0
  409. django_spire/knowledge/entry/version/views/json_views.py +5 -1
  410. django_spire/knowledge/entry/version/views/page_views.py +10 -3
  411. django_spire/knowledge/entry/version/views/redirect_views.py +5 -1
  412. django_spire/knowledge/entry/views/form_views.py +16 -8
  413. django_spire/knowledge/entry/views/json_views.py +3 -1
  414. django_spire/knowledge/entry/views/page_views.py +8 -2
  415. django_spire/knowledge/entry/views/template_views.py +7 -1
  416. django_spire/knowledge/exceptions.py +2 -1
  417. django_spire/knowledge/intelligence/intel/answer_intel.py +2 -1
  418. django_spire/knowledge/intelligence/intel/entry_intel.py +0 -1
  419. django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +4 -5
  420. django_spire/knowledge/models.py +1 -2
  421. django_spire/knowledge/tests/__init__.py +0 -0
  422. django_spire/knowledge/tests/test_templatetags.py +40 -0
  423. django_spire/knowledge/tests/test_urls/__init__.py +0 -0
  424. django_spire/knowledge/tests/test_urls/test_page_urls.py +24 -0
  425. django_spire/knowledge/urls/__init__.py +2 -0
  426. django_spire/knowledge/urls/page_urls.py +2 -0
  427. django_spire/knowledge/views/page_views.py +8 -3
  428. django_spire/notification/admin.py +3 -1
  429. django_spire/notification/app/admin.py +2 -0
  430. django_spire/notification/app/apps.py +3 -1
  431. django_spire/notification/app/exceptions.py +9 -2
  432. django_spire/notification/app/models.py +8 -4
  433. django_spire/notification/app/processor.py +22 -26
  434. django_spire/notification/app/querysets.py +2 -0
  435. django_spire/notification/app/tests/__init__.py +0 -0
  436. django_spire/notification/app/tests/factories.py +34 -0
  437. django_spire/notification/app/tests/test_apps.py +24 -0
  438. django_spire/notification/app/tests/test_models.py +72 -0
  439. django_spire/notification/app/tests/test_processor.py +111 -0
  440. django_spire/notification/app/tests/test_querysets.py +90 -0
  441. django_spire/notification/app/tests/test_views/__init__.py +0 -0
  442. django_spire/notification/app/tests/test_views/test_json_views.py +48 -0
  443. django_spire/notification/app/tests/test_views/test_page_views.py +19 -0
  444. django_spire/notification/app/urls/__init__.py +3 -1
  445. django_spire/notification/app/urls/json_urls.py +6 -4
  446. django_spire/notification/app/urls/page_urls.py +4 -3
  447. django_spire/notification/app/urls/template_urls.py +4 -2
  448. django_spire/notification/apps.py +4 -1
  449. django_spire/notification/email/admin.py +5 -1
  450. django_spire/notification/email/apps.py +3 -1
  451. django_spire/notification/email/exceptions.py +4 -2
  452. django_spire/notification/email/helper.py +5 -3
  453. django_spire/notification/email/models.py +4 -0
  454. django_spire/notification/email/processor.py +19 -15
  455. django_spire/notification/email/querysets.py +3 -0
  456. django_spire/notification/email/tests/__init__.py +0 -0
  457. django_spire/notification/email/tests/factories.py +35 -0
  458. django_spire/notification/email/tests/test_apps.py +24 -0
  459. django_spire/notification/email/tests/test_models.py +52 -0
  460. django_spire/notification/email/tests/test_processor.py +92 -0
  461. django_spire/notification/email/tests/test_querysets.py +43 -0
  462. django_spire/notification/exceptions.py +17 -2
  463. django_spire/notification/managers.py +7 -1
  464. django_spire/notification/maps.py +4 -1
  465. django_spire/notification/mixins.py +2 -0
  466. django_spire/notification/models.py +3 -1
  467. django_spire/notification/processors/notification.py +12 -5
  468. django_spire/notification/processors/processor.py +2 -0
  469. django_spire/notification/processors/tests/__init__.py +0 -0
  470. django_spire/notification/processors/tests/test_notification.py +106 -0
  471. django_spire/notification/push/admin.py +10 -1
  472. django_spire/notification/push/apps.py +3 -1
  473. django_spire/notification/push/models.py +2 -3
  474. django_spire/notification/push/tests/__init__.py +0 -0
  475. django_spire/notification/push/tests/test_apps.py +24 -0
  476. django_spire/notification/push/tests/test_models.py +28 -0
  477. django_spire/notification/querysets.py +7 -1
  478. django_spire/notification/sms/admin.py +2 -0
  479. django_spire/notification/sms/apps.py +4 -1
  480. django_spire/notification/sms/automations.py +2 -0
  481. django_spire/notification/sms/choices.py +2 -0
  482. django_spire/notification/sms/exceptions.py +19 -5
  483. django_spire/notification/sms/helper.py +33 -23
  484. django_spire/notification/sms/models.py +5 -1
  485. django_spire/notification/sms/processor.py +20 -20
  486. django_spire/notification/sms/querysets.py +2 -0
  487. django_spire/notification/sms/tests/factories.py +33 -0
  488. django_spire/notification/sms/tests/test_apps.py +24 -0
  489. django_spire/notification/sms/tests/test_automation.py +38 -0
  490. django_spire/notification/sms/tests/test_choices.py +15 -0
  491. django_spire/notification/sms/tests/test_consts.py +17 -0
  492. django_spire/notification/sms/tests/test_exceptions.py +27 -0
  493. django_spire/notification/sms/tests/test_helper.py +50 -0
  494. django_spire/notification/sms/tests/test_models.py +81 -0
  495. django_spire/notification/sms/tests/test_processor.py +107 -0
  496. django_spire/notification/sms/tests/test_tools.py +25 -11
  497. django_spire/notification/sms/tools.py +16 -5
  498. django_spire/notification/sms/urls/__init__.py +3 -1
  499. django_spire/notification/sms/urls/media_urls.py +2 -0
  500. django_spire/notification/sms/views/media_views.py +14 -4
  501. django_spire/notification/tests/__init__.py +0 -0
  502. django_spire/notification/tests/factories.py +26 -0
  503. django_spire/notification/tests/test_admin.py +55 -0
  504. django_spire/notification/tests/test_apps.py +30 -0
  505. django_spire/notification/tests/test_automation.py +18 -0
  506. django_spire/notification/tests/test_choices.py +59 -0
  507. django_spire/notification/tests/test_exceptions.py +58 -0
  508. django_spire/notification/tests/test_managers.py +100 -0
  509. django_spire/notification/tests/test_maps.py +31 -0
  510. django_spire/notification/tests/test_models.py +76 -0
  511. django_spire/notification/tests/test_querysets.py +184 -0
  512. django_spire/notification/tests/test_utils.py +23 -0
  513. django_spire/notification/urls.py +3 -1
  514. django_spire/notification/utils.py +3 -1
  515. django_spire/settings.py +3 -0
  516. django_spire/theme/tests/test_context_processor.py +15 -13
  517. django_spire/theme/tests/test_enums.py +2 -2
  518. django_spire/theme/tests/test_filesystem.py +2 -5
  519. django_spire/theme/tests/test_integration.py +12 -12
  520. django_spire/theme/tests/test_model.py +40 -38
  521. django_spire/theme/tests/test_views/test_json_views.py +33 -33
  522. django_spire/theme/urls/json_urls.py +3 -0
  523. django_spire/theme/urls/page_urls.py +3 -0
  524. django_spire/urls.py +19 -15
  525. django_spire/utils.py +13 -4
  526. {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/METADATA +1 -1
  527. {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/RECORD +530 -358
  528. django_spire/contrib/options/tests/test_unit.py +0 -148
  529. django_spire/contrib/seeding/tests/test_seeding.py +0 -25
  530. django_spire/core/tests/test_templatetags.py +0 -117
  531. django_spire/core/tests/tests_shortcuts.py +0 -73
  532. django_spire/history/activity/tests.py +0 -3
  533. django_spire/history/activity/views.py +0 -3
  534. django_spire/knowledge/collection/tests/test_services/test_transformation_service.py +0 -71
  535. django_spire/notification/app/tests.py +0 -3
  536. {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/WHEEL +0 -0
  537. {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/licenses/LICENSE.md +0 -0
  538. {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/top_level.txt +0 -0
@@ -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)
@@ -1,7 +1,9 @@
1
+ from __future__ import annotations
2
+
1
3
  from pydantic import constr, condecimal, create_model
2
4
  from pydantic.fields import Field
3
5
 
4
- from typing import Tuple, Type, Any, Optional
6
+ from typing import Any
5
7
 
6
8
  from django.db import models
7
9
  from django.core import validators
@@ -11,13 +13,14 @@ from django_spire.core.maps import MODEL_FIELD_TYPE_TO_TYPE_MAP
11
13
 
12
14
 
13
15
  def django_to_pydantic_model(
14
- model_class: Type[models.Model],
15
- base_class: Type | None = None,
16
- include_fields: str | list| tuple | None = None,
17
- exclude_fields: str | list | tuple | None = None
16
+ model_class: type[models.Model],
17
+ base_class: type | None = None,
18
+ include_fields: str | list| tuple | None = None,
19
+ exclude_fields: str | list | tuple | None = None
18
20
  ):
19
21
  if not issubclass(model_class, models.Model):
20
- raise ValueError("model_class must be a subclass of django.db.models.Model")
22
+ message = 'model_class must be a subclass of django.db.models.Model'
23
+ raise TypeError(message)
21
24
 
22
25
  if include_fields is None:
23
26
  include_fields = []
@@ -47,7 +50,6 @@ def django_to_pydantic_model(
47
50
 
48
51
 
49
52
  class DjangoToPydanticFieldConverter:
50
-
51
53
  def __init__(self, model_field: models.Field):
52
54
  self.model_field = model_field
53
55
  self.kwargs = {}
@@ -60,8 +62,8 @@ class DjangoToPydanticFieldConverter:
60
62
  def bool_to_json_schema(value: bool):
61
63
  if value:
62
64
  return "true"
63
- else:
64
- return "false"
65
+
66
+ return "false"
65
67
 
66
68
  @property
67
69
  def field_handlers(self):
@@ -80,22 +82,20 @@ class DjangoToPydanticFieldConverter:
80
82
  str
81
83
  )
82
84
 
83
- def _build_char_field(self) -> Type:
85
+ def _build_char_field(self) -> type:
84
86
  if self.model_field.max_length:
85
87
  return constr(max_length=self.model_field.max_length)
86
88
  return str
87
89
 
88
- def _build_date_field(self) -> Type:
89
- self.kwargs['example'] = '2022-01-01'
90
+ def _build_date_field(self) -> type:
90
91
  self.kwargs['json_schema_extra']['example'] = '2022-01-01'
91
92
  return self._base_type()
92
93
 
93
- def _build_date_time_field(self) -> Type:
94
- self.kwargs['example'] = '2022-01-01 13:37:00'
94
+ def _build_date_time_field(self) -> type:
95
95
  self.kwargs['json_schema_extra']['example'] = '2022-01-01 13:37:00'
96
96
  return self._base_type()
97
97
 
98
- def _build_integer_field(self) -> Type:
98
+ def _build_integer_field(self) -> type:
99
99
  for validator in self.model_field.validators:
100
100
  if isinstance(validator, validators.MinValueValidator):
101
101
  self.kwargs['json_schema_extra']['greater_than'] = validator.limit_value
@@ -104,7 +104,7 @@ class DjangoToPydanticFieldConverter:
104
104
 
105
105
  return self._base_type()
106
106
 
107
- def _build_decimal_field(self) -> Type:
107
+ def _build_decimal_field(self) -> type:
108
108
  self.kwargs['json_schema_extra']['example'] = '0.00'
109
109
  self.kwargs['json_schema_extra']['max_digits'] = self.model_field.max_digits
110
110
  self.kwargs['json_schema_extra']['decimal_places'] = self.model_field.decimal_places
@@ -118,21 +118,19 @@ class DjangoToPydanticFieldConverter:
118
118
  enum_name = f"{self.model_field.name.capitalize()}Enum"
119
119
  return django_choices_to_enums(enum_name, self.model_field.choices)
120
120
 
121
- def build_field(self) -> Tuple[Type, Any]:
121
+ def build_field(self) -> tuple[type, Any]:
122
122
  """Build and return the Pydantic field type and Field object."""
123
123
  return self.field_type, Field(**self.kwargs)
124
124
 
125
125
  def _build_metadata(self):
126
-
127
126
  # Cannot set a default when providing option to the LLM
127
+
128
128
  # if self.model_field.default is not models.NOT_PROVIDED:
129
129
  # if callable(self.model_field.default):
130
130
  # self.kwargs['default'] = self.model_field.default()
131
131
  # else:
132
132
  # self.kwargs['default'] = self.model_field.default
133
133
 
134
- self.kwargs['required'] = not self.model_field.null
135
-
136
134
  if self.model_field.null and self.model_field.default is models.NOT_PROVIDED:
137
135
  self.kwargs['default'] = None
138
136
 
@@ -140,6 +138,7 @@ class DjangoToPydanticFieldConverter:
140
138
  self.kwargs['description'] = str(self.model_field.help_text)
141
139
 
142
140
  self.kwargs['json_schema_extra'] = {
141
+ 'required': self.bool_to_json_schema(not self.model_field.null),
143
142
  'is_unique': self.bool_to_json_schema(self.model_field.unique),
144
143
  # 'is_required': self.bool_to_json_schema(not self.model_field.null),
145
144
  'field_name': self.model_field.name
@@ -151,7 +150,7 @@ class DjangoToPydanticFieldConverter:
151
150
  f"{value} ({label})" for value, label in self.model_field.choices
152
151
  ) + "."
153
152
 
154
- def _get_pydantic_type(self) -> Type:
153
+ def _get_pydantic_type(self) -> type:
155
154
  if self.model_field.choices:
156
155
  return self._build_enum_type()
157
156
 
@@ -164,6 +163,7 @@ class DjangoToPydanticFieldConverter:
164
163
 
165
164
  def _wrap_nullable(self):
166
165
  pass
166
+
167
167
  # This works better for the LLM
168
168
  # if self.model_field.null:
169
- # self.field_type = Optional[self.field_type]
169
+ # self.field_type = Optional[self.field_type]
@@ -1,25 +1,38 @@
1
- from django.db import connections
1
+ from __future__ import annotations
2
+
2
3
  from functools import wraps
3
4
 
4
- from django.http import JsonResponse
5
+ from typing import Callable, ParamSpec, TypeVar
6
+
7
+ from django.db import connections
8
+ from django.http import HttpRequest, HttpResponse, JsonResponse
9
+
5
10
 
11
+ P = ParamSpec('P')
12
+ T = TypeVar('T')
6
13
 
7
- def close_db_connections(func):
14
+
15
+ def close_db_connections(func: Callable[P, T]) -> Callable[P, T]:
8
16
  @wraps(func)
9
- def inner(*args, **kwargs):
17
+ def inner(*args: P.args, **kwargs: P.kwargs) -> T:
10
18
  try:
11
19
  return func(*args, **kwargs)
12
20
  finally:
13
21
  connections.close_all()
22
+
14
23
  return inner
15
24
 
16
25
 
17
- def valid_ajax_request_required(method):
26
+ def valid_ajax_request_required(
27
+ method: Callable[..., HttpResponse]
28
+ ) -> Callable[..., HttpResponse]:
18
29
  @wraps(method)
19
- def wrapper(request, *args, **kwargs):
30
+ def wrapper(request: HttpRequest, *args, **kwargs) -> HttpResponse:
20
31
  if request.method != 'POST' and request.content_type != 'application/json':
21
32
  return JsonResponse(
22
33
  {'type': 'error', 'message': 'Invalid Request'}
23
34
  )
35
+
24
36
  return method(request, *args, **kwargs)
37
+
25
38
  return wrapper