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
django_spire/ai/admin.py CHANGED
@@ -30,17 +30,17 @@ class AiUsageAdmin(AiUsageAdminMixin):
30
30
  return [field.name for field in self.model._meta.fields]
31
31
 
32
32
  def view_interactions_link(
33
- self,
34
- ai_usage: models.AiUsage,
35
- was_successful: bool | None = None,
33
+ self,
34
+ ai_usage: models.AiUsage,
35
+ was_successful: bool | None = None
36
36
  ) -> str:
37
37
  was_successful_filter = '' if was_successful is None else f'&was_successful__exact={int(was_successful)}'
38
38
 
39
39
  url = (
40
- reverse("admin:django_spire_ai_aiinteraction_changelist")
41
- + "?"
42
- + urlencode({"ai_usage__id": f"{ai_usage.id}"})
43
- + was_successful_filter
40
+ reverse("admin:django_spire_ai_aiinteraction_changelist")
41
+ + "?"
42
+ + urlencode({"ai_usage__id": f"{ai_usage.id}"})
43
+ + was_successful_filter
44
44
  )
45
45
 
46
46
  if was_successful is None:
@@ -57,16 +57,16 @@ class AiUsageAdmin(AiUsageAdminMixin):
57
57
  view_interactions_link.short_description = "All"
58
58
 
59
59
  def view_successful_interactions_link(
60
- self,
61
- ai_usage: models.AiUsage,
60
+ self,
61
+ ai_usage: models.AiUsage
62
62
  ) -> str:
63
63
  return self.view_interactions_link(ai_usage, was_successful=True)
64
64
 
65
65
  view_successful_interactions_link.short_description = "Successful"
66
66
 
67
67
  def view_failed_interactions_link(
68
- self,
69
- ai_usage: models.AiUsage,
68
+ self,
69
+ ai_usage: models.AiUsage
70
70
  ) -> str:
71
71
  return self.view_interactions_link(ai_usage, was_successful=False)
72
72
 
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from django.apps import AppConfig
4
+
4
5
  from django_spire.utils import check_required_apps
5
6
 
6
7
 
@@ -101,7 +101,7 @@
101
101
  <div class="col-12 overflow-auto p-4" style="{{ dialog_height|default:'height: calc(100vh - 90px - var(--app-top-navigation-height));' }}" x-ref="target_div"></div>
102
102
  </div>
103
103
 
104
- <div class="row">
104
+ <div class="row pe-5">
105
105
  <div class="col-auto text-center ps-2 pe-1">
106
106
  <button @click="start_listening()" class="btn btn-app-primary-outlined" x-show="!speech_listening">
