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
@@ -0,0 +1,137 @@
1
+ from __future__ import annotations
2
+
3
+ from unittest.mock import MagicMock
4
+
5
+ from django.contrib.sessions.middleware import SessionMiddleware
6
+ from django.db.models import QuerySet
7
+ from django.test import RequestFactory, TestCase
8
+
9
+ from django_spire.contrib.queryset.enums import SessionFilterActionEnum
10
+ from django_spire.contrib.queryset.filter_tools import filter_by_lookup_map
11
+ from django_spire.contrib.queryset.mixins import SearchQuerySetMixin, SessionFilterQuerySetMixin
12
+
13
+
14
+ class TestSessionFilterActionEnum(TestCase):
15
+ def test_clear_value(self) -> None:
16
+ assert SessionFilterActionEnum.CLEAR == 'Clear'
17
+
18
+ def test_filter_value(self) -> None:
19
+ assert SessionFilterActionEnum.FILTER == 'Filter'
20
+
21
+ def test_is_str_enum(self) -> None:
22
+ assert isinstance(SessionFilterActionEnum.CLEAR, str)
23
+ assert isinstance(SessionFilterActionEnum.FILTER, str)
24
+
25
+
26
+ class TestFilterByLookupMap(TestCase):
27
+ def setUp(self) -> None:
28
+ self.queryset = MagicMock(spec=QuerySet)
29
+ self.queryset.filter.return_value = self.queryset
30
+
31
+ def test_applies_filter_from_lookup_map(self) -> None:
32
+ lookup_map = {'name': 'name__icontains'}
33
+ data = {'name': 'test'}
34
+
35
+ filter_by_lookup_map(self.queryset, lookup_map, data)
36
+
37
+ self.queryset.filter.assert_called_once_with(name__icontains='test')
38
+
39
+ def test_extra_filters_applied(self) -> None:
40
+ lookup_map = {'name': 'name__icontains'}
41
+ data = {'name': 'test'}
42
+ extra_filters = {'is_active': True}
43
+
44
+ filter_by_lookup_map(self.queryset, lookup_map, data, extra_filters)
45
+
46
+ self.queryset.filter.assert_called_once_with(
47
+ name__icontains='test',
48
+ is_active=True
49
+ )
50
+
51
+ def test_extra_filters_defaults_to_empty_dict(self) -> None:
52
+ lookup_map = {}
53
+ data = {}
54
+
55
+ filter_by_lookup_map(self.queryset, lookup_map, data)
56
+
57
+ self.queryset.filter.assert_called_once_with()
58
+
59
+ def test_ignores_empty_string_values(self) -> None:
60
+ lookup_map = {'name': 'name__icontains', 'email': 'email__icontains'}
61
+ data = {'name': 'test', 'email': ''}
62
+
63
+ filter_by_lookup_map(self.queryset, lookup_map, data)
64
+
65
+ self.queryset.filter.assert_called_once_with(name__icontains='test')
66
+
67
+ def test_ignores_keys_not_in_lookup_map(self) -> None:
68
+ lookup_map = {'name': 'name__icontains'}
69
+ data = {'name': 'test', 'other': 'value'}
70
+
71
+ filter_by_lookup_map(self.queryset, lookup_map, data)
72
+
73
+ self.queryset.filter.assert_called_once_with(name__icontains='test')
74
+
75
+ def test_ignores_none_values(self) -> None:
76
+ lookup_map = {'name': 'name__icontains', 'email': 'email__icontains'}
77
+ data = {'name': 'test', 'email': None}
78
+
79
+ filter_by_lookup_map(self.queryset, lookup_map, data)
80
+
81
+ self.queryset.filter.assert_called_once_with(name__icontains='test')
82
+
83
+ def test_ignores_empty_list_values(self) -> None:
84
+ lookup_map = {'name': 'name__icontains', 'tags': 'tags__in'}
85
+ data = {'name': 'test', 'tags': []}
86
+
87
+ filter_by_lookup_map(self.queryset, lookup_map, data)
88
+
89
+ self.queryset.filter.assert_called_once_with(name__icontains='test')
90
+
91
+ def test_multiple_filters_applied(self) -> None:
92
+ lookup_map = {'name': 'name__icontains', 'status': 'status'}
93
+ data = {'name': 'test', 'status': 'active'}
94
+
95
+ filter_by_lookup_map(self.queryset, lookup_map, data)
96
+
97
+ self.queryset.filter.assert_called_once_with(
98
+ name__icontains='test',
99
+ status='active'
100
+ )
101
+
102
+ def test_returns_filtered_queryset(self) -> None:
103
+ lookup_map = {'name': 'name__icontains'}
104
+ data = {'name': 'test'}
105
+
106
+ result = filter_by_lookup_map(self.queryset, lookup_map, data)
107
+
108
+ assert result == self.queryset
109
+
110
+
111
+ class TestSessionFilterQuerySetMixin(TestCase):
112
+ def setUp(self) -> None:
113
+ self.factory = RequestFactory()
114
+
115
+ def _create_request_with_session(self, path: str = '/', data: dict | None = None):
116
+ request = self.factory.get(path, data or {})
117
+ middleware = SessionMiddleware(lambda req: None)
118
+ middleware.process_request(request)
119
+ request.session.save()
120
+ return request
121
+
122
+ def test_has_bulk_filter_abstract_method(self) -> None:
123
+ assert hasattr(SessionFilterQuerySetMixin, 'bulk_filter')
124
+
125
+ def test_has_process_session_filter_method(self) -> None:
126
+ assert hasattr(SessionFilterQuerySetMixin, 'process_session_filter')
127
+
128
+ def test_is_queryset_subclass(self) -> None:
129
+ assert issubclass(SessionFilterQuerySetMixin, QuerySet)
130
+
131
+
132
+ class TestSearchQuerySetMixin(TestCase):
133
+ def test_has_search_abstract_method(self) -> None:
134
+ assert hasattr(SearchQuerySetMixin, 'search')
135
+
136
+ def test_is_queryset_subclass(self) -> None:
137
+ assert issubclass(SearchQuerySetMixin, QuerySet)
@@ -1,24 +1,30 @@
1
+ from __future__ import annotations
2
+
1
3
  from abc import ABC, abstractmethod
