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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (538) hide show
  1. django_spire/ai/admin.py +11 -11
  2. django_spire/ai/chat/apps.py +1 -0
  3. django_spire/ai/chat/templates/django_spire/ai/chat/widget/dialog_widget.html +1 -1
  4. django_spire/ai/chat/tests/factories.py +15 -0
  5. django_spire/ai/chat/tests/test_controller.py +45 -0
  6. django_spire/ai/chat/tests/test_models.py +301 -0
  7. django_spire/ai/chat/tests/test_prompts.py +48 -0
  8. django_spire/ai/chat/tests/test_responses.py +208 -0
  9. django_spire/ai/chat/tests/test_router/test_base_chat_router.py +66 -6
  10. django_spire/ai/chat/tests/test_router/test_chat_workflow.py +73 -3
  11. django_spire/ai/chat/tests/test_router/test_integration.py +86 -6
  12. django_spire/ai/chat/tests/test_router/test_intent_decoder.py +93 -1
  13. django_spire/ai/chat/tests/test_router/test_message_intel.py +60 -1
  14. django_spire/ai/chat/tests/test_router/test_spire_chat_router.py +110 -0
  15. django_spire/ai/chat/tests/test_urls/test_json_urls.py +202 -1
  16. django_spire/ai/context/tests/__init__.py +0 -0
  17. django_spire/ai/context/tests/test_context.py +188 -0
  18. django_spire/ai/decorators.py +7 -6
  19. django_spire/ai/prompt/tests/test_bots.py +100 -10
  20. django_spire/ai/prompt/tests/test_prompt_intel.py +83 -0
  21. django_spire/ai/prompt/tests/test_prompt_tuning.py +126 -0
  22. django_spire/ai/sms/decorators.py +8 -2
  23. django_spire/ai/sms/tests/test_sms.py +240 -16
  24. django_spire/ai/sms/tests/test_sms_intel.py +42 -0
  25. django_spire/ai/sms/tests/test_webhook.py +155 -7
  26. django_spire/ai/sms/views.py +23 -24
  27. django_spire/ai/tests/test_ai.py +131 -7
  28. django_spire/auth/apps.py +4 -2
  29. django_spire/auth/controller/controller.py +36 -23
  30. django_spire/auth/controller/exceptions.py +9 -0
  31. django_spire/auth/group/admin.py +1 -0
  32. django_spire/auth/group/apps.py +2 -0
  33. django_spire/auth/group/factories.py +17 -8
  34. django_spire/auth/group/forms.py +7 -0
  35. django_spire/auth/group/tests/test_factories.py +146 -0
  36. django_spire/auth/group/tests/test_forms.py +282 -0
  37. django_spire/auth/group/tests/test_models.py +192 -0
  38. django_spire/auth/group/tests/test_querysets.py +98 -0
  39. django_spire/auth/group/tests/test_utils.py +341 -0
  40. django_spire/auth/group/tests/test_views.py +377 -0
  41. django_spire/auth/group/urls/__init__.py +3 -1
  42. django_spire/auth/group/urls/form_urls.py +2 -0
  43. django_spire/auth/group/urls/json_urls.py +3 -0
  44. django_spire/auth/group/urls/page_urls.py +2 -0
  45. django_spire/auth/group/utils.py +6 -2
  46. django_spire/auth/group/views/form_views.py +6 -3
  47. django_spire/auth/group/views/json_views.py +6 -2
  48. django_spire/auth/mfa/admin.py +2 -0
  49. django_spire/auth/mfa/apps.py +2 -0
  50. django_spire/auth/mfa/forms.py +1 -0
  51. django_spire/auth/mfa/querysets.py +9 -2
  52. django_spire/auth/mfa/tests/test_models.py +233 -0
  53. django_spire/auth/mfa/tests/test_utils.py +106 -0
  54. django_spire/auth/mfa/urls/__init__.py +2 -0
  55. django_spire/auth/mfa/urls/page_urls.py +2 -0
  56. django_spire/auth/mfa/urls/redirect_urls.py +2 -0
  57. django_spire/auth/mfa/views/page_views.py +2 -1
  58. django_spire/auth/permissions/consts.py +2 -2
  59. django_spire/auth/permissions/decorators.py +8 -8
  60. django_spire/auth/permissions/permissions.py +28 -35
  61. django_spire/auth/permissions/tests/test_decorators.py +333 -0
  62. django_spire/auth/permissions/tests/test_permissions.py +337 -0
  63. django_spire/auth/permissions/tests/test_tools.py +305 -0
  64. django_spire/auth/permissions/tools.py +21 -15
  65. django_spire/auth/seeding/seed.py +3 -0
  66. django_spire/auth/seeding/seeder.py +2 -0
  67. django_spire/auth/tests/test_controller.py +323 -0
  68. django_spire/auth/tests/test_url_endpoints.py +9 -9
  69. django_spire/auth/tests/test_views.py +406 -0
  70. django_spire/auth/urls/admin_urls.py +2 -0
  71. django_spire/auth/urls/redirect_urls.py +2 -0
  72. django_spire/auth/user/apps.py +2 -0
  73. django_spire/auth/user/forms.py +9 -0
  74. django_spire/auth/user/models.py +1 -1
  75. django_spire/auth/user/services/services.py +1 -0
  76. django_spire/auth/user/tests/factories.py +14 -13
  77. django_spire/auth/user/tests/test_factories.py +166 -2
  78. django_spire/auth/user/tests/test_forms.py +573 -0
  79. django_spire/auth/user/tests/test_models.py +257 -0
  80. django_spire/auth/user/tests/test_services.py +200 -0
  81. django_spire/auth/user/tests/test_tools.py +153 -0
  82. django_spire/auth/user/tests/test_user_factories.py +139 -0
  83. django_spire/auth/user/tests/test_views.py +363 -0
  84. django_spire/auth/user/tools.py +7 -1
  85. django_spire/auth/user/urls/form_urls.py +3 -0
  86. django_spire/auth/user/urls/page_urls.py +3 -0
  87. django_spire/auth/user/views/form_views.py +19 -10
  88. django_spire/auth/user/views/page_views.py +8 -2
  89. django_spire/auth/views/redirect_views.py +14 -9
  90. django_spire/comment/admin.py +2 -0
  91. django_spire/comment/apps.py +2 -0
  92. django_spire/comment/templatetags/comment_tags.py +1 -0
  93. django_spire/comment/tests/test_forms.py +27 -0
  94. django_spire/comment/tests/test_models.py +215 -0
  95. django_spire/comment/tests/test_querysets.py +101 -0
  96. django_spire/comment/tests/test_utils.py +90 -0
  97. django_spire/comment/urls.py +2 -0
  98. django_spire/comment/utils.py +22 -13
  99. django_spire/comment/views.py +1 -1
  100. django_spire/conf.py +8 -6
  101. django_spire/consts.py +1 -1
  102. django_spire/contrib/breadcrumb/apps.py +2 -0
  103. django_spire/contrib/breadcrumb/breadcrumbs.py +18 -18
  104. django_spire/contrib/breadcrumb/tests/test_breadcrumbs.py +198 -0
  105. django_spire/contrib/constructor/__init__.py +3 -3
  106. django_spire/contrib/constructor/constructor.py +15 -15
  107. django_spire/contrib/constructor/django_model_constructor.py +5 -4
  108. django_spire/contrib/constructor/exceptions.py +5 -3
  109. django_spire/contrib/constructor/tests/__init__.py +0 -0
  110. django_spire/contrib/constructor/tests/test_constructor.py +193 -0
  111. django_spire/contrib/form/tests/__init__.py +0 -0
  112. django_spire/contrib/form/tests/test_forms.py +203 -0
  113. django_spire/contrib/generic_views/modal_views.py +2 -1
  114. django_spire/contrib/generic_views/portal_views.py +20 -19
  115. django_spire/contrib/generic_views/tests/__init__.py +0 -0
  116. django_spire/contrib/generic_views/tests/test_views.py +459 -0
  117. django_spire/contrib/help/apps.py +2 -0
  118. django_spire/contrib/help/templatetags/help.py +1 -0
  119. django_spire/contrib/help/tests/__init__.py +0 -0
  120. django_spire/contrib/help/tests/test_templatetags.py +100 -0
  121. django_spire/contrib/options/mixins.py +6 -5
  122. django_spire/contrib/options/tests/factories.py +5 -1
  123. django_spire/contrib/options/tests/test_options.py +234 -0
  124. django_spire/contrib/ordering/exceptions.py +7 -3
  125. django_spire/contrib/ordering/mixins.py +2 -0
  126. django_spire/contrib/ordering/querysets.py +3 -1
  127. django_spire/contrib/ordering/services/processor_service.py +8 -4
  128. django_spire/contrib/ordering/services/service.py +1 -2
  129. django_spire/contrib/ordering/tests/__init__.py +0 -0
  130. django_spire/contrib/ordering/tests/test_ordering.py +165 -0
  131. django_spire/contrib/ordering/validators.py +6 -6
  132. django_spire/contrib/pagination/templatetags/pagination_tags.py +12 -5
  133. django_spire/contrib/pagination/tests/__init__.py +0 -0
  134. django_spire/contrib/pagination/tests/test_pagination.py +179 -0
  135. django_spire/contrib/performance/decorators.py +16 -6
  136. django_spire/contrib/performance/tests/__init__.py +0 -0
  137. django_spire/contrib/performance/tests/test_performance.py +107 -0
  138. django_spire/contrib/queryset/enums.py +3 -1
  139. django_spire/contrib/queryset/filter_tools.py +10 -5
  140. django_spire/contrib/queryset/mixins.py +16 -16
  141. django_spire/contrib/queryset/tests/__init__.py +0 -0
  142. django_spire/contrib/queryset/tests/test_queryset.py +137 -0
  143. django_spire/contrib/seeding/field/base.py +13 -7
  144. django_spire/contrib/seeding/field/callable.py +8 -1
  145. django_spire/contrib/seeding/field/cleaners.py +5 -5
  146. django_spire/contrib/seeding/field/custom.py +20 -10
  147. django_spire/contrib/seeding/field/django/seeder.py +8 -6
  148. django_spire/contrib/seeding/field/enums.py +7 -5
  149. django_spire/contrib/seeding/field/override.py +16 -6
  150. django_spire/contrib/seeding/field/static.py +9 -2
  151. django_spire/contrib/seeding/field/tests/test_base.py +18 -14
  152. django_spire/contrib/seeding/field/tests/test_callable.py +13 -9
  153. django_spire/contrib/seeding/field/tests/test_cleaners.py +51 -38
  154. django_spire/contrib/seeding/field/tests/test_static.py +13 -9
  155. django_spire/contrib/seeding/intelligence/bots/seeder_generator_bot.py +2 -0
  156. django_spire/contrib/seeding/intelligence/intel.py +5 -1
  157. django_spire/contrib/seeding/intelligence/prompts/factory.py +6 -1
  158. django_spire/contrib/seeding/intelligence/prompts/foreign_key_selection_prompt.py +6 -1
  159. django_spire/contrib/seeding/intelligence/prompts/generate_django_model_seeder_prompts.py +2 -0
  160. django_spire/contrib/seeding/intelligence/prompts/generic_relationship_selection_prompt.py +7 -1
  161. django_spire/contrib/seeding/intelligence/prompts/hierarchical_selection_prompt.py +6 -2
  162. django_spire/contrib/seeding/intelligence/prompts/model_field_choices_prompt.py +8 -2
  163. django_spire/contrib/seeding/intelligence/prompts/negation_prompt.py +2 -0
  164. django_spire/contrib/seeding/intelligence/prompts/objective_prompt.py +6 -1
  165. django_spire/contrib/seeding/management/commands/seeding.py +9 -3
  166. django_spire/contrib/seeding/management/example.py +2 -0
  167. django_spire/contrib/seeding/model/base.py +16 -7
  168. django_spire/contrib/seeding/model/config.py +31 -15
  169. django_spire/contrib/seeding/model/django/config.py +13 -13
  170. django_spire/contrib/seeding/model/django/seeder.py +4 -4
  171. django_spire/contrib/seeding/model/django/tests/test_seeder.py +34 -23
  172. django_spire/contrib/seeding/model/enums.py +2 -0
  173. django_spire/contrib/seeding/tests/test_config.py +71 -0
  174. django_spire/contrib/seeding/tests/test_custom.py +35 -0
  175. django_spire/contrib/seeding/tests/test_enums.py +40 -0
  176. django_spire/contrib/seeding/tests/test_intel.py +32 -0
  177. django_spire/contrib/seeding/tests/test_override.py +63 -0
  178. django_spire/contrib/service/__init__.py +2 -2
  179. django_spire/contrib/service/django_model_service.py +16 -15
  180. django_spire/contrib/service/exceptions.py +5 -3
  181. django_spire/contrib/service/tests/__init__.py +0 -0
  182. django_spire/contrib/service/tests/test_service.py +153 -0
  183. django_spire/contrib/session/apps.py +2 -0
  184. django_spire/contrib/session/controller.py +48 -42
  185. django_spire/contrib/session/templatetags/session_tags.py +11 -2
  186. django_spire/contrib/session/tests/test_session_controller.py +117 -53
  187. django_spire/contrib/tests/__init__.py +0 -0
  188. django_spire/contrib/tests/test_utils.py +37 -0
  189. django_spire/contrib/utils.py +4 -1
  190. django_spire/core/apps.py +2 -0
  191. django_spire/core/converters/tests/test_to_data.py +353 -0
  192. django_spire/core/converters/tests/test_to_enums.py +61 -41
  193. django_spire/core/converters/tests/test_to_pydantic.py +138 -109
  194. django_spire/core/converters/to_data.py +29 -10
  195. django_spire/core/converters/to_enums.py +4 -2
  196. django_spire/core/converters/to_pydantic.py +22 -22
  197. django_spire/core/decorators.py +19 -6
  198. django_spire/core/forms/widgets.py +4 -0
  199. django_spire/core/maps.py +3 -1
  200. django_spire/core/middleware/maintenance.py +3 -3
  201. django_spire/core/middleware.py +8 -6
  202. django_spire/core/redirect/__init__.py +5 -0
  203. django_spire/core/redirect/generic_redirect.py +1 -2
  204. django_spire/core/redirect/tests/__init__.py +0 -0
  205. django_spire/core/redirect/tests/test_generic_redirect.py +34 -0
  206. django_spire/core/{tests/tests_redirect.py → redirect/tests/test_safe_redirect.py} +55 -81
  207. django_spire/core/shortcuts.py +3 -3
  208. django_spire/core/static/django_spire/css/app-layout.css +1 -1
  209. django_spire/core/static/django_spire/css/app-navigation.css +3 -3
  210. django_spire/core/static/django_spire/css/bootstrap-override.css +4 -0
  211. django_spire/core/tag/admin.py +12 -0
  212. django_spire/core/tag/intelligence/tag_set_bot.py +2 -0
  213. django_spire/core/tag/mixins.py +2 -0
  214. django_spire/core/tag/models.py +2 -0
  215. django_spire/core/tag/querysets.py +2 -0
  216. django_spire/core/tag/service/tag_service.py +6 -3
  217. django_spire/core/tag/tests/test_intelligence.py +9 -9
  218. django_spire/core/tag/tests/test_tags.py +44 -54
  219. django_spire/core/tag/tests/test_tools.py +191 -0
  220. django_spire/core/tag/tools.py +3 -0
  221. django_spire/core/templates/django_spire/card/card.html +5 -2
  222. django_spire/core/templates/django_spire/card/title_card.html +9 -4
  223. django_spire/core/templates/django_spire/infinite_scroll/base.html +1 -0
  224. django_spire/core/templates/django_spire/navigation/side_navigation.html +19 -24
  225. django_spire/core/templates/django_spire/page/full_page.html +46 -16
  226. django_spire/core/templates/django_spire/table/base.html +4 -2
  227. django_spire/core/templatetags/json.py +6 -2
  228. django_spire/core/templatetags/message.py +13 -8
  229. django_spire/core/templatetags/string_formating.py +8 -5
  230. django_spire/core/templatetags/tests/__init__.py +0 -0
  231. django_spire/core/templatetags/tests/test_templatetags.py +427 -0
  232. django_spire/core/templatetags/variable_types.py +17 -9
  233. django_spire/core/tests/test_cases.py +1 -1
  234. django_spire/core/tests/test_conf.py +43 -0
  235. django_spire/core/tests/test_consts.py +28 -0
  236. django_spire/core/tests/test_context_processors.py +93 -0
  237. django_spire/core/tests/test_decorators.py +95 -0
  238. django_spire/core/tests/test_django_spire_utils.py +56 -0
  239. django_spire/core/tests/test_exceptions.py +37 -0
  240. django_spire/core/tests/test_models.py +54 -0
  241. django_spire/core/tests/test_settings.py +45 -0
  242. django_spire/core/tests/test_shortcuts.py +74 -0
  243. django_spire/core/tests/test_urls.py +16 -0
  244. django_spire/core/tests/test_utils.py +58 -0
  245. django_spire/core/urls.py +4 -1
  246. django_spire/core/utils.py +12 -8
  247. django_spire/exceptions.py +16 -1
  248. django_spire/file/admin.py +4 -2
  249. django_spire/file/apps.py +8 -10
  250. django_spire/file/fields.py +7 -7
  251. django_spire/file/forms.py +1 -1
  252. django_spire/file/interfaces.py +15 -15
  253. django_spire/file/mixins.py +1 -4
  254. django_spire/file/models.py +3 -5
  255. django_spire/file/tests/factories.py +59 -0
  256. django_spire/file/tests/test_admin.py +69 -0
  257. django_spire/file/tests/test_apps.py +24 -0
  258. django_spire/file/tests/test_fields.py +114 -0
  259. django_spire/file/tests/test_forms.py +20 -0
  260. django_spire/file/tests/test_interfaces.py +183 -0
  261. django_spire/file/tests/test_models.py +82 -0
  262. django_spire/file/tests/test_querysets.py +102 -0
  263. django_spire/file/tests/test_utils.py +32 -0
  264. django_spire/file/tests/test_views.py +145 -0
  265. django_spire/file/tests/test_widgets.py +82 -0
  266. django_spire/file/tools.py +8 -2
  267. django_spire/file/views.py +7 -3
  268. django_spire/file/widgets.py +12 -12
  269. django_spire/help_desk/admin.py +15 -0
  270. django_spire/help_desk/apps.py +2 -0
  271. django_spire/help_desk/auth/controller.py +2 -0
  272. django_spire/help_desk/choices.py +2 -0
  273. django_spire/help_desk/enums.py +2 -0
  274. django_spire/help_desk/exceptions.py +31 -3
  275. django_spire/help_desk/forms.py +2 -0
  276. django_spire/help_desk/models.py +2 -0
  277. django_spire/help_desk/querysets.py +4 -1
  278. django_spire/help_desk/services/notification_service.py +26 -27
  279. django_spire/help_desk/services/service.py +2 -3
  280. django_spire/help_desk/tests/factories.py +8 -3
  281. django_spire/help_desk/tests/test_admin.py +41 -0
  282. django_spire/help_desk/tests/test_apps.py +41 -0
  283. django_spire/help_desk/tests/test_choices.py +50 -0
  284. django_spire/help_desk/tests/test_controller.py +87 -0
  285. django_spire/help_desk/tests/test_enums.py +18 -0
  286. django_spire/help_desk/tests/test_exceptions.py +37 -0
  287. django_spire/help_desk/tests/test_forms.py +89 -0
  288. django_spire/help_desk/tests/test_models.py +59 -0
  289. django_spire/help_desk/tests/test_querysets.py +38 -0
  290. django_spire/help_desk/tests/test_services/test_notification_service.py +15 -8
  291. django_spire/help_desk/tests/test_services/test_service.py +92 -0
  292. django_spire/help_desk/tests/test_urls/test_form_urls.py +6 -6
  293. django_spire/help_desk/tests/test_urls/test_page_urls.py +8 -9
  294. django_spire/help_desk/tests/test_views/test_form_views.py +46 -19
  295. django_spire/help_desk/tests/test_views/test_page_views.py +32 -9
  296. django_spire/help_desk/urls/__init__.py +4 -1
  297. django_spire/help_desk/urls/form_urls.py +3 -0
  298. django_spire/help_desk/urls/page_urls.py +3 -0
  299. django_spire/help_desk/views/form_views.py +13 -5
  300. django_spire/help_desk/views/page_views.py +11 -3
  301. django_spire/history/activity/admin.py +2 -0
  302. django_spire/history/activity/apps.py +3 -1
  303. django_spire/history/activity/mixins.py +13 -7
  304. django_spire/history/activity/models.py +6 -5
  305. django_spire/history/activity/querysets.py +2 -0
  306. django_spire/history/activity/tests/__init__.py +0 -0
  307. django_spire/history/activity/tests/test_activity.py +176 -0
  308. django_spire/history/admin.py +9 -2
  309. django_spire/history/choices.py +3 -0
  310. django_spire/history/models.py +5 -5
  311. django_spire/history/tests/test_admin.py +93 -0
  312. django_spire/history/tests/test_history.py +101 -0
  313. django_spire/history/tests/test_mixins.py +84 -0
  314. django_spire/history/viewed/admin.py +3 -1
  315. django_spire/history/viewed/apps.py +3 -1
  316. django_spire/history/viewed/models.py +2 -0
  317. django_spire/history/viewed/tests/__init__.py +0 -0
  318. django_spire/history/viewed/tests/test_viewed.py +46 -0
  319. django_spire/knowledge/auth/tests/__init__.py +0 -0
  320. django_spire/knowledge/auth/tests/test_controller.py +116 -0
  321. django_spire/knowledge/collection/admin.py +5 -1
  322. django_spire/knowledge/collection/models.py +3 -1
  323. django_spire/knowledge/collection/seeding/seed.py +1 -0
  324. django_spire/knowledge/collection/services/factory_service.py +10 -11
  325. django_spire/knowledge/collection/services/ordering_service.py +1 -2
  326. django_spire/knowledge/collection/services/service.py +5 -10
  327. django_spire/knowledge/collection/services/tag_service.py +5 -2
  328. django_spire/knowledge/collection/tests/factories.py +28 -1
  329. django_spire/knowledge/collection/tests/test_models.py +48 -0
  330. django_spire/knowledge/collection/tests/test_querysets.py +93 -0
  331. django_spire/knowledge/collection/tests/test_services/test_factory_service.py +100 -0
  332. django_spire/knowledge/collection/tests/test_services/test_services.py +160 -0
  333. django_spire/knowledge/collection/tests/test_urls/test_form_urls.py +21 -3
  334. django_spire/knowledge/collection/tests/test_urls/test_json_urls.py +39 -1
  335. django_spire/knowledge/collection/tests/test_urls/test_page_urls.py +12 -4
  336. django_spire/knowledge/collection/urls/__init__.py +3 -0
  337. django_spire/knowledge/collection/urls/form_urls.py +2 -0
  338. django_spire/knowledge/collection/urls/json_urls.py +2 -0
  339. django_spire/knowledge/collection/urls/page_urls.py +2 -0
  340. django_spire/knowledge/collection/views/form_views.py +4 -4
  341. django_spire/knowledge/collection/views/json_views.py +5 -1
  342. django_spire/knowledge/collection/views/page_views.py +5 -2
  343. django_spire/knowledge/entry/admin.py +7 -1
  344. django_spire/knowledge/entry/forms.py +2 -0
  345. django_spire/knowledge/entry/models.py +2 -0
  346. django_spire/knowledge/entry/seeding/seed.py +3 -0
  347. django_spire/knowledge/entry/services/automation_service.py +5 -4
  348. django_spire/knowledge/entry/services/factory_service.py +7 -5
  349. django_spire/knowledge/entry/services/service.py +4 -7
  350. django_spire/knowledge/entry/services/tag_service.py +0 -1
  351. django_spire/knowledge/entry/services/tool_service.py +1 -0
  352. django_spire/knowledge/entry/services/transformation_services.py +1 -5
  353. django_spire/knowledge/entry/tests/factories.py +1 -2
  354. django_spire/knowledge/entry/tests/test_factory_service.py +20 -0
  355. django_spire/knowledge/entry/tests/test_models.py +41 -0
  356. django_spire/knowledge/entry/tests/test_querysets.py +71 -0
  357. django_spire/knowledge/entry/tests/test_services.py +94 -0
  358. django_spire/knowledge/entry/tests/test_urls/test_form_urls.py +9 -14
  359. django_spire/knowledge/entry/tests/test_urls/test_json_urls.py +48 -5
  360. django_spire/knowledge/entry/tests/test_urls/test_page_urls.py +6 -8
  361. django_spire/knowledge/entry/tests/test_urls/test_template_urls.py +40 -0
  362. django_spire/knowledge/entry/urls/form_urls.py +2 -0
  363. django_spire/knowledge/entry/urls/json_urls.py +2 -0
  364. django_spire/knowledge/entry/urls/page_urls.py +2 -0
  365. django_spire/knowledge/entry/urls/template_urls.py +2 -0
  366. django_spire/knowledge/entry/version/block/choices.py +2 -0
  367. django_spire/knowledge/entry/version/block/data/data.py +1 -0
  368. django_spire/knowledge/entry/version/block/data/list/data.py +8 -13
  369. django_spire/knowledge/entry/version/block/data/list/maps.py +3 -0
  370. django_spire/knowledge/entry/version/block/data/list/meta.py +1 -2
  371. django_spire/knowledge/entry/version/block/data/list/tests/__init__.py +0 -0
  372. django_spire/knowledge/entry/version/block/data/list/tests/test_maps.py +32 -0
  373. django_spire/knowledge/entry/version/block/data/list/tests/test_meta.py +58 -0
  374. django_spire/knowledge/entry/version/block/data/maps.py +3 -6
  375. django_spire/knowledge/entry/version/block/models.py +7 -5
  376. django_spire/knowledge/entry/version/block/seeding/constants.py +5 -4
  377. django_spire/knowledge/entry/version/block/services/service.py +2 -3
  378. django_spire/knowledge/entry/version/block/tests/factories.py +4 -10
  379. django_spire/knowledge/entry/version/block/tests/test_choices.py +56 -0
  380. django_spire/knowledge/entry/version/block/tests/test_data.py +90 -0
  381. django_spire/knowledge/entry/version/block/tests/test_maps.py +37 -0
  382. django_spire/knowledge/entry/version/block/tests/test_models.py +55 -0
  383. django_spire/knowledge/entry/version/block/tests/test_querysets.py +35 -0
  384. django_spire/knowledge/entry/version/block/tests/test_services.py +65 -0
  385. django_spire/knowledge/entry/version/choices.py +2 -0
  386. django_spire/knowledge/entry/version/converters/converter.py +1 -1
  387. django_spire/knowledge/entry/version/converters/docx_converter.py +4 -7
  388. django_spire/knowledge/entry/version/converters/markdown_converter.py +20 -20
  389. django_spire/knowledge/entry/version/maps.py +4 -5
  390. django_spire/knowledge/entry/version/querysets.py +1 -1
  391. django_spire/knowledge/entry/version/seeding/seeder.py +1 -2
  392. django_spire/knowledge/entry/version/services/processor_service.py +5 -4
  393. django_spire/knowledge/entry/version/services/service.py +1 -2
  394. django_spire/knowledge/entry/version/tests/factories.py +2 -2
  395. django_spire/knowledge/entry/version/tests/test_choices.py +18 -0
  396. django_spire/knowledge/entry/version/tests/test_converters/test_docx_converter.py +56 -8
  397. django_spire/knowledge/entry/version/tests/test_converters/test_markdown_converter.py +78 -0
  398. django_spire/knowledge/entry/version/tests/test_maps.py +58 -0
  399. django_spire/knowledge/entry/version/tests/test_models.py +23 -0
  400. django_spire/knowledge/entry/version/tests/test_querysets.py +26 -0
  401. django_spire/knowledge/entry/version/tests/test_services.py +62 -0
  402. django_spire/knowledge/entry/version/tests/test_urls/test_json_urls.py +27 -8
  403. django_spire/knowledge/entry/version/tests/test_urls/test_page_urls.py +15 -8
  404. django_spire/knowledge/entry/version/tests/test_urls/test_redirect_urls.py +38 -0
  405. django_spire/knowledge/entry/version/urls/__init__.py +3 -0
  406. django_spire/knowledge/entry/version/urls/json_urls.py +2 -1
  407. django_spire/knowledge/entry/version/urls/page_urls.py +2 -0
  408. django_spire/knowledge/entry/version/urls/redirect_urls.py +2 -0
  409. django_spire/knowledge/entry/version/views/json_views.py +5 -1
  410. django_spire/knowledge/entry/version/views/page_views.py +10 -3
  411. django_spire/knowledge/entry/version/views/redirect_views.py +5 -1
  412. django_spire/knowledge/entry/views/form_views.py +16 -8
  413. django_spire/knowledge/entry/views/json_views.py +3 -1
  414. django_spire/knowledge/entry/views/page_views.py +8 -2
  415. django_spire/knowledge/entry/views/template_views.py +7 -1
  416. django_spire/knowledge/exceptions.py +2 -1
  417. django_spire/knowledge/intelligence/intel/answer_intel.py +2 -1
  418. django_spire/knowledge/intelligence/intel/entry_intel.py +0 -1
  419. django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +4 -5
  420. django_spire/knowledge/models.py +1 -2
  421. django_spire/knowledge/tests/__init__.py +0 -0
  422. django_spire/knowledge/tests/test_templatetags.py +40 -0
  423. django_spire/knowledge/tests/test_urls/__init__.py +0 -0
  424. django_spire/knowledge/tests/test_urls/test_page_urls.py +24 -0
  425. django_spire/knowledge/urls/__init__.py +2 -0
  426. django_spire/knowledge/urls/page_urls.py +2 -0
  427. django_spire/knowledge/views/page_views.py +8 -3
  428. django_spire/notification/admin.py +3 -1
  429. django_spire/notification/app/admin.py +2 -0
  430. django_spire/notification/app/apps.py +3 -1
  431. django_spire/notification/app/exceptions.py +9 -2
  432. django_spire/notification/app/models.py +8 -4
  433. django_spire/notification/app/processor.py +22 -26
  434. django_spire/notification/app/querysets.py +2 -0
  435. django_spire/notification/app/tests/__init__.py +0 -0
  436. django_spire/notification/app/tests/factories.py +34 -0
  437. django_spire/notification/app/tests/test_apps.py +24 -0
  438. django_spire/notification/app/tests/test_models.py +72 -0
  439. django_spire/notification/app/tests/test_processor.py +111 -0
  440. django_spire/notification/app/tests/test_querysets.py +90 -0
  441. django_spire/notification/app/tests/test_views/__init__.py +0 -0
  442. django_spire/notification/app/tests/test_views/test_json_views.py +48 -0
  443. django_spire/notification/app/tests/test_views/test_page_views.py +19 -0
  444. django_spire/notification/app/urls/__init__.py +3 -1
  445. django_spire/notification/app/urls/json_urls.py +6 -4
  446. django_spire/notification/app/urls/page_urls.py +4 -3
  447. django_spire/notification/app/urls/template_urls.py +4 -2
  448. django_spire/notification/apps.py +4 -1
  449. django_spire/notification/email/admin.py +5 -1
  450. django_spire/notification/email/apps.py +3 -1
  451. django_spire/notification/email/exceptions.py +4 -2
  452. django_spire/notification/email/helper.py +5 -3
  453. django_spire/notification/email/models.py +4 -0
  454. django_spire/notification/email/processor.py +19 -15
  455. django_spire/notification/email/querysets.py +3 -0
  456. django_spire/notification/email/tests/__init__.py +0 -0
  457. django_spire/notification/email/tests/factories.py +35 -0
  458. django_spire/notification/email/tests/test_apps.py +24 -0
  459. django_spire/notification/email/tests/test_models.py +52 -0
  460. django_spire/notification/email/tests/test_processor.py +92 -0
  461. django_spire/notification/email/tests/test_querysets.py +43 -0
  462. django_spire/notification/exceptions.py +17 -2
  463. django_spire/notification/managers.py +7 -1
  464. django_spire/notification/maps.py +4 -1
  465. django_spire/notification/mixins.py +2 -0
  466. django_spire/notification/models.py +3 -1
  467. django_spire/notification/processors/notification.py +12 -5
  468. django_spire/notification/processors/processor.py +2 -0
  469. django_spire/notification/processors/tests/__init__.py +0 -0
  470. django_spire/notification/processors/tests/test_notification.py +106 -0
  471. django_spire/notification/push/admin.py +10 -1
  472. django_spire/notification/push/apps.py +3 -1
  473. django_spire/notification/push/models.py +2 -3
  474. django_spire/notification/push/tests/__init__.py +0 -0
  475. django_spire/notification/push/tests/test_apps.py +24 -0
  476. django_spire/notification/push/tests/test_models.py +28 -0
  477. django_spire/notification/querysets.py +7 -1
  478. django_spire/notification/sms/admin.py +2 -0
  479. django_spire/notification/sms/apps.py +4 -1
  480. django_spire/notification/sms/automations.py +2 -0
  481. django_spire/notification/sms/choices.py +2 -0
  482. django_spire/notification/sms/exceptions.py +19 -5
  483. django_spire/notification/sms/helper.py +33 -23
  484. django_spire/notification/sms/models.py +5 -1
  485. django_spire/notification/sms/processor.py +20 -20
  486. django_spire/notification/sms/querysets.py +2 -0
  487. django_spire/notification/sms/tests/factories.py +33 -0
  488. django_spire/notification/sms/tests/test_apps.py +24 -0
  489. django_spire/notification/sms/tests/test_automation.py +38 -0
  490. django_spire/notification/sms/tests/test_choices.py +15 -0
  491. django_spire/notification/sms/tests/test_consts.py +17 -0
  492. django_spire/notification/sms/tests/test_exceptions.py +27 -0
  493. django_spire/notification/sms/tests/test_helper.py +50 -0
  494. django_spire/notification/sms/tests/test_models.py +81 -0
  495. django_spire/notification/sms/tests/test_processor.py +107 -0
  496. django_spire/notification/sms/tests/test_tools.py +25 -11
  497. django_spire/notification/sms/tools.py +16 -5
  498. django_spire/notification/sms/urls/__init__.py +3 -1
  499. django_spire/notification/sms/urls/media_urls.py +2 -0
  500. django_spire/notification/sms/views/media_views.py +14 -4
  501. django_spire/notification/tests/__init__.py +0 -0
  502. django_spire/notification/tests/factories.py +26 -0
  503. django_spire/notification/tests/test_admin.py +55 -0
  504. django_spire/notification/tests/test_apps.py +30 -0
  505. django_spire/notification/tests/test_automation.py +18 -0
  506. django_spire/notification/tests/test_choices.py +59 -0
  507. django_spire/notification/tests/test_exceptions.py +58 -0
  508. django_spire/notification/tests/test_managers.py +100 -0
  509. django_spire/notification/tests/test_maps.py +31 -0
  510. django_spire/notification/tests/test_models.py +76 -0
  511. django_spire/notification/tests/test_querysets.py +184 -0
  512. django_spire/notification/tests/test_utils.py +23 -0
  513. django_spire/notification/urls.py +3 -1
  514. django_spire/notification/utils.py +3 -1
  515. django_spire/settings.py +3 -0
  516. django_spire/theme/tests/test_context_processor.py +15 -13
  517. django_spire/theme/tests/test_enums.py +2 -2
  518. django_spire/theme/tests/test_filesystem.py +2 -5
  519. django_spire/theme/tests/test_integration.py +12 -12
  520. django_spire/theme/tests/test_model.py +40 -38
  521. django_spire/theme/tests/test_views/test_json_views.py +33 -33
  522. django_spire/theme/urls/json_urls.py +3 -0
  523. django_spire/theme/urls/page_urls.py +3 -0
  524. django_spire/urls.py +19 -15
  525. django_spire/utils.py +13 -4
  526. {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/METADATA +1 -1
  527. {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/RECORD +530 -358
  528. django_spire/contrib/options/tests/test_unit.py +0 -148
  529. django_spire/contrib/seeding/tests/test_seeding.py +0 -25
  530. django_spire/core/tests/test_templatetags.py +0 -117
  531. django_spire/core/tests/tests_shortcuts.py +0 -73
  532. django_spire/history/activity/tests.py +0 -3
  533. django_spire/history/activity/views.py +0 -3
  534. django_spire/knowledge/collection/tests/test_services/test_transformation_service.py +0 -71
  535. django_spire/notification/app/tests.py +0 -3
  536. {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/WHEEL +0 -0
  537. {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/licenses/LICENSE.md +0 -0
  538. {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,377 @@
1
+ from __future__ import annotations
2
+
3
+ from django.urls import reverse
4
+
5
+ from django_spire.auth.group.models import AuthGroup
6
+ from django_spire.auth.user.tests.factories import create_user
7
+ from django_spire.core.tests.test_cases import BaseTestCase
8
+
9
+
10
+ class GroupPageViewsTestCase(BaseTestCase):
11
+ def setUp(self) -> None:
12
+ super().setUp()
13
+
14
+ self.group = AuthGroup.objects.create(name='Test Group')
15
+
16
+ def test_list_view_requires_permission(self) -> None:
17
+ user = create_user(username='normaluser')
18
+ self.client.force_login(user)
19
+ response = self.client.get(reverse('django_spire:auth:group:page:list'))
20
+ assert response.status_code == 403
21
+
22
+ def test_list_view_with_permission(self) -> None:
23
+ response = self.client.get(reverse('django_spire:auth:group:page:list'))
24
+ assert response.status_code == 200
25
+
26
+ def test_list_view_context_contains_groups(self) -> None:
27
+ response = self.client.get(reverse('django_spire:auth:group:page:list'))
28
+ assert 'group_list' in response.context
29
+
30
+ def test_list_view_contains_created_group(self) -> None:
31
+ response = self.client.get(reverse('django_spire:auth:group:page:list'))
32
+ group_names = [g.name for g in response.context['group_list']]
33
+ assert 'Test Group' in group_names
34
+
35
+ def test_detail_view_requires_permission(self) -> None:
36
+ user = create_user(username='normaluser')
37
+ self.client.force_login(user)
38
+ response = self.client.get(
39
+ reverse('django_spire:auth:group:page:detail', kwargs={'pk': self.group.pk})
40
+ )
41
+ assert response.status_code == 403
42
+
43
+ def test_detail_view_with_permission(self) -> None:
44
+ response = self.client.get(
45
+ reverse('django_spire:auth:group:page:detail', kwargs={'pk': self.group.pk})
46
+ )
47
+ assert response.status_code == 200
48
+
49
+ def test_detail_view_context_contains_group(self) -> None:
50
+ response = self.client.get(
51
+ reverse('django_spire:auth:group:page:detail', kwargs={'pk': self.group.pk})
52
+ )
53
+ assert 'group' in response.context
54
+ assert response.context['group'] == self.group
55
+
56
+ def test_detail_view_404_for_nonexistent_group(self) -> None:
57
+ response = self.client.get(
58
+ reverse('django_spire:auth:group:page:detail', kwargs={'pk': 99999})
59
+ )
60
+ assert response.status_code == 404
61
+
62
+ def test_detail_view_contains_permission_data(self) -> None:
63
+ response = self.client.get(
64
+ reverse('django_spire:auth:group:page:detail', kwargs={'pk': self.group.pk})
65
+ )
66
+ assert 'permission_data' in response.context
67
+
68
+ def test_detail_view_contains_user_lists(self) -> None:
69
+ response = self.client.get(
70
+ reverse('django_spire:auth:group:page:detail', kwargs={'pk': self.group.pk})
71
+ )
72
+ assert 'active_user_list' in response.context
73
+ assert 'inactive_user_list' in response.context
74
+
75
+ def test_list_view_contains_permission_data(self) -> None:
76
+ response = self.client.get(reverse('django_spire:auth:group:page:list'))
77
+ assert 'group_list_permission_data' in response.context
78
+
79
+ def test_list_view_multiple_groups(self) -> None:
80
+ AuthGroup.objects.create(name='Group 2')
81
+ AuthGroup.objects.create(name='Group 3')
82
+ response = self.client.get(reverse('django_spire:auth:group:page:list'))
83
+ assert len(response.context['group_list']) >= 3
84
+
85
+ def test_detail_view_with_users_in_group(self) -> None:
86
+ user = create_user(username='groupmember')
87
+ self.group.user_set.add(user)
88
+ response = self.client.get(
89
+ reverse('django_spire:auth:group:page:detail', kwargs={'pk': self.group.pk})
90
+ )
91
+ assert response.status_code == 200
92
+
93
+ def test_detail_view_separates_active_inactive_users(self) -> None:
94
+ active_user = create_user(username='activeuser', is_active=True)
95
+ inactive_user = create_user(username='inactiveuser', is_active=False)
96
+ self.group.user_set.add(active_user, inactive_user)
97
+ response = self.client.get(
98
+ reverse('django_spire:auth:group:page:detail', kwargs={'pk': self.group.pk})
99
+ )
100
+ active_ids = [u.pk for u in response.context['active_user_list']]
101
+ inactive_ids = [u.pk for u in response.context['inactive_user_list']]
102
+ assert active_user.pk in active_ids
103
+ assert inactive_user.pk in inactive_ids
104
+
105
+
106
+ class GroupFormViewsTestCase(BaseTestCase):
107
+ def setUp(self) -> None:
108
+ super().setUp()
109
+
110
+ self.group = AuthGroup.objects.create(name='Test Group')
111
+
112
+ def test_add_form_view_get(self) -> None:
113
+ response = self.client.get(reverse('django_spire:auth:group:form:add'))
114
+ assert response.status_code == 200
115
+
116
+ def test_add_form_view_post_creates_group(self) -> None:
117
+ response = self.client.post(
118
+ reverse('django_spire:auth:group:form:add'),
119
+ data={'name': 'New Group'}
120
+ )
121
+ assert response.status_code == 302
122
+ assert AuthGroup.objects.filter(name='New Group').exists()
123
+
124
+ def test_add_form_view_post_invalid_name(self) -> None:
125
+ response = self.client.post(
126
+ reverse('django_spire:auth:group:form:add'),
127
+ data={'name': 'All Users'}
128
+ )
129
+ assert response.status_code == 200
130
+ assert not AuthGroup.objects.filter(name='All Users').exists()
131
+
132
+ def test_add_form_view_post_empty_name(self) -> None:
133
+ response = self.client.post(
134
+ reverse('django_spire:auth:group:form:add'),
135
+ data={'name': ''}
136
+ )
137
+ assert response.status_code == 200
138
+
139
+ def test_update_form_view_get(self) -> None:
140
+ response = self.client.get(
141
+ reverse('django_spire:auth:group:form:update', kwargs={'pk': self.group.pk})
142
+ )
143
+ assert response.status_code == 200
144
+
145
+ def test_update_form_view_post_updates_group(self) -> None:
146
+ response = self.client.post(
147
+ reverse('django_spire:auth:group:form:update', kwargs={'pk': self.group.pk}),
148
+ data={'name': 'Updated Group'}
149
+ )
150
+ assert response.status_code == 302
151
+ self.group.refresh_from_db()
152
+ assert self.group.name == 'Updated Group'
153
+
154
+ def test_update_form_view_nonexistent_group_returns_200(self) -> None:
155
+ response = self.client.get(
156
+ reverse('django_spire:auth:group:form:update', kwargs={'pk': 99999})
157
+ )
158
+ assert response.status_code == 200
159
+
160
+ def test_delete_form_view_get(self) -> None:
161
+ response = self.client.get(
162
+ reverse('django_spire:auth:group:form:delete', kwargs={'pk': self.group.pk})
163
+ )
164
+ assert response.status_code == 200
165
+
166
+ def test_delete_form_view_404(self) -> None:
167
+ response = self.client.get(
168
+ reverse('django_spire:auth:group:form:delete', kwargs={'pk': 99999})
169
+ )
170
+ assert response.status_code == 404
171
+
172
+ def test_user_form_view_get(self) -> None:
173
+ response = self.client.get(
174
+ reverse('django_spire:auth:group:form:user', kwargs={'pk': self.group.pk})
175
+ )
176
+ assert response.status_code == 200
177
+
178
+ def test_user_form_view_404(self) -> None:
179
+ response = self.client.get(
180
+ reverse('django_spire:auth:group:form:user', kwargs={'pk': 99999})
181
+ )
182
+ assert response.status_code == 404
183
+
184
+ def test_add_form_requires_permission(self) -> None:
185
+ user = create_user(username='normaluser')
186
+ self.client.force_login(user)
187
+ response = self.client.get(reverse('django_spire:auth:group:form:add'))
188
+ assert response.status_code == 403
189
+
190
+ def test_update_form_requires_permission(self) -> None:
191
+ user = create_user(username='normaluser')
192
+ self.client.force_login(user)
193
+ response = self.client.get(
194
+ reverse('django_spire:auth:group:form:update', kwargs={'pk': self.group.pk})
195
+ )
196
+ assert response.status_code == 403
197
+
198
+ def test_delete_form_requires_permission(self) -> None:
199
+ user = create_user(username='normaluser')
200
+ self.client.force_login(user)
201
+ response = self.client.get(
202
+ reverse('django_spire:auth:group:form:delete', kwargs={'pk': self.group.pk})
203
+ )
204
+ assert response.status_code == 403
205
+
206
+ def test_user_form_requires_permission(self) -> None:
207
+ user = create_user(username='normaluser')
208
+ self.client.force_login(user)
209
+ response = self.client.get(
210
+ reverse('django_spire:auth:group:form:user', kwargs={'pk': self.group.pk})
211
+ )
212
+ assert response.status_code == 403
213
+
214
+ def test_add_form_view_post_duplicate_name(self) -> None:
215
+ response = self.client.post(
216
+ reverse('django_spire:auth:group:form:add'),
217
+ data={'name': 'Test Group'}
218
+ )
219
+ assert response.status_code == 200
220
+
221
+ def test_update_form_view_post_to_reserved_name(self) -> None:
222
+ response = self.client.post(
223
+ reverse('django_spire:auth:group:form:update', kwargs={'pk': self.group.pk}),
224
+ data={'name': 'All Users'}
225
+ )
226
+ assert response.status_code == 200
227
+ self.group.refresh_from_db()
228
+ assert self.group.name != 'All Users'
229
+
230
+ def test_add_form_view_post_unicode_name(self) -> None:
231
+ response = self.client.post(
232
+ reverse('django_spire:auth:group:form:add'),
233
+ data={'name': 'Tëst Grøup 日本語'}
234
+ )
235
+ assert response.status_code == 302
236
+ assert AuthGroup.objects.filter(name='Tëst Grøup 日本語').exists()
237
+
238
+ def test_add_form_view_post_special_characters(self) -> None:
239
+ response = self.client.post(
240
+ reverse('django_spire:auth:group:form:add'),
241
+ data={'name': 'Group & Co <Test>'}
242
+ )
243
+ assert response.status_code == 302
244
+
245
+ def test_user_form_view_post_adds_users(self) -> None:
246
+ user = create_user(username='newgroupuser')
247
+ response = self.client.post(
248
+ reverse('django_spire:auth:group:form:user', kwargs={'pk': self.group.pk}),
249
+ data={'users': [user.pk]}
250
+ )
251
+ assert response.status_code == 302
252
+ assert user in self.group.user_set.all()
253
+
254
+ def test_user_form_view_post_removes_users(self) -> None:
255
+ user = create_user(username='removeuser')
256
+ self.group.user_set.add(user)
257
+ response = self.client.post(
258
+ reverse('django_spire:auth:group:form:user', kwargs={'pk': self.group.pk}),
259
+ data={'users': []}
260
+ )
261
+ assert response.status_code == 302
262
+ assert user not in self.group.user_set.all()
263
+
264
+
265
+ class GroupJsonViewsTestCase(BaseTestCase):
266
+ def setUp(self) -> None:
267
+ super().setUp()
268
+
269
+ self.group = AuthGroup.objects.create(name='Test Group')
270
+
271
+ def test_permission_form_ajax_invalid_app(self) -> None:
272
+ response = self.client.post(
273
+ reverse(
274
+ 'django_spire:auth:group:json:group_permission_ajax',
275
+ kwargs={'pk': self.group.pk, 'app_name': 'invalid_app'}
276
+ ),
277
+ data={'perm_level': 'View'},
278
+ content_type='application/json'
279
+ )
280
+ assert response.status_code == 200
281
+ data = response.json()
282
+ assert data['status'] == 400
283
+
284
+ def test_permission_form_ajax_valid_app(self) -> None:
285
+ response = self.client.post(
286
+ reverse(
287
+ 'django_spire:auth:group:json:group_permission_ajax',
288
+ kwargs={'pk': self.group.pk, 'app_name': 'group'}
289
+ ),
290
+ data='{"perm_level": "View"}',
291
+ content_type='application/json'
292
+ )
293
+ assert response.status_code == 200
294
+
295
+ def test_permission_form_ajax_requires_post(self) -> None:
296
+ response = self.client.get(
297
+ reverse(
298
+ 'django_spire:auth:group:json:group_permission_ajax',
299
+ kwargs={'pk': self.group.pk, 'app_name': 'group'}
300
+ )
301
+ )
302
+ assert response.status_code == 405
303
+
304
+ def test_permission_form_ajax_404_group(self) -> None:
305
+ response = self.client.post(
306
+ reverse(
307
+ 'django_spire:auth:group:json:group_permission_ajax',
308
+ kwargs={'pk': 99999, 'app_name': 'group'}
309
+ ),
310
+ data='{"perm_level": "View"}',
311
+ content_type='application/json'
312
+ )
313
+ assert response.status_code == 404
314
+
315
+ def test_special_role_ajax_invalid_app(self) -> None:
316
+ response = self.client.post(
317
+ reverse(
318
+ 'django_spire:auth:group:json:group_special_role_ajax',
319
+ kwargs={'pk': self.group.pk, 'app_name': 'invalid_app'}
320
+ ),
321
+ data='{"codename": "test", "grant_special_role_access": true}',
322
+ content_type='application/json'
323
+ )
324
+ assert response.status_code == 200
325
+ data = response.json()
326
+ assert data['status'] == 400
327
+
328
+ def test_permission_form_ajax_requires_permission(self) -> None:
329
+ user = create_user(username='normaluser')
330
+ self.client.force_login(user)
331
+ response = self.client.post(
332
+ reverse(
333
+ 'django_spire:auth:group:json:group_permission_ajax',
334
+ kwargs={'pk': self.group.pk, 'app_name': 'group'}
335
+ ),
336
+ data='{"perm_level": "View"}',
337
+ content_type='application/json'
338
+ )
339
+ assert response.status_code == 403
340
+
341
+ def test_special_role_ajax_requires_permission(self) -> None:
342
+ user = create_user(username='normaluser')
343
+ self.client.force_login(user)
344
+ response = self.client.post(
345
+ reverse(
346
+ 'django_spire:auth:group:json:group_special_role_ajax',
347
+ kwargs={'pk': self.group.pk, 'app_name': 'group'}
348
+ ),
349
+ data='{"codename": "test", "grant_special_role_access": true}',
350
+ content_type='application/json'
351
+ )
352
+ assert response.status_code == 403
353
+
354
+ def test_permission_form_ajax_all_perm_levels(self) -> None:
355
+ for perm_level in ['None', 'View', 'Add', 'Change', 'Delete']:
356
+ response = self.client.post(
357
+ reverse(
358
+ 'django_spire:auth:group:json:group_permission_ajax',
359
+ kwargs={'pk': self.group.pk, 'app_name': 'group'}
360
+ ),
361
+ data=f'{{"perm_level": "{perm_level}"}}',
362
+ content_type='application/json'
363
+ )
364
+ assert response.status_code == 200
365
+
366
+ def test_permission_form_ajax_invalid_perm_level_defaults_to_none(self) -> None:
367
+ response = self.client.post(
368
+ reverse(
369
+ 'django_spire:auth:group:json:group_permission_ajax',
370
+ kwargs={'pk': self.group.pk, 'app_name': 'group'}
371
+ ),
372
+ data='{"perm_level": "InvalidLevel"}',
373
+ content_type='application/json'
374
+ )
375
+ assert response.status_code == 200
376
+ data = response.json()
377
+ assert data['status'] == 200
@@ -1,4 +1,6 @@
1
- from django.urls import path, include
1
+ from __future__ import annotations
2
+
3
+ from django.urls import include, path
2
4
 
3
5
 
4
6
  app_name = 'group'
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.urls import path
2
4
 
3
5
  from django_spire.auth.group.views import form_views
@@ -1,7 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.urls import path
2
4
 
3
5
  from django_spire.auth.group.views import json_views
4
6
 
7
+
5
8
  app_name = 'group'
6
9
 
7
10
  # Group
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.urls import path
2
4
 
3
5
  from django_spire.auth.group.views import page_views
@@ -2,7 +2,10 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING
4
4
 
5
- from django_spire.auth.permissions.consts import VALID_PERMISSION_LEVELS, PERMISSIONS_LEVEL_CHOICES
5
+ from django_spire.auth.permissions.consts import (
6
+ PERMISSIONS_LEVEL_CHOICES,
7
+ VALID_PERMISSION_LEVELS
8
+ )
6
9
 
7
10
  if TYPE_CHECKING:
8
11
  from django.contrib.auth.models import Group, User
@@ -31,7 +34,8 @@ def codename_list_to_perm_level(codenames_list: list[str]) -> int:
31
34
  def perm_level_to_int(perm_level: VALID_PERMISSION_LEVELS | str) -> int:
32
35
  if isinstance(perm_level, int) and 4 >= perm_level >= 0:
33
36
  return perm_level
34
- elif isinstance(perm_level, str):
37
+
38
+ if isinstance(perm_level, str):
35
39
  for perm in PERMISSIONS_LEVEL_CHOICES:
36
40
  if perm_level.lower() == perm[1].lower():
37
41
  return perm[0]
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from typing import TYPE_CHECKING
4
+
3
5
  from django.http import HttpResponseRedirect
4
6
  from django.shortcuts import get_object_or_404
5
7
  from django.urls import reverse
@@ -15,12 +17,13 @@ from django_spire.contrib.form.utils import show_form_errors
15
17
  from django_spire.contrib.generic_views import portal_views
16
18
  from django_spire.core.shortcuts import get_object_or_null_obj
17
19
  from django_spire.history.activity.utils import add_form_activity
18
- from typing import TYPE_CHECKING
19
20
 
20
21
  if TYPE_CHECKING:
21
22
  from django.core.handlers.wsgi import WSGIRequest
22
23
  from django.template.response import TemplateResponse
23
24
 
25
+ from django_spire.contrib.breadcrumb.models import Breadcrumbs
26
+
24
27
 
25
28
  @permission_required('django_spire_auth_group.change_authgroup')
26
29
  def form_view(
@@ -94,7 +97,7 @@ def user_form_view(
94
97
 
95
98
  form = forms.GroupUserForm()
96
99
 
97
- def crumbs(breadcrumbs) -> None:
100
+ def crumbs(breadcrumbs: Breadcrumbs) -> None:
98
101
  breadcrumbs.add_breadcrumb(name='Edit Users')
99
102
 
100
103
  context_data = {
@@ -160,7 +163,7 @@ def group_remove_user_form_view(
160
163
 
161
164
  form = DeleteConfirmationForm(request.GET, obj=user)
162
165
 
163
- def get_breadcrumbs(breadcrumbs) -> None:
166
+ def get_breadcrumbs(breadcrumbs: Breadcrumbs) -> None:
164
167
  breadcrumbs = group.breadcrumbs()
165
168
  breadcrumbs.add_breadcrumb(name=user.get_full_name())
166
169
  breadcrumbs.add_breadcrumb(name='Remove')
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
- from django.core.handlers.wsgi import WSGIRequest
3
+ from typing import TYPE_CHECKING
4
+
4
5
  from django.http import JsonResponse
5
6
  from django.shortcuts import get_object_or_404
6
7
  from django.views.decorators.http import require_POST
@@ -9,9 +10,12 @@ from django_spire.auth.group import models
9
10
  from django_spire.auth.group.utils import perm_level_to_int, perm_level_to_django_permission
10
11
  from django_spire.auth.permissions.decorators import permission_required
11
12
  from django_spire.auth.permissions.permissions import GroupPermissions
12
- from django_spire.auth.permissions.tools import generate_model_permissions, generate_model_key_permission_map
13
+ from django_spire.auth.permissions.tools import generate_model_key_permission_map
13
14
  from django_spire.core.shortcuts import process_request_body
14
15
 
16
+ if TYPE_CHECKING:
17
+ from django.core.handlers.wsgi import WSGIRequest
18
+
15
19
 
16
20
  @require_POST
17
21
  @permission_required('django_spire_auth_group.change_authgroup')
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.contrib import admin
2
4
  from django.urls import reverse
3
5
  from django.utils.html import format_html
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.apps import AppConfig
2
4
 
3
5
  from django_spire.utils import check_required_apps
@@ -4,6 +4,7 @@ from django import forms
4
4
 
5
5
  class MFAForm(forms.Form):
6
6
  pass
7
+
7
8
  # def __init__(self, generated_mfa_code, *args, **kwargs):
8
9
  # super().__init__(*args, **kwargs)
9
10
  #
@@ -1,9 +1,16 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from typing import TYPE_CHECKING
4
+
3
5
  from django.db.models import QuerySet
4
6
  from django.utils.timezone import localtime
5
7
 
8
+ if TYPE_CHECKING:
9
+ from django.contrib.auth.models import User
10
+
11
+ from django_spire.auth.mfa.models import MfaCode
12
+
6
13
 
7
14
  class MfaCodeQuerySet(QuerySet):
8
- def valid_code(self, user):
9
- return self.filter(user=user, expiration_datetime__gte=localtime()).first()
15
+ def valid_code(self, user: User) -> MfaCode | None:
16
+ return self.filter(user=user, expiration_datetime__gt=localtime()).first()