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,7 +1,9 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.conf import settings
2
4
 
3
5
  from django_spire.consts import NOTIFICATION_THROTTLE_RATE_PER_MINUTE_SETTINGS_NAME
4
6
 
5
7
 
6
8
  def get_throttle_sleep_time() -> float:
7
- return float(getattr(settings, NOTIFICATION_THROTTLE_RATE_PER_MINUTE_SETTINGS_NAME) / 60)
9
+ return float(getattr(settings, NOTIFICATION_THROTTLE_RATE_PER_MINUTE_SETTINGS_NAME) / 60)
django_spire/settings.py CHANGED
@@ -1,3 +1,6 @@
1
+ from __future__ import annotations
2
+
3
+
1
4
  DJANGO_SPIRE_AUTH_CONTROLLERS = {
2
5
  'ai_chat': 'django_spire.ai.chat.auth.controller.BaseAiChatAuthController',
3
6
  'help_desk': 'django_spire.help_desk.auth.controller.BaseHelpDeskAuthController',
@@ -20,12 +20,12 @@ class ThemeContextProcessorTests(TestCase):
20
20
  with patch('django_spire.conf.settings.DJANGO_SPIRE_DEFAULT_THEME', 'gruvbox-dark'):
21
21
  context = theme_context(request)
22
22
 
23
- self.assertEqual(context['DJANGO_SPIRE_DEFAULT_THEME'], 'gruvbox-dark')
24
- self.assertIn('theme', context)
23
+ assert context['DJANGO_SPIRE_DEFAULT_THEME'] == 'gruvbox-dark'
24
+ assert 'theme' in context
25
25
 
26
26
  data = context['theme']
27
- self.assertEqual(data['family'], 'gruvbox')
28
- self.assertEqual(data['mode'], 'dark')
27
+ assert data['family'] == 'gruvbox'
28
+ assert data['mode'] == 'dark'
29
29
 
30
30
  def test_theme_context_with_cookie(self) -> None:
31
31
  request = self.factory.get('/')
@@ -34,10 +34,10 @@ class ThemeContextProcessorTests(TestCase):
34
34
 
35
35
  context = theme_context(request)
36
36
 
37
- self.assertIn('theme', context)
37
+ assert 'theme' in context
38
38
  data = context['theme']
39
- self.assertEqual(data['family'], 'one-dark')
40
- self.assertEqual(data['mode'], 'light')
39
+ assert data['family'] == 'one-dark'
40
+ assert data['mode'] == 'light'
41
41
 
42
42
  def test_theme_context_invalid_cookie(self) -> None:
43
43
  request = self.factory.get('/')
@@ -48,30 +48,32 @@ class ThemeContextProcessorTests(TestCase):
48
48
  context = theme_context(request)
49
49
 
50
50
  data = context['theme']
51
- self.assertEqual(data['family'], 'gruvbox')
52
- self.assertEqual(data['mode'], 'dark')
51
+ assert data['family'] == 'gruvbox'
52
+ assert data['mode'] == 'dark'
53
53
 
54
54
  def test_theme_context_path_setting(self) -> None:
55
55
  request = self.factory.get('/')
56
56
  request.COOKIES = {}
57
57
 
58
58
  path = '/custom/path/{family}/{mode}.css'
59
+
59
60
  with patch('django_spire.conf.settings.DJANGO_SPIRE_THEME_PATH', path):
60
61
  context = theme_context(request)
61
62
 
62
- self.assertEqual(context['DJANGO_SPIRE_THEME_PATH'], path)
63
+ assert context['DJANGO_SPIRE_THEME_PATH'] == path
63
64
 
64
65
 
65
66
  class ThemeContextProcessorIntegrationTests(BaseTestCase):
66
67
  def setUp(self) -> None:
67
68
  super().setUp()
69
+
68
70
  self.factory = RequestFactory()
69
71
 
70
72
  def test_theme_context_with_authenticated_client(self) -> None:
71
73
  cookie_name = get_theme_cookie_name()
72
74
  self.client.cookies[cookie_name] = 'gruvbox-dark'
73
75
  response = self.client.get('/')
74
- self.assertEqual(response.status_code, 200)
76
+ assert response.status_code == 200
75
77
 
76
78
  def test_theme_context_with_media_root_override(self) -> None:
77
79
  request = self.factory.get('/')
@@ -80,5 +82,5 @@ class ThemeContextProcessorIntegrationTests(BaseTestCase):
80
82
 
81
83
  context = theme_context(request)
82
84
  data = context['theme']
83
- self.assertEqual(data['family'], 'material')
84
- self.assertEqual(data['mode'], 'light')
85
+ assert data['family'] == 'material'
86
+ assert data['mode'] == 'light'
@@ -21,9 +21,9 @@ class ThemeEnumTests(TestCase):
21
21
  }
