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
@@ -3,11 +3,9 @@ from __future__ import annotations
3
3
  import json
4
4
 
5
5
  from django_spire.knowledge.entry.version.block.choices import BlockTypeChoices
6
- from django_spire.knowledge.entry.version.block.data.text_data import \
7
- TextEditorBlockData
6
+ from django_spire.knowledge.entry.version.block.data.text_data import TextEditorBlockData
8
7
  from django_spire.knowledge.entry.version.block.models import EntryVersionBlock
9
- from django_spire.knowledge.entry.version.tests.factories import \
10
- create_test_entry_version
8
+ from django_spire.knowledge.entry.version.tests.factories import create_test_entry_version
11
9
 
12
10
 
13
11
  def create_test_block_form_data(**kwargs) -> dict:
@@ -18,25 +16,21 @@ def create_test_block_form_data(**kwargs) -> dict:
18
16
  'data': {'text': 'test text'},
19
17
  'tunes': {}
20
18
  }
21
-
22
19
  data.update(kwargs)
23
20
  return data
24
21
 
25
22
 
26
23
  def create_test_version_block(**kwargs) -> EntryVersionBlock:
27
24
  data = {
28
- 'version': create_test_entry_version(),
25
+ 'version': kwargs.pop('version', None) or create_test_entry_version(),
29
26
  'order': 1,
30
27
  'type': BlockTypeChoices.TEXT,
31
28
  '_block_data': json.dumps({}),
32
29
  '_text_data': ''
33
30
  }
34
-
35
31
  data.update(kwargs)
36
32
  version_block = EntryVersionBlock.objects.create(**data)
37
- version_block.editor_js_block_data = TextEditorBlockData(
38
- text='',
39
- )
33
+ version_block.editor_js_block_data = TextEditorBlockData(text='')
40
34
  version_block.save()
41
35
 
42
36
  return version_block
