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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (542) hide show
  1. django_spire/ai/admin.py +11 -11
  2. django_spire/ai/chat/apps.py +1 -0
  3. django_spire/ai/chat/templates/django_spire/ai/chat/widget/dialog_widget.html +1 -1
  4. django_spire/ai/chat/tests/factories.py +15 -0
  5. django_spire/ai/chat/tests/test_controller.py +45 -0
  6. django_spire/ai/chat/tests/test_models.py +301 -0
  7. django_spire/ai/chat/tests/test_prompts.py +48 -0
  8. django_spire/ai/chat/tests/test_responses.py +208 -0
  9. django_spire/ai/chat/tests/test_router/test_base_chat_router.py +66 -6
  10. django_spire/ai/chat/tests/test_router/test_chat_workflow.py +73 -3
  11. django_spire/ai/chat/tests/test_router/test_integration.py +86 -6
  12. django_spire/ai/chat/tests/test_router/test_intent_decoder.py +93 -1
  13. django_spire/ai/chat/tests/test_router/test_message_intel.py +60 -1
  14. django_spire/ai/chat/tests/test_router/test_spire_chat_router.py +110 -0
  15. django_spire/ai/chat/tests/test_urls/test_json_urls.py +202 -1
  16. django_spire/ai/context/tests/__init__.py +0 -0
  17. django_spire/ai/context/tests/test_context.py +188 -0
  18. django_spire/ai/decorators.py +7 -6
  19. django_spire/ai/prompt/tests/test_bots.py +100 -10
  20. django_spire/ai/prompt/tests/test_prompt_intel.py +83 -0
  21. django_spire/ai/prompt/tests/test_prompt_tuning.py +126 -0
  22. django_spire/ai/sms/decorators.py +8 -2
  23. django_spire/ai/sms/tests/test_sms.py +240 -16
  24. django_spire/ai/sms/tests/test_sms_intel.py +42 -0
  25. django_spire/ai/sms/tests/test_webhook.py +155 -7
  26. django_spire/ai/sms/views.py +23 -24
  27. django_spire/ai/tests/test_ai.py +131 -7
  28. django_spire/auth/apps.py +4 -2
  29. django_spire/auth/controller/controller.py +36 -23
  30. django_spire/auth/controller/exceptions.py +9 -0
  31. django_spire/auth/group/admin.py +1 -0
  32. django_spire/auth/group/apps.py +2 -0
  33. django_spire/auth/group/factories.py +17 -8
  34. django_spire/auth/group/forms.py +7 -0
  35. django_spire/auth/group/tests/test_factories.py +146 -0
  36. django_spire/auth/group/tests/test_forms.py +282 -0
  37. django_spire/auth/group/tests/test_models.py +192 -0
  38. django_spire/auth/group/tests/test_querysets.py +98 -0
  39. django_spire/auth/group/tests/test_utils.py +341 -0
  40. django_spire/auth/group/tests/test_views.py +377 -0
  41. django_spire/auth/group/urls/__init__.py +3 -1
  42. django_spire/auth/group/urls/form_urls.py +2 -0
  43. django_spire/auth/group/urls/json_urls.py +3 -0
  44. django_spire/auth/group/urls/page_urls.py +2 -0
  45. django_spire/auth/group/utils.py +6 -2
  46. django_spire/auth/group/views/form_views.py +6 -3
  47. django_spire/auth/group/views/json_views.py +6 -2
  48. django_spire/auth/mfa/admin.py +2 -0
  49. django_spire/auth/mfa/apps.py +2 -0
  50. django_spire/auth/mfa/forms.py +1 -0
  51. django_spire/auth/mfa/querysets.py +9 -2
  52. django_spire/auth/mfa/tests/test_models.py +233 -0
  53. django_spire/auth/mfa/tests/test_utils.py +106 -0
  54. django_spire/auth/mfa/urls/__init__.py +2 -0
  55. django_spire/auth/mfa/urls/page_urls.py +2 -0
  56. django_spire/auth/mfa/urls/redirect_urls.py +2 -0
  57. django_spire/auth/mfa/views/page_views.py +2 -1
  58. django_spire/auth/permissions/consts.py +2 -2
  59. django_spire/auth/permissions/decorators.py +8 -8
  60. django_spire/auth/permissions/permissions.py +28 -35
  61. django_spire/auth/permissions/tests/test_decorators.py +333 -0
  62. django_spire/auth/permissions/tests/test_permissions.py +337 -0
  63. django_spire/auth/permissions/tests/test_tools.py +305 -0
  64. django_spire/auth/permissions/tools.py +21 -15
  65. django_spire/auth/seeding/seed.py +3 -0
  66. django_spire/auth/seeding/seeder.py +2 -0
  67. django_spire/auth/tests/test_controller.py +323 -0
  68. django_spire/auth/tests/test_url_endpoints.py +9 -9
  69. django_spire/auth/tests/test_views.py +406 -0
  70. django_spire/auth/urls/admin_urls.py +2 -0
  71. django_spire/auth/urls/redirect_urls.py +2 -0
  72. django_spire/auth/user/apps.py +2 -0
  73. django_spire/auth/user/forms.py +9 -0
  74. django_spire/auth/user/models.py +1 -1
  75. django_spire/auth/user/services/services.py +1 -0
  76. django_spire/auth/user/tests/factories.py +14 -13
  77. django_spire/auth/user/tests/test_factories.py +166 -2
  78. django_spire/auth/user/tests/test_forms.py +573 -0
  79. django_spire/auth/user/tests/test_models.py +257 -0
  80. django_spire/auth/user/tests/test_services.py +200 -0
  81. django_spire/auth/user/tests/test_tools.py +153 -0
  82. django_spire/auth/user/tests/test_user_factories.py +139 -0
  83. django_spire/auth/user/tests/test_views.py +363 -0
  84. django_spire/auth/user/tools.py +7 -1
  85. django_spire/auth/user/urls/form_urls.py +3 -0
  86. django_spire/auth/user/urls/page_urls.py +3 -0
  87. django_spire/auth/user/views/form_views.py +19 -10
  88. django_spire/auth/user/views/page_views.py +8 -2
  89. django_spire/auth/views/redirect_views.py +14 -9
  90. django_spire/comment/admin.py +2 -0
  91. django_spire/comment/apps.py +2 -0
  92. django_spire/comment/templatetags/comment_tags.py +1 -0
  93. django_spire/comment/tests/test_forms.py +27 -0
  94. django_spire/comment/tests/test_models.py +215 -0
  95. django_spire/comment/tests/test_querysets.py +101 -0
  96. django_spire/comment/tests/test_utils.py +90 -0
  97. django_spire/comment/urls.py +2 -0
  98. django_spire/comment/utils.py +22 -13
  99. django_spire/comment/views.py +1 -1
  100. django_spire/conf.py +8 -6
  101. django_spire/consts.py +1 -1
  102. django_spire/contrib/breadcrumb/apps.py +2 -0
  103. django_spire/contrib/breadcrumb/breadcrumbs.py +18 -18
  104. django_spire/contrib/breadcrumb/tests/test_breadcrumbs.py +198 -0
  105. django_spire/contrib/constructor/__init__.py +3 -3
  106. django_spire/contrib/constructor/constructor.py +15 -15
  107. django_spire/contrib/constructor/django_model_constructor.py +5 -4
  108. django_spire/contrib/constructor/exceptions.py +5 -3
  109. django_spire/contrib/constructor/tests/__init__.py +0 -0
  110. django_spire/contrib/constructor/tests/test_constructor.py +193 -0
  111. django_spire/contrib/form/tests/__init__.py +0 -0
  112. django_spire/contrib/form/tests/test_forms.py +203 -0
  113. django_spire/contrib/generic_views/modal_views.py +2 -1
  114. django_spire/contrib/generic_views/portal_views.py +20 -19
  115. django_spire/contrib/generic_views/tests/__init__.py +0 -0
  116. django_spire/contrib/generic_views/tests/test_views.py +459 -0
  117. django_spire/contrib/help/apps.py +2 -0
  118. django_spire/contrib/help/templatetags/help.py +1 -0
  119. django_spire/contrib/help/tests/__init__.py +0 -0
  120. django_spire/contrib/help/tests/test_templatetags.py +100 -0
  121. django_spire/contrib/options/mixins.py +6 -5
  122. django_spire/contrib/options/tests/factories.py +5 -1
  123. django_spire/contrib/options/tests/test_options.py +234 -0
  124. django_spire/contrib/ordering/exceptions.py +7 -3
  125. django_spire/contrib/ordering/mixins.py +2 -0
  126. django_spire/contrib/ordering/querysets.py +3 -1
  127. django_spire/contrib/ordering/services/processor_service.py +8 -4
  128. django_spire/contrib/ordering/services/service.py +1 -2
  129. django_spire/contrib/ordering/tests/__init__.py +0 -0
  130. django_spire/contrib/ordering/tests/test_ordering.py +165 -0
  131. django_spire/contrib/ordering/validators.py +6 -6
  132. django_spire/contrib/pagination/templatetags/pagination_tags.py +12 -5
  133. django_spire/contrib/pagination/tests/__init__.py +0 -0
  134. django_spire/contrib/pagination/tests/test_pagination.py +179 -0
  135. django_spire/contrib/performance/decorators.py +16 -6
  136. django_spire/contrib/performance/tests/__init__.py +0 -0
  137. django_spire/contrib/performance/tests/test_performance.py +107 -0
  138. django_spire/contrib/queryset/enums.py +3 -1
  139. django_spire/contrib/queryset/filter_tools.py +10 -5
  140. django_spire/contrib/queryset/mixins.py +16 -16
  141. django_spire/contrib/queryset/tests/__init__.py +0 -0
  142. django_spire/contrib/queryset/tests/test_queryset.py +137 -0
  143. django_spire/contrib/seeding/field/base.py +13 -7
  144. django_spire/contrib/seeding/field/callable.py +8 -1
  145. django_spire/contrib/seeding/field/cleaners.py +5 -5
  146. django_spire/contrib/seeding/field/custom.py +20 -10
  147. django_spire/contrib/seeding/field/django/seeder.py +8 -6
  148. django_spire/contrib/seeding/field/enums.py +7 -5
  149. django_spire/contrib/seeding/field/override.py +16 -6
  150. django_spire/contrib/seeding/field/static.py +9 -2
  151. django_spire/contrib/seeding/field/tests/test_base.py +18 -14
  152. django_spire/contrib/seeding/field/tests/test_callable.py +13 -9
  153. django_spire/contrib/seeding/field/tests/test_cleaners.py +51 -38
  154. django_spire/contrib/seeding/field/tests/test_static.py +13 -9
  155. django_spire/contrib/seeding/intelligence/bots/seeder_generator_bot.py +2 -0
  156. django_spire/contrib/seeding/intelligence/intel.py +5 -1
  157. django_spire/contrib/seeding/intelligence/prompts/factory.py +6 -1
  158. django_spire/contrib/seeding/intelligence/prompts/foreign_key_selection_prompt.py +6 -1
  159. django_spire/contrib/seeding/intelligence/prompts/generate_django_model_seeder_prompts.py +2 -0
  160. django_spire/contrib/seeding/intelligence/prompts/generic_relationship_selection_prompt.py +7 -1
  161. django_spire/contrib/seeding/intelligence/prompts/hierarchical_selection_prompt.py +6 -2
  162. django_spire/contrib/seeding/intelligence/prompts/model_field_choices_prompt.py +8 -2
  163. django_spire/contrib/seeding/intelligence/prompts/negation_prompt.py +2 -0
  164. django_spire/contrib/seeding/intelligence/prompts/objective_prompt.py +6 -1
  165. django_spire/contrib/seeding/management/commands/seeding.py +9 -3
  166. django_spire/contrib/seeding/management/example.py +2 -0
  167. django_spire/contrib/seeding/model/base.py +16 -7
  168. django_spire/contrib/seeding/model/config.py +31 -15
  169. django_spire/contrib/seeding/model/django/config.py +13 -13
  170. django_spire/contrib/seeding/model/django/seeder.py +4 -4
  171. django_spire/contrib/seeding/model/django/tests/test_seeder.py +34 -23
  172. django_spire/contrib/seeding/model/enums.py +2 -0
  173. django_spire/contrib/seeding/tests/test_config.py +71 -0
  174. django_spire/contrib/seeding/tests/test_custom.py +35 -0
  175. django_spire/contrib/seeding/tests/test_enums.py +40 -0
  176. django_spire/contrib/seeding/tests/test_intel.py +32 -0
  177. django_spire/contrib/seeding/tests/test_override.py +63 -0
  178. django_spire/contrib/service/__init__.py +2 -2
  179. django_spire/contrib/service/django_model_service.py +16 -15
  180. django_spire/contrib/service/exceptions.py +5 -3
  181. django_spire/contrib/service/tests/__init__.py +0 -0
  182. django_spire/contrib/service/tests/test_service.py +153 -0
  183. django_spire/contrib/session/apps.py +2 -0
  184. django_spire/contrib/session/controller.py +48 -42
  185. django_spire/contrib/session/templatetags/session_tags.py +11 -2
  186. django_spire/contrib/session/tests/test_session_controller.py +117 -53
  187. django_spire/contrib/tests/__init__.py +0 -0
  188. django_spire/contrib/tests/test_utils.py +37 -0
  189. django_spire/contrib/utils.py +4 -1
  190. django_spire/core/apps.py +2 -0
  191. django_spire/core/converters/tests/test_to_data.py +353 -0
  192. django_spire/core/converters/tests/test_to_enums.py +61 -41
  193. django_spire/core/converters/tests/test_to_pydantic.py +138 -109
  194. django_spire/core/converters/to_data.py +29 -10
  195. django_spire/core/converters/to_enums.py +4 -2
  196. django_spire/core/converters/to_pydantic.py +22 -22
  197. django_spire/core/decorators.py +19 -6
  198. django_spire/core/forms/widgets.py +4 -0
  199. django_spire/core/management/commands/spire_startapp_pkg/template/app/apps.py.template +2 -0
  200. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/__init__.py.template +2 -0
  201. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/form_urls.py.template +2 -0
  202. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/page_urls.py.template +2 -0
  203. django_spire/core/maps.py +3 -1
  204. django_spire/core/middleware/maintenance.py +3 -3
  205. django_spire/core/middleware.py +8 -6
  206. django_spire/core/redirect/__init__.py +5 -0
  207. django_spire/core/redirect/generic_redirect.py +1 -2
  208. django_spire/core/redirect/tests/__init__.py +0 -0
  209. django_spire/core/redirect/tests/test_generic_redirect.py +34 -0
  210. django_spire/core/{tests/tests_redirect.py → redirect/tests/test_safe_redirect.py} +55 -81
  211. django_spire/core/shortcuts.py +3 -3
  212. django_spire/core/static/django_spire/css/app-layout.css +1 -1
  213. django_spire/core/static/django_spire/css/app-navigation.css +3 -3
  214. django_spire/core/static/django_spire/css/bootstrap-override.css +4 -0
  215. django_spire/core/tag/admin.py +12 -0
  216. django_spire/core/tag/intelligence/tag_set_bot.py +2 -0
  217. django_spire/core/tag/mixins.py +2 -0
  218. django_spire/core/tag/models.py +2 -0
  219. django_spire/core/tag/querysets.py +2 -0
  220. django_spire/core/tag/service/tag_service.py +6 -3
  221. django_spire/core/tag/tests/test_intelligence.py +9 -9
  222. django_spire/core/tag/tests/test_tags.py +44 -54
  223. django_spire/core/tag/tests/test_tools.py +191 -0
  224. django_spire/core/tag/tools.py +3 -0
  225. django_spire/core/templates/django_spire/card/card.html +5 -2
  226. django_spire/core/templates/django_spire/card/title_card.html +12 -4
  227. django_spire/core/templates/django_spire/infinite_scroll/base.html +1 -0
  228. django_spire/core/templates/django_spire/navigation/side_navigation.html +19 -24
  229. django_spire/core/templates/django_spire/page/full_page.html +46 -16
  230. django_spire/core/templates/django_spire/table/base.html +5 -3
  231. django_spire/core/templatetags/json.py +6 -2
  232. django_spire/core/templatetags/message.py +13 -8
  233. django_spire/core/templatetags/string_formating.py +8 -5
  234. django_spire/core/templatetags/tests/__init__.py +0 -0
  235. django_spire/core/templatetags/tests/test_templatetags.py +427 -0
  236. django_spire/core/templatetags/variable_types.py +17 -9
  237. django_spire/core/tests/test_cases.py +1 -1
  238. django_spire/core/tests/test_conf.py +43 -0
  239. django_spire/core/tests/test_consts.py +28 -0
  240. django_spire/core/tests/test_context_processors.py +93 -0
  241. django_spire/core/tests/test_decorators.py +95 -0
  242. django_spire/core/tests/test_django_spire_utils.py +56 -0
  243. django_spire/core/tests/test_exceptions.py +37 -0
  244. django_spire/core/tests/test_models.py +54 -0
  245. django_spire/core/tests/test_settings.py +45 -0
  246. django_spire/core/tests/test_shortcuts.py +74 -0
  247. django_spire/core/tests/test_urls.py +16 -0
  248. django_spire/core/tests/test_utils.py +58 -0
  249. django_spire/core/urls.py +4 -1
  250. django_spire/core/utils.py +12 -8
  251. django_spire/exceptions.py +16 -1
  252. django_spire/file/admin.py +4 -2
  253. django_spire/file/apps.py +8 -10
  254. django_spire/file/fields.py +7 -7
  255. django_spire/file/forms.py +1 -1
  256. django_spire/file/interfaces.py +15 -15
  257. django_spire/file/mixins.py +1 -4
  258. django_spire/file/models.py +3 -5
  259. django_spire/file/tests/factories.py +59 -0
  260. django_spire/file/tests/test_admin.py +69 -0
  261. django_spire/file/tests/test_apps.py +24 -0
  262. django_spire/file/tests/test_fields.py +114 -0
  263. django_spire/file/tests/test_forms.py +20 -0
  264. django_spire/file/tests/test_interfaces.py +183 -0
  265. django_spire/file/tests/test_models.py +82 -0
  266. django_spire/file/tests/test_querysets.py +102 -0
  267. django_spire/file/tests/test_utils.py +32 -0
  268. django_spire/file/tests/test_views.py +145 -0
  269. django_spire/file/tests/test_widgets.py +82 -0
  270. django_spire/file/tools.py +8 -2
  271. django_spire/file/views.py +7 -3
  272. django_spire/file/widgets.py +12 -12
  273. django_spire/help_desk/admin.py +15 -0
  274. django_spire/help_desk/apps.py +2 -0
  275. django_spire/help_desk/auth/controller.py +2 -0
  276. django_spire/help_desk/choices.py +2 -0
  277. django_spire/help_desk/enums.py +2 -0
  278. django_spire/help_desk/exceptions.py +31 -3
  279. django_spire/help_desk/forms.py +2 -0
  280. django_spire/help_desk/models.py +2 -0
  281. django_spire/help_desk/querysets.py +4 -1
  282. django_spire/help_desk/services/notification_service.py +26 -27
  283. django_spire/help_desk/services/service.py +2 -3
  284. django_spire/help_desk/tests/factories.py +8 -3
  285. django_spire/help_desk/tests/test_admin.py +41 -0
  286. django_spire/help_desk/tests/test_apps.py +41 -0
  287. django_spire/help_desk/tests/test_choices.py +50 -0
  288. django_spire/help_desk/tests/test_controller.py +87 -0
  289. django_spire/help_desk/tests/test_enums.py +18 -0
  290. django_spire/help_desk/tests/test_exceptions.py +37 -0
  291. django_spire/help_desk/tests/test_forms.py +89 -0
  292. django_spire/help_desk/tests/test_models.py +59 -0
  293. django_spire/help_desk/tests/test_querysets.py +38 -0
  294. django_spire/help_desk/tests/test_services/test_notification_service.py +15 -8
  295. django_spire/help_desk/tests/test_services/test_service.py +92 -0
  296. django_spire/help_desk/tests/test_urls/test_form_urls.py +6 -6
  297. django_spire/help_desk/tests/test_urls/test_page_urls.py +8 -9
  298. django_spire/help_desk/tests/test_views/test_form_views.py +46 -19
  299. django_spire/help_desk/tests/test_views/test_page_views.py +32 -9
  300. django_spire/help_desk/urls/__init__.py +4 -1
  301. django_spire/help_desk/urls/form_urls.py +3 -0
  302. django_spire/help_desk/urls/page_urls.py +3 -0
  303. django_spire/help_desk/views/form_views.py +13 -5
  304. django_spire/help_desk/views/page_views.py +11 -3
  305. django_spire/history/activity/admin.py +2 -0
  306. django_spire/history/activity/apps.py +3 -1
  307. django_spire/history/activity/mixins.py +13 -7
  308. django_spire/history/activity/models.py +6 -5
  309. django_spire/history/activity/querysets.py +2 -0
  310. django_spire/history/activity/tests/__init__.py +0 -0
  311. django_spire/history/activity/tests/test_activity.py +176 -0
  312. django_spire/history/admin.py +9 -2
  313. django_spire/history/choices.py +3 -0
  314. django_spire/history/models.py +5 -5
  315. django_spire/history/tests/test_admin.py +93 -0
  316. django_spire/history/tests/test_history.py +101 -0
  317. django_spire/history/tests/test_mixins.py +84 -0
  318. django_spire/history/viewed/admin.py +3 -1
  319. django_spire/history/viewed/apps.py +3 -1
  320. django_spire/history/viewed/models.py +2 -0
  321. django_spire/history/viewed/tests/__init__.py +0 -0
  322. django_spire/history/viewed/tests/test_viewed.py +46 -0
  323. django_spire/knowledge/auth/tests/__init__.py +0 -0
  324. django_spire/knowledge/auth/tests/test_controller.py +116 -0
  325. django_spire/knowledge/collection/admin.py +5 -1
  326. django_spire/knowledge/collection/models.py +3 -1
  327. django_spire/knowledge/collection/seeding/seed.py +1 -0
  328. django_spire/knowledge/collection/services/factory_service.py +10 -11
  329. django_spire/knowledge/collection/services/ordering_service.py +1 -2
  330. django_spire/knowledge/collection/services/service.py +5 -10
  331. django_spire/knowledge/collection/services/tag_service.py +5 -2
  332. django_spire/knowledge/collection/tests/factories.py +28 -1
  333. django_spire/knowledge/collection/tests/test_models.py +48 -0
  334. django_spire/knowledge/collection/tests/test_querysets.py +93 -0
  335. django_spire/knowledge/collection/tests/test_services/test_factory_service.py +100 -0
  336. django_spire/knowledge/collection/tests/test_services/test_services.py +160 -0
  337. django_spire/knowledge/collection/tests/test_urls/test_form_urls.py +21 -3
  338. django_spire/knowledge/collection/tests/test_urls/test_json_urls.py +39 -1
  339. django_spire/knowledge/collection/tests/test_urls/test_page_urls.py +12 -4
  340. django_spire/knowledge/collection/urls/__init__.py +3 -0
  341. django_spire/knowledge/collection/urls/form_urls.py +2 -0
  342. django_spire/knowledge/collection/urls/json_urls.py +2 -0
  343. django_spire/knowledge/collection/urls/page_urls.py +2 -0
  344. django_spire/knowledge/collection/views/form_views.py +4 -4
  345. django_spire/knowledge/collection/views/json_views.py +5 -1
  346. django_spire/knowledge/collection/views/page_views.py +5 -2
  347. django_spire/knowledge/entry/admin.py +7 -1
  348. django_spire/knowledge/entry/forms.py +2 -0
  349. django_spire/knowledge/entry/models.py +2 -0
  350. django_spire/knowledge/entry/seeding/seed.py +3 -0
  351. django_spire/knowledge/entry/services/automation_service.py +5 -4
  352. django_spire/knowledge/entry/services/factory_service.py +7 -5
  353. django_spire/knowledge/entry/services/service.py +4 -7
  354. django_spire/knowledge/entry/services/tag_service.py +0 -1
  355. django_spire/knowledge/entry/services/tool_service.py +1 -0
  356. django_spire/knowledge/entry/services/transformation_services.py +1 -5
  357. django_spire/knowledge/entry/tests/factories.py +1 -2
  358. django_spire/knowledge/entry/tests/test_factory_service.py +20 -0
  359. django_spire/knowledge/entry/tests/test_models.py +41 -0
  360. django_spire/knowledge/entry/tests/test_querysets.py +71 -0
  361. django_spire/knowledge/entry/tests/test_services.py +94 -0
  362. django_spire/knowledge/entry/tests/test_urls/test_form_urls.py +9 -14
  363. django_spire/knowledge/entry/tests/test_urls/test_json_urls.py +48 -5
  364. django_spire/knowledge/entry/tests/test_urls/test_page_urls.py +6 -8
  365. django_spire/knowledge/entry/tests/test_urls/test_template_urls.py +40 -0
  366. django_spire/knowledge/entry/urls/form_urls.py +2 -0
  367. django_spire/knowledge/entry/urls/json_urls.py +2 -0
  368. django_spire/knowledge/entry/urls/page_urls.py +2 -0
  369. django_spire/knowledge/entry/urls/template_urls.py +2 -0
  370. django_spire/knowledge/entry/version/block/choices.py +2 -0
  371. django_spire/knowledge/entry/version/block/data/data.py +1 -0
  372. django_spire/knowledge/entry/version/block/data/list/data.py +8 -13
  373. django_spire/knowledge/entry/version/block/data/list/maps.py +3 -0
  374. django_spire/knowledge/entry/version/block/data/list/meta.py +1 -2
  375. django_spire/knowledge/entry/version/block/data/list/tests/__init__.py +0 -0
  376. django_spire/knowledge/entry/version/block/data/list/tests/test_maps.py +32 -0
  377. django_spire/knowledge/entry/version/block/data/list/tests/test_meta.py +58 -0
  378. django_spire/knowledge/entry/version/block/data/maps.py +3 -6
  379. django_spire/knowledge/entry/version/block/models.py +7 -5
  380. django_spire/knowledge/entry/version/block/seeding/constants.py +5 -4
  381. django_spire/knowledge/entry/version/block/services/service.py +2 -3
  382. django_spire/knowledge/entry/version/block/tests/factories.py +4 -10
  383. django_spire/knowledge/entry/version/block/tests/test_choices.py +56 -0
  384. django_spire/knowledge/entry/version/block/tests/test_data.py +90 -0
  385. django_spire/knowledge/entry/version/block/tests/test_maps.py +37 -0
  386. django_spire/knowledge/entry/version/block/tests/test_models.py +55 -0
  387. django_spire/knowledge/entry/version/block/tests/test_querysets.py +35 -0
  388. django_spire/knowledge/entry/version/block/tests/test_services.py +65 -0
  389. django_spire/knowledge/entry/version/choices.py +2 -0
  390. django_spire/knowledge/entry/version/converters/converter.py +1 -1
  391. django_spire/knowledge/entry/version/converters/docx_converter.py +4 -7
  392. django_spire/knowledge/entry/version/converters/markdown_converter.py +20 -20
  393. django_spire/knowledge/entry/version/maps.py +4 -5
  394. django_spire/knowledge/entry/version/querysets.py +1 -1
  395. django_spire/knowledge/entry/version/seeding/seeder.py +1 -2
  396. django_spire/knowledge/entry/version/services/processor_service.py +5 -4
  397. django_spire/knowledge/entry/version/services/service.py +1 -2
  398. django_spire/knowledge/entry/version/tests/factories.py +2 -2
  399. django_spire/knowledge/entry/version/tests/test_choices.py +18 -0
  400. django_spire/knowledge/entry/version/tests/test_converters/test_docx_converter.py +56 -8
  401. django_spire/knowledge/entry/version/tests/test_converters/test_markdown_converter.py +78 -0
  402. django_spire/knowledge/entry/version/tests/test_maps.py +58 -0
  403. django_spire/knowledge/entry/version/tests/test_models.py +23 -0
  404. django_spire/knowledge/entry/version/tests/test_querysets.py +26 -0
  405. django_spire/knowledge/entry/version/tests/test_services.py +62 -0
  406. django_spire/knowledge/entry/version/tests/test_urls/test_json_urls.py +27 -8
  407. django_spire/knowledge/entry/version/tests/test_urls/test_page_urls.py +15 -8
  408. django_spire/knowledge/entry/version/tests/test_urls/test_redirect_urls.py +38 -0
  409. django_spire/knowledge/entry/version/urls/__init__.py +3 -0
  410. django_spire/knowledge/entry/version/urls/json_urls.py +2 -1
  411. django_spire/knowledge/entry/version/urls/page_urls.py +2 -0
  412. django_spire/knowledge/entry/version/urls/redirect_urls.py +2 -0
  413. django_spire/knowledge/entry/version/views/json_views.py +5 -1
  414. django_spire/knowledge/entry/version/views/page_views.py +10 -3
  415. django_spire/knowledge/entry/version/views/redirect_views.py +5 -1
  416. django_spire/knowledge/entry/views/form_views.py +16 -8
  417. django_spire/knowledge/entry/views/json_views.py +3 -1
  418. django_spire/knowledge/entry/views/page_views.py +8 -2
  419. django_spire/knowledge/entry/views/template_views.py +7 -1
  420. django_spire/knowledge/exceptions.py +2 -1
  421. django_spire/knowledge/intelligence/intel/answer_intel.py +2 -1
  422. django_spire/knowledge/intelligence/intel/entry_intel.py +0 -1
  423. django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +4 -5
  424. django_spire/knowledge/models.py +1 -2
  425. django_spire/knowledge/tests/__init__.py +0 -0
  426. django_spire/knowledge/tests/test_templatetags.py +40 -0
  427. django_spire/knowledge/tests/test_urls/__init__.py +0 -0
  428. django_spire/knowledge/tests/test_urls/test_page_urls.py +24 -0
  429. django_spire/knowledge/urls/__init__.py +2 -0
  430. django_spire/knowledge/urls/page_urls.py +2 -0
  431. django_spire/knowledge/views/page_views.py +8 -3
  432. django_spire/notification/admin.py +3 -1
  433. django_spire/notification/app/admin.py +2 -0
  434. django_spire/notification/app/apps.py +3 -1
  435. django_spire/notification/app/exceptions.py +9 -2
  436. django_spire/notification/app/models.py +8 -4
  437. django_spire/notification/app/processor.py +22 -26
  438. django_spire/notification/app/querysets.py +2 -0
  439. django_spire/notification/app/tests/__init__.py +0 -0
  440. django_spire/notification/app/tests/factories.py +34 -0
  441. django_spire/notification/app/tests/test_apps.py +24 -0
  442. django_spire/notification/app/tests/test_models.py +72 -0
  443. django_spire/notification/app/tests/test_processor.py +111 -0
  444. django_spire/notification/app/tests/test_querysets.py +90 -0
  445. django_spire/notification/app/tests/test_views/__init__.py +0 -0
  446. django_spire/notification/app/tests/test_views/test_json_views.py +48 -0
  447. django_spire/notification/app/tests/test_views/test_page_views.py +19 -0
  448. django_spire/notification/app/urls/__init__.py +3 -1
  449. django_spire/notification/app/urls/json_urls.py +6 -4
  450. django_spire/notification/app/urls/page_urls.py +4 -3
  451. django_spire/notification/app/urls/template_urls.py +4 -2
  452. django_spire/notification/apps.py +4 -1
  453. django_spire/notification/email/admin.py +5 -1
  454. django_spire/notification/email/apps.py +3 -1
  455. django_spire/notification/email/exceptions.py +4 -2
  456. django_spire/notification/email/helper.py +5 -3
  457. django_spire/notification/email/models.py +4 -0
  458. django_spire/notification/email/processor.py +19 -15
  459. django_spire/notification/email/querysets.py +3 -0
  460. django_spire/notification/email/tests/__init__.py +0 -0
  461. django_spire/notification/email/tests/factories.py +35 -0
  462. django_spire/notification/email/tests/test_apps.py +24 -0
  463. django_spire/notification/email/tests/test_models.py +52 -0
  464. django_spire/notification/email/tests/test_processor.py +92 -0
  465. django_spire/notification/email/tests/test_querysets.py +43 -0
  466. django_spire/notification/exceptions.py +17 -2
  467. django_spire/notification/managers.py +7 -1
  468. django_spire/notification/maps.py +4 -1
  469. django_spire/notification/mixins.py +2 -0
  470. django_spire/notification/models.py +3 -1
  471. django_spire/notification/processors/notification.py +12 -5
  472. django_spire/notification/processors/processor.py +2 -0
  473. django_spire/notification/processors/tests/__init__.py +0 -0
  474. django_spire/notification/processors/tests/test_notification.py +106 -0
  475. django_spire/notification/push/admin.py +10 -1
  476. django_spire/notification/push/apps.py +3 -1
  477. django_spire/notification/push/models.py +2 -3
  478. django_spire/notification/push/tests/__init__.py +0 -0
  479. django_spire/notification/push/tests/test_apps.py +24 -0
  480. django_spire/notification/push/tests/test_models.py +28 -0
  481. django_spire/notification/querysets.py +7 -1
  482. django_spire/notification/sms/admin.py +2 -0
  483. django_spire/notification/sms/apps.py +4 -1
  484. django_spire/notification/sms/automations.py +2 -0
  485. django_spire/notification/sms/choices.py +2 -0
  486. django_spire/notification/sms/exceptions.py +19 -5
  487. django_spire/notification/sms/helper.py +33 -23
  488. django_spire/notification/sms/models.py +5 -1
  489. django_spire/notification/sms/processor.py +20 -20
  490. django_spire/notification/sms/querysets.py +2 -0
  491. django_spire/notification/sms/tests/factories.py +33 -0
  492. django_spire/notification/sms/tests/test_apps.py +24 -0
  493. django_spire/notification/sms/tests/test_automation.py +38 -0
  494. django_spire/notification/sms/tests/test_choices.py +15 -0
  495. django_spire/notification/sms/tests/test_consts.py +17 -0
  496. django_spire/notification/sms/tests/test_exceptions.py +27 -0
  497. django_spire/notification/sms/tests/test_helper.py +50 -0
  498. django_spire/notification/sms/tests/test_models.py +81 -0
  499. django_spire/notification/sms/tests/test_processor.py +107 -0
  500. django_spire/notification/sms/tests/test_tools.py +25 -11
  501. django_spire/notification/sms/tools.py +16 -5
  502. django_spire/notification/sms/urls/__init__.py +3 -1
  503. django_spire/notification/sms/urls/media_urls.py +2 -0
  504. django_spire/notification/sms/views/media_views.py +14 -4
  505. django_spire/notification/tests/__init__.py +0 -0
  506. django_spire/notification/tests/factories.py +26 -0
  507. django_spire/notification/tests/test_admin.py +55 -0
  508. django_spire/notification/tests/test_apps.py +30 -0
  509. django_spire/notification/tests/test_automation.py +18 -0
  510. django_spire/notification/tests/test_choices.py +59 -0
  511. django_spire/notification/tests/test_exceptions.py +58 -0
  512. django_spire/notification/tests/test_managers.py +100 -0
  513. django_spire/notification/tests/test_maps.py +31 -0
  514. django_spire/notification/tests/test_models.py +76 -0
  515. django_spire/notification/tests/test_querysets.py +184 -0
  516. django_spire/notification/tests/test_utils.py +23 -0
  517. django_spire/notification/urls.py +3 -1
  518. django_spire/notification/utils.py +3 -1
  519. django_spire/settings.py +3 -0
  520. django_spire/theme/tests/test_context_processor.py +15 -13
  521. django_spire/theme/tests/test_enums.py +2 -2
  522. django_spire/theme/tests/test_filesystem.py +2 -5
  523. django_spire/theme/tests/test_integration.py +12 -12
  524. django_spire/theme/tests/test_model.py +40 -38
  525. django_spire/theme/tests/test_views/test_json_views.py +33 -33
  526. django_spire/theme/urls/json_urls.py +3 -0
  527. django_spire/theme/urls/page_urls.py +3 -0
  528. django_spire/urls.py +19 -15
  529. django_spire/utils.py +13 -4
  530. {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/METADATA +2 -2
  531. {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/RECORD +534 -362
  532. {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/licenses/LICENSE.md +1 -1
  533. django_spire/contrib/options/tests/test_unit.py +0 -148
  534. django_spire/contrib/seeding/tests/test_seeding.py +0 -25
  535. django_spire/core/tests/test_templatetags.py +0 -117
  536. django_spire/core/tests/tests_shortcuts.py +0 -73
  537. django_spire/history/activity/tests.py +0 -3
  538. django_spire/history/activity/views.py +0 -3
  539. django_spire/knowledge/collection/tests/test_services/test_transformation_service.py +0 -71
  540. django_spire/notification/app/tests.py +0 -3
  541. {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/WHEEL +0 -0
  542. {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,116 @@
1
+ from __future__ import annotations
2
+
3
+ from django.contrib.auth.models import Permission
4
+ from django.contrib.contenttypes.models import ContentType
5
+ from django.test import RequestFactory
6
+
7
+ from django_spire.auth.user.tests.factories import create_user
8
+ from django_spire.core.tests.test_cases import BaseTestCase
9
+ from django_spire.knowledge.auth.controller import BaseKnowledgeAuthController
10
+ from django_spire.knowledge.collection.models import Collection
11
+
12
+
13
+ class BaseKnowledgeAuthControllerTests(BaseTestCase):
14
+ def setUp(self):
15
+ super().setUp()
16
+ self.factory = RequestFactory()
17
+ self.user = create_user(username='test_auth_user')
18
+ self.request = self.factory.get('/')
19
+ self.request.user = self.user
20
+
21
+ content_type = ContentType.objects.get_for_model(Collection)
22
+
23
+ self.view_permission = Permission.objects.get(
24
+ codename='view_collection',
25
+ content_type=content_type
26
+ )
27
+ self.add_permission = Permission.objects.get(
28
+ codename='add_collection',
29
+ content_type=content_type
30
+ )
31
+ self.change_permission = Permission.objects.get(
32
+ codename='change_collection',
33
+ content_type=content_type
34
+ )
35
+ self.delete_permission = Permission.objects.get(
36
+ codename='delete_collection',
37
+ content_type=content_type
38
+ )
39
+ self.access_all_permission = Permission.objects.get(
40
+ codename='can_access_all_collections',
41
+ content_type=content_type
42
+ )
43
+ self.change_groups_permission = Permission.objects.get(
44
+ codename='can_change_collection_groups',
45
+ content_type=content_type
46
+ )
47
+
48
+ def _refresh_user_and_request(self):
49
+ self.user = type(self.user).objects.get(pk=self.user.pk)
50
+ self.request.user = self.user
51
+
52
+ def test_can_view_without_permission(self):
53
+ controller = BaseKnowledgeAuthController(self.request)
54
+ assert controller.can_view() is False
55
+
56
+ def test_can_view_with_permission(self):
57
+ self.user.user_permissions.add(self.view_permission)
58
+ self._refresh_user_and_request()
59
+
60
+ controller = BaseKnowledgeAuthController(self.request)
61
+ assert controller.can_view() is True
62
+
63
+ def test_can_add_without_permission(self):
64
+ controller = BaseKnowledgeAuthController(self.request)
65
+ assert controller.can_add() is False
66
+
67
+ def test_can_add_with_permission(self):
68
+ self.user.user_permissions.add(self.add_permission)
69
+ self._refresh_user_and_request()
70
+
71
+ controller = BaseKnowledgeAuthController(self.request)
72
+ assert controller.can_add() is True
73
+
74
+ def test_can_change_without_permission(self):
75
+ controller = BaseKnowledgeAuthController(self.request)
76
+ assert controller.can_change() is False
77
+
78
+ def test_can_change_with_permission(self):
79
+ self.user.user_permissions.add(self.change_permission)
80
+ self._refresh_user_and_request()
81
+
82
+ controller = BaseKnowledgeAuthController(self.request)
83
+ assert controller.can_change() is True
84
+
85
+ def test_can_delete_without_permission(self):
86
+ controller = BaseKnowledgeAuthController(self.request)
87
+ assert controller.can_delete() is False
88
+
89
+ def test_can_delete_with_permission(self):
90
+ self.user.user_permissions.add(self.delete_permission)
91
+ self._refresh_user_and_request()
92
+
93
+ controller = BaseKnowledgeAuthController(self.request)
94
+ assert controller.can_delete() is True
95
+
96
+ def test_can_access_all_collections_without_permission(self):
97
+ controller = BaseKnowledgeAuthController(self.request)
98
+ assert controller.can_access_all_collections() is False
99
+
100
+ def test_can_access_all_collections_with_permission(self):
101
+ self.user.user_permissions.add(self.access_all_permission)
102
+ self._refresh_user_and_request()
103
+
104
+ controller = BaseKnowledgeAuthController(self.request)
105
+ assert controller.can_access_all_collections() is True
106
+
107
+ def test_can_change_collection_groups_without_permission(self):
108
+ controller = BaseKnowledgeAuthController(self.request)
109
+ assert controller.can_change_collection_groups() is False
110
+
111
+ def test_can_change_collection_groups_with_permission(self):
112
+ self.user.user_permissions.add(self.change_groups_permission)
113
+ self._refresh_user_and_request()
114
+
115
+ controller = BaseKnowledgeAuthController(self.request)
116
+ assert controller.can_change_collection_groups() is True
@@ -1,11 +1,15 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from typing import TYPE_CHECKING
4
+
3
5
  from django.contrib import admin
4
- from django.db.models import QuerySet
5
6
  from django.contrib import messages
6
7
 
7
8
  from .models import Collection, CollectionGroup
8
9
 
10
+ if TYPE_CHECKING:
11
+ from django.db.models import QuerySet
12
+
9
13
 
10
14
  @admin.register(Collection)
11
15
  class CollectionAdmin(admin.ModelAdmin):
@@ -9,8 +9,10 @@ from django_spire.core.tag.mixins import TagModelMixin
9
9
  from django_spire.contrib.utils import truncate_string
10
10
  from django_spire.history.mixins import HistoryModelMixin
11
11
  from django_spire.knowledge.collection.querysets import CollectionQuerySet
12
- from django_spire.knowledge.collection.services.service import CollectionGroupService, \
12
+ from django_spire.knowledge.collection.services.service import (
13
+ CollectionGroupService,
13
14
  CollectionService
15
+ )
14
16
 
15
17
 
16
18
  class Collection(
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from django_spire.knowledge.collection.seeding.seeder import CollectionSeeder
4
4
 
5
+
5
6
  parent_collections = CollectionSeeder.seed_database(count=5)
6
7
  child_collections = CollectionSeeder.seed_child_collections(count=15)
7
8
 
@@ -2,12 +2,12 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING
4
4
 
5
- from django.core.handlers.wsgi import WSGIRequest
6
-
7
5
  from django_spire.auth.controller.controller import AppAuthController
8
6
  from django_spire.contrib.service import BaseDjangoModelService
9
7
 
10
8
  if TYPE_CHECKING:
9
+ from django.core.handlers.wsgi import WSGIRequest
10
+
11
11
  from django_spire.knowledge.collection.models import Collection, CollectionGroup
12
12
 
13
13
 
@@ -15,10 +15,10 @@ class CollectionGroupFactoryService(BaseDjangoModelService['CollectionGroup']):
15
15
  obj: CollectionGroup
16
16
 
17
17
  def replace_groups(
18
- self,
19
- request: WSGIRequest,
20
- group_pks: list[int] | None,
21
- collection: Collection
18
+ self,
19
+ request: WSGIRequest,
20
+ group_pks: list[int] | None,
21
+ collection: Collection
22
22
  ) -> list[CollectionGroup]:
23
23
  if not AppAuthController('knowledge', request).can_change_collection_groups():
24
24
  return []
@@ -28,11 +28,10 @@ class CollectionGroupFactoryService(BaseDjangoModelService['CollectionGroup']):
28
28
 
29
29
  old_collection_groups = list(collection.groups.all())
30
30
 
31
- new_collection_groups = []
32
- for group_pk in group_pks:
33
- new_collection_groups.append(
34
- self.obj_class(auth_group_id=group_pk, collection=collection)
35
- )
31
+ new_collection_groups = [
32
+ self.obj_class(auth_group_id=group_pk, collection=collection)
33
+ for group_pk in group_pks
34
+ ]
36
35
 
37
36
  self.obj_class.objects.bulk_create(new_collection_groups)
38
37
 
@@ -1,9 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
- from django_spire.contrib.service import BaseDjangoModelService
4
-
5
3
  from typing import TYPE_CHECKING
6
4
 
5
+ from django_spire.contrib.service import BaseDjangoModelService
7
6
 
8
7
  if TYPE_CHECKING:
9
8
  from django_spire.knowledge.collection.models import Collection
@@ -4,17 +4,12 @@ from django_spire.contrib.service import BaseDjangoModelService
4
4
 
5
5
  from typing import TYPE_CHECKING
6
6
 
7
- from django_spire.knowledge.collection.services.factory_service import \
8
- CollectionGroupFactoryService
9
- from django_spire.knowledge.collection.services.ordering_service import \
10
- CollectionOrderingService
11
- from django_spire.knowledge.collection.services.processor_service import \
12
- CollectionProcessorService
7
+ from django_spire.knowledge.collection.services.factory_service import CollectionGroupFactoryService
8
+ from django_spire.knowledge.collection.services.ordering_service import CollectionOrderingService
9
+ from django_spire.knowledge.collection.services.processor_service import CollectionProcessorService
13
10
  from django_spire.knowledge.collection.services.tag_service import CollectionTagService
14
- from django_spire.knowledge.collection.services.tool_services import \
15
- CollectionToolService
16
- from django_spire.knowledge.collection.services.transformation_service import \
17
- CollectionTransformationService
11
+ from django_spire.knowledge.collection.services.tool_services import CollectionToolService
12
+ from django_spire.knowledge.collection.services.transformation_service import CollectionTransformationService
18
13
 
19
14
  if TYPE_CHECKING:
20
15
  from django_spire.knowledge.collection.models import Collection, CollectionGroup
@@ -6,8 +6,11 @@ from dandy import Prompt
6
6
 
7
7
  from django_spire.core.tag.intelligence.tag_set_bot import TagSetBot
8
8
  from django_spire.core.tag.service.tag_service import BaseTagService
9
- from django_spire.core.tag.tools import simplify_tag_set, simplify_and_weight_tag_set_to_dict, \
10
- get_score_percentage_from_tag_set_weighted
9
+ from django_spire.core.tag.tools import (
10
+ get_score_percentage_from_tag_set_weighted,
11
+ simplify_and_weight_tag_set_to_dict,
12
+ simplify_tag_set
13
+ )
11
14
 
12
15
  if TYPE_CHECKING:
13
16
  from django_spire.knowledge.collection.models import Collection
@@ -1,6 +1,15 @@
1
1
  from __future__ import annotations
2
2
 
3
- from django_spire.knowledge.collection.models import Collection
3
+ from django_spire.auth.group.models import AuthGroup
4
+ from django_spire.knowledge.collection.models import Collection, CollectionGroup
5
+
6
+
7
+ def create_test_auth_group(**kwargs) -> AuthGroup:
8
+ data = {
9
+ 'name': 'Test Group',
10
+ }
11
+ data.update(kwargs)
12
+ return AuthGroup.objects.create(**data)
4
13
 
5
14
 
6
15
  def create_test_collection(**kwargs) -> Collection:
@@ -13,3 +22,21 @@ def create_test_collection(**kwargs) -> Collection:
13
22
  }
14
23
  data.update(kwargs)
15
24
  return Collection.objects.create(**data)
25
+
26
+
27
+ def create_test_collection_group(
28
+ collection: Collection | None = None,
29
+ auth_group: AuthGroup | None = None,
30
+ **kwargs
31
+ ) -> CollectionGroup:
32
+ if collection is None:
33
+ collection = create_test_collection()
34
+ if auth_group is None:
35
+ auth_group = create_test_auth_group()
36
+
37
+ data = {
38
+ 'collection': collection,
39
+ 'auth_group': auth_group,
40
+ }
41
+ data.update(kwargs)
42
+ return CollectionGroup.objects.create(**data)
@@ -0,0 +1,48 @@
1
+ from __future__ import annotations
2
+
3
+ from django_spire.core.tests.test_cases import BaseTestCase
4
+ from django_spire.knowledge.collection.tests.factories import (
5
+ create_test_collection,
6
+ create_test_collection_group,
7
+ )
8
+
9
+
10
+ class CollectionModelTests(BaseTestCase):
11
+ def setUp(self):
12
+ super().setUp()
13
+ self.collection = create_test_collection()
14
+
15
+ def test_str(self):
16
+ assert str(self.collection) == self.collection.name
17
+
18
+ def test_name_short_truncates(self):
19
+ long_name = 'A' * 100
20
+ collection = create_test_collection(name=long_name)
21
+ assert len(collection.name_short) <= 35
22
+
23
+ def test_name_short_no_truncate(self):
24
+ short_name = 'Short'
25
+ collection = create_test_collection(name=short_name)
26
+ assert collection.name_short == short_name
27
+
28
+ def test_top_level_parent_returns_self_when_no_parent(self):
29
+ assert self.collection.top_level_parent == self.collection
30
+
31
+ def test_top_level_parent_returns_root(self):
32
+ child = create_test_collection(parent=self.collection, name='Child')
33
+ grandchild = create_test_collection(parent=child, name='Grandchild')
34
+ assert grandchild.top_level_parent == self.collection
35
+
36
+ def test_base_breadcrumb(self):
37
+ breadcrumbs = self.collection.base_breadcrumb()
38
+ assert breadcrumbs is not None
39
+
40
+
41
+ class CollectionGroupModelTests(BaseTestCase):
42
+ def setUp(self):
43
+ super().setUp()
44
+ self.collection_group = create_test_collection_group()
45
+
46
+ def test_str(self):
47
+ expected = f'{self.collection_group.collection.name} - {self.collection_group.auth_group.name}'
48
+ assert str(self.collection_group) == expected
@@ -0,0 +1,93 @@
1
+ from __future__ import annotations
2
+
3
+ from django.test import RequestFactory
4
+
5
+ from django_spire.auth.user.tests.factories import create_user
6
+ from django_spire.core.tests.test_cases import BaseTestCase
7
+ from django_spire.knowledge.collection.models import Collection
8
+ from django_spire.knowledge.collection.tests.factories import (
9
+ create_test_auth_group,
10
+ create_test_collection,
11
+ create_test_collection_group,
12
+ )
13
+
14
+
15
+ class CollectionQuerySetTests(BaseTestCase):
16
+ def setUp(self):
17
+ super().setUp()
18
+ self.factory = RequestFactory()
19
+ self.user = create_user(username='test_queryset_user')
20
+ self.parent_collection = create_test_collection(name='Parent')
21
+ self.child_collection = create_test_collection(
22
+ parent=self.parent_collection,
23
+ name='Child'
24
+ )
25
+
26
+ def test_by_parent(self):
27
+ result = Collection.objects.by_parent(parent=self.parent_collection)
28
+ assert self.child_collection in result
29
+ assert self.parent_collection not in result
30
+
31
+ def test_by_parent_id(self):
32
+ result = Collection.objects.by_parent_id(parent_id=self.parent_collection.id)
33
+ assert self.child_collection in result
34
+
35
+ def test_by_parent_ids(self):
36
+ result = Collection.objects.by_parent_ids(parent_ids=[self.parent_collection.id])
37
+ assert self.child_collection in result
38
+
39
+ def test_parentless(self):
40
+ result = Collection.objects.parentless()
41
+ assert self.parent_collection in result
42
+ assert self.child_collection not in result
43
+
44
+ def test_childless(self):
45
+ grandchild = create_test_collection(
46
+ parent=self.child_collection,
47
+ name='Grandchild'
48
+ )
49
+ result = Collection.objects.childless()
50
+ assert grandchild in result
51
+ assert self.parent_collection not in result
52
+
53
+ def test_children(self):
54
+ grandchild = create_test_collection(
55
+ parent=self.child_collection,
56
+ name='Grandchild'
57
+ )
58
+ result = Collection.objects.children(collection_id=self.parent_collection.id)
59
+ assert self.child_collection in result
60
+ assert grandchild in result
61
+
62
+ def test_exclude_children(self):
63
+ grandchild = create_test_collection(
64
+ parent=self.child_collection,
65
+ name='Grandchild'
66
+ )
67
+ result = Collection.objects.exclude_children(collection_id=self.parent_collection.id)
68
+ assert self.parent_collection in result
69
+ assert self.child_collection not in result
70
+ assert grandchild not in result
71
+
72
+ def test_annotate_entry_count(self):
73
+ result = Collection.objects.annotate_entry_count()
74
+ assert result.first().entry_count == 0
75
+
76
+ def test_request_user_has_access_superuser(self):
77
+ request = self.factory.get('/')
78
+ request.user = self.super_user
79
+ result = Collection.objects.request_user_has_access(request)
80
+ assert self.parent_collection in result
81
+
82
+ def test_request_user_has_access_with_group(self):
83
+ auth_group = create_test_auth_group(name='Access Group')
84
+ self.user.groups.add(auth_group)
85
+ create_test_collection_group(
86
+ collection=self.parent_collection,
87
+ auth_group=auth_group
88
+ )
89
+
90
+ request = self.factory.get('/')
91
+ request.user = self.user
92
+ result = Collection.objects.request_user_has_access(request)
93
+ assert self.parent_collection in result
@@ -0,0 +1,100 @@
1
+ from __future__ import annotations
2
+
3
+ from django.contrib.auth.models import Permission
4
+ from django.contrib.contenttypes.models import ContentType
5
+ from django.test import RequestFactory
6
+
7
+ from django_spire.auth.user.tests.factories import create_user
8
+ from django_spire.core.tests.test_cases import BaseTestCase
9
+ from django_spire.knowledge.collection.models import Collection, CollectionGroup
10
+ from django_spire.knowledge.collection.tests.factories import (
11
+ create_test_auth_group,
12
+ create_test_collection,
13
+ create_test_collection_group,
14
+ )
15
+
16
+
17
+ class CollectionGroupFactoryServiceTests(BaseTestCase):
18
+ def setUp(self):
19
+ super().setUp()
20
+ self.factory = RequestFactory()
21
+ self.user = create_user(username='test_factory_user')
22
+ self.collection = create_test_collection()
23
+ self.auth_group1 = create_test_auth_group(name='Group 1')
24
+ self.auth_group2 = create_test_auth_group(name='Group 2')
25
+
26
+ content_type = ContentType.objects.get_for_model(Collection)
27
+ self.change_groups_permission = Permission.objects.get(
28
+ codename='can_change_collection_groups',
29
+ content_type=content_type
30
+ )
31
+
32
+ def _refresh_user(self):
33
+ self.user = type(self.user).objects.get(pk=self.user.pk)
34
+
35
+ def test_replace_groups_without_permission(self):
36
+ request = self.factory.get('/')
37
+ request.user = self.user
38
+
39
+ result = CollectionGroup.services.factory.replace_groups(
40
+ request=request,
41
+ group_pks=[self.auth_group1.pk],
42
+ collection=self.collection
43
+ )
44
+ assert result == []
45
+
46
+ def test_replace_groups_with_permission(self):
47
+ self.user.user_permissions.add(self.change_groups_permission)
48
+ self._refresh_user()
49
+
50
+ request = self.factory.get('/')
51
+ request.user = self.user
52
+
53
+ result = CollectionGroup.services.factory.replace_groups(
54
+ request=request,
55
+ group_pks=[self.auth_group1.pk, self.auth_group2.pk],
56
+ collection=self.collection
57
+ )
58
+ assert len(result) == 2
59
+ assert self.collection.groups.count() == 2
60
+
61
+ def test_replace_groups_removes_old(self):
62
+ self.user.user_permissions.add(self.change_groups_permission)
63
+ self._refresh_user()
64
+
65
+ create_test_collection_group(
66
+ collection=self.collection,
67
+ auth_group=self.auth_group1
68
+ )
69
+
70
+ request = self.factory.get('/')
71
+ request.user = self.user
72
+
73
+ CollectionGroup.services.factory.replace_groups(
74
+ request=request,
75
+ group_pks=[self.auth_group2.pk],
76
+ collection=self.collection
77
+ )
78
+
79
+ assert self.collection.groups.count() == 1
80
+ assert self.collection.groups.first().auth_group == self.auth_group2
81
+
82
+ def test_replace_groups_with_none_deletes_all(self):
83
+ self.user.user_permissions.add(self.change_groups_permission)
84
+ self._refresh_user()
85
+
86
+ create_test_collection_group(
87
+ collection=self.collection,
88
+ auth_group=self.auth_group1
89
+ )
90
+
91
+ request = self.factory.get('/')
92
+ request.user = self.user
93
+
94
+ CollectionGroup.services.factory.replace_groups(
95
+ request=request,
96
+ group_pks=None,
97
+ collection=self.collection
98
+ )
99
+
100
+ assert self.collection.groups.count() == 0