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
@@ -0,0 +1,139 @@
1
+ from __future__ import annotations
2
+
3
+ from django_spire.auth.user.factories import register_new_user
4
+ from django_spire.auth.user.models import AuthUser
5
+ from django_spire.core.tests.test_cases import BaseTestCase
6
+
7
+
8
+ class RegisterNewUserTestCase(BaseTestCase):
9
+ def test_creates_user(self) -> None:
10
+ user = register_new_user(
11
+ first_name='Test',
12
+ last_name='User',
13
+ email='test@example.com',
14
+ password='securepassword123' # noqa: S106
15
+ )
16
+ assert isinstance(user, AuthUser) is True
17
+ assert AuthUser.objects.filter(pk=user.pk).exists() is True
18
+
19
+ def test_sets_correct_attributes(self) -> None:
20
+ user = register_new_user(
21
+ first_name='John',
22
+ last_name='Doe',
23
+ email='john.doe@example.com',
24
+ password='securepassword123' # noqa: S106
25
+ )
26
+ assert user.first_name == 'John'
27
+ assert user.last_name == 'Doe'
28
+ assert user.email == 'john.doe@example.com'
29
+
30
+ def test_username_is_lowercase_email(self) -> None:
31
+ user = register_new_user(
32
+ first_name='Test',
33
+ last_name='User',
34
+ email='Test.User@Example.COM',
35
+ password='securepassword123' # noqa: S106
36
+ )
37
+ assert user.username == 'test.user@example.com'
38
+ assert user.email == 'test.user@example.com'
39
+
40
+ def test_password_is_set(self) -> None:
41
+ user = register_new_user(
42
+ first_name='Test',
43
+ last_name='User',
44
+ email='test@example.com',
45
+ password='securepassword123' # noqa: S106
46
+ )
47
+ assert user.check_password('securepassword123') is True
48
+
49
+ def test_password_is_hashed(self) -> None:
50
+ user = register_new_user(
51
+ first_name='Test',
52
+ last_name='User',
53
+ email='test@example.com',
54
+ password='securepassword123' # noqa: S106
55
+ )
56
+ assert user.password != 'securepassword123' # noqa: S106
57
+
58
+ def test_user_is_active_by_default(self) -> None:
59
+ user = register_new_user(
60
+ first_name='Test',
61
+ last_name='User',
62
+ email='test@example.com',
63
+ password='securepassword123' # noqa: S106
64
+ )
65
+ assert user.is_active is True
66
+
67
+ def test_user_is_not_staff_by_default(self) -> None:
68
+ user = register_new_user(
69
+ first_name='Test',
70
+ last_name='User',
71
+ email='test@example.com',
72
+ password='securepassword123' # noqa: S106
73
+ )
74
+ assert user.is_staff is False
75
+
76
+ def test_user_is_not_superuser_by_default(self) -> None:
77
+ user = register_new_user(
78
+ first_name='Test',
79
+ last_name='User',
80
+ email='test@example.com',
81
+ password='securepassword123' # noqa: S106
82
+ )
83
+ assert user.is_superuser is False
84
+
85
+ def test_empty_first_name(self) -> None:
86
+ user = register_new_user(
87
+ first_name='',
88
+ last_name='User',
89
+ email='test@example.com',
90
+ password='securepassword123' # noqa: S106
91
+ )
92
+ assert user.first_name == ''
93
+
94
+ def test_empty_last_name(self) -> None:
95
+ user = register_new_user(
96
+ first_name='Test',
97
+ last_name='',
98
+ email='test@example.com',
99
+ password='securepassword123' # noqa: S106
100
+ )
101
+ assert user.last_name == ''
102
+
103
+ def test_unicode_names(self) -> None:
104
+ user = register_new_user(
105
+ first_name='Tëst',
106
+ last_name='Üser',
107
+ email='test@example.com',
108
+ password='securepassword123' # noqa: S106
109
+ )
110
+ assert user.first_name == 'Tëst'
111
+ assert user.last_name == 'Üser'
112
+
113
+ def test_long_password(self) -> None:
114
+ long_password = 'a' * 100
115
+ user = register_new_user(
116
+ first_name='Test',
117
+ last_name='User',
118
+ email='test@example.com',
119
+ password=long_password
120
+ )
121
+ assert user.check_password(long_password) is True
122
+
123
+ def test_special_characters_in_email(self) -> None:
124
+ user = register_new_user(
125
+ first_name='Test',
126
+ last_name='User',
127
+ email='test+special@example.com',
128
+ password='securepassword123' # noqa: S106
129
+ )
130
+ assert user.email == 'test+special@example.com'
131
+
132
+ def test_wrong_password_fails(self) -> None:
133
+ user = register_new_user(
134
+ first_name='Test',
135
+ last_name='User',
136
+ email='test@example.com',
137
+ password='securepassword123' # noqa: S106
138
+ )
139
+ assert user.check_password('wrongpassword') is False
@@ -0,0 +1,363 @@
1
+ from __future__ import annotations
2
+
3
+ from django.urls import reverse
4
+
5
+ from django_spire.auth.group.models import AuthGroup
6
+ from django_spire.auth.user.models import AuthUser
7
+ from django_spire.auth.user.tests.factories import create_user
8
+ from django_spire.core.tests.test_cases import BaseTestCase
9
+
10
+
11
+ class UserPageViewsTestCase(BaseTestCase):
12
+ def setUp(self) -> None:
13
+ super().setUp()
14
+
15
+ self.user = create_user(
16
+ username='testuser',
17
+ first_name='Test',
18
+ last_name='User',
19
+ email='test@example.com'
20
+ )
21
+
22
+ def test_list_view_requires_permission(self) -> None:
23
+ normal_user = create_user(username='normaluser')
24
+ self.client.force_login(normal_user)
25
+ response = self.client.get(reverse('django_spire:auth:user:page:list'))
26
+ assert response.status_code == 403
27
+
28
+ def test_list_view_with_permission(self) -> None:
29
+ response = self.client.get(reverse('django_spire:auth:user:page:list'))
30
+ assert response.status_code == 200
31
+
32
+ def test_list_view_context_contains_users(self) -> None:
33
+ response = self.client.get(reverse('django_spire:auth:user:page:list'))
34
+ assert 'active_user_list' in response.context
35
+ assert 'inactive_user_list' in response.context
36
+
37
+ def test_list_view_separates_active_inactive(self) -> None:
38
+ inactive_user = create_user(username='inactiveuser', is_active=False)
39
+ response = self.client.get(reverse('django_spire:auth:user:page:list'))
40
+ active_ids = [u.pk for u in response.context['active_user_list']]
41
+ inactive_ids = [u.pk for u in response.context['inactive_user_list']]
42
+ assert inactive_user.pk in inactive_ids
43
+ assert inactive_user.pk not in active_ids
44
+
45
+ def test_detail_view_requires_permission(self) -> None:
46
+ normal_user = create_user(username='normaluser')
47
+ self.client.force_login(normal_user)
48
+ response = self.client.get(
49
+ reverse('django_spire:auth:user:page:detail', kwargs={'pk': self.user.pk})
50
+ )
51
+ assert response.status_code == 403
52
+
53
+ def test_detail_view_with_permission(self) -> None:
54
+ response = self.client.get(
55
+ reverse('django_spire:auth:user:page:detail', kwargs={'pk': self.user.pk})
56
+ )
57
+ assert response.status_code == 200
58
+
59
+ def test_detail_view_context_contains_user(self) -> None:
60
+ response = self.client.get(
61
+ reverse('django_spire:auth:user:page:detail', kwargs={'pk': self.user.pk})
62
+ )
63
+ assert 'user' in response.context
64
+
65
+ def test_detail_view_context_contains_groups(self) -> None:
66
+ response = self.client.get(
67
+ reverse('django_spire:auth:user:page:detail', kwargs={'pk': self.user.pk})
68
+ )
69
+ assert 'group_list' in response.context
70
+
71
+ def test_detail_view_context_contains_permissions(self) -> None:
72
+ response = self.client.get(
73
+ reverse('django_spire:auth:user:page:detail', kwargs={'pk': self.user.pk})
74
+ )
75
+ assert 'user_perm_data' in response.context
76
+
77
+ def test_detail_view_404_for_nonexistent_user(self) -> None:
78
+ response = self.client.get(
79
+ reverse('django_spire:auth:user:page:detail', kwargs={'pk': 99999})
80
+ )
81
+ assert response.status_code == 404
82
+
83
+ def test_list_view_contains_active_user(self) -> None:
84
+ response = self.client.get(reverse('django_spire:auth:user:page:list'))
85
+ active_ids = [u.pk for u in response.context['active_user_list']]
86
+ assert self.user.pk in active_ids
87
+
88
+ def test_list_view_pagination(self) -> None:
89
+ response = self.client.get(reverse('django_spire:auth:user:page:list'))
90
+ assert 'active_user_list' in response.context
91
+ assert hasattr(response.context['active_user_list'], 'paginator')
92
+
93
+ def test_detail_view_with_groups(self) -> None:
94
+ group = AuthGroup.objects.create(name='Test Group')
95
+ self.user.groups.add(group)
96
+ response = self.client.get(
97
+ reverse('django_spire:auth:user:page:detail', kwargs={'pk': self.user.pk})
98
+ )
99
+ assert response.status_code == 200
100
+ assert group in response.context['group_list']
101
+
102
+ def test_detail_view_context_contains_group_permission_data(self) -> None:
103
+ group = AuthGroup.objects.create(name='Test Group')
104
+ self.user.groups.add(group)
105
+ response = self.client.get(
106
+ reverse('django_spire:auth:user:page:detail', kwargs={'pk': self.user.pk})
107
+ )
108
+ assert 'group_list_permission_data' in response.context
109
+
110
+ def test_list_view_multiple_users(self) -> None:
111
+ create_user(username='user1', first_name='User', last_name='One')
112
+ create_user(username='user2', first_name='User', last_name='Two')
113
+ response = self.client.get(reverse('django_spire:auth:user:page:list'))
114
+ assert len(response.context['active_user_list']) >= 2
115
+
116
+
117
+ class UserFormViewsTestCase(BaseTestCase):
118
+ def setUp(self) -> None:
119
+ super().setUp()
120
+
121
+ self.user = create_user(
122
+ username='testuser',
123
+ first_name='Test',
124
+ last_name='User',
125
+ email='test@example.com'
126
+ )
127
+
128
+ def test_register_form_view_get(self) -> None:
129
+ response = self.client.get(reverse('django_spire:auth:user:form:register'))
130
+ assert response.status_code == 200
131
+
132
+ def test_register_form_view_post_creates_user(self) -> None:
133
+ response = self.client.post(
134
+ reverse('django_spire:auth:user:form:register'),
135
+ data={
136
+ 'first_name': 'New',
137
+ 'last_name': 'User',
138
+ 'email': 'newuser@example.com',
139
+ 'password': 'securepassword123'
140
+ }
141
+ )
142
+ assert response.status_code == 302
143
+ assert AuthUser.objects.filter(email='newuser@example.com').exists()
144
+
145
+ def test_register_form_view_post_invalid_password(self) -> None:
146
+ response = self.client.post(
147
+ reverse('django_spire:auth:user:form:register'),
148
+ data={
149
+ 'first_name': 'New',
150
+ 'last_name': 'User',
151
+ 'email': 'newuser@example.com',
152
+ 'password': 'short'
153
+ }
154
+ )
155
+ assert response.status_code == 200
156
+ assert not AuthUser.objects.filter(email='newuser@example.com').exists()
157
+
158
+ def test_register_form_view_post_invalid_email(self) -> None:
159
+ response = self.client.post(
160
+ reverse('django_spire:auth:user:form:register'),
161
+ data={
162
+ 'first_name': 'New',
163
+ 'last_name': 'User',
164
+ 'email': 'invalid-email',
165
+ 'password': 'securepassword123'
166
+ }
167
+ )
168
+ assert response.status_code == 200
169
+
170
+ def test_register_form_requires_permission(self) -> None:
171
+ normal_user = create_user(username='normaluser')
172
+ self.client.force_login(normal_user)
173
+ response = self.client.get(reverse('django_spire:auth:user:form:register'))
174
+ assert response.status_code == 403
175
+
176
+ def test_update_form_view_get(self) -> None:
177
+ response = self.client.get(
178
+ reverse('django_spire:auth:user:form:update', kwargs={'pk': self.user.pk})
179
+ )
180
+ assert response.status_code == 200
181
+
182
+ def test_update_form_view_404(self) -> None:
183
+ response = self.client.get(
184
+ reverse('django_spire:auth:user:form:update', kwargs={'pk': 99999})
185
+ )
186
+ assert response.status_code == 404
187
+
188
+ def test_update_form_requires_permission(self) -> None:
189
+ normal_user = create_user(username='normaluser')
190
+ self.client.force_login(normal_user)
191
+ response = self.client.get(
192
+ reverse('django_spire:auth:user:form:update', kwargs={'pk': self.user.pk})
193
+ )
194
+ assert response.status_code == 403
195
+
196
+ def test_group_form_view_get(self) -> None:
197
+ response = self.client.get(
198
+ reverse('django_spire:auth:user:form:group_form', kwargs={'pk': self.user.pk})
199
+ )
200
+ assert response.status_code == 200
201
+
202
+ def test_group_form_view_post_updates_groups(self) -> None:
203
+ group = AuthGroup.objects.create(name='Test Group')
204
+ response = self.client.post(
205
+ reverse('django_spire:auth:user:form:group_form', kwargs={'pk': self.user.pk}),
206
+ data={'group_list': [group.pk]}
207
+ )
208
+ assert response.status_code == 302
209
+ self.user.refresh_from_db()
210
+ assert group in self.user.groups.all()
211
+
212
+ def test_group_form_view_post_multiple_groups(self) -> None:
213
+ group1 = AuthGroup.objects.create(name='Group 1')
214
+ group2 = AuthGroup.objects.create(name='Group 2')
215
+ response = self.client.post(
216
+ reverse('django_spire:auth:user:form:group_form', kwargs={'pk': self.user.pk}),
217
+ data={'group_list': [group1.pk, group2.pk]}
218
+ )
219
+ assert response.status_code == 302
220
+ self.user.refresh_from_db()
221
+ assert group1 in self.user.groups.all()
222
+ assert group2 in self.user.groups.all()
223
+
224
+ def test_group_form_view_404(self) -> None:
225
+ response = self.client.get(
226
+ reverse('django_spire:auth:user:form:group_form', kwargs={'pk': 99999})
227
+ )
228
+ assert response.status_code == 404
229
+
230
+ def test_group_form_requires_permission(self) -> None:
231
+ normal_user = create_user(username='normaluser')
232
+ self.client.force_login(normal_user)
233
+ response = self.client.get(
234
+ reverse('django_spire:auth:user:form:group_form', kwargs={'pk': self.user.pk})
235
+ )
236
+ assert response.status_code == 403
237
+
238
+ def test_register_form_view_post_empty_password(self) -> None:
239
+ response = self.client.post(
240
+ reverse('django_spire:auth:user:form:register'),
241
+ data={
242
+ 'first_name': 'New',
243
+ 'last_name': 'User',
244
+ 'email': 'newuser@example.com',
245
+ 'password': ''
246
+ }
247
+ )
248
+ assert response.status_code == 200
249
+
250
+ def test_register_form_view_post_empty_email(self) -> None:
251
+ response = self.client.post(
252
+ reverse('django_spire:auth:user:form:register'),
253
+ data={
254
+ 'first_name': 'New',
255
+ 'last_name': 'User',
256
+ 'email': '',
257
+ 'password': 'securepassword123'
258
+ }
259
+ )
260
+ assert response.status_code == 200
261
+
262
+ def test_register_form_view_post_unicode_names(self) -> None:
263
+ response = self.client.post(
264
+ reverse('django_spire:auth:user:form:register'),
265
+ data={
266
+ 'first_name': 'Tëst',
267
+ 'last_name': 'Üsér',
268
+ 'email': 'unicode@example.com',
269
+ 'password': 'securepassword123'
270
+ }
271
+ )
272
+ assert response.status_code == 302
273
+ user = AuthUser.objects.get(email='unicode@example.com')
274
+ assert user.first_name == 'Tëst'
275
+ assert user.last_name == 'Üsér'
276
+
277
+ def test_group_form_view_post_removes_existing_groups(self) -> None:
278
+ group1 = AuthGroup.objects.create(name='Group 1')
279
+ group2 = AuthGroup.objects.create(name='Group 2')
280
+ self.user.groups.add(group1)
281
+
282
+ response = self.client.post(
283
+ reverse('django_spire:auth:user:form:group_form', kwargs={'pk': self.user.pk}),
284
+ data={'group_list': [group2.pk]}
285
+ )
286
+ assert response.status_code == 302
287
+ self.user.refresh_from_db()
288
+ assert group1 not in self.user.groups.all()
289
+ assert group2 in self.user.groups.all()
290
+
291
+ def test_group_form_view_post_invalid_group_id(self) -> None:
292
+ response = self.client.post(
293
+ reverse('django_spire:auth:user:form:group_form', kwargs={'pk': self.user.pk}),
294
+ data={'group_list': [99999]}
295
+ )
296
+ assert response.status_code == 200
297
+
298
+ def test_register_redirects_after_success(self) -> None:
299
+ response = self.client.post(
300
+ reverse('django_spire:auth:user:form:register'),
301
+ data={
302
+ 'first_name': 'New',
303
+ 'last_name': 'User',
304
+ 'email': 'newuser@example.com',
305
+ 'password': 'securepassword123'
306
+ }
307
+ )
308
+ assert response.status_code == 302
309
+ assert 'list' in response.url or 'user' in response.url
310
+
311
+ def test_group_form_redirects_after_success(self) -> None:
312
+ group = AuthGroup.objects.create(name='Test Group')
313
+ response = self.client.post(
314
+ reverse('django_spire:auth:user:form:group_form', kwargs={'pk': self.user.pk}),
315
+ data={'group_list': [group.pk]}
316
+ )
317
+ assert response.status_code == 302
318
+ assert str(self.user.pk) in response.url
319
+
320
+ def test_update_form_view_post_updates_user(self) -> None:
321
+ response = self.client.post(
322
+ reverse('django_spire:auth:user:form:update', kwargs={'pk': self.user.pk}),
323
+ data={
324
+ 'first_name': 'Updated',
325
+ 'last_name': 'Name',
326
+ 'email': 'updated@example.com',
327
+ 'is_active': True
328
+ }
329
+ )
330
+ assert response.status_code == 302
331
+ self.user.refresh_from_db()
332
+ assert self.user.first_name == 'Updated'
333
+ assert self.user.last_name == 'Name'
334
+ assert self.user.email == 'updated@example.com'
335
+
336
+ def test_update_form_view_post_deactivates_user(self) -> None:
337
+ response = self.client.post(
338
+ reverse('django_spire:auth:user:form:update', kwargs={'pk': self.user.pk}),
339
+ data={
340
+ 'first_name': 'Test',
341
+ 'last_name': 'User',
342
+ 'email': 'test@example.com',
343
+ 'is_active': False
344
+ }
345
+ )
346
+ assert response.status_code == 302
347
+ self.user.refresh_from_db()
348
+ assert not self.user.is_active
349
+
350
+ def test_register_form_adds_to_all_users_group(self) -> None:
351
+ response = self.client.post(
352
+ reverse('django_spire:auth:user:form:register'),
353
+ data={
354
+ 'first_name': 'New',
355
+ 'last_name': 'User',
356
+ 'email': 'newuser@example.com',
357
+ 'password': 'securepassword123'
358
+ }
359
+ )
360
+ assert response.status_code == 302
361
+ user = AuthUser.objects.get(email='newuser@example.com')
362
+ group_names = [g.name for g in user.groups.all()]
363
+ assert 'All Users' in group_names
@@ -1,5 +1,11 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
1
5
  from django.contrib.auth.models import Group
