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,282 @@
1
+ from __future__ import annotations
2
+
3
+ from django_spire.auth.group.forms import GroupForm, GroupNamesForm, GroupUserForm
4
+ from django_spire.auth.group.models import AuthGroup
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 GroupFormTestCase(BaseTestCase):
10
+ def test_valid_form(self) -> None:
11
+ form = GroupForm(data={'name': 'Test Group'})
12
+ assert form.is_valid()
13
+
14
+ def test_reserved_name_all_users(self) -> None:
15
+ form = GroupForm(data={'name': 'All Users'})
16
+ assert not form.is_valid()
17
+ assert 'name' in form.errors
18
+
19
+ def test_reserved_name_all_users_case_insensitive(self) -> None:
20
+ form = GroupForm(data={'name': 'all users'})
21
+ assert not form.is_valid()
22
+ assert 'name' in form.errors
23
+
24
+ def test_reserved_name_all_users_mixed_case(self) -> None:
25
+ form = GroupForm(data={'name': 'ALL USERS'})
26
+ assert not form.is_valid()
27
+ assert 'name' in form.errors
28
+
29
+ def test_reserved_name_all_users_with_extra_spaces(self) -> None:
30
+ form = GroupForm(data={'name': 'All Users'})
31
+ assert form.is_valid()
32
+
33
+ def test_empty_name(self) -> None:
34
+ form = GroupForm(data={'name': ''})
35
+ assert not form.is_valid()
36
+
37
+ def test_whitespace_only_name(self) -> None:
38
+ form = GroupForm(data={'name': ' '})
39
+ assert not form.is_valid()
40
+
41
+ def test_save_creates_group(self) -> None:
42
+ form = GroupForm(data={'name': 'New Group'})
43
+ assert form.is_valid()
44
+ group = form.save()
45
+ assert group.name == 'New Group'
46
+ assert AuthGroup.objects.filter(name='New Group').exists()
47
+
48
+ def test_save_updates_existing_group(self) -> None:
49
+ group = AuthGroup.objects.create(name='Original Name')
50
+ form = GroupForm(data={'name': 'Updated Name'}, instance=group)
51
+ assert form.is_valid()
52
+ updated_group = form.save()
53
+ assert updated_group.pk == group.pk
54
+ assert updated_group.name == 'Updated Name'
55
+
56
+ def test_name_with_special_characters(self) -> None:
57
+ form = GroupForm(data={'name': 'Test & Group <Special>'})
58
+ assert form.is_valid()
59
+
60
+ def test_name_with_unicode(self) -> None:
61
+ form = GroupForm(data={'name': 'Tëst Grøup 日本語'})
62
+ assert form.is_valid()
63
+
64
+ def test_name_max_length(self) -> None:
65
+ long_name = 'A' * 150
66
+ form = GroupForm(data={'name': long_name})
67
+ assert form.is_valid()
68
+
69
+ def test_name_exceeds_max_length(self) -> None:
70
+ long_name = 'A' * 151
71
+ form = GroupForm(data={'name': long_name})
72
+ assert not form.is_valid()
73
+
74
+ def test_duplicate_name_fails(self) -> None:
75
+ AuthGroup.objects.create(name='Existing Group')
76
+ form = GroupForm(data={'name': 'Existing Group'})
77
+ assert not form.is_valid()
78
+
79
+ def test_duplicate_name_case_sensitive(self) -> None:
80
+ AuthGroup.objects.create(name='Existing Group')
81
+ form = GroupForm(data={'name': 'existing group'})
82
+ assert form.is_valid()
83
+
84
+ def test_reserved_name_error_message(self) -> None:
85
+ form = GroupForm(data={'name': 'All Users'})
86
+ form.is_valid()
87
+ assert 'reserved' in form.errors['name'][0].lower()
88
+
89
+ def test_form_excludes_permissions_field(self) -> None:
90
+ form = GroupForm()
91
+ assert 'permissions' not in form.fields
92
+
93
+ def test_update_to_reserved_name_fails(self) -> None:
94
+ group = AuthGroup.objects.create(name='Original')
95
+ form = GroupForm(data={'name': 'All Users'}, instance=group)
96
+ assert not form.is_valid()
97
+
98
+ def test_name_with_leading_trailing_whitespace(self) -> None:
99
+ form = GroupForm(data={'name': ' Test Group '})
100
+ assert form.is_valid()
101
+
102
+ def test_name_with_newline(self) -> None:
103
+ form = GroupForm(data={'name': 'Test\nGroup'})
104
+ assert form.is_valid()
105
+
106
+ def test_name_with_tab(self) -> None:
107
+ form = GroupForm(data={'name': 'Test\tGroup'})
108
+ assert form.is_valid()
109
+
110
+ def test_numeric_name(self) -> None:
111
+ form = GroupForm(data={'name': '12345'})
112
+ assert form.is_valid()
113
+
114
+
115
+ class GroupNamesFormTestCase(BaseTestCase):
116
+ def test_valid_json_groups(self) -> None:
117
+ form = GroupNamesForm(data={'groups': '["Group A", "Group B"]'})
118
+ assert form.is_valid()
119
+ assert form.cleaned_data['groups'] == ['Group A', 'Group B']
120
+
121
+ def test_empty_groups(self) -> None:
122
+ form = GroupNamesForm(data={'groups': '[]'})
123
+ assert form.is_valid()
124
+ assert form.cleaned_data['groups'] == []
125
+
126
+ def test_save_creates_groups(self) -> None:
127
+ form = GroupNamesForm(data={'groups': '["Test A", "Test B"]'})
128
+ assert form.is_valid()
129
+ groups = form.save()
130
+ assert len(groups) == 2
131
+
132
+ def test_single_group(self) -> None:
133
+ form = GroupNamesForm(data={'groups': '["Single Group"]'})
134
+ assert form.is_valid()
135
+ assert form.cleaned_data['groups'] == ['Single Group']
136
+
137
+ def test_groups_with_special_characters(self) -> None:
138
+ form = GroupNamesForm(data={'groups': '["Group & Co", "Test <Group>"]'})
139
+ assert form.is_valid()
140
+ assert len(form.cleaned_data['groups']) == 2
141
+
142
+ def test_save_skips_existing_groups(self) -> None:
143
+ AuthGroup.objects.create(name='Existing')
144
+ form = GroupNamesForm(data={'groups': '["Existing", "New"]'})
145
+ assert form.is_valid()
146
+ groups = form.save()
147
+ assert len(groups) == 1
148
+ assert groups[0].name == 'New'
149
+
150
+ def test_many_groups(self) -> None:
151
+ group_names = [f'Group {i}' for i in range(20)]
152
+ form = GroupNamesForm(data={'groups': str(group_names).replace("'", '"')})
153
+ assert form.is_valid()
154
+ assert len(form.cleaned_data['groups']) == 20
155
+
156
+ def test_unicode_group_names(self) -> None:
157
+ form = GroupNamesForm(data={'groups': '["Tëst", "日本語"]'})
158
+ assert form.is_valid()
159
+ assert len(form.cleaned_data['groups']) == 2
160
+
161
+ def test_empty_string_in_list(self) -> None:
162
+ form = GroupNamesForm(data={'groups': '["", "Valid"]'})
163
+ assert form.is_valid()
164
+ assert '' in form.cleaned_data['groups']
165
+
166
+ def test_whitespace_group_names(self) -> None:
167
+ form = GroupNamesForm(data={'groups': '[" Spaces ", "Normal"]'})
168
+ assert form.is_valid()
169
+
170
+ def test_nested_json_fails(self) -> None:
171
+ form = GroupNamesForm(data={'groups': '[["nested"]]'})
172
+ assert form.is_valid()
173
+
174
+ def test_null_in_list(self) -> None:
175
+ form = GroupNamesForm(data={'groups': '[null, "Valid"]'})
176
+ assert form.is_valid()
177
+
178
+ def test_number_in_list(self) -> None:
179
+ form = GroupNamesForm(data={'groups': '[123, "Valid"]'})
180
+ assert form.is_valid()
181
+
182
+ def test_duplicate_names_in_list(self) -> None:
183
+ form = GroupNamesForm(data={'groups': '["Dup", "Dup", "Dup"]'})
184
+ assert form.is_valid()
185
+
186
+ def test_groups_field_not_required(self) -> None:
187
+ form = GroupNamesForm(data={})
188
+ assert not form.is_valid()
189
+
190
+
191
+ class GroupUserFormTestCase(BaseTestCase):
192
+ def setUp(self) -> None:
193
+ super().setUp()
194
+
195
+ self.user1 = create_user(username='user1', first_name='User', last_name='One')
196
+ self.user2 = create_user(username='user2', first_name='User', last_name='Two')
197
+ self.user3 = create_user(username='user3', first_name='User', last_name='Three')
198
+
199
+ def test_valid_form_with_users(self) -> None:
200
+ form = GroupUserForm(data={'users': [self.user1.pk, self.user2.pk]})
201
+ assert form.is_valid()
202
+
203
+ def test_valid_form_empty_users(self) -> None:
204
+ form = GroupUserForm(data={'users': []})
205
+ assert form.is_valid()
206
+
207
+ def test_user_label(self) -> None:
208
+ label = GroupUserForm.user_label(self.user1)
209
+ assert label == 'User One'
210
+
211
+ def test_single_user(self) -> None:
212
+ form = GroupUserForm(data={'users': [self.user1.pk]})
213
+ assert form.is_valid()
214
+ assert len(form.cleaned_data['users']) == 1
215
+
216
+ def test_all_users(self) -> None:
217
+ form = GroupUserForm(data={'users': [self.user1.pk, self.user2.pk, self.user3.pk]})
218
+ assert form.is_valid()
219
+ assert len(form.cleaned_data['users']) == 3
220
+
221
+ def test_invalid_user_id(self) -> None:
222
+ form = GroupUserForm(data={'users': [99999]})
223
+ assert not form.is_valid()
224
+
225
+ def test_inactive_user_excluded_from_queryset(self) -> None:
226
+ inactive_user = create_user(username='inactive', is_active=False)
227
+ form = GroupUserForm()
228
+ user_ids = list(form.fields['users'].queryset.values_list('id', flat=True))
229
+ assert inactive_user.pk not in user_ids
230
+
231
+ def test_user_label_with_empty_names(self) -> None:
232
+ user = create_user(username='noname', first_name='', last_name='')
233
+ label = GroupUserForm.user_label(user)
234
+ assert label == ''
235
+
236
+ def test_user_label_first_name_only(self) -> None:
237
+ user = create_user(username='firstonly', first_name='First', last_name='')
238
+ label = GroupUserForm.user_label(user)
239
+ assert 'First' in label
240
+
241
+ def test_user_label_last_name_only(self) -> None:
242
+ user = create_user(username='lastonly', first_name='', last_name='Last')
243
+ label = GroupUserForm.user_label(user)
244
+ assert 'Last' in label
245
+
246
+ def test_mixed_valid_invalid_user_ids(self) -> None:
247
+ form = GroupUserForm(data={'users': [self.user1.pk, 99999]})
248
+ assert not form.is_valid()
249
+
250
+ def test_duplicate_user_ids(self) -> None:
251
+ form = GroupUserForm(data={'users': [self.user1.pk, self.user1.pk]})
252
+ assert form.is_valid()
253
+ assert len(form.cleaned_data['users']) == 1
254
+
255
+ def test_negative_user_id(self) -> None:
256
+ form = GroupUserForm(data={'users': [-1]})
257
+ assert not form.is_valid()
258
+
259
+ def test_string_user_id(self) -> None:
260
+ form = GroupUserForm(data={'users': ['invalid']})
261
+ assert not form.is_valid()
262
+
263
+ def test_queryset_excludes_inactive_users(self) -> None:
264
+ create_user(username='inactive1', is_active=False)
265
+ create_user(username='inactive2', is_active=False)
266
+ form = GroupUserForm()
267
+ queryset = form.fields['users'].queryset
268
+ assert not queryset.filter(is_active=False).exists()
269
+
270
+ def test_form_users_field_not_required(self) -> None:
271
+ form = GroupUserForm(data={})
272
+ assert form.is_valid()
273
+
274
+ def test_user_label_with_unicode(self) -> None:
275
+ user = create_user(username='unicode', first_name='Tëst', last_name='Üsér')
276
+ label = GroupUserForm.user_label(user)
277
+ assert 'Tëst' in label
278
+ assert 'Üsér' in label
279
+
280
+ def test_zero_user_id(self) -> None:
281
+ form = GroupUserForm(data={'users': [0]})
282
+ assert not form.is_valid()
@@ -0,0 +1,192 @@
1
+ from __future__ import annotations
2
+
3
+ from django.contrib.auth.models import Permission
4
+ from django.contrib.contenttypes.models import ContentType
5
+
6
+ from django_spire.auth.group.models import AuthGroup
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 AuthGroupModelTestCase(BaseTestCase):
12
+ def setUp(self) -> None:
13
+ super().setUp()
14
+
15
+ self.group = AuthGroup.objects.create(name='Test Group')
16
+
17
+ def test_str_representation(self) -> None:
18
+ assert str(self.group) == 'Test Group'
19
+
20
+ def test_str_representation_with_special_characters(self) -> None:
21
+ group = AuthGroup.objects.create(name='Test & Group <Special>')
22
+ assert str(group) == 'Test & Group <Special>'
23
+
24
+ def test_base_breadcrumb_returns_breadcrumbs(self) -> None:
25
+ crumbs = AuthGroup.base_breadcrumb()
26
+ assert crumbs is not None
27
+
28
+ def test_breadcrumbs_with_pk_returns_breadcrumbs(self) -> None:
29
+ crumbs = self.group.breadcrumbs()
30
+ assert crumbs is not None
31
+
32
+ def test_breadcrumbs_without_pk_returns_breadcrumbs(self) -> None:
33
+ unsaved_group = AuthGroup(name='Unsaved Group')
34
+ crumbs = unsaved_group.breadcrumbs()
35
+ assert crumbs is not None
36
+
37
+ def test_meta_proxy(self) -> None:
38
+ assert AuthGroup._meta.proxy
39
+
40
+ def test_meta_verbose_name(self) -> None:
41
+ assert AuthGroup._meta.verbose_name == 'Auth Group'
42
+ assert AuthGroup._meta.verbose_name_plural == 'Auth Groups'
43
+
44
+ def test_group_creation(self) -> None:
45
+ group = AuthGroup.objects.create(name='New Test Group')
46
+ assert group.pk is not None
47
+ assert AuthGroup.objects.filter(pk=group.pk).exists()
48
+
49
+ def test_group_update(self) -> None:
50
+ self.group.name = 'Updated Group Name'
51
+ self.group.save()
52
+ self.group.refresh_from_db()
53
+ assert self.group.name == 'Updated Group Name'
54
+
55
+ def test_group_deletion(self) -> None:
56
+ group_pk = self.group.pk
57
+ self.group.delete()
58
+ assert not AuthGroup.objects.filter(pk=group_pk).exists()
59
+
60
+ def test_group_user_relationship(self) -> None:
61
+ user = create_user(username='groupuser')
62
+ self.group.user_set.add(user)
63
+ assert user in self.group.user_set.all()
64
+ assert self.group in user.groups.all()
65
+
66
+ def test_group_permissions_relationship(self) -> None:
67
+ assert hasattr(self.group, 'permissions')
68
+
69
+ def test_group_name_max_length(self) -> None:
70
+ max_length = AuthGroup._meta.get_field('name').max_length
71
+ assert max_length == 150
72
+
73
+ def test_multiple_groups_creation(self) -> None:
74
+ groups = [
75
+ AuthGroup.objects.create(name=f'Group {i}')
76
+ for i in range(5)
77
+ ]
78
+ assert len(groups) == 5
79
+ assert AuthGroup.objects.count() >= 5
80
+
81
+ def test_group_queryset_ordering(self) -> None:
82
+ AuthGroup.objects.create(name='Zebra Group')
83
+ AuthGroup.objects.create(name='Alpha Group')
84
+ groups = AuthGroup.objects.all().order_by('name')
85
+ names = list(groups.values_list('name', flat=True))
86
+ assert names == sorted(names)
87
+
88
+ def test_group_add_multiple_users(self) -> None:
89
+ user1 = create_user(username='user1')
90
+ user2 = create_user(username='user2')
91
+ self.group.user_set.add(user1, user2)
92
+ assert self.group.user_set.count() == 2
93
+
94
+ def test_group_remove_user(self) -> None:
95
+ user = create_user(username='removeuser')
96
+ self.group.user_set.add(user)
97
+ self.group.user_set.remove(user)
98
+ assert user not in self.group.user_set.all()
99
+
100
+ def test_group_clear_users(self) -> None:
101
+ user1 = create_user(username='clearuser1')
102
+ user2 = create_user(username='clearuser2')
103
+ self.group.user_set.add(user1, user2)
104
+ self.group.user_set.clear()
105
+ assert self.group.user_set.count() == 0
106
+
107
+ def test_group_add_permission(self) -> None:
108
+ content_type = ContentType.objects.get_for_model(AuthGroup)
109
+ permission = Permission.objects.filter(content_type=content_type).first()
110
+ if permission:
111
+ self.group.permissions.add(permission)
112
+ assert permission in self.group.permissions.all()
113
+
114
+ def test_group_remove_permission(self) -> None:
115
+ content_type = ContentType.objects.get_for_model(AuthGroup)
116
+ permission = Permission.objects.filter(content_type=content_type).first()
117
+ if permission:
118
+ self.group.permissions.add(permission)
119
+ self.group.permissions.remove(permission)
120
+ assert permission not in self.group.permissions.all()
121
+
122
+ def test_group_with_unicode_name(self) -> None:
123
+ group = AuthGroup.objects.create(name='Tëst Grøup 日本語')
124
+ assert group.name == 'Tëst Grøup 日本語'
125
+ assert str(group) == 'Tëst Grøup 日本語'
126
+
127
+ def test_group_name_with_max_length(self) -> None:
128
+ long_name = 'A' * 150
129
+ group = AuthGroup.objects.create(name=long_name)
130
+ assert group.name == long_name
131
+
132
+ def test_breadcrumbs_contains_group_name(self) -> None:
133
+ crumbs = self.group.breadcrumbs()
134
+ names = [c['name'] for c in crumbs]
135
+ assert 'Test Group' in names
136
+
137
+ def test_base_breadcrumb_contains_groups(self) -> None:
138
+ crumbs = AuthGroup.base_breadcrumb()
139
+ names = [c['name'] for c in crumbs]
140
+ assert 'Groups' in names
141
+
142
+ def test_group_user_set_query(self) -> None:
143
+ user = create_user(username='queryuser')
144
+ self.group.user_set.add(user)
145
+ queried_users = self.group.user_set.filter(username='queryuser')
146
+ assert queried_users.count() == 1
147
+
148
+ def test_group_inheritance_from_django_group(self) -> None:
149
+ from django.contrib.auth.models import Group
150
+ assert issubclass(AuthGroup, Group)
151
+
152
+ def test_group_activity_mixin(self) -> None:
153
+ assert hasattr(self.group, 'add_activity')
154
+
155
+ def test_group_empty_name_constraint(self) -> None:
156
+ group = AuthGroup.objects.create(name='')
157
+ assert group.pk is not None
158
+
159
+ def test_group_whitespace_name(self) -> None:
160
+ group = AuthGroup.objects.create(name=' ')
161
+ assert group.name == ' '
162
+
163
+ def test_group_special_characters_name(self) -> None:
164
+ special_name = '!@#$%^&*()_+-=[]{}|;:,.<>?'
165
+ group = AuthGroup.objects.create(name=special_name)
166
+ assert group.name == special_name
167
+
168
+ def test_group_refresh_from_db(self) -> None:
169
+ original_name = self.group.name
170
+ AuthGroup.objects.filter(pk=self.group.pk).update(name='Changed Name')
171
+ self.group.refresh_from_db()
172
+ assert self.group.name == 'Changed Name'
173
+ assert self.group.name != original_name
174
+
175
+ def test_group_get_or_create(self) -> None:
176
+ group, created = AuthGroup.objects.get_or_create(name='Get Or Create Group')
177
+ assert created
178
+ group2, created2 = AuthGroup.objects.get_or_create(name='Get Or Create Group')
179
+ assert not created2
180
+ assert group.pk == group2.pk
181
+
182
+ def test_group_bulk_create(self) -> None:
183
+ groups = AuthGroup.objects.bulk_create([
184
+ AuthGroup(name='Bulk 1'),
185
+ AuthGroup(name='Bulk 2'),
186
+ AuthGroup(name='Bulk 3'),
187
+ ])
188
+ assert len(groups) == 3
189
+
190
+ def test_group_exists(self) -> None:
191
+ assert AuthGroup.objects.filter(name='Test Group').exists()
192
+ assert not AuthGroup.objects.filter(name='Nonexistent Group').exists()
@@ -0,0 +1,98 @@
1
+ from __future__ import annotations
2
+
3
+ from django_spire.auth.group.models import AuthGroup
4
+ from django_spire.auth.group.querysets import GroupQuerySet
5
+ from django_spire.core.tests.test_cases import BaseTestCase
6
+
7
+
8
+ class GroupQuerySetTestCase(BaseTestCase):
9
+ def setUp(self) -> None:
10
+ super().setUp()
11
+
12
+ self.group_c = AuthGroup.objects.create(name='Charlie Group')
13
+ self.group_a = AuthGroup.objects.create(name='Alpha Group')
14
+ self.group_b = AuthGroup.objects.create(name='Beta Group')
15
+
16
+ def test_active_returns_ordered_by_name(self) -> None:
17
+ queryset = AuthGroup.objects.all()
18
+ queryset.__class__ = GroupQuerySet
19
+ result = queryset.active()
20
+ names = list(result.values_list('name', flat=True))
21
+ assert names == ['Alpha Group', 'Beta Group', 'Charlie Group']
22
+
23
+ def test_active_returns_all_groups(self) -> None:
24
+ queryset = AuthGroup.objects.all()
25
+ queryset.__class__ = GroupQuerySet
26
+ result = queryset.active()
27
+ assert result.count() == 3
28
+
29
+ def test_active_empty_queryset(self) -> None:
30
+ AuthGroup.objects.all().delete()
31
+ queryset = AuthGroup.objects.all()
32
+ queryset.__class__ = GroupQuerySet
33
+ result = queryset.active()
34
+ assert result.count() == 0
35
+
36
+ def test_active_single_group(self) -> None:
37
+ AuthGroup.objects.all().delete()
38
+ AuthGroup.objects.create(name='Single Group')
39
+ queryset = AuthGroup.objects.all()
40
+ queryset.__class__ = GroupQuerySet
41
+ result = queryset.active()
42
+ assert result.count() == 1
43
+
44
+ def test_active_ordering_with_numbers(self) -> None:
45
+ group1 = AuthGroup.objects.create(name='AAA First')
46
+ group2 = AuthGroup.objects.create(name='BBB Second')
47
+ group3 = AuthGroup.objects.create(name='CCC Third')
48
+ queryset = AuthGroup.objects.filter(pk__in=[group1.pk, group2.pk, group3.pk])
49
+ queryset.__class__ = GroupQuerySet
50
+ result = queryset.active()
51
+ names = list(result.values_list('name', flat=True))
52
+ assert names == ['AAA First', 'BBB Second', 'CCC Third']
53
+
54
+ def test_active_ordering_with_special_characters(self) -> None:
55
+ AuthGroup.objects.create(name='!Special')
56
+ AuthGroup.objects.create(name='@Another')
57
+ queryset = AuthGroup.objects.all()
58
+ queryset.__class__ = GroupQuerySet
59
+ result = queryset.active()
60
+ assert result.count() >= 2
61
+
62
+ def test_active_ordering_case_sensitive(self) -> None:
63
+ AuthGroup.objects.all().delete()
64
+ AuthGroup.objects.create(name='alpha')
65
+ AuthGroup.objects.create(name='Alpha')
66
+ AuthGroup.objects.create(name='ALPHA')
67
+ queryset = AuthGroup.objects.all()
68
+ queryset.__class__ = GroupQuerySet
69
+ result = queryset.active()
70
+ names = list(result.values_list('name', flat=True))
71
+ assert len(names) == 3
72
+
73
+ def test_active_ordering_with_unicode(self) -> None:
74
+ AuthGroup.objects.create(name='Ñame')
75
+ AuthGroup.objects.create(name='Ääkkönen')
76
+ queryset = AuthGroup.objects.all()
77
+ queryset.__class__ = GroupQuerySet
78
+ result = queryset.active()
79
+ assert result.count() >= 2
80
+
81
+ def test_active_is_chainable(self) -> None:
82
+ queryset = AuthGroup.objects.all()
83
+ queryset.__class__ = GroupQuerySet
84
+ result = queryset.active().filter(name__contains='Group')
85
+ assert result.count() == 3
86
+
87
+ def test_active_with_prefetch(self) -> None:
88
+ queryset = AuthGroup.objects.all().prefetch_related('permissions')
89
+ queryset.__class__ = GroupQuerySet
90
+ result = queryset.active()
91
+ assert result.count() == 3
92
+
93
+ def test_active_returns_queryset(self) -> None:
94
+ queryset = AuthGroup.objects.all()
95
+ queryset.__class__ = GroupQuerySet
96
+ result = queryset.active()
97
+ assert hasattr(result, 'filter')
98
+ assert hasattr(result, 'exclude')