@@ -0,0 +1,56 @@
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.block.data.list.choices import (
6
+ ListEditorBlockDataStyle,
7
+ OrderedListCounterType,
8
+ )
9
+
10
+
11
+ class BlockTypeChoicesTests(BaseTestCase):
12
+ def test_text_value(self):
13
+ assert BlockTypeChoices.TEXT == 'text'
14
+
15
+ def test_heading_value(self):
16
+ assert BlockTypeChoices.HEADING == 'heading'
17
+
18
+ def test_list_value(self):
19
+ assert BlockTypeChoices.LIST == 'list'
20
+
21
+ def test_choices_count(self):
22
+ assert len(BlockTypeChoices.choices) == 3
23
+
24
+
25
+ class ListEditorBlockDataStyleTests(BaseTestCase):
26
+ def test_unordered_value(self):
27
+ assert ListEditorBlockDataStyle.UNORDERED == 'unordered'
28
+
29
+ def test_ordered_value(self):
30
+ assert ListEditorBlockDataStyle.ORDERED == 'ordered'
31
+
32
+ def test_checklist_value(self):
33
+ assert ListEditorBlockDataStyle.CHECKLIST == 'checklist'
34
+
35
+ def test_choices_count(self):
36
+ assert len(ListEditorBlockDataStyle.choices) == 3
37
+
38
+
39
+ class OrderedListCounterTypeTests(BaseTestCase):
40
+ def test_numeric_value(self):
41
+ assert OrderedListCounterType.NUMERIC == 'numeric'
42
+
43
+ def test_upper_roman_value(self):
44
+ assert OrderedListCounterType.UPPER_ROMAN == 'upper-roman'
45
+
46
+ def test_lower_roman_value(self):
47
+ assert OrderedListCounterType.LOWER_ROMAN == 'lower-roman'
48
+
49
+ def test_upper_alpha_value(self):
50
+ assert OrderedListCounterType.UPPER_ALPHA == 'upper-alpha'
51
+
52
+ def test_lower_alpha_value(self):
53
+ assert OrderedListCounterType.LOWER_ALPHA == 'lower-alpha'
54
+
55
+ def test_choices_count(self):
56
+ assert len(OrderedListCounterType.choices) == 5
@@ -0,0 +1,90 @@
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.heading_data import HeadingEditorBlockData
5
+ from django_spire.knowledge.entry.version.block.data.list.choices import ListEditorBlockDataStyle
6
+ from django_spire.knowledge.entry.version.block.data.list.data import ListEditorBlockData
7
+ from django_spire.knowledge.entry.version.block.data.text_data import TextEditorBlockData
8
+
9
+
10
+ class TextEditorBlockDataTests(BaseTestCase):
11
+ def test_render_to_text(self):
12
+ data = TextEditorBlockData(text='Hello World')
13
+ result = data.render_to_text()
14
+ assert 'Hello World' in result
15
+
16
+ def test_render_to_text_with_html(self):
17
+ data = TextEditorBlockData(text='<b>Bold</b> text')
18
+ result = data.render_to_text()
19
+ assert 'Bold' in result
20
+
21
+
22
+ class HeadingEditorBlockDataTests(BaseTestCase):
23
+ def test_render_to_text_h1(self):
24
+ data = HeadingEditorBlockData(text='Title', level=1)
25
+ result = data.render_to_text()
26
+ assert '# Title' in result
27
+
28
+ def test_render_to_text_h2(self):
29
+ data = HeadingEditorBlockData(text='Subtitle', level=2)
30
+ result = data.render_to_text()
31
+ assert '## Subtitle' in result
32
+
33
+ def test_render_to_text_h3(self):
34
+ data = HeadingEditorBlockData(text='Section', level=3)
35
+ result = data.render_to_text()
36
+ assert '### Section' in result
37
+
38
+
39
+ class ListEditorBlockDataTests(BaseTestCase):
40
+ def test_render_to_text_unordered(self):
41
+ data = ListEditorBlockData(
42
+ style=ListEditorBlockDataStyle.UNORDERED,
43
+ items=[
44
+ {'content': 'Item 1', 'items': []},
45
+ {'content': 'Item 2', 'items': []},
46
+ ]
47
+ )
48
+ result = data.render_to_text()
49
+ assert '- Item 1' in result
50
+ assert '- Item 2' in result
51
+
52
+ def test_render_to_text_ordered(self):
53
+ data = ListEditorBlockData(
54
+ style=ListEditorBlockDataStyle.ORDERED,
55
+ meta={'start': 1},
56
+ items=[
57
+ {'content': 'First', 'items': [], 'meta': {'start': 1}},
58
+ {'content': 'Second', 'items': [], 'meta': {'start': 1}},
59
+ ]
60
+ )
61
+ result = data.render_to_text()
62
+ assert '1. First' in result
63
+
64
+ def test_render_to_text_checklist(self):
65
+ data = ListEditorBlockData(
66
+ style=ListEditorBlockDataStyle.CHECKLIST,
67
+ items=[
68
+ {'content': 'Done', 'items': [], 'meta': {'checked': True}},
69
+ {'content': 'Not done', 'items': [], 'meta': {'checked': False}},
70
+ ]
71
+ )
72
+ result = data.render_to_text()
73
+ assert '[X] Done' in result
74
+ assert '[ ] Not done' in result
75
+
76
+ def test_render_to_text_nested(self):
77
+ data = ListEditorBlockData(
78
+ style=ListEditorBlockDataStyle.UNORDERED,
79
+ items=[
80
+ {
81
+ 'content': 'Parent',
82
+ 'items': [
83
+ {'content': 'Child', 'items': []}
84
+ ]
85
+ },
86
+ ]
87
+ )
88
+ result = data.render_to_text()
89
+ assert 'Parent' in result
90
+ assert 'Child' in result
@@ -0,0 +1,37 @@
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.block.data.heading_data import HeadingEditorBlockData
6
+ from django_spire.knowledge.entry.version.block.data.list.data import ListEditorBlockData
7
+ from django_spire.knowledge.entry.version.block.data.maps import (
8
+ EDITOR_JS_BLOCK_DATA_MAP,
9
+ EDITOR_JS_BLOCK_DATA_REVERSE_MAP,
10
+ )
11
+ from django_spire.knowledge.entry.version.block.data.text_data import TextEditorBlockData
12
+
13
+
14
+ class EditorJsBlockDataMapTests(BaseTestCase):
15
+ def test_map_contains_text(self):
16
+ assert BlockTypeChoices.TEXT in EDITOR_JS_BLOCK_DATA_MAP
17
+ assert EDITOR_JS_BLOCK_DATA_MAP[BlockTypeChoices.TEXT] == TextEditorBlockData
18
+
19
+ def test_map_contains_heading(self):
20
+ assert BlockTypeChoices.HEADING in EDITOR_JS_BLOCK_DATA_MAP
21
+ assert EDITOR_JS_BLOCK_DATA_MAP[BlockTypeChoices.HEADING] == HeadingEditorBlockData
22
+
23
+ def test_map_contains_list(self):
24
+ assert BlockTypeChoices.LIST in EDITOR_JS_BLOCK_DATA_MAP
25
+ assert EDITOR_JS_BLOCK_DATA_MAP[BlockTypeChoices.LIST] == ListEditorBlockData
26
+
27
+ def test_reverse_map_contains_text(self):
28
+ assert TextEditorBlockData in EDITOR_JS_BLOCK_DATA_REVERSE_MAP
29
+ assert EDITOR_JS_BLOCK_DATA_REVERSE_MAP[TextEditorBlockData] == BlockTypeChoices.TEXT
30
+
31
+ def test_reverse_map_contains_heading(self):
32
+ assert HeadingEditorBlockData in EDITOR_JS_BLOCK_DATA_REVERSE_MAP
33
+ assert EDITOR_JS_BLOCK_DATA_REVERSE_MAP[HeadingEditorBlockData] == BlockTypeChoices.HEADING
34
+
35
+ def test_reverse_map_contains_list(self):
36
+ assert ListEditorBlockData in EDITOR_JS_BLOCK_DATA_REVERSE_MAP
37
+ assert EDITOR_JS_BLOCK_DATA_REVERSE_MAP[ListEditorBlockData] == BlockTypeChoices.LIST
@@ -0,0 +1,55 @@
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.block.data.heading_data import HeadingEditorBlockData
6
+ from django_spire.knowledge.entry.version.block.data.list.data import ListEditorBlockData
7
+ from django_spire.knowledge.entry.version.block.data.text_data import TextEditorBlockData
8
+ from django_spire.knowledge.entry.version.block.tests.factories import create_test_version_block
9
+
10
+
11
+ class EntryVersionBlockModelTests(BaseTestCase):
12
+ def setUp(self):
13
+ super().setUp()
14
+ self.version_block = create_test_version_block()
15
+
16
+ def test_editor_js_block_data_getter_text(self):
17
+ self.version_block.type = BlockTypeChoices.TEXT
18
+ self.version_block._block_data = {'text': 'Hello'}
19
+ result = self.version_block.editor_js_block_data
20
+ assert isinstance(result, TextEditorBlockData)
21
+ assert result.text == 'Hello'
22
+
23
+ def test_editor_js_block_data_getter_heading(self):
24
+ self.version_block.type = BlockTypeChoices.HEADING
25
+ self.version_block._block_data = {'text': 'Title', 'level': 1}
26
+ result = self.version_block.editor_js_block_data
27
+ assert isinstance(result, HeadingEditorBlockData)
28
+ assert result.text == 'Title'
29
+ assert result.level == 1
30
+
31
+ def test_editor_js_block_data_getter_list(self):
32
+ self.version_block.type = BlockTypeChoices.LIST
33
+ self.version_block._block_data = {
34
+ 'style': 'unordered',
35
+ 'items': [{'content': 'Item 1', 'items': []}]
36
+ }
37
+ result = self.version_block.editor_js_block_data
38
+ assert isinstance(result, ListEditorBlockData)
39
+
40
+ def test_editor_js_block_data_setter(self):
41
+ text_data = TextEditorBlockData(text='New text')
42
+ self.version_block.editor_js_block_data = text_data
43
+ assert self.version_block._block_data == {'text': 'New text'}
44
+ assert 'New text' in self.version_block._text_data
45
+
46
+ def test_update_editor_js_block_data_from_dict(self):
47
+ self.version_block.type = BlockTypeChoices.TEXT
48
+ self.version_block.update_editor_js_block_data_from_dict({'text': 'Updated'})
49
+ assert self.version_block._block_data == {'text': 'Updated'}
50
+
51
+ def test_render_to_text(self):
52
+ self.version_block.type = BlockTypeChoices.TEXT
53
+ self.version_block._block_data = {'text': 'Hello World'}
54
+ result = self.version_block.render_to_text()
55
+ assert 'Hello World' in result
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+
3
+ from django_spire.core.tests.test_cases import BaseTestCase
4
+ from django_spire.knowledge.entry.version.block.models import EntryVersionBlock
5
+ from django_spire.knowledge.entry.version.block.tests.factories import create_test_version_block
6
+ from django_spire.knowledge.entry.version.tests.factories import create_test_entry_version
7
+
8
+
9
+ class EntryVersionBlockQuerySetTests(BaseTestCase):
10
+ def setUp(self):
11
+ super().setUp()
12
+ self.entry_version = create_test_entry_version()
13
+ self.block1 = create_test_version_block(version=self.entry_version, order=0)
14
+ self.block2 = create_test_version_block(version=self.entry_version, order=1)
15
+
16
+ def test_format_for_editor(self):
17
+ result = list(
18
+ EntryVersionBlock.objects
19
+ .filter(version=self.entry_version)
20
+ .format_for_editor()
21
+ )
22
+ assert len(result) == 2
23
+ assert 'id' in result[0]
24
+ assert 'type' in result[0]
25
+ assert 'data' in result[0]
26
+ assert 'tunes' in result[0]
27
+
28
+ def test_format_for_editor_ordering(self):
29
+ result = list(
30
+ EntryVersionBlock.objects
31
+ .filter(version=self.entry_version)
32
+ .format_for_editor()
33
+ )
34
+ assert result[0]['id'] == self.block1.id
35
+ assert result[1]['id'] == self.block2.id
@@ -0,0 +1,65 @@
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.block.models import EntryVersionBlock
6
+ from django_spire.knowledge.entry.version.tests.factories import create_test_entry_version
7
+
8
+
9
+ class EntryVersionBlockFactoryServiceTests(BaseTestCase):
10
+ def setUp(self):
11
+ super().setUp()
12
+ self.entry_version = create_test_entry_version()
13
+
14
+ def test_create_validated_block_text(self):
15
+ block = EntryVersionBlock.services.factory.create_validated_block(
16
+ entry_version=self.entry_version,
17
+ type=BlockTypeChoices.TEXT,
18
+ data={'text': 'Test content'},
19
+ order=0
20
+ )
21
+ assert block.type == BlockTypeChoices.TEXT
22
+ assert block.order == 0
23
+ assert block._block_data == {'text': 'Test content'}
24
+
25
+ def test_create_validated_block_heading(self):
26
+ block = EntryVersionBlock.services.factory.create_validated_block(
27
+ entry_version=self.entry_version,
28
+ type=BlockTypeChoices.HEADING,
29
+ data={'text': 'Title', 'level': 1},
30
+ order=0
31
+ )
32
+ assert block.type == BlockTypeChoices.HEADING
33
+ assert block._block_data['level'] == 1
34
+
35
+ def test_create_validated_block_list(self):
36
+ block = EntryVersionBlock.services.factory.create_validated_block(
37
+ entry_version=self.entry_version,
38
+ type=BlockTypeChoices.LIST,
39
+ data={
40
+ 'style': 'unordered',
41
+ 'items': [{'content': 'Item', 'items': []}]
42
+ },
43
+ order=0
44
+ )
45
+ assert block.type == BlockTypeChoices.LIST
46
+
47
+ def test_create_validated_block_with_tunes(self):
48
+ tunes = {'alignment': 'center'}
49
+ block = EntryVersionBlock.services.factory.create_validated_block(
50
+ entry_version=self.entry_version,
51
+ type=BlockTypeChoices.TEXT,
52
+ data={'text': 'Centered'},
53
+ order=0,
54
+ tunes=tunes
55
+ )
56
+ assert block.tunes == tunes
57
+
58
+ def test_create_validated_block_sets_version(self):
59
+ block = EntryVersionBlock.services.factory.create_validated_block(
60
+ entry_version=self.entry_version,
61
+ type=BlockTypeChoices.TEXT,
62
+ data={'text': 'Test'},
63
+ order=0
64
+ )
65
+ assert block.version == self.entry_version
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.db.models import TextChoices
2
4
 
