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
@@ -7,14 +7,13 @@ from unittest.mock import MagicMock, patch
7
7
  from django_spire.core.tests.test_cases import BaseTestCase
8
8
  from django_spire.knowledge.entry.version.consts import MARKDOWN_AI_CHUNK_SIZE
9
9
  from django_spire.knowledge.entry.version.converters.docx_converter import DocxConverter
10
-
11
- from django_spire.knowledge.entry.version.tests.factories import \
12
- create_test_entry_version
10
+ from django_spire.knowledge.entry.version.tests.factories import create_test_entry_version
13
11
 
14
12
 
15
13
  class DocxConverterTests(BaseTestCase):
16
14
  def setUp(self):
17
15
  super().setUp()
16
+
18
17
  self.test_entry_version = create_test_entry_version()
19
18
 
20
19
  @patch(
@@ -22,8 +21,8 @@ class DocxConverterTests(BaseTestCase):
22
21
  'MarkdownFormatLlmBot.process'
23
22
  )
24
23
  def test_improve_markdown_structure_concurrent_execution(
25
- self,
26
- mock_process_method: MagicMock
24
+ self,
25
+ mock_process_method: MagicMock
27
26
  ):
28
27
  def slow_process(markdown_content: str):
29
28
  time.sleep(0.1)
@@ -40,6 +39,55 @@ class DocxConverterTests(BaseTestCase):
40
39
  markdown_content=''.join(mock_chunks)
41
40
  )
42
41
  end_time = time.time()