4
+ from typing import TYPE_CHECKING
2
5
 
3
6
  from django_spire.contrib.seeding.field.cleaners import normalize_seeder_fields
4
7
  from django_spire.contrib.seeding.field.enums import FieldSeederTypesEnum
5
8
 
9
+ if TYPE_CHECKING:
10
+ from typing import Any
11
+
6
12
 
7
13
  class BaseFieldSeeder(ABC):
8
14
  keyword: str = None
9
15
  seed_keywords = FieldSeederTypesEnum._value2member_map_.keys()
10
16
 
11
17
  def __init__(
12
- self,
13
- fields: dict = None,
14
- default_to: str = "llm"
15
- ):
18
+ self,
19
+ fields: dict | None = None,
20
+ default_to: str = 'llm'
21
+ ) -> None:
16
22
 
17
23
  self.fields = self._normalize_fields(fields or {})
18
24
  self.default_to = default_to
19
25
 
20
26
 
21
- def __init_subclass__(cls, **kwargs):
27
+ def __init_subclass__(cls, **kwargs: Any) -> None:
22
28
  super().__init_subclass__(**kwargs)
23
29
 
24
30
  if cls.keyword is None:
@@ -35,9 +41,9 @@ class BaseFieldSeeder(ABC):
35
41
  }
36
42
 
37
43
  @property
38
- def seeder_fields(self):
44
+ def seeder_fields(self) -> dict:
39
45
  return self.filter_fields(self.keyword)
40
46
 
41
47
  @abstractmethod
42
- def seed(self, model_seeder_cls, count: int):
48
+ def seed(self, model_seeder_cls: Any, count: int) -> list[dict]:
43
49
  pass
@@ -1,11 +1,18 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
1
5
  from django_spire.contrib.seeding.field.base import BaseFieldSeeder
2
6
  from django_spire.contrib.seeding.field.enums import FieldSeederTypesEnum
3
7
 
8
+ if TYPE_CHECKING:
9
+ from typing import Any
10
+
4
11
 
5
12
  class CallableFieldSeeder(BaseFieldSeeder):
6
13
  keyword = FieldSeederTypesEnum.CALLABLE
7
14
 