22
22
 
23
23
  actual = {family.value for family in ThemeFamily}
24
- self.assertEqual(actual, expected)
24
+ assert actual == expected
25
25
 
26
26
  def test_theme_modes(self) -> None:
27
27
  expected = {'dark', 'light'}
28
28
  actual = {mode.value for mode in ThemeMode}
29
- self.assertEqual(actual, expected)
29
+ assert actual == expected
@@ -21,7 +21,7 @@ class ThemeFilesystemValidationTests(TestCase):
21
21
  if not self.base_path.exists():
22
22
  self.skipTest(f'Themes directory does not exist: {self.base_path}. Create it to enable filesystem validation tests.')
23
23
 
24
- self.assertTrue(self.base_path.is_dir(), f'Themes path is not a directory: {self.base_path}')
24
+ assert self.base_path.is_dir(), f'Themes path is not a directory: {self.base_path}'
25
25
 
26
26
  def test_all_theme_families_have_directories(self) -> None:
27
27
  if not self.base_path.exists():
@@ -175,7 +175,4 @@ class ThemeFilesystemValidationTests(TestCase):
175
175
  file = directory / filename
176
176
 
177
177
  if file.exists():
178
- self.assertEqual(
179
- file.name, filename,
180
- f'Filename {file.name} does not match expected {filename}'
181
- )
178
+ assert file.name == filename, f'Filename {file.name} does not match expected {filename}'
@@ -10,39 +10,39 @@ class ThemeIntegrationTests(BaseTestCase):
10
10
  for theme in Theme.get_available():
11
11
  stylesheet = theme.stylesheet
12
12
 
13
- self.assertIn(f'{theme.family.value}/', stylesheet)
14
- self.assertIn(f'app-{theme.mode.value}.css', stylesheet)
13
+ assert f'{theme.family.value}/' in stylesheet
14
+ assert f'app-{theme.mode.value}.css' in stylesheet
15
15
 
16
16
  def test_roundtrip_conversion(self) -> None:
17
17
  for theme in Theme.get_available():
18
18
  string = theme.value
19
19
  recovered = Theme.from_string(string)
20
- self.assertEqual(theme, recovered)
20
+ assert theme == recovered
21
21
 
22
22
  dictionary = theme.to_dict()
23
23
  recovered2 = Theme.from_string(dictionary['full'])
24
- self.assertEqual(theme, recovered2)
24
+ assert theme == recovered2
25
25
 
26
26
  def test_display_names_complete(self) -> None:
27
27
  for family in ThemeFamily:
28
- self.assertIn(family, Theme.FAMILY_DISPLAY_NAMES)
28
+ assert family in Theme.FAMILY_DISPLAY_NAMES
29
29
  display_name = Theme.FAMILY_DISPLAY_NAMES[family]
30
- self.assertIsInstance(display_name, str)
31
- self.assertTrue(len(display_name) > 0)
30
+ assert isinstance(display_name, str)
31
+ assert len(display_name) > 0
32
32
 
33
33
  def test_theme_integration_with_authenticated_user(self) -> None:
34
34
  available = Theme.get_available()
35
- self.assertGreater(len(available), 0)
35
+ assert len(available) > 0
36
36
 
37
37
  for theme in available[:3]:
38
38
  self.client.cookies['app-theme'] = theme.value
39
39
  response = self.client.get('/')
40
- self.assertEqual(response.status_code, 200)
40
+ assert response.status_code == 200
41
41
 
