django-spire 0.23.6__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 (541) 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/progress/__init__.py +1 -3
  139. django_spire/contrib/progress/static/django_spire/js/contrib/progress/progress.js +38 -82
  140. django_spire/contrib/queryset/enums.py +3 -1
  141. django_spire/contrib/queryset/filter_tools.py +10 -5
  142. django_spire/contrib/queryset/mixins.py +16 -16
  143. django_spire/contrib/queryset/tests/__init__.py +0 -0
  144. django_spire/contrib/queryset/tests/test_queryset.py +137 -0
  145. django_spire/contrib/seeding/field/base.py +13 -7
  146. django_spire/contrib/seeding/field/callable.py +8 -1
  147. django_spire/contrib/seeding/field/cleaners.py +5 -5
  148. django_spire/contrib/seeding/field/custom.py +20 -10
  149. django_spire/contrib/seeding/field/django/seeder.py +8 -6
  150. django_spire/contrib/seeding/field/enums.py +7 -5
  151. django_spire/contrib/seeding/field/override.py +16 -6
  152. django_spire/contrib/seeding/field/static.py +9 -2
  153. django_spire/contrib/seeding/field/tests/test_base.py +18 -14
  154. django_spire/contrib/seeding/field/tests/test_callable.py +13 -9
  155. django_spire/contrib/seeding/field/tests/test_cleaners.py +51 -38
  156. django_spire/contrib/seeding/field/tests/test_static.py +13 -9
  157. django_spire/contrib/seeding/intelligence/bots/seeder_generator_bot.py +2 -0
  158. django_spire/contrib/seeding/intelligence/intel.py +5 -1
  159. django_spire/contrib/seeding/intelligence/prompts/factory.py +6 -1
  160. django_spire/contrib/seeding/intelligence/prompts/foreign_key_selection_prompt.py +6 -1
  161. django_spire/contrib/seeding/intelligence/prompts/generate_django_model_seeder_prompts.py +2 -0
  162. django_spire/contrib/seeding/intelligence/prompts/generic_relationship_selection_prompt.py +7 -1
  163. django_spire/contrib/seeding/intelligence/prompts/hierarchical_selection_prompt.py +6 -2
  164. django_spire/contrib/seeding/intelligence/prompts/model_field_choices_prompt.py +8 -2
  165. django_spire/contrib/seeding/intelligence/prompts/negation_prompt.py +2 -0
  166. django_spire/contrib/seeding/intelligence/prompts/objective_prompt.py +6 -1
  167. django_spire/contrib/seeding/management/commands/seeding.py +9 -3
  168. django_spire/contrib/seeding/management/example.py +2 -0
  169. django_spire/contrib/seeding/model/base.py +16 -7
  170. django_spire/contrib/seeding/model/config.py +31 -15
  171. django_spire/contrib/seeding/model/django/config.py +13 -13
  172. django_spire/contrib/seeding/model/django/seeder.py +4 -4
  173. django_spire/contrib/seeding/model/django/tests/test_seeder.py +34 -23
  174. django_spire/contrib/seeding/model/enums.py +2 -0
  175. django_spire/contrib/seeding/tests/test_config.py +71 -0
  176. django_spire/contrib/seeding/tests/test_custom.py +35 -0
  177. django_spire/contrib/seeding/tests/test_enums.py +40 -0
  178. django_spire/contrib/seeding/tests/test_intel.py +32 -0
  179. django_spire/contrib/seeding/tests/test_override.py +63 -0
  180. django_spire/contrib/service/__init__.py +2 -2
  181. django_spire/contrib/service/django_model_service.py +16 -15
  182. django_spire/contrib/service/exceptions.py +5 -3
  183. django_spire/contrib/service/tests/__init__.py +0 -0
  184. django_spire/contrib/service/tests/test_service.py +153 -0
  185. django_spire/contrib/session/apps.py +2 -0
  186. django_spire/contrib/session/controller.py +48 -42
  187. django_spire/contrib/session/templatetags/session_tags.py +11 -2
  188. django_spire/contrib/session/tests/test_session_controller.py +117 -53
  189. django_spire/contrib/tests/__init__.py +0 -0
  190. django_spire/contrib/tests/test_utils.py +37 -0
  191. django_spire/contrib/utils.py +4 -1
  192. django_spire/core/apps.py +2 -0
  193. django_spire/core/converters/tests/test_to_data.py +353 -0
  194. django_spire/core/converters/tests/test_to_enums.py +61 -41
  195. django_spire/core/converters/tests/test_to_pydantic.py +138 -109
  196. django_spire/core/converters/to_data.py +29 -10
  197. django_spire/core/converters/to_enums.py +4 -2
  198. django_spire/core/converters/to_pydantic.py +22 -22
  199. django_spire/core/decorators.py +19 -6
  200. django_spire/core/forms/widgets.py +4 -0
  201. django_spire/core/maps.py +3 -1
  202. django_spire/core/middleware/maintenance.py +3 -3
  203. django_spire/core/middleware.py +8 -6
  204. django_spire/core/redirect/__init__.py +5 -0
  205. django_spire/core/redirect/generic_redirect.py +1 -2
  206. django_spire/core/redirect/tests/__init__.py +0 -0
  207. django_spire/core/redirect/tests/test_generic_redirect.py +34 -0
  208. django_spire/core/{tests/tests_redirect.py → redirect/tests/test_safe_redirect.py} +55 -81
  209. django_spire/core/shortcuts.py +3 -3
  210. django_spire/core/static/django_spire/css/app-layout.css +1 -1
  211. django_spire/core/static/django_spire/css/app-navigation.css +3 -3
  212. django_spire/core/static/django_spire/css/bootstrap-override.css +4 -0
  213. django_spire/core/tag/admin.py +12 -0
  214. django_spire/core/tag/intelligence/tag_set_bot.py +2 -0
  215. django_spire/core/tag/mixins.py +2 -0
  216. django_spire/core/tag/models.py +2 -0
  217. django_spire/core/tag/querysets.py +2 -0
  218. django_spire/core/tag/service/tag_service.py +6 -3
  219. django_spire/core/tag/tests/test_intelligence.py +9 -9
  220. django_spire/core/tag/tests/test_tags.py +44 -54
  221. django_spire/core/tag/tests/test_tools.py +191 -0
  222. django_spire/core/tag/tools.py +3 -0
  223. django_spire/core/templates/django_spire/card/card.html +5 -2
  224. django_spire/core/templates/django_spire/card/title_card.html +9 -4
  225. django_spire/core/templates/django_spire/infinite_scroll/base.html +1 -0
  226. django_spire/core/templates/django_spire/navigation/side_navigation.html +19 -24
  227. django_spire/core/templates/django_spire/page/full_page.html +46 -16
  228. django_spire/core/templates/django_spire/table/base.html +4 -2
  229. django_spire/core/templatetags/json.py +6 -2
  230. django_spire/core/templatetags/message.py +13 -8
  231. django_spire/core/templatetags/string_formating.py +8 -5
  232. django_spire/core/templatetags/tests/__init__.py +0 -0
  233. django_spire/core/templatetags/tests/test_templatetags.py +427 -0
  234. django_spire/core/templatetags/variable_types.py +17 -9
  235. django_spire/core/tests/test_cases.py +1 -1
  236. django_spire/core/tests/test_conf.py +43 -0
  237. django_spire/core/tests/test_consts.py +28 -0
  238. django_spire/core/tests/test_context_processors.py +93 -0
  239. django_spire/core/tests/test_decorators.py +95 -0
  240. django_spire/core/tests/test_django_spire_utils.py +56 -0
  241. django_spire/core/tests/test_exceptions.py +37 -0
  242. django_spire/core/tests/test_models.py +54 -0
  243. django_spire/core/tests/test_settings.py +45 -0
  244. django_spire/core/tests/test_shortcuts.py +74 -0
  245. django_spire/core/tests/test_urls.py +16 -0
  246. django_spire/core/tests/test_utils.py +58 -0
  247. django_spire/core/urls.py +4 -1
  248. django_spire/core/utils.py +12 -8
  249. django_spire/exceptions.py +16 -1
  250. django_spire/file/admin.py +4 -2
  251. django_spire/file/apps.py +8 -10
  252. django_spire/file/fields.py +7 -7
  253. django_spire/file/forms.py +1 -1
  254. django_spire/file/interfaces.py +15 -15
  255. django_spire/file/mixins.py +1 -4
  256. django_spire/file/models.py +3 -5
  257. django_spire/file/tests/factories.py +59 -0
  258. django_spire/file/tests/test_admin.py +69 -0
  259. django_spire/file/tests/test_apps.py +24 -0
  260. django_spire/file/tests/test_fields.py +114 -0
  261. django_spire/file/tests/test_forms.py +20 -0
  262. django_spire/file/tests/test_interfaces.py +183 -0
  263. django_spire/file/tests/test_models.py +82 -0
  264. django_spire/file/tests/test_querysets.py +102 -0
  265. django_spire/file/tests/test_utils.py +32 -0
  266. django_spire/file/tests/test_views.py +145 -0
  267. django_spire/file/tests/test_widgets.py +82 -0
  268. django_spire/file/tools.py +8 -2
  269. django_spire/file/views.py +7 -3
  270. django_spire/file/widgets.py +12 -12
  271. django_spire/help_desk/admin.py +15 -0
  272. django_spire/help_desk/apps.py +2 -0
  273. django_spire/help_desk/auth/controller.py +2 -0
  274. django_spire/help_desk/choices.py +2 -0
  275. django_spire/help_desk/enums.py +2 -0
  276. django_spire/help_desk/exceptions.py +31 -3
  277. django_spire/help_desk/forms.py +2 -0
  278. django_spire/help_desk/models.py +2 -0
  279. django_spire/help_desk/querysets.py +4 -1
  280. django_spire/help_desk/services/notification_service.py +26 -27
  281. django_spire/help_desk/services/service.py +2 -3
  282. django_spire/help_desk/tests/factories.py +8 -3
  283. django_spire/help_desk/tests/test_admin.py +41 -0
  284. django_spire/help_desk/tests/test_apps.py +41 -0
  285. django_spire/help_desk/tests/test_choices.py +50 -0
  286. django_spire/help_desk/tests/test_controller.py +87 -0
  287. django_spire/help_desk/tests/test_enums.py +18 -0
  288. django_spire/help_desk/tests/test_exceptions.py +37 -0
  289. django_spire/help_desk/tests/test_forms.py +89 -0
  290. django_spire/help_desk/tests/test_models.py +59 -0
  291. django_spire/help_desk/tests/test_querysets.py +38 -0
  292. django_spire/help_desk/tests/test_services/test_notification_service.py +15 -8
  293. django_spire/help_desk/tests/test_services/test_service.py +92 -0
  294. django_spire/help_desk/tests/test_urls/test_form_urls.py +6 -6
  295. django_spire/help_desk/tests/test_urls/test_page_urls.py +8 -9
  296. django_spire/help_desk/tests/test_views/test_form_views.py +46 -19
  297. django_spire/help_desk/tests/test_views/test_page_views.py +32 -9
  298. django_spire/help_desk/urls/__init__.py +4 -1
  299. django_spire/help_desk/urls/form_urls.py +3 -0
  300. django_spire/help_desk/urls/page_urls.py +3 -0
  301. django_spire/help_desk/views/form_views.py +13 -5
  302. django_spire/help_desk/views/page_views.py +11 -3
  303. django_spire/history/activity/admin.py +2 -0
  304. django_spire/history/activity/apps.py +3 -1
  305. django_spire/history/activity/mixins.py +13 -7
  306. django_spire/history/activity/models.py +6 -5
  307. django_spire/history/activity/querysets.py +2 -0
  308. django_spire/history/activity/tests/__init__.py +0 -0
  309. django_spire/history/activity/tests/test_activity.py +176 -0
  310. django_spire/history/admin.py +9 -2
  311. django_spire/history/choices.py +3 -0
  312. django_spire/history/models.py +5 -5
  313. django_spire/history/tests/test_admin.py +93 -0
  314. django_spire/history/tests/test_history.py +101 -0
  315. django_spire/history/tests/test_mixins.py +84 -0
  316. django_spire/history/viewed/admin.py +3 -1
  317. django_spire/history/viewed/apps.py +3 -1
  318. django_spire/history/viewed/models.py +2 -0
  319. django_spire/history/viewed/tests/__init__.py +0 -0
  320. django_spire/history/viewed/tests/test_viewed.py +46 -0
  321. django_spire/knowledge/auth/tests/__init__.py +0 -0
  322. django_spire/knowledge/auth/tests/test_controller.py +116 -0
  323. django_spire/knowledge/collection/admin.py +5 -1
  324. django_spire/knowledge/collection/models.py +3 -1
  325. django_spire/knowledge/collection/seeding/seed.py +1 -0
  326. django_spire/knowledge/collection/services/factory_service.py +10 -11
  327. django_spire/knowledge/collection/services/ordering_service.py +1 -2
  328. django_spire/knowledge/collection/services/service.py +5 -10
  329. django_spire/knowledge/collection/services/tag_service.py +5 -2
  330. django_spire/knowledge/collection/tests/factories.py +28 -1
  331. django_spire/knowledge/collection/tests/test_models.py +48 -0
  332. django_spire/knowledge/collection/tests/test_querysets.py +93 -0
  333. django_spire/knowledge/collection/tests/test_services/test_factory_service.py +100 -0
  334. django_spire/knowledge/collection/tests/test_services/test_services.py +160 -0
  335. django_spire/knowledge/collection/tests/test_urls/test_form_urls.py +21 -3
  336. django_spire/knowledge/collection/tests/test_urls/test_json_urls.py +39 -1
  337. django_spire/knowledge/collection/tests/test_urls/test_page_urls.py +12 -4
  338. django_spire/knowledge/collection/urls/__init__.py +3 -0
  339. django_spire/knowledge/collection/urls/form_urls.py +2 -0
  340. django_spire/knowledge/collection/urls/json_urls.py +2 -0
  341. django_spire/knowledge/collection/urls/page_urls.py +2 -0
  342. django_spire/knowledge/collection/views/form_views.py +4 -4
  343. django_spire/knowledge/collection/views/json_views.py +5 -1
  344. django_spire/knowledge/collection/views/page_views.py +5 -2
  345. django_spire/knowledge/entry/admin.py +7 -1
  346. django_spire/knowledge/entry/forms.py +2 -0
  347. django_spire/knowledge/entry/models.py +2 -0
  348. django_spire/knowledge/entry/seeding/seed.py +3 -0
  349. django_spire/knowledge/entry/services/automation_service.py +5 -4
  350. django_spire/knowledge/entry/services/factory_service.py +7 -5
  351. django_spire/knowledge/entry/services/service.py +4 -7
  352. django_spire/knowledge/entry/services/tag_service.py +0 -1
  353. django_spire/knowledge/entry/services/tool_service.py +1 -0
  354. django_spire/knowledge/entry/services/transformation_services.py +1 -5
  355. django_spire/knowledge/entry/tests/factories.py +1 -2
  356. django_spire/knowledge/entry/tests/test_factory_service.py +20 -0
  357. django_spire/knowledge/entry/tests/test_models.py +41 -0
  358. django_spire/knowledge/entry/tests/test_querysets.py +71 -0
  359. django_spire/knowledge/entry/tests/test_services.py +94 -0
  360. django_spire/knowledge/entry/tests/test_urls/test_form_urls.py +9 -14
  361. django_spire/knowledge/entry/tests/test_urls/test_json_urls.py +48 -5
  362. django_spire/knowledge/entry/tests/test_urls/test_page_urls.py +6 -8
  363. django_spire/knowledge/entry/tests/test_urls/test_template_urls.py +40 -0
  364. django_spire/knowledge/entry/urls/form_urls.py +2 -0
  365. django_spire/knowledge/entry/urls/json_urls.py +2 -0
  366. django_spire/knowledge/entry/urls/page_urls.py +2 -0
  367. django_spire/knowledge/entry/urls/template_urls.py +2 -0
  368. django_spire/knowledge/entry/version/block/choices.py +2 -0
  369. django_spire/knowledge/entry/version/block/data/data.py +1 -0
  370. django_spire/knowledge/entry/version/block/data/list/data.py +8 -13
  371. django_spire/knowledge/entry/version/block/data/list/maps.py +3 -0
  372. django_spire/knowledge/entry/version/block/data/list/meta.py +1 -2
  373. django_spire/knowledge/entry/version/block/data/list/tests/__init__.py +0 -0
  374. django_spire/knowledge/entry/version/block/data/list/tests/test_maps.py +32 -0
  375. django_spire/knowledge/entry/version/block/data/list/tests/test_meta.py +58 -0
  376. django_spire/knowledge/entry/version/block/data/maps.py +3 -6
  377. django_spire/knowledge/entry/version/block/models.py +7 -5
  378. django_spire/knowledge/entry/version/block/seeding/constants.py +5 -4
  379. django_spire/knowledge/entry/version/block/services/service.py +2 -3
  380. django_spire/knowledge/entry/version/block/tests/factories.py +4 -10
  381. django_spire/knowledge/entry/version/block/tests/test_choices.py +56 -0
  382. django_spire/knowledge/entry/version/block/tests/test_data.py +90 -0
  383. django_spire/knowledge/entry/version/block/tests/test_maps.py +37 -0
  384. django_spire/knowledge/entry/version/block/tests/test_models.py +55 -0
  385. django_spire/knowledge/entry/version/block/tests/test_querysets.py +35 -0
  386. django_spire/knowledge/entry/version/block/tests/test_services.py +65 -0
  387. django_spire/knowledge/entry/version/choices.py +2 -0
  388. django_spire/knowledge/entry/version/converters/converter.py +1 -1
  389. django_spire/knowledge/entry/version/converters/docx_converter.py +4 -7
  390. django_spire/knowledge/entry/version/converters/markdown_converter.py +20 -20
  391. django_spire/knowledge/entry/version/maps.py +4 -5
  392. django_spire/knowledge/entry/version/querysets.py +1 -1
  393. django_spire/knowledge/entry/version/seeding/seeder.py +1 -2
  394. django_spire/knowledge/entry/version/services/processor_service.py +5 -4
  395. django_spire/knowledge/entry/version/services/service.py +1 -2
  396. django_spire/knowledge/entry/version/tests/factories.py +2 -2
  397. django_spire/knowledge/entry/version/tests/test_choices.py +18 -0
  398. django_spire/knowledge/entry/version/tests/test_converters/test_docx_converter.py +56 -8
  399. django_spire/knowledge/entry/version/tests/test_converters/test_markdown_converter.py +78 -0
  400. django_spire/knowledge/entry/version/tests/test_maps.py +58 -0
  401. django_spire/knowledge/entry/version/tests/test_models.py +23 -0
  402. django_spire/knowledge/entry/version/tests/test_querysets.py +26 -0
  403. django_spire/knowledge/entry/version/tests/test_services.py +62 -0
  404. django_spire/knowledge/entry/version/tests/test_urls/test_json_urls.py +27 -8
  405. django_spire/knowledge/entry/version/tests/test_urls/test_page_urls.py +15 -8
  406. django_spire/knowledge/entry/version/tests/test_urls/test_redirect_urls.py +38 -0
  407. django_spire/knowledge/entry/version/urls/__init__.py +3 -0
  408. django_spire/knowledge/entry/version/urls/json_urls.py +2 -1
  409. django_spire/knowledge/entry/version/urls/page_urls.py +2 -0
  410. django_spire/knowledge/entry/version/urls/redirect_urls.py +2 -0
  411. django_spire/knowledge/entry/version/views/json_views.py +5 -1
  412. django_spire/knowledge/entry/version/views/page_views.py +10 -3
  413. django_spire/knowledge/entry/version/views/redirect_views.py +5 -1
  414. django_spire/knowledge/entry/views/form_views.py +16 -8
  415. django_spire/knowledge/entry/views/json_views.py +3 -1
  416. django_spire/knowledge/entry/views/page_views.py +8 -2
  417. django_spire/knowledge/entry/views/template_views.py +7 -1
  418. django_spire/knowledge/exceptions.py +2 -1
  419. django_spire/knowledge/intelligence/intel/answer_intel.py +2 -1
  420. django_spire/knowledge/intelligence/intel/entry_intel.py +0 -1
  421. django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +4 -5
  422. django_spire/knowledge/models.py +1 -2
  423. django_spire/knowledge/tests/__init__.py +0 -0
  424. django_spire/knowledge/tests/test_templatetags.py +40 -0
  425. django_spire/knowledge/tests/test_urls/__init__.py +0 -0
  426. django_spire/knowledge/tests/test_urls/test_page_urls.py +24 -0
  427. django_spire/knowledge/urls/__init__.py +2 -0
  428. django_spire/knowledge/urls/page_urls.py +2 -0
  429. django_spire/knowledge/views/page_views.py +8 -3
  430. django_spire/notification/admin.py +3 -1
  431. django_spire/notification/app/admin.py +2 -0
  432. django_spire/notification/app/apps.py +3 -1
  433. django_spire/notification/app/exceptions.py +9 -2
  434. django_spire/notification/app/models.py +8 -4
  435. django_spire/notification/app/processor.py +22 -26
  436. django_spire/notification/app/querysets.py +2 -0
  437. django_spire/notification/app/tests/__init__.py +0 -0
  438. django_spire/notification/app/tests/factories.py +34 -0
  439. django_spire/notification/app/tests/test_apps.py +24 -0
  440. django_spire/notification/app/tests/test_models.py +72 -0
  441. django_spire/notification/app/tests/test_processor.py +111 -0
  442. django_spire/notification/app/tests/test_querysets.py +90 -0
  443. django_spire/notification/app/tests/test_views/__init__.py +0 -0
  444. django_spire/notification/app/tests/test_views/test_json_views.py +48 -0
  445. django_spire/notification/app/tests/test_views/test_page_views.py +19 -0
  446. django_spire/notification/app/urls/__init__.py +3 -1
  447. django_spire/notification/app/urls/json_urls.py +6 -4
  448. django_spire/notification/app/urls/page_urls.py +4 -3
  449. django_spire/notification/app/urls/template_urls.py +4 -2
  450. django_spire/notification/apps.py +4 -1
  451. django_spire/notification/email/admin.py +5 -1
  452. django_spire/notification/email/apps.py +3 -1
  453. django_spire/notification/email/exceptions.py +4 -2
  454. django_spire/notification/email/helper.py +5 -3
  455. django_spire/notification/email/models.py +4 -0
  456. django_spire/notification/email/processor.py +19 -15
  457. django_spire/notification/email/querysets.py +3 -0
  458. django_spire/notification/email/tests/__init__.py +0 -0
  459. django_spire/notification/email/tests/factories.py +35 -0
  460. django_spire/notification/email/tests/test_apps.py +24 -0
  461. django_spire/notification/email/tests/test_models.py +52 -0
  462. django_spire/notification/email/tests/test_processor.py +92 -0
  463. django_spire/notification/email/tests/test_querysets.py +43 -0
  464. django_spire/notification/exceptions.py +17 -2
  465. django_spire/notification/managers.py +7 -1
  466. django_spire/notification/maps.py +4 -1
  467. django_spire/notification/mixins.py +2 -0
  468. django_spire/notification/models.py +3 -1
  469. django_spire/notification/processors/notification.py +12 -5
  470. django_spire/notification/processors/processor.py +2 -0
  471. django_spire/notification/processors/tests/__init__.py +0 -0
  472. django_spire/notification/processors/tests/test_notification.py +106 -0
  473. django_spire/notification/push/admin.py +10 -1
  474. django_spire/notification/push/apps.py +3 -1
  475. django_spire/notification/push/models.py +2 -3
  476. django_spire/notification/push/tests/__init__.py +0 -0
  477. django_spire/notification/push/tests/test_apps.py +24 -0
  478. django_spire/notification/push/tests/test_models.py +28 -0
  479. django_spire/notification/querysets.py +7 -1
  480. django_spire/notification/sms/admin.py +2 -0
  481. django_spire/notification/sms/apps.py +4 -1
  482. django_spire/notification/sms/automations.py +2 -0
  483. django_spire/notification/sms/choices.py +2 -0
  484. django_spire/notification/sms/exceptions.py +19 -5
  485. django_spire/notification/sms/helper.py +33 -23
  486. django_spire/notification/sms/models.py +5 -1
  487. django_spire/notification/sms/processor.py +20 -20
  488. django_spire/notification/sms/querysets.py +2 -0
  489. django_spire/notification/sms/tests/factories.py +33 -0
  490. django_spire/notification/sms/tests/test_apps.py +24 -0
  491. django_spire/notification/sms/tests/test_automation.py +38 -0
  492. django_spire/notification/sms/tests/test_choices.py +15 -0
  493. django_spire/notification/sms/tests/test_consts.py +17 -0
  494. django_spire/notification/sms/tests/test_exceptions.py +27 -0
  495. django_spire/notification/sms/tests/test_helper.py +50 -0
  496. django_spire/notification/sms/tests/test_models.py +81 -0
  497. django_spire/notification/sms/tests/test_processor.py +107 -0
  498. django_spire/notification/sms/tests/test_tools.py +25 -11
  499. django_spire/notification/sms/tools.py +16 -5
  500. django_spire/notification/sms/urls/__init__.py +3 -1
  501. django_spire/notification/sms/urls/media_urls.py +2 -0
  502. django_spire/notification/sms/views/media_views.py +14 -4
  503. django_spire/notification/tests/__init__.py +0 -0
  504. django_spire/notification/tests/factories.py +26 -0
  505. django_spire/notification/tests/test_admin.py +55 -0
  506. django_spire/notification/tests/test_apps.py +30 -0
  507. django_spire/notification/tests/test_automation.py +18 -0
  508. django_spire/notification/tests/test_choices.py +59 -0
  509. django_spire/notification/tests/test_exceptions.py +58 -0
  510. django_spire/notification/tests/test_managers.py +100 -0
  511. django_spire/notification/tests/test_maps.py +31 -0
  512. django_spire/notification/tests/test_models.py +76 -0
  513. django_spire/notification/tests/test_querysets.py +184 -0
  514. django_spire/notification/tests/test_utils.py +23 -0
  515. django_spire/notification/urls.py +3 -1
  516. django_spire/notification/utils.py +3 -1
  517. django_spire/settings.py +3 -0
  518. django_spire/theme/tests/test_context_processor.py +15 -13
  519. django_spire/theme/tests/test_enums.py +2 -2
  520. django_spire/theme/tests/test_filesystem.py +2 -5
  521. django_spire/theme/tests/test_integration.py +12 -12
  522. django_spire/theme/tests/test_model.py +40 -38
  523. django_spire/theme/tests/test_views/test_json_views.py +33 -33
  524. django_spire/theme/urls/json_urls.py +3 -0
  525. django_spire/theme/urls/page_urls.py +3 -0
  526. django_spire/urls.py +19 -15
  527. django_spire/utils.py +13 -4
  528. {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/METADATA +1 -1
  529. {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/RECORD +532 -361
  530. django_spire/contrib/options/tests/test_unit.py +0 -148
  531. django_spire/contrib/progress/views.py +0 -64
  532. django_spire/contrib/seeding/tests/test_seeding.py +0 -25
  533. django_spire/core/tests/test_templatetags.py +0 -117
  534. django_spire/core/tests/tests_shortcuts.py +0 -73
  535. django_spire/history/activity/tests.py +0 -3
  536. django_spire/history/activity/views.py +0 -3
  537. django_spire/knowledge/collection/tests/test_services/test_transformation_service.py +0 -71
  538. django_spire/notification/app/tests.py +0 -3
  539. {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/WHEEL +0 -0
  540. {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/licenses/LICENSE.md +0 -0
  541. {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/top_level.txt +0 -0
@@ -1,27 +1,32 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from typing import TYPE_CHECKING
4
+
3
5
  from django.conf import settings
4
6
  from django.contrib.auth import logout
5
7
  from django.contrib.auth.decorators import login_required
6
8
  from django.http import HttpResponseRedirect
7
9
  from django.urls import reverse
8
10
 
11
+ if TYPE_CHECKING:
12
+ from django.core.handlers.wsgi import WSGIRequest
13
+
9
14
 
10
15
  @login_required()
11
- def login_redirect_view(request, **kwargs):
16
+ def login_redirect_view(request: WSGIRequest, **kwargs):
12
17
  if not request.user.is_authenticated:
13
18
  return HttpResponseRedirect(reverse('django_spire:auth:admin:login'))
14
- else:
15
- # MFA implementation if required
16
- # profile = request.user.profile
17
- #
18
- # if profile.requires_mfa():
19
- # return HttpResponseRedirect(reverse('django_spire:auth:mfa:redirect:notification'))
20
19
 
21
- return HttpResponseRedirect(reverse(settings.LOGIN_REDIRECT_SUCCESS_URL))
20
+ # MFA implementation if required
21
+ # profile = request.user.profile
22
+ #
23
+ # if profile.requires_mfa():
24
+ # return HttpResponseRedirect(reverse('django_spire:auth:mfa:redirect:notification'))
25
+
26
+ return HttpResponseRedirect(reverse(settings.LOGIN_REDIRECT_SUCCESS_URL))
22
27
 
23
28
 
24
29
  @login_required()
25
- def logout_redirect_view(request, **kwargs):
30
+ def logout_redirect_view(request: WSGIRequest, **kwargs):
26
31
  logout(request)
27
32
  return HttpResponseRedirect(reverse('django_spire:auth:admin:login'))
@@ -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
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from django import template
4
4
 
5
+
5
6
  register = template.Library()
6
7
 
7
8
 
@@ -0,0 +1,27 @@
1
+ from __future__ import annotations
2
+
3
+ from django.contrib.contenttypes.models import ContentType
4
+
5
+ from django_spire.comment.forms import CommentForm
6
+ from django_spire.core.tests.test_cases import BaseTestCase
7
+
8
+
9
+ class TestCommentForm(BaseTestCase):
10
+ def setUp(self) -> None:
11
+ super().setUp()
12
+
13
+ self.content_type = ContentType.objects.get_for_model(self.super_user)
14
+
15
+ def test_valid_form(self) -> None:
16
+ form = CommentForm(data={'information': 'Test comment'})
17
+ assert form.is_valid() is True
18
+
19
+ def test_form_save(self) -> None:
20
+ form = CommentForm(data={'information': 'New comment'})
21
+ assert form.is_valid() is True
22
+ comment = form.save(commit=False)
23
+ comment.content_type = self.content_type
24
+ comment.object_id = self.super_user.pk
25
+ comment.user = self.super_user
26
+ comment.save()
27
+ assert comment.information == 'New comment'
@@ -0,0 +1,215 @@
1
+ from __future__ import annotations
2
+
3
+ from django.contrib.contenttypes.models import ContentType
4
+
5
+ from django_spire.comment.models import Comment
6
+ from django_spire.core.tests.test_cases import BaseTestCase
7
+
8
+
9
+ class TestCommentModel(BaseTestCase):
10
+ def setUp(self) -> None:
11
+ super().setUp()
12
+
13
+ self.content_type = ContentType.objects.get_for_model(self.super_user)
14
+ self.comment = Comment.objects.create(
15
+ content_type=self.content_type,
16
+ object_id=self.super_user.pk,
17
+ user=self.super_user,
18
+ information='This is a test comment.'
19
+ )
20
+
21
+ def test_str(self) -> None:
22
+ assert str(self.comment) == 'This is a ...'
23
+
24
+ def test_str_short_information(self) -> None:
25
+ comment = Comment.objects.create(
26
+ content_type=self.content_type,
27
+ object_id=self.super_user.pk,
28
+ user=self.super_user,
29
+ information='Short'
30
+ )
31
+ assert str(comment) == 'Short...'
32
+
33
+ def test_scrape_username_list(self) -> None:
34
+ comment = Comment.objects.create(
35
+ content_type=self.content_type,
36
+ object_id=self.super_user.pk,
37
+ user=self.super_user,
38
+ information='Hello @john_doe and @jane_doe'
39
+ )
40
+ usernames = comment.scrape_username_list()
41
+ assert usernames == ['john_doe', 'jane_doe']
42
+
43
+ def test_scrape_username_list_empty(self) -> None:
44
+ usernames = self.comment.scrape_username_list()
45
+ assert usernames == []
46
+
47
+ def test_scrape_username_list_single(self) -> None:
48
+ comment = Comment.objects.create(
49
+ content_type=self.content_type,
50
+ object_id=self.super_user.pk,
51
+ user=self.super_user,
52
+ information='Hello @stratus'
53
+ )
54
+ usernames = comment.scrape_username_list()
55
+ assert usernames == ['stratus']
56
+
57
+ def test_find_user_list(self) -> None:
58
+ comment = Comment.objects.create(
59
+ content_type=self.content_type,
60
+ object_id=self.super_user.pk,
61
+ user=self.super_user,
62
+ information='Hello @stratus'
63
+ )
64
+ users = comment.find_user_list()
65
+ assert self.super_user in users
66
+
67
+ def test_find_user_list_empty(self) -> None:
68
+ users = self.comment.find_user_list()
69
+ assert users.count() == 0
70
+
71
+ def test_update(self) -> None:
72
+ self.comment.update('Updated information')
73
+ self.comment.refresh_from_db()
74
+ assert self.comment.information == 'Updated information'
75
+ assert self.comment.is_edited is True
76
+
77
+ def test_update_sets_is_edited(self) -> None:
78
+ assert self.comment.is_edited is False
79
+ self.comment.update('New content')
80
+ assert self.comment.is_edited is True
81
+
82
+ def test_parent_relationship(self) -> None:
83
+ parent_comment = Comment.objects.create(
84
+ content_type=self.content_type,
85
+ object_id=self.super_user.pk,
86
+ user=self.super_user,
87
+ information='Parent comment'
88
+ )
89
+ child_comment = Comment.objects.create(
90
+ content_type=self.content_type,
91
+ object_id=self.super_user.pk,
92
+ user=self.super_user,
93
+ parent=parent_comment,
94
+ information='Child comment'
95
+ )
96
+ assert child_comment.parent == parent_comment
97
+ assert child_comment in parent_comment.children.all()
98
+
99
+ def test_default_ordering(self) -> None:
100
+ comment1 = Comment.objects.create(
101
+ content_type=self.content_type,
102
+ object_id=self.super_user.pk,
103
+ user=self.super_user,
104
+ information='First'
105
+ )
106
+
107
+ comment2 = Comment.objects.create(
108
+ content_type=self.content_type,
109
+ object_id=self.super_user.pk,
110
+ user=self.super_user,
111
+ information='Second'
112
+ )
113
+
114
+ comments = list(Comment.objects.order_by('-pk')[:2])
115
+ assert comments == [comment2, comment1]
116
+
117
+
118
+ class TestCommentRelationships(BaseTestCase):
119
+ def setUp(self) -> None:
120
+ super().setUp()
121
+
122
+ self.content_type = ContentType.objects.get_for_model(self.super_user)
123
+ self.comment = Comment.objects.create(
124
+ content_type=self.content_type,
125
+ object_id=self.super_user.pk,
126
+ user=self.super_user,
127
+ information='Test comment'
128
+ )
129
+
130
+ def test_comment_user_relationship(self) -> None:
131
+ assert self.comment.user == self.super_user
132
+ assert self.comment in self.super_user.comment_list.all()
133
+
134
+ def test_comment_content_object(self) -> None:
135
+ assert self.comment.content_object == self.super_user
136
+ assert self.comment.object_id == self.super_user.pk
137
+
138
+ def test_nested_replies(self) -> None:
139
+ parent_comment = Comment.objects.create(
140
+ content_type=self.content_type,
141
+ object_id=self.super_user.pk,
142
+ user=self.super_user,
143
+ information='Parent'
144
+ )
145
+ child1 = Comment.objects.create(
146
+ content_type=self.content_type,
147
+ object_id=self.super_user.pk,
148
+ user=self.super_user,
149
+ parent=parent_comment,
150
+ information='Child 1'
151
+ )
152
+ child2 = Comment.objects.create(
153
+ content_type=self.content_type,
154
+ object_id=self.super_user.pk,
155
+ user=self.super_user,
156
+ parent=parent_comment,
157
+ information='Child 2'
158
+ )
159
+ assert parent_comment.children.count() == 2
160
+ assert child1 in parent_comment.children.all()
161
+ assert child2 in parent_comment.children.all()
162
+
163
+ def test_cascade_delete_parent(self) -> None:
164
+ parent_comment = Comment.objects.create(
165
+ content_type=self.content_type,
166
+ object_id=self.super_user.pk,
167
+ user=self.super_user,
168
+ information='Parent'
169
+ )
170
+ child_comment = Comment.objects.create(
171
+ content_type=self.content_type,
172
+ object_id=self.super_user.pk,
173
+ user=self.super_user,
174
+ parent=parent_comment,
175
+ information='Child'
176
+ )
177
+ child_pk = child_comment.pk
178
+ parent_comment.delete()
179
+ assert Comment.objects.filter(pk=child_pk).exists() is False
180
+
181
+
182
+ class TestCommentDefaults(BaseTestCase):
183
+ def setUp(self) -> None:
184
+ super().setUp()
185
+
186
+ self.content_type = ContentType.objects.get_for_model(self.super_user)
187
+ self.comment = Comment.objects.create(
188
+ content_type=self.content_type,
189
+ object_id=self.super_user.pk,
190
+ user=self.super_user,
191
+ information='Test comment'
192
+ )
193
+
194
+ def test_default_is_active(self) -> None:
195
+ assert self.comment.is_active is True
196
+
197
+ def test_default_is_deleted(self) -> None:
198
+ assert self.comment.is_deleted is False
199
+
200
+ def test_default_is_edited(self) -> None:
201
+ assert self.comment.is_edited is False
202
+
203
+ def test_default_information(self) -> None:
204
+ comment = Comment.objects.create(
205
+ content_type=self.content_type,
206
+ object_id=self.super_user.pk,
207
+ user=self.super_user
208
+ )
209
+ assert comment.information == ''
210
+
211
+ def test_created_datetime_auto_set(self) -> None:
212
+ assert self.comment.created_datetime is not None
213
+
214
+ def test_parent_default_null(self) -> None:
215
+ assert self.comment.parent is None
@@ -0,0 +1,101 @@
1
+ from __future__ import annotations
2
+
3
+ from django.contrib.contenttypes.models import ContentType
4
+
5
+ from django_spire.comment.models import Comment
6
+ from django_spire.core.tests.test_cases import BaseTestCase
7
+
8
+
9
+ class TestCommentQuerySet(BaseTestCase):
10
+ def setUp(self) -> None:
11
+ super().setUp()
12
+
13
+ self.content_type = ContentType.objects.get_for_model(self.super_user)
14
+ self.comment = Comment.objects.create(
15
+ content_type=self.content_type,
16
+ object_id=self.super_user.pk,
17
+ user=self.super_user,
18
+ information='Test comment'
19
+ )
20
+
21
+ def test_active(self) -> None:
22
+ comments = Comment.objects.active()
23
+ assert self.comment in comments
24
+
25
+ def test_active_excludes_deleted(self) -> None:
26
+ comment = Comment.objects.create(
27
+ content_type=self.content_type,
28
+ object_id=self.super_user.pk,
29
+ user=self.super_user,
30
+ information='Deleted comment',
31
+ is_deleted=True
32
+ )
33
+ comments = Comment.objects.active()
34
+ assert comment not in comments
35
+
36
+ def test_top_level(self) -> None:
37
+ parent_comment = Comment.objects.create(
38
+ content_type=self.content_type,
39
+ object_id=self.super_user.pk,
40
+ user=self.super_user,
41
+ information='Parent'
42
+ )
43
+ child_comment = Comment.objects.create(
44
+ content_type=self.content_type,
45
+ object_id=self.super_user.pk,
46
+ user=self.super_user,
47
+ parent=parent_comment,
48
+ information='Child'
49
+ )
50
+ top_level = Comment.objects.top_level()
51
+ assert parent_comment in top_level
52
+ assert child_comment not in top_level
53
+
54
+ def test_prefetch_user(self) -> None:
55
+ comments = Comment.objects.prefetch_user()
56
+ assert self.comment in comments
57
+
58
+ def test_prefetch_parent(self) -> None:
59
+ parent_comment = Comment.objects.create(
60
+ content_type=self.content_type,
61
+ object_id=self.super_user.pk,
62
+ user=self.super_user,
63
+ information='Parent'
64
+ )
65
+ child_comment = Comment.objects.create(
66
+ content_type=self.content_type,
67
+ object_id=self.super_user.pk,
68
+ user=self.super_user,
69
+ parent=parent_comment,
70
+ information='Child'
71
+ )
72
+ comments = Comment.objects.prefetch_parent()
73
+ assert child_comment in comments
74
+
75
+ def test_prefetch_replies(self) -> None:
76
+ parent_comment = Comment.objects.create(
77
+ content_type=self.content_type,
78
+ object_id=self.super_user.pk,
79
+ user=self.super_user,
80
+ information='Parent'
81
+ )
82
+ comments = Comment.objects.prefetch_replies()
83
+ assert parent_comment in comments
84
+
85
+ def test_chained_querysets(self) -> None:
86
+ parent_comment = Comment.objects.create(
87
+ content_type=self.content_type,
88
+ object_id=self.super_user.pk,
89
+ user=self.super_user,
90
+ information='Parent'
91
+ )
92
+ child_comment = Comment.objects.create(
93
+ content_type=self.content_type,
94
+ object_id=self.super_user.pk,
95
+ user=self.super_user,
96
+ parent=parent_comment,
97
+ information='Child'
98
+ )
99
+ comments = Comment.objects.active().top_level().prefetch_user()
100
+ assert parent_comment in comments
101
+ assert child_comment not in comments
@@ -0,0 +1,90 @@
1
+ from __future__ import annotations
2
+
3
+ from django.contrib.auth.models import Group, Permission
4
+ from django.contrib.contenttypes.models import ContentType
5
+
6
+ from django_spire.auth.user.models import AuthUser
7
+ from django_spire.comment.models import Comment
8
+ from django_spire.comment.utils import (
9
+ find_user_list_from_content_type,
10
+ generate_comment_user_list_data,
11
+ parse_user_id_to_int_list,
12
+ )
13
+ from django_spire.core.tests.test_cases import BaseTestCase
14
+
15
+
16
+ class TestGenerateCommentUserListData(BaseTestCase):
17
+ def test_single_user(self) -> None:
18
+ self.super_user.first_name = 'Test'
19
+ self.super_user.last_name = 'User'
20
+ self.super_user.save()
21
+ user_list = [self.super_user]
22
+ data = generate_comment_user_list_data(user_list)
23
+ assert len(data) == 1
24
+ assert data[0]['full_name'] == 'Test_User'
25
+ assert data[0]['id'] == self.super_user.pk
26
+
27
+ def test_empty_list(self) -> None:
28
+ data = generate_comment_user_list_data([])
29
+ assert data == []
30
+
31
+ def test_multiple_users(self) -> None:
32
+ self.super_user.first_name = 'Test'
33
+ self.super_user.last_name = 'User'
34
+ self.super_user.save()
35
+ other_user = AuthUser.objects.create_user(
36
+ username='other',
37
+ first_name='Other',
38
+ last_name='User'
39
+ )
40
+ user_list = [self.super_user, other_user]
41
+ data = generate_comment_user_list_data(user_list)
42
+ assert len(data) == 2
43
+ full_names = [d['full_name'] for d in data]
44
+ assert 'Test_User' in full_names
45
+ assert 'Other_User' in full_names
46
+
47
+
48
+ class TestParseUserIdToIntList(BaseTestCase):
49
+ def test_multiple_ids(self) -> None:
50
+ other_user = AuthUser.objects.create_user(username='other')
51
+ user_id_str = f'{self.super_user.pk},{other_user.pk}'
52
+ users = parse_user_id_to_int_list(user_id_str)
53
+ assert self.super_user in users
54
+ assert other_user in users
55
+
56
+ def test_single_id(self) -> None:
57
+ user_id_str = str(self.super_user.pk)
58
+ users = parse_user_id_to_int_list(user_id_str)
59
+ assert self.super_user in users
60
+ assert users.count() == 1
61
+
62
+
63
+ class TestFindUserListFromContentType(BaseTestCase):
64
+ def test_find_users_with_permission(self) -> None:
65
+ content_type = ContentType.objects.get_for_model(Comment)
66
+ permission = Permission.objects.get(
67
+ content_type=content_type,
68
+ codename='view_comment'
69
+ )
70
+ group = Group.objects.create(name='test_group')
71
+ group.permissions.add(permission)
72
+ self.super_user.groups.add(group)
73
+
74
+ users = find_user_list_from_content_type('django_spire_comment', 'comment')
75
+ assert self.super_user in users
76
+
77
+ def test_excludes_users_without_permission(self) -> None:
78
+ other_user = AuthUser.objects.create_user(username='other')
79
+ content_type = ContentType.objects.get_for_model(Comment)
80
+ permission = Permission.objects.get(
81
+ content_type=content_type,
82
+ codename='view_comment'
83
+ )
84
+ group = Group.objects.create(name='test_group')
85
+ group.permissions.add(permission)
86
+ self.super_user.groups.add(group)
87
+
88
+ users = find_user_list_from_content_type('django_spire_comment', 'comment')
89
+ assert self.super_user in users
90
+ assert other_user not in users
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.urls import path
2
4
 
3
5
  from django_spire.comment import views
@@ -1,9 +1,15 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from typing import TYPE_CHECKING
3
4
 
4
- def find_user_list_from_content_type(app_label, model_name):
5
- from django.contrib.contenttypes.models import ContentType
5
+ if TYPE_CHECKING:
6
+ from django.contrib.auth.models import User
7
+ from django.db.models import Model, QuerySet
8
+
9
+
10
+ def find_user_list_from_content_type(app_label: str, model_name: str) -> QuerySet[User]:
6
11
  from django.contrib.auth.models import Permission, Group, User
12
+ from django.contrib.contenttypes.models import ContentType
7
13
 
8
14
  content_type = ContentType.objects.get(app_label=app_label, model=model_name)
9
15
  permission = Permission.objects.get(content_type=content_type, codename=f'view_{model_name}')
@@ -12,28 +18,31 @@ def find_user_list_from_content_type(app_label, model_name):
12
18
  return User.objects.filter(groups__in=groups).distinct()
13
19
 
14
20
 
15
- def generate_comment_user_list_data(user_list):
16
- user_data = []
17
-
18
- for user in user_list:
19
- user_data.append({
21
+ def generate_comment_user_list_data(user_list: QuerySet[User] | list[User]) -> list[dict]:
22
+ return [
23
+ {
20
24
  'full_name': user.get_full_name().replace(' ', '_'),
21
25
  'id': user.pk,
22
- })
23
-
24
- return user_data
26
+ }
27
+ for user in user_list
28
+ ]
25
29
 
26
30
 
27
- def parse_user_id_to_int_list(user_id_str):
31
+ def parse_user_id_to_int_list(user_id_str: str) -> QuerySet[User]:
28
32
  from django.contrib.auth.models import User
29
33
 
30
34
  user_id_list = [int(user_id) for user_id in user_id_str.split(',')]
31
35
  return User.objects.filter(id__in=user_id_list)
32
36
 
33
37
 
34
- def process_comment_notifications(user_list, comment_information, related_obj, user_commenting):
35
- from django_spire.notification.models import Notification
38
+ def process_comment_notifications(
39
+ user_list: QuerySet[User] | list[User],
40
+ comment_information: str,
41
+ related_obj: Model,
42
+ user_commenting: User
43
+ ) -> None:
36
44
  from django.contrib.sites.models import Site
45
+ from django_spire.notification.models import Notification
37
46
 
38
47
  for user in user_list:
39
48
  if user != user_commenting:
@@ -78,7 +78,7 @@ def comment_form_view(
78
78
  if request.method == 'POST':
79
79
  form = CommentForm(request.POST, instance=comment)
80
80
  if form.is_valid():
81
- # Todo: Create comment factory.
81
+ # TODO: Create comment factory.
82
82
  if comment_pk == 0:
83
83
  obj.add_comment(
84
84
  user=request.user,
django_spire/conf.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.conf import settings as django_settings
2
4
 
3
5
  from django_spire import settings as django_spire_default_settings
@@ -14,12 +16,11 @@ class Settings:
14
16
  if hasattr(django_spire_default_settings, name):
15
17
  django_spire_value = getattr(django_spire_default_settings, name)
16
18
 
17
- if name == 'DJANGO_SPIRE_AUTH_CONTROLLERS':
18
- if isinstance(django_value, dict) and isinstance(django_spire_value, dict):
19
- return {
20
- **django_spire_value,
21
- **django_value
22
- }
19
+ if name == 'DJANGO_SPIRE_AUTH_CONTROLLERS' and isinstance(django_value, dict) and isinstance(django_spire_value, dict):
20
+ return {
21
+ **django_spire_value,
22
+ **django_value
23
+ }
23
24
 
24
25
  if django_value is not None:
25
26
  return django_value
@@ -29,4 +30,5 @@ class Settings:
29
30
 
30
31
  return None
31
32
 
33
+
32
34
  settings = Settings()
django_spire/consts.py CHANGED
@@ -1,4 +1,4 @@
1
- __VERSION__ = '0.23.6'
1
+ __VERSION__ = '0.23.8'
2
2
 
3
3
  MAINTENANCE_MODE_SETTINGS_NAME = 'MAINTENANCE_MODE'
4
4
 
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.apps import AppConfig
2
4
 
3
5