django-spire 0.23.7__py3-none-any.whl → 0.23.9__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 (542) hide show
  1. django_spire/ai/admin.py +11 -11
  2. django_spire/ai/chat/apps.py +1 -0
  3. django_spire/ai/chat/templates/django_spire/ai/chat/widget/dialog_widget.html +1 -1
  4. django_spire/ai/chat/tests/factories.py +15 -0
  5. django_spire/ai/chat/tests/test_controller.py +45 -0
  6. django_spire/ai/chat/tests/test_models.py +301 -0
  7. django_spire/ai/chat/tests/test_prompts.py +48 -0
  8. django_spire/ai/chat/tests/test_responses.py +208 -0
  9. django_spire/ai/chat/tests/test_router/test_base_chat_router.py +66 -6
  10. django_spire/ai/chat/tests/test_router/test_chat_workflow.py +73 -3
  11. django_spire/ai/chat/tests/test_router/test_integration.py +86 -6
  12. django_spire/ai/chat/tests/test_router/test_intent_decoder.py +93 -1
  13. django_spire/ai/chat/tests/test_router/test_message_intel.py +60 -1
  14. django_spire/ai/chat/tests/test_router/test_spire_chat_router.py +110 -0
  15. django_spire/ai/chat/tests/test_urls/test_json_urls.py +202 -1
  16. django_spire/ai/context/tests/__init__.py +0 -0
  17. django_spire/ai/context/tests/test_context.py +188 -0
  18. django_spire/ai/decorators.py +7 -6
  19. django_spire/ai/prompt/tests/test_bots.py +100 -10
  20. django_spire/ai/prompt/tests/test_prompt_intel.py +83 -0
  21. django_spire/ai/prompt/tests/test_prompt_tuning.py +126 -0
  22. django_spire/ai/sms/decorators.py +8 -2
  23. django_spire/ai/sms/tests/test_sms.py +240 -16
  24. django_spire/ai/sms/tests/test_sms_intel.py +42 -0
  25. django_spire/ai/sms/tests/test_webhook.py +155 -7
  26. django_spire/ai/sms/views.py +23 -24
  27. django_spire/ai/tests/test_ai.py +131 -7
  28. django_spire/auth/apps.py +4 -2
  29. django_spire/auth/controller/controller.py +36 -23
  30. django_spire/auth/controller/exceptions.py +9 -0
  31. django_spire/auth/group/admin.py +1 -0
  32. django_spire/auth/group/apps.py +2 -0
  33. django_spire/auth/group/factories.py +17 -8
  34. django_spire/auth/group/forms.py +7 -0
  35. django_spire/auth/group/tests/test_factories.py +146 -0
  36. django_spire/auth/group/tests/test_forms.py +282 -0
  37. django_spire/auth/group/tests/test_models.py +192 -0
  38. django_spire/auth/group/tests/test_querysets.py +98 -0
  39. django_spire/auth/group/tests/test_utils.py +341 -0
  40. django_spire/auth/group/tests/test_views.py +377 -0
  41. django_spire/auth/group/urls/__init__.py +3 -1
  42. django_spire/auth/group/urls/form_urls.py +2 -0
  43. django_spire/auth/group/urls/json_urls.py +3 -0
  44. django_spire/auth/group/urls/page_urls.py +2 -0
  45. django_spire/auth/group/utils.py +6 -2
  46. django_spire/auth/group/views/form_views.py +6 -3
  47. django_spire/auth/group/views/json_views.py +6 -2
  48. django_spire/auth/mfa/admin.py +2 -0
  49. django_spire/auth/mfa/apps.py +2 -0
  50. django_spire/auth/mfa/forms.py +1 -0
  51. django_spire/auth/mfa/querysets.py +9 -2
  52. django_spire/auth/mfa/tests/test_models.py +233 -0
  53. django_spire/auth/mfa/tests/test_utils.py +106 -0
  54. django_spire/auth/mfa/urls/__init__.py +2 -0
  55. django_spire/auth/mfa/urls/page_urls.py +2 -0
  56. django_spire/auth/mfa/urls/redirect_urls.py +2 -0
  57. django_spire/auth/mfa/views/page_views.py +2 -1
  58. django_spire/auth/permissions/consts.py +2 -2
  59. django_spire/auth/permissions/decorators.py +8 -8
  60. django_spire/auth/permissions/permissions.py +28 -35
  61. django_spire/auth/permissions/tests/test_decorators.py +333 -0
  62. django_spire/auth/permissions/tests/test_permissions.py +337 -0
  63. django_spire/auth/permissions/tests/test_tools.py +305 -0
  64. django_spire/auth/permissions/tools.py +21 -15
  65. django_spire/auth/seeding/seed.py +3 -0
  66. django_spire/auth/seeding/seeder.py +2 -0
  67. django_spire/auth/tests/test_controller.py +323 -0
  68. django_spire/auth/tests/test_url_endpoints.py +9 -9
  69. django_spire/auth/tests/test_views.py +406 -0
  70. django_spire/auth/urls/admin_urls.py +2 -0
  71. django_spire/auth/urls/redirect_urls.py +2 -0
  72. django_spire/auth/user/apps.py +2 -0
  73. django_spire/auth/user/forms.py +9 -0
  74. django_spire/auth/user/models.py +1 -1
  75. django_spire/auth/user/services/services.py +1 -0
  76. django_spire/auth/user/tests/factories.py +14 -13
  77. django_spire/auth/user/tests/test_factories.py +166 -2
  78. django_spire/auth/user/tests/test_forms.py +573 -0
  79. django_spire/auth/user/tests/test_models.py +257 -0
  80. django_spire/auth/user/tests/test_services.py +200 -0
  81. django_spire/auth/user/tests/test_tools.py +153 -0
  82. django_spire/auth/user/tests/test_user_factories.py +139 -0
  83. django_spire/auth/user/tests/test_views.py +363 -0
  84. django_spire/auth/user/tools.py +7 -1
  85. django_spire/auth/user/urls/form_urls.py +3 -0
  86. django_spire/auth/user/urls/page_urls.py +3 -0
  87. django_spire/auth/user/views/form_views.py +19 -10
  88. django_spire/auth/user/views/page_views.py +8 -2
  89. django_spire/auth/views/redirect_views.py +14 -9
  90. django_spire/comment/admin.py +2 -0
  91. django_spire/comment/apps.py +2 -0
  92. django_spire/comment/templatetags/comment_tags.py +1 -0
  93. django_spire/comment/tests/test_forms.py +27 -0
  94. django_spire/comment/tests/test_models.py +215 -0
  95. django_spire/comment/tests/test_querysets.py +101 -0
  96. django_spire/comment/tests/test_utils.py +90 -0
  97. django_spire/comment/urls.py +2 -0
  98. django_spire/comment/utils.py +22 -13
  99. django_spire/comment/views.py +1 -1
  100. django_spire/conf.py +8 -6
  101. django_spire/consts.py +1 -1
  102. django_spire/contrib/breadcrumb/apps.py +2 -0
  103. django_spire/contrib/breadcrumb/breadcrumbs.py +18 -18
  104. django_spire/contrib/breadcrumb/tests/test_breadcrumbs.py +198 -0
  105. django_spire/contrib/constructor/__init__.py +3 -3
  106. django_spire/contrib/constructor/constructor.py +15 -15
  107. django_spire/contrib/constructor/django_model_constructor.py +5 -4
  108. django_spire/contrib/constructor/exceptions.py +5 -3
  109. django_spire/contrib/constructor/tests/__init__.py +0 -0
  110. django_spire/contrib/constructor/tests/test_constructor.py +193 -0
  111. django_spire/contrib/form/tests/__init__.py +0 -0
  112. django_spire/contrib/form/tests/test_forms.py +203 -0
  113. django_spire/contrib/generic_views/modal_views.py +2 -1
  114. django_spire/contrib/generic_views/portal_views.py +20 -19
  115. django_spire/contrib/generic_views/tests/__init__.py +0 -0
  116. django_spire/contrib/generic_views/tests/test_views.py +459 -0
  117. django_spire/contrib/help/apps.py +2 -0
  118. django_spire/contrib/help/templatetags/help.py +1 -0
  119. django_spire/contrib/help/tests/__init__.py +0 -0
  120. django_spire/contrib/help/tests/test_templatetags.py +100 -0
  121. django_spire/contrib/options/mixins.py +6 -5
  122. django_spire/contrib/options/tests/factories.py +5 -1
  123. django_spire/contrib/options/tests/test_options.py +234 -0
  124. django_spire/contrib/ordering/exceptions.py +7 -3
  125. django_spire/contrib/ordering/mixins.py +2 -0
  126. django_spire/contrib/ordering/querysets.py +3 -1
  127. django_spire/contrib/ordering/services/processor_service.py +8 -4
  128. django_spire/contrib/ordering/services/service.py +1 -2
  129. django_spire/contrib/ordering/tests/__init__.py +0 -0
  130. django_spire/contrib/ordering/tests/test_ordering.py +165 -0
  131. django_spire/contrib/ordering/validators.py +6 -6
  132. django_spire/contrib/pagination/templatetags/pagination_tags.py +12 -5
  133. django_spire/contrib/pagination/tests/__init__.py +0 -0
  134. django_spire/contrib/pagination/tests/test_pagination.py +179 -0
  135. django_spire/contrib/performance/decorators.py +16 -6
  136. django_spire/contrib/performance/tests/__init__.py +0 -0
  137. django_spire/contrib/performance/tests/test_performance.py +107 -0
  138. django_spire/contrib/queryset/enums.py +3 -1
  139. django_spire/contrib/queryset/filter_tools.py +10 -5
  140. django_spire/contrib/queryset/mixins.py +16 -16
  141. django_spire/contrib/queryset/tests/__init__.py +0 -0
  142. django_spire/contrib/queryset/tests/test_queryset.py +137 -0
  143. django_spire/contrib/seeding/field/base.py +13 -7
  144. django_spire/contrib/seeding/field/callable.py +8 -1
  145. django_spire/contrib/seeding/field/cleaners.py +5 -5
  146. django_spire/contrib/seeding/field/custom.py +20 -10
  147. django_spire/contrib/seeding/field/django/seeder.py +8 -6
  148. django_spire/contrib/seeding/field/enums.py +7 -5
  149. django_spire/contrib/seeding/field/override.py +16 -6
  150. django_spire/contrib/seeding/field/static.py +9 -2
  151. django_spire/contrib/seeding/field/tests/test_base.py +18 -14
  152. django_spire/contrib/seeding/field/tests/test_callable.py +13 -9
  153. django_spire/contrib/seeding/field/tests/test_cleaners.py +51 -38
  154. django_spire/contrib/seeding/field/tests/test_static.py +13 -9
  155. django_spire/contrib/seeding/intelligence/bots/seeder_generator_bot.py +2 -0
  156. django_spire/contrib/seeding/intelligence/intel.py +5 -1
  157. django_spire/contrib/seeding/intelligence/prompts/factory.py +6 -1
  158. django_spire/contrib/seeding/intelligence/prompts/foreign_key_selection_prompt.py +6 -1
  159. django_spire/contrib/seeding/intelligence/prompts/generate_django_model_seeder_prompts.py +2 -0
  160. django_spire/contrib/seeding/intelligence/prompts/generic_relationship_selection_prompt.py +7 -1
  161. django_spire/contrib/seeding/intelligence/prompts/hierarchical_selection_prompt.py +6 -2
  162. django_spire/contrib/seeding/intelligence/prompts/model_field_choices_prompt.py +8 -2
  163. django_spire/contrib/seeding/intelligence/prompts/negation_prompt.py +2 -0
  164. django_spire/contrib/seeding/intelligence/prompts/objective_prompt.py +6 -1
  165. django_spire/contrib/seeding/management/commands/seeding.py +9 -3
  166. django_spire/contrib/seeding/management/example.py +2 -0
  167. django_spire/contrib/seeding/model/base.py +16 -7
  168. django_spire/contrib/seeding/model/config.py +31 -15
  169. django_spire/contrib/seeding/model/django/config.py +13 -13
  170. django_spire/contrib/seeding/model/django/seeder.py +4 -4
  171. django_spire/contrib/seeding/model/django/tests/test_seeder.py +34 -23
  172. django_spire/contrib/seeding/model/enums.py +2 -0
  173. django_spire/contrib/seeding/tests/test_config.py +71 -0
  174. django_spire/contrib/seeding/tests/test_custom.py +35 -0
  175. django_spire/contrib/seeding/tests/test_enums.py +40 -0
  176. django_spire/contrib/seeding/tests/test_intel.py +32 -0
  177. django_spire/contrib/seeding/tests/test_override.py +63 -0
  178. django_spire/contrib/service/__init__.py +2 -2
  179. django_spire/contrib/service/django_model_service.py +16 -15
  180. django_spire/contrib/service/exceptions.py +5 -3
  181. django_spire/contrib/service/tests/__init__.py +0 -0
  182. django_spire/contrib/service/tests/test_service.py +153 -0
  183. django_spire/contrib/session/apps.py +2 -0
  184. django_spire/contrib/session/controller.py +48 -42
  185. django_spire/contrib/session/templatetags/session_tags.py +11 -2
  186. django_spire/contrib/session/tests/test_session_controller.py +117 -53
  187. django_spire/contrib/tests/__init__.py +0 -0
  188. django_spire/contrib/tests/test_utils.py +37 -0
  189. django_spire/contrib/utils.py +4 -1
  190. django_spire/core/apps.py +2 -0
  191. django_spire/core/converters/tests/test_to_data.py +353 -0
  192. django_spire/core/converters/tests/test_to_enums.py +61 -41
  193. django_spire/core/converters/tests/test_to_pydantic.py +138 -109
  194. django_spire/core/converters/to_data.py +29 -10
  195. django_spire/core/converters/to_enums.py +4 -2
  196. django_spire/core/converters/to_pydantic.py +22 -22
  197. django_spire/core/decorators.py +19 -6
  198. django_spire/core/forms/widgets.py +4 -0
  199. django_spire/core/management/commands/spire_startapp_pkg/template/app/apps.py.template +2 -0
  200. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/__init__.py.template +2 -0
  201. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/form_urls.py.template +2 -0
  202. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/page_urls.py.template +2 -0
  203. django_spire/core/maps.py +3 -1
  204. django_spire/core/middleware/maintenance.py +3 -3
  205. django_spire/core/middleware.py +8 -6
  206. django_spire/core/redirect/__init__.py +5 -0
  207. django_spire/core/redirect/generic_redirect.py +1 -2
  208. django_spire/core/redirect/tests/__init__.py +0 -0
  209. django_spire/core/redirect/tests/test_generic_redirect.py +34 -0
  210. django_spire/core/{tests/tests_redirect.py → redirect/tests/test_safe_redirect.py} +55 -81
  211. django_spire/core/shortcuts.py +3 -3
  212. django_spire/core/static/django_spire/css/app-layout.css +1 -1
  213. django_spire/core/static/django_spire/css/app-navigation.css +3 -3
  214. django_spire/core/static/django_spire/css/bootstrap-override.css +4 -0
  215. django_spire/core/tag/admin.py +12 -0
  216. django_spire/core/tag/intelligence/tag_set_bot.py +2 -0
  217. django_spire/core/tag/mixins.py +2 -0
  218. django_spire/core/tag/models.py +2 -0
  219. django_spire/core/tag/querysets.py +2 -0
  220. django_spire/core/tag/service/tag_service.py +6 -3
  221. django_spire/core/tag/tests/test_intelligence.py +9 -9
  222. django_spire/core/tag/tests/test_tags.py +44 -54
  223. django_spire/core/tag/tests/test_tools.py +191 -0
  224. django_spire/core/tag/tools.py +3 -0
  225. django_spire/core/templates/django_spire/card/card.html +5 -2
  226. django_spire/core/templates/django_spire/card/title_card.html +12 -4
  227. django_spire/core/templates/django_spire/infinite_scroll/base.html +1 -0
  228. django_spire/core/templates/django_spire/navigation/side_navigation.html +19 -24
  229. django_spire/core/templates/django_spire/page/full_page.html +46 -16
  230. django_spire/core/templates/django_spire/table/base.html +5 -3
  231. django_spire/core/templatetags/json.py +6 -2
  232. django_spire/core/templatetags/message.py +13 -8
  233. django_spire/core/templatetags/string_formating.py +8 -5
  234. django_spire/core/templatetags/tests/__init__.py +0 -0
  235. django_spire/core/templatetags/tests/test_templatetags.py +427 -0
  236. django_spire/core/templatetags/variable_types.py +17 -9
  237. django_spire/core/tests/test_cases.py +1 -1
  238. django_spire/core/tests/test_conf.py +43 -0
  239. django_spire/core/tests/test_consts.py +28 -0
  240. django_spire/core/tests/test_context_processors.py +93 -0
  241. django_spire/core/tests/test_decorators.py +95 -0
  242. django_spire/core/tests/test_django_spire_utils.py +56 -0
  243. django_spire/core/tests/test_exceptions.py +37 -0
  244. django_spire/core/tests/test_models.py +54 -0
  245. django_spire/core/tests/test_settings.py +45 -0
  246. django_spire/core/tests/test_shortcuts.py +74 -0
  247. django_spire/core/tests/test_urls.py +16 -0
  248. django_spire/core/tests/test_utils.py +58 -0
  249. django_spire/core/urls.py +4 -1
  250. django_spire/core/utils.py +12 -8
  251. django_spire/exceptions.py +16 -1
  252. django_spire/file/admin.py +4 -2
  253. django_spire/file/apps.py +8 -10
  254. django_spire/file/fields.py +7 -7
  255. django_spire/file/forms.py +1 -1
  256. django_spire/file/interfaces.py +15 -15
  257. django_spire/file/mixins.py +1 -4
  258. django_spire/file/models.py +3 -5
  259. django_spire/file/tests/factories.py +59 -0
  260. django_spire/file/tests/test_admin.py +69 -0
  261. django_spire/file/tests/test_apps.py +24 -0
  262. django_spire/file/tests/test_fields.py +114 -0
  263. django_spire/file/tests/test_forms.py +20 -0
  264. django_spire/file/tests/test_interfaces.py +183 -0
  265. django_spire/file/tests/test_models.py +82 -0
  266. django_spire/file/tests/test_querysets.py +102 -0
  267. django_spire/file/tests/test_utils.py +32 -0
  268. django_spire/file/tests/test_views.py +145 -0
  269. django_spire/file/tests/test_widgets.py +82 -0
  270. django_spire/file/tools.py +8 -2
  271. django_spire/file/views.py +7 -3
  272. django_spire/file/widgets.py +12 -12
  273. django_spire/help_desk/admin.py +15 -0
  274. django_spire/help_desk/apps.py +2 -0
  275. django_spire/help_desk/auth/controller.py +2 -0
  276. django_spire/help_desk/choices.py +2 -0
  277. django_spire/help_desk/enums.py +2 -0
  278. django_spire/help_desk/exceptions.py +31 -3
  279. django_spire/help_desk/forms.py +2 -0
  280. django_spire/help_desk/models.py +2 -0
  281. django_spire/help_desk/querysets.py +4 -1
  282. django_spire/help_desk/services/notification_service.py +26 -27
  283. django_spire/help_desk/services/service.py +2 -3
  284. django_spire/help_desk/tests/factories.py +8 -3
  285. django_spire/help_desk/tests/test_admin.py +41 -0
  286. django_spire/help_desk/tests/test_apps.py +41 -0
  287. django_spire/help_desk/tests/test_choices.py +50 -0
  288. django_spire/help_desk/tests/test_controller.py +87 -0
  289. django_spire/help_desk/tests/test_enums.py +18 -0
  290. django_spire/help_desk/tests/test_exceptions.py +37 -0
  291. django_spire/help_desk/tests/test_forms.py +89 -0
  292. django_spire/help_desk/tests/test_models.py +59 -0
  293. django_spire/help_desk/tests/test_querysets.py +38 -0
  294. django_spire/help_desk/tests/test_services/test_notification_service.py +15 -8
  295. django_spire/help_desk/tests/test_services/test_service.py +92 -0
  296. django_spire/help_desk/tests/test_urls/test_form_urls.py +6 -6
  297. django_spire/help_desk/tests/test_urls/test_page_urls.py +8 -9
  298. django_spire/help_desk/tests/test_views/test_form_views.py +46 -19
  299. django_spire/help_desk/tests/test_views/test_page_views.py +32 -9
  300. django_spire/help_desk/urls/__init__.py +4 -1
  301. django_spire/help_desk/urls/form_urls.py +3 -0
  302. django_spire/help_desk/urls/page_urls.py +3 -0
  303. django_spire/help_desk/views/form_views.py +13 -5
  304. django_spire/help_desk/views/page_views.py +11 -3
  305. django_spire/history/activity/admin.py +2 -0
  306. django_spire/history/activity/apps.py +3 -1
  307. django_spire/history/activity/mixins.py +13 -7
  308. django_spire/history/activity/models.py +6 -5
  309. django_spire/history/activity/querysets.py +2 -0
  310. django_spire/history/activity/tests/__init__.py +0 -0
  311. django_spire/history/activity/tests/test_activity.py +176 -0
  312. django_spire/history/admin.py +9 -2
  313. django_spire/history/choices.py +3 -0
  314. django_spire/history/models.py +5 -5
  315. django_spire/history/tests/test_admin.py +93 -0
  316. django_spire/history/tests/test_history.py +101 -0
  317. django_spire/history/tests/test_mixins.py +84 -0
  318. django_spire/history/viewed/admin.py +3 -1
  319. django_spire/history/viewed/apps.py +3 -1
  320. django_spire/history/viewed/models.py +2 -0
  321. django_spire/history/viewed/tests/__init__.py +0 -0
  322. django_spire/history/viewed/tests/test_viewed.py +46 -0
  323. django_spire/knowledge/auth/tests/__init__.py +0 -0
  324. django_spire/knowledge/auth/tests/test_controller.py +116 -0
  325. django_spire/knowledge/collection/admin.py +5 -1
  326. django_spire/knowledge/collection/models.py +3 -1
  327. django_spire/knowledge/collection/seeding/seed.py +1 -0
  328. django_spire/knowledge/collection/services/factory_service.py +10 -11
  329. django_spire/knowledge/collection/services/ordering_service.py +1 -2
  330. django_spire/knowledge/collection/services/service.py +5 -10
  331. django_spire/knowledge/collection/services/tag_service.py +5 -2
  332. django_spire/knowledge/collection/tests/factories.py +28 -1
  333. django_spire/knowledge/collection/tests/test_models.py +48 -0
  334. django_spire/knowledge/collection/tests/test_querysets.py +93 -0
  335. django_spire/knowledge/collection/tests/test_services/test_factory_service.py +100 -0
  336. django_spire/knowledge/collection/tests/test_services/test_services.py +160 -0
  337. django_spire/knowledge/collection/tests/test_urls/test_form_urls.py +21 -3
  338. django_spire/knowledge/collection/tests/test_urls/test_json_urls.py +39 -1
  339. django_spire/knowledge/collection/tests/test_urls/test_page_urls.py +12 -4
  340. django_spire/knowledge/collection/urls/__init__.py +3 -0
  341. django_spire/knowledge/collection/urls/form_urls.py +2 -0
  342. django_spire/knowledge/collection/urls/json_urls.py +2 -0
  343. django_spire/knowledge/collection/urls/page_urls.py +2 -0
  344. django_spire/knowledge/collection/views/form_views.py +4 -4
  345. django_spire/knowledge/collection/views/json_views.py +5 -1
  346. django_spire/knowledge/collection/views/page_views.py +5 -2
  347. django_spire/knowledge/entry/admin.py +7 -1
  348. django_spire/knowledge/entry/forms.py +2 -0
  349. django_spire/knowledge/entry/models.py +2 -0
  350. django_spire/knowledge/entry/seeding/seed.py +3 -0
  351. django_spire/knowledge/entry/services/automation_service.py +5 -4
  352. django_spire/knowledge/entry/services/factory_service.py +7 -5
  353. django_spire/knowledge/entry/services/service.py +4 -7
  354. django_spire/knowledge/entry/services/tag_service.py +0 -1
  355. django_spire/knowledge/entry/services/tool_service.py +1 -0
  356. django_spire/knowledge/entry/services/transformation_services.py +1 -5
  357. django_spire/knowledge/entry/tests/factories.py +1 -2
  358. django_spire/knowledge/entry/tests/test_factory_service.py +20 -0
  359. django_spire/knowledge/entry/tests/test_models.py +41 -0
  360. django_spire/knowledge/entry/tests/test_querysets.py +71 -0
  361. django_spire/knowledge/entry/tests/test_services.py +94 -0
  362. django_spire/knowledge/entry/tests/test_urls/test_form_urls.py +9 -14
  363. django_spire/knowledge/entry/tests/test_urls/test_json_urls.py +48 -5
  364. django_spire/knowledge/entry/tests/test_urls/test_page_urls.py +6 -8
  365. django_spire/knowledge/entry/tests/test_urls/test_template_urls.py +40 -0
  366. django_spire/knowledge/entry/urls/form_urls.py +2 -0
  367. django_spire/knowledge/entry/urls/json_urls.py +2 -0
  368. django_spire/knowledge/entry/urls/page_urls.py +2 -0
  369. django_spire/knowledge/entry/urls/template_urls.py +2 -0
  370. django_spire/knowledge/entry/version/block/choices.py +2 -0
  371. django_spire/knowledge/entry/version/block/data/data.py +1 -0
  372. django_spire/knowledge/entry/version/block/data/list/data.py +8 -13
  373. django_spire/knowledge/entry/version/block/data/list/maps.py +3 -0
  374. django_spire/knowledge/entry/version/block/data/list/meta.py +1 -2
  375. django_spire/knowledge/entry/version/block/data/list/tests/__init__.py +0 -0
  376. django_spire/knowledge/entry/version/block/data/list/tests/test_maps.py +32 -0
  377. django_spire/knowledge/entry/version/block/data/list/tests/test_meta.py +58 -0
  378. django_spire/knowledge/entry/version/block/data/maps.py +3 -6
  379. django_spire/knowledge/entry/version/block/models.py +7 -5
  380. django_spire/knowledge/entry/version/block/seeding/constants.py +5 -4
  381. django_spire/knowledge/entry/version/block/services/service.py +2 -3
  382. django_spire/knowledge/entry/version/block/tests/factories.py +4 -10
  383. django_spire/knowledge/entry/version/block/tests/test_choices.py +56 -0
  384. django_spire/knowledge/entry/version/block/tests/test_data.py +90 -0
  385. django_spire/knowledge/entry/version/block/tests/test_maps.py +37 -0
  386. django_spire/knowledge/entry/version/block/tests/test_models.py +55 -0
  387. django_spire/knowledge/entry/version/block/tests/test_querysets.py +35 -0
  388. django_spire/knowledge/entry/version/block/tests/test_services.py +65 -0
  389. django_spire/knowledge/entry/version/choices.py +2 -0
  390. django_spire/knowledge/entry/version/converters/converter.py +1 -1
  391. django_spire/knowledge/entry/version/converters/docx_converter.py +4 -7
  392. django_spire/knowledge/entry/version/converters/markdown_converter.py +20 -20
  393. django_spire/knowledge/entry/version/maps.py +4 -5
  394. django_spire/knowledge/entry/version/querysets.py +1 -1
  395. django_spire/knowledge/entry/version/seeding/seeder.py +1 -2
  396. django_spire/knowledge/entry/version/services/processor_service.py +5 -4
  397. django_spire/knowledge/entry/version/services/service.py +1 -2
  398. django_spire/knowledge/entry/version/tests/factories.py +2 -2
  399. django_spire/knowledge/entry/version/tests/test_choices.py +18 -0
  400. django_spire/knowledge/entry/version/tests/test_converters/test_docx_converter.py +56 -8
  401. django_spire/knowledge/entry/version/tests/test_converters/test_markdown_converter.py +78 -0
  402. django_spire/knowledge/entry/version/tests/test_maps.py +58 -0
  403. django_spire/knowledge/entry/version/tests/test_models.py +23 -0
  404. django_spire/knowledge/entry/version/tests/test_querysets.py +26 -0
  405. django_spire/knowledge/entry/version/tests/test_services.py +62 -0
  406. django_spire/knowledge/entry/version/tests/test_urls/test_json_urls.py +27 -8
  407. django_spire/knowledge/entry/version/tests/test_urls/test_page_urls.py +15 -8
  408. django_spire/knowledge/entry/version/tests/test_urls/test_redirect_urls.py +38 -0
  409. django_spire/knowledge/entry/version/urls/__init__.py +3 -0
  410. django_spire/knowledge/entry/version/urls/json_urls.py +2 -1
  411. django_spire/knowledge/entry/version/urls/page_urls.py +2 -0
  412. django_spire/knowledge/entry/version/urls/redirect_urls.py +2 -0
  413. django_spire/knowledge/entry/version/views/json_views.py +5 -1
  414. django_spire/knowledge/entry/version/views/page_views.py +10 -3
  415. django_spire/knowledge/entry/version/views/redirect_views.py +5 -1
  416. django_spire/knowledge/entry/views/form_views.py +16 -8
  417. django_spire/knowledge/entry/views/json_views.py +3 -1
  418. django_spire/knowledge/entry/views/page_views.py +8 -2
  419. django_spire/knowledge/entry/views/template_views.py +7 -1
  420. django_spire/knowledge/exceptions.py +2 -1
  421. django_spire/knowledge/intelligence/intel/answer_intel.py +2 -1
  422. django_spire/knowledge/intelligence/intel/entry_intel.py +0 -1
  423. django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +4 -5
  424. django_spire/knowledge/models.py +1 -2
  425. django_spire/knowledge/tests/__init__.py +0 -0
  426. django_spire/knowledge/tests/test_templatetags.py +40 -0
  427. django_spire/knowledge/tests/test_urls/__init__.py +0 -0
  428. django_spire/knowledge/tests/test_urls/test_page_urls.py +24 -0
  429. django_spire/knowledge/urls/__init__.py +2 -0
  430. django_spire/knowledge/urls/page_urls.py +2 -0
  431. django_spire/knowledge/views/page_views.py +8 -3
  432. django_spire/notification/admin.py +3 -1
  433. django_spire/notification/app/admin.py +2 -0
  434. django_spire/notification/app/apps.py +3 -1
  435. django_spire/notification/app/exceptions.py +9 -2
  436. django_spire/notification/app/models.py +8 -4
  437. django_spire/notification/app/processor.py +22 -26
  438. django_spire/notification/app/querysets.py +2 -0
  439. django_spire/notification/app/tests/__init__.py +0 -0
  440. django_spire/notification/app/tests/factories.py +34 -0
  441. django_spire/notification/app/tests/test_apps.py +24 -0
  442. django_spire/notification/app/tests/test_models.py +72 -0
  443. django_spire/notification/app/tests/test_processor.py +111 -0
  444. django_spire/notification/app/tests/test_querysets.py +90 -0
  445. django_spire/notification/app/tests/test_views/__init__.py +0 -0
  446. django_spire/notification/app/tests/test_views/test_json_views.py +48 -0
  447. django_spire/notification/app/tests/test_views/test_page_views.py +19 -0
  448. django_spire/notification/app/urls/__init__.py +3 -1
  449. django_spire/notification/app/urls/json_urls.py +6 -4
  450. django_spire/notification/app/urls/page_urls.py +4 -3
  451. django_spire/notification/app/urls/template_urls.py +4 -2
  452. django_spire/notification/apps.py +4 -1
  453. django_spire/notification/email/admin.py +5 -1
  454. django_spire/notification/email/apps.py +3 -1
  455. django_spire/notification/email/exceptions.py +4 -2
  456. django_spire/notification/email/helper.py +5 -3
  457. django_spire/notification/email/models.py +4 -0
  458. django_spire/notification/email/processor.py +19 -15
  459. django_spire/notification/email/querysets.py +3 -0
  460. django_spire/notification/email/tests/__init__.py +0 -0
  461. django_spire/notification/email/tests/factories.py +35 -0
  462. django_spire/notification/email/tests/test_apps.py +24 -0
  463. django_spire/notification/email/tests/test_models.py +52 -0
  464. django_spire/notification/email/tests/test_processor.py +92 -0
  465. django_spire/notification/email/tests/test_querysets.py +43 -0
  466. django_spire/notification/exceptions.py +17 -2
  467. django_spire/notification/managers.py +7 -1
  468. django_spire/notification/maps.py +4 -1
  469. django_spire/notification/mixins.py +2 -0
  470. django_spire/notification/models.py +3 -1
  471. django_spire/notification/processors/notification.py +12 -5
  472. django_spire/notification/processors/processor.py +2 -0
  473. django_spire/notification/processors/tests/__init__.py +0 -0
  474. django_spire/notification/processors/tests/test_notification.py +106 -0
  475. django_spire/notification/push/admin.py +10 -1
  476. django_spire/notification/push/apps.py +3 -1
  477. django_spire/notification/push/models.py +2 -3
  478. django_spire/notification/push/tests/__init__.py +0 -0
  479. django_spire/notification/push/tests/test_apps.py +24 -0
  480. django_spire/notification/push/tests/test_models.py +28 -0
  481. django_spire/notification/querysets.py +7 -1
  482. django_spire/notification/sms/admin.py +2 -0
  483. django_spire/notification/sms/apps.py +4 -1
  484. django_spire/notification/sms/automations.py +2 -0
  485. django_spire/notification/sms/choices.py +2 -0
  486. django_spire/notification/sms/exceptions.py +19 -5
  487. django_spire/notification/sms/helper.py +33 -23
  488. django_spire/notification/sms/models.py +5 -1
  489. django_spire/notification/sms/processor.py +20 -20
  490. django_spire/notification/sms/querysets.py +2 -0
  491. django_spire/notification/sms/tests/factories.py +33 -0
  492. django_spire/notification/sms/tests/test_apps.py +24 -0
  493. django_spire/notification/sms/tests/test_automation.py +38 -0
  494. django_spire/notification/sms/tests/test_choices.py +15 -0
  495. django_spire/notification/sms/tests/test_consts.py +17 -0
  496. django_spire/notification/sms/tests/test_exceptions.py +27 -0
  497. django_spire/notification/sms/tests/test_helper.py +50 -0
  498. django_spire/notification/sms/tests/test_models.py +81 -0
  499. django_spire/notification/sms/tests/test_processor.py +107 -0
  500. django_spire/notification/sms/tests/test_tools.py +25 -11
  501. django_spire/notification/sms/tools.py +16 -5
  502. django_spire/notification/sms/urls/__init__.py +3 -1
  503. django_spire/notification/sms/urls/media_urls.py +2 -0
  504. django_spire/notification/sms/views/media_views.py +14 -4
  505. django_spire/notification/tests/__init__.py +0 -0
  506. django_spire/notification/tests/factories.py +26 -0
  507. django_spire/notification/tests/test_admin.py +55 -0
  508. django_spire/notification/tests/test_apps.py +30 -0
  509. django_spire/notification/tests/test_automation.py +18 -0
  510. django_spire/notification/tests/test_choices.py +59 -0
  511. django_spire/notification/tests/test_exceptions.py +58 -0
  512. django_spire/notification/tests/test_managers.py +100 -0
  513. django_spire/notification/tests/test_maps.py +31 -0
  514. django_spire/notification/tests/test_models.py +76 -0
  515. django_spire/notification/tests/test_querysets.py +184 -0
  516. django_spire/notification/tests/test_utils.py +23 -0
  517. django_spire/notification/urls.py +3 -1
  518. django_spire/notification/utils.py +3 -1
  519. django_spire/settings.py +3 -0
  520. django_spire/theme/tests/test_context_processor.py +15 -13
  521. django_spire/theme/tests/test_enums.py +2 -2
  522. django_spire/theme/tests/test_filesystem.py +2 -5
  523. django_spire/theme/tests/test_integration.py +12 -12
  524. django_spire/theme/tests/test_model.py +40 -38
  525. django_spire/theme/tests/test_views/test_json_views.py +33 -33
  526. django_spire/theme/urls/json_urls.py +3 -0
  527. django_spire/theme/urls/page_urls.py +3 -0
  528. django_spire/urls.py +19 -15
  529. django_spire/utils.py +13 -4
  530. {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/METADATA +2 -2
  531. {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/RECORD +534 -362
  532. {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/licenses/LICENSE.md +1 -1
  533. django_spire/contrib/options/tests/test_unit.py +0 -148
  534. django_spire/contrib/seeding/tests/test_seeding.py +0 -25
  535. django_spire/core/tests/test_templatetags.py +0 -117
  536. django_spire/core/tests/tests_shortcuts.py +0 -73
  537. django_spire/history/activity/tests.py +0 -3
  538. django_spire/history/activity/views.py +0 -3
  539. django_spire/knowledge/collection/tests/test_services/test_transformation_service.py +0 -71
  540. django_spire/notification/app/tests.py +0 -3
  541. {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/WHEEL +0 -0
  542. {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import json
4
+
5
+ from django.urls import reverse
6
+
3
7
  from django_spire.ai.chat.tests.test_urls.factories import create_test_chat
4
8
  from django_spire.core.tests.test_cases import BaseTestCase
5
- from django.urls import reverse
6
9
 
7
10
 
8
11
  class ChatJsonUrlTests(BaseTestCase):
@@ -21,3 +24,201 @@ class ChatJsonUrlTests(BaseTestCase):
21
24
  )
22
25
 
23
26
  assert response.status_code == 200
27
+
28
+ def test_delete_view_success_response(self) -> None:
29
+ response = self.client.post(
30
+ reverse(
31
+ 'django_spire:ai:chat:json:delete',
32
+ kwargs={'pk': self.test_chat.pk}
33
+ ),
34
+ content_type='application/json'
35
+ )
36
+
37
+ data = response.json()
38
+
39
+ assert data['type'] == 'success'
40
+ assert 'deleted' in data['message'].lower()
41
+
42
+ def test_delete_view_sets_deleted_flag(self) -> None:
43
+ self.client.post(
44
+ reverse(
45
+ 'django_spire:ai:chat:json:delete',
46
+ kwargs={'pk': self.test_chat.pk}
47
+ ),
48
+ content_type='application/json'
49
+ )
50
+
51
+ self.test_chat.refresh_from_db()
52
+
53
+ assert self.test_chat.is_deleted
54
+
55
+ def test_delete_view_nonexistent_chat(self) -> None:
56
+ response = self.client.post(
57
+ reverse(
58
+ 'django_spire:ai:chat:json:delete',
59
+ kwargs={'pk': 99999}
60
+ ),
61
+ content_type='application/json'
62
+ )
63
+
64
+ data = response.json()
65
+
66
+ assert data['type'] == 'error'
67
+
68
+ def test_rename_view_url_path(self) -> None:
69
+ response = self.client.post(
70
+ reverse(
71
+ 'django_spire:ai:chat:json:rename',
72
+ kwargs={'pk': self.test_chat.pk}
73
+ ),
74
+ data=json.dumps({'new_name': 'New Chat Name'}),
75
+ content_type='application/json'
76
+ )
77
+
78
+ assert response.status_code == 200
79
+
80
+ def test_rename_view_success_response(self) -> None:
81
+ response = self.client.post(
82
+ reverse(
83
+ 'django_spire:ai:chat:json:rename',
84
+ kwargs={'pk': self.test_chat.pk}
85
+ ),
86
+ data=json.dumps({'new_name': 'Updated Name'}),
87
+ content_type='application/json'
88
+ )
89
+
90
+ data = response.json()
91
+
92
+ assert data['type'] == 'success'
93
+
94
+ def test_rename_view_updates_name(self) -> None:
95
+ new_name = 'Brand New Name'
96
+
97
+ self.client.post(
98
+ reverse(
99
+ 'django_spire:ai:chat:json:rename',
100
+ kwargs={'pk': self.test_chat.pk}
101
+ ),
102
+ data=json.dumps({'new_name': new_name}),
103
+ content_type='application/json'
104
+ )
105
+
106
+ self.test_chat.refresh_from_db()
107
+
108
+ assert self.test_chat.name == new_name
109
+
110
+ def test_rename_view_empty_name(self) -> None:
111
+ response = self.client.post(
112
+ reverse(
113
+ 'django_spire:ai:chat:json:rename',
114
+ kwargs={'pk': self.test_chat.pk}
115
+ ),
116
+ data=json.dumps({'new_name': ''}),
117
+ content_type='application/json'
118
+ )
119
+
120
+ data = response.json()
121
+
122
+ assert data['type'] == 'error'
123
+
124
+ def test_rename_view_name_too_long(self) -> None:
125
+ long_name = 'A' * 200
126
+
127
+ response = self.client.post(
128
+ reverse(
129
+ 'django_spire:ai:chat:json:rename',
130
+ kwargs={'pk': self.test_chat.pk}
131
+ ),
132
+ data=json.dumps({'new_name': long_name}),
133
+ content_type='application/json'
134
+ )
135
+
136
+ data = response.json()
137
+
138
+ assert data['type'] == 'error'
139
+
140
+ def test_rename_view_nonexistent_chat(self) -> None:
141
+ response = self.client.post(
142
+ reverse(
143
+ 'django_spire:ai:chat:json:rename',
144
+ kwargs={'pk': 99999}
145
+ ),
146
+ data=json.dumps({'new_name': 'New Name'}),
147
+ content_type='application/json'
148
+ )
149
+
150
+ data = response.json()
151
+
152
+ assert data['type'] == 'error'
153
+
154
+ def test_rename_view_max_length_name(self) -> None:
155
+ max_length_name = 'A' * 128
156
+
157
+ response = self.client.post(
158
+ reverse(
159
+ 'django_spire:ai:chat:json:rename',
160
+ kwargs={'pk': self.test_chat.pk}
161
+ ),
162
+ data=json.dumps({'new_name': max_length_name}),
163
+ content_type='application/json'
164
+ )
165
+
166
+ data = response.json()
167
+
168
+ assert data['type'] == 'success'
169
+
170
+ self.test_chat.refresh_from_db()
171
+ assert self.test_chat.name == max_length_name
172
+
173
+
174
+ class ChatJsonUrlAdditionalTests(BaseTestCase):
175
+ def test_delete_multiple_chats(self) -> None:
176
+ chat1 = create_test_chat(user=self.super_user)
177
+ chat2 = create_test_chat(user=self.super_user)
178
+
179
+ self.client.post(
180
+ reverse('django_spire:ai:chat:json:delete', kwargs={'pk': chat1.pk}),
181
+ content_type='application/json'
182
+ )
183
+
184
+ self.client.post(
185
+ reverse('django_spire:ai:chat:json:delete', kwargs={'pk': chat2.pk}),
186
+ content_type='application/json'
187
+ )
188
+
189
+ chat1.refresh_from_db()
190
+ chat2.refresh_from_db()
191
+
192
+ assert chat1.is_deleted
193
+ assert chat2.is_deleted
194
+
195
+ def test_rename_with_special_characters(self) -> None:
196
+ chat = create_test_chat(user=self.super_user)
197
+ special_name = "Chat with 'quotes' and \"double quotes\""
198
+
199
+ response = self.client.post(
200
+ reverse('django_spire:ai:chat:json:rename', kwargs={'pk': chat.pk}),
201
+ data=json.dumps({'new_name': special_name}),
202
+ content_type='application/json'
203
+ )
204
+
205
+ data = response.json()
206
+
207
+ assert data['type'] == 'success'
208
+
209
+ def test_rename_with_unicode(self) -> None:
210
+ chat = create_test_chat(user=self.super_user)
211
+ unicode_name = 'Chat 日本語 🎉'
212
+
213
+ response = self.client.post(
214
+ reverse('django_spire:ai:chat:json:rename', kwargs={'pk': chat.pk}),
215
+ data=json.dumps({'new_name': unicode_name}),
216
+ content_type='application/json'
217
+ )
218
+
219
+ data = response.json()
220
+
221
+ assert data['type'] == 'success'
222
+
223
+ chat.refresh_from_db()
224
+ assert chat.name == unicode_name
File without changes
@@ -0,0 +1,188 @@
1
+ from __future__ import annotations
2
+
3
+ import pytest
4
+
5
+ from django.core.exceptions import ValidationError
6
+
7
+ from django_spire.ai.context.choices import PersonRoleChoices
8
+ from django_spire.ai.context.intelligence.prompts.organization_prompts import organization_info_prompt
9
+ from django_spire.ai.context.models import Organization, Person
10
+ from django_spire.core.tests.test_cases import BaseTestCase
11
+
12
+
13
+ class OrganizationModelTests(BaseTestCase):
14
+ def test_organization_creation(self) -> None:
15
+ org = Organization.objects.create(
16
+ name='Test Organization',
17
+ legal_name='Test Organization Inc.'
18
+ )
19
+
20
+ assert org.name == 'Test Organization'
21
+ assert org.legal_name == 'Test Organization Inc.'
22
+
23
+ def test_organization_only_one_allowed(self) -> None:
24
+ Organization.objects.create(name='First Org')
25
+
26
+ with pytest.raises(ValidationError):
27
+ Organization.objects.create(name='Second Org')
28
+
29
+ def test_organization_all_fields(self) -> None:
30
+ org = Organization.objects.create(
31
+ name='Test Org',
32
+ legal_name='Test Org LLC',
33
+ description='A test organization',
34
+ sector='Technology',
35
+ sub_sector='Software',
36
+ website='https://example.com',
37
+ street_address='123 Main St',
38
+ unit_number='Suite 100',
39
+ city='New York',
40
+ province='NY',
41
+ postal_code='10001',
42
+ country='USA',
43
+ phone='555-1234',
44
+ email='info@example.com'
45
+ )
46
+
47
+ assert org.name == 'Test Org'
48
+ assert org.sector == 'Technology'
49
+ assert org.city == 'New York'
50
+ assert org.email == 'info@example.com'
51
+
52
+ def test_organization_nullable_fields(self) -> None:
53
+ org = Organization.objects.create()
54
+
55
+ assert org.name is None
56
+ assert org.legal_name is None
57
+ assert org.description is None
58
+
59
+ def test_organization_get_only_or_none_exists(self) -> None:
60
+ Organization.objects.create(name='Test Org')
61
+
62
+ result = Organization.objects.get_only_or_none()
63
+
64
+ assert result is not None
65
+ assert result.name == 'Test Org'
66
+
67
+ def test_organization_get_only_or_none_not_exists(self) -> None:
68
+ result = Organization.objects.get_only_or_none()
69
+
70
+ assert result is None
71
+
72
+
73
+ class PersonModelTests(BaseTestCase):
74
+ def test_person_creation(self) -> None:
75
+ person = Person.objects.create(
76
+ first_name='John',
77
+ last_name='Doe',
78
+ role=PersonRoleChoices.ADMIN
79
+ )
80
+
81
+ assert person.first_name == 'John'
82
+ assert person.last_name == 'Doe'
83
+ assert person.role == PersonRoleChoices.ADMIN
84
+
85
+ def test_person_default_role(self) -> None:
86
+ person = Person.objects.create(
87
+ first_name='Jane',
88
+ last_name='Smith'
89
+ )
90
+
91
+ assert person.role == PersonRoleChoices.ADMIN
92
+
93
+ def test_person_default_is_internal(self) -> None:
94
+ person = Person.objects.create(
95
+ first_name='Test',
96
+ last_name='User'
97
+ )
98
+
99
+ assert person.is_internal_to_organization is True
100
+
101
+ def test_person_all_roles(self) -> None:
102
+ for role_choice in PersonRoleChoices:
103
+ person = Person.objects.create(
104
+ first_name=f'Test_{role_choice.value}',
105
+ last_name='User',
106
+ role=role_choice
107
+ )
108
+
109
+ assert person.role == role_choice
110
+
111
+ def test_person_with_user(self) -> None:
112
+ person = Person.objects.create(
113
+ user=self.super_user,
114
+ first_name='Super',
115
+ last_name='User'
116
+ )
117
+
118
+ assert person.user == self.super_user
119
+
120
+ def test_person_nullable_fields(self) -> None:
121
+ person = Person.objects.create()
122
+
123
+ assert person.first_name is None
124
+ assert person.last_name is None
125
+ assert person.phone is None
126
+ assert person.email is None
127
+ assert person.role_details is None
128
+
129
+ def test_person_external_to_organization(self) -> None:
130
+ person = Person.objects.create(
131
+ first_name='External',
132
+ last_name='Person',
133
+ is_internal_to_organization=False
134
+ )
135
+
136
+ assert person.is_internal_to_organization is False
137
+
138
+
139
+ class PersonRoleChoicesTests(BaseTestCase):
140
+ def test_admin_choice(self) -> None:
141
+ assert PersonRoleChoices.ADMIN.value == 'admin'
142
+ assert PersonRoleChoices.ADMIN.label == 'Admin'
143
+
144
+ def test_human_resources_choice(self) -> None:
145
+ assert PersonRoleChoices.HUMAN_RESOURCES.value == 'human_resources'
146
+ assert PersonRoleChoices.HUMAN_RESOURCES.label == 'Human Resources'
147
+
148
+ def test_sales_choice(self) -> None:
149
+ assert PersonRoleChoices.SALES.value == 'sales'
150
+ assert PersonRoleChoices.SALES.label == 'Sales'
151
+
152
+ def test_manager_choice(self) -> None:
153
+ assert PersonRoleChoices.MANAGER.value == 'manager'
154
+ assert PersonRoleChoices.MANAGER.label == 'Manager'
155
+
156
+ def test_marketing_choice(self) -> None:
157
+ assert PersonRoleChoices.MARKETING.value == 'marketing'
158
+ assert PersonRoleChoices.MARKETING.label == 'Marketing'
159
+
160
+ def test_technical_choice(self) -> None:
161
+ assert PersonRoleChoices.TECHNICAL.value == 'technical'
162
+ assert PersonRoleChoices.TECHNICAL.label == 'Technical'
163
+
164
+ def test_it_support_choice(self) -> None:
165
+ assert PersonRoleChoices.IT_SUPPORT.value == 'it_support'
166
+ assert PersonRoleChoices.IT_SUPPORT.label == 'IT Support'
167
+
168
+ def test_all_choices_count(self) -> None:
169
+ assert len(PersonRoleChoices) == 7
170
+
171
+
172
+ class OrganizationPromptTests(BaseTestCase):
173
+ def test_organization_info_prompt_with_org(self) -> None:
174
+ Organization.objects.create(
175
+ name='Test Org',
176
+ legal_name='Test Org Inc.'
177
+ )
178
+
179
+ prompt = organization_info_prompt()
180
+
181
+ assert prompt is not None
182
+ assert 'Organization Information' in prompt.to_str()
183
+
184
+ def test_organization_info_prompt_without_org(self) -> None:
185
+ prompt = organization_info_prompt()
186
+
187
+ assert prompt is not None
188
+ assert 'no organization information available' in prompt.to_str()
@@ -4,7 +4,7 @@ import json
4
4
  import traceback
5
5
  import uuid
6
6
 
7
- from typing import TYPE_CHECKING
7
+ from typing import TYPE_CHECKING, Callable, TypeVar
8
8
 
9
9
  from dandy import Recorder
10
10
  from django.utils.timezone import now
@@ -15,16 +15,19 @@ if TYPE_CHECKING:
15
15
  from django.contrib.auth.models import AbstractBaseUser
16
16
 
17
17
 
18
+ T = TypeVar('T')
19
+
20
+
18
21
  def log_ai_interaction_from_recorder(
19
22
  user: AbstractBaseUser | None = None,
20
23
  actor: str | None = None
21
- ):
24
+ ) -> Callable[[Callable[..., T]], Callable[..., T]]:
22
25
  if user is None and actor is None:
23
26
  message = 'user or actor must be provided'
24
27
  raise ValueError(message)
25
28
 
26
- def decorator(func):
27
- def wrapper(*args, **kwargs):
29
+ def decorator(func: Callable[..., T]) -> Callable[..., T]:
30
+ def wrapper(*args, **kwargs) -> T:
28
31
  recording_uuid = f'Recording-{uuid.uuid4()}'
29
32
 
30
33
  ai_usage, _ = AiUsage.objects.get_or_create(
@@ -41,7 +44,6 @@ def log_ai_interaction_from_recorder(
41
44
  try:
42
45
  Recorder.start_recording(recording_uuid)
43
46
  return func(*args, **kwargs)
44
-
45
47
  except Exception as e:
46
48
  ai_usage.was_successful = False
47
49
 
@@ -55,7 +57,6 @@ def log_ai_interaction_from_recorder(
55
57
  ai_interaction.stack_trace = stack_trace
56
58
 
57
59
  raise
58
-
59
60
  finally:
60
61
  Recorder.stop_recording(recording_uuid)
61
62
 
@@ -1,31 +1,121 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from django_spire.ai.prompt.system import bots
4
+ from django_spire.ai.prompt.system.intel import SystemPromptIntel, SystemPromptResultIntel
4
5
  from django_spire.core.tests.test_cases import BaseTestCase
5
6
 
6
7
 
7
8
  class PromptBotTestCase(BaseTestCase):
8
- def test_system_prompt_system_prompt(self):
9
+ def test_role_system_prompt_bot(self) -> None:
9
10
  ISSUE_PROMPT = (
10
- "I am using jira to plan developer workloads And I want to be able to build very clear issues that"
11
- "developers will be able to read understand and take action on to complete the goal."
12
- "I will give you details about a software development task that we have to achieve and I want you "
13
- "to return a short one sentence summary that the developer can read and understand the scope of the issue"
14
- "before they get into the details of it "
11
+ 'I am using jira to plan developer workloads And I want to be able to build very clear issues that '
12
+ 'developers will be able to read understand and take action on to complete the goal. '
13
+ 'I will give you details about a software development task that we have to achieve and I want you '
14
+ 'to return a short one sentence summary that the developer can read and understand the scope of the issue '
15
+ 'before they get into the details of it '
15
16
  )
16
17
 
17
18
  role_bot = bots.RoleSystemPromptBot()
18
19
  role_result = role_bot.process(ISSUE_PROMPT)
19
- self.assertIsNotNone(role_result)
20
+
21
+ assert role_result is not None
22
+ assert role_result.result is not None
23
+ assert len(role_result.result) > 0
24
+
25
+ def test_task_system_prompt_bot(self) -> None:
26
+ ISSUE_PROMPT = (
27
+ 'I am using jira to plan developer workloads And I want to be able to build very clear issues that '
28
+ 'developers will be able to read understand and take action on to complete the goal.'
29
+ )
20
30
 
21
31
  task_bot = bots.TaskSystemPromptBot()
22
32
  task_result = task_bot.process(ISSUE_PROMPT)
23
- self.assertIsNotNone(task_result)
33
+
34
+ assert task_result is not None
35
+ assert task_result.result is not None
36
+ assert len(task_result.result) > 0
37
+
38
+ def test_guidelines_system_prompt_bot(self) -> None:
39
+ ISSUE_PROMPT = (
40
+ 'I want to create a system that helps users write better emails.'
41
+ )
24
42
 
25
43
  guidelines_bot = bots.GuidelinesSystemPromptBot()
26
44
  guidelines_result = guidelines_bot.process(ISSUE_PROMPT)
27
- self.assertIsNotNone(guidelines_result)
45
+
46
+ assert guidelines_result is not None
47
+ assert guidelines_result.result is not None
48
+ assert len(guidelines_result.result) > 0
49
+
50
+ def test_output_format_system_prompt_bot(self) -> None:
51
+ ISSUE_PROMPT = (
52
+ 'I want to create a system that returns a summary of a document.'
53
+ )
28
54
 
29
55
  output_format_bot = bots.OutputFormatSystemPromptBot()
30
56
  output_format_result = output_format_bot.process(ISSUE_PROMPT)
31
- self.assertIsNotNone(output_format_result)
57
+
58
+ assert output_format_result is not None
59
+ assert output_format_result.result is not None
60
+
61
+ def test_system_prompt_bot_full_workflow(self) -> None:
62
+ ISSUE_PROMPT = (
63
+ 'I am using jira to plan developer workloads And I want to be able to build very clear issues that '
64
+ 'developers will be able to read understand and take action on to complete the goal. '
65
+ 'I will give you details about a software development task that we have to achieve and I want you '
66
+ 'to return a short one sentence summary that the developer can read and understand the scope of the issue '
67
+ 'before they get into the details of it '
68
+ )
69
+
70
+ role_bot = bots.RoleSystemPromptBot()
71
+ role_result = role_bot.process(ISSUE_PROMPT)
72
+ assert role_result is not None
73
+
74
+ task_bot = bots.TaskSystemPromptBot()
75
+ task_result = task_bot.process(ISSUE_PROMPT)
76
+ assert task_result is not None
77
+
78
+ guidelines_bot = bots.GuidelinesSystemPromptBot()
79
+ guidelines_result = guidelines_bot.process(ISSUE_PROMPT)
80
+ assert guidelines_result is not None
81
+
82
+ output_format_bot = bots.OutputFormatSystemPromptBot()
83
+ output_format_result = output_format_bot.process(ISSUE_PROMPT)
84
+ assert output_format_result is not None
85
+
86
+
87
+ class SystemPromptIntelTestCase(BaseTestCase):
88
+ def test_system_prompt_intel_to_string(self) -> None:
89
+ intel = SystemPromptIntel(
90
+ role='Test Role',
91
+ task='Test Task',
92
+ guidelines='Test Guidelines',
93
+ output_format='Test Output Format'
94
+ )
95
+
96
+ result = intel.to_string()
97
+
98
+ assert 'Role: Test Role' in result
99
+ assert 'Task: Test Task' in result
100
+ assert 'Guidelines: Test Guidelines' in result
101
+ assert 'Output Format: Test Output Format' in result
102
+
103
+ def test_system_prompt_intel_to_string_without_output_format(self) -> None:
104
+ intel = SystemPromptIntel(
105
+ role='Test Role',
106
+ task='Test Task',
107
+ guidelines='Test Guidelines',
108
+ output_format=None
109
+ )
110
+
111
+ result = intel.to_string()
112
+
113
+ assert 'Role: Test Role' in result
114
+ assert 'Task: Test Task' in result
115
+ assert 'Guidelines: Test Guidelines' in result
116
+ assert 'Output Format' not in result
117
+
118
+ def test_system_prompt_result_intel(self) -> None:
119
+ intel = SystemPromptResultIntel(result='Test result')
120
+
121
+ assert intel.result == 'Test result'
@@ -0,0 +1,83 @@
1
+ from __future__ import annotations
2
+
3
+ from django_spire.ai.prompt.intel import DandyPromptPythonFileIntel, TextToMarkdownIntel
4
+ from django_spire.core.tests.test_cases import BaseTestCase
5
+
6
+
7
+ class DandyPromptPythonFileIntelTests(BaseTestCase):
8
+ def test_dandy_prompt_python_file_intel_creation(self) -> None:
9
+ intel = DandyPromptPythonFileIntel(
10
+ source_code='print("hello")',
11
+ file_name='test.py'
12
+ )
13
+
14
+ assert intel.source_code == 'print("hello")'
15
+ assert intel.file_name == 'test.py'
16
+
17
+ def test_dandy_prompt_python_file_intel_empty_source(self) -> None:
18
+ intel = DandyPromptPythonFileIntel(
19
+ source_code='',
20
+ file_name='empty.py'
21
+ )
22
+
23
+ assert intel.source_code == ''
24
+
25
+ def test_dandy_prompt_python_file_intel_long_source(self) -> None:
26
+ long_source = 'x = 1\n' * 1000
27
+ intel = DandyPromptPythonFileIntel(
28
+ source_code=long_source,
29
+ file_name='long.py'
30
+ )
31
+
32
+ assert intel.source_code == long_source
33
+
34
+
35
+ class TextToMarkdownIntelTests(BaseTestCase):
36
+ def test_text_to_markdown_intel_creation(self) -> None:
37
+ intel = TextToMarkdownIntel(
38
+ markdown_content='# Hello World',
39
+ file_name='test.md'
40
+ )
41
+
42
+ assert intel.markdown_content == '# Hello World'
43
+ assert intel.file_name == 'test.md'
44
+
45
+ def test_text_to_markdown_intel_empty_content(self) -> None:
46
+ intel = TextToMarkdownIntel(
47
+ markdown_content='',
48
+ file_name='empty.md'
49
+ )
50
+
51
+ assert intel.markdown_content == ''
52
+
53
+ def test_text_to_markdown_intel_complex_markdown(self) -> None:
54
+ complex_md = """
55
+ # Title
56
+
57
+ ## Section 1
58
+
59
+ - Item 1
60
+ - Item 2
61
+
62
+ ```python
63
+ print("code")
64
+ ```
65
+ """
66
+
67
+ intel = TextToMarkdownIntel(
68
+ markdown_content=complex_md,
69
+ file_name='complex.md'
70
+ )
71
+
72
+ assert intel.markdown_content == complex_md
73
+
74
+ def test_text_to_markdown_intel_model_dump(self) -> None:
75
+ intel = TextToMarkdownIntel(
76
+ markdown_content='Test',
77
+ file_name='test.md'
78
+ )
79
+
80
+ dump = intel.model_dump()
81
+
82
+ assert 'markdown_content' in dump
83
+ assert 'file_name' in dump