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,257 @@
1
+ from __future__ import annotations
2
+
3
+ import pytest
4
+
5
+ from django.db import IntegrityError
6
+
7
+ from django_spire.auth.group.models import AuthGroup
8
+ from django_spire.auth.user.models import AuthUser
9
+ from django_spire.auth.user.tests.factories import create_user
10
+ from django_spire.core.tests.test_cases import BaseTestCase
11
+
12
+
13
+ class AuthUserModelTestCase(BaseTestCase):
14
+ def setUp(self) -> None:
15
+ super().setUp()
16
+
17
+ self.user = create_user(
18
+ username='testuser',
19
+ first_name='Test',
20
+ last_name='User',
21
+ email='test@example.com'
22
+ )
23
+
24
+ def test_meta_proxy(self) -> None:
25
+ assert AuthUser._meta.proxy
26
+
27
+ def test_meta_verbose_name(self) -> None:
28
+ assert AuthUser._meta.verbose_name == 'Auth User'
29
+ assert AuthUser._meta.verbose_name_plural == 'Auth Users'
30
+
31
+ def test_get_full_name(self) -> None:
32
+ assert self.user.get_full_name() == 'Test User'
33
+
34
+ def test_get_full_name_first_only(self) -> None:
35
+ user = create_user(username='firstonly', first_name='First', last_name='')
36
+ assert 'First' in user.get_full_name()
37
+
38
+ def test_get_full_name_last_only(self) -> None:
39
+ user = create_user(username='lastonly', first_name='', last_name='Last')
40
+ assert 'Last' in user.get_full_name()
41
+
42
+ def test_get_full_name_empty(self) -> None:
43
+ user = create_user(username='noname', first_name='', last_name='')
44
+ assert user.get_full_name() == ''
45
+
46
+ def test_services_class_attribute(self) -> None:
47
+ assert AuthUser.services is not None
48
+
49
+ def test_user_creation(self) -> None:
50
+ user = create_user(
51
+ username='newuser',
52
+ first_name='New',
53
+ last_name='User',
54
+ email='new@example.com'
55
+ )
56
+ assert user.pk is not None
57
+ assert AuthUser.objects.filter(pk=user.pk).exists()
58
+
59
+ def test_user_update(self) -> None:
60
+ self.user.first_name = 'Updated'
61
+ self.user.save()
62
+ self.user.refresh_from_db()
63
+ assert self.user.first_name == 'Updated'
64
+
65
+ def test_user_deletion(self) -> None:
66
+ user_pk = self.user.pk
67
+ self.user.delete()
68
+ assert not AuthUser.objects.filter(pk=user_pk).exists()
69
+
70
+ def test_user_email(self) -> None:
71
+ assert self.user.email == 'test@example.com'
72
+
73
+ def test_user_is_active_default(self) -> None:
74
+ user = create_user(username='activeuser')
75
+ assert user.is_active
76
+
77
+ def test_user_is_active_false(self) -> None:
78
+ user = create_user(username='inactiveuser', is_active=False)
79
+ assert not user.is_active
80
+
81
+ def test_user_groups_relationship(self) -> None:
82
+ group = AuthGroup.objects.create(name='Test Group')
83
+ self.user.groups.add(group)
84
+ assert group in self.user.groups.all()
85
+
86
+ def test_user_multiple_groups(self) -> None:
87
+ group1 = AuthGroup.objects.create(name='Group 1')
88
+ group2 = AuthGroup.objects.create(name='Group 2')
89
+ self.user.groups.add(group1, group2)
90
+ assert self.user.groups.count() == 2
91
+
92
+ def test_user_remove_from_group(self) -> None:
93
+ group = AuthGroup.objects.create(name='Test Group')
94
+ self.user.groups.add(group)
95
+ self.user.groups.remove(group)
96
+ assert group not in self.user.groups.all()
97
+
98
+ def test_user_clear_groups(self) -> None:
99
+ group1 = AuthGroup.objects.create(name='Group 1')
100
+ group2 = AuthGroup.objects.create(name='Group 2')
101
+ self.user.groups.add(group1, group2)
102
+ self.user.groups.clear()
103
+ assert self.user.groups.count() == 0
104
+
105
+ def test_user_username_unique(self) -> None:
106
+ with pytest.raises(IntegrityError):
107
+ AuthUser.objects.create(username='testuser')
108
+
109
+ def test_user_password_hashing(self) -> None:
110
+ user = create_user(username='passworduser')
111
+ user.set_password('testpassword123')
112
+ user.save()
113
+ assert user.check_password('testpassword123')
114
+ assert not user.check_password('wrongpassword')
115
+
116
+ def test_user_last_login_initially_none(self) -> None:
117
+ user = create_user(username='nologinuser')
118
+ assert user.last_login is None
119
+
120
+ def test_breadcrumbs_returns_breadcrumbs(self) -> None:
121
+ crumbs = self.user.breadcrumbs()
122
+ assert crumbs is not None
123
+
124
+ def test_breadcrumbs_without_pk(self) -> None:
125
+ unsaved_user = AuthUser(username='unsaved', first_name='Unsaved')
126
+ crumbs = unsaved_user.breadcrumbs()
127
+ assert crumbs is not None
128
+
129
+ def test_breadcrumbs_contains_user_name(self) -> None:
130
+ crumbs = self.user.breadcrumbs()
131
+ names = [c.name for c in crumbs.data]
132
+ assert 'Test User' in names
133
+
134
+ def test_base_breadcrumb_returns_breadcrumbs(self) -> None:
135
+ crumbs = AuthUser.base_breadcrumb()
136
+ assert crumbs is not None
137
+
138
+ def test_base_breadcrumb_contains_users(self) -> None:
139
+ crumbs = AuthUser.base_breadcrumb()
140
+ names = [c.name for c in crumbs.data]
141
+ assert 'Users' in names
142
+
143
+ def test_user_inheritance_from_django_user(self) -> None:
144
+ from django.contrib.auth.models import User
145
+ assert issubclass(AuthUser, User)
146
+
147
+ def test_activity_mixin(self) -> None:
148
+ assert hasattr(self.user, 'add_activity')
149
+
150
+ def test_user_get_short_name(self) -> None:
151
+ assert self.user.get_short_name() == 'Test'
152
+
153
+ def test_user_str_representation(self) -> None:
154
+ assert str(self.user) == 'testuser'
155
+
156
+ def test_user_email_user(self) -> None:
157
+ assert hasattr(self.user, 'email_user')
158
+
159
+ def test_user_has_perm(self) -> None:
160
+ assert hasattr(self.user, 'has_perm')
161
+
162
+ def test_user_has_perms(self) -> None:
163
+ assert hasattr(self.user, 'has_perms')
164
+
165
+ def test_user_has_module_perms(self) -> None:
166
+ assert hasattr(self.user, 'has_module_perms')
167
+
168
+ def test_user_is_anonymous(self) -> None:
169
+ assert not self.user.is_anonymous
170
+
171
+ def test_user_is_authenticated(self) -> None:
172
+ assert self.user.is_authenticated
173
+
174
+ def test_user_date_joined(self) -> None:
175
+ assert self.user.date_joined is not None
176
+
177
+ def test_user_username_max_length(self) -> None:
178
+ max_length = AuthUser._meta.get_field('username').max_length
179
+ assert max_length == 150
180
+
181
+ def test_user_first_name_max_length(self) -> None:
182
+ max_length = AuthUser._meta.get_field('first_name').max_length
183
+ assert max_length == 150
184
+
185
+ def test_user_last_name_max_length(self) -> None:
186
+ max_length = AuthUser._meta.get_field('last_name').max_length
187
+ assert max_length == 150
188
+
189
+ def test_user_email_max_length(self) -> None:
190
+ max_length = AuthUser._meta.get_field('email').max_length
191
+ assert max_length == 254
192
+
193
+ def test_user_with_unicode_names(self) -> None:
194
+ user = create_user(
195
+ username='unicodeuser',
196
+ first_name='Tëst',
197
+ last_name='Üsér'
198
+ )
199
+ assert user.first_name == 'Tëst'
200
+ assert user.last_name == 'Üsér'
201
+ assert 'Tëst' in user.get_full_name()
202
+
203
+ def test_user_refresh_from_db(self) -> None:
204
+ original_name = self.user.first_name
205
+ AuthUser.objects.filter(pk=self.user.pk).update(first_name='Changed')
206
+ self.user.refresh_from_db()
207
+ assert self.user.first_name == 'Changed'
208
+ assert self.user.first_name != original_name
209
+
210
+ def test_user_get_or_create(self) -> None:
211
+ user, created = AuthUser.objects.get_or_create(
212
+ username='getorcreate',
213
+ defaults={'first_name': 'Get', 'last_name': 'OrCreate'}
214
+ )
215
+ assert created
216
+ user2, created2 = AuthUser.objects.get_or_create(
217
+ username='getorcreate',
218
+ defaults={'first_name': 'Different', 'last_name': 'Name'}
219
+ )
220
+ assert not created2
221
+ assert user.pk == user2.pk
222
+
223
+ def test_user_exists(self) -> None:
224
+ assert AuthUser.objects.filter(username='testuser').exists()
225
+ assert not AuthUser.objects.filter(username='nonexistent').exists()
226
+
227
+ def test_user_count(self) -> None:
228
+ initial_count = AuthUser.objects.count()
229
+ create_user(username='countuser1')
230
+ create_user(username='countuser2')
231
+ assert AuthUser.objects.count() == initial_count + 2
232
+
233
+ def test_user_filter_by_is_active(self) -> None:
234
+ create_user(username='active1', is_active=True)
235
+ create_user(username='inactive1', is_active=False)
236
+ active_users = AuthUser.objects.filter(is_active=True)
237
+ inactive_users = AuthUser.objects.filter(is_active=False)
238
+ assert active_users.filter(username='active1').exists()
239
+ assert inactive_users.filter(username='inactive1').exists()
240
+
241
+ def test_user_order_by_username(self) -> None:
242
+ create_user(username='zebra')
243
+ create_user(username='alpha')
244
+ users = AuthUser.objects.order_by('username')
245
+ usernames = list(users.values_list('username', flat=True))
246
+ assert usernames == sorted(usernames)
247
+
248
+ def test_user_permissions_relationship(self) -> None:
249
+ assert hasattr(self.user, 'user_permissions')
250
+
251
+ def test_user_set_unusable_password(self) -> None:
252
+ self.user.set_unusable_password()
253
+ assert not self.user.has_usable_password()
254
+
255
+ def test_user_check_password_with_unusable(self) -> None:
256
+ self.user.set_unusable_password()
257
+ assert not self.user.check_password('anypassword')
@@ -0,0 +1,200 @@
1
+ from __future__ import annotations
2
+
3
+ from django_spire.auth.group.models import AuthGroup
4
+ from django_spire.auth.user.models import AuthUser
5
+ from django_spire.auth.user.tests.factories import create_user
6
+ from django_spire.core.tests.test_cases import BaseTestCase
7
+
8
+
9
+ class AuthUserServiceTestCase(BaseTestCase):
10
+ def setUp(self) -> None:
11
+ super().setUp()
12
+
13
+ self.user1 = create_user(
14
+ username='user1',
15
+ first_name='Alice',
16
+ last_name='Smith',
17
+ is_active=True
18
+ )
19
+ self.user2 = create_user(
20
+ username='user2',
21
+ first_name='Bob',
22
+ last_name='Jones',
23
+ is_active=True
24
+ )
25
+ self.inactive_user = create_user(
26
+ username='inactive',
27
+ first_name='Charlie',
28
+ last_name='Brown',
29
+ is_active=False
30
+ )
31
+
32
+ def test_get_user_choices_returns_list(self) -> None:
33
+ choices = AuthUser.services.get_user_choices()
34
+ assert isinstance(choices, list)
35
+
36
+ def test_get_user_choices_format(self) -> None:
37
+ choices = AuthUser.services.get_user_choices()
38
+ for choice in choices:
39
+ assert len(choice) == 2
40
+ assert isinstance(choice[0], int)
41
+ assert isinstance(choice[1], str)
42
+
43
+ def test_get_user_choices_excludes_inactive(self) -> None:
44
+ choices = AuthUser.services.get_user_choices()
45
+ user_ids = [c[0] for c in choices]
46
+ assert self.inactive_user.pk not in user_ids
47
+
48
+ def test_get_user_choices_includes_active(self) -> None:
49
+ choices = AuthUser.services.get_user_choices()
50
+ user_ids = [c[0] for c in choices]
51
+ assert self.user1.pk in user_ids
52
+ assert self.user2.pk in user_ids
53
+
54
+ def test_get_user_choices_by_group(self) -> None:
55
+ group = AuthGroup.objects.create(name='Test Group')
56
+ self.user1.groups.add(group)
57
+
58
+ choices = AuthUser.services.get_user_choices_by_group(group)
59
+ user_ids = [c[0] for c in choices]
60
+
61
+ assert self.user1.pk in user_ids
62
+ assert self.user2.pk not in user_ids
63
+
64
+ def test_get_user_choices_by_group_empty(self) -> None:
65
+ group = AuthGroup.objects.create(name='Empty Group')
66
+ choices = AuthUser.services.get_user_choices_by_group(group)
67
+ assert len(choices) == 0
68
+
69
+ def test_get_user_choices_by_group_multiple_users(self) -> None:
70
+ group = AuthGroup.objects.create(name='Multi User Group')
71
+ self.user1.groups.add(group)
72
+ self.user2.groups.add(group)
73
+
74
+ choices = AuthUser.services.get_user_choices_by_group(group)
75
+ user_ids = [c[0] for c in choices]
76
+
77
+ assert self.user1.pk in user_ids
78
+ assert self.user2.pk in user_ids
79
+
80
+ def test_get_user_choices_by_group_excludes_inactive(self) -> None:
81
+ group = AuthGroup.objects.create(name='Mixed Group')
82
+ self.user1.groups.add(group)
83
+ self.inactive_user.groups.add(group)
84
+
85
+ choices = AuthUser.services.get_user_choices_by_group(group)
86
+ user_ids = [c[0] for c in choices]
87
+
88
+ assert self.user1.pk in user_ids
89
+ assert self.inactive_user.pk not in user_ids
90
+
91
+ def test_get_user_choices_returns_full_name(self) -> None:
92
+ choices = AuthUser.services.get_user_choices()
93
+ user1_choice = next((c for c in choices if c[0] == self.user1.pk), None)
94
+ assert user1_choice is not None
95
+ assert user1_choice[1] == 'Alice Smith'
96
+
97
+ def test_get_user_choices_with_no_active_users(self) -> None:
98
+ AuthUser.objects.filter(is_active=True).update(is_active=False)
99
+ choices = AuthUser.services.get_user_choices()
100
+ assert len(choices) == 0
101
+
102
+ def test_get_user_choices_user_in_multiple_groups(self) -> None:
103
+ group1 = AuthGroup.objects.create(name='Group 1')
104
+ group2 = AuthGroup.objects.create(name='Group 2')
105
+ self.user1.groups.add(group1, group2)
106
+
107
+ choices1 = AuthUser.services.get_user_choices_by_group(group1)
108
+ choices2 = AuthUser.services.get_user_choices_by_group(group2)
109
+
110
+ assert any(c[0] == self.user1.pk for c in choices1)
111
+ assert any(c[0] == self.user1.pk for c in choices2)
112
+
113
+ def test_get_user_choices_empty_name(self) -> None:
114
+ user = create_user(username='emptyname', first_name='', last_name='')
115
+ choices = AuthUser.services.get_user_choices()
116
+ user_choice = next((c for c in choices if c[0] == user.pk), None)
117
+ assert user_choice is not None
118
+ assert user_choice[1] == ''
119
+
120
+ def test_get_user_choices_unicode_name(self) -> None:
121
+ user = create_user(username='unicode', first_name='Tëst', last_name='Üsér')
122
+ choices = AuthUser.services.get_user_choices()
123
+ user_choice = next((c for c in choices if c[0] == user.pk), None)
124
+ assert user_choice is not None
125
+ assert 'Tëst' in user_choice[1]
126
+ assert 'Üsér' in user_choice[1]
127
+
128
+ def test_get_user_choices_first_name_only(self) -> None:
129
+ user = create_user(username='firstonly', first_name='First', last_name='')
130
+ choices = AuthUser.services.get_user_choices()
131
+ user_choice = next((c for c in choices if c[0] == user.pk), None)
132
+ assert user_choice is not None
133
+ assert 'First' in user_choice[1]
134
+
135
+ def test_get_user_choices_last_name_only(self) -> None:
136
+ user = create_user(username='lastonly', first_name='', last_name='Last')
137
+ choices = AuthUser.services.get_user_choices()
138
+ user_choice = next((c for c in choices if c[0] == user.pk), None)
139
+ assert user_choice is not None
140
+ assert 'Last' in user_choice[1]
141
+
142
+ def test_get_user_choices_by_group_nonexistent_group(self) -> None:
143
+ group = AuthGroup.objects.create(name='Temp Group')
144
+ group_pk = group.pk
145
+ group.delete()
146
+ new_group = AuthGroup(pk=group_pk, name='Fake')
147
+ choices = AuthUser.services.get_user_choices_by_group(new_group)
148
+ assert len(choices) == 0
149
+
150
+ def test_get_user_choices_many_users(self) -> None:
151
+ for i in range(50):
152
+ create_user(username=f'manyuser{i}', first_name=f'User{i}', last_name='Test')
153
+ choices = AuthUser.services.get_user_choices()
154
+ assert len(choices) >= 50
155
+
156
+ def test_get_user_choices_by_group_after_user_removal(self) -> None:
157
+ group = AuthGroup.objects.create(name='Removal Group')
158
+ self.user1.groups.add(group)
159
+
160
+ choices_before = AuthUser.services.get_user_choices_by_group(group)
161
+ assert any(c[0] == self.user1.pk for c in choices_before)
162
+
163
+ self.user1.groups.remove(group)
164
+
165
+ choices_after = AuthUser.services.get_user_choices_by_group(group)
166
+ assert not any(c[0] == self.user1.pk for c in choices_after)
167
+
168
+ def test_get_user_choices_by_group_after_user_deactivation(self) -> None:
169
+ group = AuthGroup.objects.create(name='Deactivation Group')
170
+ self.user1.groups.add(group)
171
+
172
+ choices_before = AuthUser.services.get_user_choices_by_group(group)
173
+ assert any(c[0] == self.user1.pk for c in choices_before)
174
+
175
+ self.user1.is_active = False
176
+ self.user1.save()
177
+
178
+ choices_after = AuthUser.services.get_user_choices_by_group(group)
179
+ assert not any(c[0] == self.user1.pk for c in choices_after)
180
+
181
+ def test_service_obj_class_is_auth_user(self) -> None:
182
+ assert AuthUser.services.obj_class == AuthUser
183
+
184
+ def test_get_user_choices_order(self) -> None:
185
+ choices = AuthUser.services.get_user_choices()
186
+ assert len(choices) > 0
187
+
188
+ def test_get_user_choices_by_group_returns_list(self) -> None:
189
+ group = AuthGroup.objects.create(name='List Group')
190
+ choices = AuthUser.services.get_user_choices_by_group(group)
191
+ assert isinstance(choices, list)
192
+
193
+ def test_get_user_choices_by_group_format(self) -> None:
194
+ group = AuthGroup.objects.create(name='Format Group')
195
+ self.user1.groups.add(group)
196
+ choices = AuthUser.services.get_user_choices_by_group(group)
197
+ for choice in choices:
198
+ assert len(choice) == 2
199
+ assert isinstance(choice[0], int)
200
+ assert isinstance(choice[1], str)
@@ -0,0 +1,153 @@
1
+ from __future__ import annotations
2
+
3
+ from django.contrib.auth.models import Group
4
+
5
+ from django_spire.auth.user.tests.factories import create_user
6
+ from django_spire.auth.user.tools import add_user_to_all_user_group
7
+ from django_spire.core.tests.test_cases import BaseTestCase
8
+
9
+
10
+ class AddUserToAllUserGroupTestCase(BaseTestCase):
11
+ def setUp(self) -> None:
12
+ super().setUp()
13
+
14
+ self.user = create_user(username='testuser')
15
+
16
+ def test_creates_all_users_group_if_not_exists(self) -> None:
17
+ Group.objects.filter(name='All Users').delete()
18
+ add_user_to_all_user_group(self.user)
19
+ assert Group.objects.filter(name='All Users').exists()
20
+
21
+ def test_adds_user_to_existing_group(self) -> None:
22
+ group = Group.objects.create(name='All Users')
23
+ add_user_to_all_user_group(self.user)
24
+ assert self.user in group.user_set.all()
25
+
26
+ def test_user_in_all_users_group(self) -> None:
27
+ add_user_to_all_user_group(self.user)
28
+ group = Group.objects.get(name='All Users')
29
+ assert self.user in group.user_set.all()
30
+
31
+ def test_idempotent_add(self) -> None:
32
+ add_user_to_all_user_group(self.user)
33
+ add_user_to_all_user_group(self.user)
34
+ group = Group.objects.get(name='All Users')
35
+ assert group.user_set.filter(pk=self.user.pk).count() == 1
36
+
37
+ def test_multiple_users_added(self) -> None:
38
+ user2 = create_user(username='testuser2')
39
+ add_user_to_all_user_group(self.user)
40
+ add_user_to_all_user_group(user2)
41
+ group = Group.objects.get(name='All Users')
42
+ assert group.user_set.count() >= 2
43
+ assert self.user in group.user_set.all()
44
+ assert user2 in group.user_set.all()
45
+
46
+ def test_user_remains_in_other_groups(self) -> None:
47
+ other_group = Group.objects.create(name='Other Group')
48
+ self.user.groups.add(other_group)
49
+ add_user_to_all_user_group(self.user)
50
+ assert other_group in self.user.groups.all()
51
+
52
+ def test_group_created_with_exact_name(self) -> None:
53
+ Group.objects.filter(name='All Users').delete()
54
+ add_user_to_all_user_group(self.user)
55
+ group = Group.objects.get(name='All Users')
56
+ assert group.name == 'All Users'
57
+
58
+ def test_inactive_user_can_be_added(self) -> None:
59
+ inactive_user = create_user(username='inactive', is_active=False)
60
+ add_user_to_all_user_group(inactive_user)
61
+ group = Group.objects.get(name='All Users')
62
+ assert inactive_user in group.user_set.all()
63
+
64
+ def test_does_not_affect_other_groups(self) -> None:
65
+ other_group = Group.objects.create(name='Other Group')
66
+ initial_count = other_group.user_set.count()
67
+ add_user_to_all_user_group(self.user)
68
+ assert other_group.user_set.count() == initial_count
69
+
70
+ def test_user_not_added_to_other_groups(self) -> None:
71
+ other_group = Group.objects.create(name='Other Group')
72
+ add_user_to_all_user_group(self.user)
73
+ assert self.user not in other_group.user_set.all()
74
+
75
+ def test_multiple_calls_single_group_entry(self) -> None:
76
+ for _ in range(5):
77
+ add_user_to_all_user_group(self.user)
78
+ group = Group.objects.get(name='All Users')
79
+ assert group.user_set.filter(pk=self.user.pk).count() == 1
80
+
81
+ def test_group_persists_after_function_call(self) -> None:
82
+ Group.objects.filter(name='All Users').delete()
83
+ add_user_to_all_user_group(self.user)
84
+ assert Group.objects.filter(name='All Users').exists()
85
+
86
+ def test_user_membership_persists(self) -> None:
87
+ add_user_to_all_user_group(self.user)
88
+ self.user.refresh_from_db()
89
+ group = Group.objects.get(name='All Users')
90
+ assert group in self.user.groups.all()
91
+
92
+ def test_does_not_remove_existing_members(self) -> None:
93
+ existing_user = create_user(username='existinguser')
94
+ group = Group.objects.create(name='All Users')
95
+ group.user_set.add(existing_user)
96
+
97
+ add_user_to_all_user_group(self.user)
98
+
99
+ assert existing_user in group.user_set.all()
100
+ assert self.user in group.user_set.all()
101
+
102
+ def test_works_with_staff_user(self) -> None:
103
+ staff_user = create_user(username='staffuser', is_staff=True)
104
+ add_user_to_all_user_group(staff_user)
105
+ group = Group.objects.get(name='All Users')
106
+ assert staff_user in group.user_set.all()
107
+
108
+ def test_works_with_superuser(self) -> None:
109
+ superuser = create_user(username='superuser', is_superuser=True)
110
+ add_user_to_all_user_group(superuser)
111
+ group = Group.objects.get(name='All Users')
112
+ assert superuser in group.user_set.all()
113
+
114
+ def test_case_sensitive_group_name(self) -> None:
115
+ Group.objects.create(name='all users')
116
+ Group.objects.filter(name='All Users').delete()
117
+ add_user_to_all_user_group(self.user)
118
+ assert Group.objects.filter(name='All Users').exists()
119
+ assert Group.objects.filter(name='all users').exists()
120
+
121
+ def test_many_users_added(self) -> None:
122
+ users = [create_user(username=f'manyuser{i}') for i in range(20)]
123
+ for user in users:
124
+ add_user_to_all_user_group(user)
125
+ group = Group.objects.get(name='All Users')
126
+ for user in users:
127
+ assert user in group.user_set.all()
128
+
129
+ def test_user_groups_query(self) -> None:
130
+ add_user_to_all_user_group(self.user)
131
+ groups = self.user.groups.all()
132
+ group_names = [g.name for g in groups]
133
+ assert 'All Users' in group_names
134
+
135
+ def test_group_user_count_increments(self) -> None:
136
+ Group.objects.filter(name='All Users').delete()
137
+ group = Group.objects.create(name='All Users')
138
+ initial_count = group.user_set.count()
139
+
140
+ add_user_to_all_user_group(self.user)
141
+ group.refresh_from_db()
142
+
143
+ assert group.user_set.count() == initial_count + 1
144
+
145
+ def test_does_not_create_duplicate_groups(self) -> None:
146
+ Group.objects.filter(name='All Users').delete()
147
+ user1 = create_user(username='user1')
148
+ user2 = create_user(username='user2')
149
+
150
+ add_user_to_all_user_group(user1)
151
+ add_user_to_all_user_group(user2)
152
+
153
+ assert Group.objects.filter(name='All Users').count() == 1