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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (542) hide show
  1. django_spire/ai/admin.py +11 -11
  2. django_spire/ai/chat/apps.py +1 -0
  3. django_spire/ai/chat/templates/django_spire/ai/chat/widget/dialog_widget.html +1 -1
  4. django_spire/ai/chat/tests/factories.py +15 -0
  5. django_spire/ai/chat/tests/test_controller.py +45 -0
  6. django_spire/ai/chat/tests/test_models.py +301 -0
  7. django_spire/ai/chat/tests/test_prompts.py +48 -0
  8. django_spire/ai/chat/tests/test_responses.py +208 -0
  9. django_spire/ai/chat/tests/test_router/test_base_chat_router.py +66 -6
  10. django_spire/ai/chat/tests/test_router/test_chat_workflow.py +73 -3
  11. django_spire/ai/chat/tests/test_router/test_integration.py +86 -6
  12. django_spire/ai/chat/tests/test_router/test_intent_decoder.py +93 -1
  13. django_spire/ai/chat/tests/test_router/test_message_intel.py +60 -1
  14. django_spire/ai/chat/tests/test_router/test_spire_chat_router.py +110 -0
  15. django_spire/ai/chat/tests/test_urls/test_json_urls.py +202 -1
  16. django_spire/ai/context/tests/__init__.py +0 -0
  17. django_spire/ai/context/tests/test_context.py +188 -0
  18. django_spire/ai/decorators.py +7 -6
  19. django_spire/ai/prompt/tests/test_bots.py +100 -10
  20. django_spire/ai/prompt/tests/test_prompt_intel.py +83 -0
  21. django_spire/ai/prompt/tests/test_prompt_tuning.py +126 -0
  22. django_spire/ai/sms/decorators.py +8 -2
  23. django_spire/ai/sms/tests/test_sms.py +240 -16
  24. django_spire/ai/sms/tests/test_sms_intel.py +42 -0
  25. django_spire/ai/sms/tests/test_webhook.py +155 -7
  26. django_spire/ai/sms/views.py +23 -24
  27. django_spire/ai/tests/test_ai.py +131 -7
  28. django_spire/auth/apps.py +4 -2
  29. django_spire/auth/controller/controller.py +36 -23
  30. django_spire/auth/controller/exceptions.py +9 -0
  31. django_spire/auth/group/admin.py +1 -0
  32. django_spire/auth/group/apps.py +2 -0
  33. django_spire/auth/group/factories.py +17 -8
  34. django_spire/auth/group/forms.py +7 -0
  35. django_spire/auth/group/tests/test_factories.py +146 -0
  36. django_spire/auth/group/tests/test_forms.py +282 -0
  37. django_spire/auth/group/tests/test_models.py +192 -0
  38. django_spire/auth/group/tests/test_querysets.py +98 -0
  39. django_spire/auth/group/tests/test_utils.py +341 -0
  40. django_spire/auth/group/tests/test_views.py +377 -0
  41. django_spire/auth/group/urls/__init__.py +3 -1
  42. django_spire/auth/group/urls/form_urls.py +2 -0
  43. django_spire/auth/group/urls/json_urls.py +3 -0
  44. django_spire/auth/group/urls/page_urls.py +2 -0
  45. django_spire/auth/group/utils.py +6 -2
  46. django_spire/auth/group/views/form_views.py +6 -3
  47. django_spire/auth/group/views/json_views.py +6 -2
  48. django_spire/auth/mfa/admin.py +2 -0
  49. django_spire/auth/mfa/apps.py +2 -0
  50. django_spire/auth/mfa/forms.py +1 -0
  51. django_spire/auth/mfa/querysets.py +9 -2
  52. django_spire/auth/mfa/tests/test_models.py +233 -0
  53. django_spire/auth/mfa/tests/test_utils.py +106 -0
  54. django_spire/auth/mfa/urls/__init__.py +2 -0
  55. django_spire/auth/mfa/urls/page_urls.py +2 -0
  56. django_spire/auth/mfa/urls/redirect_urls.py +2 -0
  57. django_spire/auth/mfa/views/page_views.py +2 -1
  58. django_spire/auth/permissions/consts.py +2 -2
  59. django_spire/auth/permissions/decorators.py +8 -8
  60. django_spire/auth/permissions/permissions.py +28 -35
  61. django_spire/auth/permissions/tests/test_decorators.py +333 -0
  62. django_spire/auth/permissions/tests/test_permissions.py +337 -0
  63. django_spire/auth/permissions/tests/test_tools.py +305 -0
  64. django_spire/auth/permissions/tools.py +21 -15
  65. django_spire/auth/seeding/seed.py +3 -0
  66. django_spire/auth/seeding/seeder.py +2 -0
  67. django_spire/auth/tests/test_controller.py +323 -0
  68. django_spire/auth/tests/test_url_endpoints.py +9 -9
  69. django_spire/auth/tests/test_views.py +406 -0
  70. django_spire/auth/urls/admin_urls.py +2 -0
  71. django_spire/auth/urls/redirect_urls.py +2 -0
  72. django_spire/auth/user/apps.py +2 -0
  73. django_spire/auth/user/forms.py +9 -0
  74. django_spire/auth/user/models.py +1 -1
  75. django_spire/auth/user/services/services.py +1 -0
  76. django_spire/auth/user/tests/factories.py +14 -13
  77. django_spire/auth/user/tests/test_factories.py +166 -2
  78. django_spire/auth/user/tests/test_forms.py +573 -0
  79. django_spire/auth/user/tests/test_models.py +257 -0
  80. django_spire/auth/user/tests/test_services.py +200 -0
  81. django_spire/auth/user/tests/test_tools.py +153 -0
  82. django_spire/auth/user/tests/test_user_factories.py +139 -0
  83. django_spire/auth/user/tests/test_views.py +363 -0
  84. django_spire/auth/user/tools.py +7 -1
  85. django_spire/auth/user/urls/form_urls.py +3 -0
  86. django_spire/auth/user/urls/page_urls.py +3 -0
  87. django_spire/auth/user/views/form_views.py +19 -10
  88. django_spire/auth/user/views/page_views.py +8 -2
  89. django_spire/auth/views/redirect_views.py +14 -9
  90. django_spire/comment/admin.py +2 -0
  91. django_spire/comment/apps.py +2 -0
  92. django_spire/comment/templatetags/comment_tags.py +1 -0
  93. django_spire/comment/tests/test_forms.py +27 -0
  94. django_spire/comment/tests/test_models.py +215 -0
  95. django_spire/comment/tests/test_querysets.py +101 -0
  96. django_spire/comment/tests/test_utils.py +90 -0
  97. django_spire/comment/urls.py +2 -0
  98. django_spire/comment/utils.py +22 -13
  99. django_spire/comment/views.py +1 -1
  100. django_spire/conf.py +8 -6
  101. django_spire/consts.py +1 -1
  102. django_spire/contrib/breadcrumb/apps.py +2 -0
  103. django_spire/contrib/breadcrumb/breadcrumbs.py +18 -18
  104. django_spire/contrib/breadcrumb/tests/test_breadcrumbs.py +198 -0
  105. django_spire/contrib/constructor/__init__.py +3 -3
  106. django_spire/contrib/constructor/constructor.py +15 -15
  107. django_spire/contrib/constructor/django_model_constructor.py +5 -4
  108. django_spire/contrib/constructor/exceptions.py +5 -3
  109. django_spire/contrib/constructor/tests/__init__.py +0 -0
  110. django_spire/contrib/constructor/tests/test_constructor.py +193 -0
  111. django_spire/contrib/form/tests/__init__.py +0 -0
  112. django_spire/contrib/form/tests/test_forms.py +203 -0
  113. django_spire/contrib/generic_views/modal_views.py +2 -1
  114. django_spire/contrib/generic_views/portal_views.py +20 -19
  115. django_spire/contrib/generic_views/tests/__init__.py +0 -0
  116. django_spire/contrib/generic_views/tests/test_views.py +459 -0
  117. django_spire/contrib/help/apps.py +2 -0
  118. django_spire/contrib/help/templatetags/help.py +1 -0
  119. django_spire/contrib/help/tests/__init__.py +0 -0
  120. django_spire/contrib/help/tests/test_templatetags.py +100 -0
  121. django_spire/contrib/options/mixins.py +6 -5
  122. django_spire/contrib/options/tests/factories.py +5 -1
  123. django_spire/contrib/options/tests/test_options.py +234 -0
  124. django_spire/contrib/ordering/exceptions.py +7 -3
  125. django_spire/contrib/ordering/mixins.py +2 -0
  126. django_spire/contrib/ordering/querysets.py +3 -1
  127. django_spire/contrib/ordering/services/processor_service.py +8 -4
  128. django_spire/contrib/ordering/services/service.py +1 -2
  129. django_spire/contrib/ordering/tests/__init__.py +0 -0
  130. django_spire/contrib/ordering/tests/test_ordering.py +165 -0
  131. django_spire/contrib/ordering/validators.py +6 -6
  132. django_spire/contrib/pagination/templatetags/pagination_tags.py +12 -5
  133. django_spire/contrib/pagination/tests/__init__.py +0 -0
  134. django_spire/contrib/pagination/tests/test_pagination.py +179 -0
  135. django_spire/contrib/performance/decorators.py +16 -6
  136. django_spire/contrib/performance/tests/__init__.py +0 -0
  137. django_spire/contrib/performance/tests/test_performance.py +107 -0
  138. django_spire/contrib/queryset/enums.py +3 -1
  139. django_spire/contrib/queryset/filter_tools.py +10 -5
  140. django_spire/contrib/queryset/mixins.py +16 -16
  141. django_spire/contrib/queryset/tests/__init__.py +0 -0
  142. django_spire/contrib/queryset/tests/test_queryset.py +137 -0
  143. django_spire/contrib/seeding/field/base.py +13 -7
  144. django_spire/contrib/seeding/field/callable.py +8 -1
  145. django_spire/contrib/seeding/field/cleaners.py +5 -5
  146. django_spire/contrib/seeding/field/custom.py +20 -10
  147. django_spire/contrib/seeding/field/django/seeder.py +8 -6
  148. django_spire/contrib/seeding/field/enums.py +7 -5
  149. django_spire/contrib/seeding/field/override.py +16 -6
  150. django_spire/contrib/seeding/field/static.py +9 -2
  151. django_spire/contrib/seeding/field/tests/test_base.py +18 -14
  152. django_spire/contrib/seeding/field/tests/test_callable.py +13 -9
  153. django_spire/contrib/seeding/field/tests/test_cleaners.py +51 -38
  154. django_spire/contrib/seeding/field/tests/test_static.py +13 -9
  155. django_spire/contrib/seeding/intelligence/bots/seeder_generator_bot.py +2 -0
  156. django_spire/contrib/seeding/intelligence/intel.py +5 -1
  157. django_spire/contrib/seeding/intelligence/prompts/factory.py +6 -1
  158. django_spire/contrib/seeding/intelligence/prompts/foreign_key_selection_prompt.py +6 -1
  159. django_spire/contrib/seeding/intelligence/prompts/generate_django_model_seeder_prompts.py +2 -0
  160. django_spire/contrib/seeding/intelligence/prompts/generic_relationship_selection_prompt.py +7 -1
  161. django_spire/contrib/seeding/intelligence/prompts/hierarchical_selection_prompt.py +6 -2
  162. django_spire/contrib/seeding/intelligence/prompts/model_field_choices_prompt.py +8 -2
  163. django_spire/contrib/seeding/intelligence/prompts/negation_prompt.py +2 -0
  164. django_spire/contrib/seeding/intelligence/prompts/objective_prompt.py +6 -1
  165. django_spire/contrib/seeding/management/commands/seeding.py +9 -3
  166. django_spire/contrib/seeding/management/example.py +2 -0
  167. django_spire/contrib/seeding/model/base.py +16 -7
  168. django_spire/contrib/seeding/model/config.py +31 -15
  169. django_spire/contrib/seeding/model/django/config.py +13 -13
  170. django_spire/contrib/seeding/model/django/seeder.py +4 -4
  171. django_spire/contrib/seeding/model/django/tests/test_seeder.py +34 -23
  172. django_spire/contrib/seeding/model/enums.py +2 -0
  173. django_spire/contrib/seeding/tests/test_config.py +71 -0
  174. django_spire/contrib/seeding/tests/test_custom.py +35 -0
  175. django_spire/contrib/seeding/tests/test_enums.py +40 -0
  176. django_spire/contrib/seeding/tests/test_intel.py +32 -0
  177. django_spire/contrib/seeding/tests/test_override.py +63 -0
  178. django_spire/contrib/service/__init__.py +2 -2
  179. django_spire/contrib/service/django_model_service.py +16 -15
  180. django_spire/contrib/service/exceptions.py +5 -3
  181. django_spire/contrib/service/tests/__init__.py +0 -0
  182. django_spire/contrib/service/tests/test_service.py +153 -0
  183. django_spire/contrib/session/apps.py +2 -0
  184. django_spire/contrib/session/controller.py +48 -42
  185. django_spire/contrib/session/templatetags/session_tags.py +11 -2
  186. django_spire/contrib/session/tests/test_session_controller.py +117 -53
  187. django_spire/contrib/tests/__init__.py +0 -0
  188. django_spire/contrib/tests/test_utils.py +37 -0
  189. django_spire/contrib/utils.py +4 -1
  190. django_spire/core/apps.py +2 -0
  191. django_spire/core/converters/tests/test_to_data.py +353 -0
  192. django_spire/core/converters/tests/test_to_enums.py +61 -41
  193. django_spire/core/converters/tests/test_to_pydantic.py +138 -109
  194. django_spire/core/converters/to_data.py +29 -10
  195. django_spire/core/converters/to_enums.py +4 -2
  196. django_spire/core/converters/to_pydantic.py +22 -22
  197. django_spire/core/decorators.py +19 -6
  198. django_spire/core/forms/widgets.py +4 -0
  199. django_spire/core/management/commands/spire_startapp_pkg/template/app/apps.py.template +2 -0
  200. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/__init__.py.template +2 -0
  201. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/form_urls.py.template +2 -0
  202. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/page_urls.py.template +2 -0
  203. django_spire/core/maps.py +3 -1
  204. django_spire/core/middleware/maintenance.py +3 -3
  205. django_spire/core/middleware.py +8 -6
  206. django_spire/core/redirect/__init__.py +5 -0
  207. django_spire/core/redirect/generic_redirect.py +1 -2
  208. django_spire/core/redirect/tests/__init__.py +0 -0
  209. django_spire/core/redirect/tests/test_generic_redirect.py +34 -0
  210. django_spire/core/{tests/tests_redirect.py → redirect/tests/test_safe_redirect.py} +55 -81
  211. django_spire/core/shortcuts.py +3 -3
  212. django_spire/core/static/django_spire/css/app-layout.css +1 -1
  213. django_spire/core/static/django_spire/css/app-navigation.css +3 -3
  214. django_spire/core/static/django_spire/css/bootstrap-override.css +4 -0
  215. django_spire/core/tag/admin.py +12 -0
  216. django_spire/core/tag/intelligence/tag_set_bot.py +2 -0
  217. django_spire/core/tag/mixins.py +2 -0
  218. django_spire/core/tag/models.py +2 -0
  219. django_spire/core/tag/querysets.py +2 -0
  220. django_spire/core/tag/service/tag_service.py +6 -3
  221. django_spire/core/tag/tests/test_intelligence.py +9 -9
  222. django_spire/core/tag/tests/test_tags.py +44 -54
  223. django_spire/core/tag/tests/test_tools.py +191 -0
  224. django_spire/core/tag/tools.py +3 -0
  225. django_spire/core/templates/django_spire/card/card.html +5 -2
  226. django_spire/core/templates/django_spire/card/title_card.html +12 -4
  227. django_spire/core/templates/django_spire/infinite_scroll/base.html +1 -0
  228. django_spire/core/templates/django_spire/navigation/side_navigation.html +19 -24
  229. django_spire/core/templates/django_spire/page/full_page.html +46 -16
  230. django_spire/core/templates/django_spire/table/base.html +5 -3
  231. django_spire/core/templatetags/json.py +6 -2
  232. django_spire/core/templatetags/message.py +13 -8
  233. django_spire/core/templatetags/string_formating.py +8 -5
  234. django_spire/core/templatetags/tests/__init__.py +0 -0
  235. django_spire/core/templatetags/tests/test_templatetags.py +427 -0
  236. django_spire/core/templatetags/variable_types.py +17 -9
  237. django_spire/core/tests/test_cases.py +1 -1
  238. django_spire/core/tests/test_conf.py +43 -0
  239. django_spire/core/tests/test_consts.py +28 -0
  240. django_spire/core/tests/test_context_processors.py +93 -0
  241. django_spire/core/tests/test_decorators.py +95 -0
  242. django_spire/core/tests/test_django_spire_utils.py +56 -0
  243. django_spire/core/tests/test_exceptions.py +37 -0
  244. django_spire/core/tests/test_models.py +54 -0
  245. django_spire/core/tests/test_settings.py +45 -0
  246. django_spire/core/tests/test_shortcuts.py +74 -0
  247. django_spire/core/tests/test_urls.py +16 -0
  248. django_spire/core/tests/test_utils.py +58 -0
  249. django_spire/core/urls.py +4 -1
  250. django_spire/core/utils.py +12 -8
  251. django_spire/exceptions.py +16 -1
  252. django_spire/file/admin.py +4 -2
  253. django_spire/file/apps.py +8 -10
  254. django_spire/file/fields.py +7 -7
  255. django_spire/file/forms.py +1 -1
  256. django_spire/file/interfaces.py +15 -15
  257. django_spire/file/mixins.py +1 -4
  258. django_spire/file/models.py +3 -5
  259. django_spire/file/tests/factories.py +59 -0
  260. django_spire/file/tests/test_admin.py +69 -0
  261. django_spire/file/tests/test_apps.py +24 -0
  262. django_spire/file/tests/test_fields.py +114 -0
  263. django_spire/file/tests/test_forms.py +20 -0
  264. django_spire/file/tests/test_interfaces.py +183 -0
  265. django_spire/file/tests/test_models.py +82 -0
  266. django_spire/file/tests/test_querysets.py +102 -0
  267. django_spire/file/tests/test_utils.py +32 -0
  268. django_spire/file/tests/test_views.py +145 -0
  269. django_spire/file/tests/test_widgets.py +82 -0
  270. django_spire/file/tools.py +8 -2
  271. django_spire/file/views.py +7 -3
  272. django_spire/file/widgets.py +12 -12
  273. django_spire/help_desk/admin.py +15 -0
  274. django_spire/help_desk/apps.py +2 -0
  275. django_spire/help_desk/auth/controller.py +2 -0
  276. django_spire/help_desk/choices.py +2 -0
  277. django_spire/help_desk/enums.py +2 -0
  278. django_spire/help_desk/exceptions.py +31 -3
  279. django_spire/help_desk/forms.py +2 -0
  280. django_spire/help_desk/models.py +2 -0
  281. django_spire/help_desk/querysets.py +4 -1
  282. django_spire/help_desk/services/notification_service.py +26 -27
  283. django_spire/help_desk/services/service.py +2 -3
  284. django_spire/help_desk/tests/factories.py +8 -3
  285. django_spire/help_desk/tests/test_admin.py +41 -0
  286. django_spire/help_desk/tests/test_apps.py +41 -0
  287. django_spire/help_desk/tests/test_choices.py +50 -0
  288. django_spire/help_desk/tests/test_controller.py +87 -0
  289. django_spire/help_desk/tests/test_enums.py +18 -0
  290. django_spire/help_desk/tests/test_exceptions.py +37 -0
  291. django_spire/help_desk/tests/test_forms.py +89 -0
  292. django_spire/help_desk/tests/test_models.py +59 -0
  293. django_spire/help_desk/tests/test_querysets.py +38 -0
  294. django_spire/help_desk/tests/test_services/test_notification_service.py +15 -8
  295. django_spire/help_desk/tests/test_services/test_service.py +92 -0
  296. django_spire/help_desk/tests/test_urls/test_form_urls.py +6 -6
  297. django_spire/help_desk/tests/test_urls/test_page_urls.py +8 -9
  298. django_spire/help_desk/tests/test_views/test_form_views.py +46 -19
  299. django_spire/help_desk/tests/test_views/test_page_views.py +32 -9
  300. django_spire/help_desk/urls/__init__.py +4 -1
  301. django_spire/help_desk/urls/form_urls.py +3 -0
  302. django_spire/help_desk/urls/page_urls.py +3 -0
  303. django_spire/help_desk/views/form_views.py +13 -5
  304. django_spire/help_desk/views/page_views.py +11 -3
  305. django_spire/history/activity/admin.py +2 -0
  306. django_spire/history/activity/apps.py +3 -1
  307. django_spire/history/activity/mixins.py +13 -7
  308. django_spire/history/activity/models.py +6 -5
  309. django_spire/history/activity/querysets.py +2 -0
  310. django_spire/history/activity/tests/__init__.py +0 -0
  311. django_spire/history/activity/tests/test_activity.py +176 -0
  312. django_spire/history/admin.py +9 -2
  313. django_spire/history/choices.py +3 -0
  314. django_spire/history/models.py +5 -5
  315. django_spire/history/tests/test_admin.py +93 -0
  316. django_spire/history/tests/test_history.py +101 -0
  317. django_spire/history/tests/test_mixins.py +84 -0
  318. django_spire/history/viewed/admin.py +3 -1
  319. django_spire/history/viewed/apps.py +3 -1
  320. django_spire/history/viewed/models.py +2 -0
  321. django_spire/history/viewed/tests/__init__.py +0 -0
  322. django_spire/history/viewed/tests/test_viewed.py +46 -0
  323. django_spire/knowledge/auth/tests/__init__.py +0 -0
  324. django_spire/knowledge/auth/tests/test_controller.py +116 -0
  325. django_spire/knowledge/collection/admin.py +5 -1
  326. django_spire/knowledge/collection/models.py +3 -1
  327. django_spire/knowledge/collection/seeding/seed.py +1 -0
  328. django_spire/knowledge/collection/services/factory_service.py +10 -11
  329. django_spire/knowledge/collection/services/ordering_service.py +1 -2
  330. django_spire/knowledge/collection/services/service.py +5 -10
  331. django_spire/knowledge/collection/services/tag_service.py +5 -2
  332. django_spire/knowledge/collection/tests/factories.py +28 -1
  333. django_spire/knowledge/collection/tests/test_models.py +48 -0
  334. django_spire/knowledge/collection/tests/test_querysets.py +93 -0
  335. django_spire/knowledge/collection/tests/test_services/test_factory_service.py +100 -0
  336. django_spire/knowledge/collection/tests/test_services/test_services.py +160 -0
  337. django_spire/knowledge/collection/tests/test_urls/test_form_urls.py +21 -3
  338. django_spire/knowledge/collection/tests/test_urls/test_json_urls.py +39 -1
  339. django_spire/knowledge/collection/tests/test_urls/test_page_urls.py +12 -4
  340. django_spire/knowledge/collection/urls/__init__.py +3 -0
  341. django_spire/knowledge/collection/urls/form_urls.py +2 -0
  342. django_spire/knowledge/collection/urls/json_urls.py +2 -0
  343. django_spire/knowledge/collection/urls/page_urls.py +2 -0
  344. django_spire/knowledge/collection/views/form_views.py +4 -4
  345. django_spire/knowledge/collection/views/json_views.py +5 -1
  346. django_spire/knowledge/collection/views/page_views.py +5 -2
  347. django_spire/knowledge/entry/admin.py +7 -1
  348. django_spire/knowledge/entry/forms.py +2 -0
  349. django_spire/knowledge/entry/models.py +2 -0
  350. django_spire/knowledge/entry/seeding/seed.py +3 -0
  351. django_spire/knowledge/entry/services/automation_service.py +5 -4
  352. django_spire/knowledge/entry/services/factory_service.py +7 -5
  353. django_spire/knowledge/entry/services/service.py +4 -7
  354. django_spire/knowledge/entry/services/tag_service.py +0 -1
  355. django_spire/knowledge/entry/services/tool_service.py +1 -0
  356. django_spire/knowledge/entry/services/transformation_services.py +1 -5
  357. django_spire/knowledge/entry/tests/factories.py +1 -2
  358. django_spire/knowledge/entry/tests/test_factory_service.py +20 -0
  359. django_spire/knowledge/entry/tests/test_models.py +41 -0
  360. django_spire/knowledge/entry/tests/test_querysets.py +71 -0
  361. django_spire/knowledge/entry/tests/test_services.py +94 -0
  362. django_spire/knowledge/entry/tests/test_urls/test_form_urls.py +9 -14
  363. django_spire/knowledge/entry/tests/test_urls/test_json_urls.py +48 -5
  364. django_spire/knowledge/entry/tests/test_urls/test_page_urls.py +6 -8
  365. django_spire/knowledge/entry/tests/test_urls/test_template_urls.py +40 -0
  366. django_spire/knowledge/entry/urls/form_urls.py +2 -0
  367. django_spire/knowledge/entry/urls/json_urls.py +2 -0
  368. django_spire/knowledge/entry/urls/page_urls.py +2 -0
  369. django_spire/knowledge/entry/urls/template_urls.py +2 -0
  370. django_spire/knowledge/entry/version/block/choices.py +2 -0
  371. django_spire/knowledge/entry/version/block/data/data.py +1 -0
  372. django_spire/knowledge/entry/version/block/data/list/data.py +8 -13
  373. django_spire/knowledge/entry/version/block/data/list/maps.py +3 -0
  374. django_spire/knowledge/entry/version/block/data/list/meta.py +1 -2
  375. django_spire/knowledge/entry/version/block/data/list/tests/__init__.py +0 -0
  376. django_spire/knowledge/entry/version/block/data/list/tests/test_maps.py +32 -0
  377. django_spire/knowledge/entry/version/block/data/list/tests/test_meta.py +58 -0
  378. django_spire/knowledge/entry/version/block/data/maps.py +3 -6
  379. django_spire/knowledge/entry/version/block/models.py +7 -5
  380. django_spire/knowledge/entry/version/block/seeding/constants.py +5 -4
  381. django_spire/knowledge/entry/version/block/services/service.py +2 -3
  382. django_spire/knowledge/entry/version/block/tests/factories.py +4 -10
  383. django_spire/knowledge/entry/version/block/tests/test_choices.py +56 -0
  384. django_spire/knowledge/entry/version/block/tests/test_data.py +90 -0
  385. django_spire/knowledge/entry/version/block/tests/test_maps.py +37 -0
  386. django_spire/knowledge/entry/version/block/tests/test_models.py +55 -0
  387. django_spire/knowledge/entry/version/block/tests/test_querysets.py +35 -0
  388. django_spire/knowledge/entry/version/block/tests/test_services.py +65 -0
  389. django_spire/knowledge/entry/version/choices.py +2 -0
  390. django_spire/knowledge/entry/version/converters/converter.py +1 -1
  391. django_spire/knowledge/entry/version/converters/docx_converter.py +4 -7
  392. django_spire/knowledge/entry/version/converters/markdown_converter.py +20 -20
  393. django_spire/knowledge/entry/version/maps.py +4 -5
  394. django_spire/knowledge/entry/version/querysets.py +1 -1
  395. django_spire/knowledge/entry/version/seeding/seeder.py +1 -2
  396. django_spire/knowledge/entry/version/services/processor_service.py +5 -4
  397. django_spire/knowledge/entry/version/services/service.py +1 -2
  398. django_spire/knowledge/entry/version/tests/factories.py +2 -2
  399. django_spire/knowledge/entry/version/tests/test_choices.py +18 -0
  400. django_spire/knowledge/entry/version/tests/test_converters/test_docx_converter.py +56 -8
  401. django_spire/knowledge/entry/version/tests/test_converters/test_markdown_converter.py +78 -0
  402. django_spire/knowledge/entry/version/tests/test_maps.py +58 -0
  403. django_spire/knowledge/entry/version/tests/test_models.py +23 -0
  404. django_spire/knowledge/entry/version/tests/test_querysets.py +26 -0
  405. django_spire/knowledge/entry/version/tests/test_services.py +62 -0
  406. django_spire/knowledge/entry/version/tests/test_urls/test_json_urls.py +27 -8
  407. django_spire/knowledge/entry/version/tests/test_urls/test_page_urls.py +15 -8
  408. django_spire/knowledge/entry/version/tests/test_urls/test_redirect_urls.py +38 -0
  409. django_spire/knowledge/entry/version/urls/__init__.py +3 -0
  410. django_spire/knowledge/entry/version/urls/json_urls.py +2 -1
  411. django_spire/knowledge/entry/version/urls/page_urls.py +2 -0
  412. django_spire/knowledge/entry/version/urls/redirect_urls.py +2 -0
  413. django_spire/knowledge/entry/version/views/json_views.py +5 -1
  414. django_spire/knowledge/entry/version/views/page_views.py +10 -3
  415. django_spire/knowledge/entry/version/views/redirect_views.py +5 -1
  416. django_spire/knowledge/entry/views/form_views.py +16 -8
  417. django_spire/knowledge/entry/views/json_views.py +3 -1
  418. django_spire/knowledge/entry/views/page_views.py +8 -2
  419. django_spire/knowledge/entry/views/template_views.py +7 -1
  420. django_spire/knowledge/exceptions.py +2 -1
  421. django_spire/knowledge/intelligence/intel/answer_intel.py +2 -1
  422. django_spire/knowledge/intelligence/intel/entry_intel.py +0 -1
  423. django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +4 -5
  424. django_spire/knowledge/models.py +1 -2
  425. django_spire/knowledge/tests/__init__.py +0 -0
  426. django_spire/knowledge/tests/test_templatetags.py +40 -0
  427. django_spire/knowledge/tests/test_urls/__init__.py +0 -0
  428. django_spire/knowledge/tests/test_urls/test_page_urls.py +24 -0
  429. django_spire/knowledge/urls/__init__.py +2 -0
  430. django_spire/knowledge/urls/page_urls.py +2 -0
  431. django_spire/knowledge/views/page_views.py +8 -3
  432. django_spire/notification/admin.py +3 -1
  433. django_spire/notification/app/admin.py +2 -0
  434. django_spire/notification/app/apps.py +3 -1
  435. django_spire/notification/app/exceptions.py +9 -2
  436. django_spire/notification/app/models.py +8 -4
  437. django_spire/notification/app/processor.py +22 -26
  438. django_spire/notification/app/querysets.py +2 -0
  439. django_spire/notification/app/tests/__init__.py +0 -0
  440. django_spire/notification/app/tests/factories.py +34 -0
  441. django_spire/notification/app/tests/test_apps.py +24 -0
  442. django_spire/notification/app/tests/test_models.py +72 -0
  443. django_spire/notification/app/tests/test_processor.py +111 -0
  444. django_spire/notification/app/tests/test_querysets.py +90 -0
  445. django_spire/notification/app/tests/test_views/__init__.py +0 -0
  446. django_spire/notification/app/tests/test_views/test_json_views.py +48 -0
  447. django_spire/notification/app/tests/test_views/test_page_views.py +19 -0
  448. django_spire/notification/app/urls/__init__.py +3 -1
  449. django_spire/notification/app/urls/json_urls.py +6 -4
  450. django_spire/notification/app/urls/page_urls.py +4 -3
  451. django_spire/notification/app/urls/template_urls.py +4 -2
  452. django_spire/notification/apps.py +4 -1
  453. django_spire/notification/email/admin.py +5 -1
  454. django_spire/notification/email/apps.py +3 -1
  455. django_spire/notification/email/exceptions.py +4 -2
  456. django_spire/notification/email/helper.py +5 -3
  457. django_spire/notification/email/models.py +4 -0
  458. django_spire/notification/email/processor.py +19 -15
  459. django_spire/notification/email/querysets.py +3 -0
  460. django_spire/notification/email/tests/__init__.py +0 -0
  461. django_spire/notification/email/tests/factories.py +35 -0
  462. django_spire/notification/email/tests/test_apps.py +24 -0
  463. django_spire/notification/email/tests/test_models.py +52 -0
  464. django_spire/notification/email/tests/test_processor.py +92 -0
  465. django_spire/notification/email/tests/test_querysets.py +43 -0
  466. django_spire/notification/exceptions.py +17 -2
  467. django_spire/notification/managers.py +7 -1
  468. django_spire/notification/maps.py +4 -1
  469. django_spire/notification/mixins.py +2 -0
  470. django_spire/notification/models.py +3 -1
  471. django_spire/notification/processors/notification.py +12 -5
  472. django_spire/notification/processors/processor.py +2 -0
  473. django_spire/notification/processors/tests/__init__.py +0 -0
  474. django_spire/notification/processors/tests/test_notification.py +106 -0
  475. django_spire/notification/push/admin.py +10 -1
  476. django_spire/notification/push/apps.py +3 -1
  477. django_spire/notification/push/models.py +2 -3
  478. django_spire/notification/push/tests/__init__.py +0 -0
  479. django_spire/notification/push/tests/test_apps.py +24 -0
  480. django_spire/notification/push/tests/test_models.py +28 -0
  481. django_spire/notification/querysets.py +7 -1
  482. django_spire/notification/sms/admin.py +2 -0
  483. django_spire/notification/sms/apps.py +4 -1
  484. django_spire/notification/sms/automations.py +2 -0
  485. django_spire/notification/sms/choices.py +2 -0
  486. django_spire/notification/sms/exceptions.py +19 -5
  487. django_spire/notification/sms/helper.py +33 -23
  488. django_spire/notification/sms/models.py +5 -1
  489. django_spire/notification/sms/processor.py +20 -20
  490. django_spire/notification/sms/querysets.py +2 -0
  491. django_spire/notification/sms/tests/factories.py +33 -0
  492. django_spire/notification/sms/tests/test_apps.py +24 -0
  493. django_spire/notification/sms/tests/test_automation.py +38 -0
  494. django_spire/notification/sms/tests/test_choices.py +15 -0
  495. django_spire/notification/sms/tests/test_consts.py +17 -0
  496. django_spire/notification/sms/tests/test_exceptions.py +27 -0
  497. django_spire/notification/sms/tests/test_helper.py +50 -0
  498. django_spire/notification/sms/tests/test_models.py +81 -0
  499. django_spire/notification/sms/tests/test_processor.py +107 -0
  500. django_spire/notification/sms/tests/test_tools.py +25 -11
  501. django_spire/notification/sms/tools.py +16 -5
  502. django_spire/notification/sms/urls/__init__.py +3 -1
  503. django_spire/notification/sms/urls/media_urls.py +2 -0
  504. django_spire/notification/sms/views/media_views.py +14 -4
  505. django_spire/notification/tests/__init__.py +0 -0
  506. django_spire/notification/tests/factories.py +26 -0
  507. django_spire/notification/tests/test_admin.py +55 -0
  508. django_spire/notification/tests/test_apps.py +30 -0
  509. django_spire/notification/tests/test_automation.py +18 -0
  510. django_spire/notification/tests/test_choices.py +59 -0
  511. django_spire/notification/tests/test_exceptions.py +58 -0
  512. django_spire/notification/tests/test_managers.py +100 -0
  513. django_spire/notification/tests/test_maps.py +31 -0
  514. django_spire/notification/tests/test_models.py +76 -0
  515. django_spire/notification/tests/test_querysets.py +184 -0
  516. django_spire/notification/tests/test_utils.py +23 -0
  517. django_spire/notification/urls.py +3 -1
  518. django_spire/notification/utils.py +3 -1
  519. django_spire/settings.py +3 -0
  520. django_spire/theme/tests/test_context_processor.py +15 -13
  521. django_spire/theme/tests/test_enums.py +2 -2
  522. django_spire/theme/tests/test_filesystem.py +2 -5
  523. django_spire/theme/tests/test_integration.py +12 -12
  524. django_spire/theme/tests/test_model.py +40 -38
  525. django_spire/theme/tests/test_views/test_json_views.py +33 -33
  526. django_spire/theme/urls/json_urls.py +3 -0
  527. django_spire/theme/urls/page_urls.py +3 -0
  528. django_spire/urls.py +19 -15
  529. django_spire/utils.py +13 -4
  530. {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/METADATA +2 -2
  531. {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/RECORD +534 -362
  532. {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/licenses/LICENSE.md +1 -1
  533. django_spire/contrib/options/tests/test_unit.py +0 -148
  534. django_spire/contrib/seeding/tests/test_seeding.py +0 -25
  535. django_spire/core/tests/test_templatetags.py +0 -117
  536. django_spire/core/tests/tests_shortcuts.py +0 -73
  537. django_spire/history/activity/tests.py +0 -3
  538. django_spire/history/activity/views.py +0 -3
  539. django_spire/knowledge/collection/tests/test_services/test_transformation_service.py +0 -71
  540. django_spire/notification/app/tests.py +0 -3
  541. {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/WHEEL +0 -0
  542. {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/top_level.txt +0 -0
@@ -1,23 +1,26 @@
1
+ from __future__ import annotations
2
+
1
3
  from django import template
2
4
 
5
+
3
6
  register = template.Library()
4
7
 
5
8
 
6
9
  @register.filter
7
- def dashes_to_underscore(value):
10
+ def dashes_to_underscore(value: str) -> str:
8
11
  return value.replace('-', '_')
9
12
 
10
13
 
11
14
  @register.filter
12
- def spaces_to_underscore(value):
15
+ def spaces_to_underscore(value: str) -> str:
13
16
  return value.replace(' ', '_')
14
17
 
15
18
 
16
19
  @register.filter
17
- def dashes_and_spaces_to_underscore(value):
20
+ def dashes_and_spaces_to_underscore(value: str) -> str:
18
21
  return spaces_to_underscore(dashes_to_underscore(value))
19
22
 
20
23
 
21
24
  @register.filter
22
- def underscores_to_spaces(value):
23
- return value.replace('_', ' ')
25
+ def underscores_to_spaces(value: str) -> str:
26
+ return value.replace('_', ' ')
File without changes
@@ -0,0 +1,427 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import string
5
+
6
+ from unittest.mock import MagicMock, patch
7
+
8
+ from django.template import Context, RequestContext, Template
9
+ from django.test import RequestFactory, TestCase
10
+
11
+ from django_spire.core.templatetags.json import to_json
12
+ from django_spire.core.templatetags.message import django_messages_to_json
13
+ from django_spire.core.templatetags.spire_core_tags import (
14
+ add_str,
15
+ content_type_url,
16
+ generate_id,
17
+ in_list,
18
+ index,
19
+ is_path,
20
+ not_in_list,
21
+ query_param_url,
22
+ safe_dict_items,
23
+ to_snake_case,
24
+ )
25
+ from django_spire.core.templatetags.string_formating import (
26
+ dashes_and_spaces_to_underscore,
27
+ dashes_to_underscore,
28
+ spaces_to_underscore,
29
+ underscores_to_spaces,
30
+ )
31
+ from django_spire.core.templatetags.variable_types import (
32
+ is_dict,
33
+ is_list,
34
+ is_list_or_tuple,
35
+ is_not_dict,
36
+ is_not_list,
37
+ is_not_list_or_tuple,
38
+ is_not_tuple,
39
+ is_tuple,
40
+ )
41
+
42
+
43
+ class TestAddStr(TestCase):
44
+ def test_concatenates_strings(self) -> None:
45
+ assert add_str('Hello', 'World') == 'HelloWorld'
46
+
47
+ def test_empty_strings(self) -> None:
48
+ assert add_str('', '') == ''
49
+
50
+ def test_with_spaces(self) -> None:
51
+ assert add_str('Hello ', 'World') == 'Hello World'
52
+
53
+
54
+ class TestContentTypeUrl(TestCase):
55
+ def test_constructs_url_with_metadata(self) -> None:
56
+ class Dummy:
57
+ pass
58
+
59
+ dummy = Dummy()
60
+ dummy._meta = type(
61
+ 'meta',
62
+ (),
63
+ {'app_label': 'myapp', 'model_name': 'dummy'}
64
+ )
65
+
66
+ func = 'django_spire.core.templatetags.spire_core_tags.reverse'
67
+ return_value = 'http://example.com/dummy'
68
+
69
+ with patch(func, return_value=return_value) as mock_reverse:
70
+ url = content_type_url('dummy_url', dummy)
71
+
72
+ mock_reverse.assert_called_once_with(
73
+ 'dummy_url',
74
+ kwargs={'app_label': 'myapp', 'model_name': 'dummy'}
75
+ )
76
+
77
+ assert url == 'http://example.com/dummy'
78
+
79
+
80
+ class TestDashesAndSpacesToUnderscore(TestCase):
81
+ def test_dashes_and_spaces(self) -> None:
82
+ assert dashes_and_spaces_to_underscore('hello-world test') == 'hello_world_test'
83
+
84
+ def test_no_changes_needed(self) -> None:
85
+ assert dashes_and_spaces_to_underscore('hello_world') == 'hello_world'
86
+
87
+ def test_only_dashes(self) -> None:
88
+ assert dashes_and_spaces_to_underscore('hello-world') == 'hello_world'
89
+
90
+ def test_only_spaces(self) -> None:
91
+ assert dashes_and_spaces_to_underscore('hello world') == 'hello_world'
92
+
93
+
94
+ class TestDashesToUnderscore(TestCase):
95
+ def test_multiple_dashes(self) -> None:
96
+ assert dashes_to_underscore('hello-world-test') == 'hello_world_test'
97
+
98
+ def test_no_dashes(self) -> None:
99
+ assert dashes_to_underscore('hello') == 'hello'
100
+
101
+ def test_single_dash(self) -> None:
102
+ assert dashes_to_underscore('hello-world') == 'hello_world'
103
+
104
+
105
+ class TestDjangoMessagesToJson(TestCase):
106
+ def test_empty_messages(self) -> None:
107
+ result = django_messages_to_json([])
108
+ assert json.loads(result) == []
109
+
110
+ def test_multiple_messages(self) -> None:
111
+ messages = [
112
+ MagicMock(message='First', level_tag='info'),
113
+ MagicMock(message='Second', level_tag='error'),
114
+ ]
115
+
116
+ result = django_messages_to_json(messages)
117
+ parsed = json.loads(result)
118
+
119
+ assert len(parsed) == 2
120
+ assert parsed[0]['message'] == 'First'
121
+ assert parsed[0]['type'] == 'info'
122
+ assert parsed[1]['message'] == 'Second'
123
+ assert parsed[1]['type'] == 'error'
124
+
125
+ def test_single_message(self) -> None:
126
+ messages = [MagicMock(message='Test message', level_tag='success')]
127
+
128
+ result = django_messages_to_json(messages)
129
+ parsed = json.loads(result)
130
+
131
+ assert len(parsed) == 1
132
+ assert parsed[0]['message'] == 'Test message'
133
+ assert parsed[0]['type'] == 'success'
134
+ assert len(parsed[0]['id']) == 8
135
+
136
+
137
+ class TestGenerateId(TestCase):
138
+ def test_generates_8_characters(self) -> None:
139
+ identifier = generate_id()
140
+ assert len(identifier) == 8
141
+
142
+ def test_only_ascii_letters(self) -> None:
143
+ identifier = generate_id()
144
+ assert all(ch in string.ascii_letters for ch in identifier)
145
+
146
+ def test_unique_ids(self) -> None:
147
+ ids = {generate_id() for _ in range(100)}
148
+ assert len(ids) == 100
149
+
150
+
151
+ class TestInList(TestCase):
152
+ def test_empty_list(self) -> None:
153
+ assert in_list('a', '') is False
154
+
155
+ def test_value_in_list(self) -> None:
156
+ assert in_list('a', 'a,b,c') is True
157
+
158
+ def test_value_not_in_list(self) -> None:
159
+ assert in_list('d', 'a,b,c') is False
160
+
161
+
162
+ class TestIndex(TestCase):
163
+ def test_index_out_of_bounds(self) -> None:
164
+ items = [10, 20, 30]
165
+ assert index(items, 5) == items
166
+
167
+ def test_negative_index(self) -> None:
168
+ items = [10, 20, 30]
169
+ assert index(items, -1) == 30
170
+
171
+ def test_valid_index(self) -> None:
172
+ items = [10, 20, 30]
173
+ assert index(items, 1) == 20
174
+
175
+
176
+ class TestIsDict(TestCase):
177
+ def test_dict(self) -> None:
178
+ assert is_dict({'key': 'value'}) is True
179
+
180
+ def test_empty_dict(self) -> None:
181
+ assert is_dict({}) is True
182
+
183
+ def test_list(self) -> None:
184
+ assert is_dict([]) is False
185
+
186
+ def test_string(self) -> None:
187
+ assert is_dict('string') is False
188
+
189
+
190
+ class TestIsList(TestCase):
191
+ def test_dict(self) -> None:
192
+ assert is_list({}) is False
193
+
194
+ def test_empty_list(self) -> None:
195
+ assert is_list([]) is True
196
+
197
+ def test_list(self) -> None:
198
+ assert is_list([1, 2, 3]) is True
199
+
200
+ def test_tuple(self) -> None:
201
+ assert is_list((1, 2, 3)) is False
202
+
203
+
204
+ class TestIsListOrTuple(TestCase):
205
+ def test_dict(self) -> None:
206
+ assert is_list_or_tuple({}) is False
207
+
208
+ def test_list(self) -> None:
209
+ assert is_list_or_tuple([1, 2, 3]) is True
210
+
211
+ def test_string(self) -> None:
212
+ assert is_list_or_tuple('string') is False
213
+
214
+ def test_tuple(self) -> None:
215
+ assert is_list_or_tuple((1, 2, 3)) is True
216
+
217
+
218
+ class TestIsNotDict(TestCase):
219
+ def test_dict(self) -> None:
220
+ assert is_not_dict({'key': 'value'}) is False
221
+
222
+ def test_list(self) -> None:
223
+ assert is_not_dict([]) is True
224
+
225
+ def test_string(self) -> None:
226
+ assert is_not_dict('string') is True
227
+
228
+
229
+ class TestIsNotList(TestCase):
230
+ def test_dict(self) -> None:
231
+ assert is_not_list({}) is True
232
+
233
+ def test_list(self) -> None:
234
+ assert is_not_list([1, 2, 3]) is False
235
+
236
+ def test_tuple(self) -> None:
237
+ assert is_not_list((1, 2, 3)) is True
238
+
239
+
240
+ class TestIsNotListOrTuple(TestCase):
241
+ def test_dict(self) -> None:
242
+ assert is_not_list_or_tuple({}) is True
243
+
244
+ def test_list(self) -> None:
245
+ assert is_not_list_or_tuple([1, 2, 3]) is False
246
+
247
+ def test_string(self) -> None:
248
+ assert is_not_list_or_tuple('string') is True
249
+
250
+ def test_tuple(self) -> None:
251
+ assert is_not_list_or_tuple((1, 2, 3)) is False
252
+
253
+
254
+ class TestIsNotTuple(TestCase):
255
+ def test_list(self) -> None:
256
+ assert is_not_tuple([1, 2, 3]) is True
257
+
258
+ def test_tuple(self) -> None:
259
+ assert is_not_tuple((1, 2, 3)) is False
260
+
261
+
262
+ class TestIsPath(TestCase):
263
+ def test_empty_current(self) -> None:
264
+ assert is_path('', '/test') is False
265
+
266
+ def test_empty_url(self) -> None:
267
+ assert is_path('/test', '') is False
268
+
269
+ def test_exact_match(self) -> None:
270
+ assert is_path('/test', '/test') is True
271
+
272
+ def test_root_url(self) -> None:
273
+ assert is_path('/test', '/') is False
274
+
275
+ def test_starts_with(self) -> None:
276
+ assert is_path('/test/page', '/test') is True
277
+
278
+
279
+ class TestIsTuple(TestCase):
280
+ def test_list(self) -> None:
281
+ assert is_tuple([1, 2, 3]) is False
282
+
283
+ def test_tuple(self) -> None:
284
+ assert is_tuple((1, 2, 3)) is True
285
+
286
+
287
+ class TestNotInList(TestCase):
288
+ def test_empty_list(self) -> None:
289
+ assert not_in_list('a', '') is True
290
+
291
+ def test_value_in_list(self) -> None:
292
+ assert not_in_list('a', 'a,b,c') is False
293
+
294
+ def test_value_not_in_list(self) -> None:
295
+ assert not_in_list('x', 'a,b,c') is True
296
+
297
+
298
+ class TestQueryParamUrl(TestCase):
299
+ def setUp(self) -> None:
300
+ super().setUp()
301
+
302
+ self.factory = RequestFactory()
303
+
304
+ def test_multiple_params(self) -> None:
305
+ request = self.factory.get('/some_path', {'foo': 'bar', 'baz': 'qux'})
306
+ context = RequestContext(request, {})
307
+
308
+ func = 'django_spire.core.templatetags.spire_core_tags.reverse'
309
+ return_value = 'http://example.com/dummy'
310
+
311
+ with patch(func, return_value=return_value) as mock_reverse:
312
+ url = query_param_url(context, 'dummy_url')
313
+ mock_reverse.assert_called_once_with('dummy_url', kwargs={})
314
+
315
+ assert url.startswith('http://example.com/dummy?')
316
+ assert 'foo=bar' in url
317
+ assert 'baz=qux' in url
318
+
319
+ def test_no_params(self) -> None:
320
+ request = self.factory.get('/some_path')
321
+ context = RequestContext(request, {})
322
+
323
+ func = 'django_spire.core.templatetags.spire_core_tags.reverse'
324
+ return_value = 'http://example.com/dummy'
325
+
326
+ with patch(func, return_value=return_value):
327
+ url = query_param_url(context, 'dummy_url')
328
+
329
+ assert url == 'http://example.com/dummy?'
330
+
331
+
332
+ class TestSafeDictItems(TestCase):
333
+ def test_dict_with_items_key(self) -> None:
334
+ d = {'items': 'value', 'other': 'data'}
335
+ result = list(safe_dict_items(d))
336
+
337
+ assert ('items', 'value') in result
338
+ assert ('other', 'data') in result
339
+
340
+ def test_no_items_attribute(self) -> None:
341
+ result = safe_dict_items('not a dict')
342
+ assert result == []
343
+
344
+ def test_normal_dict(self) -> None:
345
+ d = {'key': 'value'}
346
+ result = list(safe_dict_items(d))
347
+
348
+ assert result == [('key', 'value')]
349
+
350
+
351
+ class TestSpacesToUnderscore(TestCase):
352
+ def test_multiple_spaces(self) -> None:
353
+ assert spaces_to_underscore('hello world test') == 'hello_world_test'
354
+
355
+ def test_no_spaces(self) -> None:
356
+ assert spaces_to_underscore('hello') == 'hello'
357
+
358
+ def test_single_space(self) -> None:
359
+ assert spaces_to_underscore('hello world') == 'hello_world'
360
+
361
+
362
+ class TestToJson(TestCase):
363
+ def test_dict(self) -> None:
364
+ result = to_json({'key': 'value'})
365
+ assert result == '{"key": "value"}'
366
+
367
+ def test_invalid_value(self) -> None:
368
+ result = to_json(object())
369
+ assert result == ''
370
+
371
+ def test_list(self) -> None:
372
+ result = to_json([1, 2, 3])
373
+ assert result == '[1, 2, 3]'
374
+
375
+ def test_nested_structure(self) -> None:
376
+ result = to_json({'list': [1, 2, 3], 'nested': {'key': 'value'}})
377
+ parsed = json.loads(result)
378
+
379
+ assert parsed['list'] == [1, 2, 3]
380
+ assert parsed['nested'] == {'key': 'value'}
381
+
382
+
383
+ class TestToSnakeCase(TestCase):
384
+ def test_already_snake_case(self) -> None:
385
+ assert to_snake_case('hello_world') == 'hello_world'
386
+
387
+ def test_multiple_spaces(self) -> None:
388
+ assert to_snake_case('Hello World Test') == 'hello_world_test'
389
+
390
+ def test_simple_conversion(self) -> None:
391
+ assert to_snake_case('Hello World') == 'hello_world'
392
+
393
+
394
+ class TestUnderscoresToSpaces(TestCase):
395
+ def test_multiple_underscores(self) -> None:
396
+ assert underscores_to_spaces('hello_world_test') == 'hello world test'
397
+
398
+ def test_no_underscores(self) -> None:
399
+ assert underscores_to_spaces('hello') == 'hello'
400
+
401
+ def test_single_underscore(self) -> None:
402
+ assert underscores_to_spaces('hello_world') == 'hello world'
403
+
404
+
405
+ class TemplateRenderingTests(TestCase):
406
+ def test_render_add_str_filter(self) -> None:
407
+ template_code = """
408
+ {% load spire_core_tags %}
409
+
410
+ {{ "Hello" | add_str:" World" }}
411
+ """
412
+
413
+ tmpl = Template(template_code)
414
+ rendered = tmpl.render(Context())
415
+ assert 'Hello World' in rendered
416
+
417
+ def test_render_to_snake_case_tag(self) -> None:
418
+ template_code = """
419
+ {% load spire_core_tags %}
420
+
421
+ {% to_snake_case "Hello World" as snake_case %}
422
+ {{ snake_case }}
423
+ """
424
+
425
+ tmpl = Template(template_code)
426
+ rendered = tmpl.render(Context())
427
+ assert 'hello_world' in rendered
@@ -1,43 +1,51 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
1
5
  from django import template
2
6
 
7
+ if TYPE_CHECKING:
8
+ from typing import Any
9
+
10
+
3
11
  register = template.Library()
4
12
 
5
13
 
6
14
  @register.filter
7
- def is_dict(value):
15
+ def is_dict(value: Any) -> bool:
8
16
  return isinstance(value, dict)
9
17
 
10
18
 
11
19
  @register.filter
12
- def is_not_dict(value):
20
+ def is_not_dict(value: Any) -> bool:
13
21
  return not is_dict(value)
14
22
 
15
23
 
16
24
  @register.filter
17
- def is_list(value):
25
+ def is_list(value: Any) -> bool:
18
26
  return isinstance(value, list)
19
27
 
20
28
 
21
29
  @register.filter
22
- def is_not_list(value):
30
+ def is_not_list(value: Any) -> bool:
23
31
  return not is_list(value)
24
32
 
25
33
 
26
34
  @register.filter
27
- def is_list_or_tuple(value):
35
+ def is_list_or_tuple(value: Any) -> bool:
28
36
  return isinstance(value, (list, tuple))
29
37
 
30
38
 
31
39
  @register.filter
32
- def is_not_list_or_tuple(value):
40
+ def is_not_list_or_tuple(value: Any) -> bool:
33
41
  return not is_list_or_tuple(value)
34
42
 
35
43
 
36
44
  @register.filter
37
- def is_tuple(value):
45
+ def is_tuple(value: Any) -> bool:
38
46
  return isinstance(value, tuple)
39
47
 
40
48
 
41
49
  @register.filter
42
- def is_not_tuple(value):
43
- return not is_tuple(value)
50
+ def is_not_tuple(value: Any) -> bool:
51
+ return not is_tuple(value)
@@ -5,7 +5,6 @@ import tempfile
5
5
  from django.test import Client, override_settings, TestCase
6
6
 
7
7
  from django_spire.auth.user.models import AuthUser
8
- from django_spire.auth.user.tests.factories import create_super_user
9
8
 
10
9
 
11
10
  TEMP_MEDIA_ROOT = tempfile.mkdtemp()
@@ -19,4 +18,5 @@ class BaseTestCase(TestCase):
19
18
  self.super_user = AuthUser.objects.create_superuser(
20
19
  username='stratus',
21
20
  )
21
+
22
22
  self.client.force_login(self.super_user)
@@ -0,0 +1,43 @@
1
+ from __future__ import annotations
2
+
3
+ from django.test import TestCase, override_settings
4
+
5
+ from django_spire.conf import Settings, settings
6
+
7
+
8
+ class TestSettings(TestCase):
9
+ def test_getattr_returns_django_setting(self) -> None:
10
+ assert settings.DEBUG is not None
11
+
12
+ def test_getattr_returns_django_spire_default(self) -> None:
13
+ result = settings.DJANGO_SPIRE_DEFAULT_THEME
14
+
15
+ assert result == 'default-light'
16
+
17
+ def test_getattr_returns_none_for_unknown_setting(self) -> None:
18
+ result = settings.NONEXISTENT_SETTING_NAME
19
+
20
+ assert result is None
21
+
22
+ @override_settings(DJANGO_SPIRE_DEFAULT_THEME='custom-dark')
23
+ def test_django_setting_overrides_default(self) -> None:
24
+ fresh_settings = Settings()
25
+
26
+ result = fresh_settings.DJANGO_SPIRE_DEFAULT_THEME
27
+
28
+ assert result == 'custom-dark'
29
+
30
+ @override_settings(
31
+ DJANGO_SPIRE_AUTH_CONTROLLERS={'custom_app': 'custom.path.Controller'}
32
+ )
33
+ def test_auth_controllers_merges_dicts(self) -> None:
34
+ fresh_settings = Settings()
35
+
36
+ result = fresh_settings.DJANGO_SPIRE_AUTH_CONTROLLERS
37
+
38
+ assert 'custom_app' in result
39
+ assert 'ai_chat' in result
40
+
41
+ def test_settings_instance_exists(self) -> None:
42
+ assert settings is not None
43
+ assert isinstance(settings, Settings)
@@ -0,0 +1,28 @@
1
+ from __future__ import annotations
2
+
3
+ from django.test import TestCase
4
+
5
+ from django_spire.consts import (
6
+ __VERSION__,
7
+ MAINTENANCE_MODE_SETTINGS_NAME,
8
+ NOTIFICATION_THROTTLE_RATE_PER_MINUTE_SETTINGS_NAME,
9
+ )
10
+
11
+
12
+ class TestConsts(TestCase):
13
+ def test_maintenance_mode_settings_name(self) -> None:
14
+ assert MAINTENANCE_MODE_SETTINGS_NAME == 'MAINTENANCE_MODE'
15
+
16
+ def test_notification_throttle_rate_settings_name(self) -> None:
17
+ assert NOTIFICATION_THROTTLE_RATE_PER_MINUTE_SETTINGS_NAME == 'NOTIFICATION_THROTTLE_RATE_PER_MINUTE'
18
+
19
+ def test_version_is_string(self) -> None:
20
+ assert isinstance(__VERSION__, str)
21
+
22
+ def test_version_format(self) -> None:
23
+ parts = __VERSION__.split('.')
24
+
25
+ assert len(parts) == 3
26
+
27
+ for part in parts:
28
+ assert part.isdigit()
@@ -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)