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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (538) hide show
  1. django_spire/ai/admin.py +11 -11
  2. django_spire/ai/chat/apps.py +1 -0
  3. django_spire/ai/chat/templates/django_spire/ai/chat/widget/dialog_widget.html +1 -1
  4. django_spire/ai/chat/tests/factories.py +15 -0
  5. django_spire/ai/chat/tests/test_controller.py +45 -0
  6. django_spire/ai/chat/tests/test_models.py +301 -0
  7. django_spire/ai/chat/tests/test_prompts.py +48 -0
  8. django_spire/ai/chat/tests/test_responses.py +208 -0
  9. django_spire/ai/chat/tests/test_router/test_base_chat_router.py +66 -6
  10. django_spire/ai/chat/tests/test_router/test_chat_workflow.py +73 -3
  11. django_spire/ai/chat/tests/test_router/test_integration.py +86 -6
  12. django_spire/ai/chat/tests/test_router/test_intent_decoder.py +93 -1
  13. django_spire/ai/chat/tests/test_router/test_message_intel.py +60 -1
  14. django_spire/ai/chat/tests/test_router/test_spire_chat_router.py +110 -0
  15. django_spire/ai/chat/tests/test_urls/test_json_urls.py +202 -1
  16. django_spire/ai/context/tests/__init__.py +0 -0
  17. django_spire/ai/context/tests/test_context.py +188 -0
  18. django_spire/ai/decorators.py +7 -6
  19. django_spire/ai/prompt/tests/test_bots.py +100 -10
  20. django_spire/ai/prompt/tests/test_prompt_intel.py +83 -0
  21. django_spire/ai/prompt/tests/test_prompt_tuning.py +126 -0
  22. django_spire/ai/sms/decorators.py +8 -2
  23. django_spire/ai/sms/tests/test_sms.py +240 -16
  24. django_spire/ai/sms/tests/test_sms_intel.py +42 -0
  25. django_spire/ai/sms/tests/test_webhook.py +155 -7
  26. django_spire/ai/sms/views.py +23 -24
  27. django_spire/ai/tests/test_ai.py +131 -7
  28. django_spire/auth/apps.py +4 -2
  29. django_spire/auth/controller/controller.py +36 -23
  30. django_spire/auth/controller/exceptions.py +9 -0
  31. django_spire/auth/group/admin.py +1 -0
  32. django_spire/auth/group/apps.py +2 -0
  33. django_spire/auth/group/factories.py +17 -8
  34. django_spire/auth/group/forms.py +7 -0
  35. django_spire/auth/group/tests/test_factories.py +146 -0
  36. django_spire/auth/group/tests/test_forms.py +282 -0
  37. django_spire/auth/group/tests/test_models.py +192 -0
  38. django_spire/auth/group/tests/test_querysets.py +98 -0
  39. django_spire/auth/group/tests/test_utils.py +341 -0
  40. django_spire/auth/group/tests/test_views.py +377 -0
  41. django_spire/auth/group/urls/__init__.py +3 -1
  42. django_spire/auth/group/urls/form_urls.py +2 -0
  43. django_spire/auth/group/urls/json_urls.py +3 -0
  44. django_spire/auth/group/urls/page_urls.py +2 -0
  45. django_spire/auth/group/utils.py +6 -2
  46. django_spire/auth/group/views/form_views.py +6 -3
  47. django_spire/auth/group/views/json_views.py +6 -2
  48. django_spire/auth/mfa/admin.py +2 -0
  49. django_spire/auth/mfa/apps.py +2 -0
  50. django_spire/auth/mfa/forms.py +1 -0
  51. django_spire/auth/mfa/querysets.py +9 -2
  52. django_spire/auth/mfa/tests/test_models.py +233 -0
  53. django_spire/auth/mfa/tests/test_utils.py +106 -0
  54. django_spire/auth/mfa/urls/__init__.py +2 -0
  55. django_spire/auth/mfa/urls/page_urls.py +2 -0
  56. django_spire/auth/mfa/urls/redirect_urls.py +2 -0
  57. django_spire/auth/mfa/views/page_views.py +2 -1
  58. django_spire/auth/permissions/consts.py +2 -2
  59. django_spire/auth/permissions/decorators.py +8 -8
  60. django_spire/auth/permissions/permissions.py +28 -35
  61. django_spire/auth/permissions/tests/test_decorators.py +333 -0
  62. django_spire/auth/permissions/tests/test_permissions.py +337 -0
  63. django_spire/auth/permissions/tests/test_tools.py +305 -0
  64. django_spire/auth/permissions/tools.py +21 -15
  65. django_spire/auth/seeding/seed.py +3 -0
  66. django_spire/auth/seeding/seeder.py +2 -0
  67. django_spire/auth/tests/test_controller.py +323 -0
  68. django_spire/auth/tests/test_url_endpoints.py +9 -9
  69. django_spire/auth/tests/test_views.py +406 -0
  70. django_spire/auth/urls/admin_urls.py +2 -0
  71. django_spire/auth/urls/redirect_urls.py +2 -0
  72. django_spire/auth/user/apps.py +2 -0
  73. django_spire/auth/user/forms.py +9 -0
  74. django_spire/auth/user/models.py +1 -1
  75. django_spire/auth/user/services/services.py +1 -0
  76. django_spire/auth/user/tests/factories.py +14 -13
  77. django_spire/auth/user/tests/test_factories.py +166 -2
  78. django_spire/auth/user/tests/test_forms.py +573 -0
  79. django_spire/auth/user/tests/test_models.py +257 -0
  80. django_spire/auth/user/tests/test_services.py +200 -0
  81. django_spire/auth/user/tests/test_tools.py +153 -0
  82. django_spire/auth/user/tests/test_user_factories.py +139 -0
  83. django_spire/auth/user/tests/test_views.py +363 -0
  84. django_spire/auth/user/tools.py +7 -1
  85. django_spire/auth/user/urls/form_urls.py +3 -0
  86. django_spire/auth/user/urls/page_urls.py +3 -0
  87. django_spire/auth/user/views/form_views.py +19 -10
  88. django_spire/auth/user/views/page_views.py +8 -2
  89. django_spire/auth/views/redirect_views.py +14 -9
  90. django_spire/comment/admin.py +2 -0
  91. django_spire/comment/apps.py +2 -0
  92. django_spire/comment/templatetags/comment_tags.py +1 -0
  93. django_spire/comment/tests/test_forms.py +27 -0
  94. django_spire/comment/tests/test_models.py +215 -0
  95. django_spire/comment/tests/test_querysets.py +101 -0
  96. django_spire/comment/tests/test_utils.py +90 -0
  97. django_spire/comment/urls.py +2 -0
  98. django_spire/comment/utils.py +22 -13
  99. django_spire/comment/views.py +1 -1
  100. django_spire/conf.py +8 -6
  101. django_spire/consts.py +1 -1
  102. django_spire/contrib/breadcrumb/apps.py +2 -0
  103. django_spire/contrib/breadcrumb/breadcrumbs.py +18 -18
  104. django_spire/contrib/breadcrumb/tests/test_breadcrumbs.py +198 -0
  105. django_spire/contrib/constructor/__init__.py +3 -3
  106. django_spire/contrib/constructor/constructor.py +15 -15
  107. django_spire/contrib/constructor/django_model_constructor.py +5 -4
  108. django_spire/contrib/constructor/exceptions.py +5 -3
  109. django_spire/contrib/constructor/tests/__init__.py +0 -0
  110. django_spire/contrib/constructor/tests/test_constructor.py +193 -0
  111. django_spire/contrib/form/tests/__init__.py +0 -0
  112. django_spire/contrib/form/tests/test_forms.py +203 -0
  113. django_spire/contrib/generic_views/modal_views.py +2 -1
  114. django_spire/contrib/generic_views/portal_views.py +20 -19
  115. django_spire/contrib/generic_views/tests/__init__.py +0 -0
  116. django_spire/contrib/generic_views/tests/test_views.py +459 -0
  117. django_spire/contrib/help/apps.py +2 -0
  118. django_spire/contrib/help/templatetags/help.py +1 -0
  119. django_spire/contrib/help/tests/__init__.py +0 -0
  120. django_spire/contrib/help/tests/test_templatetags.py +100 -0
  121. django_spire/contrib/options/mixins.py +6 -5
  122. django_spire/contrib/options/tests/factories.py +5 -1
  123. django_spire/contrib/options/tests/test_options.py +234 -0
  124. django_spire/contrib/ordering/exceptions.py +7 -3
  125. django_spire/contrib/ordering/mixins.py +2 -0
  126. django_spire/contrib/ordering/querysets.py +3 -1
  127. django_spire/contrib/ordering/services/processor_service.py +8 -4
  128. django_spire/contrib/ordering/services/service.py +1 -2
  129. django_spire/contrib/ordering/tests/__init__.py +0 -0
  130. django_spire/contrib/ordering/tests/test_ordering.py +165 -0
  131. django_spire/contrib/ordering/validators.py +6 -6
  132. django_spire/contrib/pagination/templatetags/pagination_tags.py +12 -5
  133. django_spire/contrib/pagination/tests/__init__.py +0 -0
  134. django_spire/contrib/pagination/tests/test_pagination.py +179 -0
  135. django_spire/contrib/performance/decorators.py +16 -6
  136. django_spire/contrib/performance/tests/__init__.py +0 -0
  137. django_spire/contrib/performance/tests/test_performance.py +107 -0
  138. django_spire/contrib/queryset/enums.py +3 -1
  139. django_spire/contrib/queryset/filter_tools.py +10 -5
  140. django_spire/contrib/queryset/mixins.py +16 -16
  141. django_spire/contrib/queryset/tests/__init__.py +0 -0
  142. django_spire/contrib/queryset/tests/test_queryset.py +137 -0
  143. django_spire/contrib/seeding/field/base.py +13 -7
  144. django_spire/contrib/seeding/field/callable.py +8 -1
  145. django_spire/contrib/seeding/field/cleaners.py +5 -5
  146. django_spire/contrib/seeding/field/custom.py +20 -10
  147. django_spire/contrib/seeding/field/django/seeder.py +8 -6
  148. django_spire/contrib/seeding/field/enums.py +7 -5
  149. django_spire/contrib/seeding/field/override.py +16 -6
  150. django_spire/contrib/seeding/field/static.py +9 -2
  151. django_spire/contrib/seeding/field/tests/test_base.py +18 -14
  152. django_spire/contrib/seeding/field/tests/test_callable.py +13 -9
  153. django_spire/contrib/seeding/field/tests/test_cleaners.py +51 -38
  154. django_spire/contrib/seeding/field/tests/test_static.py +13 -9
  155. django_spire/contrib/seeding/intelligence/bots/seeder_generator_bot.py +2 -0
  156. django_spire/contrib/seeding/intelligence/intel.py +5 -1
  157. django_spire/contrib/seeding/intelligence/prompts/factory.py +6 -1
  158. django_spire/contrib/seeding/intelligence/prompts/foreign_key_selection_prompt.py +6 -1
  159. django_spire/contrib/seeding/intelligence/prompts/generate_django_model_seeder_prompts.py +2 -0
  160. django_spire/contrib/seeding/intelligence/prompts/generic_relationship_selection_prompt.py +7 -1
  161. django_spire/contrib/seeding/intelligence/prompts/hierarchical_selection_prompt.py +6 -2
  162. django_spire/contrib/seeding/intelligence/prompts/model_field_choices_prompt.py +8 -2
  163. django_spire/contrib/seeding/intelligence/prompts/negation_prompt.py +2 -0
  164. django_spire/contrib/seeding/intelligence/prompts/objective_prompt.py +6 -1
  165. django_spire/contrib/seeding/management/commands/seeding.py +9 -3
  166. django_spire/contrib/seeding/management/example.py +2 -0
  167. django_spire/contrib/seeding/model/base.py +16 -7
  168. django_spire/contrib/seeding/model/config.py +31 -15
  169. django_spire/contrib/seeding/model/django/config.py +13 -13
  170. django_spire/contrib/seeding/model/django/seeder.py +4 -4
  171. django_spire/contrib/seeding/model/django/tests/test_seeder.py +34 -23
  172. django_spire/contrib/seeding/model/enums.py +2 -0
  173. django_spire/contrib/seeding/tests/test_config.py +71 -0
  174. django_spire/contrib/seeding/tests/test_custom.py +35 -0
  175. django_spire/contrib/seeding/tests/test_enums.py +40 -0
  176. django_spire/contrib/seeding/tests/test_intel.py +32 -0
  177. django_spire/contrib/seeding/tests/test_override.py +63 -0
  178. django_spire/contrib/service/__init__.py +2 -2
  179. django_spire/contrib/service/django_model_service.py +16 -15
  180. django_spire/contrib/service/exceptions.py +5 -3
  181. django_spire/contrib/service/tests/__init__.py +0 -0
  182. django_spire/contrib/service/tests/test_service.py +153 -0
  183. django_spire/contrib/session/apps.py +2 -0
  184. django_spire/contrib/session/controller.py +48 -42
  185. django_spire/contrib/session/templatetags/session_tags.py +11 -2
  186. django_spire/contrib/session/tests/test_session_controller.py +117 -53
  187. django_spire/contrib/tests/__init__.py +0 -0
  188. django_spire/contrib/tests/test_utils.py +37 -0
  189. django_spire/contrib/utils.py +4 -1
  190. django_spire/core/apps.py +2 -0
  191. django_spire/core/converters/tests/test_to_data.py +353 -0
  192. django_spire/core/converters/tests/test_to_enums.py +61 -41
  193. django_spire/core/converters/tests/test_to_pydantic.py +138 -109
  194. django_spire/core/converters/to_data.py +29 -10
  195. django_spire/core/converters/to_enums.py +4 -2
  196. django_spire/core/converters/to_pydantic.py +22 -22
  197. django_spire/core/decorators.py +19 -6
  198. django_spire/core/forms/widgets.py +4 -0
  199. django_spire/core/maps.py +3 -1
  200. django_spire/core/middleware/maintenance.py +3 -3
  201. django_spire/core/middleware.py +8 -6
  202. django_spire/core/redirect/__init__.py +5 -0
  203. django_spire/core/redirect/generic_redirect.py +1 -2
  204. django_spire/core/redirect/tests/__init__.py +0 -0
  205. django_spire/core/redirect/tests/test_generic_redirect.py +34 -0
  206. django_spire/core/{tests/tests_redirect.py → redirect/tests/test_safe_redirect.py} +55 -81
  207. django_spire/core/shortcuts.py +3 -3
  208. django_spire/core/static/django_spire/css/app-layout.css +1 -1
  209. django_spire/core/static/django_spire/css/app-navigation.css +3 -3
  210. django_spire/core/static/django_spire/css/bootstrap-override.css +4 -0
  211. django_spire/core/tag/admin.py +12 -0
  212. django_spire/core/tag/intelligence/tag_set_bot.py +2 -0
  213. django_spire/core/tag/mixins.py +2 -0
  214. django_spire/core/tag/models.py +2 -0
  215. django_spire/core/tag/querysets.py +2 -0
  216. django_spire/core/tag/service/tag_service.py +6 -3
  217. django_spire/core/tag/tests/test_intelligence.py +9 -9
  218. django_spire/core/tag/tests/test_tags.py +44 -54
  219. django_spire/core/tag/tests/test_tools.py +191 -0
  220. django_spire/core/tag/tools.py +3 -0
  221. django_spire/core/templates/django_spire/card/card.html +5 -2
  222. django_spire/core/templates/django_spire/card/title_card.html +9 -4
  223. django_spire/core/templates/django_spire/infinite_scroll/base.html +1 -0
  224. django_spire/core/templates/django_spire/navigation/side_navigation.html +19 -24
  225. django_spire/core/templates/django_spire/page/full_page.html +46 -16
  226. django_spire/core/templates/django_spire/table/base.html +4 -2
  227. django_spire/core/templatetags/json.py +6 -2
  228. django_spire/core/templatetags/message.py +13 -8
  229. django_spire/core/templatetags/string_formating.py +8 -5
  230. django_spire/core/templatetags/tests/__init__.py +0 -0
  231. django_spire/core/templatetags/tests/test_templatetags.py +427 -0
  232. django_spire/core/templatetags/variable_types.py +17 -9
  233. django_spire/core/tests/test_cases.py +1 -1
  234. django_spire/core/tests/test_conf.py +43 -0
  235. django_spire/core/tests/test_consts.py +28 -0
  236. django_spire/core/tests/test_context_processors.py +93 -0
  237. django_spire/core/tests/test_decorators.py +95 -0
  238. django_spire/core/tests/test_django_spire_utils.py +56 -0
  239. django_spire/core/tests/test_exceptions.py +37 -0
  240. django_spire/core/tests/test_models.py +54 -0
  241. django_spire/core/tests/test_settings.py +45 -0
  242. django_spire/core/tests/test_shortcuts.py +74 -0
  243. django_spire/core/tests/test_urls.py +16 -0
  244. django_spire/core/tests/test_utils.py +58 -0
  245. django_spire/core/urls.py +4 -1
  246. django_spire/core/utils.py +12 -8
  247. django_spire/exceptions.py +16 -1
  248. django_spire/file/admin.py +4 -2
  249. django_spire/file/apps.py +8 -10
  250. django_spire/file/fields.py +7 -7
  251. django_spire/file/forms.py +1 -1
  252. django_spire/file/interfaces.py +15 -15
  253. django_spire/file/mixins.py +1 -4
  254. django_spire/file/models.py +3 -5
  255. django_spire/file/tests/factories.py +59 -0
  256. django_spire/file/tests/test_admin.py +69 -0
  257. django_spire/file/tests/test_apps.py +24 -0
  258. django_spire/file/tests/test_fields.py +114 -0
  259. django_spire/file/tests/test_forms.py +20 -0
  260. django_spire/file/tests/test_interfaces.py +183 -0
  261. django_spire/file/tests/test_models.py +82 -0
  262. django_spire/file/tests/test_querysets.py +102 -0
  263. django_spire/file/tests/test_utils.py +32 -0
  264. django_spire/file/tests/test_views.py +145 -0
  265. django_spire/file/tests/test_widgets.py +82 -0
  266. django_spire/file/tools.py +8 -2
  267. django_spire/file/views.py +7 -3
  268. django_spire/file/widgets.py +12 -12
  269. django_spire/help_desk/admin.py +15 -0
  270. django_spire/help_desk/apps.py +2 -0
  271. django_spire/help_desk/auth/controller.py +2 -0
  272. django_spire/help_desk/choices.py +2 -0
  273. django_spire/help_desk/enums.py +2 -0
  274. django_spire/help_desk/exceptions.py +31 -3
  275. django_spire/help_desk/forms.py +2 -0
  276. django_spire/help_desk/models.py +2 -0
  277. django_spire/help_desk/querysets.py +4 -1
  278. django_spire/help_desk/services/notification_service.py +26 -27
  279. django_spire/help_desk/services/service.py +2 -3
  280. django_spire/help_desk/tests/factories.py +8 -3
  281. django_spire/help_desk/tests/test_admin.py +41 -0
  282. django_spire/help_desk/tests/test_apps.py +41 -0
  283. django_spire/help_desk/tests/test_choices.py +50 -0
  284. django_spire/help_desk/tests/test_controller.py +87 -0
  285. django_spire/help_desk/tests/test_enums.py +18 -0
  286. django_spire/help_desk/tests/test_exceptions.py +37 -0
  287. django_spire/help_desk/tests/test_forms.py +89 -0
  288. django_spire/help_desk/tests/test_models.py +59 -0
  289. django_spire/help_desk/tests/test_querysets.py +38 -0
  290. django_spire/help_desk/tests/test_services/test_notification_service.py +15 -8
  291. django_spire/help_desk/tests/test_services/test_service.py +92 -0
  292. django_spire/help_desk/tests/test_urls/test_form_urls.py +6 -6
  293. django_spire/help_desk/tests/test_urls/test_page_urls.py +8 -9
  294. django_spire/help_desk/tests/test_views/test_form_views.py +46 -19
  295. django_spire/help_desk/tests/test_views/test_page_views.py +32 -9
  296. django_spire/help_desk/urls/__init__.py +4 -1
  297. django_spire/help_desk/urls/form_urls.py +3 -0
  298. django_spire/help_desk/urls/page_urls.py +3 -0
  299. django_spire/help_desk/views/form_views.py +13 -5
  300. django_spire/help_desk/views/page_views.py +11 -3
  301. django_spire/history/activity/admin.py +2 -0
  302. django_spire/history/activity/apps.py +3 -1
  303. django_spire/history/activity/mixins.py +13 -7
  304. django_spire/history/activity/models.py +6 -5
  305. django_spire/history/activity/querysets.py +2 -0
  306. django_spire/history/activity/tests/__init__.py +0 -0
  307. django_spire/history/activity/tests/test_activity.py +176 -0
  308. django_spire/history/admin.py +9 -2
  309. django_spire/history/choices.py +3 -0
  310. django_spire/history/models.py +5 -5
  311. django_spire/history/tests/test_admin.py +93 -0
  312. django_spire/history/tests/test_history.py +101 -0
  313. django_spire/history/tests/test_mixins.py +84 -0
  314. django_spire/history/viewed/admin.py +3 -1
  315. django_spire/history/viewed/apps.py +3 -1
  316. django_spire/history/viewed/models.py +2 -0
  317. django_spire/history/viewed/tests/__init__.py +0 -0
  318. django_spire/history/viewed/tests/test_viewed.py +46 -0
  319. django_spire/knowledge/auth/tests/__init__.py +0 -0
  320. django_spire/knowledge/auth/tests/test_controller.py +116 -0
  321. django_spire/knowledge/collection/admin.py +5 -1
  322. django_spire/knowledge/collection/models.py +3 -1
  323. django_spire/knowledge/collection/seeding/seed.py +1 -0
  324. django_spire/knowledge/collection/services/factory_service.py +10 -11
  325. django_spire/knowledge/collection/services/ordering_service.py +1 -2
  326. django_spire/knowledge/collection/services/service.py +5 -10
  327. django_spire/knowledge/collection/services/tag_service.py +5 -2
  328. django_spire/knowledge/collection/tests/factories.py +28 -1
  329. django_spire/knowledge/collection/tests/test_models.py +48 -0
  330. django_spire/knowledge/collection/tests/test_querysets.py +93 -0
  331. django_spire/knowledge/collection/tests/test_services/test_factory_service.py +100 -0
  332. django_spire/knowledge/collection/tests/test_services/test_services.py +160 -0
  333. django_spire/knowledge/collection/tests/test_urls/test_form_urls.py +21 -3
  334. django_spire/knowledge/collection/tests/test_urls/test_json_urls.py +39 -1
  335. django_spire/knowledge/collection/tests/test_urls/test_page_urls.py +12 -4
  336. django_spire/knowledge/collection/urls/__init__.py +3 -0
  337. django_spire/knowledge/collection/urls/form_urls.py +2 -0
  338. django_spire/knowledge/collection/urls/json_urls.py +2 -0
  339. django_spire/knowledge/collection/urls/page_urls.py +2 -0
  340. django_spire/knowledge/collection/views/form_views.py +4 -4
  341. django_spire/knowledge/collection/views/json_views.py +5 -1
  342. django_spire/knowledge/collection/views/page_views.py +5 -2
  343. django_spire/knowledge/entry/admin.py +7 -1
  344. django_spire/knowledge/entry/forms.py +2 -0
  345. django_spire/knowledge/entry/models.py +2 -0
  346. django_spire/knowledge/entry/seeding/seed.py +3 -0
  347. django_spire/knowledge/entry/services/automation_service.py +5 -4
  348. django_spire/knowledge/entry/services/factory_service.py +7 -5
  349. django_spire/knowledge/entry/services/service.py +4 -7
  350. django_spire/knowledge/entry/services/tag_service.py +0 -1
  351. django_spire/knowledge/entry/services/tool_service.py +1 -0
  352. django_spire/knowledge/entry/services/transformation_services.py +1 -5
  353. django_spire/knowledge/entry/tests/factories.py +1 -2
  354. django_spire/knowledge/entry/tests/test_factory_service.py +20 -0
  355. django_spire/knowledge/entry/tests/test_models.py +41 -0
  356. django_spire/knowledge/entry/tests/test_querysets.py +71 -0
  357. django_spire/knowledge/entry/tests/test_services.py +94 -0
  358. django_spire/knowledge/entry/tests/test_urls/test_form_urls.py +9 -14
  359. django_spire/knowledge/entry/tests/test_urls/test_json_urls.py +48 -5
  360. django_spire/knowledge/entry/tests/test_urls/test_page_urls.py +6 -8
  361. django_spire/knowledge/entry/tests/test_urls/test_template_urls.py +40 -0
  362. django_spire/knowledge/entry/urls/form_urls.py +2 -0
  363. django_spire/knowledge/entry/urls/json_urls.py +2 -0
  364. django_spire/knowledge/entry/urls/page_urls.py +2 -0
  365. django_spire/knowledge/entry/urls/template_urls.py +2 -0
  366. django_spire/knowledge/entry/version/block/choices.py +2 -0
  367. django_spire/knowledge/entry/version/block/data/data.py +1 -0
  368. django_spire/knowledge/entry/version/block/data/list/data.py +8 -13
  369. django_spire/knowledge/entry/version/block/data/list/maps.py +3 -0
  370. django_spire/knowledge/entry/version/block/data/list/meta.py +1 -2
  371. django_spire/knowledge/entry/version/block/data/list/tests/__init__.py +0 -0
  372. django_spire/knowledge/entry/version/block/data/list/tests/test_maps.py +32 -0
  373. django_spire/knowledge/entry/version/block/data/list/tests/test_meta.py +58 -0
  374. django_spire/knowledge/entry/version/block/data/maps.py +3 -6
  375. django_spire/knowledge/entry/version/block/models.py +7 -5
  376. django_spire/knowledge/entry/version/block/seeding/constants.py +5 -4
  377. django_spire/knowledge/entry/version/block/services/service.py +2 -3
  378. django_spire/knowledge/entry/version/block/tests/factories.py +4 -10
  379. django_spire/knowledge/entry/version/block/tests/test_choices.py +56 -0
  380. django_spire/knowledge/entry/version/block/tests/test_data.py +90 -0
  381. django_spire/knowledge/entry/version/block/tests/test_maps.py +37 -0
  382. django_spire/knowledge/entry/version/block/tests/test_models.py +55 -0
  383. django_spire/knowledge/entry/version/block/tests/test_querysets.py +35 -0
  384. django_spire/knowledge/entry/version/block/tests/test_services.py +65 -0
  385. django_spire/knowledge/entry/version/choices.py +2 -0
  386. django_spire/knowledge/entry/version/converters/converter.py +1 -1
  387. django_spire/knowledge/entry/version/converters/docx_converter.py +4 -7
  388. django_spire/knowledge/entry/version/converters/markdown_converter.py +20 -20
  389. django_spire/knowledge/entry/version/maps.py +4 -5
  390. django_spire/knowledge/entry/version/querysets.py +1 -1
  391. django_spire/knowledge/entry/version/seeding/seeder.py +1 -2
  392. django_spire/knowledge/entry/version/services/processor_service.py +5 -4
  393. django_spire/knowledge/entry/version/services/service.py +1 -2
  394. django_spire/knowledge/entry/version/tests/factories.py +2 -2
  395. django_spire/knowledge/entry/version/tests/test_choices.py +18 -0
  396. django_spire/knowledge/entry/version/tests/test_converters/test_docx_converter.py +56 -8
  397. django_spire/knowledge/entry/version/tests/test_converters/test_markdown_converter.py +78 -0
  398. django_spire/knowledge/entry/version/tests/test_maps.py +58 -0
  399. django_spire/knowledge/entry/version/tests/test_models.py +23 -0
  400. django_spire/knowledge/entry/version/tests/test_querysets.py +26 -0
  401. django_spire/knowledge/entry/version/tests/test_services.py +62 -0
  402. django_spire/knowledge/entry/version/tests/test_urls/test_json_urls.py +27 -8
  403. django_spire/knowledge/entry/version/tests/test_urls/test_page_urls.py +15 -8
  404. django_spire/knowledge/entry/version/tests/test_urls/test_redirect_urls.py +38 -0
  405. django_spire/knowledge/entry/version/urls/__init__.py +3 -0
  406. django_spire/knowledge/entry/version/urls/json_urls.py +2 -1
  407. django_spire/knowledge/entry/version/urls/page_urls.py +2 -0
  408. django_spire/knowledge/entry/version/urls/redirect_urls.py +2 -0
  409. django_spire/knowledge/entry/version/views/json_views.py +5 -1
  410. django_spire/knowledge/entry/version/views/page_views.py +10 -3
  411. django_spire/knowledge/entry/version/views/redirect_views.py +5 -1
  412. django_spire/knowledge/entry/views/form_views.py +16 -8
  413. django_spire/knowledge/entry/views/json_views.py +3 -1
  414. django_spire/knowledge/entry/views/page_views.py +8 -2
  415. django_spire/knowledge/entry/views/template_views.py +7 -1
  416. django_spire/knowledge/exceptions.py +2 -1
  417. django_spire/knowledge/intelligence/intel/answer_intel.py +2 -1
  418. django_spire/knowledge/intelligence/intel/entry_intel.py +0 -1
  419. django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +4 -5
  420. django_spire/knowledge/models.py +1 -2
  421. django_spire/knowledge/tests/__init__.py +0 -0
  422. django_spire/knowledge/tests/test_templatetags.py +40 -0
  423. django_spire/knowledge/tests/test_urls/__init__.py +0 -0
  424. django_spire/knowledge/tests/test_urls/test_page_urls.py +24 -0
  425. django_spire/knowledge/urls/__init__.py +2 -0
  426. django_spire/knowledge/urls/page_urls.py +2 -0
  427. django_spire/knowledge/views/page_views.py +8 -3
  428. django_spire/notification/admin.py +3 -1
  429. django_spire/notification/app/admin.py +2 -0
  430. django_spire/notification/app/apps.py +3 -1
  431. django_spire/notification/app/exceptions.py +9 -2
  432. django_spire/notification/app/models.py +8 -4
  433. django_spire/notification/app/processor.py +22 -26
  434. django_spire/notification/app/querysets.py +2 -0
  435. django_spire/notification/app/tests/__init__.py +0 -0
  436. django_spire/notification/app/tests/factories.py +34 -0
  437. django_spire/notification/app/tests/test_apps.py +24 -0
  438. django_spire/notification/app/tests/test_models.py +72 -0
  439. django_spire/notification/app/tests/test_processor.py +111 -0
  440. django_spire/notification/app/tests/test_querysets.py +90 -0
  441. django_spire/notification/app/tests/test_views/__init__.py +0 -0
  442. django_spire/notification/app/tests/test_views/test_json_views.py +48 -0
  443. django_spire/notification/app/tests/test_views/test_page_views.py +19 -0
  444. django_spire/notification/app/urls/__init__.py +3 -1
  445. django_spire/notification/app/urls/json_urls.py +6 -4
  446. django_spire/notification/app/urls/page_urls.py +4 -3
  447. django_spire/notification/app/urls/template_urls.py +4 -2
  448. django_spire/notification/apps.py +4 -1
  449. django_spire/notification/email/admin.py +5 -1
  450. django_spire/notification/email/apps.py +3 -1
  451. django_spire/notification/email/exceptions.py +4 -2
  452. django_spire/notification/email/helper.py +5 -3
  453. django_spire/notification/email/models.py +4 -0
  454. django_spire/notification/email/processor.py +19 -15
  455. django_spire/notification/email/querysets.py +3 -0
  456. django_spire/notification/email/tests/__init__.py +0 -0
  457. django_spire/notification/email/tests/factories.py +35 -0
  458. django_spire/notification/email/tests/test_apps.py +24 -0
  459. django_spire/notification/email/tests/test_models.py +52 -0
  460. django_spire/notification/email/tests/test_processor.py +92 -0
  461. django_spire/notification/email/tests/test_querysets.py +43 -0
  462. django_spire/notification/exceptions.py +17 -2
  463. django_spire/notification/managers.py +7 -1
  464. django_spire/notification/maps.py +4 -1
  465. django_spire/notification/mixins.py +2 -0
  466. django_spire/notification/models.py +3 -1
  467. django_spire/notification/processors/notification.py +12 -5
  468. django_spire/notification/processors/processor.py +2 -0
  469. django_spire/notification/processors/tests/__init__.py +0 -0
  470. django_spire/notification/processors/tests/test_notification.py +106 -0
  471. django_spire/notification/push/admin.py +10 -1
  472. django_spire/notification/push/apps.py +3 -1
  473. django_spire/notification/push/models.py +2 -3
  474. django_spire/notification/push/tests/__init__.py +0 -0
  475. django_spire/notification/push/tests/test_apps.py +24 -0
  476. django_spire/notification/push/tests/test_models.py +28 -0
  477. django_spire/notification/querysets.py +7 -1
  478. django_spire/notification/sms/admin.py +2 -0
  479. django_spire/notification/sms/apps.py +4 -1
  480. django_spire/notification/sms/automations.py +2 -0
  481. django_spire/notification/sms/choices.py +2 -0
  482. django_spire/notification/sms/exceptions.py +19 -5
  483. django_spire/notification/sms/helper.py +33 -23
  484. django_spire/notification/sms/models.py +5 -1
  485. django_spire/notification/sms/processor.py +20 -20
  486. django_spire/notification/sms/querysets.py +2 -0
  487. django_spire/notification/sms/tests/factories.py +33 -0
  488. django_spire/notification/sms/tests/test_apps.py +24 -0
  489. django_spire/notification/sms/tests/test_automation.py +38 -0
  490. django_spire/notification/sms/tests/test_choices.py +15 -0
  491. django_spire/notification/sms/tests/test_consts.py +17 -0
  492. django_spire/notification/sms/tests/test_exceptions.py +27 -0
  493. django_spire/notification/sms/tests/test_helper.py +50 -0
  494. django_spire/notification/sms/tests/test_models.py +81 -0
  495. django_spire/notification/sms/tests/test_processor.py +107 -0
  496. django_spire/notification/sms/tests/test_tools.py +25 -11
  497. django_spire/notification/sms/tools.py +16 -5
  498. django_spire/notification/sms/urls/__init__.py +3 -1
  499. django_spire/notification/sms/urls/media_urls.py +2 -0
  500. django_spire/notification/sms/views/media_views.py +14 -4
  501. django_spire/notification/tests/__init__.py +0 -0
  502. django_spire/notification/tests/factories.py +26 -0
  503. django_spire/notification/tests/test_admin.py +55 -0
  504. django_spire/notification/tests/test_apps.py +30 -0
  505. django_spire/notification/tests/test_automation.py +18 -0
  506. django_spire/notification/tests/test_choices.py +59 -0
  507. django_spire/notification/tests/test_exceptions.py +58 -0
  508. django_spire/notification/tests/test_managers.py +100 -0
  509. django_spire/notification/tests/test_maps.py +31 -0
  510. django_spire/notification/tests/test_models.py +76 -0
  511. django_spire/notification/tests/test_querysets.py +184 -0
  512. django_spire/notification/tests/test_utils.py +23 -0
  513. django_spire/notification/urls.py +3 -1
  514. django_spire/notification/utils.py +3 -1
  515. django_spire/settings.py +3 -0
  516. django_spire/theme/tests/test_context_processor.py +15 -13
  517. django_spire/theme/tests/test_enums.py +2 -2
  518. django_spire/theme/tests/test_filesystem.py +2 -5
  519. django_spire/theme/tests/test_integration.py +12 -12
  520. django_spire/theme/tests/test_model.py +40 -38
  521. django_spire/theme/tests/test_views/test_json_views.py +33 -33
  522. django_spire/theme/urls/json_urls.py +3 -0
  523. django_spire/theme/urls/page_urls.py +3 -0
  524. django_spire/urls.py +19 -15
  525. django_spire/utils.py +13 -4
  526. {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/METADATA +1 -1
  527. {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/RECORD +530 -358
  528. django_spire/contrib/options/tests/test_unit.py +0 -148
  529. django_spire/contrib/seeding/tests/test_seeding.py +0 -25
  530. django_spire/core/tests/test_templatetags.py +0 -117
  531. django_spire/core/tests/tests_shortcuts.py +0 -73
  532. django_spire/history/activity/tests.py +0 -3
  533. django_spire/history/activity/views.py +0 -3
  534. django_spire/knowledge/collection/tests/test_services/test_transformation_service.py +0 -71
  535. django_spire/notification/app/tests.py +0 -3
  536. {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/WHEEL +0 -0
  537. {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/licenses/LICENSE.md +0 -0
  538. {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/top_level.txt +0 -0
@@ -1,18 +1,16 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from typing import TYPE_CHECKING
4
+
3
5
  from django.conf import settings
4
6
  from django.contrib.auth.models import User
5
7
  from django.contrib.contenttypes.models import ContentType
6
8
  from django.contrib.sites.models import Site
7
9
  from django.db.models import Q
8
- from typing import TYPE_CHECKING
9
-
10
10
  from django.urls import reverse
11
11
 
12
12
  from django_spire.contrib.service import BaseDjangoModelService
13
- from django_spire.help_desk.exceptions import (
14
- TicketEventNotificationTypeNotSupportedError,
15
- )
13
+ from django_spire.help_desk.exceptions import TicketEventNotificationTypeNotSupportedError
16
14
  from django_spire.help_desk.enums import TicketEventType
17
15
  from django_spire.notification.app.models import AppNotification
18
16
  from django_spire.notification.choices import NotificationTypeChoices
@@ -63,9 +61,9 @@ class HelpDeskTicketNotificationService(BaseDjangoModelService['HelpDeskTicket']
63
61
  return content_map[event_type]
64
62
 
65
63
  def _get_ticket_event_notification_body(
66
- self,
67
- event_type: TicketEventType,
68
- notification_type: NotificationTypeChoices,
64
+ self,
65
+ event_type: TicketEventType,
66
+ notification_type: NotificationTypeChoices,
69
67
  ) -> str:
70
68
  content_map = {
71
69
  TicketEventType.NEW: {
@@ -86,14 +84,11 @@ class HelpDeskTicketNotificationService(BaseDjangoModelService['HelpDeskTicket']
86
84
  try:
87
85
  return content_map[event_type][notification_type]
88
86
  except KeyError:
89
- raise TicketEventNotificationTypeNotSupportedError(
90
- f'Combination of event type and notification type not supported: '
91
- f'Event type {event_type} - Notification type {notification_type}'
92
- )
87
+ raise TicketEventNotificationTypeNotSupportedError(event_type, notification_type) from None
93
88
 
94
89
  def _get_ticket_notification_url(
95
- self,
96
- notification_type: NotificationTypeChoices
90
+ self,
91
+ notification_type: NotificationTypeChoices
97
92
  ) -> str:
98
93
  path = reverse('django_spire:help_desk:page:detail', kwargs={'pk': self.obj.pk})
99
94
 
@@ -104,12 +99,12 @@ class HelpDeskTicketNotificationService(BaseDjangoModelService['HelpDeskTicket']
104
99
  return path
105
100
 
106
101
  def _create_ticket_event_notification_for_user(
107
- self,
108
- notification_type: NotificationTypeChoices,
109
- body: str,
110
- title: str,
111
- url: str,
112
- user: User,
102
+ self,
103
+ notification_type: NotificationTypeChoices,
104
+ body: str,
105
+ title: str,
106
+ url: str,
107
+ user: User,
113
108
  ) -> EmailNotification | AppNotification:
114
109
  base_notification = Notification(
115
110
  content_type=ContentType.objects.get_for_model(self.obj),
@@ -128,22 +123,25 @@ class HelpDeskTicketNotificationService(BaseDjangoModelService['HelpDeskTicket']
128
123
  settings.DEVELOPMENT_EMAIL if settings.DEBUG else user.email
129
124
  )
130
125
  )
131
- elif notification_type == NotificationTypeChoices.APP:
126
+
127
+ if notification_type == NotificationTypeChoices.APP:
132
128
  return AppNotification(notification=base_notification)
133
- else:
134
- raise TicketEventNotificationTypeNotSupportedError(notification_type)
129
+
130
+ raise TicketEventNotificationTypeNotSupportedError(None, notification_type)
135
131
 
136
132
  def _create_ticket_event_notifications(
137
- self,
138
- users: list[User],
139
- notification_type: NotificationTypeChoices,
140
- event_type: TicketEventType,
133
+ self,
134
+ users: list[User],
135
+ notification_type: NotificationTypeChoices,
136
+ event_type: TicketEventType
141
137
  ):
142
138
  title = self._get_ticket_event_notification_title(event_type)
139
+
143
140
  body = self._get_ticket_event_notification_body(
144
141
  event_type=event_type,
145
142
  notification_type=notification_type,
146
143
  )
144
+
147
145
  url = self._get_ticket_notification_url(notification_type)
148
146
 
149
147
  notifications = [
@@ -164,6 +162,7 @@ class HelpDeskTicketNotificationService(BaseDjangoModelService['HelpDeskTicket']
164
162
  if notification
165
163
  ]
166
164
  )
165
+
167
166
  NOTIFICATION_TYPE_CHOICE_TO_MODEL_MAP[notification_type].objects.bulk_create(
168
167
  notifications
169
168
  )
@@ -1,10 +1,9 @@
1
1
  from __future__ import annotations
2
- from typing import TYPE_CHECKING
3
2
 
3
+ from typing import TYPE_CHECKING
4
4
 
5
5
  from django_spire.contrib.service import BaseDjangoModelService
6
- from django_spire.help_desk.services.notification_service import \
7
- HelpDeskTicketNotificationService
6
+ from django_spire.help_desk.services.notification_service import HelpDeskTicketNotificationService
8
7
 
9
8
  if TYPE_CHECKING:
10
9
  from django.contrib.auth.models import User
@@ -1,11 +1,16 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.contrib.auth.models import User
2
4
 
3
- from django_spire.help_desk.choices import HelpDeskTicketPriorityChoices, \
4
- HelpDeskTicketPurposeChoices, HelpDeskTicketStatusChoices
5
+ from django_spire.help_desk.choices import (
6
+ HelpDeskTicketPriorityChoices,
7
+ HelpDeskTicketPurposeChoices,
8
+ HelpDeskTicketStatusChoices,
9
+ )
5
10
  from django_spire.help_desk.models import HelpDeskTicket
6
11
 
7
12
 
8
- def create_test_helpdesk_ticket(**kwargs):
13
+ def create_test_helpdesk_ticket(**kwargs) -> HelpDeskTicket:
9
14
  if 'created_by' not in kwargs:
10
15
  kwargs['created_by'] = User.objects.first()
11
16
 
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+ from django.contrib.admin.sites import AdminSite
4
+ from django.test import RequestFactory
5
+
6
+ from django_spire.core.tests.test_cases import BaseTestCase
7
+ from django_spire.help_desk.admin import HelpDeskTicketAdmin
8
+ from django_spire.help_desk.models import HelpDeskTicket
9
+ from django_spire.help_desk.tests.factories import create_test_helpdesk_ticket
10
+
11
+
12
+ class HelpDeskTicketAdminTests(BaseTestCase):
13
+ def setUp(self):
14
+ super().setUp()
15
+
16
+ self.site = AdminSite()
17
+ self.admin = HelpDeskTicketAdmin(HelpDeskTicket, self.site)
18
+ self.factory = RequestFactory()
19
+ self.ticket = create_test_helpdesk_ticket()
20
+
21
+ def test_list_display(self):
22
+ expected = ('pk', 'purpose', 'priority', 'status', 'created_by', 'created_datetime')
23
+ assert self.admin.list_display == expected
24
+
25
+ def test_list_filter(self):
26
+ expected = ('priority', 'purpose', 'status', 'is_active', 'is_deleted')
27
+ assert self.admin.list_filter == expected
28
+
29
+ def test_ordering(self):
30
+ assert self.admin.ordering == ('-created_datetime',)
31
+
32
+ def test_raw_id_fields(self):
33
+ assert self.admin.raw_id_fields == ('created_by',)
34
+
35
+ def test_readonly_fields(self):
36
+ expected = ('created_by', 'created_datetime', 'is_active', 'is_deleted')
37
+ assert self.admin.readonly_fields == expected
38
+
39
+ def test_search_fields(self):
40
+ expected = ('description', 'created_by__username', 'created_by__email')
41
+ assert self.admin.search_fields == expected
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+ from django.apps import apps
4
+
5
+ from django_spire.core.tests.test_cases import BaseTestCase
6
+ from django_spire.help_desk.apps import HelpDeskConfig
7
+
8
+
9
+ class HelpDeskConfigTests(BaseTestCase):
10
+ def test_app_name(self):
11
+ assert HelpDeskConfig.name == 'django_spire.help_desk'
12
+
13
+ def test_app_label(self):
14
+ assert HelpDeskConfig.label == 'django_spire_help_desk'
15
+
16
+ def test_default_auto_field(self):
17
+ assert HelpDeskConfig.default_auto_field == 'django.db.models.BigAutoField'
18
+
19
+ def test_required_apps(self):
20
+ assert HelpDeskConfig.REQUIRED_APPS == ('django_spire_core',)
21
+
22
+ def test_urlpatterns_include(self):
23
+ assert HelpDeskConfig.URLPATTERNS_INCLUDE == 'django_spire.help_desk.urls'
24
+
25
+ def test_urlpatterns_namespace(self):
26
+ assert HelpDeskConfig.URLPATTERNS_NAMESPACE == 'help_desk'
27
+
28
+ def test_model_permissions(self):
29
+ expected = (
30
+ {
31
+ 'name': 'help_desk',
32
+ 'verbose_name': 'Help Desk',
33
+ 'model_class_path': 'django_spire.help_desk.models.HelpDeskTicket',
34
+ 'is_proxy_model': False,
35
+ },
36
+ )
37
+
38
+ assert expected == HelpDeskConfig.MODEL_PERMISSIONS
39
+
40
+ def test_app_is_installed(self):
41
+ assert apps.is_installed('django_spire.help_desk')
@@ -0,0 +1,50 @@
1
+ from __future__ import annotations
2
+
3
+ from django_spire.core.tests.test_cases import BaseTestCase
4
+ from django_spire.help_desk.choices import (
5
+ HelpDeskTicketPriorityChoices,
6
+ HelpDeskTicketPurposeChoices,
7
+ HelpDeskTicketStatusChoices,
8
+ )
9
+
10
+
11
+ class HelpDeskTicketPurposeChoicesTests(BaseTestCase):
12
+ def test_app_value(self):
13
+ assert HelpDeskTicketPurposeChoices.APP == 'app'
14
+
15
+ def test_company_value(self):
16
+ assert HelpDeskTicketPurposeChoices.COMPANY == 'comp'
17
+
18
+ def test_choices_count(self):
19
+ assert len(HelpDeskTicketPurposeChoices.choices) == 2
20
+
21
+
22
+ class HelpDeskTicketStatusChoicesTests(BaseTestCase):
23
+ def test_ready_value(self):
24
+ assert HelpDeskTicketStatusChoices.READY == 'read'
25
+
26
+ def test_inprogress_value(self):
27
+ assert HelpDeskTicketStatusChoices.INPROGRESS == 'prog'
28
+
29
+ def test_done_value(self):
30
+ assert HelpDeskTicketStatusChoices.DONE == 'done'
31
+
32
+ def test_choices_count(self):
33
+ assert len(HelpDeskTicketStatusChoices.choices) == 3
34
+
35
+
36
+ class HelpDeskTicketPriorityChoicesTests(BaseTestCase):
37
+ def test_low_value(self):
38
+ assert HelpDeskTicketPriorityChoices.LOW == 'low'
39
+
40
+ def test_medium_value(self):
41
+ assert HelpDeskTicketPriorityChoices.MEDIUM == 'med'
42
+
43
+ def test_high_value(self):
44
+ assert HelpDeskTicketPriorityChoices.HIGH == 'high'
45
+
46
+ def test_urgent_value(self):
47
+ assert HelpDeskTicketPriorityChoices.URGENT == 'urge'
48
+
49
+ def test_choices_count(self):
50
+ assert len(HelpDeskTicketPriorityChoices.choices) == 4
@@ -0,0 +1,87 @@
1
+ from __future__ import annotations
2
+
3
+ from django.contrib.auth.models import Permission
4
+ from django.contrib.contenttypes.models import ContentType
5
+ from django.test import RequestFactory
6
+
7
+ from django_spire.auth.user.tests.factories import create_user
8
+ from django_spire.core.tests.test_cases import BaseTestCase
9
+ from django_spire.help_desk.auth.controller import BaseHelpDeskAuthController
10
+ from django_spire.help_desk.models import HelpDeskTicket
11
+
12
+
13
+ class BaseHelpDeskAuthControllerTests(BaseTestCase):
14
+ def setUp(self):
15
+ super().setUp()
16
+
17
+ self.factory = RequestFactory()
18
+ self.user = create_user(username='test_helpdesk_auth_user')
19
+ self.request = self.factory.get('/')
20
+ self.request.user = self.user
21
+
22
+ content_type = ContentType.objects.get_for_model(HelpDeskTicket)
23
+
24
+ self.view_permission = Permission.objects.get(
25
+ codename='view_helpdeskticket',
26
+ content_type=content_type
27
+ )
28
+ self.add_permission = Permission.objects.get(
29
+ codename='add_helpdeskticket',
30
+ content_type=content_type
31
+ )
32
+ self.change_permission = Permission.objects.get(
33
+ codename='change_helpdeskticket',
34
+ content_type=content_type
35
+ )
36
+ self.delete_permission = Permission.objects.get(
37
+ codename='delete_helpdeskticket',
38
+ content_type=content_type
39
+ )
40
+
41
+ def _refresh_user_and_request(self):
42
+ self.user = type(self.user).objects.get(pk=self.user.pk)
43
+ self.request.user = self.user
44
+
45
+ def test_can_view_without_permission(self):
46
+ controller = BaseHelpDeskAuthController(self.request)
47
+ assert controller.can_view() is False
48
+
49
+ def test_can_view_with_permission(self):
50
+ self.user.user_permissions.add(self.view_permission)
51
+ self._refresh_user_and_request()
52
+
53
+ controller = BaseHelpDeskAuthController(self.request)
54
+ assert controller.can_view() is True
55
+
56
+ def test_can_add_without_permission(self):
57
+ controller = BaseHelpDeskAuthController(self.request)
58
+ assert controller.can_add() is False
59
+
60
+ def test_can_add_with_permission(self):
61
+ self.user.user_permissions.add(self.add_permission)
62
+ self._refresh_user_and_request()
63
+
64
+ controller = BaseHelpDeskAuthController(self.request)
65
+ assert controller.can_add() is True
66
+
67
+ def test_can_change_without_permission(self):
68
+ controller = BaseHelpDeskAuthController(self.request)
69
+ assert controller.can_change() is False
70
+
71
+ def test_can_change_with_permission(self):
72
+ self.user.user_permissions.add(self.change_permission)
73
+ self._refresh_user_and_request()
74
+
75
+ controller = BaseHelpDeskAuthController(self.request)
76
+ assert controller.can_change() is True
77
+
78
+ def test_can_delete_without_permission(self):
79
+ controller = BaseHelpDeskAuthController(self.request)
80
+ assert controller.can_delete() is False
81
+
82
+ def test_can_delete_with_permission(self):
83
+ self.user.user_permissions.add(self.delete_permission)
84
+ self._refresh_user_and_request()
85
+
86
+ controller = BaseHelpDeskAuthController(self.request)
87
+ assert controller.can_delete() is True
@@ -0,0 +1,18 @@
1
+ from __future__ import annotations
2
+
3
+ from django_spire.core.tests.test_cases import BaseTestCase
4
+ from django_spire.help_desk.enums import TicketEventType
5
+
6
+
7
+ class TicketEventTypeTests(BaseTestCase):
8
+ def test_new_value(self):
9
+ assert TicketEventType.NEW.value == 'new'
10
+
11
+ def test_update_value(self):
12
+ assert TicketEventType.UPDATE.value == 'update'
13
+
14
+ def test_comment_value(self):
15
+ assert TicketEventType.COMMENT.value == 'comment'
16
+
17
+ def test_enum_members_count(self):
18
+ assert len(TicketEventType) == 3
@@ -0,0 +1,37 @@
1
+ from __future__ import annotations
2
+
3
+ from django_spire.core.tests.test_cases import BaseTestCase
4
+ from django_spire.help_desk.enums import TicketEventType
5
+ from django_spire.help_desk.exceptions import (
6
+ HelpDeskNotificationRecipientMissingEmailError,
7
+ TicketEventNotificationTypeNotSupportedError,
8
+ )
9
+ from django_spire.notification.choices import NotificationTypeChoices
10
+
11
+
12
+ class HelpDeskExceptionsTests(BaseTestCase):
13
+ def test_ticket_event_notification_type_not_supported_error_is_type_error(self):
14
+ assert issubclass(TicketEventNotificationTypeNotSupportedError, TypeError)
15
+
16
+ def test_ticket_event_notification_type_not_supported_error_message(self):
17
+ error = TicketEventNotificationTypeNotSupportedError(
18
+ TicketEventType.NEW,
19
+ NotificationTypeChoices.SMS
20
+ )
21
+ assert 'Event type' in str(error)
22
+ assert 'Notification type' in str(error)
23
+
24
+ def test_ticket_event_notification_type_not_supported_error_message_without_event_type(self):
25
+ error = TicketEventNotificationTypeNotSupportedError(
26
+ None,
27
+ NotificationTypeChoices.SMS
28
+ )
29
+ assert 'Notification type not supported' in str(error)
30
+
31
+ def test_helpdesk_notification_recipient_missing_email_error_is_value_error(self):
32
+ assert issubclass(HelpDeskNotificationRecipientMissingEmailError, ValueError)
33
+
34
+ def test_helpdesk_notification_recipient_missing_email_error_message(self):
35
+ error = HelpDeskNotificationRecipientMissingEmailError('John Doe')
36
+ assert 'John Doe' in str(error)
37
+ assert 'missing an email address' in str(error)
@@ -0,0 +1,89 @@
1
+ from __future__ import annotations
2
+
3
+ from django_spire.core.tests.test_cases import BaseTestCase
4
+ from django_spire.help_desk.choices import (
5
+ HelpDeskTicketPriorityChoices,
6
+ HelpDeskTicketPurposeChoices,
7
+ HelpDeskTicketStatusChoices,
8
+ )
9
+ from django_spire.help_desk.forms import (
10
+ HelpDeskTicketCreateForm,
11
+ HelpDeskTicketUpdateForm,
12
+ )
13
+ from django_spire.help_desk.tests.factories import create_test_helpdesk_ticket
14
+
15
+
16
+ class HelpDeskTicketCreateFormTests(BaseTestCase):
17
+ def test_valid_form(self):
18
+ form_data = {
19
+ 'purpose': HelpDeskTicketPurposeChoices.APP,
20
+ 'priority': HelpDeskTicketPriorityChoices.LOW,
21
+ 'description': 'Test description',
22
+ }
23
+ form = HelpDeskTicketCreateForm(data=form_data)
24
+ assert form.is_valid()
25
+
26
+ def test_invalid_form_missing_purpose(self):
27
+ form_data = {
28
+ 'priority': HelpDeskTicketPriorityChoices.LOW,
29
+ 'description': 'Test description',
30
+ }
31
+ form = HelpDeskTicketCreateForm(data=form_data)
32
+ assert not form.is_valid()
33
+ assert 'purpose' in form.errors
34
+
35
+ def test_invalid_form_missing_priority(self):
36
+ form_data = {
37
+ 'purpose': HelpDeskTicketPurposeChoices.APP,
38
+ 'description': 'Test description',
39
+ }
40
+ form = HelpDeskTicketCreateForm(data=form_data)
41
+ assert not form.is_valid()
42
+ assert 'priority' in form.errors
43
+
44
+ def test_invalid_form_missing_description(self):
45
+ form_data = {
46
+ 'purpose': HelpDeskTicketPurposeChoices.APP,
47
+ 'priority': HelpDeskTicketPriorityChoices.LOW,
48
+ }
49
+ form = HelpDeskTicketCreateForm(data=form_data)
50
+ assert not form.is_valid()
51
+ assert 'description' in form.errors
52
+
53
+ def test_excludes_created_by(self):
54
+ assert 'created_by' not in HelpDeskTicketCreateForm().fields
55
+
56
+ def test_excludes_status(self):
57
+ assert 'status' not in HelpDeskTicketCreateForm().fields
58
+
59
+
60
+ class HelpDeskTicketUpdateFormTests(BaseTestCase):
61
+ def setUp(self):
62
+ super().setUp()
63
+ self.ticket = create_test_helpdesk_ticket()
64
+
65
+ def test_valid_form(self):
66
+ form_data = {
67
+ 'purpose': HelpDeskTicketPurposeChoices.COMPANY,
68
+ 'priority': HelpDeskTicketPriorityChoices.HIGH,
69
+ 'status': HelpDeskTicketStatusChoices.INPROGRESS,
70
+ 'description': 'Updated description',
71
+ }
72
+ form = HelpDeskTicketUpdateForm(data=form_data, instance=self.ticket)
73
+ assert form.is_valid()
74
+
75
+ def test_includes_status(self):
76
+ assert 'status' in HelpDeskTicketUpdateForm().fields
77
+
78
+ def test_excludes_created_by(self):
79
+ assert 'created_by' not in HelpDeskTicketUpdateForm().fields
80
+
81
+ def test_invalid_form_missing_status(self):
82
+ form_data = {
83
+ 'purpose': HelpDeskTicketPurposeChoices.APP,
84
+ 'priority': HelpDeskTicketPriorityChoices.LOW,
85
+ 'description': 'Test description',
86
+ }
87
+ form = HelpDeskTicketUpdateForm(data=form_data, instance=self.ticket)
88
+ assert not form.is_valid()
89
+ assert 'status' in form.errors
@@ -0,0 +1,59 @@
1
+ from __future__ import annotations
2
+
3
+ from django_spire.core.tests.test_cases import BaseTestCase
4
+ from django_spire.help_desk.choices import (
5
+ HelpDeskTicketPriorityChoices,
6
+ HelpDeskTicketPurposeChoices,
7
+ HelpDeskTicketStatusChoices,
8
+ )
9
+ from django_spire.help_desk.tests.factories import create_test_helpdesk_ticket
10
+
11
+
12
+ class HelpDeskTicketModelTests(BaseTestCase):
13
+ def setUp(self):
14
+ super().setUp()
15
+
16
+ self.ticket = create_test_helpdesk_ticket()
17
+
18
+ def test_str(self):
19
+ assert str(self.ticket) == f'Ticket #{self.ticket.pk}'
20
+
21
+ def test_default_status_is_ready(self):
22
+ assert self.ticket.status == HelpDeskTicketStatusChoices.READY
23
+
24
+ def test_get_priority_display(self):
25
+ self.ticket.priority = HelpDeskTicketPriorityChoices.LOW
26
+ assert self.ticket.get_priority_display() == 'Low'
27
+
28
+ self.ticket.priority = HelpDeskTicketPriorityChoices.MEDIUM
29
+ assert self.ticket.get_priority_display() == 'Medium'
30
+
31
+ self.ticket.priority = HelpDeskTicketPriorityChoices.HIGH
32
+ assert self.ticket.get_priority_display() == 'High'
33
+
34
+ self.ticket.priority = HelpDeskTicketPriorityChoices.URGENT
35
+ assert self.ticket.get_priority_display() == 'Urgent'
36
+
37
+ def test_get_purpose_display(self):
38
+ self.ticket.purpose = HelpDeskTicketPurposeChoices.APP
39
+ assert self.ticket.get_purpose_display() == 'App'
40
+
41
+ self.ticket.purpose = HelpDeskTicketPurposeChoices.COMPANY
42
+ assert self.ticket.get_purpose_display() == 'Company'
43
+
44
+ def test_get_status_display(self):
45
+ self.ticket.status = HelpDeskTicketStatusChoices.READY
46
+ assert self.ticket.get_status_display() == 'Ready'
47
+
48
+ self.ticket.status = HelpDeskTicketStatusChoices.INPROGRESS
49
+ assert self.ticket.get_status_display() == 'In Progress'
50
+
51
+ self.ticket.status = HelpDeskTicketStatusChoices.DONE
52
+ assert self.ticket.get_status_display() == 'Done'
53
+
54
+ def test_created_by_relationship(self):
55
+ assert self.ticket.created_by is not None
56
+ assert self.ticket.created_by.pk is not None
57
+
58
+ def test_created_datetime_auto_set(self):
59
+ assert self.ticket.created_datetime is not None
@@ -0,0 +1,38 @@
1
+ from __future__ import annotations
2
+
3
+ from django_spire.core.tests.test_cases import BaseTestCase
4
+ from django_spire.help_desk.models import HelpDeskTicket
5
+ from django_spire.help_desk.tests.factories import create_test_helpdesk_ticket
6
+
7
+
8
+ class HelpDeskTicketQuerySetTests(BaseTestCase):
9
+ def setUp(self):
10
+ super().setUp()
11
+
12
+ self.ticket1 = create_test_helpdesk_ticket(description='Ticket 1')
13
+ self.ticket2 = create_test_helpdesk_ticket(description='Ticket 2')
14
+
15
+ def test_active_returns_non_deleted(self):
16
+ result = HelpDeskTicket.objects.active()
17
+ assert self.ticket1 in result
18
+ assert self.ticket2 in result
19
+
20
+ def test_active_excludes_deleted(self):
21
+ self.ticket1.is_deleted = True
22
+ self.ticket1.save()
23
+
24
+ result = HelpDeskTicket.objects.active()
25
+ assert self.ticket1 not in result
26
+ assert self.ticket2 in result
27
+
28
+ def test_active_excludes_inactive(self):
29
+ self.ticket1.is_active = False
30
+ self.ticket1.save()
31
+
32
+ result = HelpDeskTicket.objects.active()
33
+ assert self.ticket1 not in result
34
+ assert self.ticket2 in result
35
+
36
+ def test_order_by_created_datetime(self):
37
+ result = list(HelpDeskTicket.objects.order_by('-created_datetime'))
38
+ assert result[0].created_datetime >= result[1].created_datetime
@@ -1,14 +1,21 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
1
5
  from django.conf import settings
2
6
  from django.contrib.auth.models import User
3
7
  from django.db.models import Q, QuerySet
4
8
  from django.test import override_settings
5
9
 
6
10
  from django_spire.core.tests.test_cases import BaseTestCase
7
- from django_spire.help_desk.models import HelpDeskTicket
8
11
  from django_spire.help_desk.tests.factories import create_test_helpdesk_ticket
9
12
  from django_spire.notification.app.models import AppNotification
10
13
  from django_spire.notification.email.models import EmailNotification
11
14
 
15
+ if TYPE_CHECKING:
16
+ from django_spire.help_desk.models import HelpDeskTicket
17
+
18
+
12
19
  TEST_ADMINS = [
13
20
  ('developer1', 'developer1@stratus.com'),
14
21
  ('developer2', 'developer2@stratus.com'),
@@ -17,17 +24,17 @@ TEST_ADMINS = [
17
24
 
18
25
  class HelpDeskTicketNotificationServiceTestCase(BaseTestCase):
19
26
  def _assert_user_notification_ticket_integrity(
20
- self,
21
- ticket: HelpDeskTicket,
22
- notification_type: type,
23
- users: QuerySet[User]
27
+ self,
28
+ ticket: HelpDeskTicket,
29
+ notification_type: type,
30
+ users: QuerySet[User]
24
31
  ):
25
32
  notifications = notification_type.objects.by_users(users)
26
- self.assertEqual(len(notifications), len(users))
33
+ assert len(notifications) == len(users)
27
34
 
28
35
  for notification in notifications:
29
- self.assertEqual(notification.notification.content_object, ticket)
30
- self.assertTrue(users.filter(pk=notification.notification.user.pk).exists())
36
+ assert notification.notification.content_object == ticket
37
+ assert users.filter(pk=notification.notification.user.pk).exists()
31
38
 
32
39
  @override_settings(ADMINS=TEST_ADMINS)
33
40
  def test_create_new_ticket_notifications(self):