3
5
 
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING
4
3
  from abc import ABC, abstractmethod
4
+ from typing import TYPE_CHECKING
5
5
 
6
6
 
7
7
  if TYPE_CHECKING:
@@ -7,16 +7,13 @@ from typing import TYPE_CHECKING
7
7
  from markitdown import MarkItDown
8
8
 
9
9
  from django_spire.knowledge.entry.version.consts import MARKDOWN_AI_CHUNK_SIZE
10
- from django_spire.knowledge.entry.version.converters.converter import \
11
- BaseConverter
12
- from django_spire.knowledge.entry.version.block import models
13
- from django_spire.knowledge.entry.version.converters.markdown_converter import \
14
- MarkdownConverter
15
- from django_spire.knowledge.entry.version.intelligence.bots.markdown_format_llm_bot import \
16
- MarkdownFormatLlmBot
10
+ from django_spire.knowledge.entry.version.converters.converter import BaseConverter
11
+ from django_spire.knowledge.entry.version.converters.markdown_converter import MarkdownConverter
12
+ from django_spire.knowledge.entry.version.intelligence.bots.markdown_format_llm_bot import MarkdownFormatLlmBot
17
13
 
18
14
  if TYPE_CHECKING:
19
15
  from django_spire.file.models import File
16
+ from django_spire.knowledge.entry.version.block import models
20
17
 
