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
@@ -0,0 +1,93 @@
1
+ from __future__ import annotations
2
+
3
+ from unittest.mock import MagicMock, patch
4
+
5
+ from django.test import RequestFactory, TestCase, override_settings
6
+
7
+ from django_spire.core.context_processors import django_spire, theme_context
8
+
9
+
10
+ class TestDjangoSpireContextProcessor(TestCase):
11
+ def setUp(self) -> None:
12
+ super().setUp()
13
+
14
+ self.factory = RequestFactory()
15
+
16
+ @override_settings(DJANGO_SPIRE_AUTH_CONTROLLERS=[])
17
+ def test_empty_auth_controllers(self) -> None:
18
+ request = self.factory.get('/')
19
+ result = django_spire(request)
20
+
21
+ assert 'DJANGO_SPIRE_VERSION' in result
22
+ assert 'AuthController' in result
23
+ assert result['AuthController'] == {}
24
+
25
+ @override_settings(DJANGO_SPIRE_AUTH_CONTROLLERS=['test_app'])
26
+ @patch('django_spire.core.context_processors.AppAuthController')
27
+ def test_with_auth_controllers(self, mock_controller: MagicMock) -> None:
28
+ mock_instance = MagicMock()
29
+ mock_controller.return_value = mock_instance
30
+
31
+ request = self.factory.get('/')
32
+ result = django_spire(request)
33
+
34
+ assert 'AuthController' in result
35
+ assert 'test_app' in result['AuthController']
36
+ mock_controller.assert_called_once_with('test_app', request=request)
37
+
38
+
39
+ class TestThemeContextProcessor(TestCase):
40
+ def setUp(self) -> None:
41
+ super().setUp()
42
+
43
+ self.factory = RequestFactory()
44
+
45
+ def test_default_theme_applied(self) -> None:
46
+ request = self.factory.get('/')
47
+ request.COOKIES = {}
48
+ result = theme_context(request)
49
+
50
+ assert 'DJANGO_SPIRE_DEFAULT_THEME' in result
51
+ assert 'DJANGO_SPIRE_THEME_COOKIE_NAME' in result
52
+ assert 'DJANGO_SPIRE_THEME_PATH' in result
53
+ assert 'theme' in result
54
+
55
+ @override_settings(DJANGO_SPIRE_DEFAULT_THEME='default-dark')
56
+ def test_custom_default_theme(self) -> None:
57
+ request = self.factory.get('/')
58
+ request.COOKIES = {}
59
+ result = theme_context(request)
60
+
61
+ assert result['DJANGO_SPIRE_DEFAULT_THEME'] == 'default-dark'
62
+
63
+ @override_settings(DJANGO_SPIRE_THEME_PATH='/custom/path/{family}/app-{mode}.css')
64
+ def test_custom_theme_path(self) -> None:
65
+ request = self.factory.get('/')
66
+ request.COOKIES = {}
67
+ result = theme_context(request)
68
+
69
+ assert result['DJANGO_SPIRE_THEME_PATH'] == '/custom/path/{family}/app-{mode}.css'
70
+
71
+ @patch('django_spire.core.context_processors.get_theme_cookie_name')
72
+ @patch('django_spire.core.context_processors.Theme')
73
+ def test_theme_from_cookie(self, mock_theme: MagicMock, mock_cookie_name: MagicMock) -> None:
74
+ mock_cookie_name.return_value = 'theme_cookie'
75
+ mock_theme_instance = MagicMock()
76
+ mock_theme_instance.value = 'custom-dark'
77
+ mock_theme_instance.to_dict.return_value = {'family': 'custom', 'mode': 'dark'}
78
+ mock_theme.from_string.return_value = mock_theme_instance
79
+ mock_theme.get_default.return_value = mock_theme_instance
80
+
81
+ request = self.factory.get('/')
82
+ request.COOKIES = {'theme_cookie': 'custom-dark'}
83
+ result = theme_context(request)
84
+
85
+ assert result['theme'] == {'family': 'custom', 'mode': 'dark'}
86
+
87
+ def test_theme_result_contains_required_keys(self) -> None:
88
+ request = self.factory.get('/')
89
+ request.COOKIES = {}
90
+ result = theme_context(request)
91
+
92
+ assert 'theme' in result
93
+ assert isinstance(result['theme'], dict)
@@ -0,0 +1,95 @@
1
+ from __future__ import annotations
2
+
3
+ import pytest
4
+
5
+ from unittest.mock import MagicMock, patch
6
+
7
+ from django.http import HttpRequest, JsonResponse
8
+ from django.test import RequestFactory, TestCase
9
+
10
+ from django_spire.core.decorators import close_db_connections, valid_ajax_request_required
11
+
12
+
13
+ class TestCloseDbConnections(TestCase):
14
+ @patch('django_spire.core.decorators.connections')
15
+ def test_closes_connections_after_exception(self, mock_connections: MagicMock) -> None:
16
+ @close_db_connections
17
+ def sample_func():
18
+ raise ValueError
19
+
20
+ with pytest.raises(ValueError):
21
+ sample_func()
22
+
23
+ mock_connections.close_all.assert_called_once()
24
+
25
+ @patch('django_spire.core.decorators.connections')
26
+ def test_closes_connections_after_success(self, mock_connections: MagicMock) -> None:
27
+ @close_db_connections
28
+ def sample_func():
29
+ return 'result'
30
+
31
+ result = sample_func()
32
+
33
+ assert result == 'result'
34
+ mock_connections.close_all.assert_called_once()
35
+
36
+ @patch('django_spire.core.decorators.connections')
37
+ def test_preserves_function_metadata(self, _mock_connections: MagicMock) -> None:
38
+ @close_db_connections
39
+ def sample_func():
40
+ """Sample docstring"""
41
+
42
+ assert sample_func.__name__ == 'sample_func'
43
+ assert sample_func.__doc__ == 'Sample docstring'
44
+
45
+
46
+ class TestValidAjaxRequestRequired(TestCase):
47
+ def setUp(self) -> None:
48
+ super().setUp()
49
+
50
+ self.factory = RequestFactory()
51
+
52
+ def test_get_request_returns_error(self) -> None:
53
+ @valid_ajax_request_required
54
+ def sample_view(_request: HttpRequest) -> dict:
55
+ return {'success': True}
56
+
57
+ request = self.factory.get('/test/')
58
+ response = sample_view(request)
59
+
60
+ assert response.status_code == 200
61
+ assert b'error' in response.content
62
+
63
+ def test_post_request_with_json_content_type_succeeds(self) -> None:
64
+ @valid_ajax_request_required
65
+ def sample_view(_request: HttpRequest) -> JsonResponse:
66
+ return JsonResponse({'success': True})
67
+
68
+ request = self.factory.post(
69
+ '/test/',
70
+ data='{}',
71
+ content_type='application/json'
72
+ )
73
+ response = sample_view(request)
74
+
75
+ assert response.status_code == 200
76
+ assert b'success' in response.content
77
+
78
+ def test_post_request_without_json_content_type_passes(self) -> None:
79
+ @valid_ajax_request_required
80
+ def sample_view(_request: HttpRequest) -> JsonResponse:
81
+ return JsonResponse({'success': True})
82
+
83
+ request = self.factory.post('/test/', data={})
84
+ response = sample_view(request)
85
+
86
+ assert response.status_code == 200
87
+ assert b'success' in response.content
88
+
89
+ def test_preserves_function_metadata(self) -> None:
90
+ @valid_ajax_request_required
91
+ def sample_view(_request: HttpRequest) -> None:
92
+ """Sample docstring"""
93
+
94
+ assert sample_view.__name__ == 'sample_view'
95
+ assert sample_view.__doc__ == 'Sample docstring'
@@ -0,0 +1,56 @@
1
+ from __future__ import annotations
2
+
3
+ import pytest
4
+
5
+ from django.test import TestCase
6
+
7
+ from django_spire.utils import (
8
+ app_is_installed,
9
+ get_class_from_string,
10
+ get_class_name_from_class,
11
+ )
12
+
13
+
14
+ class TestAppIsInstalled(TestCase):
15
+ def test_installed_app_returns_true(self) -> None:
16
+ result = app_is_installed('auth')
17
+
18
+ assert result is True
19
+
20
+ def test_not_installed_app_returns_false(self) -> None:
21
+ result = app_is_installed('nonexistent_app')
22
+
23
+ assert result is False
24
+
25
+
26
+ class TestGetClassFromString(TestCase):
27
+ def test_imports_class(self) -> None:
28
+ result = get_class_from_string('django.test.TestCase')
29
+
30
+ assert result is TestCase
31
+
32
+ def test_invalid_class_string_raises_exception(self) -> None:
33
+ with pytest.raises(Exception, match='not a valid class string'):
34
+ get_class_from_string('InvalidString')
35
+
36
+ def test_nonexistent_module_raises_exception(self) -> None:
37
+ with pytest.raises(ModuleNotFoundError):
38
+ get_class_from_string('nonexistent.module.ClassName')
39
+
40
+
41
+ class TestGetClassNameFromClass(TestCase):
42
+ def test_returns_full_class_path(self) -> None:
43
+ result = get_class_name_from_class(TestCase)
44
+
45
+ assert result == 'django.test.testcases.TestCase'
46
+
47
+ def test_returns_string(self) -> None:
48
+ result = get_class_name_from_class(TestCase)
49
+
50
+ assert isinstance(result, str)
51
+
52
+ def test_contains_module_and_class_name(self) -> None:
53
+ result = get_class_name_from_class(TestCase)
54
+
55
+ assert 'TestCase' in result
56
+ assert 'django' in result
@@ -0,0 +1,37 @@
1
+ from __future__ import annotations
2
+
3
+ import pytest
4
+
5
+ from django.test import TestCase
6
+
7
+ from django_spire.exceptions import DjangoSpireConfigurationError, DjangoSpireError
8
+
9
+
10
+ class TestDjangoSpireError(TestCase):
11
+ def test_can_be_raised(self) -> None:
12
+ with pytest.raises(DjangoSpireError):
13
+ message = 'Test error'
14
+ raise DjangoSpireError(message)
15
+
16
+ def test_is_exception_subclass(self) -> None:
17
+ assert issubclass(DjangoSpireError, Exception)
18
+
19
+ def test_message(self) -> None:
20
+ error = DjangoSpireError('Test error message')
21
+
22
+ assert str(error) == 'Test error message'
23
+
24
+
25
+ class TestDjangoSpireConfigurationError(TestCase):
26
+ def test_can_be_raised(self) -> None:
27
+ with pytest.raises(DjangoSpireConfigurationError):
28
+ message = 'Test error'
29
+ raise DjangoSpireConfigurationError(message)
30
+
31
+ def test_is_django_spire_error_subclass(self) -> None:
32
+ assert issubclass(DjangoSpireConfigurationError, DjangoSpireError)
33
+
34
+ def test_message(self) -> None:
35
+ error = DjangoSpireConfigurationError('Configuration error message')
36
+
37
+ assert str(error) == 'Configuration error message'
@@ -0,0 +1,54 @@
1
+ from __future__ import annotations
2
+
3
+ from django.db import connection, models
4
+
5
+
6
+ class DummyModel(models.Model):
7
+ name = models.CharField(max_length=255, default='')
8
+
9
+ class Meta:
10
+ app_label = 'django_spire_core'
11
+ db_table = 'django_spire_core_dummymodel'
12
+
13
+ def __str__(self) -> str:
14
+ return 'dummy'
15
+
16
+ @classmethod
17
+ def create_table(cls) -> None:
18
+ vendor = connection.vendor
19
+
20
+ with connection.cursor() as cursor:
21
+ if vendor == 'sqlite':
22
+ query = '''
23
+ CREATE TABLE IF NOT EXISTS django_spire_core_dummymodel (
24
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
25
+ name VARCHAR(255) DEFAULT ''
26
+ )
27
+ '''
28
+ else:
29
+ query = '''
30
+ CREATE TABLE IF NOT EXISTS django_spire_core_dummymodel (
31
+ id SERIAL PRIMARY KEY,
32
+ name VARCHAR(255) DEFAULT ''
33
+ )
34
+ '''
35
+
36
+ cursor.execute(query)
37
+
38
+ @classmethod
39
+ def drop_table(cls) -> None:
40
+ with connection.cursor() as cursor:
41
+ cursor.execute('DROP TABLE IF EXISTS django_spire_core_dummymodel')
42
+
43
+
44
+ class DummyModelMixin:
45
+ @classmethod
46
+ def setUpClass(cls) -> None:
47
+ super().setUpClass()
48
+
49
+ DummyModel.create_table()
50
+
51
+ @classmethod
52
+ def tearDownClass(cls) -> None:
53
+ DummyModel.drop_table()
54
+ super().tearDownClass()
@@ -0,0 +1,45 @@
1
+ from __future__ import annotations
2
+
3
+ from django.test import TestCase
4
+
5
+ from django_spire import settings
6
+
7
+
8
+ class TestDefaultSettings(TestCase):
9
+ def test_ai_chat_routers_contains_spire(self) -> None:
10
+ assert 'SPIRE' in settings.DJANGO_SPIRE_AI_CHAT_ROUTERS
11
+
12
+ def test_ai_default_chat_router(self) -> None:
13
+ assert settings.DJANGO_SPIRE_AI_DEFAULT_CHAT_ROUTER == 'SPIRE'
14
+
15
+ def test_ai_intent_chat_routers_contains_knowledge_search(self) -> None:
16
+ assert 'KNOWLEDGE_SEARCH' in settings.DJANGO_SPIRE_AI_INTENT_CHAT_ROUTERS
17
+
18
+ def test_ai_intent_chat_routers_structure(self) -> None:
19
+ knowledge_search = settings.DJANGO_SPIRE_AI_INTENT_CHAT_ROUTERS['KNOWLEDGE_SEARCH']
20
+
21
+ assert 'INTENT_DESCRIPTION' in knowledge_search
22
+ assert 'REQUIRED_PERMISSION' in knowledge_search
23
+ assert 'CHAT_ROUTER' in knowledge_search
24
+
25
+ def test_ai_persona_name(self) -> None:
26
+ assert settings.DJANGO_SPIRE_AI_PERSONA_NAME == 'AI Assistant'
27
+
28
+ def test_auth_controllers_contains_ai_chat(self) -> None:
29
+ assert 'ai_chat' in settings.DJANGO_SPIRE_AUTH_CONTROLLERS
30
+
31
+ def test_auth_controllers_contains_help_desk(self) -> None:
32
+ assert 'help_desk' in settings.DJANGO_SPIRE_AUTH_CONTROLLERS
33
+
34
+ def test_auth_controllers_contains_knowledge(self) -> None:
35
+ assert 'knowledge' in settings.DJANGO_SPIRE_AUTH_CONTROLLERS
36
+
37
+ def test_auth_controllers_is_dict(self) -> None:
38
+ assert isinstance(settings.DJANGO_SPIRE_AUTH_CONTROLLERS, dict)
39
+
40
+ def test_default_theme(self) -> None:
41
+ assert settings.DJANGO_SPIRE_DEFAULT_THEME == 'default-light'
42
+
43
+ def test_theme_path_contains_placeholders(self) -> None:
44
+ assert '{family}' in settings.DJANGO_SPIRE_THEME_PATH
45
+ assert '{mode}' in settings.DJANGO_SPIRE_THEME_PATH
@@ -0,0 +1,74 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+
5
+ from django.contrib.auth.models import User
6
+ from django.test import RequestFactory
7
+
8
+ from django_spire.core.shortcuts import (
9
+ get_object_or_none,
10
+ get_object_or_null_obj,
11
+ model_object_from_app_label,
12
+ process_request_body
13
+ )
14
+ from django_spire.core.tests.test_cases import BaseTestCase
15
+ from django_spire.core.tests.test_models import DummyModel, DummyModelMixin
16
+
17
+
18
+ class ShortcutsTestCase(DummyModelMixin, BaseTestCase):
19
+ def setUp(self) -> None:
20
+ super().setUp()
21
+
22
+ self.dummy = DummyModel.objects.create(name='test')
23
+ self.user = User.objects.create_user(username='testuser', password='testpass') # noqa: S106
24
+ self.request_factory = RequestFactory()
25
+
26
+ def test_get_object_or_none_found(self) -> None:
27
+ obj = get_object_or_none(DummyModel, pk=self.dummy.pk)
28
+ assert obj == self.dummy
29
+
30
+ def test_get_object_or_none_not_found(self) -> None:
31
+ obj = get_object_or_none(DummyModel, pk=9999)
32
+ assert obj is None
33
+
34
+ def test_get_object_or_null_obj_model_found(self) -> None:
35
+ obj = get_object_or_null_obj(DummyModel, name='test')
36
+ assert obj == self.dummy
37
+
38
+ def test_get_object_or_null_obj_model_not_found(self) -> None:
39
+ obj = get_object_or_null_obj(DummyModel, name='nonexistent')
40
+ assert obj.pk is None
41
+ assert obj.name == ''
42
+
43
+ def test_get_object_or_null_obj_queryset_found(self) -> None:
44
+ obj = get_object_or_null_obj(DummyModel.objects.all(), name='test')
45
+ assert obj == self.dummy
46
+
47
+ def test_get_object_or_null_obj_queryset_not_found(self) -> None:
48
+ obj = get_object_or_null_obj(DummyModel.objects.all(), name='nonexistent')
49
+ assert obj.pk is None
50
+ assert obj.name == ''
51
+
52
+ def test_model_object_from_app_label_contenttype_not_found(self) -> None:
53
+ obj = model_object_from_app_label('nonexistent', 'nonexistent', 1)
54
+ assert obj is None
55
+
56
+ def test_model_object_from_app_label_found(self) -> None:
57
+ obj = model_object_from_app_label('auth', 'user', self.user.pk)
58
+ assert obj == self.user
59
+
60
+ def test_model_object_from_app_label_not_found(self) -> None:
61
+ obj = model_object_from_app_label('auth', 'user', 9999)
62
+ assert obj is None
63
+
64
+ def test_process_request_body(self) -> None:
65
+ data = {'data': {'key': 'value'}}
66
+
67
+ request = self.request_factory.post(
68
+ '/dummy-url/',
69
+ data=json.dumps(data),
70
+ content_type='application/json'
71
+ )
72
+
73
+ result = process_request_body(request)
74
+ assert result == data['data']
@@ -0,0 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ from django.test import TestCase
4
+
5
+ from django_spire import urls
6
+
7
+
8
+ class TestUrls(TestCase):
9
+ def test_app_name_is_django_spire(self) -> None:
10
+ assert urls.app_name == 'django_spire'
11
+
12
+ def test_urlpatterns_is_list(self) -> None:
13
+ assert isinstance(urls.urlpatterns, list)
14
+
15
+ def test_urlpatterns_is_not_empty(self) -> None:
16
+ assert len(urls.urlpatterns) > 0
@@ -0,0 +1,58 @@
1
+ from __future__ import annotations
2
+
3
+ import pytest
4
+
5
+ from django.test import TestCase
6
+
7
+ from django_spire.core.utils import (
8
+ get_callable_from_module_string_and_validate_arguments,
9
+ get_object_from_module_string
10
+ )
11
+
12
+
13
+ class TestGetObjectFromModuleString(TestCase):
14
+ def test_import_class(self) -> None:
15
+ obj = get_object_from_module_string('django.test.TestCase')
16
+ assert obj is TestCase
17
+
18
+ def test_import_function(self) -> None:
19
+ obj = get_object_from_module_string('os.path.join')
20
+ assert callable(obj)
21
+
22
+ def test_import_invalid_module(self) -> None:
23
+ with pytest.raises(ImportError, match='Could not import module'):
24
+ get_object_from_module_string('nonexistent.module.Object')
25
+
26
+ def test_import_module_constant(self) -> None:
27
+ obj = get_object_from_module_string('os.sep')
28
+ assert isinstance(obj, str)
29
+
30
+
31
+ class TestGetCallableFromModuleStringAndValidateArguments(TestCase):
32
+ def test_callable_with_missing_argument(self) -> None:
33
+ with pytest.raises(TypeError, match='missing required argument'):
34
+ get_callable_from_module_string_and_validate_arguments(
35
+ 'json.dumps',
36
+ ['nonexistent_arg']
37
+ )
38
+
39
+ def test_callable_with_valid_arguments(self) -> None:
40
+ callable_ = get_callable_from_module_string_and_validate_arguments(
41
+ 'json.dumps',
42
+ ['obj']
43
+ )
44
+ assert callable(callable_)
45
+
46
+ def test_invalid_module(self) -> None:
47
+ with pytest.raises(ImportError, match='Could not import module'):
48
+ get_callable_from_module_string_and_validate_arguments(
49
+ 'nonexistent.module.func',
50
+ []
51
+ )
52
+
53
+ def test_non_callable_object(self) -> None:
54
+ with pytest.raises(TypeError, match='is not callable'):
55
+ get_callable_from_module_string_and_validate_arguments(
56
+ 'os.sep',
57
+ []
58
+ )
django_spire/core/urls.py CHANGED
@@ -1,6 +1,9 @@
1
- from django.urls import include, path
1
+ from __future__ import annotations
2
+
3
+ from django.urls import path
2
4
  from django.views.generic import TemplateView