43
- self.assertLess(end_time - start_time, 0.3)
44
- self.assertEqual(mock_process_method.call_count, 3)
45
- self.assertEqual(improved_markdown, ''.join(mock_chunks))
42
+ assert end_time - start_time < 0.3
43
+ assert mock_process_method.call_count == 3
44
+ assert improved_markdown == ''.join(mock_chunks)
45
+
46
+ @patch(
47
+ 'django_spire.knowledge.entry.version.converters.docx_converter.'
48
+ 'MarkdownFormatLlmBot.process'
49
+ )
50
+ def test_improve_markdown_structure_returns_content(
51
+ self,
52
+ mock_process_method: MagicMock
53
+ ):
54
+ mock_process_method.side_effect = lambda x: x
55
+ docx_converter = DocxConverter(entry_version=self.test_entry_version)
56
+
57
+ markdown = 'a' * MARKDOWN_AI_CHUNK_SIZE + 'b' * MARKDOWN_AI_CHUNK_SIZE
58
+ result = docx_converter.improve_markdown_structure(markdown_content=markdown)
59
+
60
+ assert len(result) == len(markdown)
61
+ assert mock_process_method.call_count == 2
62
+
63
+ @patch(
64
+ 'django_spire.knowledge.entry.version.converters.docx_converter.'
65
+ 'MarkdownFormatLlmBot.process'
66
+ )
67
+ def test_improve_markdown_structure_handles_exception(
68
+ self,
69
+ mock_process_method: MagicMock
70
+ ):
71
+ original_chunk = 'x' * MARKDOWN_AI_CHUNK_SIZE
72
+ mock_process_method.side_effect = Exception('Test error')
73
+ docx_converter = DocxConverter(entry_version=self.test_entry_version)
74
+
75
+ result = docx_converter.improve_markdown_structure(markdown_content=original_chunk)
76
+ assert result == original_chunk
77
+
78
+ @patch(
79
+ 'django_spire.knowledge.entry.version.converters.docx_converter.'
80
+ 'MarkdownFormatLlmBot.process'
81
+ )
82
+ def test_improve_markdown_structure_single_chunk(
83
+ self,
84
+ mock_process_method: MagicMock
85
+ ):
86
+ mock_process_method.side_effect = lambda x: x
87
+ docx_converter = DocxConverter(entry_version=self.test_entry_version)
88
+
89
+ short_content = 'short content'
90
+ result = docx_converter.improve_markdown_structure(markdown_content=short_content)
91
+
92
+ assert result == short_content
93
+ assert mock_process_method.call_count == 1
@@ -0,0 +1,78 @@
1
+ from __future__ import annotations
2
+
3
+ from django_spire.core.tests.test_cases import BaseTestCase
4
+ from django_spire.knowledge.entry.version.block.choices import BlockTypeChoices
5
+ from django_spire.knowledge.entry.version.converters.markdown_converter import MarkdownConverter
6
+ from django_spire.knowledge.entry.version.tests.factories import create_test_entry_version
7
+
8
+
9
+ class MarkdownConverterTests(BaseTestCase):
10
+ def setUp(self):
11
+ super().setUp()
12
+
13
+ self.entry_version = create_test_entry_version()
14
+ self.converter = MarkdownConverter(entry_version=self.entry_version)
15
+
16
+ def test_convert_markdown_to_blocks_paragraph(self):
17
+ markdown = 'This is a paragraph.'
18
+ blocks = self.converter.convert_markdown_to_blocks(markdown)
19
+ assert len(blocks) == 1
20
+ assert blocks[0].type == BlockTypeChoices.TEXT
21
+
22
+ def test_convert_markdown_to_blocks_heading(self):
23
+ markdown = '# Heading 1'
24
+ blocks = self.converter.convert_markdown_to_blocks(markdown)
25
+ assert len(blocks) == 1
26
+ assert blocks[0].type == BlockTypeChoices.HEADING
27
+
28
+ def test_convert_markdown_to_blocks_multiple_headings(self):
29
+ markdown = '# H1\n\n## H2\n\n### H3'
30
+ blocks = self.converter.convert_markdown_to_blocks(markdown)
31
+ heading_blocks = [b for b in blocks if b.type == BlockTypeChoices.HEADING]
32
+ assert len(heading_blocks) == 3
33
+
34
+ def test_convert_markdown_to_blocks_unordered_list(self):
35
+ markdown = '- Item 1\n- Item 2\n- Item 3'
36
+ blocks = self.converter.convert_markdown_to_blocks(markdown)
37
+ list_blocks = [b for b in blocks if b.type == BlockTypeChoices.LIST]
38
+ assert len(list_blocks) == 1
39
+
40
+ def test_convert_markdown_to_blocks_ordered_list(self):
41
+ markdown = '1. First\n2. Second\n3. Third'
42
+ blocks = self.converter.convert_markdown_to_blocks(markdown)
43
+ list_blocks = [b for b in blocks if b.type == BlockTypeChoices.LIST]
44
+ assert len(list_blocks) == 1
45
+
46
+ def test_convert_markdown_to_blocks_mixed_content(self):
47
+ markdown = '# Title\n\nSome text.\n\n- List item'
48
+ blocks = self.converter.convert_markdown_to_blocks(markdown)
49
+ assert len(blocks) >= 3
50
+
51
+ def test_convert_markdown_to_blocks_blank_lines(self):
52
+ markdown = 'Para 1\n\n\n\nPara 2'
53
+ blocks = self.converter.convert_markdown_to_blocks(markdown)
54
+ assert len(blocks) >= 2
55
+
56
+ def test_html_to_markdown(self):
57
+ html = '<p>Hello <b>World</b></p>'
58
+ result = MarkdownConverter.html_to_markdown(html)
59
+ assert 'Hello' in result
60
+ assert 'World' in result
61
+
62
+ def test_convert_markdown_to_blocks_checklist(self):
63
+ markdown = '- [ ] Todo\n- [x] Done'
64
+ blocks = self.converter.convert_markdown_to_blocks(markdown)
65
+ list_blocks = [b for b in blocks if b.type == BlockTypeChoices.LIST]
66
+ assert len(list_blocks) == 1
67
+
68
+ def test_convert_markdown_to_blocks_nested_list(self):
69
+ markdown = '- Parent\n - Child'
70
+ blocks = self.converter.convert_markdown_to_blocks(markdown)
71
+ list_blocks = [b for b in blocks if b.type == BlockTypeChoices.LIST]
72
+ assert len(list_blocks) == 1
73
+
74
+ def test_block_ordering(self):
75
+ markdown = '# H1\n\nParagraph\n\n- List'
76
+ blocks = self.converter.convert_markdown_to_blocks(markdown)
77
+ for i, block in enumerate(blocks):
78
+ assert block.order == i
@@ -0,0 +1,58 @@
1
+ from __future__ import annotations
2
+
3
+ from django_spire.core.tests.test_cases import BaseTestCase
4
+ from django_spire.knowledge.entry.version.block.data.list.choices import OrderedListCounterType
5
+ from django_spire.knowledge.entry.version.block.data.list.meta import (
6
+ ChecklistItemMeta,
7
+ OrderedListItemMeta,
8
+ )
9
+
10
+
11
+ class ChecklistItemMetaTests(BaseTestCase):
12
+ def test_default_unchecked(self):
13
+ meta = ChecklistItemMeta()
14
+ assert meta.checked is False
15
+
16
+ def test_checked_true(self):
17
+ meta = ChecklistItemMeta(checked=True)
18
+ assert meta.checked is True
19
+
20
+ def test_checked_false(self):
21
+ meta = ChecklistItemMeta(checked=False)
22
+ assert meta.checked is False
23
+
24
+
25
+ class OrderedListItemMetaTests(BaseTestCase):
26
+ def test_default_values(self):
27
+ meta = OrderedListItemMeta()
28
+ assert meta.start is None
29
+ assert meta.counterType is None
30
+
31
+ def test_with_start(self):
32
+ meta = OrderedListItemMeta(start=5)
33
+ assert meta.start == 5
34
+
35
+ def test_with_counter_type_numeric(self):
36
+ meta = OrderedListItemMeta(counterType=OrderedListCounterType.NUMERIC)
37
+ assert meta.counterType == OrderedListCounterType.NUMERIC
38
+
39
+ def test_with_counter_type_upper_roman(self):
40
+ meta = OrderedListItemMeta(counterType=OrderedListCounterType.UPPER_ROMAN)
41
+ assert meta.counterType == OrderedListCounterType.UPPER_ROMAN
42
+
43
+ def test_with_counter_type_lower_roman(self):
44
+ meta = OrderedListItemMeta(counterType=OrderedListCounterType.LOWER_ROMAN)
45
+ assert meta.counterType == OrderedListCounterType.LOWER_ROMAN
46
+
47
+ def test_with_counter_type_upper_alpha(self):
48
+ meta = OrderedListItemMeta(counterType=OrderedListCounterType.UPPER_ALPHA)
49
+ assert meta.counterType == OrderedListCounterType.UPPER_ALPHA
50
+
51
+ def test_with_counter_type_lower_alpha(self):
52
+ meta = OrderedListItemMeta(counterType=OrderedListCounterType.LOWER_ALPHA)
53
+ assert meta.counterType == OrderedListCounterType.LOWER_ALPHA
54
+
55
+ def test_with_all_values(self):
56
+ meta = OrderedListItemMeta(start=10, counterType=OrderedListCounterType.NUMERIC)
57
+ assert meta.start == 10
58
+ assert meta.counterType == OrderedListCounterType.NUMERIC
@@ -0,0 +1,23 @@
1
+ from __future__ import annotations
2
+
3
+ from django_spire.core.tests.test_cases import BaseTestCase
4
+ from django_spire.knowledge.entry.version.choices import EntryVersionStatusChoices
5
+ from django_spire.knowledge.entry.version.tests.factories import create_test_entry_version
6
+
7
+
8
+ class EntryVersionModelTests(BaseTestCase):
9
+ def setUp(self):
10
+ super().setUp()
11
+ self.entry_version = create_test_entry_version()
12
+
13
+ def test_is_published_false_when_draft(self):
14
+ self.entry_version.status = EntryVersionStatusChoices.DRAFT
15
+ assert self.entry_version.is_published() is False
16
+
17
+ def test_is_published_true_when_published(self):
18
+ self.entry_version.status = EntryVersionStatusChoices.PUBLISHED
19
+ assert self.entry_version.is_published() is True
20
+
21
+ def test_is_published_false_when_archived(self):
22
+ self.entry_version.status = EntryVersionStatusChoices.ARCHIVED
23
+ assert self.entry_version.is_published() is False
@@ -0,0 +1,26 @@
1
+ from __future__ import annotations
2
+
3
+ from django_spire.core.tests.test_cases import BaseTestCase
4
+ from django_spire.knowledge.entry.version.models import EntryVersion
5
+ from django_spire.knowledge.entry.version.tests.factories import create_test_entry_version
6
+ from django_spire.knowledge.entry.version.block.tests.factories import create_test_version_block
7
+
8
+
9
+ class EntryVersionQuerySetTests(BaseTestCase):
10
+ def setUp(self):
11
+ super().setUp()
12
+ self.entry_version = create_test_entry_version()
13
+
14
+ def test_prefetch_blocks(self):
15
+ create_test_version_block(version=self.entry_version)
16
+ result = EntryVersion.objects.prefetch_blocks().get(pk=self.entry_version.pk)
17
+ assert result.blocks.count() == 1
18
+
19
+ def test_prefetch_blocks_ordering(self):
20
+ create_test_version_block(version=self.entry_version, order=2)
21
+ create_test_version_block(version=self.entry_version, order=1)
22
+
23
+ result = EntryVersion.objects.prefetch_blocks().get(pk=self.entry_version.pk)
24
+ blocks = list(result.blocks.all())
25
+
26
+ assert blocks[0].order < blocks[1].order
@@ -0,0 +1,62 @@
1
+ from __future__ import annotations
2
+
3
+ from django_spire.core.tests.test_cases import BaseTestCase
4
+ from django_spire.knowledge.entry.version.choices import EntryVersionStatusChoices
5
+ from django_spire.knowledge.entry.version.block.models import EntryVersionBlock
6
+ from django_spire.knowledge.entry.version.block.tests.factories import (
7
+ create_test_block_form_data,
8
+ create_test_version_block,
9
+ )
10
+ from django_spire.knowledge.entry.version.tests.factories import create_test_entry_version
11
+
12
+
13
+ class EntryVersionProcessorServiceTests(BaseTestCase):
14
+ def setUp(self):
15
+ super().setUp()
16
+ self.entry_version = create_test_entry_version()
17
+
18
+ def test_publish(self):
19
+ self.entry_version.services.processor.publish()
20
+ self.entry_version.refresh_from_db()
21
+ assert self.entry_version.status == EntryVersionStatusChoices.PUBLISHED
22
+ assert self.entry_version.published_datetime is not None
23
+
24
+ def test_add_update_delete_blocks_add_new(self):
25
+ block_data = create_test_block_form_data(id='new_block_123')
26
+ self.entry_version.services.processor.add_update_delete_blocks([block_data])
27
+ assert EntryVersionBlock.objects.filter(version=self.entry_version).count() == 1
28
+
29
+ def test_add_update_delete_blocks_update_existing(self):
30
+ existing_block = create_test_version_block(version=self.entry_version)
31
+ block_data = create_test_block_form_data(
32
+ id=existing_block.id,
33
+ data={'text': 'updated text'}
34
+ )
35
+ self.entry_version.services.processor.add_update_delete_blocks([block_data])
36
+
37
+ existing_block.refresh_from_db()
38
+ assert existing_block._block_data['text'] == 'updated text'
39
+
40
+ def test_add_update_delete_blocks_delete_missing(self):
41
+ existing_block = create_test_version_block(version=self.entry_version)
42
+ self.entry_version.services.processor.add_update_delete_blocks([])
43
+ assert not EntryVersionBlock.objects.filter(pk=existing_block.pk).exists()
44
+
45
+ def test_add_update_delete_blocks_mixed_operations(self):
46
+ existing_block = create_test_version_block(version=self.entry_version)
47
+ block_to_delete = create_test_version_block(version=self.entry_version, order=2)
48
+
49
+ block_data_update = create_test_block_form_data(
50
+ id=existing_block.id,
51
+ order=0,
52
+ data={'text': 'updated'}
53
+ )
54
+ block_data_new = create_test_block_form_data(id='new_block', order=1)
55
+
56
+ self.entry_version.services.processor.add_update_delete_blocks([
57
+ block_data_update,
58
+ block_data_new
59
+ ])
60
+
61
+ assert EntryVersionBlock.objects.filter(version=self.entry_version).count() == 2
62
+ assert not EntryVersionBlock.objects.filter(pk=block_to_delete.pk).exists()
@@ -5,23 +5,22 @@ import json
5
5
  from django.urls import reverse