42
42
  def test_theme_media_integration(self) -> None:
43
43
  theme = Theme(family=ThemeFamily.GRUVBOX, mode='dark')
44
44
  stylesheet = theme.stylesheet
45
45
 
46
- self.assertIn('django_spire/css/themes/', stylesheet)
47
- self.assertIn('gruvbox', stylesheet)
48
- self.assertIn('app-dark.css', stylesheet)
46
+ assert 'django_spire/css/themes/' in stylesheet
47
+ assert 'gruvbox' in stylesheet
48
+ assert 'app-dark.css' in stylesheet
@@ -14,37 +14,39 @@ class ThemeModelTests(TestCase):
14
14
  def test_dataclass_fields(self) -> None:
15
15
  theme_fields = {f.name: f.type for f in fields(Theme)}
16
16
 
17
- self.assertEqual(set(theme_fields.keys()), {'family', 'mode'})
18
- self.assertEqual(theme_fields['family'], 'ThemeFamily')
19
- self.assertEqual(theme_fields['mode'], 'ThemeMode')
17
+ assert set(theme_fields.keys()) == {'family', 'mode'}
18
+ assert theme_fields['family'] == 'ThemeFamily'
19
+ assert theme_fields['mode'] == 'ThemeMode'
20
20
 
21
21
  def test_class_variables(self) -> None:
22
- self.assertEqual(Theme.DEFAULT_FAMILY, ThemeFamily.DEFAULT)
23
- self.assertEqual(Theme.DEFAULT_MODE, ThemeMode.LIGHT)
24
- self.assertEqual(Theme.SEPARATOR, '-')
22
+ assert Theme.DEFAULT_FAMILY == ThemeFamily.DEFAULT
23
+ assert Theme.DEFAULT_MODE == ThemeMode.LIGHT
24
+ assert Theme.SEPARATOR == '-'
25
25
 
26
26
  for family in ThemeFamily:
27
- self.assertIn(family, Theme.FAMILY_DISPLAY_NAMES)
27
+ assert family in Theme.FAMILY_DISPLAY_NAMES
28
28
 
29
29
  def test_theme_initialization_with_enums(self) -> None:
30
30
  theme = Theme(family=ThemeFamily.GRUVBOX, mode=ThemeMode.DARK)
31
- self.assertEqual(theme.family, ThemeFamily.GRUVBOX)
32
- self.assertEqual(theme.mode, ThemeMode.DARK)
31
+ assert theme.family == ThemeFamily.GRUVBOX
32
+ assert theme.mode == ThemeMode.DARK
33
33
 
34
34
  def test_theme_initialization_with_strings(self) -> None:
35
35
  theme = Theme(family='gruvbox', mode='dark')
36
- self.assertEqual(theme.family, ThemeFamily.GRUVBOX)
37
- self.assertEqual(theme.mode, ThemeMode.DARK)
36
+ assert theme.family == ThemeFamily.GRUVBOX
37
+ assert theme.mode == ThemeMode.DARK
38
38
 
39
39
  def test_theme_initialization_invalid_family(self) -> None:
40
40
  with pytest.raises(ValueError) as ctx:
41
41
  Theme(family='invalid-family', mode='dark')
42
- self.assertIn('Invalid theme family', str(ctx.value))
42
+
43
+ assert 'Invalid theme family' in str(ctx.value)
43
44
 
44
45
  def test_theme_initialization_invalid_mode(self) -> None:
45
46
  with pytest.raises(ValueError) as ctx:
46
47
  Theme(family='gruvbox', mode='invalid-mode')
47
- self.assertIn('Invalid theme mode', str(ctx.value))
48
+
49
+ assert 'Invalid theme mode' in str(ctx.value)
48
50
 
49
51
  def test_from_string_valid(self) -> None:
50
52
  cases = [
@@ -57,23 +59,23 @@ class ThemeModelTests(TestCase):
57
59
  for string, family, mode in cases:
58
60
  with self.subTest(string=string):
59
61
  theme = Theme.from_string(string)
60
- self.assertEqual(theme.family, family)
61
- self.assertEqual(theme.mode, mode)
62
+ assert theme.family == family
63
+ assert theme.mode == mode
62
64
 
63
65
  def test_from_string_empty_with_default(self) -> None:
64
66
  default = Theme(family=ThemeFamily.GRUVBOX, mode=ThemeMode.DARK)
65
67
  theme = Theme.from_string('', default=default)
66
- self.assertEqual(theme, default)
68
+ assert theme == default
67
69
 
68
70
  def test_from_string_empty_without_default(self) -> None:
69
71
  theme = Theme.from_string('')
70
- self.assertEqual(theme.family, Theme.DEFAULT_FAMILY)
71
- self.assertEqual(theme.mode, Theme.DEFAULT_MODE)
72
+ assert theme.family == Theme.DEFAULT_FAMILY
73
+ assert theme.mode == Theme.DEFAULT_MODE
72
74
 
73
75
  def test_from_string_invalid_with_default(self) -> None:
74
76
  default = Theme(family=ThemeFamily.GRUVBOX, mode=ThemeMode.DARK)
75
77
  theme = Theme.from_string('invalid', default=default)
76
- self.assertEqual(theme, default)
78
+ assert theme == default
77
79
 
78
80
  def test_from_string_invalid_without_default(self) -> None:
79
81
  with pytest.raises(ValueError):
@@ -83,35 +85,35 @@ class ThemeModelTests(TestCase):
83
85
  available = Theme.get_available()
84
86
 
85
87
  count = len(ThemeFamily) * len(ThemeMode)
86
- self.assertEqual(len(available), count)
88
+ assert len(available) == count
87
89
 
88
90
  for family in ThemeFamily:
89
91
  for mode in ThemeMode:
90
92
  theme = Theme(family=family, mode=mode)
91
- self.assertIn(theme, available)
93
+ assert theme in available
92
94
 
93
95
  def test_get_default(self) -> None:
94
96
  default = Theme.get_default()
95
- self.assertEqual(default.family, Theme.DEFAULT_FAMILY)
96
- self.assertEqual(default.mode, Theme.DEFAULT_MODE)
97
+ assert default.family == Theme.DEFAULT_FAMILY
98
+ assert default.mode == Theme.DEFAULT_MODE
97
99
 
98
100
  def test_display_property(self) -> None:
99
101
  theme = Theme(family=ThemeFamily.GRUVBOX, mode=ThemeMode.DARK)
100
- self.assertEqual(theme.display, 'Gruvbox - Dark')
102
+ assert theme.display == 'Gruvbox - Dark'
101
103
 
102
104
  theme = Theme(family=ThemeFamily.ONE_DARK, mode=ThemeMode.LIGHT)
103
- self.assertEqual(theme.display, 'One Dark Pro - Light')
105
+ assert theme.display == 'One Dark Pro - Light'
104
106
 
105
107
  def test_family_display_property(self) -> None:
106
108
  theme = Theme(family=ThemeFamily.ONE_DARK, mode=ThemeMode.DARK)
107
- self.assertEqual(theme.family_display, 'One Dark Pro')
109
+ assert theme.family_display == 'One Dark Pro'
108
110
 
109
111
  def test_is_dark_property(self) -> None:
110
112
  dark = Theme(family=ThemeFamily.GRUVBOX, mode=ThemeMode.DARK)
111
- self.assertTrue(dark.is_dark)
113
+ assert dark.is_dark
112
114
 
113
115
  light = Theme(family=ThemeFamily.GRUVBOX, mode=ThemeMode.LIGHT)
114
- self.assertFalse(light.is_dark)
116
+ assert not light.is_dark
115
117
 
116
118
  def test_stylesheet_property(self) -> None:
117
119
  cases = [
@@ -124,7 +126,7 @@ class ThemeModelTests(TestCase):
124
126
  for family, mode, path in cases:
125
127
  with self.subTest(family=family, mode=mode):
126
128
  theme = Theme(family=family, mode=mode)
127
- self.assertEqual(theme.stylesheet, path)
129
+ assert theme.stylesheet == path
128
130
 
129
131
  def test_value_property(self) -> None:
130
132
  cases = [
@@ -137,7 +139,7 @@ class ThemeModelTests(TestCase):
137
139
  for family, mode, value in cases:
138
140
  with self.subTest(family=family, mode=mode):
139
141
  theme = Theme(family=family, mode=mode)
140
- self.assertEqual(theme.value, value)
142
+ assert theme.value == value
141
143
 
142
144
  def test_to_dict(self) -> None:
143
145
  theme = Theme(family=ThemeFamily.GRUVBOX, mode=ThemeMode.DARK)
@@ -153,15 +155,15 @@ class ThemeModelTests(TestCase):
153
155
  'stylesheet'
154
156
  }
155
157
 
156
- self.assertEqual(set(result.keys()), keys)
158
+ assert set(result.keys()) == keys
157
159
 
158
- self.assertEqual(result['display'], 'Gruvbox - Dark')
159
- self.assertEqual(result['family'], 'gruvbox')
160
- self.assertEqual(result['family_display'], 'Gruvbox')
161
- self.assertEqual(result['full'], 'gruvbox-dark')
162
- self.assertTrue(result['is_dark'])
163
- self.assertEqual(result['mode'], 'dark')
164
- self.assertEqual(result['stylesheet'], 'django_spire/css/themes/gruvbox/app-dark.css')
160
+ assert result['display'] == 'Gruvbox - Dark'
161
+ assert result['family'] == 'gruvbox'
162
+ assert result['family_display'] == 'Gruvbox'
163
+ assert result['full'] == 'gruvbox-dark'
164
+ assert result['is_dark']
165
+ assert result['mode'] == 'dark'
166
+ assert result['stylesheet'] == 'django_spire/css/themes/gruvbox/app-dark.css'
165
167
 
166
168
  def test_theme_immutability(self) -> None:
167
169
  theme = Theme(family=ThemeFamily.GRUVBOX, mode=ThemeMode.DARK)
@@ -22,30 +22,30 @@ class ThemeViewTests(TestCase):
22
22
  request = self.factory.get('/django_spire/theme/json/get_config/')
23
23
  response = json_views.get_config(request)
24
24
 
25
- self.assertIsInstance(response, JsonResponse)
26
- self.assertEqual(response.status_code, HTTPStatus.OK)
25
+ assert isinstance(response, JsonResponse)
26
+ assert response.status_code == HTTPStatus.OK
27
27
 
28
28
  data = json.loads(response.content)
29
- self.assertTrue(data['success'])
29
+ assert data['success']
30
30
 
31
31
  config = data['data']
32
- self.assertIn('families', config)
33
- self.assertIn('default_family', config)
34
- self.assertIn('default_mode', config)
35
- self.assertIn('separator', config)
32
+ assert 'families' in config
33
+ assert 'default_family' in config
34
+ assert 'default_mode' in config
35
+ assert 'separator' in config
36
36
 
37
37
  for family in ThemeFamily:
38
- self.assertIn(family.value, config['families'])
38
+ assert family.value in config['families']
39
39
  family_config = config['families'][family.value]
40
- self.assertIn('name', family_config)
41
- self.assertIn('modes', family_config)
42
- self.assertEqual(set(family_config['modes']), {'dark', 'light'})
40
+ assert 'name' in family_config
41
+ assert 'modes' in family_config
42
+ assert set(family_config['modes']) == {'dark', 'light'}
43
43
 
44
44
  def test_get_config_cache_header(self) -> None:
45
45
  _ = self.factory.get('/django_spire/theme/json/get_config/')
46
46
 
47
47
  with patch('django_spire.theme.views.json_views.cache_page') as _:
48
- self.assertTrue(hasattr(json_views.get_config, '__wrapped__'))
48
+ assert hasattr(json_views.get_config, '__wrapped__')
49
49
 
50
50
  def test_set_theme_success(self) -> None:
51
51
  request = self.factory.post(
@@ -55,20 +55,20 @@ class ThemeViewTests(TestCase):
55
55
  )
56
56
  response = json_views.set_theme(request)
57
57
 
58
- self.assertIsInstance(response, JsonResponse)
59
- self.assertEqual(response.status_code, HTTPStatus.OK)
58
+ assert isinstance(response, JsonResponse)
59
+ assert response.status_code == HTTPStatus.OK
60
60
 
61
61
  data = json.loads(response.content)
62
- self.assertTrue(data['success'])
63
- self.assertIn('theme', data)
62
+ assert data['success']
63
+ assert 'theme' in data
64
64
 
65
65
  data = data['theme']
66
- self.assertEqual(data['family'], 'gruvbox')
67
- self.assertEqual(data['mode'], 'dark')
66
+ assert data['family'] == 'gruvbox'
67
+ assert data['mode'] == 'dark'
68
68
 
69
69
  cookie_name = get_theme_cookie_name()
70
- self.assertIn(cookie_name, response.cookies)
71
- self.assertEqual(response.cookies[cookie_name].value, 'gruvbox-dark')
70
+ assert cookie_name in response.cookies
71
+ assert response.cookies[cookie_name].value == 'gruvbox-dark'
72
72
 
73
73
  def test_set_theme_missing_theme(self) -> None:
74
74
  request = self.factory.post(
@@ -78,11 +78,11 @@ class ThemeViewTests(TestCase):
78
78
  )
79
79
  response = json_views.set_theme(request)
80
80
 
81
- self.assertEqual(response.status_code, HTTPStatus.BAD_REQUEST)
81
+ assert response.status_code == HTTPStatus.BAD_REQUEST
82
82
  data = json.loads(response.content)
83
- self.assertFalse(data['success'])
84
- self.assertIn('error', data)
85
- self.assertEqual(data['error'], 'Theme is required')
83
+ assert not data['success']
84
+ assert 'error' in data
85
+ assert data['error'] == 'Theme is required'
86
86
 
87
87
  def test_set_theme_invalid_theme(self) -> None:
88
88
  request = self.factory.post(
@@ -92,11 +92,11 @@ class ThemeViewTests(TestCase):
92
92
  )
93
93
  response = json_views.set_theme(request)
94
94
 
95
- self.assertEqual(response.status_code, HTTPStatus.BAD_REQUEST)
95
+ assert response.status_code == HTTPStatus.BAD_REQUEST
96
96
  data = json.loads(response.content)
97
- self.assertFalse(data['success'])
98
- self.assertIn('error', data)
99
- self.assertIn('Invalid theme', data['error'])
97
+ assert not data['success']
98
+ assert 'error' in data
99
+ assert 'Invalid theme' in data['error']
100
100
 
101
101
 
102
102
  class ThemeViewIntegrationTests(BaseTestCase):
@@ -107,14 +107,14 @@ class ThemeViewIntegrationTests(BaseTestCase):
107
107
  content_type='application/json'
108
108
  )
109
109
 
110
- self.assertEqual(response.status_code, HTTPStatus.OK)
110
+ assert response.status_code == HTTPStatus.OK
111
111
  data = json.loads(response.content)
112
- self.assertTrue(data['success'])
112
+ assert data['success']
113
113
 
114
114
  def test_get_config_with_authenticated_client(self) -> None:
115
115
  response = self.client.get('/django_spire/theme/json/get_config/')
116
- self.assertEqual(response.status_code, HTTPStatus.OK)
116
+ assert response.status_code == HTTPStatus.OK
117
117
 
118
118
  data = json.loads(response.content)
119
- self.assertTrue(data['success'])
120
- self.assertIn('families', data['data'])
119
+ assert data['success']
120
+ assert 'families' in data['data']
@@ -1,7 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.urls import path
2
4
 
3
5
  from django_spire.theme.views import json_views
4
6
 
7
+
5
8
  app_name = 'json'
6
9
 
7
10
  urlpatterns = [
@@ -1,7 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.urls import path
2
4
 
3
5
  from django_spire.theme.views import page_views
4
6
 
7
+
5
8
  app_name = 'page'
6
9
 
7
10
  urlpatterns = [
django_spire/urls.py CHANGED
@@ -1,21 +1,25 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.apps import apps
2
- from django.urls import path, include
4
+ from django.urls import include, path
3
5
 
4
- app_name = 'django_spire'
6
+ from django_spire.exceptions import DjangoSpireConfigurationError
5
7
 
6
- urlpatterns = []
7
8
 
8
- for app_config in apps.get_app_configs():
9
- if hasattr(app_config, 'URLPATTERNS_INCLUDE') and hasattr(app_config, 'URLPATTERNS_NAMESPACE'):
10
- urlpatterns.append(
11
- path(
12
- f'{app_config.URLPATTERNS_NAMESPACE}/',
13
- include(
14
- app_config.URLPATTERNS_INCLUDE,
15
- namespace=app_config.URLPATTERNS_NAMESPACE,
16
- )
17
- ),
9
+ app_name = 'django_spire'
10
+
11
+ urlpatterns = [
12
+ path(
13
+ f'{app_config.URLPATTERNS_NAMESPACE}/',
14
+ include(
15
+ app_config.URLPATTERNS_INCLUDE,
16
+ namespace=app_config.URLPATTERNS_NAMESPACE,
18
17
  )
18
+ )
19
+ for app_config in apps.get_app_configs()
20
+ if hasattr(app_config, 'URLPATTERNS_INCLUDE') and hasattr(app_config, 'URLPATTERNS_NAMESPACE')
21
+ ]
19
22
 
20
- if len(urlpatterns) == 0:
21
- raise Exception('You need to have at least one app installed to use Django Spire.')
23
+ if not urlpatterns:
24
+ message = 'You need to have at least one app installed to use Django Spire.'
25
+ raise DjangoSpireConfigurationError(message)
django_spire/utils.py CHANGED
@@ -1,5 +1,12 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.apps import apps
2
4
 
5
+ from django_spire.exceptions import (
6
+ DjangoSpireInvalidClassStringError,
7
+ DjangoSpireMissingRequiredAppError
8
+ )
9
+
3
10
 
4
11
  def app_is_installed(app_label: str) -> bool:
5
12
  return app_label in list(apps.app_configs.keys())
@@ -7,17 +14,19 @@ def app_is_installed(app_label: str) -> bool:
7
14
 
8
15
  def check_required_apps(app_label: str) -> None:
9
16
  app_config = apps.get_app_config(app_label)
17
+
10
18
  for required_app_name in app_config.REQUIRED_APPS:
11
19
  if not app_is_installed(required_app_name):
12
- raise Exception(
13
- f"{app_label} requires {required_app_name} is be in the 'INSTALLED_APPS' list before {app_label} in the django settings module.")
20
+ message = f'{app_label} requires {required_app_name} is be in the "INSTALLED_APPS" list before {app_label} in the django settings module.'
21
+ raise DjangoSpireMissingRequiredAppError(message)
14
22
 
15
23
 
16
24
  def get_class_from_string(class_string: str) -> type:
17
25
  class_parts = class_string.split('.')
18
26
 
19
27
  if len(class_parts) < 2:
20
- raise Exception(f"Class string {class_string} is not a valid class string.")
28
+ message = f'Class string {class_string} is not a valid class string.'
29
+ raise DjangoSpireInvalidClassStringError(message)
21
30
 
22
31
  module_path = '.'.join(class_parts[:-1])
23
32
  class_name = class_parts[-1]
@@ -28,4 +37,4 @@ def get_class_from_string(class_string: str) -> type:
28
37
 
29
38
 
30
39
  def get_class_name_from_class(cls: type) -> str:
31
- return cls.__module__ + '.' + cls.__qualname__
40
+ return cls.__module__ + '.' + cls.__qualname__
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-spire
3
- Version: 0.23.7
3
+ Version: 0.23.9
4
4
  Summary: A project for Django Spire
5
5
  Author-email: Brayden Carlson <braydenc@stratusadv.com>, Nathan Johnson <nathanj@stratusadv.com>
6
- License: Copyright (c) 2024 Stratus Advanced Technologies and Contributors.
6
+ License: Copyright (c) 2025 Stratus Advanced Technologies and Contributors.
7
7
 
8
8
  Permission is hereby granted, free of charge, to any person
9
9
  obtaining a copy of this software and associated documentation