21
18
 
22
19
  class DocxConverter(BaseConverter):
@@ -1,25 +1,24 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
+ import marko
4
5
  import re
6
+
5
7
  from collections import defaultdict
6
8
  from typing import TYPE_CHECKING
7
9
 
8
- import marko
9
10
  from bs4 import BeautifulSoup
10
11
  from django.core.files.storage import default_storage
11
12
  from markitdown.converters import HtmlConverter
12
13
  from marko.block import Heading, List, ListItem, Paragraph, BlankLine
13
- from marko.element import Element
14
14
 
15
15
  from django_spire.knowledge.entry.version.block import models
16
16
  from django_spire.knowledge.entry.version.block.choices import BlockTypeChoices
17
- from django_spire.knowledge.entry.version.block.data.list.choices import \
18
- ListEditorBlockDataStyle
19
- from django_spire.knowledge.entry.version.converters.converter import \
20
- BaseConverter
17
+ from django_spire.knowledge.entry.version.block.data.list.choices import ListEditorBlockDataStyle
18
+ from django_spire.knowledge.entry.version.converters.converter import BaseConverter
21
19
 
22
20
  if TYPE_CHECKING:
21
+ from marko.element import Element
23
22
  from django_spire.knowledge.entry.version.models import EntryVersion
24
23
  from django_spire.file.models import File
