django-spire 0.23.6__py3-none-any.whl → 0.23.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (541) hide show
  1. django_spire/ai/admin.py +11 -11
  2. django_spire/ai/chat/apps.py +1 -0
  3. django_spire/ai/chat/templates/django_spire/ai/chat/widget/dialog_widget.html +1 -1
  4. django_spire/ai/chat/tests/factories.py +15 -0
  5. django_spire/ai/chat/tests/test_controller.py +45 -0
  6. django_spire/ai/chat/tests/test_models.py +301 -0
  7. django_spire/ai/chat/tests/test_prompts.py +48 -0
  8. django_spire/ai/chat/tests/test_responses.py +208 -0
  9. django_spire/ai/chat/tests/test_router/test_base_chat_router.py +66 -6
  10. django_spire/ai/chat/tests/test_router/test_chat_workflow.py +73 -3
  11. django_spire/ai/chat/tests/test_router/test_integration.py +86 -6
  12. django_spire/ai/chat/tests/test_router/test_intent_decoder.py +93 -1
  13. django_spire/ai/chat/tests/test_router/test_message_intel.py +60 -1
  14. django_spire/ai/chat/tests/test_router/test_spire_chat_router.py +110 -0
  15. django_spire/ai/chat/tests/test_urls/test_json_urls.py +202 -1
  16. django_spire/ai/context/tests/__init__.py +0 -0
  17. django_spire/ai/context/tests/test_context.py +188 -0
  18. django_spire/ai/decorators.py +7 -6
  19. django_spire/ai/prompt/tests/test_bots.py +100 -10
  20. django_spire/ai/prompt/tests/test_prompt_intel.py +83 -0
  21. django_spire/ai/prompt/tests/test_prompt_tuning.py +126 -0
  22. django_spire/ai/sms/decorators.py +8 -2
  23. django_spire/ai/sms/tests/test_sms.py +240 -16
  24. django_spire/ai/sms/tests/test_sms_intel.py +42 -0
  25. django_spire/ai/sms/tests/test_webhook.py +155 -7
  26. django_spire/ai/sms/views.py +23 -24
  27. django_spire/ai/tests/test_ai.py +131 -7
  28. django_spire/auth/apps.py +4 -2
  29. django_spire/auth/controller/controller.py +36 -23
  30. django_spire/auth/controller/exceptions.py +9 -0
  31. django_spire/auth/group/admin.py +1 -0
  32. django_spire/auth/group/apps.py +2 -0
  33. django_spire/auth/group/factories.py +17 -8
  34. django_spire/auth/group/forms.py +7 -0
  35. django_spire/auth/group/tests/test_factories.py +146 -0
  36. django_spire/auth/group/tests/test_forms.py +282 -0
  37. django_spire/auth/group/tests/test_models.py +192 -0
  38. django_spire/auth/group/tests/test_querysets.py +98 -0
  39. django_spire/auth/group/tests/test_utils.py +341 -0
  40. django_spire/auth/group/tests/test_views.py +377 -0
  41. django_spire/auth/group/urls/__init__.py +3 -1
  42. django_spire/auth/group/urls/form_urls.py +2 -0
  43. django_spire/auth/group/urls/json_urls.py +3 -0
  44. django_spire/auth/group/urls/page_urls.py +2 -0
  45. django_spire/auth/group/utils.py +6 -2
  46. django_spire/auth/group/views/form_views.py +6 -3
  47. django_spire/auth/group/views/json_views.py +6 -2
  48. django_spire/auth/mfa/admin.py +2 -0
  49. django_spire/auth/mfa/apps.py +2 -0
  50. django_spire/auth/mfa/forms.py +1 -0
  51. django_spire/auth/mfa/querysets.py +9 -2
  52. django_spire/auth/mfa/tests/test_models.py +233 -0
  53. django_spire/auth/mfa/tests/test_utils.py +106 -0
  54. django_spire/auth/mfa/urls/__init__.py +2 -0
  55. django_spire/auth/mfa/urls/page_urls.py +2 -0
  56. django_spire/auth/mfa/urls/redirect_urls.py +2 -0
  57. django_spire/auth/mfa/views/page_views.py +2 -1
  58. django_spire/auth/permissions/consts.py +2 -2
  59. django_spire/auth/permissions/decorators.py +8 -8
  60. django_spire/auth/permissions/permissions.py +28 -35
  61. django_spire/auth/permissions/tests/test_decorators.py +333 -0
  62. django_spire/auth/permissions/tests/test_permissions.py +337 -0
  63. django_spire/auth/permissions/tests/test_tools.py +305 -0
  64. django_spire/auth/permissions/tools.py +21 -15
  65. django_spire/auth/seeding/seed.py +3 -0
  66. django_spire/auth/seeding/seeder.py +2 -0
  67. django_spire/auth/tests/test_controller.py +323 -0
  68. django_spire/auth/tests/test_url_endpoints.py +9 -9
  69. django_spire/auth/tests/test_views.py +406 -0
  70. django_spire/auth/urls/admin_urls.py +2 -0
  71. django_spire/auth/urls/redirect_urls.py +2 -0
  72. django_spire/auth/user/apps.py +2 -0
  73. django_spire/auth/user/forms.py +9 -0
  74. django_spire/auth/user/models.py +1 -1
  75. django_spire/auth/user/services/services.py +1 -0
  76. django_spire/auth/user/tests/factories.py +14 -13
  77. django_spire/auth/user/tests/test_factories.py +166 -2
  78. django_spire/auth/user/tests/test_forms.py +573 -0
  79. django_spire/auth/user/tests/test_models.py +257 -0
  80. django_spire/auth/user/tests/test_services.py +200 -0
  81. django_spire/auth/user/tests/test_tools.py +153 -0
  82. django_spire/auth/user/tests/test_user_factories.py +139 -0
  83. django_spire/auth/user/tests/test_views.py +363 -0
  84. django_spire/auth/user/tools.py +7 -1
  85. django_spire/auth/user/urls/form_urls.py +3 -0
  86. django_spire/auth/user/urls/page_urls.py +3 -0
  87. django_spire/auth/user/views/form_views.py +19 -10
  88. django_spire/auth/user/views/page_views.py +8 -2
  89. django_spire/auth/views/redirect_views.py +14 -9
  90. django_spire/comment/admin.py +2 -0
  91. django_spire/comment/apps.py +2 -0
  92. django_spire/comment/templatetags/comment_tags.py +1 -0
  93. django_spire/comment/tests/test_forms.py +27 -0
  94. django_spire/comment/tests/test_models.py +215 -0
  95. django_spire/comment/tests/test_querysets.py +101 -0
  96. django_spire/comment/tests/test_utils.py +90 -0
  97. django_spire/comment/urls.py +2 -0
  98. django_spire/comment/utils.py +22 -13
  99. django_spire/comment/views.py +1 -1
  100. django_spire/conf.py +8 -6
  101. django_spire/consts.py +1 -1
  102. django_spire/contrib/breadcrumb/apps.py +2 -0
  103. django_spire/contrib/breadcrumb/breadcrumbs.py +18 -18
  104. django_spire/contrib/breadcrumb/tests/test_breadcrumbs.py +198 -0
  105. django_spire/contrib/constructor/__init__.py +3 -3
  106. django_spire/contrib/constructor/constructor.py +15 -15
  107. django_spire/contrib/constructor/django_model_constructor.py +5 -4
  108. django_spire/contrib/constructor/exceptions.py +5 -3
  109. django_spire/contrib/constructor/tests/__init__.py +0 -0
  110. django_spire/contrib/constructor/tests/test_constructor.py +193 -0
  111. django_spire/contrib/form/tests/__init__.py +0 -0
  112. django_spire/contrib/form/tests/test_forms.py +203 -0
  113. django_spire/contrib/generic_views/modal_views.py +2 -1
  114. django_spire/contrib/generic_views/portal_views.py +20 -19
  115. django_spire/contrib/generic_views/tests/__init__.py +0 -0
  116. django_spire/contrib/generic_views/tests/test_views.py +459 -0
  117. django_spire/contrib/help/apps.py +2 -0
  118. django_spire/contrib/help/templatetags/help.py +1 -0
  119. django_spire/contrib/help/tests/__init__.py +0 -0
  120. django_spire/contrib/help/tests/test_templatetags.py +100 -0
  121. django_spire/contrib/options/mixins.py +6 -5
  122. django_spire/contrib/options/tests/factories.py +5 -1
  123. django_spire/contrib/options/tests/test_options.py +234 -0
  124. django_spire/contrib/ordering/exceptions.py +7 -3
  125. django_spire/contrib/ordering/mixins.py +2 -0
  126. django_spire/contrib/ordering/querysets.py +3 -1
  127. django_spire/contrib/ordering/services/processor_service.py +8 -4
  128. django_spire/contrib/ordering/services/service.py +1 -2
  129. django_spire/contrib/ordering/tests/__init__.py +0 -0
  130. django_spire/contrib/ordering/tests/test_ordering.py +165 -0
  131. django_spire/contrib/ordering/validators.py +6 -6
  132. django_spire/contrib/pagination/templatetags/pagination_tags.py +12 -5
  133. django_spire/contrib/pagination/tests/__init__.py +0 -0
  134. django_spire/contrib/pagination/tests/test_pagination.py +179 -0
  135. django_spire/contrib/performance/decorators.py +16 -6
  136. django_spire/contrib/performance/tests/__init__.py +0 -0
  137. django_spire/contrib/performance/tests/test_performance.py +107 -0
  138. django_spire/contrib/progress/__init__.py +1 -3
  139. django_spire/contrib/progress/static/django_spire/js/contrib/progress/progress.js +38 -82
  140. django_spire/contrib/queryset/enums.py +3 -1
  141. django_spire/contrib/queryset/filter_tools.py +10 -5
  142. django_spire/contrib/queryset/mixins.py +16 -16
  143. django_spire/contrib/queryset/tests/__init__.py +0 -0
  144. django_spire/contrib/queryset/tests/test_queryset.py +137 -0
  145. django_spire/contrib/seeding/field/base.py +13 -7
  146. django_spire/contrib/seeding/field/callable.py +8 -1
  147. django_spire/contrib/seeding/field/cleaners.py +5 -5
  148. django_spire/contrib/seeding/field/custom.py +20 -10
  149. django_spire/contrib/seeding/field/django/seeder.py +8 -6
  150. django_spire/contrib/seeding/field/enums.py +7 -5
  151. django_spire/contrib/seeding/field/override.py +16 -6
  152. django_spire/contrib/seeding/field/static.py +9 -2
  153. django_spire/contrib/seeding/field/tests/test_base.py +18 -14
  154. django_spire/contrib/seeding/field/tests/test_callable.py +13 -9
  155. django_spire/contrib/seeding/field/tests/test_cleaners.py +51 -38
  156. django_spire/contrib/seeding/field/tests/test_static.py +13 -9
  157. django_spire/contrib/seeding/intelligence/bots/seeder_generator_bot.py +2 -0
  158. django_spire/contrib/seeding/intelligence/intel.py +5 -1
  159. django_spire/contrib/seeding/intelligence/prompts/factory.py +6 -1
  160. django_spire/contrib/seeding/intelligence/prompts/foreign_key_selection_prompt.py +6 -1
  161. django_spire/contrib/seeding/intelligence/prompts/generate_django_model_seeder_prompts.py +2 -0
  162. django_spire/contrib/seeding/intelligence/prompts/generic_relationship_selection_prompt.py +7 -1
  163. django_spire/contrib/seeding/intelligence/prompts/hierarchical_selection_prompt.py +6 -2
  164. django_spire/contrib/seeding/intelligence/prompts/model_field_choices_prompt.py +8 -2
  165. django_spire/contrib/seeding/intelligence/prompts/negation_prompt.py +2 -0
  166. django_spire/contrib/seeding/intelligence/prompts/objective_prompt.py +6 -1
  167. django_spire/contrib/seeding/management/commands/seeding.py +9 -3
  168. django_spire/contrib/seeding/management/example.py +2 -0
  169. django_spire/contrib/seeding/model/base.py +16 -7
  170. django_spire/contrib/seeding/model/config.py +31 -15
  171. django_spire/contrib/seeding/model/django/config.py +13 -13
  172. django_spire/contrib/seeding/model/django/seeder.py +4 -4
  173. django_spire/contrib/seeding/model/django/tests/test_seeder.py +34 -23
  174. django_spire/contrib/seeding/model/enums.py +2 -0
  175. django_spire/contrib/seeding/tests/test_config.py +71 -0
  176. django_spire/contrib/seeding/tests/test_custom.py +35 -0
  177. django_spire/contrib/seeding/tests/test_enums.py +40 -0
  178. django_spire/contrib/seeding/tests/test_intel.py +32 -0
  179. django_spire/contrib/seeding/tests/test_override.py +63 -0
  180. django_spire/contrib/service/__init__.py +2 -2
  181. django_spire/contrib/service/django_model_service.py +16 -15
  182. django_spire/contrib/service/exceptions.py +5 -3
  183. django_spire/contrib/service/tests/__init__.py +0 -0
  184. django_spire/contrib/service/tests/test_service.py +153 -0
  185. django_spire/contrib/session/apps.py +2 -0
  186. django_spire/contrib/session/controller.py +48 -42
  187. django_spire/contrib/session/templatetags/session_tags.py +11 -2
  188. django_spire/contrib/session/tests/test_session_controller.py +117 -53
  189. django_spire/contrib/tests/__init__.py +0 -0
  190. django_spire/contrib/tests/test_utils.py +37 -0
  191. django_spire/contrib/utils.py +4 -1
  192. django_spire/core/apps.py +2 -0
  193. django_spire/core/converters/tests/test_to_data.py +353 -0
  194. django_spire/core/converters/tests/test_to_enums.py +61 -41
  195. django_spire/core/converters/tests/test_to_pydantic.py +138 -109
  196. django_spire/core/converters/to_data.py +29 -10
  197. django_spire/core/converters/to_enums.py +4 -2
  198. django_spire/core/converters/to_pydantic.py +22 -22
  199. django_spire/core/decorators.py +19 -6
  200. django_spire/core/forms/widgets.py +4 -0
  201. django_spire/core/maps.py +3 -1
  202. django_spire/core/middleware/maintenance.py +3 -3
  203. django_spire/core/middleware.py +8 -6
  204. django_spire/core/redirect/__init__.py +5 -0
  205. django_spire/core/redirect/generic_redirect.py +1 -2
  206. django_spire/core/redirect/tests/__init__.py +0 -0
  207. django_spire/core/redirect/tests/test_generic_redirect.py +34 -0
  208. django_spire/core/{tests/tests_redirect.py → redirect/tests/test_safe_redirect.py} +55 -81
  209. django_spire/core/shortcuts.py +3 -3
  210. django_spire/core/static/django_spire/css/app-layout.css +1 -1
  211. django_spire/core/static/django_spire/css/app-navigation.css +3 -3
  212. django_spire/core/static/django_spire/css/bootstrap-override.css +4 -0
  213. django_spire/core/tag/admin.py +12 -0
  214. django_spire/core/tag/intelligence/tag_set_bot.py +2 -0
  215. django_spire/core/tag/mixins.py +2 -0
  216. django_spire/core/tag/models.py +2 -0
  217. django_spire/core/tag/querysets.py +2 -0
  218. django_spire/core/tag/service/tag_service.py +6 -3
  219. django_spire/core/tag/tests/test_intelligence.py +9 -9
  220. django_spire/core/tag/tests/test_tags.py +44 -54
  221. django_spire/core/tag/tests/test_tools.py +191 -0
  222. django_spire/core/tag/tools.py +3 -0
  223. django_spire/core/templates/django_spire/card/card.html +5 -2
  224. django_spire/core/templates/django_spire/card/title_card.html +9 -4
  225. django_spire/core/templates/django_spire/infinite_scroll/base.html +1 -0
  226. django_spire/core/templates/django_spire/navigation/side_navigation.html +19 -24
  227. django_spire/core/templates/django_spire/page/full_page.html +46 -16
  228. django_spire/core/templates/django_spire/table/base.html +4 -2
  229. django_spire/core/templatetags/json.py +6 -2
  230. django_spire/core/templatetags/message.py +13 -8
  231. django_spire/core/templatetags/string_formating.py +8 -5
  232. django_spire/core/templatetags/tests/__init__.py +0 -0
  233. django_spire/core/templatetags/tests/test_templatetags.py +427 -0
  234. django_spire/core/templatetags/variable_types.py +17 -9
  235. django_spire/core/tests/test_cases.py +1 -1
  236. django_spire/core/tests/test_conf.py +43 -0
  237. django_spire/core/tests/test_consts.py +28 -0
  238. django_spire/core/tests/test_context_processors.py +93 -0
  239. django_spire/core/tests/test_decorators.py +95 -0
  240. django_spire/core/tests/test_django_spire_utils.py +56 -0
  241. django_spire/core/tests/test_exceptions.py +37 -0
  242. django_spire/core/tests/test_models.py +54 -0
  243. django_spire/core/tests/test_settings.py +45 -0
  244. django_spire/core/tests/test_shortcuts.py +74 -0
  245. django_spire/core/tests/test_urls.py +16 -0
  246. django_spire/core/tests/test_utils.py +58 -0
  247. django_spire/core/urls.py +4 -1
  248. django_spire/core/utils.py +12 -8
  249. django_spire/exceptions.py +16 -1
  250. django_spire/file/admin.py +4 -2
  251. django_spire/file/apps.py +8 -10
  252. django_spire/file/fields.py +7 -7
  253. django_spire/file/forms.py +1 -1
  254. django_spire/file/interfaces.py +15 -15
  255. django_spire/file/mixins.py +1 -4
  256. django_spire/file/models.py +3 -5
  257. django_spire/file/tests/factories.py +59 -0
  258. django_spire/file/tests/test_admin.py +69 -0
  259. django_spire/file/tests/test_apps.py +24 -0
  260. django_spire/file/tests/test_fields.py +114 -0
  261. django_spire/file/tests/test_forms.py +20 -0
  262. django_spire/file/tests/test_interfaces.py +183 -0
  263. django_spire/file/tests/test_models.py +82 -0
  264. django_spire/file/tests/test_querysets.py +102 -0
  265. django_spire/file/tests/test_utils.py +32 -0
  266. django_spire/file/tests/test_views.py +145 -0
  267. django_spire/file/tests/test_widgets.py +82 -0
  268. django_spire/file/tools.py +8 -2
  269. django_spire/file/views.py +7 -3
  270. django_spire/file/widgets.py +12 -12
  271. django_spire/help_desk/admin.py +15 -0
  272. django_spire/help_desk/apps.py +2 -0
  273. django_spire/help_desk/auth/controller.py +2 -0
  274. django_spire/help_desk/choices.py +2 -0
  275. django_spire/help_desk/enums.py +2 -0
  276. django_spire/help_desk/exceptions.py +31 -3
  277. django_spire/help_desk/forms.py +2 -0
  278. django_spire/help_desk/models.py +2 -0
  279. django_spire/help_desk/querysets.py +4 -1
  280. django_spire/help_desk/services/notification_service.py +26 -27
  281. django_spire/help_desk/services/service.py +2 -3
  282. django_spire/help_desk/tests/factories.py +8 -3
  283. django_spire/help_desk/tests/test_admin.py +41 -0
  284. django_spire/help_desk/tests/test_apps.py +41 -0
  285. django_spire/help_desk/tests/test_choices.py +50 -0
  286. django_spire/help_desk/tests/test_controller.py +87 -0
  287. django_spire/help_desk/tests/test_enums.py +18 -0
  288. django_spire/help_desk/tests/test_exceptions.py +37 -0
  289. django_spire/help_desk/tests/test_forms.py +89 -0
  290. django_spire/help_desk/tests/test_models.py +59 -0
  291. django_spire/help_desk/tests/test_querysets.py +38 -0
  292. django_spire/help_desk/tests/test_services/test_notification_service.py +15 -8
  293. django_spire/help_desk/tests/test_services/test_service.py +92 -0
  294. django_spire/help_desk/tests/test_urls/test_form_urls.py +6 -6
  295. django_spire/help_desk/tests/test_urls/test_page_urls.py +8 -9
  296. django_spire/help_desk/tests/test_views/test_form_views.py +46 -19
  297. django_spire/help_desk/tests/test_views/test_page_views.py +32 -9
  298. django_spire/help_desk/urls/__init__.py +4 -1
  299. django_spire/help_desk/urls/form_urls.py +3 -0
  300. django_spire/help_desk/urls/page_urls.py +3 -0
  301. django_spire/help_desk/views/form_views.py +13 -5
  302. django_spire/help_desk/views/page_views.py +11 -3
  303. django_spire/history/activity/admin.py +2 -0
  304. django_spire/history/activity/apps.py +3 -1
  305. django_spire/history/activity/mixins.py +13 -7
  306. django_spire/history/activity/models.py +6 -5
  307. django_spire/history/activity/querysets.py +2 -0
  308. django_spire/history/activity/tests/__init__.py +0 -0
  309. django_spire/history/activity/tests/test_activity.py +176 -0
  310. django_spire/history/admin.py +9 -2
  311. django_spire/history/choices.py +3 -0
  312. django_spire/history/models.py +5 -5
  313. django_spire/history/tests/test_admin.py +93 -0
  314. django_spire/history/tests/test_history.py +101 -0
  315. django_spire/history/tests/test_mixins.py +84 -0
  316. django_spire/history/viewed/admin.py +3 -1
  317. django_spire/history/viewed/apps.py +3 -1
  318. django_spire/history/viewed/models.py +2 -0
  319. django_spire/history/viewed/tests/__init__.py +0 -0
  320. django_spire/history/viewed/tests/test_viewed.py +46 -0
  321. django_spire/knowledge/auth/tests/__init__.py +0 -0
  322. django_spire/knowledge/auth/tests/test_controller.py +116 -0
  323. django_spire/knowledge/collection/admin.py +5 -1
  324. django_spire/knowledge/collection/models.py +3 -1
  325. django_spire/knowledge/collection/seeding/seed.py +1 -0
  326. django_spire/knowledge/collection/services/factory_service.py +10 -11
  327. django_spire/knowledge/collection/services/ordering_service.py +1 -2
  328. django_spire/knowledge/collection/services/service.py +5 -10
  329. django_spire/knowledge/collection/services/tag_service.py +5 -2
  330. django_spire/knowledge/collection/tests/factories.py +28 -1
  331. django_spire/knowledge/collection/tests/test_models.py +48 -0
  332. django_spire/knowledge/collection/tests/test_querysets.py +93 -0
  333. django_spire/knowledge/collection/tests/test_services/test_factory_service.py +100 -0
  334. django_spire/knowledge/collection/tests/test_services/test_services.py +160 -0
  335. django_spire/knowledge/collection/tests/test_urls/test_form_urls.py +21 -3
  336. django_spire/knowledge/collection/tests/test_urls/test_json_urls.py +39 -1
  337. django_spire/knowledge/collection/tests/test_urls/test_page_urls.py +12 -4
  338. django_spire/knowledge/collection/urls/__init__.py +3 -0
  339. django_spire/knowledge/collection/urls/form_urls.py +2 -0
  340. django_spire/knowledge/collection/urls/json_urls.py +2 -0
  341. django_spire/knowledge/collection/urls/page_urls.py +2 -0
  342. django_spire/knowledge/collection/views/form_views.py +4 -4
  343. django_spire/knowledge/collection/views/json_views.py +5 -1
  344. django_spire/knowledge/collection/views/page_views.py +5 -2
  345. django_spire/knowledge/entry/admin.py +7 -1
  346. django_spire/knowledge/entry/forms.py +2 -0
  347. django_spire/knowledge/entry/models.py +2 -0
  348. django_spire/knowledge/entry/seeding/seed.py +3 -0
  349. django_spire/knowledge/entry/services/automation_service.py +5 -4
  350. django_spire/knowledge/entry/services/factory_service.py +7 -5
  351. django_spire/knowledge/entry/services/service.py +4 -7
  352. django_spire/knowledge/entry/services/tag_service.py +0 -1
  353. django_spire/knowledge/entry/services/tool_service.py +1 -0
  354. django_spire/knowledge/entry/services/transformation_services.py +1 -5
  355. django_spire/knowledge/entry/tests/factories.py +1 -2
  356. django_spire/knowledge/entry/tests/test_factory_service.py +20 -0
  357. django_spire/knowledge/entry/tests/test_models.py +41 -0
  358. django_spire/knowledge/entry/tests/test_querysets.py +71 -0
  359. django_spire/knowledge/entry/tests/test_services.py +94 -0
  360. django_spire/knowledge/entry/tests/test_urls/test_form_urls.py +9 -14
  361. django_spire/knowledge/entry/tests/test_urls/test_json_urls.py +48 -5
  362. django_spire/knowledge/entry/tests/test_urls/test_page_urls.py +6 -8
  363. django_spire/knowledge/entry/tests/test_urls/test_template_urls.py +40 -0
  364. django_spire/knowledge/entry/urls/form_urls.py +2 -0
  365. django_spire/knowledge/entry/urls/json_urls.py +2 -0
  366. django_spire/knowledge/entry/urls/page_urls.py +2 -0
  367. django_spire/knowledge/entry/urls/template_urls.py +2 -0
  368. django_spire/knowledge/entry/version/block/choices.py +2 -0
  369. django_spire/knowledge/entry/version/block/data/data.py +1 -0
  370. django_spire/knowledge/entry/version/block/data/list/data.py +8 -13
  371. django_spire/knowledge/entry/version/block/data/list/maps.py +3 -0
  372. django_spire/knowledge/entry/version/block/data/list/meta.py +1 -2
  373. django_spire/knowledge/entry/version/block/data/list/tests/__init__.py +0 -0
  374. django_spire/knowledge/entry/version/block/data/list/tests/test_maps.py +32 -0
  375. django_spire/knowledge/entry/version/block/data/list/tests/test_meta.py +58 -0
  376. django_spire/knowledge/entry/version/block/data/maps.py +3 -6
  377. django_spire/knowledge/entry/version/block/models.py +7 -5
  378. django_spire/knowledge/entry/version/block/seeding/constants.py +5 -4
  379. django_spire/knowledge/entry/version/block/services/service.py +2 -3
  380. django_spire/knowledge/entry/version/block/tests/factories.py +4 -10
  381. django_spire/knowledge/entry/version/block/tests/test_choices.py +56 -0
  382. django_spire/knowledge/entry/version/block/tests/test_data.py +90 -0
  383. django_spire/knowledge/entry/version/block/tests/test_maps.py +37 -0
  384. django_spire/knowledge/entry/version/block/tests/test_models.py +55 -0
  385. django_spire/knowledge/entry/version/block/tests/test_querysets.py +35 -0
  386. django_spire/knowledge/entry/version/block/tests/test_services.py +65 -0
  387. django_spire/knowledge/entry/version/choices.py +2 -0
  388. django_spire/knowledge/entry/version/converters/converter.py +1 -1
  389. django_spire/knowledge/entry/version/converters/docx_converter.py +4 -7
  390. django_spire/knowledge/entry/version/converters/markdown_converter.py +20 -20
  391. django_spire/knowledge/entry/version/maps.py +4 -5
  392. django_spire/knowledge/entry/version/querysets.py +1 -1
  393. django_spire/knowledge/entry/version/seeding/seeder.py +1 -2
  394. django_spire/knowledge/entry/version/services/processor_service.py +5 -4
  395. django_spire/knowledge/entry/version/services/service.py +1 -2
  396. django_spire/knowledge/entry/version/tests/factories.py +2 -2
  397. django_spire/knowledge/entry/version/tests/test_choices.py +18 -0
  398. django_spire/knowledge/entry/version/tests/test_converters/test_docx_converter.py +56 -8
  399. django_spire/knowledge/entry/version/tests/test_converters/test_markdown_converter.py +78 -0
  400. django_spire/knowledge/entry/version/tests/test_maps.py +58 -0
  401. django_spire/knowledge/entry/version/tests/test_models.py +23 -0
  402. django_spire/knowledge/entry/version/tests/test_querysets.py +26 -0
  403. django_spire/knowledge/entry/version/tests/test_services.py +62 -0
  404. django_spire/knowledge/entry/version/tests/test_urls/test_json_urls.py +27 -8
  405. django_spire/knowledge/entry/version/tests/test_urls/test_page_urls.py +15 -8
  406. django_spire/knowledge/entry/version/tests/test_urls/test_redirect_urls.py +38 -0
  407. django_spire/knowledge/entry/version/urls/__init__.py +3 -0
  408. django_spire/knowledge/entry/version/urls/json_urls.py +2 -1
  409. django_spire/knowledge/entry/version/urls/page_urls.py +2 -0
  410. django_spire/knowledge/entry/version/urls/redirect_urls.py +2 -0
  411. django_spire/knowledge/entry/version/views/json_views.py +5 -1
  412. django_spire/knowledge/entry/version/views/page_views.py +10 -3
  413. django_spire/knowledge/entry/version/views/redirect_views.py +5 -1
  414. django_spire/knowledge/entry/views/form_views.py +16 -8
  415. django_spire/knowledge/entry/views/json_views.py +3 -1
  416. django_spire/knowledge/entry/views/page_views.py +8 -2
  417. django_spire/knowledge/entry/views/template_views.py +7 -1
  418. django_spire/knowledge/exceptions.py +2 -1
  419. django_spire/knowledge/intelligence/intel/answer_intel.py +2 -1
  420. django_spire/knowledge/intelligence/intel/entry_intel.py +0 -1
  421. django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +4 -5
  422. django_spire/knowledge/models.py +1 -2
  423. django_spire/knowledge/tests/__init__.py +0 -0
  424. django_spire/knowledge/tests/test_templatetags.py +40 -0
  425. django_spire/knowledge/tests/test_urls/__init__.py +0 -0
  426. django_spire/knowledge/tests/test_urls/test_page_urls.py +24 -0
  427. django_spire/knowledge/urls/__init__.py +2 -0
  428. django_spire/knowledge/urls/page_urls.py +2 -0
  429. django_spire/knowledge/views/page_views.py +8 -3
  430. django_spire/notification/admin.py +3 -1
  431. django_spire/notification/app/admin.py +2 -0
  432. django_spire/notification/app/apps.py +3 -1
  433. django_spire/notification/app/exceptions.py +9 -2
  434. django_spire/notification/app/models.py +8 -4
  435. django_spire/notification/app/processor.py +22 -26
  436. django_spire/notification/app/querysets.py +2 -0
  437. django_spire/notification/app/tests/__init__.py +0 -0
  438. django_spire/notification/app/tests/factories.py +34 -0
  439. django_spire/notification/app/tests/test_apps.py +24 -0
  440. django_spire/notification/app/tests/test_models.py +72 -0
  441. django_spire/notification/app/tests/test_processor.py +111 -0
  442. django_spire/notification/app/tests/test_querysets.py +90 -0
  443. django_spire/notification/app/tests/test_views/__init__.py +0 -0
  444. django_spire/notification/app/tests/test_views/test_json_views.py +48 -0
  445. django_spire/notification/app/tests/test_views/test_page_views.py +19 -0
  446. django_spire/notification/app/urls/__init__.py +3 -1
  447. django_spire/notification/app/urls/json_urls.py +6 -4
  448. django_spire/notification/app/urls/page_urls.py +4 -3
  449. django_spire/notification/app/urls/template_urls.py +4 -2
  450. django_spire/notification/apps.py +4 -1
  451. django_spire/notification/email/admin.py +5 -1
  452. django_spire/notification/email/apps.py +3 -1
  453. django_spire/notification/email/exceptions.py +4 -2
  454. django_spire/notification/email/helper.py +5 -3
  455. django_spire/notification/email/models.py +4 -0
  456. django_spire/notification/email/processor.py +19 -15
  457. django_spire/notification/email/querysets.py +3 -0
  458. django_spire/notification/email/tests/__init__.py +0 -0
  459. django_spire/notification/email/tests/factories.py +35 -0
  460. django_spire/notification/email/tests/test_apps.py +24 -0
  461. django_spire/notification/email/tests/test_models.py +52 -0
  462. django_spire/notification/email/tests/test_processor.py +92 -0
  463. django_spire/notification/email/tests/test_querysets.py +43 -0
  464. django_spire/notification/exceptions.py +17 -2
  465. django_spire/notification/managers.py +7 -1
  466. django_spire/notification/maps.py +4 -1
  467. django_spire/notification/mixins.py +2 -0
  468. django_spire/notification/models.py +3 -1
  469. django_spire/notification/processors/notification.py +12 -5
  470. django_spire/notification/processors/processor.py +2 -0
  471. django_spire/notification/processors/tests/__init__.py +0 -0
  472. django_spire/notification/processors/tests/test_notification.py +106 -0
  473. django_spire/notification/push/admin.py +10 -1
  474. django_spire/notification/push/apps.py +3 -1
  475. django_spire/notification/push/models.py +2 -3
  476. django_spire/notification/push/tests/__init__.py +0 -0
  477. django_spire/notification/push/tests/test_apps.py +24 -0
  478. django_spire/notification/push/tests/test_models.py +28 -0
  479. django_spire/notification/querysets.py +7 -1
  480. django_spire/notification/sms/admin.py +2 -0
  481. django_spire/notification/sms/apps.py +4 -1
  482. django_spire/notification/sms/automations.py +2 -0
  483. django_spire/notification/sms/choices.py +2 -0
  484. django_spire/notification/sms/exceptions.py +19 -5
  485. django_spire/notification/sms/helper.py +33 -23
  486. django_spire/notification/sms/models.py +5 -1
  487. django_spire/notification/sms/processor.py +20 -20
  488. django_spire/notification/sms/querysets.py +2 -0
  489. django_spire/notification/sms/tests/factories.py +33 -0
  490. django_spire/notification/sms/tests/test_apps.py +24 -0
  491. django_spire/notification/sms/tests/test_automation.py +38 -0
  492. django_spire/notification/sms/tests/test_choices.py +15 -0
  493. django_spire/notification/sms/tests/test_consts.py +17 -0
  494. django_spire/notification/sms/tests/test_exceptions.py +27 -0
  495. django_spire/notification/sms/tests/test_helper.py +50 -0
  496. django_spire/notification/sms/tests/test_models.py +81 -0
  497. django_spire/notification/sms/tests/test_processor.py +107 -0
  498. django_spire/notification/sms/tests/test_tools.py +25 -11
  499. django_spire/notification/sms/tools.py +16 -5
  500. django_spire/notification/sms/urls/__init__.py +3 -1
  501. django_spire/notification/sms/urls/media_urls.py +2 -0
  502. django_spire/notification/sms/views/media_views.py +14 -4
  503. django_spire/notification/tests/__init__.py +0 -0
  504. django_spire/notification/tests/factories.py +26 -0
  505. django_spire/notification/tests/test_admin.py +55 -0
  506. django_spire/notification/tests/test_apps.py +30 -0
  507. django_spire/notification/tests/test_automation.py +18 -0
  508. django_spire/notification/tests/test_choices.py +59 -0
  509. django_spire/notification/tests/test_exceptions.py +58 -0
  510. django_spire/notification/tests/test_managers.py +100 -0
  511. django_spire/notification/tests/test_maps.py +31 -0
  512. django_spire/notification/tests/test_models.py +76 -0
  513. django_spire/notification/tests/test_querysets.py +184 -0
  514. django_spire/notification/tests/test_utils.py +23 -0
  515. django_spire/notification/urls.py +3 -1
  516. django_spire/notification/utils.py +3 -1
  517. django_spire/settings.py +3 -0
  518. django_spire/theme/tests/test_context_processor.py +15 -13
  519. django_spire/theme/tests/test_enums.py +2 -2
  520. django_spire/theme/tests/test_filesystem.py +2 -5
  521. django_spire/theme/tests/test_integration.py +12 -12
  522. django_spire/theme/tests/test_model.py +40 -38
  523. django_spire/theme/tests/test_views/test_json_views.py +33 -33
  524. django_spire/theme/urls/json_urls.py +3 -0
  525. django_spire/theme/urls/page_urls.py +3 -0
  526. django_spire/urls.py +19 -15
  527. django_spire/utils.py +13 -4
  528. {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/METADATA +1 -1
  529. {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/RECORD +532 -361
  530. django_spire/contrib/options/tests/test_unit.py +0 -148
  531. django_spire/contrib/progress/views.py +0 -64
  532. django_spire/contrib/seeding/tests/test_seeding.py +0 -25
  533. django_spire/core/tests/test_templatetags.py +0 -117
  534. django_spire/core/tests/tests_shortcuts.py +0 -73
  535. django_spire/history/activity/tests.py +0 -3
  536. django_spire/history/activity/views.py +0 -3
  537. django_spire/knowledge/collection/tests/test_services/test_transformation_service.py +0 -71
  538. django_spire/notification/app/tests.py +0 -3
  539. {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/WHEEL +0 -0
  540. {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/licenses/LICENSE.md +0 -0
  541. {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,9 @@
1
+ from __future__ import annotations
2
+
1
3
  from pydantic import constr, condecimal, create_model
2
4
  from pydantic.fields import Field
3
5
 
4
- from typing import Tuple, Type, Any, Optional
6
+ from typing import Any
5
7
 
6
8
  from django.db import models
7
9
  from django.core import validators
@@ -11,13 +13,14 @@ from django_spire.core.maps import MODEL_FIELD_TYPE_TO_TYPE_MAP
11
13
 
12
14
 
13
15
  def django_to_pydantic_model(
14
- model_class: Type[models.Model],
15
- base_class: Type | None = None,
16
- include_fields: str | list| tuple | None = None,
17
- exclude_fields: str | list | tuple | None = None
16
+ model_class: type[models.Model],
17
+ base_class: type | None = None,
18
+ include_fields: str | list| tuple | None = None,
19
+ exclude_fields: str | list | tuple | None = None
18
20
  ):
19
21
  if not issubclass(model_class, models.Model):
20
- raise ValueError("model_class must be a subclass of django.db.models.Model")
22
+ message = 'model_class must be a subclass of django.db.models.Model'
23
+ raise TypeError(message)
21
24
 
22
25
  if include_fields is None:
23
26
  include_fields = []
@@ -47,7 +50,6 @@ def django_to_pydantic_model(
47
50
 
48
51
 
49
52
  class DjangoToPydanticFieldConverter:
50
-
51
53
  def __init__(self, model_field: models.Field):
52
54
  self.model_field = model_field
53
55
  self.kwargs = {}
@@ -60,8 +62,8 @@ class DjangoToPydanticFieldConverter:
60
62
  def bool_to_json_schema(value: bool):
61
63
  if value:
62
64
  return "true"
63
- else:
64
- return "false"
65
+
66
+ return "false"
65
67
 
66
68
  @property
67
69
  def field_handlers(self):
@@ -80,22 +82,20 @@ class DjangoToPydanticFieldConverter:
80
82
  str
81
83
  )
82
84
 
83
- def _build_char_field(self) -> Type:
85
+ def _build_char_field(self) -> type:
84
86
  if self.model_field.max_length:
85
87
  return constr(max_length=self.model_field.max_length)
86
88
  return str
87
89
 
88
- def _build_date_field(self) -> Type:
89
- self.kwargs['example'] = '2022-01-01'
90
+ def _build_date_field(self) -> type:
90
91
  self.kwargs['json_schema_extra']['example'] = '2022-01-01'
91
92
  return self._base_type()
92
93
 
93
- def _build_date_time_field(self) -> Type:
94
- self.kwargs['example'] = '2022-01-01 13:37:00'
94
+ def _build_date_time_field(self) -> type:
95
95
  self.kwargs['json_schema_extra']['example'] = '2022-01-01 13:37:00'
96
96
  return self._base_type()
97
97
 
98
- def _build_integer_field(self) -> Type:
98
+ def _build_integer_field(self) -> type:
99
99
  for validator in self.model_field.validators:
100
100
  if isinstance(validator, validators.MinValueValidator):
101
101
  self.kwargs['json_schema_extra']['greater_than'] = validator.limit_value
@@ -104,7 +104,7 @@ class DjangoToPydanticFieldConverter:
104
104
 
105
105
  return self._base_type()
106
106
 
107
- def _build_decimal_field(self) -> Type:
107
+ def _build_decimal_field(self) -> type:
108
108
  self.kwargs['json_schema_extra']['example'] = '0.00'
109
109
  self.kwargs['json_schema_extra']['max_digits'] = self.model_field.max_digits
110
110
  self.kwargs['json_schema_extra']['decimal_places'] = self.model_field.decimal_places
@@ -118,21 +118,19 @@ class DjangoToPydanticFieldConverter:
118
118
  enum_name = f"{self.model_field.name.capitalize()}Enum"
119
119
  return django_choices_to_enums(enum_name, self.model_field.choices)
120
120
 
121
- def build_field(self) -> Tuple[Type, Any]:
121
+ def build_field(self) -> tuple[type, Any]:
122
122
  """Build and return the Pydantic field type and Field object."""
123
123
  return self.field_type, Field(**self.kwargs)
124
124
 
125
125
  def _build_metadata(self):
126
-
127
126
  # Cannot set a default when providing option to the LLM
127
+
128
128
  # if self.model_field.default is not models.NOT_PROVIDED:
129
129
  # if callable(self.model_field.default):
130
130
  # self.kwargs['default'] = self.model_field.default()
131
131
  # else:
132
132
  # self.kwargs['default'] = self.model_field.default
133
133
 
134
- self.kwargs['required'] = not self.model_field.null
135
-
136
134
  if self.model_field.null and self.model_field.default is models.NOT_PROVIDED:
137
135
  self.kwargs['default'] = None
138
136
 
@@ -140,6 +138,7 @@ class DjangoToPydanticFieldConverter:
140
138
  self.kwargs['description'] = str(self.model_field.help_text)
141
139
 
142
140
  self.kwargs['json_schema_extra'] = {
141
+ 'required': self.bool_to_json_schema(not self.model_field.null),
143
142
  'is_unique': self.bool_to_json_schema(self.model_field.unique),
144
143
  # 'is_required': self.bool_to_json_schema(not self.model_field.null),
145
144
  'field_name': self.model_field.name
@@ -151,7 +150,7 @@ class DjangoToPydanticFieldConverter:
151
150
  f"{value} ({label})" for value, label in self.model_field.choices
152
151
  ) + "."
153
152
 
154
- def _get_pydantic_type(self) -> Type:
153
+ def _get_pydantic_type(self) -> type:
155
154
  if self.model_field.choices:
156
155
  return self._build_enum_type()
157
156
 
@@ -164,6 +163,7 @@ class DjangoToPydanticFieldConverter:
164
163
 
165
164
  def _wrap_nullable(self):
166
165
  pass
166
+
167
167
  # This works better for the LLM
168
168
  # if self.model_field.null:
169
- # self.field_type = Optional[self.field_type]
169
+ # self.field_type = Optional[self.field_type]
@@ -1,25 +1,38 @@
1
- from django.db import connections
1
+ from __future__ import annotations
2
+
2
3
  from functools import wraps
3
4
 
4
- from django.http import JsonResponse
5
+ from typing import Callable, ParamSpec, TypeVar
6
+
7
+ from django.db import connections
8
+ from django.http import HttpRequest, HttpResponse, JsonResponse
9
+
5
10
 
11
+ P = ParamSpec('P')
12
+ T = TypeVar('T')
6
13
 
7
- def close_db_connections(func):
14
+
15
+ def close_db_connections(func: Callable[P, T]) -> Callable[P, T]:
8
16
  @wraps(func)
9
- def inner(*args, **kwargs):
17
+ def inner(*args: P.args, **kwargs: P.kwargs) -> T:
10
18
  try:
11
19
  return func(*args, **kwargs)
12
20
  finally:
13
21
  connections.close_all()
22
+
14
23
  return inner
15
24
 
16
25
 
17
- def valid_ajax_request_required(method):
26
+ def valid_ajax_request_required(
27
+ method: Callable[..., HttpResponse]
28
+ ) -> Callable[..., HttpResponse]:
18
29
  @wraps(method)
19
- def wrapper(request, *args, **kwargs):
30
+ def wrapper(request: HttpRequest, *args, **kwargs) -> HttpResponse:
20
31
  if request.method != 'POST' and request.content_type != 'application/json':
21
32
  return JsonResponse(
22
33
  {'type': 'error', 'message': 'Invalid Request'}
23
34
  )
35
+
24
36
  return method(request, *args, **kwargs)
37
+
25
38
  return wrapper
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import json
2
4
  import uuid
3
5
 
@@ -7,12 +9,14 @@ from django import forms
7
9
  class JsonTreeWidget(forms.Textarea):
8
10
  def __init__(self, *args, **kwargs):
9
11
  super().__init__(*args, **kwargs)
12
+
10
13
  self.template_name = 'django_spire/forms/widgets/json_tree_widget.html'
11
14
 
12
15
  def get_context(self, *args, **kwargs):
13
16
  context = super().get_context(*args, **kwargs)
14
17
  context['open_dropdowns'] = True
15
18
  context['widget_render_uuid'] = uuid.uuid4()
19
+
16
20
  if context['widget']['value'] is None:
17
21
  context['json_tree_dict'] = {}
18
22
  else:
django_spire/core/maps.py CHANGED
@@ -1,6 +1,8 @@
1
+ from __future__ import annotations
2
+
1
3
  import datetime
2
- from decimal import Decimal
3
4
 
5
+ from decimal import Decimal
4
6
  from uuid import UUID
5
7
 
6
8
  from django.db import models
@@ -2,10 +2,10 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING
4
4
 
5
- from django.conf import settings
6
- from django.template.response import TemplateResponse
5
+ # from django.conf import settings
6
+ # from django.template.response import TemplateResponse
7
7
 
8
- from django_spire.consts import MAINTENANCE_MODE_SETTINGS_NAME
8
+ # from django_spire.consts import MAINTENANCE_MODE_SETTINGS_NAME
9
9
 
10
10
  if TYPE_CHECKING:
11
11
  from django.core.handlers.wsgi import WSGIRequest
@@ -1,21 +1,22 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING
3
+ from typing import TYPE_CHECKING, Callable
4
4
 
5
- from django.conf import settings
6
- from django.template.response import TemplateResponse
5
+ # from django.conf import settings
6
+ # from django.template.response import TemplateResponse
7
7
 
8
- from django_spire.consts import MAINTENANCE_MODE_SETTINGS_NAME
8
+ # from django_spire.consts import MAINTENANCE_MODE_SETTINGS_NAME
9
9
 
10
10
  if TYPE_CHECKING:
11
11
  from django.core.handlers.wsgi import WSGIRequest
12
+ from django.http import HttpResponse
12
13
 
13
14
 
14
15
  class MaintenanceMiddleware:
15
- def __init__(self, get_response):
16
+ def __init__(self, get_response: Callable[[WSGIRequest], HttpResponse]) -> None:
16
17
  self.get_response = get_response
17
18
 
18
- def __call__(self, request: WSGIRequest):
19
+ def __call__(self, request: WSGIRequest) -> HttpResponse:
19
20
  # maintenance_mode = getattr(settings, MAINTENANCE_MODE_SETTINGS_NAME)
20
21
  #
21
22
  # if maintenance_mode is None:
@@ -27,4 +28,5 @@ class MaintenanceMiddleware:
27
28
  # template='django_spire/page/maintenance_page.html',
28
29
  # )
29
30
  #
31
+
30
32
  return self.get_response(request)
@@ -1 +1,6 @@
1
1
  from django_spire.core.redirect.safe_redirect import safe_redirect_url
2
+
3
+
4
+ __all__ = [
5
+ 'safe_redirect_url'
6
+ ]
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from operator import attrgetter
3
4
  from typing import Any
4
5
 
5
6
  from django.http import HttpResponse
@@ -7,8 +8,6 @@ from django.urls import reverse
7
8
 
8
9
 
9
10
  def reverse_generic_relation(content_object: Any, **kwargs) -> HttpResponse | None:
10
- from operator import attrgetter
11
-
12
11
  model_name = content_object.__class__.__name__.lower()
13
12
 
14
13
  CONTENT_OBJECT_URL_MAP = {
File without changes
@@ -0,0 +1,34 @@
1
+ from __future__ import annotations
2
+
3
+ import pytest
4
+
5
+ from unittest.mock import MagicMock
6
+
7
+ from django.test import TestCase
8
+
9
+ from django_spire.core.redirect.generic_redirect import reverse_generic_relation
10
+
11
+
12
+ class TestReverseGenericRelation(TestCase):
13
+ def test_model_not_in_map_raises_key_error(self) -> None:
14
+ """
15
+ Since CONTENT_OBJECT_URL_MAP is empty by default,
16
+ any model lookup raises KeyError.
17
+ """
18
+
19
+ content_object = MagicMock()
20
+ content_object.__class__.__name__ = 'UnknownModel'
21
+
22
+ with pytest.raises(KeyError):
23
+ reverse_generic_relation(content_object)
24
+
25
+ def test_uses_lowercase_model_name(self) -> None:
26
+ """Verifies the model name is lowercased for map lookup."""
27
+
28
+ content_object = MagicMock()
29
+ content_object.__class__.__name__ = 'MyModel'
30
+
31
+ with pytest.raises(KeyError) as exc_info:
32
+ reverse_generic_relation(content_object)
33
+
34
+ assert exc_info.value.args[0] == 'mymodel'
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from django.test import override_settings, RequestFactory
4
- from django.urls import reverse
5
4
 
6
5
  from django_spire.core.redirect.safe_redirect import (
7
6
  is_url_valid_and_safe,
@@ -15,53 +14,28 @@ class UrlsTestCase(BaseTestCase):
15
14
  def setUp(self) -> None:
16
15
  super().setUp()
17
16
 
18
- # def test_resolve_valid_url(self) -> None:
19
- # valid_url = 'dummy:home'
20
- # resolved_url = resolve_url(valid_url)
21
- # self.assertEqual(resolved_url, reverse(valid_url))
17
+ def test_is_url_valid_and_safe_empty_url(self) -> None:
18
+ assert is_url_valid_and_safe('', {'localhost'}) is False
22
19
 
23
- def test_resolve_invalid_url(self) -> None:
24
- invalid_url = 'invalid:url'
25
- resolved_url = resolve_url(invalid_url)
26
- self.assertEqual(resolved_url, invalid_url)
20
+ def test_is_url_valid_and_safe_invalid_host(self) -> None:
21
+ url = 'http://invalid.com'
27
22
 
28
- def test_is_url_valid_and_safe_empty_url(self) -> None:
29
- self.assertFalse(
30
- is_url_valid_and_safe(
31
- '',
32
- {'localhost'}
33
- )
34
- )
23
+ assert is_url_valid_and_safe(url, {'example.com'}) is False
35
24
 
36
25
  def test_is_url_valid_and_safe_invalid_scheme(self) -> None:
37
26
  url = 'ftp://example.com'
38
27
 
39
- self.assertFalse(
40
- is_url_valid_and_safe(
41
- url,
42
- {'example.com'}
43
- )
44
- )
45
-
46
- def test_is_url_valid_and_safe_invalid_host(self) -> None:
47
- url = 'http://invalid.com'
48
-
49
- self.assertFalse(
50
- is_url_valid_and_safe(
51
- url,
52
- {'example.com'}
53
- )
54
- )
28
+ assert is_url_valid_and_safe(url, {'example.com'}) is False
55
29
 
56
30
  def test_is_url_valid_and_safe_valid_url(self) -> None:
57
31
  url = 'http://example.com'
58
32
 
59
- self.assertTrue(
60
- is_url_valid_and_safe(
61
- url,
62
- {'example.com'}
63
- )
64
- )
33
+ assert is_url_valid_and_safe(url, {'example.com'}) is True
34
+
35
+ def test_resolve_invalid_url(self) -> None:
36
+ invalid_url = 'invalid:url'
37
+ resolved_url = resolve_url(invalid_url)
38
+ assert resolved_url == invalid_url
65
39
 
66
40
 
67
41
  @override_settings(ALLOWED_HOSTS=['example.com'])
@@ -71,25 +45,25 @@ class ReturnUrlTestCase(BaseTestCase):
71
45
 
72
46
  self.request_factory = RequestFactory()
73
47
 
74
- def test_safe_redirect_url_valid_return_url(self) -> None:
48
+ def test_safe_redirect_url_invalid_return_url(self) -> None:
75
49
  request = self.request_factory.get(
76
50
  '/some_path',
77
- {'return_url': 'http://example.com/valid'}
51
+ {'return_url': 'http://invalid.com'}
78
52
  )
79
53
 
80
54
  request.META['HTTP_HOST'] = 'example.com'
81
55
  response = safe_redirect_url(request)
82
- self.assertEqual(response, 'http://example.com/valid')
56
+ assert response == '/'
83
57
 
84
- def test_safe_redirect_url_invalid_return_url(self) -> None:
58
+ def test_safe_redirect_url_return_url_with_encoded_characters(self) -> None:
85
59
  request = self.request_factory.get(
86
60
  '/some_path',
87
- {'return_url': 'http://invalid.com'}
61
+ {'return_url': 'http://example.com/valid%20path'}
88
62
  )
89
63
 
90
64
  request.META['HTTP_HOST'] = 'example.com'
91
65
  response = safe_redirect_url(request)
92
- self.assertEqual(response, '/')
66
+ assert response == 'http://example.com/valid%20path'
93
67
 
94
68
  def test_safe_redirect_url_return_url_with_invalid_characters(self) -> None:
95
69
  request = self.request_factory.get(
@@ -99,17 +73,17 @@ class ReturnUrlTestCase(BaseTestCase):
99
73
 
100
74
  request.META['HTTP_HOST'] = 'example.com'
101
75
  response = safe_redirect_url(request)
102
- self.assertEqual(response, '/')
76
+ assert response == '/'
103
77
 
104
- def test_safe_redirect_url_return_url_with_encoded_characters(self) -> None:
78
+ def test_safe_redirect_url_return_url_with_javascript_code(self) -> None:
105
79
  request = self.request_factory.get(
106
80
  '/some_path',
107
- {'return_url': 'http://example.com/valid%20path'}
81
+ {'return_url': 'javascript:alert("XSS")'}
108
82
  )
109
83
 
110
84
  request.META['HTTP_HOST'] = 'example.com'
111
85
  response = safe_redirect_url(request)
112
- self.assertEqual(response, 'http://example.com/valid%20path')
86
+ assert response == '/'
113
87
 
114
88
  def test_safe_redirect_url_return_url_with_utf8_characters(self) -> None:
115
89
  request = self.request_factory.get(
@@ -119,17 +93,17 @@ class ReturnUrlTestCase(BaseTestCase):
119
93
 
120
94
  request.META['HTTP_HOST'] = 'example.com'
121
95
  response = safe_redirect_url(request)
122
- self.assertEqual(response, 'http://example.com/ümlaut')
96
+ assert response == 'http://example.com/ümlaut'
123
97
 
124
- def test_safe_redirect_url_return_url_with_javascript_code(self) -> None:
98
+ def test_safe_redirect_url_valid_return_url(self) -> None:
125
99
  request = self.request_factory.get(
126
100
  '/some_path',
127
- {'return_url': 'javascript:alert("XSS")'}
101
+ {'return_url': 'http://example.com/valid'}
128
102
  )
129
103
 
130
104
  request.META['HTTP_HOST'] = 'example.com'
131
105
  response = safe_redirect_url(request)
132
- self.assertEqual(response, '/')
106
+ assert response == 'http://example.com/valid'
133
107
 
134
108
 
135
109
  @override_settings(ALLOWED_HOSTS=['example.com'])
@@ -139,15 +113,13 @@ class RefererTestCase(BaseTestCase):
139
113
 
140
114
  self.request_factory = RequestFactory()
141
115
 
142
- def test_safe_redirect_url_valid_return_url(self) -> None:
143
- request = self.request_factory.get(
144
- '/some_path',
145
- {'return_url': 'http://example.com/valid'}
146
- )
147
-
116
+ def test_safe_redirect_url_invalid_referer(self) -> None:
117
+ request = self.request_factory.get('/some_path')
148
118
  request.META['HTTP_HOST'] = 'example.com'
119
+ request.META['HTTP_REFERER'] = 'http://invalid.com'
120
+
149
121
  response = safe_redirect_url(request)
150
- self.assertEqual(response, 'http://example.com/valid')
122
+ assert response == '/'
151
123
 
152
124
  def test_safe_redirect_url_invalid_return_url(self) -> None:
153
125
  request = self.request_factory.get(
@@ -157,30 +129,22 @@ class RefererTestCase(BaseTestCase):
157
129
 
158
130
  request.META['HTTP_HOST'] = 'example.com'
159
131
  response = safe_redirect_url(request)
160
- self.assertEqual(response, '/')
132
+ assert response == '/'
161
133
 
162
- def test_safe_redirect_url_valid_referer(self) -> None:
163
- request = self.request_factory.get('/some_path')
164
- request.META['HTTP_HOST'] = 'example.com'
165
- request.META['HTTP_REFERER'] = 'http://example.com/valid'
166
-
167
- response = safe_redirect_url(request)
168
- self.assertEqual(response, 'http://example.com/valid')
169
-
170
- def test_safe_redirect_url_invalid_referer(self) -> None:
134
+ def test_safe_redirect_url_no_return_url_or_referer(self) -> None:
171
135
  request = self.request_factory.get('/some_path')
172
136
  request.META['HTTP_HOST'] = 'example.com'
173
- request.META['HTTP_REFERER'] = 'http://invalid.com'
174
137
 
175
138
  response = safe_redirect_url(request)
176
- self.assertEqual(response, '/')
139
+ assert response == '/'
177
140
 
178
- def test_safe_redirect_url_no_return_url_or_referer(self) -> None:
141
+ def test_safe_redirect_url_referer_with_encoded_characters(self) -> None:
179
142
  request = self.request_factory.get('/some_path')
180
143
  request.META['HTTP_HOST'] = 'example.com'
144
+ request.META['HTTP_REFERER'] = 'http://example.com/valid%20path'
181
145
 
182
146
  response = safe_redirect_url(request)
183
- self.assertEqual(response, '/')
147
+ assert response == 'http://example.com/valid%20path'
184
148
 
185
149
  def test_safe_redirect_url_referer_with_invalid_characters(self) -> None:
186
150
  request = self.request_factory.get('/some_path')
@@ -188,7 +152,7 @@ class RefererTestCase(BaseTestCase):
188
152
  request.META['HTTP_REFERER'] = 'http://example.com/invalid<>chars'
189
153
 
190
154
  response = safe_redirect_url(request)
191
- self.assertEqual(response, '/')
155
+ assert response == '/'
192
156
 
193
157
  def test_safe_redirect_url_referer_with_javascript_code(self) -> None:
194
158
  request = self.request_factory.get('/some_path')
@@ -196,20 +160,30 @@ class RefererTestCase(BaseTestCase):
196
160
  request.META['HTTP_REFERER'] = 'javascript:alert("XSS")'
197
161
 
198
162
  response = safe_redirect_url(request)
199
- self.assertEqual(response, '/')
163
+ assert response == '/'
200
164
 
201
- def test_safe_redirect_url_referer_with_encoded_characters(self) -> None:
165
+ def test_safe_redirect_url_referer_with_utf8_characters(self) -> None:
202
166
  request = self.request_factory.get('/some_path')
203
167
  request.META['HTTP_HOST'] = 'example.com'
204
- request.META['HTTP_REFERER'] = 'http://example.com/valid%20path'
168
+ request.META['HTTP_REFERER'] = 'http://example.com/ümlaut'
205
169
 
206
170
  response = safe_redirect_url(request)
207
- self.assertEqual(response, 'http://example.com/valid%20path')
171
+ assert response == 'http://example.com/ümlaut'
208
172
 
209
- def test_safe_redirect_url_referer_with_utf8_characters(self) -> None:
173
+ def test_safe_redirect_url_valid_referer(self) -> None:
210
174
  request = self.request_factory.get('/some_path')
211
175
  request.META['HTTP_HOST'] = 'example.com'
212
- request.META['HTTP_REFERER'] = 'http://example.com/ümlaut'
176
+ request.META['HTTP_REFERER'] = 'http://example.com/valid'
213
177
 
214
178
  response = safe_redirect_url(request)
215
- self.assertEqual(response, 'http://example.com/ümlaut')
179
+ assert response == 'http://example.com/valid'
180
+
181
+ def test_safe_redirect_url_valid_return_url(self) -> None:
182
+ request = self.request_factory.get(
183
+ '/some_path',
184
+ {'return_url': 'http://example.com/valid'}
185
+ )
186
+
187
+ request.META['HTTP_HOST'] = 'example.com'
188
+ response = safe_redirect_url(request)
189
+ assert response == 'http://example.com/valid'
@@ -56,7 +56,7 @@ def get_object_or_none(model: type[T], pk: int, **kwargs) -> Model:
56
56
  return None
57
57
 
58
58
 
59
- def process_request_body(request: HttpRequest, key='data') -> Any:
59
+ def process_request_body(request: HttpRequest, key: str = 'data') -> Any:
60
60
  """
61
61
  Processes the HTTP request body and returns the 'data' field from the parsed JSON.
62
62
 
@@ -68,8 +68,8 @@ def process_request_body(request: HttpRequest, key='data') -> Any:
68
68
 
69
69
  if key:
70
70
  return json.loads(body_unicode)[key]
71
- else:
72
- return json.loads(body_unicode)
71
+
72
+ return json.loads(body_unicode)
73
73
 
74
74
 
75
75
  def model_object_from_app_label(
@@ -2,7 +2,7 @@
2
2
  --app-footer-height: 70px;
3
3
  --app-mobile-navigation-width: 250px;
4
4
  --app-side-navigation-width: 250px;
5
- --app-side-navigation-top-content-height: 230px;
5
+ --app-side-navigation-top-content-height: 100px;
6
6
  --app-top-navigation-height: 80px;
7
7
 
8
8
  --app-content-height: calc(100vh - var(--app-top-navigation-height) );
@@ -82,13 +82,13 @@
82
82
  }
83
83
 
84
84
  .side-navigation-fade-bottom {
85
- background: linear-gradient(to top, var(--app-side-navigation-bg-color) 0%, transparent 100%);
86
- height: 60px;
85
+ background: linear-gradient(to top, var(--app-side-navigation-bg-color) 0%, var(--app-side-navigation-bg-color) 20%, transparent 75%);
86
+ height: 20%;
87
87
  pointer-events: none;
88
88
  }
89
89
 
90
90
  .side-navigation-fade-top {
91
- background: linear-gradient(to bottom, var(--app-side-navigation-bg-color) 0%, transparent 100%);
91
+ background: linear-gradient(to bottom, var(--app-side-navigation-bg-color) 0%, var(--app-side-navigation-bg-color) 20%, transparent 100%);
92
92
  height: 60px;
93
93
  pointer-events: none;
94
94
  }
@@ -287,6 +287,10 @@
287
287
  color: var(--app-default-text-color);
288
288
  }
289
289
 
290
+ .table-container thead.sticky-top {
291
+ z-index: 1;
292
+ }
293
+
290
294
  .ce-block__content [data-placeholder]:empty::before,
291
295
  .ce-block__content [data-placeholder][data-empty="true"]::before {
292
296
  color: var(--app-primary) !important;
@@ -0,0 +1,12 @@
1
+ from __future__ import annotations
2
+
3
+ from django.contrib import admin
4
+
5
+ from django_spire.core.tag.models import Tag
6
+
7
+
8
+ @admin.register(Tag)
9
+ class TagAdmin(admin.ModelAdmin):
10
+ list_display = ('name',)
11
+ ordering = ('name',)
12
+ search_fields = ('name',)
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from dandy import Bot, Prompt, BaseListIntel
2
4
  from django.utils.text import slugify
3
5
 
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.db import models
2
4
 
3
5
  from django_spire.core.tag.models import Tag