3
5
 
6
+
4
7
  app_name = 'core'
5
8
 
6
9
  urlpatterns = [
@@ -1,25 +1,29 @@
1
+ from __future__ import annotations
2
+
1
3
  import inspect
2
- from inspect import Signature
3
- from typing import Callable, Any, Sequence
4
+
5
+ from typing import Any, Callable, Sequence
4
6
 
5
7
 
6
8
  def get_object_from_module_string(module_string: str) -> Any:
7
9
  try:
8
10
  module_string, object_name = module_string.rsplit('.', 1)
9
11
  module = __import__(module_string, fromlist=[object_name])
10
- except ImportError:
11
- raise ImportError(f'Could not import module: {module_string}')
12
+ except ImportError as e:
13
+ message = f'Could not import module: {module_string}'
14
+ raise ImportError(message) from e
12
15
 
13
16
  return getattr(module, object_name)
14
17
 
15
18
  def get_callable_from_module_string_and_validate_arguments(
16
- module_string: str,
17
- valid_args: Sequence[str],
19
+ module_string: str,
20
+ valid_args: Sequence[str]
18
21
  ) -> Callable:
19
22
  callable_ = get_object_from_module_string(module_string)
20
23
 
21
24
  if not callable(callable_):
22
- raise TypeError(f'Object {module_string} is not callable')
25
+ message = f'Object {module_string} is not callable'
26
+ raise TypeError(message)
23
27
 
24
28
  sig = inspect.signature(callable_)
25
29
  params = sig.parameters
@@ -29,4 +33,4 @@ def get_callable_from_module_string_and_validate_arguments(
29
33
  message = f'{callable_.__qualname__} is missing required argument: {name}'
30
34
  raise TypeError(message)
31
35
 
32
- return callable_
36
+ return callable_
@@ -1,2 +1,17 @@
1
- class DjangoSpireException(Exception):
1
+ from __future__ import annotations
2
+
3
+
4
+ class DjangoSpireError(Exception):
5
+ pass
6
+
7
+
8
+ class DjangoSpireConfigurationError(DjangoSpireError):
9
+ pass
10
+
11
+
12
+ class DjangoSpireInvalidClassStringError(DjangoSpireError):
13
+ pass
14
+
15
+
16
+ class DjangoSpireMissingRequiredAppError(DjangoSpireError):
2
17
  pass
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.contrib import admin
2
4
  from django.urls import reverse
3
5
  from django.utils.html import format_html
@@ -21,13 +23,13 @@ class FileAdmin(admin.ModelAdmin):
21
23
  args=[file.object_id]
22
24
  )
23
25
 
24
- return format_html(f'<a href="{url}">{file.content_object}</a>')
26
+ return format_html('<a href="{}">{}</a>', url, file.content_object)
25
27
 
26
28
  return 'No Related Object'
27
29
 
28
30
  content_object_link.short_description = 'Content Object'
29
31
 
30
32
  def file_link(self, file: models.File) -> str:
31
- return format_html(f'<a href="{file.file.url}" download>{file.name}</a>')
33
+ return format_html('<a href="{}" download>{}</a>', file.file.url, file.name)
32
34
 
33
35
  file_link.short_description = 'File Download Link'
django_spire/file/apps.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.apps import AppConfig
2
4
  from django.conf import settings
3
5
 
@@ -15,15 +17,11 @@ class FileConfig(AppConfig):
15
17
 
16
18
  def ready(self) -> None:
17
19
  if not hasattr(settings, 'BASE_FOLDER_NAME'):
18
- raise ValueError(
19
- f'"BASE_FOLDER_NAME" must be set in the django settings when '
20
- f'using "{self.label}".'
21
- )
22
-
23
- elif not isinstance(getattr(settings, 'BASE_FOLDER_NAME'), str):
24
- raise ValueError(
25
- f'"BASE_FOLDER_NAME" must be a string in the django settings when '
26
- f'using "{self.label}".'
27
- )
20
+ message = f'"BASE_FOLDER_NAME" must be set in the django settings when using "{self.label}".'
21
+ raise ValueError(message)
22
+
23
+ if not isinstance(settings.BASE_FOLDER_NAME, str):
24
+ message = f'"BASE_FOLDER_NAME" must be a string in the django settings when using "{self.label}".'
25
+ raise TypeError(message)
28
26
 
29
27
  check_required_apps(self.label)