8
- def seed(self, manager = None, count = 1) -> list[dict]:
15
+ def seed(self, manager: Any = None, count: int = 1) -> list[dict]:
9
16
  return [
10
17
  {field_name: func[1]() for field_name, func in self.seeder_fields.items()}
11
18
  for _ in range(count)
@@ -1,7 +1,10 @@
1
- from django_spire.contrib.seeding.field.enums import FieldSeederTypesEnum
1
+ from __future__ import annotations
2
2
 
3
3
  import copy
4
4
 
5
+ from django_spire.contrib.seeding.field.enums import FieldSeederTypesEnum
6
+
7
+
5
8
  def normalize_seeder_fields(fields: dict) -> dict:
6
9
  normalized = {}
7
10
 
@@ -21,14 +24,11 @@ def normalize_seeder_fields(fields: dict) -> dict:
21
24
  else:
22
25
  extra = (extra_value,)
23
26
 
24
- normalized[k] = (v[0],) + v[1:2] + extra
25
-
27
+ normalized[k] = (v[0], *v[1:2], *extra)
26
28
  elif callable(v):
27
29
  normalized[k] = ("callable", v)
28
-
29
30
  elif isinstance(v, str) and v.lower() in FieldSeederTypesEnum._value2member_map_:
30
31
  normalized[k] = (v.lower(),)
31
-
32
32
  else:
33
33
  normalized[k] = ("static", v)
34
34
 
@@ -1,36 +1,46 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
1
5
  from django.utils import timezone
2
6
  from faker import Faker
3
7
 
4
8
  from django_spire.contrib.seeding.field.base import BaseFieldSeeder
5
9
  from django_spire.contrib.seeding.field.enums import FieldSeederTypesEnum
6
10
 
11
+ if TYPE_CHECKING:
12
+ from datetime import datetime
13
+ from typing import Any
14
+
15
+ from django.db.models import Model
16
+
7
17
 
8
18
  class CustomFieldSeeder(BaseFieldSeeder):
9
19
  keyword = FieldSeederTypesEnum.CUSTOM
10
20
 
11
- def in_order(self, values: list, index: int) -> any:
21
+ def in_order(self, values: list, index: int) -> Any:
12
22
  if not values:
13
- raise ValueError(
14
- "Cannot select from empty values list. "
15
- "Make sure the related model has existing records before seeding foreign keys."
16
- )
17
- index = index % len(values) # Index loops back on itself
23
+ message = 'Cannot select from empty values list. Make sure the related model has existing records before seeding foreign keys.'
24
+ raise ValueError(message)
25
+
26
+ # Index loops back on itself
27
+ index = index % len(values)
18
28
  return values[index]
19
29
 
20
- def date_time_between(self, start_date: str, end_date: str):
30
+ def date_time_between(self, start_date: str, end_date: str) -> datetime:
21
31
  faker = Faker()
22
32
  naive_dt = faker.date_time_between(start_date=start_date, end_date=end_date)
23
33
  return timezone.make_aware(naive_dt)
24
34
 
25
- def fk_random(self, model_class, ids: list[int]):
35
+ def fk_random(self, model_class: type[Model], ids: list[int]) -> int:
26
36
  faker = Faker()
27
37
  return faker.random_element(elements=ids)
28
38
 
29
- def fk_in_order(self, model_class, index: int, ids: list[int]):
39
+ def fk_in_order(self, model_class: type[Model], index: int, ids: list[int]) -> int:
30
40
  """Takes a queryset, calls it then returns a random id"""
31
41
  return self.in_order(values=ids, index=index)
32
42
 
33
- def seed(self, manager, count) -> list[dict]:
43
+ def seed(self, manager: Any, count: int) -> list[dict]:
34
44
  data = []
35
45
 
36
46
  foreign_keys = {}
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from typing import TYPE_CHECKING
4
+
3
5
  from dandy import BaseIntel, Prompt
4
6
 
5
7
  from django_spire.core.converters import django_to_pydantic_model, fake_model_field_value
@@ -7,11 +9,14 @@ from django_spire.contrib.seeding.field.base import BaseFieldSeeder
7
9
  from django_spire.contrib.seeding.field.enums import FieldSeederTypesEnum
8
10
  from django_spire.contrib.seeding.intelligence.bots.field_seeding_bots import LlmFieldSeedingBot
9
11
 
12
+ if TYPE_CHECKING:
13
+ from typing import Any
14
+
10
15
 
11
16
  class DjangoFieldLlmSeeder(BaseFieldSeeder):
12
17
  keyword = FieldSeederTypesEnum.LLM
13
18
 
14
- def seed(self, model_seeder, count: int = 1,) -> list[dict]:
19
+ def seed(self, model_seeder: Any, count: int = 1) -> list[dict]:
15
20
 
16
21
  include_fields = list(self.seeder_fields.keys())
17
22
 
@@ -31,10 +36,7 @@ class DjangoFieldLlmSeeder(BaseFieldSeeder):
31
36
  base_prompt = (
32
37
  Prompt()
33
38
  .heading('General Seeding Rules')
34
- .list(
35
- [
36
- 'Create data for each field provided.'
37
- ])
39
+ .list(['Create data for each field provided.'])
38
40
  .heading('Field Rules & Context')
39
41
  .prompt(self.field_prompt)
40
42
  )
@@ -115,7 +117,7 @@ class DjangoFieldLlmSeeder(BaseFieldSeeder):
115
117
  class DjangoFieldFakerSeeder(BaseFieldSeeder):
116
118
  keyword = FieldSeederTypesEnum.FAKER
117
119
 
118
- def seed(self, model_seeder, count = 1) -> list[dict]:
120
+ def seed(self, model_seeder: Any, count: int = 1) -> list[dict]:
119
121
  data = []
120
122
 
121
123
  for _ in range(count):
@@ -1,9 +1,11 @@
1
+ from __future__ import annotations
2
+
1
3
  from enum import Enum
2
4
 
3
5
 
4
6
  class FieldSeederTypesEnum(str, Enum):
5
- LLM = "llm"
6
- FAKER = "faker"
7
- STATIC = "static"
8
- CALLABLE = "callable"
9
- CUSTOM = "custom"
7
+ LLM = 'llm'
8
+ FAKER = 'faker'
9
+ STATIC = 'static'
10
+ CALLABLE = 'callable'
11
+ CUSTOM = 'custom'
@@ -1,18 +1,28 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from typing import Any
7
+
8
+
1
9
  class FieldOverride:
2
- def __init__(self, seeder_class):
10
+ def __init__(self, seeder_class: Any) -> None:
3
11
  self.seeder_class = seeder_class
4
12
  self.overrides = {}
5
13
 
6
- def __getattr__(self, name):
14
+ def __getattr__(self, name: str) -> Any:
7
15
  """
8
16
  Delegate attribute lookup to self.seeder_class if the attribute isn't found
9
17
  in this FieldOverride instance.
10
18
  """
11
19
 
12
20
  attr = getattr(self.seeder_class, name)
21
+
13
22
  if callable(attr):
14
- def wrapper(*args, **kwargs):
23
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
15
24
  # TODO: Error here if fields is passed as an arg.
25
+
16
26
  # Pass the overrides field to the seed method
17
27
  if 'fields' in kwargs and isinstance(kwargs['fields'], dict):
18
28
  kwargs['fields'] = {**kwargs['fields'], **self.overrides}
@@ -22,12 +32,12 @@ class FieldOverride:
22
32
  return attr(*args, **kwargs)
23
33
 
24
34
  return wrapper
35
+
25
36
  return attr
26
37
 
27
- def filter(self, **kwargs):
38
+ def filter(self, **kwargs: Any) -> FieldOverride:
28
39
  self.overrides.update(kwargs)
29
40
  return self
30
41
 
31
- def seed(self, count=1):
42
+ def seed(self, count: int = 1) -> list:
32
43
  return self.seeder_class.seed(count=count, fields=self.overrides)
33
-
@@ -1,10 +1,17 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
1
5
  from django_spire.contrib.seeding.field.base import BaseFieldSeeder
2
6
 
7
+ if TYPE_CHECKING:
8
+ from typing import Any
9
+
3
10
 
4
11
  class StaticFieldSeeder(BaseFieldSeeder):
5
- keyword = "static"
12
+ keyword = 'static'
6
13
 
7
- def seed(self, manager = None, count = 1) -> list:
14
+ def seed(self, manager: Any = None, count: int = 1) -> list[dict]:
8
15
  return [
9
16
  {field_name: value[1] for field_name, value in self.seeder_fields.items()}
10
17
  for _ in range(count)
@@ -1,24 +1,28 @@
1
- import unittest
1
+ from __future__ import annotations
2
+
3
+ from django.test import TestCase
4
+
2
5
  from django_spire.contrib.seeding.field.base import BaseFieldSeeder
3
6
 
4
7
 
5
8
  class DummyFieldSeeder(BaseFieldSeeder):
6
- keyword = "faker"
7
-
8
- def seed(self, model_seeder_cls=None, count: int = 1):
9
- return [{"fake": True} for _ in range(count)]
9
+ keyword = 'faker'
10
10
 
11
+ def seed(self, model_seeder_cls=None, count: int = 1) -> list[dict]:
12
+ return [{'fake': True} for _ in range(count)]
11
13
 
12
- class TestBaseFieldSeeder(unittest.TestCase):
13
14
 
14
- def test_filter_fields(self):
15
+ class TestBaseFieldSeeder(TestCase):
16
+ def test_filter_fields(self) -> None:
15
17
  fields = {
16
- "name": ("faker", "name"),
17
- "email": ("faker", "email"),
18
- "description": ("llm", "prompt")
18
+ 'name': ('faker', 'name'),
19
+ 'email': ('faker', 'email'),
20
+ 'description': ('llm', 'prompt')
19
21
  }
20
22
  seeder = DummyFieldSeeder(fields=fields)
21
- faker_fields = seeder.filter_fields("faker")
22
- self.assertIn("name", faker_fields)
23
- self.assertIn("email", faker_fields)
24
- self.assertNotIn("description", faker_fields)
23
+
24
+ faker_fields = seeder.filter_fields('faker')
25
+
26
+ assert 'name' in faker_fields
27
+ assert 'email' in faker_fields
28
+ assert 'description' not in faker_fields
@@ -1,18 +1,22 @@
1
- import unittest
2
- from django_spire.contrib.seeding.field.callable import CallableFieldSeeder
1
+ from __future__ import annotations
2
+
3
+ from django.test import TestCase
3
4
 
5
+ from django_spire.contrib.seeding.field.callable import CallableFieldSeeder
4
6
 
5
- class TestCallableFieldSeeder(unittest.TestCase):
6
7
 
7
- def test_seeds_callable_fields(self):
8
+ class TestCallableFieldSeeder(TestCase):
9
+ def test_seeds_callable_fields(self) -> None:
8
10
  fields = {
9
- "timestamp": lambda: "2024-01-01 12:00:00",
10
- "status": lambda: "active"
11
+ 'timestamp': lambda: '2024-01-01 12:00:00',
12
+ 'status': lambda: 'active'
11
13
  }
12
14
  seeder = CallableFieldSeeder(fields=fields)
15
+
13
16
  result = seeder.seed(None, count=3)
14
17
 
15
- self.assertEqual(len(result), 3)
18
+ assert len(result) == 3
19
+
16
20
  for row in result:
17
- self.assertEqual(row["timestamp"], "2024-01-01 12:00:00")
18
- self.assertEqual(row["status"], "active")
21
+ assert row['timestamp'] == '2024-01-01 12:00:00'
22
+ assert row['status'] == 'active'
@@ -1,61 +1,74 @@
1
- import unittest
1
+ from __future__ import annotations
2
+
2
3
  from copy import copy
4
+
5
+ from django.test import TestCase
6
+
3
7
  from django_spire.contrib.seeding.field.cleaners import normalize_seeder_fields
4
8
 
5
9
 
6
10
  FIELDS = {
7
- "faker_tuple": ("faker", "name"),
8
- "llm_type": "llm",
9
- "faker_type": "faker",
10
- "static_type": "static",
11
- "callable_type": "callable",
12
- "custom_type": "custom",
13
- "static_bool": True,
14
- "static_str": "approved",
15
- "static_int": 10,
16
- "callable_func": lambda: "now",
17
- "exclude_str": "exclude",
18
- "exclude_tuple": ("exclude",)
11
+ 'faker_tuple': ('faker', 'name'),
12
+ 'llm_type': 'llm',
13
+ 'faker_type': 'faker',
14
+ 'static_type': 'static',
15
+ 'callable_type': 'callable',
16
+ 'custom_type': 'custom',
17
+ 'static_bool': True,
18
+ 'static_str': 'approved',
19
+ 'static_int': 10,
20
+ 'callable_func': lambda: 'now',
21
+ 'exclude_str': 'exclude',
22
+ 'exclude_tuple': ('exclude',)
19
23
  }
20
24
 
21
25
 
22
- class TestNormalizeSeederFields(unittest.TestCase):
26
+ class TestNormalizeSeederFields(TestCase):
27
+ def test_excludes_fields(self) -> None:
28
+ fields = copy(FIELDS)
23
29
 
24
- def test_normalizes_faker_tuple(self):
25
- fields = {"faker_tuple": FIELDS["faker_tuple"]}
26
30
  normalized = normalize_seeder_fields(fields)
27
- self.assertEqual(normalized["faker_tuple"], ("faker", "name"))
28
31
 
29
- def test_excludes_fields(self):
30
- fields = copy(FIELDS)
32
+ assert 'exclude_str' not in normalized
33
+ assert 'exclude_tuple' not in normalized
34
+ assert 'static_str' in normalized
35
+
36
+ def test_normalizes_callable(self) -> None:
37
+ fields = {'callable_func': FIELDS['callable_func']}
38
+
39
+ normalized = normalize_seeder_fields(fields)
40
+
41
+ assert normalized['callable_func'][0] == 'callable'
42
+ assert callable(normalized['callable_func'][1])
43
+
44
+ def test_normalizes_faker_tuple(self) -> None:
45
+ fields = {'faker_tuple': FIELDS['faker_tuple']}
46
+
31
47
  normalized = normalize_seeder_fields(fields)
32
- self.assertNotIn("exclude_str", normalized)
33
- self.assertNotIn("exclude_tuple", normalized)
34
- self.assertIn("static_str", normalized)
35
48
 
36
- def test_normalizes_single_value_strings_to_tuples(self):
49
+ assert normalized['faker_tuple'] == ('faker', 'name')
50
+
51
+ def test_normalizes_single_value_strings_to_tuples(self) -> None:
37
52
  fields = {
38
53
  k: FIELDS[k] for k in (
39
- "llm_type", "faker_type", "static_type", "callable_type", "custom_type"
54
+ 'llm_type', 'faker_type', 'static_type', 'callable_type', 'custom_type'
40
55
  )
41
56
  }
42
- normalized = normalize_seeder_fields(fields)
43
- for key, value in fields.items():
44
- self.assertEqual(normalized[key], (value,))
45
57
 
46
- def test_normalizes_callable(self):
47
- fields = {"callable_func": FIELDS["callable_func"]}
48
58
  normalized = normalize_seeder_fields(fields)
49
- self.assertEqual(normalized["callable_func"][0], "callable")
50
- self.assertTrue(callable(normalized["callable_func"][1]))
51
59
 
52
- def test_normalizes_static_values(self):
60
+ for key, value in fields.items():
61
+ assert normalized[key] == (value,)
62
+
63
+ def test_normalizes_static_values(self) -> None:
53
64
  fields = {
54
- "static_bool": FIELDS["static_bool"],
55
- "static_str": FIELDS["static_str"],
56
- "static_int": FIELDS["static_int"]
65
+ 'static_bool': FIELDS['static_bool'],
66
+ 'static_str': FIELDS['static_str'],
67
+ 'static_int': FIELDS['static_int']
57
68
  }
69
+
58
70
  normalized = normalize_seeder_fields(fields)
59
- self.assertEqual(normalized["static_bool"], ("static", True))
60
- self.assertEqual(normalized["static_str"], ("static", "approved"))
61
- self.assertEqual(normalized["static_int"], ("static", 10))
71
+
72
+ assert normalized['static_bool'] == ('static', True)
73
+ assert normalized['static_str'] == ('static', 'approved')
74
+ assert normalized['static_int'] == ('static', 10)
@@ -1,18 +1,22 @@
1
- import unittest
2
- from django_spire.contrib.seeding.field.static import StaticFieldSeeder
1
+ from __future__ import annotations
2
+
3
+ from django.test import TestCase
3
4
 
5
+ from django_spire.contrib.seeding.field.static import StaticFieldSeeder
4
6
 
5
- class TestStaticFieldSeeder(unittest.TestCase):
6
7
 
7
- def test_seeds_static_fields(self):
8
+ class TestStaticFieldSeeder(TestCase):
9
+ def test_seeds_static_fields(self) -> None:
8
10
  fields = {
9
- "category": ("static", "books"),
10
- "in_stock": ("static", True)
11
+ 'category': ('static', 'books'),
12
+ 'in_stock': ('static', True)
11
13
  }
12
14
  seeder = StaticFieldSeeder(fields=fields)
15
+
13
16
  result = seeder.seed(count=3)
14
17
 
15
- self.assertEqual(len(result), 3)
18
+ assert len(result) == 3
19
+
16
20
  for row in result:
17
- self.assertEqual(row["category"], "books")
18
- self.assertEqual(row["in_stock"], True)
21
+ assert row['category'] == 'books'
22
+ assert row['in_stock'] is True
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from dandy import Bot, LlmConfigOptions
2
4
 
3
5
  from django_spire.contrib.seeding.intelligence.intel import SourceIntel