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
@@ -0,0 +1,26 @@
1
+ from __future__ import annotations
2
+
3
+ from django.contrib.auth.models import User
4
+
5
+ from django_spire.notification.choices import (
6
+ NotificationPriorityChoices,
7
+ NotificationStatusChoices,
8
+ NotificationTypeChoices,
9
+ )
10
+ from django_spire.notification.models import Notification
11
+
12
+
13
+ def create_test_notification(**kwargs) -> Notification:
14
+ if 'user' not in kwargs:
15
+ kwargs['user'] = User.objects.first()
16
+
17
+ data = {
18
+ 'type': NotificationTypeChoices.APP,
19
+ 'title': 'Test Notification',
20
+ 'body': 'This is a test notification body.',
21
+ 'url': 'https://example.com',
22
+ 'status': NotificationStatusChoices.PENDING,
23
+ 'priority': NotificationPriorityChoices.LOW,
24
+ }
25
+ data.update(kwargs)
26
+ return Notification.objects.create(**data)
@@ -0,0 +1,55 @@
1
+ from __future__ import annotations
2
+
3
+ from django.contrib.admin.sites import AdminSite
4
+
5
+ from django_spire.core.tests.test_cases import BaseTestCase
6
+ from django_spire.notification.admin import NotificationAdmin
7
+ from django_spire.notification.models import Notification
8
+ from django_spire.notification.tests.factories import create_test_notification
9
+
10
+
11
+ class NotificationAdminTests(BaseTestCase):
12
+ def setUp(self):
13
+ super().setUp()
14
+
15
+ self.site = AdminSite()
16
+ self.admin = NotificationAdmin(Notification, self.site)
17
+ self.notification = create_test_notification()
18
+
19
+ def test_list_display(self):
20
+ expected = (
21
+ 'id', 'title', 'type', 'user', 'view_body_snippet', 'url_link', 'status',
22
+ 'status_message', 'priority', 'sent_datetime', 'publish_datetime',
23
+ 'content_type', 'object_id', 'is_deleted'
24
+ )
25
+ assert self.admin.list_display == expected
26
+
27
+ def test_list_filter(self):
28
+ expected = ('type',)
29
+ assert self.admin.list_filter == expected
30
+
31
+ def test_search_fields(self):
32
+ expected = ('id', 'title', 'type')
33
+ assert self.admin.search_fields == expected
34
+
35
+ def test_view_body_snippet_truncates_long_body(self):
36
+ self.notification.body = 'A' * 50
37
+ result = self.admin.view_body_snippet(self.notification)
38
+ assert len(result) <= 23
39
+ assert result.endswith('...')
40
+
41
+ def test_view_body_snippet_short_body(self):
42
+ self.notification.body = 'Short'
43
+ result = self.admin.view_body_snippet(self.notification)
44
+ assert result == 'Short'
45
+
46
+ def test_url_link_with_url(self):
47
+ self.notification.url = 'https://example.com'
48
+ result = self.admin.url_link(self.notification)
49
+ assert 'href="https://example.com"' in result
50
+ assert 'Link' in result
51
+
52
+ def test_url_link_without_url(self):
53
+ self.notification.url = ''
54
+ result = self.admin.url_link(self.notification)
55
+ assert result == 'No URL'
@@ -0,0 +1,30 @@
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.notification.apps import NotificationConfig
7
+
8
+
9
+ class NotificationConfigTests(BaseTestCase):
10
+ def test_app_name(self):
11
+ assert NotificationConfig.name == 'django_spire.notification'
12
+
13
+ def test_app_label(self):
14
+ assert NotificationConfig.label == 'django_spire_notification'
15
+
16
+ def test_default_auto_field(self):
17
+ assert NotificationConfig.default_auto_field == 'django.db.models.BigAutoField'
18
+
19
+ def test_required_apps(self):
20
+ expected = ('django_spire_core', 'django_spire_history', 'django_spire_history_viewed')
21
+ assert expected == NotificationConfig.REQUIRED_APPS
22
+
23
+ def test_urlpatterns_include(self):
24
+ assert NotificationConfig.URLPATTERNS_INCLUDE == 'django_spire.notification.urls'
25
+
26
+ def test_urlpatterns_namespace(self):
27
+ assert NotificationConfig.URLPATTERNS_NAMESPACE == 'notification'
28
+
29
+ def test_app_is_installed(self):
30
+ assert apps.is_installed('django_spire.notification')
@@ -0,0 +1,18 @@
1
+ from __future__ import annotations
2
+
3
+ from unittest.mock import MagicMock, patch
4
+
5
+ from django_spire.core.tests.test_cases import BaseTestCase
6
+ from django_spire.notification.automations import process_notifications
7
+
8
+
9
+ class NotificationAutomationsTests(BaseTestCase):
10
+ @patch('django_spire.notification.automations.NotificationManager')
11
+ def test_process_notifications_calls_manager(self, mock_manager_class: MagicMock):
12
+ mock_manager = MagicMock()
13
+ mock_manager_class.return_value = mock_manager
14
+
15
+ result = process_notifications()
16
+
17
+ mock_manager.process_ready_notifications.assert_called_once()
18
+ assert result == 'Successfully Completed'
@@ -0,0 +1,59 @@
1
+ from __future__ import annotations
2
+
3
+ from django_spire.core.tests.test_cases import BaseTestCase
4
+ from django_spire.notification.choices import (
5
+ NotificationPriorityChoices,
6
+ NotificationStatusChoices,
7
+ NotificationTypeChoices,
8
+ )
9
+
10
+
11
+ class NotificationTypeChoicesTests(BaseTestCase):
12
+ def test_app_value(self):
13
+ assert NotificationTypeChoices.APP == 'app'
14
+
15
+ def test_email_value(self):
16
+ assert NotificationTypeChoices.EMAIL == 'email'
17
+
18
+ def test_push_value(self):
19
+ assert NotificationTypeChoices.PUSH == 'push'
20
+
21
+ def test_sms_value(self):
22
+ assert NotificationTypeChoices.SMS == 'sms'
23
+
24
+ def test_choices_count(self):
25
+ assert len(NotificationTypeChoices.choices) == 4
26
+
27
+
28
+ class NotificationStatusChoicesTests(BaseTestCase):
29
+ def test_pending_value(self):
30
+ assert NotificationStatusChoices.PENDING == 'pending'
31
+
32
+ def test_processing_value(self):
33
+ assert NotificationStatusChoices.PROCESSING == 'processing'
34
+
35
+ def test_sent_value(self):
36
+ assert NotificationStatusChoices.SENT == 'sent'
37
+
38
+ def test_errored_value(self):
39
+ assert NotificationStatusChoices.ERRORED == 'errored'
40
+
41
+ def test_failed_value(self):
42
+ assert NotificationStatusChoices.FAILED == 'failed'
43
+
44
+ def test_choices_count(self):
45
+ assert len(NotificationStatusChoices.choices) == 5
46
+
47
+
48
+ class NotificationPriorityChoicesTests(BaseTestCase):
49
+ def test_low_value(self):
50
+ assert NotificationPriorityChoices.LOW == 'low'
51
+
52
+ def test_medium_value(self):
53
+ assert NotificationPriorityChoices.MEDIUM == 'medium'
54
+
55
+ def test_high_value(self):
56
+ assert NotificationPriorityChoices.HIGH == 'high'
57
+
58
+ def test_choices_count(self):
59
+ assert len(NotificationPriorityChoices.choices) == 3
@@ -0,0 +1,58 @@
1
+ from __future__ import annotations
2
+
3
+ from django_spire.core.tests.test_cases import BaseTestCase
4
+ from django_spire.exceptions import DjangoSpireError
5
+ from django_spire.notification.app.exceptions import AppNotificationError
6
+ from django_spire.notification.email.exceptions import EmailNotificationError
7
+ from django_spire.notification.exceptions import NotificationError
8
+ from django_spire.notification.sms.exceptions import (
9
+ SmsNotificationError,
10
+ SmsTemporaryMediaError,
11
+ TwilioAPIConcurrentError,
12
+ TwilioError,
13
+ )
14
+
15
+
16
+ class NotificationExceptionsTests(BaseTestCase):
17
+ def test_notification_error_is_django_spire_error(self):
18
+ assert issubclass(NotificationError, DjangoSpireError)
19
+
20
+ def test_notification_error_message(self):
21
+ error = NotificationError('Test error')
22
+ assert str(error) == 'Test error'
23
+
24
+
25
+ class AppNotificationExceptionsTests(BaseTestCase):
26
+ def test_app_notification_error_is_django_spire_error(self):
27
+ assert issubclass(AppNotificationError, DjangoSpireError)
28
+
29
+ def test_app_notification_error_message(self):
30
+ error = AppNotificationError('Test error')
31
+ assert str(error) == 'Test error'
32
+
33
+
34
+ class EmailNotificationExceptionsTests(BaseTestCase):
35
+ def test_email_notification_error_is_django_spire_error(self):
36
+ assert issubclass(EmailNotificationError, DjangoSpireError)
37
+
38
+ def test_email_notification_error_message(self):
39
+ error = EmailNotificationError('Test error')
40
+ assert str(error) == 'Test error'
41
+
42
+
43
+ class SmsNotificationExceptionsTests(BaseTestCase):
44
+ def test_sms_notification_error_is_django_spire_error(self):
45
+ assert issubclass(SmsNotificationError, DjangoSpireError)
46
+
47
+ def test_sms_notification_error_message(self):
48
+ error = SmsNotificationError('Test error')
49
+ assert str(error) == 'Test error'
50
+
51
+ def test_sms_temporary_media_error_is_sms_notification_error(self):
52
+ assert issubclass(SmsTemporaryMediaError, SmsNotificationError)
53
+
54
+ def test_twilio_error_is_exception(self):
55
+ assert issubclass(TwilioError, Exception)
56
+
57
+ def test_twilio_api_concurrent_error_is_twilio_error(self):
58
+ assert issubclass(TwilioAPIConcurrentError, TwilioError)
@@ -0,0 +1,100 @@
1
+ from __future__ import annotations
2
+
3
+ from unittest.mock import MagicMock, patch
4
+
5
+ from django_spire.core.tests.test_cases import BaseTestCase
6
+ from django_spire.notification.managers import NotificationManager
7
+
8
+
9
+ class NotificationManagerTests(BaseTestCase):
10
+ @patch('django_spire.notification.managers.NotificationProcessor')
11
+ def test_process_ready_notifications(self, mock_processor_class: MagicMock):
12
+ mock_processor = MagicMock()
13
+ mock_processor_class.return_value = mock_processor
14
+
15
+ NotificationManager.process_ready_notifications()
16
+
17
+ mock_processor.process_ready.assert_called_once()
18
+
19
+ @patch('django_spire.notification.managers.NotificationProcessor')
20
+ def test_process_errored_notifications(self, mock_processor_class: MagicMock):
21
+ mock_processor = MagicMock()
22
+ mock_processor_class.return_value = mock_processor
23
+
24
+ NotificationManager.process_errored_notifications()
25
+
26
+ mock_processor.process_errored.assert_called_once()
27
+
28
+ @patch('django_spire.notification.managers.AppNotificationProcessor')
29
+ def test_process_ready_app_notifications(self, mock_processor_class: MagicMock):
30
+ mock_processor = MagicMock()
31
+ mock_processor_class.return_value = mock_processor
32
+
33
+ NotificationManager.process_ready_app_notifications()
34
+
35
+ mock_processor.process_ready.assert_called_once()
36
+
37
+ @patch('django_spire.notification.managers.AppNotificationProcessor')
38
+ def test_process_errored_app_notifications(self, mock_processor_class: MagicMock):
39
+ mock_processor = MagicMock()
40
+ mock_processor_class.return_value = mock_processor
41
+
42
+ NotificationManager.process_errored_app_notifications()
43
+
44
+ mock_processor.process_errored.assert_called_once()
45
+
46
+ @patch('django_spire.notification.managers.EmailNotificationProcessor')
47
+ def test_process_ready_email_notifications(self, mock_processor_class: MagicMock):
48
+ mock_processor = MagicMock()
49
+ mock_processor_class.return_value = mock_processor
50
+
51
+ NotificationManager.process_ready_email_notifications()
52
+
53
+ mock_processor.process_ready.assert_called_once()
54
+
55
+ @patch('django_spire.notification.managers.EmailNotificationProcessor')
56
+ def test_process_errored_email_notifications(self, mock_processor_class: MagicMock):
57
+ mock_processor = MagicMock()
58
+ mock_processor_class.return_value = mock_processor
59
+
60
+ NotificationManager.process_errored_email_notifications()
61
+
62
+ mock_processor.process_errored.assert_called_once()
63
+
64
+ @patch('django_spire.notification.managers.SMSNotificationProcessor')
65
+ def test_process_ready_sms_notifications(self, mock_processor_class: MagicMock):
66
+ mock_processor = MagicMock()
67
+ mock_processor_class.return_value = mock_processor
68
+
69
+ NotificationManager.process_ready_sms_notifications()
70
+
71
+ mock_processor.process_ready.assert_called_once()
72
+
73
+ @patch('django_spire.notification.managers.SMSNotificationProcessor')
74
+ def test_process_errored_sms_notifications(self, mock_processor_class: MagicMock):
75
+ mock_processor = MagicMock()
76
+ mock_processor_class.return_value = mock_processor
77
+
78
+ NotificationManager.process_errored_sms_notifications()
79
+
80
+ mock_processor.process_errored.assert_called_once()
81
+
82
+ @patch('django_spire.notification.managers.NotificationProcessor')
83
+ def test_process_notification(self, mock_processor_class: MagicMock):
84
+ mock_processor = MagicMock()
85
+ mock_processor_class.return_value = mock_processor
86
+ mock_notification = MagicMock()
87
+
88
+ NotificationManager.process_notification(mock_notification)
89
+
90
+ mock_processor.process.assert_called_once_with(mock_notification)
91
+
92
+ @patch('django_spire.notification.managers.NotificationProcessor')
93
+ def test_process_notifications(self, mock_processor_class: MagicMock):
94
+ mock_processor = MagicMock()
95
+ mock_processor_class.return_value = mock_processor
96
+ mock_notifications = [MagicMock(), MagicMock()]
97
+
98
+ NotificationManager.process_notifications(mock_notifications)
99
+
100
+ mock_processor.process_list.assert_called_once_with(mock_notifications)
@@ -0,0 +1,31 @@
1
+ from __future__ import annotations
2
+
3
+ from django_spire.core.tests.test_cases import BaseTestCase
4
+ from django_spire.notification.app.models import AppNotification
5
+ from django_spire.notification.choices import NotificationTypeChoices
6
+ from django_spire.notification.email.models import EmailNotification
7
+ from django_spire.notification.maps import NOTIFICATION_TYPE_CHOICE_TO_MODEL_MAP
8
+ from django_spire.notification.push.models import PushNotification
9
+ from django_spire.notification.sms.models import SmsNotification
10
+
11
+
12
+ class NotificationMapsTests(BaseTestCase):
13
+ def test_map_contains_app(self):
14
+ assert NotificationTypeChoices.APP in NOTIFICATION_TYPE_CHOICE_TO_MODEL_MAP
15
+ assert NOTIFICATION_TYPE_CHOICE_TO_MODEL_MAP[NotificationTypeChoices.APP] == AppNotification
16
+
17
+ def test_map_contains_email(self):
18
+ assert NotificationTypeChoices.EMAIL in NOTIFICATION_TYPE_CHOICE_TO_MODEL_MAP
19
+ assert NOTIFICATION_TYPE_CHOICE_TO_MODEL_MAP[NotificationTypeChoices.EMAIL] == EmailNotification
20
+
21
+ def test_map_contains_push(self):
22
+ assert NotificationTypeChoices.PUSH in NOTIFICATION_TYPE_CHOICE_TO_MODEL_MAP
23
+ assert NOTIFICATION_TYPE_CHOICE_TO_MODEL_MAP[NotificationTypeChoices.PUSH] == PushNotification
24
+
25
+ def test_map_contains_sms(self):
26
+ assert NotificationTypeChoices.SMS in NOTIFICATION_TYPE_CHOICE_TO_MODEL_MAP
27
+ assert NOTIFICATION_TYPE_CHOICE_TO_MODEL_MAP[NotificationTypeChoices.SMS] == SmsNotification
28
+
29
+ def test_map_has_all_notification_types(self):
30
+ for choice in NotificationTypeChoices:
31
+ assert choice in NOTIFICATION_TYPE_CHOICE_TO_MODEL_MAP
@@ -0,0 +1,76 @@
1
+ from __future__ import annotations
2
+
3
+ from django_spire.core.tests.test_cases import BaseTestCase
4
+ from django_spire.notification.choices import (
5
+ NotificationPriorityChoices,
6
+ NotificationStatusChoices,
7
+ NotificationTypeChoices,
8
+ )
9
+ from django_spire.notification.tests.factories import create_test_notification
10
+
11
+
12
+ class NotificationModelTests(BaseTestCase):
13
+ def setUp(self):
14
+ super().setUp()
15
+
16
+ self.notification = create_test_notification()
17
+
18
+ def test_default_status_is_pending(self):
19
+ assert self.notification.status == NotificationStatusChoices.PENDING
20
+
21
+ def test_default_priority_is_low(self):
22
+ assert self.notification.priority == NotificationPriorityChoices.LOW
23
+
24
+ def test_get_type_display(self):
25
+ self.notification.type = NotificationTypeChoices.APP
26
+ assert self.notification.get_type_display() == 'App'
27
+
28
+ self.notification.type = NotificationTypeChoices.EMAIL
29
+ assert self.notification.get_type_display() == 'Email'
30
+
31
+ self.notification.type = NotificationTypeChoices.SMS
32
+ assert self.notification.get_type_display() == 'Sms'
33
+
34
+ self.notification.type = NotificationTypeChoices.PUSH
35
+ assert self.notification.get_type_display() == 'Push'
36
+
37
+ def test_get_status_display(self):
38
+ self.notification.status = NotificationStatusChoices.PENDING
39
+ assert self.notification.get_status_display() == 'Pending'
40
+
41
+ self.notification.status = NotificationStatusChoices.PROCESSING
42
+ assert self.notification.get_status_display() == 'Processing'
43
+
44
+ self.notification.status = NotificationStatusChoices.SENT
45
+ assert self.notification.get_status_display() == 'Sent'
46
+
47
+ self.notification.status = NotificationStatusChoices.ERRORED
48
+ assert self.notification.get_status_display() == 'Errored'
49
+
50
+ self.notification.status = NotificationStatusChoices.FAILED
51
+ assert self.notification.get_status_display() == 'Failed'
52
+
53
+ def test_get_priority_display(self):
54
+ self.notification.priority = NotificationPriorityChoices.LOW
55
+ assert self.notification.get_priority_display() == 'Low'
56
+
57
+ self.notification.priority = NotificationPriorityChoices.MEDIUM
58
+ assert self.notification.get_priority_display() == 'Medium'
59
+
60
+ self.notification.priority = NotificationPriorityChoices.HIGH
61
+ assert self.notification.get_priority_display() == 'High'
62
+
63
+ def test_user_relationship(self):
64
+ assert self.notification.user is not None
65
+ assert self.notification.user.pk is not None
66
+
67
+ def test_created_datetime_auto_set(self):
68
+ assert self.notification.created_datetime is not None
69
+
70
+ def test_sent_datetime_null_by_default(self):
71
+ assert self.notification.sent_datetime is None
72
+
73
+ def test_content_object_null_by_default(self):
74
+ assert self.notification.content_object is None
75
+ assert self.notification.content_type is None
76
+ assert self.notification.object_id is None
@@ -0,0 +1,184 @@
1
+ from __future__ import annotations
2
+
3
+ from django.utils.timezone import now, timedelta
4
+
5
+ from django_spire.auth.user.tests.factories import create_user
6
+ from django_spire.core.tests.test_cases import BaseTestCase
7
+ from django_spire.notification.choices import (
8
+ NotificationPriorityChoices,
9
+ NotificationStatusChoices,
10
+ NotificationTypeChoices,
11
+ )
12
+ from django_spire.notification.models import Notification
13
+ from django_spire.notification.tests.factories import create_test_notification
14
+
15
+
16
+ class NotificationQuerySetTests(BaseTestCase):
17
+ def setUp(self):
18
+ super().setUp()
19
+
20
+ self.user = create_user(username='test_notification_user')
21
+ self.notification = create_test_notification(user=self.user)
22
+
23
+ def test_active_returns_non_deleted(self):
24
+ result = Notification.objects.active()
25
+ assert self.notification in result
26
+
27
+ def test_active_excludes_deleted(self):
28
+ self.notification.is_deleted = True
29
+ self.notification.save()
30
+
31
+ result = Notification.objects.active()
32
+ assert self.notification not in result
33
+
34
+ def test_by_user(self):
35
+ other_user = create_user(username='other_user')
36
+ other_notification = create_test_notification(user=other_user)
37
+
38
+ result = Notification.objects.by_user(self.user)
39
+ assert self.notification in result
40
+ assert other_notification not in result
41
+
42
+ def test_by_users(self):
43
+ other_user = create_user(username='other_user')
44
+ other_notification = create_test_notification(user=other_user)
45
+
46
+ result = Notification.objects.by_users([self.user, other_user])
47
+ assert self.notification in result
48
+ assert other_notification in result
49
+
50
+ def test_app_notifications(self):
51
+ self.notification.type = NotificationTypeChoices.APP
52
+ self.notification.save()
53
+
54
+ email_notification = create_test_notification(
55
+ user=self.user,
56
+ type=NotificationTypeChoices.EMAIL
57
+ )
58
+
59
+ result = Notification.objects.app_notifications()
60
+ assert self.notification in result
61
+ assert email_notification not in result
62
+
63
+ def test_email_notifications(self):
64
+ self.notification.type = NotificationTypeChoices.EMAIL
65
+ self.notification.save()
66
+
67
+ app_notification = create_test_notification(
68
+ user=self.user,
69
+ type=NotificationTypeChoices.APP
70
+ )
71
+
72
+ result = Notification.objects.email_notifications()
73
+ assert self.notification in result
74
+ assert app_notification not in result
75
+
76
+ def test_sms_notifications(self):
77
+ self.notification.type = NotificationTypeChoices.SMS
78
+ self.notification.save()
79
+
80
+ result = Notification.objects.sms_notifications()
81
+ assert self.notification in result
82
+
83
+ def test_pending(self):
84
+ self.notification.status = NotificationStatusChoices.PENDING
85
+ self.notification.save()
86
+
87
+ sent_notification = create_test_notification(
88
+ user=self.user,
89
+ status=NotificationStatusChoices.SENT
90
+ )
91
+
92
+ result = Notification.objects.pending()
93
+ assert self.notification in result
94
+ assert sent_notification not in result
95
+
96
+ def test_processing(self):
97
+ self.notification.status = NotificationStatusChoices.PROCESSING
98
+ self.notification.save()
99
+
100
+ result = Notification.objects.processing()
101
+ assert self.notification in result
102
+
103
+ def test_sent(self):
104
+ self.notification.status = NotificationStatusChoices.SENT
105
+ self.notification.save()
106
+
107
+ result = Notification.objects.sent()
108
+ assert self.notification in result
109
+
110
+ def test_errored(self):
111
+ self.notification.status = NotificationStatusChoices.ERRORED
112
+ self.notification.save()
113
+
114
+ result = Notification.objects.errored()
115
+ assert self.notification in result
116
+
117
+ def test_failed(self):
118
+ self.notification.status = NotificationStatusChoices.FAILED
119
+ self.notification.save()
120
+
121
+ result = Notification.objects.failed()
122
+ assert self.notification in result
123
+
124
+ def test_unsent(self):
125
+ pending = create_test_notification(
126
+ user=self.user,
127
+ status=NotificationStatusChoices.PENDING
128
+ )
129
+ processing = create_test_notification(
130
+ user=self.user,
131
+ status=NotificationStatusChoices.PROCESSING
132
+ )
133
+ sent = create_test_notification(
134
+ user=self.user,
135
+ status=NotificationStatusChoices.SENT
136
+ )
137
+
138
+ result = Notification.objects.unsent()
139
+ assert pending in result
140
+ assert processing in result
141
+ assert sent not in result
142
+
143
+ def test_ready_to_send(self):
144
+ past_notification = create_test_notification(
145
+ user=self.user,
146
+ status=NotificationStatusChoices.PENDING,
147
+ publish_datetime=now() - timedelta(hours=1)
148
+ )
149
+ future_notification = create_test_notification(
150
+ user=self.user,
151
+ status=NotificationStatusChoices.PENDING,
152
+ publish_datetime=now() + timedelta(hours=1)
153
+ )
154
+
155
+ result = Notification.objects.ready_to_send()
156
+ assert past_notification in result
157
+ assert future_notification not in result
158
+
159
+ def test_high_priority(self):
160
+ self.notification.priority = NotificationPriorityChoices.HIGH
161
+ self.notification.save()
162
+
163
+ low_priority = create_test_notification(
164
+ user=self.user,
165
+ priority=NotificationPriorityChoices.LOW
166
+ )
167
+
168
+ result = Notification.objects.high_priority()
169
+ assert self.notification in result
170
+ assert low_priority not in result
171
+
172
+ def test_medium_priority(self):
173
+ self.notification.priority = NotificationPriorityChoices.MEDIUM
174
+ self.notification.save()
175
+
176
+ result = Notification.objects.medium_priority()
177
+ assert self.notification in result
178
+
179
+ def test_low_priority(self):
180
+ self.notification.priority = NotificationPriorityChoices.LOW
181
+ self.notification.save()
182
+
183
+ result = Notification.objects.low_priority()
184
+ assert self.notification in result
@@ -0,0 +1,23 @@
1
+ from __future__ import annotations
2
+
3
+ from django.test import override_settings
4
+
5
+ from django_spire.core.tests.test_cases import BaseTestCase
6
+ from django_spire.notification.utils import get_throttle_sleep_time
7
+
8
+
9
+ class NotificationUtilsTests(BaseTestCase):
10
+ @override_settings(NOTIFICATION_THROTTLE_RATE_PER_MINUTE=60)
11
+ def test_get_throttle_sleep_time_60_per_minute(self):
12
+ result = get_throttle_sleep_time()
13
+ assert result == 1.0
14
+
15
+ @override_settings(NOTIFICATION_THROTTLE_RATE_PER_MINUTE=120)
16
+ def test_get_throttle_sleep_time_120_per_minute(self):
17
+ result = get_throttle_sleep_time()
18
+ assert result == 2.0
19
+
20
+ @override_settings(NOTIFICATION_THROTTLE_RATE_PER_MINUTE=30)
21
+ def test_get_throttle_sleep_time_30_per_minute(self):
22
+ result = get_throttle_sleep_time()
23
+ assert result == 0.5
@@ -1,4 +1,6 @@
1
- from django.urls import path, include
1
+ from __future__ import annotations
2
+
3
+ from django.urls import include, path
2
4
 
3
5
 
4
6
  app_name = 'notification'