25
24
  from marko.block import BlockElement
@@ -54,8 +53,8 @@ class MarkdownConverter(BaseConverter):
54
53
  return self.convert_markdown_to_blocks(f.read())
55
54
 
56
55
  def convert_markdown_to_blocks(
57
- self,
58
- markdown_content: str
56
+ self,
57
+ markdown_content: str
59
58
  ) -> list[models.EntryVersionBlock]:
60
59
  marko_blocks = marko.parse(markdown_content).children
61
60
 
@@ -124,9 +123,9 @@ class MarkdownConverter(BaseConverter):
124
123
 
125
124
  @classmethod
126
125
  def _marko_list_item_block_to_editor_block_data_dict(
127
- cls,
128
- marko_list_item_block: ListItem,
129
- parent_marko_list_block: List | None = None,
126
+ cls,
127
+ marko_list_item_block: ListItem,
128
+ parent_marko_list_block: List | None = None
130
129
  ):
131
130
  list_item_editor_block_data_kwargs = {
132
131
  'items': [],
@@ -165,6 +164,7 @@ class MarkdownConverter(BaseConverter):
165
164
  list_item_editor_block_data_kwargs['content'] = content
166
165
 
167
166
  list_editor_block_data_style = None
167
+
168
168
  if parent_marko_list_block:
169
169
  # We determine the entire list style from the top level list items for simplicity.
170
170
  # If parent_marko_list_block is present, it means we are processing the top level list.
@@ -182,17 +182,17 @@ class MarkdownConverter(BaseConverter):
182
182
  if isinstance(marko_block, BlankLine):
183
183
  return { 'text': '' }
184
184
 
185
- elif isinstance(marko_block, Paragraph):
185
+ if isinstance(marko_block, Paragraph):
186
186
  editor_block_text_string = cls._remove_outer_html_tags(marko.render(marko_block))
187
187
  return { 'text': editor_block_text_string }
188
188
 
189
- elif isinstance(marko_block, Heading):
189
+ if isinstance(marko_block, Heading):
190
190
  return {
191
191
  'text': cls._remove_outer_html_tags(marko.render(marko_block)),
192
192
  'level': marko_block.level,
193
193
  }
194
194
 
195
- elif isinstance(marko_block, List):
195
+ if isinstance(marko_block, List):
196
196
  list_editor_block_data_dict = {
197
197
  'items': [],
198
198
  'style': ListEditorBlockDataStyle.UNORDERED,
@@ -208,12 +208,12 @@ class MarkdownConverter(BaseConverter):
208
208
 
209
209
  return list_editor_block_data_dict
210
210
 
211
- else:
212
- logging.warning(
213
- f'Unsupported marko block type: {marko_block.__class__.__name__!r}. '
214
- f'Rendering content to html and adding to markdown as a basic paragraph block.'
215
- )
216
- return { 'text': marko.render(marko_block) }
211
+ logging.warning(
212
+ f'Unsupported marko block type: {marko_block.__class__.__name__!r}. '
213
+ f'Rendering content to html and adding to markdown as a basic paragraph block.'
214
+ )
215
+
216
+ return { 'text': marko.render(marko_block) }
217
217
 
218
218
 
219
219
  @classmethod
@@ -1,11 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
- from django_spire.knowledge.entry.version.converters.docx_converter import \
4
- DocxConverter
5
- from django_spire.knowledge.entry.version.converters.markdown_converter import \
6
- MarkdownConverter
3
+ from django_spire.knowledge.entry.version.converters.docx_converter import DocxConverter
4
+ from django_spire.knowledge.entry.version.converters.markdown_converter import MarkdownConverter
5
+
7
6
 
8
7
  FILE_TYPE_CONVERTER_MAP = {
9
8
  'md': MarkdownConverter,
10
9
  'docx': DocxConverter
11
- }
10
+ }
@@ -14,4 +14,4 @@ class EntryVersionQuerySet(HistoryQuerySet):
14
14
  'blocks',
15
15
  queryset=EntryVersionBlock.objects.order_by('order')
16
16
  )