107
107
  <i class="bi bi-mic"></i>
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from django_spire.ai.chat.models import Chat
6
+
7
+ if TYPE_CHECKING:
8
+ from django.contrib.auth.models import User
9
+
10
+
11
+ def create_test_chat(user: User) -> Chat:
12
+ return Chat.objects.create(
13
+ user=user,
14
+ name='New Exciting Chat!'
15
+ )
@@ -0,0 +1,45 @@
1
+ from __future__ import annotations
2
+
3
+ from django.contrib.auth.models import Permission, User
4
+ from django.test import RequestFactory
5
+
6
+ from django_spire.ai.chat.auth.controller import BaseAiChatAuthController
7
+ from django_spire.core.tests.test_cases import BaseTestCase
8
+
9
+
10
+ class BaseAiChatAuthControllerTests(BaseTestCase):
11
+ def setUp(self) -> None:
12
+ super().setUp()
13
+
14
+ self.factory = RequestFactory()
15
+ self.request = self.factory.get('/')
16
+ self.request.user = self.super_user
17
+
18
+ def test_can_delete_with_permission(self) -> None:
19
+ permission = Permission.objects.get(codename='delete_chat')
20
+ self.super_user.user_permissions.add(permission)
21
+
22
+ self.super_user = type(self.super_user).objects.get(pk=self.super_user.pk)
23
+ self.request.user = self.super_user
24
+
25
+ controller = BaseAiChatAuthController(self.request)
26
+
27
+ assert controller.can_delete() is True
28
+
29
+ def test_can_delete_without_permission(self) -> None:
30
+ regular_user = User.objects.create_user(
31
+ username='regular_user',
32
+ password='testpass123' # noqa: S106
33
+ )
34
+
35
+ request = self.factory.get('/')
36
+ request.user = regular_user
37
+
38
+ controller = BaseAiChatAuthController(request)
39
+
40
+ assert controller.can_delete() is False
41
+
42
+ def test_controller_has_request(self) -> None:
43
+ controller = BaseAiChatAuthController(self.request)
44
+
45
+ assert controller.request == self.request
@@ -0,0 +1,301 @@
1
+ from __future__ import annotations
2
+
3
+ from django_spire.ai.chat.choices import MessageResponseType
4
+ from django_spire.ai.chat.message_intel import DefaultMessageIntel
5
+ from django_spire.ai.chat.models import Chat
6
+ from django_spire.ai.chat.responses import MessageResponse
7
+ from django_spire.core.tests.test_cases import BaseTestCase
8
+
9
+
10
+ class ChatModelTests(BaseTestCase):
11
+ def test_chat_creation(self) -> None:
12
+ chat = Chat.objects.create(
13
+ user=self.super_user,
14
+ name='Test Chat'
15
+ )
16
+
17
+ assert chat.name == 'Test Chat'
18
+ assert chat.user == self.super_user
19
+
20
+ def test_chat_str_short_name(self) -> None:
21
+ chat = Chat.objects.create(
22
+ user=self.super_user,
23
+ name='Short Name'
24
+ )
25
+
26
+ assert str(chat) == 'Short Name'
27
+
28
+ def test_chat_str_long_name(self) -> None:
29
+ long_name = 'A' * 100
30
+ chat = Chat.objects.create(
31
+ user=self.super_user,
32
+ name=long_name
33
+ )
34
+
35
+ assert len(str(chat)) == 51
36
+ assert str(chat).endswith('...')
37
+
38
+ def test_chat_name_shortened_short(self) -> None:
39
+ chat = Chat.objects.create(
40
+ user=self.super_user,
41
+ name='Short'
42
+ )
43
+
44
+ assert chat.name_shortened == 'Short'
45
+
46
+ def test_chat_name_shortened_long(self) -> None:
47
+ long_name = 'A' * 50
48
+ chat = Chat.objects.create(
49
+ user=self.super_user,
50
+ name=long_name
51
+ )
52
+
53
+ assert len(chat.name_shortened) == 27
54
+ assert chat.name_shortened.endswith('...')
55
+
56
+ def test_chat_is_empty(self) -> None:
57
+ chat = Chat.objects.create(
58
+ user=self.super_user,
59
+ name='Empty Chat'
60
+ )
61
+
62
+ assert chat.is_empty
63
+
64
+ def test_chat_is_not_empty(self) -> None:
65
+ chat = Chat.objects.create(
66
+ user=self.super_user,
67
+ name='Chat with messages'
68
+ )
69
+
70
+ message_response = MessageResponse(
71
+ type=MessageResponseType.REQUEST,
72
+ sender='User',
73
+ message_intel=DefaultMessageIntel(text='Hello')
74
+ )
75
+ chat.add_message_response(message_response)
76
+
77
+ assert not chat.is_empty
78
+
79
+ def test_add_message_response(self) -> None:
80
+ chat = Chat.objects.create(
81
+ user=self.super_user,
82
+ name='Test Chat'
83
+ )
84
+
85
+ message_response = MessageResponse(
86
+ type=MessageResponseType.REQUEST,
87
+ sender='User',
88
+ message_intel=DefaultMessageIntel(text='Test message')
89
+ )
90
+ chat.add_message_response(message_response)
91
+
92
+ assert chat.messages.count() == 1
93
+
94
+ def test_add_message_response_updates_last_message_datetime(self) -> None:
95
+ chat = Chat.objects.create(
96
+ user=self.super_user,
97
+ name='Test Chat'
98
+ )
99
+
100
+ initial_datetime = chat.last_message_datetime
101
+
102
+ message_response = MessageResponse(
103
+ type=MessageResponseType.REQUEST,
104
+ sender='User',
105
+ message_intel=DefaultMessageIntel(text='Test')
106
+ )
107
+ chat.add_message_response(message_response)
108
+ chat.refresh_from_db()
109
+
110
+ assert chat.last_message_datetime >= initial_datetime
111
+
112
+ def test_generate_message_history(self) -> None:
113
+ chat = Chat.objects.create(
114
+ user=self.super_user,
115
+ name='Test Chat'
116
+ )
117
+
118
+ chat.add_message_response(MessageResponse(
119
+ type=MessageResponseType.REQUEST,
120
+ sender='User',
121
+ message_intel=DefaultMessageIntel(text='Hello')
122
+ ))
123
+ chat.add_message_response(MessageResponse(
124
+ type=MessageResponseType.RESPONSE,
125
+ sender='Assistant',
126
+ message_intel=DefaultMessageIntel(text='Hi there')
127
+ ))
128
+
129
+ message_history = chat.generate_message_history()
130
+
131
+ assert message_history is not None
132
+
133
+ def test_generate_message_history_exclude_last(self) -> None:
134
+ chat = Chat.objects.create(
135
+ user=self.super_user,
136
+ name='Test Chat'
137
+ )
138
+
139
+ chat.add_message_response(MessageResponse(
140
+ type=MessageResponseType.REQUEST,
141
+ sender='User',
142
+ message_intel=DefaultMessageIntel(text='Message 1')
143
+ ))
144
+ chat.add_message_response(MessageResponse(
145
+ type=MessageResponseType.REQUEST,
146
+ sender='User',
147
+ message_intel=DefaultMessageIntel(text='Message 2')
148
+ ))
149
+
150
+ message_history = chat.generate_message_history(exclude_last_message=True)
151
+
152
+ assert message_history is not None
153
+
154
+
155
+ class ChatMessageModelTests(BaseTestCase):
156
+ def setUp(self) -> None:
157
+ super().setUp()
158
+
159
+ self.chat = Chat.objects.create(
160
+ user=self.super_user,
161
+ name='Test Chat'
162
+ )
163
+
164
+ def test_chat_message_str_short(self) -> None:
165
+ message_response = MessageResponse(
166
+ type=MessageResponseType.REQUEST,
167
+ sender='User',
168
+ message_intel=DefaultMessageIntel(text='Short')
169
+ )
170
+ self.chat.add_message_response(message_response)
171
+
172
+ message = self.chat.messages.first()
173
+
174
+ assert str(message) == 'Short'
175
+
176
+ def test_chat_message_str_long(self) -> None:
177
+ long_text = 'A' * 100
178
+ message_response = MessageResponse(
179
+ type=MessageResponseType.REQUEST,
180
+ sender='User',
181
+ message_intel=DefaultMessageIntel(text=long_text)
182
+ )
183
+ self.chat.add_message_response(message_response)
184
+
185
+ message = self.chat.messages.first()
186
+
187
+ assert len(str(message)) == 67
188
+ assert str(message).endswith('...')
189
+
190
+ def test_chat_message_intel_property(self) -> None:
191
+ message_response = MessageResponse(
192
+ type=MessageResponseType.REQUEST,
193
+ sender='User',
194
+ message_intel=DefaultMessageIntel(text='Test intel')
195
+ )
196
+ self.chat.add_message_response(message_response)
197
+
198
+ message = self.chat.messages.first()
199
+
200
+ assert isinstance(message.intel, DefaultMessageIntel)
201
+ assert message.intel.text == 'Test intel'
202
+
203
+ def test_chat_message_role_request(self) -> None:
204
+ message_response = MessageResponse(
205
+ type=MessageResponseType.REQUEST,
206
+ sender='User',
207
+ message_intel=DefaultMessageIntel(text='Test')
208
+ )
209
+ self.chat.add_message_response(message_response)
210
+
211
+ message = self.chat.messages.first()
212
+
213
+ assert message.role == 'user'
214
+
215
+ def test_chat_message_role_response(self) -> None:
216
+ message_response = MessageResponse(
217
+ type=MessageResponseType.RESPONSE,
218
+ sender='Assistant',
219
+ message_intel=DefaultMessageIntel(text='Test')
220
+ )
221
+ self.chat.add_message_response(message_response)
222
+
223
+ message = self.chat.messages.first()
224
+
225
+ assert message.role == 'assistant'
226
+
227
+ def test_chat_message_to_message_response(self) -> None:
228
+ original_response = MessageResponse(
229
+ type=MessageResponseType.REQUEST,
230
+ sender='User',
231
+ message_intel=DefaultMessageIntel(text='Test')
232
+ )
233
+ self.chat.add_message_response(original_response)
234
+
235
+ message = self.chat.messages.first()
236
+ converted_response = message.to_message_response()
237
+
238
+ assert isinstance(converted_response, MessageResponse)
239
+ assert converted_response.type == MessageResponseType.REQUEST
240
+ assert converted_response.sender == 'User'
241
+
242
+
243
+ class ChatQuerySetTests(BaseTestCase):
244
+ def test_by_user(self) -> None:
245
+ Chat.objects.create(user=self.super_user, name='User Chat')
246
+
247
+ result = Chat.objects.by_user(self.super_user)
248
+
249
+ assert result.count() == 1
250
+
251
+ def test_search(self) -> None:
252
+ Chat.objects.create(user=self.super_user, name='Python Programming')
253
+ Chat.objects.create(user=self.super_user, name='Django Framework')
254
+
255
+ result = Chat.objects.search('Python')
256
+
257
+ assert result.count() == 1
258
+ assert result.first().name == 'Python Programming'
259
+
260
+ def test_search_case_insensitive(self) -> None:
261
+ Chat.objects.create(user=self.super_user, name='Python Programming')
262
+
263
+ result = Chat.objects.search('python')
264
+
265
+ assert result.count() == 1
266
+
267
+
268
+ class ChatMessageQuerySetTests(BaseTestCase):
269
+ def setUp(self) -> None:
270
+ super().setUp()
271
+
272
+ self.chat = Chat.objects.create(
273
+ user=self.super_user,
274
+ name='Test Chat'
275
+ )
276
+
277
+ def test_newest_by_count(self) -> None:
278
+ for i in range(25):
279
+ message_response = MessageResponse(
280
+ type=MessageResponseType.REQUEST,
281
+ sender='User',
282
+ message_intel=DefaultMessageIntel(text=f'Message {i}')
283
+ )
284
+ self.chat.add_message_response(message_response)
285
+
286
+ messages = self.chat.messages.newest_by_count(20)
287
+
288
+ assert len(messages) == 20
289
+
290
+ def test_newest_by_count_reversed(self) -> None:
291
+ for i in range(5):
292
+ message_response = MessageResponse(
293
+ type=MessageResponseType.REQUEST,
294
+ sender='User',
295
+ message_intel=DefaultMessageIntel(text=f'Message {i}')
296
+ )
297
+ self.chat.add_message_response(message_response)
298
+
299
+ messages = self.chat.messages.newest_by_count_reversed(5)
300
+
301
+ assert len(messages) == 5
@@ -0,0 +1,48 @@
1
+ from __future__ import annotations
2
+
3
+ from django_spire.ai.chat.intelligence.prompts import chat_system_prompt
4
+ from django_spire.ai.context.models import Organization
5
+ from django_spire.core.tests.test_cases import BaseTestCase
6
+
7
+
8
+ class ChatPromptsTests(BaseTestCase):
9
+ def test_chat_system_prompt_returns_prompt(self) -> None:
10
+ prompt = chat_system_prompt()
11
+
12
+ assert prompt is not None
13
+
14
+ def test_chat_system_prompt_contains_assistant_text(self) -> None:
15
+ prompt = chat_system_prompt()
16
+ prompt_str = prompt.to_str()
17
+
18
+ assert 'chat assistant' in prompt_str.lower()
19
+
20
+ def test_chat_system_prompt_contains_rules(self) -> None:
21
+ prompt = chat_system_prompt()
22
+ prompt_str = prompt.to_str()
23
+
24
+ assert 'rules' in prompt_str.lower()
25
+
26
+ def test_chat_system_prompt_with_organization(self) -> None:
27
+ Organization.objects.create(
28
+ name='Test Company',
29
+ description='A test company for testing purposes'
30
+ )
31
+
32
+ prompt = chat_system_prompt()
33
+ prompt_str = prompt.to_str()
34
+
35
+ assert 'organization' in prompt_str.lower()
36
+
37
+ def test_chat_system_prompt_without_organization(self) -> None:
38
+ prompt = chat_system_prompt()
39
+ prompt_str = prompt.to_str()
40
+
41
+ assert prompt_str is not None
42
+ assert len(prompt_str) > 0
43
+
44
+ def test_chat_system_prompt_ai_illusion_rule(self) -> None:
45
+ prompt = chat_system_prompt()
46
+ prompt_str = prompt.to_str()
47
+
48
+ assert 'ai' in prompt_str.lower() or 'illusion' in prompt_str.lower()
@@ -0,0 +1,208 @@
1
+ from __future__ import annotations
2
+
3
+ from django_spire.ai.chat.choices import MessageResponseType
4
+ from django_spire.ai.chat.message_intel import DefaultMessageIntel
5
+ from django_spire.ai.chat.responses import MessageResponse, MessageResponseGroup
6
+ from django_spire.core.tests.test_cases import BaseTestCase
7
+
8
+
9
+ class MessageResponseTests(BaseTestCase):
10
+ def test_message_response_creation(self) -> None:
11
+ response = MessageResponse(
12
+ type=MessageResponseType.REQUEST,
13
+ sender='User',
14
+ message_intel=DefaultMessageIntel(text='Hello')
15
+ )
16
+
17
+ assert response.type == MessageResponseType.REQUEST
18
+ assert response.sender == 'User'
19
+ assert response.message_intel.text == 'Hello'
20
+
21
+ def test_message_response_with_timestamp(self) -> None:
22
+ response = MessageResponse(
23
+ type=MessageResponseType.REQUEST,
24
+ sender='User',
25
+ message_intel=DefaultMessageIntel(text='Hello'),
26
+ message_timestamp='Jan 01, 2024 at 12:00 PM'
27
+ )
28
+
29
+ assert response.message_timestamp == 'Jan 01, 2024 at 12:00 PM'
30
+
31
+ def test_message_response_with_synthesis_speech(self) -> None:
32
+ response = MessageResponse(
33
+ type=MessageResponseType.RESPONSE,
34
+ sender='Assistant',
35
+ message_intel=DefaultMessageIntel(text='Hello'),
36
+ synthesis_speech=True
37
+ )
38
+
39
+ assert response.synthesis_speech is True
40
+
41
+ def test_message_response_render_to_html_string_request(self) -> None:
42
+ response = MessageResponse(
43
+ type=MessageResponseType.REQUEST,
44
+ sender='User',
45
+ message_intel=DefaultMessageIntel(text='Hello')
46
+ )
47
+
48
+ html = response.render_to_html_string()
49
+
50
+ assert isinstance(html, str)
51
+ assert len(html) > 0
52
+
53
+ def test_message_response_render_to_html_string_response(self) -> None:
54
+ response = MessageResponse(
55
+ type=MessageResponseType.RESPONSE,
56
+ sender='Assistant',
57
+ message_intel=DefaultMessageIntel(text='Hello')
58
+ )
59
+
60
+ html = response.render_to_html_string()
61
+
62
+ assert isinstance(html, str)
63
+ assert len(html) > 0
64
+
65
+ def test_message_response_render_to_html_string_loading(self) -> None:
66
+ response = MessageResponse(
67
+ type=MessageResponseType.LOADING_RESPONSE,
68
+ sender='Assistant',
69
+ message_intel=DefaultMessageIntel(text='Loading...')
70
+ )
71
+
72
+ html = response.render_to_html_string()
73
+
74
+ assert isinstance(html, str)
75
+ assert len(html) > 0
76
+
77
+ def test_message_response_render_with_context_data(self) -> None:
78
+ response = MessageResponse(
79
+ type=MessageResponseType.REQUEST,
80
+ sender='User',
81
+ message_intel=DefaultMessageIntel(text='Hello')
82
+ )
83
+
84
+ html = response.render_to_html_string(context_data={'chat_id': 123})
85
+
86
+ assert isinstance(html, str)
87
+
88
+ def test_message_response_default_synthesis_speech(self) -> None:
89
+ response = MessageResponse(
90
+ type=MessageResponseType.REQUEST,
91
+ sender='User',
92
+ message_intel=DefaultMessageIntel(text='Hello')
93
+ )
94
+
95
+ assert response.synthesis_speech is False
96
+
97
+ def test_message_response_default_timestamp(self) -> None:
98
+ response = MessageResponse(
99
+ type=MessageResponseType.REQUEST,
100
+ sender='User',
101
+ message_intel=DefaultMessageIntel(text='Hello')
102
+ )
103
+
104
+ assert response.message_timestamp is None
105
+
106
+
107
+ class MessageResponseGroupTests(BaseTestCase):
108
+ def test_message_response_group_creation(self) -> None:
109
+ group = MessageResponseGroup()
110
+
111
+ assert len(group.message_responses) == 0
112
+
113
+ def test_add_message_response(self) -> None:
114
+ group = MessageResponseGroup()
115
+ response = MessageResponse(
116
+ type=MessageResponseType.REQUEST,
117
+ sender='User',
118
+ message_intel=DefaultMessageIntel(text='Hello')
119
+ )
120
+
121
+ group.add_message_response(response)
122
+
123
+ assert len(group.message_responses) == 1
124
+
125
+ def test_add_multiple_message_responses(self) -> None:
126
+ group = MessageResponseGroup()
127
+
128
+ for i in range(5):
129
+ response = MessageResponse(
130
+ type=MessageResponseType.REQUEST,
131
+ sender='User',
132
+ message_intel=DefaultMessageIntel(text=f'Message {i}')
133
+ )
134
+ group.add_message_response(response)
135
+
136
+ assert len(group.message_responses) == 5
137
+
138
+ def test_render_to_html_string_empty(self) -> None:
139
+ group = MessageResponseGroup()
140
+
141
+ html = group.render_to_html_string()
142
+
143
+ assert html == ''
144
+
145
+ def test_render_to_html_string_with_messages(self) -> None:
146
+ group = MessageResponseGroup()
147
+ group.add_message_response(MessageResponse(
148
+ type=MessageResponseType.REQUEST,
149
+ sender='User',
150
+ message_intel=DefaultMessageIntel(text='Hello')
151
+ ))
152
+ group.add_message_response(MessageResponse(
153
+ type=MessageResponseType.RESPONSE,
154
+ sender='Assistant',
155
+ message_intel=DefaultMessageIntel(text='Hi')
156
+ ))
157
+
158
+ html = group.render_to_html_string()
159
+
160
+ assert isinstance(html, str)
161
+ assert len(html) > 0
162
+
163
+ def test_render_to_html_string_with_context(self) -> None:
164
+ group = MessageResponseGroup()
165
+ group.add_message_response(MessageResponse(
166
+ type=MessageResponseType.REQUEST,
167
+ sender='User',
168
+ message_intel=DefaultMessageIntel(text='Hello')
169
+ ))
170
+
171
+ html = group.render_to_html_string(context_data={'chat_id': 123})
172
+
173
+ assert isinstance(html, str)
174
+
175
+ def test_render_to_html_string_with_is_loading(self) -> None:
176
+ group = MessageResponseGroup()
177
+ group.add_message_response(MessageResponse(
178
+ type=MessageResponseType.REQUEST,
179
+ sender='User',
180
+ message_intel=DefaultMessageIntel(text='Hello')
181
+ ))
182
+
183
+ html = group.render_to_html_string(context_data={'is_loading': True})
184
+
185
+ assert isinstance(html, str)
186
+
187
+ def test_render_to_html_string_default_is_loading(self) -> None:
188
+ group = MessageResponseGroup()
189
+ group.add_message_response(MessageResponse(
190
+ type=MessageResponseType.REQUEST,
191
+ sender='User',
192
+ message_intel=DefaultMessageIntel(text='Hello')
193
+ ))
194
+
195
+ html = group.render_to_html_string(context_data={})
196
+
197
+ assert isinstance(html, str)
198
+
199
+
200
+ class MessageResponseTypeTests(BaseTestCase):
201
+ def test_request_type(self) -> None:
202
+ assert MessageResponseType.REQUEST.value == 'request'
203
+
204
+ def test_response_type(self) -> None:
205
+ assert MessageResponseType.RESPONSE.value == 'response'
206
+
207
+ def test_loading_response_type(self) -> None:
208
+ assert MessageResponseType.LOADING_RESPONSE.value == 'loading_response'