6
6
 
7
7
  from django_spire.core.tests.test_cases import BaseTestCase
8
+ from django_spire.knowledge.collection.tests.factories import create_test_collection
8
9
  from django_spire.knowledge.entry.tests.factories import create_test_entry
9
- from django_spire.knowledge.entry.version.block.tests.factories import \
10
- create_test_block_form_data
11
- from django_spire.knowledge.entry.version.tests.factories import \
12
- create_test_entry_version
10
+ from django_spire.knowledge.entry.version.block.tests.factories import create_test_block_form_data
11
+ from django_spire.knowledge.entry.version.tests.factories import create_test_entry_version
13
12
 
14
13
 
15
14
  class EntryVersionJsonUrlsTests(BaseTestCase):
16
15
  def setUp(self):
17
16
  super().setUp()
18
-
19
- self.test_entry = create_test_entry()
17
+ self.collection = create_test_collection()
18
+ self.test_entry = create_test_entry(collection=self.collection)
20
19
  self.test_entry_version = create_test_entry_version(entry=self.test_entry)
21
20
  self.test_entry.current_version = self.test_entry_version
22
21
  self.test_entry.save()
23
22
 
24
- def test_save_version_blocks_view(self):
23
+ def test_update_blocks_view(self):
25
24
  response = self.client.post(
26
25
  reverse(
27
26
  'django_spire:knowledge:entry:version:json:update_blocks',
@@ -30,5 +29,25 @@ class EntryVersionJsonUrlsTests(BaseTestCase):
30
29
  data=json.dumps([create_test_block_form_data()]),
31
30
  content_type='application/json'
32
31
  )
32
+ assert response.status_code == 200
33
33
 
34
- self.assertEqual(response.status_code, 200)
34
+ def test_update_blocks_view_empty_list(self):
35
+ response = self.client.post(
36
+ reverse(
37
+ 'django_spire:knowledge:entry:version:json:update_blocks',
38
+ kwargs={'pk': self.test_entry_version.pk}
39
+ ),
40
+ data=json.dumps([]),
41
+ content_type='application/json'
42
+ )
43
+ assert response.status_code == 200
44
+
45
+ def test_update_entry_from_version_view(self):
46
+ response = self.client.post(
47
+ reverse(
48
+ 'django_spire:knowledge:entry:version:json:update_entry_from_version',
49
+ kwargs={'pk': self.test_entry_version.pk}
50
+ ),
51
+ content_type='application/json'
52
+ )
53
+ assert response.status_code == 200
@@ -3,28 +3,35 @@ from __future__ import annotations
3
3
  from django.urls import reverse
4
4
 
5
5
  from django_spire.core.tests.test_cases import BaseTestCase
6
+ from django_spire.knowledge.collection.tests.factories import create_test_collection
6
7
  from django_spire.knowledge.entry.tests.factories import create_test_entry
7
- from django_spire.knowledge.entry.version.tests.factories import \
8
- create_test_entry_version
8
+ from django_spire.knowledge.entry.version.tests.factories import create_test_entry_version
9
9
 
10
10
 
11
11
  class EntryVersionPageUrlsTests(BaseTestCase):
12
12
  def setUp(self):
13
13
  super().setUp()
14
14
 
15
- self.test_entry = create_test_entry()
16
- self.test_entry_version = create_test_entry_version(
17
- entry=self.test_entry
18
- )
15
+ self.collection = create_test_collection()
16
+ self.test_entry = create_test_entry(collection=self.collection)
17
+ self.test_entry_version = create_test_entry_version(entry=self.test_entry)
19
18
  self.test_entry.current_version = self.test_entry_version
20
19
  self.test_entry.save()
21
20
 
22
- def test_detail_view_url_path(self):
21
+ def test_editor_view_url_path(self):
23
22
  response = self.client.get(
24
23
  reverse(
25
24
  'django_spire:knowledge:entry:version:page:editor',
26
25
  kwargs={'pk': self.test_entry_version.pk}
27
26
  )
28
27
  )
28
+ assert response.status_code == 200
29
29
 
30
- self.assertEqual(response.status_code, 200)
30
+ def test_editor_view_with_edit_mode(self):
31
+ response = self.client.get(
32
+ reverse(
33
+ 'django_spire:knowledge:entry:version:page:editor',
34
+ kwargs={'pk': self.test_entry_version.pk}
35
+ ) + '?view_mode=edit'
36
+ )
37
+ assert response.status_code == 200
@@ -0,0 +1,38 @@
1
+ from __future__ import annotations
2
+
3
+ from django.urls import reverse
4
+
5
+ from django_spire.core.tests.test_cases import BaseTestCase
6
+ from django_spire.knowledge.collection.tests.factories import create_test_collection
7
+ from django_spire.knowledge.entry.tests.factories import create_test_entry
8
+ from django_spire.knowledge.entry.version.choices import EntryVersionStatusChoices
9
+ from django_spire.knowledge.entry.version.tests.factories import create_test_entry_version
10
+
11
+
12
+ class EntryVersionRedirectUrlsTests(BaseTestCase):
13
+ def setUp(self):
14
+ super().setUp()
15
+ self.collection = create_test_collection()
16
+ self.test_entry = create_test_entry(collection=self.collection)
17
+ self.test_entry_version = create_test_entry_version(entry=self.test_entry)
18
+ self.test_entry.current_version = self.test_entry_version
19
+ self.test_entry.save()
20
+
21
+ def test_publish_view_redirects(self):
22
+ response = self.client.post(
23
+ reverse(
24
+ 'django_spire:knowledge:entry:version:redirect:publish',
25
+ kwargs={'pk': self.test_entry_version.pk}
26
+ )
27
+ )
28
+ assert response.status_code == 302
29
+
30
+ def test_publish_view_updates_status(self):
31
+ self.client.post(
32
+ reverse(
33
+ 'django_spire:knowledge:entry:version:redirect:publish',
34
+ kwargs={'pk': self.test_entry_version.pk}
35
+ )
36
+ )
37
+ self.test_entry_version.refresh_from_db()
38
+ assert self.test_entry_version.status == EntryVersionStatusChoices.PUBLISHED
@@ -1,5 +1,8 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.urls import include, path
2
4
 
5
+
3
6
  app_name = 'version'
4
7
 
5
8
  urlpatterns = [
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.urls import path
2
4
 
3
5
  from django_spire.knowledge.entry.version.views import json_views
@@ -5,7 +7,6 @@ from django_spire.knowledge.entry.version.views import json_views
5
7
 
6
8
  app_name = 'json'
7
9
 
8
-
9
10
  urlpatterns = [
10
11
  path(
11
12
  '<int:pk>/update_blocks/',
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.urls import path
2
4
 
3
5
  from django_spire.knowledge.entry.version.views import page_views
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.urls import path
2
4
 
3
5
  from django_spire.knowledge.entry.version.views import redirect_views
@@ -2,7 +2,8 @@ from __future__ import annotations
2
2
 
3
3
  import json
4
4
 
5
- from django.core.handlers.wsgi import WSGIRequest
5
+ from typing import TYPE_CHECKING
6
+
6
7
  from django.http import JsonResponse
7
8
  from django.shortcuts import get_object_or_404
8
9
 
@@ -10,6 +11,9 @@ from django_spire.auth.controller.controller import AppAuthController
10
11
  from django_spire.core.decorators import valid_ajax_request_required
11
12
  from django_spire.knowledge.entry.version.models import EntryVersion
12
13
 
14
+ if TYPE_CHECKING:
15
+ from django.core.handlers.wsgi import WSGIRequest
16
+
13
17
 
14
18
  @valid_ajax_request_required
15
19
  @AppAuthController('knowledge').permission_required('can_change')
@@ -2,9 +2,9 @@ from __future__ import annotations
2
2
 
3
3
  import json
4
4
 
5
- from django.core.handlers.wsgi import WSGIRequest
5
+ from typing import TYPE_CHECKING
6
+
6
7
  from django.shortcuts import get_object_or_404
7
- from django.template.response import TemplateResponse
8
8
  from django.urls import reverse
9
9
 
10
10
  from django_spire.auth.controller.controller import AppAuthController
@@ -12,6 +12,12 @@ from django_spire.contrib.generic_views import portal_views
12
12
  from django_spire.knowledge.collection.models import Collection
13
13
  from django_spire.knowledge.entry.version.models import EntryVersion
14
14
 
15
+ if TYPE_CHECKING:
16
+ from django.core.handlers.wsgi import WSGIRequest
17
+ from django.template.response import TemplateResponse
18
+
19
+ from django_spire.contrib.breadcrumb.breadcrumbs import Breadcrumbs
20
+
15
21
 
16
22
  @AppAuthController('knowledge').permission_required('can_view')
17
23
  def editor_view(request: WSGIRequest, pk: int) -> TemplateResponse:
@@ -21,11 +27,12 @@ def editor_view(request: WSGIRequest, pk: int) -> TemplateResponse:
21
27
  top_level_collection = entry.top_level_collection
22
28
  version_blocks = entry_version.blocks.format_for_editor()
23
29
 
24
- def breadcrumbs_func(breadcrumbs):
30
+ def breadcrumbs_func(breadcrumbs: Breadcrumbs):
25
31
  breadcrumbs.add_breadcrumb(
26
32
  name='Knowledge',
27
33
  href=reverse('django_spire:knowledge:page:home')
28
34
  )
35
+
29
36
  breadcrumbs.add_base_breadcrumb(entry)
30
37
 
31
38
  return portal_views.detail_view(
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
- from django.core.handlers.wsgi import WSGIRequest
3
+ from typing import TYPE_CHECKING
4
+
4
5
  from django.http import HttpResponseRedirect
5
6
  from django.shortcuts import get_object_or_404
6
7
  from django.urls import reverse
@@ -8,6 +9,9 @@ from django.urls import reverse
8
9
  from django_spire.auth.controller.controller import AppAuthController
9
10
  from django_spire.knowledge.entry.version.models import EntryVersion
10
11
 
12
+ if TYPE_CHECKING:
13
+ from django.core.handlers.wsgi import WSGIRequest
14
+
11
15
 
12
16
  @AppAuthController('knowledge').permission_required('can_change')
13
17
  def publish_view(request: WSGIRequest, pk: int) -> HttpResponseRedirect:
@@ -1,8 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  import django_glue as dg
2
4
 
3
- from django.core.handlers.wsgi import WSGIRequest
5
+ from typing import TYPE_CHECKING
6
+
4
7
  from django.http import HttpResponseRedirect
5
- from django.template.response import TemplateResponse
6
8
  from django.urls import reverse
7
9
 
8
10
  from django_spire.auth.controller.controller import AppAuthController
@@ -16,12 +18,16 @@ from django_spire.knowledge.entry.models import Entry
16
18
  from django_spire.knowledge.entry.forms import EntryForm, EntryFilesForm
17
19
  from django_spire.knowledge.entry.version.maps import FILE_TYPE_CONVERTER_MAP
18
20
 
21
+ if TYPE_CHECKING:
22
+ from django.core.handlers.wsgi import WSGIRequest
23
+ from django.template.response import TemplateResponse
24
+
19
25
 
20
26
  @AppAuthController('knowledge').permission_required('can_add')
21
27
  def form_view(
22
- request: WSGIRequest,
23
- collection_pk: int,
24
- pk: int = 0,
28
+ request: WSGIRequest,
29
+ collection_pk: int,
30
+ pk: int = 0
25
31
  ) -> TemplateResponse | HttpResponseRedirect:
26
32
  entry = get_object_or_null_obj(Entry, pk=pk)
27
33
  collection = Collection.objects.get(pk=collection_pk)
@@ -70,8 +76,8 @@ def form_view(
70
76
 
71
77
  @AppAuthController('knowledge').permission_required('can_add')
72
78
  def import_form_view(
73
- request: WSGIRequest,
74
- collection_pk: int
79
+ request: WSGIRequest,
80
+ collection_pk: int
75
81
  ) -> TemplateResponse | HttpResponseRedirect:
76
82
  dg.glue_query_set(
77
83
  request,
@@ -88,13 +94,15 @@ def import_form_view(
88
94
  related_field=None,
89
95
  app_name='knowledge'
90
96
  )
97
+
91
98
  file_objects = file_uploader.upload(request.FILES.getlist('import_files'))
92
99
 
93
- _ = Entry.services.factory.create_from_files(
100
+ Entry.services.factory.create_from_files(
94
101
  author=request.user,
95
102
  collection=Collection.objects.get(pk=collection_pk),
96
103
  files=file_objects
97
104
  )
105
+
98
106
  return HttpResponseRedirect(
99
107
  reverse(
100
108
  'django_spire:knowledge:entry:template:file_list',
@@ -1,13 +1,15 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import json
4
+
4
5
  from typing import TYPE_CHECKING
5
6
 
7
+ from django.http import JsonResponse
8
+
6
9
  from django_spire.core.shortcuts import get_object_or_null_obj
7
10
  from django_spire.core.decorators import valid_ajax_request_required
8
11
  from django_spire.knowledge.collection.models import Collection
9
12
  from django_spire.knowledge.entry.models import Entry
10
- from django.http import JsonResponse
11
13
 
12
14
  if TYPE_CHECKING:
13
15
  from django.core.handlers.wsgi import WSGIRequest