17
- )
17
+ )
@@ -11,8 +11,7 @@ from django_spire.knowledge.entry.models import Entry
11
11
  from django_spire.knowledge.entry.version import models
12
12
 
13
13
  from django_spire.contrib.seeding import DjangoModelSeeder
14
- from django_spire.knowledge.entry.version.block.data.maps import \
15
- EDITOR_JS_BLOCK_DATA_REVERSE_MAP
14
+ from django_spire.knowledge.entry.version.block.data.maps import EDITOR_JS_BLOCK_DATA_REVERSE_MAP
16
15
  from django_spire.knowledge.entry.version.block.models import EntryVersionBlock
17
16
  from django_spire.knowledge.entry.version.block.seeding.constants import SAFETY_BLOCKS
18
17
  from django_spire.knowledge.entry.version.choices import EntryVersionStatusChoices
@@ -25,7 +25,6 @@ class EntryVersionProcessorService(BaseDjangoModelService['EntryVersion']):
25
25
 
26
26
  entry_blocks_to_add = []
27
27
  entry_blocks_to_update = []
28
- entry_blocks_to_delete = []
29
28
 
30
29
  handled_block_ids = []
31
30
 
@@ -54,9 +53,11 @@ class EntryVersionProcessorService(BaseDjangoModelService['EntryVersion']):
54
53
 