2
- from django_spire.auth.user.models import AuthUser
6
+
7
+ if TYPE_CHECKING:
8
+ from django_spire.auth.user.models import AuthUser
3
9
 
4
10
 
5
11
  def add_user_to_all_user_group(user: AuthUser):
@@ -1,7 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.urls import path
2
4
 
3
5
  from django_spire.auth.user.views import form_views
4
6
 
7
+
5
8
  app_name = 'form'
6
9
 
7
10
  urlpatterns = [
@@ -1,7 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.urls import path
2
4
 
3
5
  from django_spire.auth.user.views import page_views
4
6
 
7
+
5
8
  app_name = 'page'
6
9
 
7
10
  urlpatterns = [
@@ -3,11 +3,12 @@ from __future__ import annotations
3
3
  import json
4
4
 
5
5
  import django_glue as dg
6
- from django.contrib.auth.decorators import login_required
6
+
7
7
  from django.core.serializers.json import DjangoJSONEncoder
8
8
  from django.http import HttpResponseRedirect
9
9
  from django.shortcuts import get_object_or_404
10
10
  from django.urls import reverse
11
+
11
12
  from django_glue.utils import serialize_to_json
12
13
 
13
14
  from django_spire.auth.group.models import AuthGroup
@@ -16,15 +17,18 @@ from django_spire.auth.user import forms
16
17
  from django_spire.auth.user.models import AuthUser
17
18
  from django_spire.auth.user.tools import add_user_to_all_user_group
18
19
  from django_spire.contrib import Breadcrumbs
19
- from django_spire.contrib.form.confirmation_forms import DeleteConfirmationForm, ConfirmationForm
20
20
  from django_spire.contrib.form.utils import show_form_errors
21
21
  from django_spire.contrib.generic_views import portal_views
22
- from django_spire.core.redirect import safe_redirect_url
23
22
  from django_spire.history.activity.utils import add_form_activity
23
+ from typing import TYPE_CHECKING
24
+
25
+ if TYPE_CHECKING:
26
+ from django.core.handlers.wsgi import WSGIRequest
27
+ from django.template.response import TemplateResponse
24
28
 
25
29
 
26
30
  @permission_required('django_spire_auth_user.add_authuser')
27
- def register_form_view(request):
31
+ def register_form_view(request: WSGIRequest) -> TemplateResponse:
28
32
  portal_user = AuthUser()
29
33
  dg.glue_model_object(request, 'portal_user', portal_user, 'view')
30
34
 
@@ -44,7 +48,7 @@ def register_form_view(request):
44
48
  user_form = forms.RegisterUserForm(instance=portal_user)
45
49
 
46
50
  context_data = {
47
- # Todo: Function that takes in all of the forms and dumps the data here?
51
+ # TODO: Function that takes in all of the forms and dumps the data here?
48
52
  'user_form_data': json.dumps(user_form.data, cls=DjangoJSONEncoder),
49
53
  }
50
54
 
@@ -63,7 +67,7 @@ def register_form_view(request):
63
67
 
64
68
 
65
69
  @permission_required('django_spire_auth_user.change_authuser')
66
- def form_view(request, pk):
70
+ def form_view(request: WSGIRequest, pk: int) -> TemplateResponse:
67
71
  portal_user = get_object_or_404(AuthUser, pk=pk)
68
72
  dg.glue_model_object(request, 'portal_user', portal_user, 'view')
69
73
 
@@ -74,7 +78,12 @@ def form_view(request, pk):
74
78
  portal_user = form.save()
75
79
  add_form_activity(portal_user, pk, request.user)
76
80
 
77
- return HttpResponseRedirect(reverse('django_spire:auth:user:page:detail', kwargs={'pk': pk}))
81
+ return HttpResponseRedirect(
82
+ reverse(
83
+ 'django_spire:auth:user:page:detail',
84
+ kwargs={'pk': pk}
85
+ )
86
+ )
78
87
  else:
79
88
  form = forms.UserForm(instance=portal_user)
80
89
 
@@ -93,7 +102,7 @@ def form_view(request, pk):
93
102
 
94
103
 
95
104
  @permission_required('django_spire_auth_group.change_authgroup')
96
- def group_form_view(request, pk):
105
+ def group_form_view(request: WSGIRequest, pk: int) -> TemplateResponse:
97
106
  user = get_object_or_404(AuthUser, pk=pk)
98
107
  dg.glue_query_set(request, 'group_choices', AuthGroup.objects.all())
99
108
  selected_group_ids = [group.pk for group in user.groups.all()]
@@ -104,8 +113,8 @@ def group_form_view(request, pk):
104
113
  if form.is_valid():
105
114
  user.groups.set(form.cleaned_data['group_list'])
106
115
  return HttpResponseRedirect(reverse('django_spire:auth:user:page:detail', kwargs={'pk': pk}))
107
- else:
108
- show_form_errors(request, form)
116
+
117
+ show_form_errors(request, form)
109
118
 
110
119
  form = forms.UserGroupForm()
111
120
 
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from typing import TYPE_CHECKING
4
+
3
5
  from django.shortcuts import get_object_or_404
4
6
 
5
7
  from django_spire.auth.permissions.decorators import permission_required
@@ -8,9 +10,13 @@ from django_spire.contrib.pagination.pagination import paginate_list
8
10
  from django_spire.auth.user.models import AuthUser
9
11
  from django_spire.contrib.generic_views import portal_views
10
12
 
13
+ if TYPE_CHECKING:
14
+ from django.core.handlers.wsgi import WSGIRequest
15
+ from django.template.response import TemplateResponse
16
+
11
17
 
12
18
  @permission_required('django_spire_auth_user.view_authuser')
13
- def detail_view(request, pk):
19
+ def detail_view(request: WSGIRequest, pk: int) -> TemplateResponse:
14
20
  user = get_object_or_404(AuthUser, pk=pk)
15
21
  group_list = user.groups.all()
16
22
 
@@ -30,7 +36,7 @@ def detail_view(request, pk):
30
36
 
31
37
 
32
38
  @permission_required('django_spire_auth_user.view_authuser')
33
- def list_view(request):
39
+ def list_view(request: WSGIRequest) -> TemplateResponse:
34
40
  active_user_list = AuthUser.objects.filter(is_active=True).prefetch_related('groups').order_by('first_name', 'last_name')
35
41
  inactive_user_list = AuthUser.objects.filter(is_active=False).prefetch_related('groups').order_by('first_name', 'last_name')
36
42