django-spire 0.23.6__py3-none-any.whl → 0.23.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (541) hide show
  1. django_spire/ai/admin.py +11 -11
  2. django_spire/ai/chat/apps.py +1 -0
  3. django_spire/ai/chat/templates/django_spire/ai/chat/widget/dialog_widget.html +1 -1
  4. django_spire/ai/chat/tests/factories.py +15 -0
  5. django_spire/ai/chat/tests/test_controller.py +45 -0
  6. django_spire/ai/chat/tests/test_models.py +301 -0
  7. django_spire/ai/chat/tests/test_prompts.py +48 -0
  8. django_spire/ai/chat/tests/test_responses.py +208 -0
  9. django_spire/ai/chat/tests/test_router/test_base_chat_router.py +66 -6
  10. django_spire/ai/chat/tests/test_router/test_chat_workflow.py +73 -3
  11. django_spire/ai/chat/tests/test_router/test_integration.py +86 -6
  12. django_spire/ai/chat/tests/test_router/test_intent_decoder.py +93 -1
  13. django_spire/ai/chat/tests/test_router/test_message_intel.py +60 -1
  14. django_spire/ai/chat/tests/test_router/test_spire_chat_router.py +110 -0
  15. django_spire/ai/chat/tests/test_urls/test_json_urls.py +202 -1
  16. django_spire/ai/context/tests/__init__.py +0 -0
  17. django_spire/ai/context/tests/test_context.py +188 -0
  18. django_spire/ai/decorators.py +7 -6
  19. django_spire/ai/prompt/tests/test_bots.py +100 -10
  20. django_spire/ai/prompt/tests/test_prompt_intel.py +83 -0
  21. django_spire/ai/prompt/tests/test_prompt_tuning.py +126 -0
  22. django_spire/ai/sms/decorators.py +8 -2
  23. django_spire/ai/sms/tests/test_sms.py +240 -16
  24. django_spire/ai/sms/tests/test_sms_intel.py +42 -0
  25. django_spire/ai/sms/tests/test_webhook.py +155 -7
  26. django_spire/ai/sms/views.py +23 -24
  27. django_spire/ai/tests/test_ai.py +131 -7
  28. django_spire/auth/apps.py +4 -2
  29. django_spire/auth/controller/controller.py +36 -23
  30. django_spire/auth/controller/exceptions.py +9 -0
  31. django_spire/auth/group/admin.py +1 -0
  32. django_spire/auth/group/apps.py +2 -0
  33. django_spire/auth/group/factories.py +17 -8
  34. django_spire/auth/group/forms.py +7 -0
  35. django_spire/auth/group/tests/test_factories.py +146 -0
  36. django_spire/auth/group/tests/test_forms.py +282 -0
  37. django_spire/auth/group/tests/test_models.py +192 -0
  38. django_spire/auth/group/tests/test_querysets.py +98 -0
  39. django_spire/auth/group/tests/test_utils.py +341 -0
  40. django_spire/auth/group/tests/test_views.py +377 -0
  41. django_spire/auth/group/urls/__init__.py +3 -1
  42. django_spire/auth/group/urls/form_urls.py +2 -0
  43. django_spire/auth/group/urls/json_urls.py +3 -0
  44. django_spire/auth/group/urls/page_urls.py +2 -0
  45. django_spire/auth/group/utils.py +6 -2
  46. django_spire/auth/group/views/form_views.py +6 -3
  47. django_spire/auth/group/views/json_views.py +6 -2
  48. django_spire/auth/mfa/admin.py +2 -0
  49. django_spire/auth/mfa/apps.py +2 -0
  50. django_spire/auth/mfa/forms.py +1 -0
  51. django_spire/auth/mfa/querysets.py +9 -2
  52. django_spire/auth/mfa/tests/test_models.py +233 -0
  53. django_spire/auth/mfa/tests/test_utils.py +106 -0
  54. django_spire/auth/mfa/urls/__init__.py +2 -0
  55. django_spire/auth/mfa/urls/page_urls.py +2 -0
  56. django_spire/auth/mfa/urls/redirect_urls.py +2 -0
  57. django_spire/auth/mfa/views/page_views.py +2 -1
  58. django_spire/auth/permissions/consts.py +2 -2
  59. django_spire/auth/permissions/decorators.py +8 -8
  60. django_spire/auth/permissions/permissions.py +28 -35
  61. django_spire/auth/permissions/tests/test_decorators.py +333 -0
  62. django_spire/auth/permissions/tests/test_permissions.py +337 -0
  63. django_spire/auth/permissions/tests/test_tools.py +305 -0
  64. django_spire/auth/permissions/tools.py +21 -15
  65. django_spire/auth/seeding/seed.py +3 -0
  66. django_spire/auth/seeding/seeder.py +2 -0
  67. django_spire/auth/tests/test_controller.py +323 -0
  68. django_spire/auth/tests/test_url_endpoints.py +9 -9
  69. django_spire/auth/tests/test_views.py +406 -0
  70. django_spire/auth/urls/admin_urls.py +2 -0
  71. django_spire/auth/urls/redirect_urls.py +2 -0
  72. django_spire/auth/user/apps.py +2 -0
  73. django_spire/auth/user/forms.py +9 -0
  74. django_spire/auth/user/models.py +1 -1
  75. django_spire/auth/user/services/services.py +1 -0
  76. django_spire/auth/user/tests/factories.py +14 -13
  77. django_spire/auth/user/tests/test_factories.py +166 -2
  78. django_spire/auth/user/tests/test_forms.py +573 -0
  79. django_spire/auth/user/tests/test_models.py +257 -0
  80. django_spire/auth/user/tests/test_services.py +200 -0
  81. django_spire/auth/user/tests/test_tools.py +153 -0
  82. django_spire/auth/user/tests/test_user_factories.py +139 -0
  83. django_spire/auth/user/tests/test_views.py +363 -0
  84. django_spire/auth/user/tools.py +7 -1
  85. django_spire/auth/user/urls/form_urls.py +3 -0
  86. django_spire/auth/user/urls/page_urls.py +3 -0
  87. django_spire/auth/user/views/form_views.py +19 -10
  88. django_spire/auth/user/views/page_views.py +8 -2
  89. django_spire/auth/views/redirect_views.py +14 -9
  90. django_spire/comment/admin.py +2 -0
  91. django_spire/comment/apps.py +2 -0
  92. django_spire/comment/templatetags/comment_tags.py +1 -0
  93. django_spire/comment/tests/test_forms.py +27 -0
  94. django_spire/comment/tests/test_models.py +215 -0
  95. django_spire/comment/tests/test_querysets.py +101 -0
  96. django_spire/comment/tests/test_utils.py +90 -0
  97. django_spire/comment/urls.py +2 -0
  98. django_spire/comment/utils.py +22 -13
  99. django_spire/comment/views.py +1 -1
  100. django_spire/conf.py +8 -6
  101. django_spire/consts.py +1 -1
  102. django_spire/contrib/breadcrumb/apps.py +2 -0
  103. django_spire/contrib/breadcrumb/breadcrumbs.py +18 -18
  104. django_spire/contrib/breadcrumb/tests/test_breadcrumbs.py +198 -0
  105. django_spire/contrib/constructor/__init__.py +3 -3
  106. django_spire/contrib/constructor/constructor.py +15 -15
  107. django_spire/contrib/constructor/django_model_constructor.py +5 -4
  108. django_spire/contrib/constructor/exceptions.py +5 -3
  109. django_spire/contrib/constructor/tests/__init__.py +0 -0
  110. django_spire/contrib/constructor/tests/test_constructor.py +193 -0
  111. django_spire/contrib/form/tests/__init__.py +0 -0
  112. django_spire/contrib/form/tests/test_forms.py +203 -0
  113. django_spire/contrib/generic_views/modal_views.py +2 -1
  114. django_spire/contrib/generic_views/portal_views.py +20 -19
  115. django_spire/contrib/generic_views/tests/__init__.py +0 -0
  116. django_spire/contrib/generic_views/tests/test_views.py +459 -0
  117. django_spire/contrib/help/apps.py +2 -0
  118. django_spire/contrib/help/templatetags/help.py +1 -0
  119. django_spire/contrib/help/tests/__init__.py +0 -0
  120. django_spire/contrib/help/tests/test_templatetags.py +100 -0
  121. django_spire/contrib/options/mixins.py +6 -5
  122. django_spire/contrib/options/tests/factories.py +5 -1
  123. django_spire/contrib/options/tests/test_options.py +234 -0
  124. django_spire/contrib/ordering/exceptions.py +7 -3
  125. django_spire/contrib/ordering/mixins.py +2 -0
  126. django_spire/contrib/ordering/querysets.py +3 -1
  127. django_spire/contrib/ordering/services/processor_service.py +8 -4
  128. django_spire/contrib/ordering/services/service.py +1 -2
  129. django_spire/contrib/ordering/tests/__init__.py +0 -0
  130. django_spire/contrib/ordering/tests/test_ordering.py +165 -0
  131. django_spire/contrib/ordering/validators.py +6 -6
  132. django_spire/contrib/pagination/templatetags/pagination_tags.py +12 -5
  133. django_spire/contrib/pagination/tests/__init__.py +0 -0
  134. django_spire/contrib/pagination/tests/test_pagination.py +179 -0
  135. django_spire/contrib/performance/decorators.py +16 -6
  136. django_spire/contrib/performance/tests/__init__.py +0 -0
  137. django_spire/contrib/performance/tests/test_performance.py +107 -0
  138. django_spire/contrib/progress/__init__.py +1 -3
  139. django_spire/contrib/progress/static/django_spire/js/contrib/progress/progress.js +38 -82
  140. django_spire/contrib/queryset/enums.py +3 -1
  141. django_spire/contrib/queryset/filter_tools.py +10 -5
  142. django_spire/contrib/queryset/mixins.py +16 -16
  143. django_spire/contrib/queryset/tests/__init__.py +0 -0
  144. django_spire/contrib/queryset/tests/test_queryset.py +137 -0
  145. django_spire/contrib/seeding/field/base.py +13 -7
  146. django_spire/contrib/seeding/field/callable.py +8 -1
  147. django_spire/contrib/seeding/field/cleaners.py +5 -5
  148. django_spire/contrib/seeding/field/custom.py +20 -10
  149. django_spire/contrib/seeding/field/django/seeder.py +8 -6
  150. django_spire/contrib/seeding/field/enums.py +7 -5
  151. django_spire/contrib/seeding/field/override.py +16 -6
  152. django_spire/contrib/seeding/field/static.py +9 -2
  153. django_spire/contrib/seeding/field/tests/test_base.py +18 -14
  154. django_spire/contrib/seeding/field/tests/test_callable.py +13 -9
  155. django_spire/contrib/seeding/field/tests/test_cleaners.py +51 -38
  156. django_spire/contrib/seeding/field/tests/test_static.py +13 -9
  157. django_spire/contrib/seeding/intelligence/bots/seeder_generator_bot.py +2 -0
  158. django_spire/contrib/seeding/intelligence/intel.py +5 -1
  159. django_spire/contrib/seeding/intelligence/prompts/factory.py +6 -1
  160. django_spire/contrib/seeding/intelligence/prompts/foreign_key_selection_prompt.py +6 -1
  161. django_spire/contrib/seeding/intelligence/prompts/generate_django_model_seeder_prompts.py +2 -0
  162. django_spire/contrib/seeding/intelligence/prompts/generic_relationship_selection_prompt.py +7 -1
  163. django_spire/contrib/seeding/intelligence/prompts/hierarchical_selection_prompt.py +6 -2
  164. django_spire/contrib/seeding/intelligence/prompts/model_field_choices_prompt.py +8 -2
  165. django_spire/contrib/seeding/intelligence/prompts/negation_prompt.py +2 -0
  166. django_spire/contrib/seeding/intelligence/prompts/objective_prompt.py +6 -1
  167. django_spire/contrib/seeding/management/commands/seeding.py +9 -3
  168. django_spire/contrib/seeding/management/example.py +2 -0
  169. django_spire/contrib/seeding/model/base.py +16 -7
  170. django_spire/contrib/seeding/model/config.py +31 -15
  171. django_spire/contrib/seeding/model/django/config.py +13 -13
  172. django_spire/contrib/seeding/model/django/seeder.py +4 -4
  173. django_spire/contrib/seeding/model/django/tests/test_seeder.py +34 -23
  174. django_spire/contrib/seeding/model/enums.py +2 -0
  175. django_spire/contrib/seeding/tests/test_config.py +71 -0
  176. django_spire/contrib/seeding/tests/test_custom.py +35 -0
  177. django_spire/contrib/seeding/tests/test_enums.py +40 -0
  178. django_spire/contrib/seeding/tests/test_intel.py +32 -0
  179. django_spire/contrib/seeding/tests/test_override.py +63 -0
  180. django_spire/contrib/service/__init__.py +2 -2
  181. django_spire/contrib/service/django_model_service.py +16 -15
  182. django_spire/contrib/service/exceptions.py +5 -3
  183. django_spire/contrib/service/tests/__init__.py +0 -0
  184. django_spire/contrib/service/tests/test_service.py +153 -0
  185. django_spire/contrib/session/apps.py +2 -0
  186. django_spire/contrib/session/controller.py +48 -42
  187. django_spire/contrib/session/templatetags/session_tags.py +11 -2
  188. django_spire/contrib/session/tests/test_session_controller.py +117 -53
  189. django_spire/contrib/tests/__init__.py +0 -0
  190. django_spire/contrib/tests/test_utils.py +37 -0
  191. django_spire/contrib/utils.py +4 -1
  192. django_spire/core/apps.py +2 -0
  193. django_spire/core/converters/tests/test_to_data.py +353 -0
  194. django_spire/core/converters/tests/test_to_enums.py +61 -41
  195. django_spire/core/converters/tests/test_to_pydantic.py +138 -109
  196. django_spire/core/converters/to_data.py +29 -10
  197. django_spire/core/converters/to_enums.py +4 -2
  198. django_spire/core/converters/to_pydantic.py +22 -22
  199. django_spire/core/decorators.py +19 -6
  200. django_spire/core/forms/widgets.py +4 -0
  201. django_spire/core/maps.py +3 -1
  202. django_spire/core/middleware/maintenance.py +3 -3
  203. django_spire/core/middleware.py +8 -6
  204. django_spire/core/redirect/__init__.py +5 -0
  205. django_spire/core/redirect/generic_redirect.py +1 -2
  206. django_spire/core/redirect/tests/__init__.py +0 -0
  207. django_spire/core/redirect/tests/test_generic_redirect.py +34 -0
  208. django_spire/core/{tests/tests_redirect.py → redirect/tests/test_safe_redirect.py} +55 -81
  209. django_spire/core/shortcuts.py +3 -3
  210. django_spire/core/static/django_spire/css/app-layout.css +1 -1
  211. django_spire/core/static/django_spire/css/app-navigation.css +3 -3
  212. django_spire/core/static/django_spire/css/bootstrap-override.css +4 -0
  213. django_spire/core/tag/admin.py +12 -0
  214. django_spire/core/tag/intelligence/tag_set_bot.py +2 -0
  215. django_spire/core/tag/mixins.py +2 -0
  216. django_spire/core/tag/models.py +2 -0
  217. django_spire/core/tag/querysets.py +2 -0
  218. django_spire/core/tag/service/tag_service.py +6 -3
  219. django_spire/core/tag/tests/test_intelligence.py +9 -9
  220. django_spire/core/tag/tests/test_tags.py +44 -54
  221. django_spire/core/tag/tests/test_tools.py +191 -0
  222. django_spire/core/tag/tools.py +3 -0
  223. django_spire/core/templates/django_spire/card/card.html +5 -2
  224. django_spire/core/templates/django_spire/card/title_card.html +9 -4
  225. django_spire/core/templates/django_spire/infinite_scroll/base.html +1 -0
  226. django_spire/core/templates/django_spire/navigation/side_navigation.html +19 -24
  227. django_spire/core/templates/django_spire/page/full_page.html +46 -16
  228. django_spire/core/templates/django_spire/table/base.html +4 -2
  229. django_spire/core/templatetags/json.py +6 -2
  230. django_spire/core/templatetags/message.py +13 -8
  231. django_spire/core/templatetags/string_formating.py +8 -5
  232. django_spire/core/templatetags/tests/__init__.py +0 -0
  233. django_spire/core/templatetags/tests/test_templatetags.py +427 -0
  234. django_spire/core/templatetags/variable_types.py +17 -9
  235. django_spire/core/tests/test_cases.py +1 -1
  236. django_spire/core/tests/test_conf.py +43 -0
  237. django_spire/core/tests/test_consts.py +28 -0
  238. django_spire/core/tests/test_context_processors.py +93 -0
  239. django_spire/core/tests/test_decorators.py +95 -0
  240. django_spire/core/tests/test_django_spire_utils.py +56 -0
  241. django_spire/core/tests/test_exceptions.py +37 -0
  242. django_spire/core/tests/test_models.py +54 -0
  243. django_spire/core/tests/test_settings.py +45 -0
  244. django_spire/core/tests/test_shortcuts.py +74 -0
  245. django_spire/core/tests/test_urls.py +16 -0
  246. django_spire/core/tests/test_utils.py +58 -0
  247. django_spire/core/urls.py +4 -1
  248. django_spire/core/utils.py +12 -8
  249. django_spire/exceptions.py +16 -1
  250. django_spire/file/admin.py +4 -2
  251. django_spire/file/apps.py +8 -10
  252. django_spire/file/fields.py +7 -7
  253. django_spire/file/forms.py +1 -1
  254. django_spire/file/interfaces.py +15 -15
  255. django_spire/file/mixins.py +1 -4
  256. django_spire/file/models.py +3 -5
  257. django_spire/file/tests/factories.py +59 -0
  258. django_spire/file/tests/test_admin.py +69 -0
  259. django_spire/file/tests/test_apps.py +24 -0
  260. django_spire/file/tests/test_fields.py +114 -0
  261. django_spire/file/tests/test_forms.py +20 -0
  262. django_spire/file/tests/test_interfaces.py +183 -0
  263. django_spire/file/tests/test_models.py +82 -0
  264. django_spire/file/tests/test_querysets.py +102 -0
  265. django_spire/file/tests/test_utils.py +32 -0
  266. django_spire/file/tests/test_views.py +145 -0
  267. django_spire/file/tests/test_widgets.py +82 -0
  268. django_spire/file/tools.py +8 -2
  269. django_spire/file/views.py +7 -3
  270. django_spire/file/widgets.py +12 -12
  271. django_spire/help_desk/admin.py +15 -0
  272. django_spire/help_desk/apps.py +2 -0
  273. django_spire/help_desk/auth/controller.py +2 -0
  274. django_spire/help_desk/choices.py +2 -0
  275. django_spire/help_desk/enums.py +2 -0
  276. django_spire/help_desk/exceptions.py +31 -3
  277. django_spire/help_desk/forms.py +2 -0
  278. django_spire/help_desk/models.py +2 -0
  279. django_spire/help_desk/querysets.py +4 -1
  280. django_spire/help_desk/services/notification_service.py +26 -27
  281. django_spire/help_desk/services/service.py +2 -3
  282. django_spire/help_desk/tests/factories.py +8 -3
  283. django_spire/help_desk/tests/test_admin.py +41 -0
  284. django_spire/help_desk/tests/test_apps.py +41 -0
  285. django_spire/help_desk/tests/test_choices.py +50 -0
  286. django_spire/help_desk/tests/test_controller.py +87 -0
  287. django_spire/help_desk/tests/test_enums.py +18 -0
  288. django_spire/help_desk/tests/test_exceptions.py +37 -0
  289. django_spire/help_desk/tests/test_forms.py +89 -0
  290. django_spire/help_desk/tests/test_models.py +59 -0
  291. django_spire/help_desk/tests/test_querysets.py +38 -0
  292. django_spire/help_desk/tests/test_services/test_notification_service.py +15 -8
  293. django_spire/help_desk/tests/test_services/test_service.py +92 -0
  294. django_spire/help_desk/tests/test_urls/test_form_urls.py +6 -6
  295. django_spire/help_desk/tests/test_urls/test_page_urls.py +8 -9
  296. django_spire/help_desk/tests/test_views/test_form_views.py +46 -19
  297. django_spire/help_desk/tests/test_views/test_page_views.py +32 -9
  298. django_spire/help_desk/urls/__init__.py +4 -1
  299. django_spire/help_desk/urls/form_urls.py +3 -0
  300. django_spire/help_desk/urls/page_urls.py +3 -0
  301. django_spire/help_desk/views/form_views.py +13 -5
  302. django_spire/help_desk/views/page_views.py +11 -3
  303. django_spire/history/activity/admin.py +2 -0
  304. django_spire/history/activity/apps.py +3 -1
  305. django_spire/history/activity/mixins.py +13 -7
  306. django_spire/history/activity/models.py +6 -5
  307. django_spire/history/activity/querysets.py +2 -0
  308. django_spire/history/activity/tests/__init__.py +0 -0
  309. django_spire/history/activity/tests/test_activity.py +176 -0
  310. django_spire/history/admin.py +9 -2
  311. django_spire/history/choices.py +3 -0
  312. django_spire/history/models.py +5 -5
  313. django_spire/history/tests/test_admin.py +93 -0
  314. django_spire/history/tests/test_history.py +101 -0
  315. django_spire/history/tests/test_mixins.py +84 -0
  316. django_spire/history/viewed/admin.py +3 -1
  317. django_spire/history/viewed/apps.py +3 -1
  318. django_spire/history/viewed/models.py +2 -0
  319. django_spire/history/viewed/tests/__init__.py +0 -0
  320. django_spire/history/viewed/tests/test_viewed.py +46 -0
  321. django_spire/knowledge/auth/tests/__init__.py +0 -0
  322. django_spire/knowledge/auth/tests/test_controller.py +116 -0
  323. django_spire/knowledge/collection/admin.py +5 -1
  324. django_spire/knowledge/collection/models.py +3 -1
  325. django_spire/knowledge/collection/seeding/seed.py +1 -0
  326. django_spire/knowledge/collection/services/factory_service.py +10 -11
  327. django_spire/knowledge/collection/services/ordering_service.py +1 -2
  328. django_spire/knowledge/collection/services/service.py +5 -10
  329. django_spire/knowledge/collection/services/tag_service.py +5 -2
  330. django_spire/knowledge/collection/tests/factories.py +28 -1
  331. django_spire/knowledge/collection/tests/test_models.py +48 -0
  332. django_spire/knowledge/collection/tests/test_querysets.py +93 -0
  333. django_spire/knowledge/collection/tests/test_services/test_factory_service.py +100 -0
  334. django_spire/knowledge/collection/tests/test_services/test_services.py +160 -0
  335. django_spire/knowledge/collection/tests/test_urls/test_form_urls.py +21 -3
  336. django_spire/knowledge/collection/tests/test_urls/test_json_urls.py +39 -1
  337. django_spire/knowledge/collection/tests/test_urls/test_page_urls.py +12 -4
  338. django_spire/knowledge/collection/urls/__init__.py +3 -0
  339. django_spire/knowledge/collection/urls/form_urls.py +2 -0
  340. django_spire/knowledge/collection/urls/json_urls.py +2 -0
  341. django_spire/knowledge/collection/urls/page_urls.py +2 -0
  342. django_spire/knowledge/collection/views/form_views.py +4 -4
  343. django_spire/knowledge/collection/views/json_views.py +5 -1
  344. django_spire/knowledge/collection/views/page_views.py +5 -2
  345. django_spire/knowledge/entry/admin.py +7 -1
  346. django_spire/knowledge/entry/forms.py +2 -0
  347. django_spire/knowledge/entry/models.py +2 -0
  348. django_spire/knowledge/entry/seeding/seed.py +3 -0
  349. django_spire/knowledge/entry/services/automation_service.py +5 -4
  350. django_spire/knowledge/entry/services/factory_service.py +7 -5
  351. django_spire/knowledge/entry/services/service.py +4 -7
  352. django_spire/knowledge/entry/services/tag_service.py +0 -1
  353. django_spire/knowledge/entry/services/tool_service.py +1 -0
  354. django_spire/knowledge/entry/services/transformation_services.py +1 -5
  355. django_spire/knowledge/entry/tests/factories.py +1 -2
  356. django_spire/knowledge/entry/tests/test_factory_service.py +20 -0
  357. django_spire/knowledge/entry/tests/test_models.py +41 -0
  358. django_spire/knowledge/entry/tests/test_querysets.py +71 -0
  359. django_spire/knowledge/entry/tests/test_services.py +94 -0
  360. django_spire/knowledge/entry/tests/test_urls/test_form_urls.py +9 -14
  361. django_spire/knowledge/entry/tests/test_urls/test_json_urls.py +48 -5
  362. django_spire/knowledge/entry/tests/test_urls/test_page_urls.py +6 -8
  363. django_spire/knowledge/entry/tests/test_urls/test_template_urls.py +40 -0
  364. django_spire/knowledge/entry/urls/form_urls.py +2 -0
  365. django_spire/knowledge/entry/urls/json_urls.py +2 -0
  366. django_spire/knowledge/entry/urls/page_urls.py +2 -0
  367. django_spire/knowledge/entry/urls/template_urls.py +2 -0
  368. django_spire/knowledge/entry/version/block/choices.py +2 -0
  369. django_spire/knowledge/entry/version/block/data/data.py +1 -0
  370. django_spire/knowledge/entry/version/block/data/list/data.py +8 -13
  371. django_spire/knowledge/entry/version/block/data/list/maps.py +3 -0
  372. django_spire/knowledge/entry/version/block/data/list/meta.py +1 -2
  373. django_spire/knowledge/entry/version/block/data/list/tests/__init__.py +0 -0
  374. django_spire/knowledge/entry/version/block/data/list/tests/test_maps.py +32 -0
  375. django_spire/knowledge/entry/version/block/data/list/tests/test_meta.py +58 -0
  376. django_spire/knowledge/entry/version/block/data/maps.py +3 -6
  377. django_spire/knowledge/entry/version/block/models.py +7 -5
  378. django_spire/knowledge/entry/version/block/seeding/constants.py +5 -4
  379. django_spire/knowledge/entry/version/block/services/service.py +2 -3
  380. django_spire/knowledge/entry/version/block/tests/factories.py +4 -10
  381. django_spire/knowledge/entry/version/block/tests/test_choices.py +56 -0
  382. django_spire/knowledge/entry/version/block/tests/test_data.py +90 -0
  383. django_spire/knowledge/entry/version/block/tests/test_maps.py +37 -0
  384. django_spire/knowledge/entry/version/block/tests/test_models.py +55 -0
  385. django_spire/knowledge/entry/version/block/tests/test_querysets.py +35 -0
  386. django_spire/knowledge/entry/version/block/tests/test_services.py +65 -0
  387. django_spire/knowledge/entry/version/choices.py +2 -0
  388. django_spire/knowledge/entry/version/converters/converter.py +1 -1
  389. django_spire/knowledge/entry/version/converters/docx_converter.py +4 -7
  390. django_spire/knowledge/entry/version/converters/markdown_converter.py +20 -20
  391. django_spire/knowledge/entry/version/maps.py +4 -5
  392. django_spire/knowledge/entry/version/querysets.py +1 -1
  393. django_spire/knowledge/entry/version/seeding/seeder.py +1 -2
  394. django_spire/knowledge/entry/version/services/processor_service.py +5 -4
  395. django_spire/knowledge/entry/version/services/service.py +1 -2
  396. django_spire/knowledge/entry/version/tests/factories.py +2 -2
  397. django_spire/knowledge/entry/version/tests/test_choices.py +18 -0
  398. django_spire/knowledge/entry/version/tests/test_converters/test_docx_converter.py +56 -8
  399. django_spire/knowledge/entry/version/tests/test_converters/test_markdown_converter.py +78 -0
  400. django_spire/knowledge/entry/version/tests/test_maps.py +58 -0
  401. django_spire/knowledge/entry/version/tests/test_models.py +23 -0
  402. django_spire/knowledge/entry/version/tests/test_querysets.py +26 -0
  403. django_spire/knowledge/entry/version/tests/test_services.py +62 -0
  404. django_spire/knowledge/entry/version/tests/test_urls/test_json_urls.py +27 -8
  405. django_spire/knowledge/entry/version/tests/test_urls/test_page_urls.py +15 -8
  406. django_spire/knowledge/entry/version/tests/test_urls/test_redirect_urls.py +38 -0
  407. django_spire/knowledge/entry/version/urls/__init__.py +3 -0
  408. django_spire/knowledge/entry/version/urls/json_urls.py +2 -1
  409. django_spire/knowledge/entry/version/urls/page_urls.py +2 -0
  410. django_spire/knowledge/entry/version/urls/redirect_urls.py +2 -0
  411. django_spire/knowledge/entry/version/views/json_views.py +5 -1
  412. django_spire/knowledge/entry/version/views/page_views.py +10 -3
  413. django_spire/knowledge/entry/version/views/redirect_views.py +5 -1
  414. django_spire/knowledge/entry/views/form_views.py +16 -8
  415. django_spire/knowledge/entry/views/json_views.py +3 -1
  416. django_spire/knowledge/entry/views/page_views.py +8 -2
  417. django_spire/knowledge/entry/views/template_views.py +7 -1
  418. django_spire/knowledge/exceptions.py +2 -1
  419. django_spire/knowledge/intelligence/intel/answer_intel.py +2 -1
  420. django_spire/knowledge/intelligence/intel/entry_intel.py +0 -1
  421. django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +4 -5
  422. django_spire/knowledge/models.py +1 -2
  423. django_spire/knowledge/tests/__init__.py +0 -0
  424. django_spire/knowledge/tests/test_templatetags.py +40 -0
  425. django_spire/knowledge/tests/test_urls/__init__.py +0 -0
  426. django_spire/knowledge/tests/test_urls/test_page_urls.py +24 -0
  427. django_spire/knowledge/urls/__init__.py +2 -0
  428. django_spire/knowledge/urls/page_urls.py +2 -0
  429. django_spire/knowledge/views/page_views.py +8 -3
  430. django_spire/notification/admin.py +3 -1
  431. django_spire/notification/app/admin.py +2 -0
  432. django_spire/notification/app/apps.py +3 -1
  433. django_spire/notification/app/exceptions.py +9 -2
  434. django_spire/notification/app/models.py +8 -4
  435. django_spire/notification/app/processor.py +22 -26
  436. django_spire/notification/app/querysets.py +2 -0
  437. django_spire/notification/app/tests/__init__.py +0 -0
  438. django_spire/notification/app/tests/factories.py +34 -0
  439. django_spire/notification/app/tests/test_apps.py +24 -0
  440. django_spire/notification/app/tests/test_models.py +72 -0
  441. django_spire/notification/app/tests/test_processor.py +111 -0
  442. django_spire/notification/app/tests/test_querysets.py +90 -0
  443. django_spire/notification/app/tests/test_views/__init__.py +0 -0
  444. django_spire/notification/app/tests/test_views/test_json_views.py +48 -0
  445. django_spire/notification/app/tests/test_views/test_page_views.py +19 -0
  446. django_spire/notification/app/urls/__init__.py +3 -1
  447. django_spire/notification/app/urls/json_urls.py +6 -4
  448. django_spire/notification/app/urls/page_urls.py +4 -3
  449. django_spire/notification/app/urls/template_urls.py +4 -2
  450. django_spire/notification/apps.py +4 -1
  451. django_spire/notification/email/admin.py +5 -1
  452. django_spire/notification/email/apps.py +3 -1
  453. django_spire/notification/email/exceptions.py +4 -2
  454. django_spire/notification/email/helper.py +5 -3
  455. django_spire/notification/email/models.py +4 -0
  456. django_spire/notification/email/processor.py +19 -15
  457. django_spire/notification/email/querysets.py +3 -0
  458. django_spire/notification/email/tests/__init__.py +0 -0
  459. django_spire/notification/email/tests/factories.py +35 -0
  460. django_spire/notification/email/tests/test_apps.py +24 -0
  461. django_spire/notification/email/tests/test_models.py +52 -0
  462. django_spire/notification/email/tests/test_processor.py +92 -0
  463. django_spire/notification/email/tests/test_querysets.py +43 -0
  464. django_spire/notification/exceptions.py +17 -2
  465. django_spire/notification/managers.py +7 -1
  466. django_spire/notification/maps.py +4 -1
  467. django_spire/notification/mixins.py +2 -0
  468. django_spire/notification/models.py +3 -1
  469. django_spire/notification/processors/notification.py +12 -5
  470. django_spire/notification/processors/processor.py +2 -0
  471. django_spire/notification/processors/tests/__init__.py +0 -0
  472. django_spire/notification/processors/tests/test_notification.py +106 -0
  473. django_spire/notification/push/admin.py +10 -1
  474. django_spire/notification/push/apps.py +3 -1
  475. django_spire/notification/push/models.py +2 -3
  476. django_spire/notification/push/tests/__init__.py +0 -0
  477. django_spire/notification/push/tests/test_apps.py +24 -0
  478. django_spire/notification/push/tests/test_models.py +28 -0
  479. django_spire/notification/querysets.py +7 -1
  480. django_spire/notification/sms/admin.py +2 -0
  481. django_spire/notification/sms/apps.py +4 -1
  482. django_spire/notification/sms/automations.py +2 -0
  483. django_spire/notification/sms/choices.py +2 -0
  484. django_spire/notification/sms/exceptions.py +19 -5
  485. django_spire/notification/sms/helper.py +33 -23
  486. django_spire/notification/sms/models.py +5 -1
  487. django_spire/notification/sms/processor.py +20 -20
  488. django_spire/notification/sms/querysets.py +2 -0
  489. django_spire/notification/sms/tests/factories.py +33 -0
  490. django_spire/notification/sms/tests/test_apps.py +24 -0
  491. django_spire/notification/sms/tests/test_automation.py +38 -0
  492. django_spire/notification/sms/tests/test_choices.py +15 -0
  493. django_spire/notification/sms/tests/test_consts.py +17 -0
  494. django_spire/notification/sms/tests/test_exceptions.py +27 -0
  495. django_spire/notification/sms/tests/test_helper.py +50 -0
  496. django_spire/notification/sms/tests/test_models.py +81 -0
  497. django_spire/notification/sms/tests/test_processor.py +107 -0
  498. django_spire/notification/sms/tests/test_tools.py +25 -11
  499. django_spire/notification/sms/tools.py +16 -5
  500. django_spire/notification/sms/urls/__init__.py +3 -1
  501. django_spire/notification/sms/urls/media_urls.py +2 -0
  502. django_spire/notification/sms/views/media_views.py +14 -4
  503. django_spire/notification/tests/__init__.py +0 -0
  504. django_spire/notification/tests/factories.py +26 -0
  505. django_spire/notification/tests/test_admin.py +55 -0
  506. django_spire/notification/tests/test_apps.py +30 -0
  507. django_spire/notification/tests/test_automation.py +18 -0
  508. django_spire/notification/tests/test_choices.py +59 -0
  509. django_spire/notification/tests/test_exceptions.py +58 -0
  510. django_spire/notification/tests/test_managers.py +100 -0
  511. django_spire/notification/tests/test_maps.py +31 -0
  512. django_spire/notification/tests/test_models.py +76 -0
  513. django_spire/notification/tests/test_querysets.py +184 -0
  514. django_spire/notification/tests/test_utils.py +23 -0
  515. django_spire/notification/urls.py +3 -1
  516. django_spire/notification/utils.py +3 -1
  517. django_spire/settings.py +3 -0
  518. django_spire/theme/tests/test_context_processor.py +15 -13
  519. django_spire/theme/tests/test_enums.py +2 -2
  520. django_spire/theme/tests/test_filesystem.py +2 -5
  521. django_spire/theme/tests/test_integration.py +12 -12
  522. django_spire/theme/tests/test_model.py +40 -38
  523. django_spire/theme/tests/test_views/test_json_views.py +33 -33
  524. django_spire/theme/urls/json_urls.py +3 -0
  525. django_spire/theme/urls/page_urls.py +3 -0
  526. django_spire/urls.py +19 -15
  527. django_spire/utils.py +13 -4
  528. {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/METADATA +1 -1
  529. {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/RECORD +532 -361
  530. django_spire/contrib/options/tests/test_unit.py +0 -148
  531. django_spire/contrib/progress/views.py +0 -64
  532. django_spire/contrib/seeding/tests/test_seeding.py +0 -25
  533. django_spire/core/tests/test_templatetags.py +0 -117
  534. django_spire/core/tests/tests_shortcuts.py +0 -73
  535. django_spire/history/activity/tests.py +0 -3
  536. django_spire/history/activity/views.py +0 -3
  537. django_spire/knowledge/collection/tests/test_services/test_transformation_service.py +0 -71
  538. django_spire/notification/app/tests.py +0 -3
  539. {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/WHEEL +0 -0
  540. {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/licenses/LICENSE.md +0 -0
  541. {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/top_level.txt +0 -0
@@ -1,10 +1,14 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Iterator
4
+
1
5
  from dandy import BaseIntel
2
6
 
3
7
 
4
8
  class SeedingIntel(BaseIntel):
5
9
  items: list[dict]
6
10
 
7
- def __iter__(self):
11
+ def __iter__(self) -> Iterator[dict]:
8
12
  return iter(self.items)
9
13
 
10
14
 
@@ -1,7 +1,12 @@
1
- from django.db.models.base import Model
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
2
4
 
3
5
  from dandy import Prompt
4
6
 
7
+ if TYPE_CHECKING:
8
+ from django.db.models.base import Model
9
+
5
10
 
6
11
  class SeedingModelClassPromptFactory:
7
12
 
@@ -1,7 +1,12 @@
1
- from django.db.models import Model
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
2
4
 
3
5
  from dandy import Prompt
4
6
 
7
+ if TYPE_CHECKING:
8
+ from django.db.models import Model
9
+
5
10
 
6
11
  def foreign_key_selection_prompt(
7
12
  model_class: type[Model],
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from pathlib import Path
2
4
 
3
5
  from dandy import Prompt
@@ -1,8 +1,14 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
1
5
  from django.contrib.contenttypes.models import ContentType
2
- from django.db.models import Model
3
6
 
4
7
  from dandy import Prompt
5
8
 
9
+ if TYPE_CHECKING:
10
+ from django.db.models import Model
11
+
6
12
 
7
13
  def generic_relationship_selection_prompt(
8
14
  model_class: type[Model],
@@ -1,9 +1,13 @@
1
- from random import shuffle
1
+ from __future__ import annotations
2
2
 
3
- from django.db.models import Model
3
+ from random import shuffle
4
+ from typing import TYPE_CHECKING
4
5
 
5
6
  from dandy import Prompt
6
7
 
8
+ if TYPE_CHECKING:
9
+ from django.db.models import Model
10
+
7
11
 
8
12
  def hierarchical_selection_prompt(
9
13
  model_class: type[Model],
@@ -1,6 +1,12 @@
1
+ from __future__ import annotations
2
+
1
3
  from dandy import Prompt
2
- from django.db.models.enums import TextChoices
3
- from django.db.models.fields import Field
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ if TYPE_CHECKING:
8
+ from django.db.models.fields import Field
9
+ from django.db.models.enums import TextChoices
4
10
 
5
11
 
6
12
  def model_field_choices_prompt(
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from dandy import Prompt
2
4
 
3
5
 
@@ -1,7 +1,12 @@
1
- from django.db.models import Model
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
2
4
 
3
5
  from dandy import Prompt
4
6
 
7
+ if TYPE_CHECKING:
8
+ from django.db.models import Model
9
+
5
10
 
6
11
  def objective_prompt(
7
12
  model_class: type[Model],
@@ -1,12 +1,18 @@
1
+ from __future__ import annotations
2
+
1
3
  from pathlib import Path
4
+ from typing import TYPE_CHECKING
2
5
 
3
6
  from django.core.management.base import BaseCommand, CommandError
4
7
 
5
8
  from dandy.conf import settings
6
- from dandy.recorder import recorder_to_html_file
7
9
 
8
10
  from django_spire.contrib.seeding.intelligence.bots.seeder_generator_bot import SeederGeneratorBot
9
11
 
12
+ if TYPE_CHECKING:
13
+ from argparse import ArgumentParser
14
+ from typing import Any
15
+
10
16
 
11
17
  _SEEDING_OUTPUT_PATH = Path(settings.BASE_PATH, '.seeding_generator_output')
12
18
 
@@ -14,7 +20,7 @@ _SEEDING_OUTPUT_PATH = Path(settings.BASE_PATH, '.seeding_generator_output')
14
20
  class Command(BaseCommand):
15
21
  help = 'Generate a Seeder'
16
22
 
17
- def add_arguments(self, parser):
23
+ def add_arguments(self, parser: ArgumentParser) -> None:
18
24
  parser.add_argument(
19
25
  'model_import',
20
26
  type=str,
@@ -29,7 +35,7 @@ class Command(BaseCommand):
29
35
  )
30
36
 
31
37
  # @recorder_to_html_file('seeding_generator')
32
- def handle(self, *args, **kwargs):
38
+ def handle(self, *args: Any, **kwargs: Any) -> None:
33
39
  if not kwargs['model_import'] or not kwargs['model_description']:
34
40
  message = 'You must provide a model import path and a model description'
35
41
  raise CommandError(message)
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django_spire.contrib.seeding import DjangoModelSeeder
2
4
  from django_spire.contrib.seeding.model.django.tests.test_seeder import Product
3
5
 
@@ -1,4 +1,7 @@
1
+ from __future__ import annotations
2
+
1
3
  from abc import ABC, abstractmethod
4
+ from typing import TYPE_CHECKING
2
5
 
3
6
  from dandy.recorder import recorder_to_html_file
4
7
  from dandy import SqliteCache, generate_cache_key
@@ -6,12 +9,15 @@ from dandy import SqliteCache, generate_cache_key
6
9
  from django_spire.contrib.seeding.field.override import FieldOverride
7
10
  from django_spire.contrib.seeding.model.config import FieldsConfig
8
11
 
12
+ if TYPE_CHECKING:
13
+ from typing import Any
14
+
9
15
 
10
16
  class classproperty:
11
- def __init__(self, fget):
17
+ def __init__(self, fget: Any) -> None:
12
18
  self.fget = fget
13
19
 
14
- def __get__(self, instance, owner):
20
+ def __get__(self, instance: Any, owner: Any) -> Any:
15
21
  return self.fget(owner)
16
22
 
17
23
 
@@ -33,30 +39,33 @@ class BaseModelSeeder(ABC):
33
39
  _field_config: FieldsConfig = None
34
40
 
35
41
  @classproperty
36
- def override(cls):
42
+ def override(cls) -> FieldOverride:
37
43
  return cls.override_class(seeder_class=cls)
38
44
 
39
45
  @classmethod
40
46
  def get_field_config(cls) -> FieldsConfig:
41
47
  if cls._field_config is None:
42
48
  if cls.model_class is None:
43
- raise ValueError('model_class must be defined before using seeder.')
49
+ message = 'model_class must be defined before using seeder.'
50
+ raise ValueError(message)
44
51
 
45
52
  raw_fields = cls.__dict__.get('fields', {})
53
+
46
54
  cls._field_config = cls.field_config_class(
47
55
  raw_fields=raw_fields,
48
56
  field_names=cls.field_names(),
49
57
  default_to=cls.default_to,
50
58
  model_class=cls.model_class,
51
59
  )
60
+
52
61
  return cls._field_config
53
62
 
54
63
  @classmethod
55
- def resolved_fields(cls):
64
+ def resolved_fields(cls) -> dict:
56
65
  return cls.get_field_config().fields
57
66
 
58
67
  @classmethod
59
- def clear_cache(cls):
68
+ def clear_cache(cls) -> None:
60
69
  SqliteCache.clear(cache_name=cls.cache_name)
61
70
 
62
71
  @classmethod
@@ -68,7 +77,7 @@ class BaseModelSeeder(ABC):
68
77
  @recorder_to_html_file('model_seeder')
69
78
  def seed_data(
70
79
  cls,
71
- count=1,
80
+ count: int = 1,
72
81
  fields: dict | None = None,
73
82
  ) -> list[dict]:
74
83
  field_config = (
@@ -1,15 +1,22 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
1
5
  from django_spire.contrib.seeding.field.cleaners import normalize_seeder_fields
2
6
  from django_spire.contrib.seeding.model.enums import ModelSeederDefaultsEnum
3
7
 
8
+ if TYPE_CHECKING:
9
+ from typing import Any, Self
10
+
4
11
 
5
12
  class FieldsConfig:
6
13
  def __init__(
7
- self,
8
- raw_fields: dict,
9
- field_names: list[str],
10
- default_to: str,
11
- model_class
12
- ):
14
+ self,
15
+ raw_fields: dict[str, Any],
16
+ field_names: list[str],
17
+ default_to: str,
18
+ model_class: Any
19
+ ) -> None:
13
20
  self.raw_fields = raw_fields or {}
14
21
  self.default_to = default_to
15
22
  self.field_names = set(field_names)
@@ -17,7 +24,7 @@ class FieldsConfig:
17
24
 
18
25
  self._excluded = {
19
26
  k for k, v in self.raw_fields.items()
20
- if v == "exclude" or v == ("exclude",)
27
+ if v in ("exclude", ("exclude",))
21
28
  }
22
29
 
23
30
  self.fields = normalize_seeder_fields({
@@ -26,14 +33,18 @@ class FieldsConfig:
26
33
 
27
34
  self._validate()
28
35
  self._assign_defaults()
29
- self._order_fields() # Fields need to be in order for caching hash
30
36
 
31
- def _validate(self):
37
+ # Fields need to be in order for caching hash
38
+ self._order_fields()
39
+
40
+ def _validate(self) -> None:
32
41
  unknown = set(self.fields.keys()) - self.field_names
42
+
33
43
  if unknown:
34
- raise ValueError(f"Invalid field name(s): {', '.join(unknown)}")
44
+ message = f"Invalid field name(s): {', '.join(unknown)}"
45
+ raise ValueError(message)
35
46
 
36
- def _assign_defaults(self):
47
+ def _assign_defaults(self) -> None:
37
48
  if self.default_to == ModelSeederDefaultsEnum.INCLUDED:
38
49
  return
39
50
 
@@ -43,7 +54,7 @@ class FieldsConfig:
43
54
  if name not in self.fields and name not in self._excluded:
44
55
  self.fields[name] = (method,)
45
56
 
46
- def override(self, overrides: dict):
57
+ def override(self, overrides: dict) -> Self:
47
58
  merged = {**self.fields, **normalize_seeder_fields(overrides)}
48
59
  new_raw = {
49
60
  **{k: v for k, v in self.raw_fields.items() if k in self._excluded},
@@ -56,9 +67,14 @@ class FieldsConfig:
56
67
  model_class=self.model_class
57
68
  )
58
69
 
59
- def _order_fields(self):
60
- self.fields = {k: v for k, v in sorted(self.fields.items(), key=lambda x: x[0])}
70
+ def _order_fields(self) -> None:
71
+ self.fields = dict(
72
+ sorted(
73
+ self.fields.items(),
74
+ key=lambda x: x[0]
75
+ )
76
+ )
61
77
 
62
78
  @property
63
- def excluded(self):
79
+ def excluded(self) -> set:
64
80
  return self._excluded
@@ -1,9 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  from django_spire.contrib.seeding.model.config import FieldsConfig
2
4
 
3
5
 
4
6
  class DjangoModelFieldsConfig(FieldsConfig):
5
-
6
- def _validate(self):
7
+ def _validate(self) -> None:
7
8
  valid_field_names = set(self.field_names)
8
9
 
9
10
  valid_field_names.update(f.attname for f in self.model_class._meta.fields)
@@ -11,18 +12,18 @@ class DjangoModelFieldsConfig(FieldsConfig):
11
12
  unknown = set(self.fields.keys()) - valid_field_names
12
13
 
13
14
  if unknown:
14
- raise ValueError(f"Invalid field name(s): {', '.join(unknown)}")
15
+ message = f"Invalid field name(s): {', '.join(unknown)}"
16
+ raise ValueError(message)
15
17
 
16
18
  # fk_fields = {
17
19
  # f.name: f.attname # f.attname = "user_id"
18
20
  # for f in self.model_class._meta.fields
19
21
  # if isinstance(f, ForeignKey)
20
22
  # }
21
- #
23
+
22
24
  # for name, value in self.fields.items():
23
25
  # if name in fk_fields:
24
26
  # valid_types = [int, None]
25
- #
26
27
 
27
28
  # def map_foreign_keys(self):
28
29
  # fk_fields = {
@@ -30,19 +31,20 @@ class DjangoModelFieldsConfig(FieldsConfig):
30
31
  # for f in self.model_class._meta.fields
31
32
  # if isinstance(f, ForeignKey)
32
33
  # }
33
- #
34
+
34
35
  # # If asset_id and asset are passed, should I raise an error?
35
- #
36
+
36
37
  # {
37
38
  # 'asset' : ('custom', 'in_order', {'values': [a.id for a in assets]}),
38
39
  # 'asset_id': ('custom', 'in_order', {'values': [a.id for a in assets]}),
39
40
  # }
40
- #
41
+
41
42
  # translated_fields = {}
43
+
42
44
  # for name, value in self.fields.items():
43
45
  # if name in fk_fields:
44
46
  # translated_name = fk_fields[name]
45
- #
47
+
46
48
  # if translated_name in self.field_names:
47
49
  # # Do not want to override asset_id if the user has already pass it in.
48
50
  # continue
@@ -50,9 +52,7 @@ class DjangoModelFieldsConfig(FieldsConfig):
50
52
  # translated_fields[translated_name] = value
51
53
  # else:
52
54
  # translated_fields[name] = value
53
- #
55
+
54
56
  # return translated_fields
55
- #
57
+
56
58
  # # Update self.fields.
57
- #
58
- #
@@ -2,8 +2,8 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TypeVar, TYPE_CHECKING
4
4
 
5
- from django.db.models import ForeignKey
6
5
  from django.db.models.base import Model
6
+ from django.db.models import ForeignKey
7
7
 
8
8
  from django_spire.contrib.seeding.field.callable import CallableFieldSeeder
9
9
  from django_spire.contrib.seeding.field.custom import CustomFieldSeeder
@@ -37,11 +37,11 @@ class DjangoModelSeeder(BaseModelSeeder):
37
37
  super().__init_subclass__(**kwargs)
38
38
 
39
39
  if cls.model_class is None:
40
- message = "Seeds must have a model class"
40
+ message = 'Seeds must have a model class'
41
41
  raise ValueError(message)
42
42
 
43
43
  if cls.fields is None:
44
- message = "Seeds must have fields"
44
+ message = 'Seeds must have fields'
45
45
  raise ValueError(message)
46
46
 
47
47
  @classmethod
@@ -55,7 +55,7 @@ class DjangoModelSeeder(BaseModelSeeder):
55
55
  @classmethod
56
56
  def seed_database(
57
57
  cls,
58
- count=1,
58
+ count: int = 1,
59
59
  fields: dict | None = None
60
60
  ) -> list[TypeModel]:
61
61
  model_objects = cls.seed(count, fields)
@@ -1,5 +1,9 @@
1
- import unittest
1
+ from __future__ import annotations
2
+
3
+ import pytest
4
+
2
5
  from django.db import models
6
+ from django.test import TestCase
3
7
  from django.utils.timezone import now
4
8
 
5
9
  from django_spire.contrib.seeding import DjangoModelSeeder
@@ -15,39 +19,46 @@ class Product(models.Model):
15
19
  updated_at = models.DateTimeField(null=True, blank=True)
16
20
 
17
21
  class Meta:
18
- app_label = "tests"
22
+ app_label = 'tests'
19
23
  managed = False
20
24
 
25
+ def __str__(self) -> str:
26
+ return 'Product'
27
+
21
28
 
22
29
  class ProductSeeder(DjangoModelSeeder):
23
30
  model_class = Product
24
31
  fields = {
25
- "name": ('llm', 'Clothing store'),
26
- "description": ('static', 'A great product for testing.'),
27
- "price": ('faker', 'pydecimal', {'left_digits': 2, 'right_digits': 2, 'positive': True}),
28
- "in_stock": True,
29
- "created_at": ("callable", now),
30
- "updated_at": 'exclude',
32
+ 'name': ('llm', 'Clothing store'),
33
+ 'description': ('static', 'A great product for testing.'),
34
+ 'price': ('faker', 'pydecimal', {'left_digits': 2, 'right_digits': 2, 'positive': True}),
35
+ 'in_stock': True,
36
+ 'created_at': ('callable', now),
37
+ 'updated_at': 'exclude',
31
38
  }
32
39
 
33
40
 
34
- class TestProductSeeder(unittest.TestCase):
41
+ class TestProductSeeder(TestCase):
42
+ def test_assign_defaults(self) -> None:
43
+ assert 'sku' in ProductSeeder.resolved_fields()
44
+
45
+ def test_field_override_applies(self) -> None:
46
+ override_time = now()
47
+
48
+ result = ProductSeeder.seed(count=1, fields={'updated_at': ('static', override_time)})
35
49
 
36
- def test_assign_defaults(self):
37
- self.assertIn('sku', ProductSeeder.resolved_fields().keys())
50
+ assert result[0].updated_at == override_time
38
51
 
39
- def test_seeds_three_objects(self):
52
+ def test_raises_for_invalid_field(self) -> None:
53
+ with pytest.raises(Exception) as exc_info:
54
+ ProductSeeder.seed(count=1, fields={'not_a_field': ('static', 'value')})
55
+
56
+ assert 'not_a_field' in str(exc_info.value)
57
+
58
+ def test_seeds_three_objects(self) -> None:
40
59
  result = ProductSeeder.seed(count=3)
41
- self.assertEqual(len(result), 3)
42
- for obj in result:
43
- self.assertIsInstance(obj, Product)
44
60
 
45
- def test_raises_for_invalid_field(self):
46
- with self.assertRaises(Exception) as context:
47
- ProductSeeder.seed(count=1, fields={"not_a_field": ("static", "value")})
48
- self.assertIn("not_a_field", str(context.exception))
61
+ assert len(result) == 3
49
62
 
50
- def test_field_override_applies(self):
51
- override_time = now()
52
- result = ProductSeeder.seed(count=1, fields={"updated_at": ("static", override_time)})
53
- self.assertEqual(result[0].updated_at, override_time)
63
+ for obj in result:
64
+ assert isinstance(obj, Product)
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from enum import Enum
2
4
 
3
5
 
@@ -0,0 +1,71 @@
1
+ from __future__ import annotations
2
+
3
+ import pytest
4
+
5
+ from unittest.mock import MagicMock
6
+
7
+ from django.test import TestCase
8
+
9
+ from django_spire.contrib.seeding.model.config import FieldsConfig
10
+
11
+
12
+ class TestFieldsConfig(TestCase):
13
+ def setUp(self) -> None:
14
+ self.model_class = MagicMock()
15
+
16
+ def test_assign_defaults_adds_missing_fields(self) -> None:
17
+ config = FieldsConfig(
18
+ raw_fields={'name': ('static', 'test')},
19
+ field_names=['name', 'email'],
20
+ default_to='llm',
21
+ model_class=self.model_class
22
+ )
23
+
24
+ assert 'email' in config.fields
25
+ assert config.fields['email'] == ('llm',)
26
+
27
+ def test_excluded_property(self) -> None:
28
+ config = FieldsConfig(
29
+ raw_fields={'name': 'exclude', 'email': ('exclude',)},
30
+ field_names=['name', 'email', 'status'],
31
+ default_to='llm',
32
+ model_class=self.model_class
33
+ )
34
+
35
+ assert 'name' in config.excluded
36
+ assert 'email' in config.excluded
37
+ assert 'status' not in config.excluded
38
+
39
+ def test_fields_are_ordered(self) -> None:
40
+ config = FieldsConfig(
41
+ raw_fields={'zebra': ('static', 'z'), 'apple': ('static', 'a')},
42
+ field_names=['zebra', 'apple'],
43
+ default_to='included',
44
+ model_class=self.model_class
45
+ )
46
+
47
+ keys = list(config.fields.keys())
48
+
49
+ assert keys == ['apple', 'zebra']
50
+
51
+ def test_override_creates_new_config(self) -> None:
52
+ config = FieldsConfig(
53
+ raw_fields={'name': ('static', 'original')},
54
+ field_names=['name', 'email'],
55
+ default_to='llm',
56
+ model_class=self.model_class
57
+ )
58
+
59
+ new_config = config.override({'name': ('static', 'overridden')})
60
+
61
+ assert new_config is not config
62
+ assert new_config.fields['name'] == ('static', 'overridden')
63
+
64
+ def test_validate_raises_for_invalid_fields(self) -> None:
65
+ with pytest.raises(ValueError, match='Invalid field name'):
66
+ FieldsConfig(
67
+ raw_fields={'invalid_field': ('static', 'test')},
68
+ field_names=['name', 'email'],
69
+ default_to='llm',
70
+ model_class=self.model_class
71
+ )
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+
3
+ import pytest
4
+
5
+ from django.test import TestCase
6
+ from django.utils import timezone
7
+
8
+ from django_spire.contrib.seeding.field.custom import CustomFieldSeeder
9
+
10
+
11
+ class TestCustomFieldSeeder(TestCase):
12
+ def test_date_time_between_returns_aware_datetime(self) -> None:
13
+ seeder = CustomFieldSeeder(fields={})
14
+
15
+ result = seeder.date_time_between(start_date='-30d', end_date='now')
16
+
17
+ assert timezone.is_aware(result)
18
+
19
+ def test_in_order_cycles_through_values(self) -> None:
20
+ seeder = CustomFieldSeeder(fields={})
21
+ values = ['a', 'b', 'c']
22
+
23
+ assert seeder.in_order(values, index=0) == 'a'
24
+ assert seeder.in_order(values, index=1) == 'b'
25
+ assert seeder.in_order(values, index=2) == 'c'
26
+ assert seeder.in_order(values, index=3) == 'a'
27
+
28
+ def test_in_order_raises_for_empty_list(self) -> None:
29
+ seeder = CustomFieldSeeder(fields={})
30
+
31
+ with pytest.raises(ValueError, match='Cannot select from empty values list'):
32
+ seeder.in_order(values=[], index=0)
33
+
34
+ def test_keyword_is_custom(self) -> None:
35
+ assert CustomFieldSeeder.keyword == 'custom'
@@ -0,0 +1,40 @@
1
+ from __future__ import annotations
2
+
3
+ from django.test import TestCase
4
+
5
+ from django_spire.contrib.seeding.field.enums import FieldSeederTypesEnum
6
+ from django_spire.contrib.seeding.model.enums import ModelSeederDefaultsEnum
7
+
8
+
9
+ class TestFieldSeederTypesEnum(TestCase):
10
+ def test_callable_value(self) -> None:
11
+ assert FieldSeederTypesEnum.CALLABLE == 'callable'
12
+
13
+ def test_custom_value(self) -> None:
14
+ assert FieldSeederTypesEnum.CUSTOM == 'custom'
15
+
16
+ def test_faker_value(self) -> None:
17
+ assert FieldSeederTypesEnum.FAKER == 'faker'
18
+
19
+ def test_is_str_enum(self) -> None:
20
+ assert isinstance(FieldSeederTypesEnum.LLM, str)
21
+
22
+ def test_llm_value(self) -> None:
23
+ assert FieldSeederTypesEnum.LLM == 'llm'
24
+
25
+ def test_static_value(self) -> None:
26
+ assert FieldSeederTypesEnum.STATIC == 'static'
27
+
28
+
29
+ class TestModelSeederDefaultsEnum(TestCase):
30
+ def test_faker_value(self) -> None:
31
+ assert ModelSeederDefaultsEnum.FAKER == 'faker'
32
+
33
+ def test_included_value(self) -> None:
34
+ assert ModelSeederDefaultsEnum.INCLUDED == 'included'
35
+
36
+ def test_is_str_enum(self) -> None:
37
+ assert isinstance(ModelSeederDefaultsEnum.LLM, str)
38
+
39
+ def test_llm_value(self) -> None:
40
+ assert ModelSeederDefaultsEnum.LLM == 'llm'