55
54
  handled_block_ids.append(block_data['id'])
56
55
 
57
- for entry_block in old_entry_blocks:
58
- if entry_block.id not in handled_block_ids:
59
- entry_blocks_to_delete.append(entry_block.id)
56
+ entry_blocks_to_delete = [
57
+ entry_block.id
58
+ for entry_block in old_entry_blocks
59
+ if entry_block.id not in handled_block_ids
60
+ ]
60
61
 
61
62
  with transaction.atomic():
62
63
  EntryVersionBlock.objects.filter(id__in=entry_blocks_to_delete).delete()
@@ -3,8 +3,7 @@ from __future__ import annotations
3
3
  from typing import TYPE_CHECKING
4
4
 
5
5
  from django_spire.contrib.service import BaseDjangoModelService
6
- from django_spire.knowledge.entry.version.services.processor_service import \
7
- EntryVersionProcessorService
6
+ from django_spire.knowledge.entry.version.services.processor_service import EntryVersionProcessorService
8
7
 
9
8
  if TYPE_CHECKING:
10
9
  from django_spire.knowledge.entry.version.models import EntryVersion
@@ -9,8 +9,8 @@ from django_spire.knowledge.entry.version.models import EntryVersion
9
9
 
10
10
  def create_test_entry_version(**kwargs) -> EntryVersion:
11
11
  data = {
12
- 'entry': create_test_entry(),
13
- 'author': create_user(username=random_string(k=10)),
12
+ 'entry': kwargs.pop('entry', None) or create_test_entry(),
13
+ 'author': kwargs.pop('author', None) or create_user(username=random_string(k=10)),
14
14
  'is_deleted': False,
15
15
  'is_active': True
16
16
  }
@@ -0,0 +1,18 @@
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
+
6
+
7
+ class EntryVersionStatusChoicesTests(BaseTestCase):
8
+ def test_draft_value(self):
9
+ assert EntryVersionStatusChoices.DRAFT == 'draft'
10
+
11
+ def test_published_value(self):
12
+ assert EntryVersionStatusChoices.PUBLISHED == 'published'
13
+
14
+ def test_archived_value(self):
15
+ assert EntryVersionStatusChoices.ARCHIVED == 'archived'
16
+
17
+ def test_choices_count(self):
18
+ assert len(EntryVersionStatusChoices.choices) == 3