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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (541) hide show
  1. django_spire/ai/admin.py +11 -11
  2. django_spire/ai/chat/apps.py +1 -0
  3. django_spire/ai/chat/templates/django_spire/ai/chat/widget/dialog_widget.html +1 -1
  4. django_spire/ai/chat/tests/factories.py +15 -0
  5. django_spire/ai/chat/tests/test_controller.py +45 -0
  6. django_spire/ai/chat/tests/test_models.py +301 -0
  7. django_spire/ai/chat/tests/test_prompts.py +48 -0
  8. django_spire/ai/chat/tests/test_responses.py +208 -0
  9. django_spire/ai/chat/tests/test_router/test_base_chat_router.py +66 -6
  10. django_spire/ai/chat/tests/test_router/test_chat_workflow.py +73 -3
  11. django_spire/ai/chat/tests/test_router/test_integration.py +86 -6
  12. django_spire/ai/chat/tests/test_router/test_intent_decoder.py +93 -1
  13. django_spire/ai/chat/tests/test_router/test_message_intel.py +60 -1
  14. django_spire/ai/chat/tests/test_router/test_spire_chat_router.py +110 -0
  15. django_spire/ai/chat/tests/test_urls/test_json_urls.py +202 -1
  16. django_spire/ai/context/tests/__init__.py +0 -0
  17. django_spire/ai/context/tests/test_context.py +188 -0
  18. django_spire/ai/decorators.py +7 -6
  19. django_spire/ai/prompt/tests/test_bots.py +100 -10
  20. django_spire/ai/prompt/tests/test_prompt_intel.py +83 -0
  21. django_spire/ai/prompt/tests/test_prompt_tuning.py +126 -0
  22. django_spire/ai/sms/decorators.py +8 -2
  23. django_spire/ai/sms/tests/test_sms.py +240 -16
  24. django_spire/ai/sms/tests/test_sms_intel.py +42 -0
  25. django_spire/ai/sms/tests/test_webhook.py +155 -7
  26. django_spire/ai/sms/views.py +23 -24
  27. django_spire/ai/tests/test_ai.py +131 -7
  28. django_spire/auth/apps.py +4 -2
  29. django_spire/auth/controller/controller.py +36 -23
  30. django_spire/auth/controller/exceptions.py +9 -0
  31. django_spire/auth/group/admin.py +1 -0
  32. django_spire/auth/group/apps.py +2 -0
  33. django_spire/auth/group/factories.py +17 -8
  34. django_spire/auth/group/forms.py +7 -0
  35. django_spire/auth/group/tests/test_factories.py +146 -0
  36. django_spire/auth/group/tests/test_forms.py +282 -0
  37. django_spire/auth/group/tests/test_models.py +192 -0
  38. django_spire/auth/group/tests/test_querysets.py +98 -0
  39. django_spire/auth/group/tests/test_utils.py +341 -0
  40. django_spire/auth/group/tests/test_views.py +377 -0
  41. django_spire/auth/group/urls/__init__.py +3 -1
  42. django_spire/auth/group/urls/form_urls.py +2 -0
  43. django_spire/auth/group/urls/json_urls.py +3 -0
  44. django_spire/auth/group/urls/page_urls.py +2 -0
  45. django_spire/auth/group/utils.py +6 -2
  46. django_spire/auth/group/views/form_views.py +6 -3
  47. django_spire/auth/group/views/json_views.py +6 -2
  48. django_spire/auth/mfa/admin.py +2 -0
  49. django_spire/auth/mfa/apps.py +2 -0
  50. django_spire/auth/mfa/forms.py +1 -0
  51. django_spire/auth/mfa/querysets.py +9 -2
  52. django_spire/auth/mfa/tests/test_models.py +233 -0
  53. django_spire/auth/mfa/tests/test_utils.py +106 -0
  54. django_spire/auth/mfa/urls/__init__.py +2 -0
  55. django_spire/auth/mfa/urls/page_urls.py +2 -0
  56. django_spire/auth/mfa/urls/redirect_urls.py +2 -0
  57. django_spire/auth/mfa/views/page_views.py +2 -1
  58. django_spire/auth/permissions/consts.py +2 -2
  59. django_spire/auth/permissions/decorators.py +8 -8
  60. django_spire/auth/permissions/permissions.py +28 -35
  61. django_spire/auth/permissions/tests/test_decorators.py +333 -0
  62. django_spire/auth/permissions/tests/test_permissions.py +337 -0
  63. django_spire/auth/permissions/tests/test_tools.py +305 -0
  64. django_spire/auth/permissions/tools.py +21 -15
  65. django_spire/auth/seeding/seed.py +3 -0
  66. django_spire/auth/seeding/seeder.py +2 -0
  67. django_spire/auth/tests/test_controller.py +323 -0
  68. django_spire/auth/tests/test_url_endpoints.py +9 -9
  69. django_spire/auth/tests/test_views.py +406 -0
  70. django_spire/auth/urls/admin_urls.py +2 -0
  71. django_spire/auth/urls/redirect_urls.py +2 -0
  72. django_spire/auth/user/apps.py +2 -0
  73. django_spire/auth/user/forms.py +9 -0
  74. django_spire/auth/user/models.py +1 -1
  75. django_spire/auth/user/services/services.py +1 -0
  76. django_spire/auth/user/tests/factories.py +14 -13
  77. django_spire/auth/user/tests/test_factories.py +166 -2
  78. django_spire/auth/user/tests/test_forms.py +573 -0
  79. django_spire/auth/user/tests/test_models.py +257 -0
  80. django_spire/auth/user/tests/test_services.py +200 -0
  81. django_spire/auth/user/tests/test_tools.py +153 -0
  82. django_spire/auth/user/tests/test_user_factories.py +139 -0
  83. django_spire/auth/user/tests/test_views.py +363 -0
  84. django_spire/auth/user/tools.py +7 -1
  85. django_spire/auth/user/urls/form_urls.py +3 -0
  86. django_spire/auth/user/urls/page_urls.py +3 -0
  87. django_spire/auth/user/views/form_views.py +19 -10
  88. django_spire/auth/user/views/page_views.py +8 -2
  89. django_spire/auth/views/redirect_views.py +14 -9
  90. django_spire/comment/admin.py +2 -0
  91. django_spire/comment/apps.py +2 -0
  92. django_spire/comment/templatetags/comment_tags.py +1 -0
  93. django_spire/comment/tests/test_forms.py +27 -0
  94. django_spire/comment/tests/test_models.py +215 -0
  95. django_spire/comment/tests/test_querysets.py +101 -0
  96. django_spire/comment/tests/test_utils.py +90 -0
  97. django_spire/comment/urls.py +2 -0
  98. django_spire/comment/utils.py +22 -13
  99. django_spire/comment/views.py +1 -1
  100. django_spire/conf.py +8 -6
  101. django_spire/consts.py +1 -1
  102. django_spire/contrib/breadcrumb/apps.py +2 -0
  103. django_spire/contrib/breadcrumb/breadcrumbs.py +18 -18
  104. django_spire/contrib/breadcrumb/tests/test_breadcrumbs.py +198 -0
  105. django_spire/contrib/constructor/__init__.py +3 -3
  106. django_spire/contrib/constructor/constructor.py +15 -15
  107. django_spire/contrib/constructor/django_model_constructor.py +5 -4
  108. django_spire/contrib/constructor/exceptions.py +5 -3
  109. django_spire/contrib/constructor/tests/__init__.py +0 -0
  110. django_spire/contrib/constructor/tests/test_constructor.py +193 -0
  111. django_spire/contrib/form/tests/__init__.py +0 -0
  112. django_spire/contrib/form/tests/test_forms.py +203 -0
  113. django_spire/contrib/generic_views/modal_views.py +2 -1
  114. django_spire/contrib/generic_views/portal_views.py +20 -19
  115. django_spire/contrib/generic_views/tests/__init__.py +0 -0
  116. django_spire/contrib/generic_views/tests/test_views.py +459 -0
  117. django_spire/contrib/help/apps.py +2 -0
  118. django_spire/contrib/help/templatetags/help.py +1 -0
  119. django_spire/contrib/help/tests/__init__.py +0 -0
  120. django_spire/contrib/help/tests/test_templatetags.py +100 -0
  121. django_spire/contrib/options/mixins.py +6 -5
  122. django_spire/contrib/options/tests/factories.py +5 -1
  123. django_spire/contrib/options/tests/test_options.py +234 -0
  124. django_spire/contrib/ordering/exceptions.py +7 -3
  125. django_spire/contrib/ordering/mixins.py +2 -0
  126. django_spire/contrib/ordering/querysets.py +3 -1
  127. django_spire/contrib/ordering/services/processor_service.py +8 -4
  128. django_spire/contrib/ordering/services/service.py +1 -2
  129. django_spire/contrib/ordering/tests/__init__.py +0 -0
  130. django_spire/contrib/ordering/tests/test_ordering.py +165 -0
  131. django_spire/contrib/ordering/validators.py +6 -6
  132. django_spire/contrib/pagination/templatetags/pagination_tags.py +12 -5
  133. django_spire/contrib/pagination/tests/__init__.py +0 -0
  134. django_spire/contrib/pagination/tests/test_pagination.py +179 -0
  135. django_spire/contrib/performance/decorators.py +16 -6
  136. django_spire/contrib/performance/tests/__init__.py +0 -0
  137. django_spire/contrib/performance/tests/test_performance.py +107 -0
  138. django_spire/contrib/progress/__init__.py +1 -3
  139. django_spire/contrib/progress/static/django_spire/js/contrib/progress/progress.js +38 -82
  140. django_spire/contrib/queryset/enums.py +3 -1
  141. django_spire/contrib/queryset/filter_tools.py +10 -5
  142. django_spire/contrib/queryset/mixins.py +16 -16
  143. django_spire/contrib/queryset/tests/__init__.py +0 -0
  144. django_spire/contrib/queryset/tests/test_queryset.py +137 -0
  145. django_spire/contrib/seeding/field/base.py +13 -7
  146. django_spire/contrib/seeding/field/callable.py +8 -1
  147. django_spire/contrib/seeding/field/cleaners.py +5 -5
  148. django_spire/contrib/seeding/field/custom.py +20 -10
  149. django_spire/contrib/seeding/field/django/seeder.py +8 -6
  150. django_spire/contrib/seeding/field/enums.py +7 -5
  151. django_spire/contrib/seeding/field/override.py +16 -6
  152. django_spire/contrib/seeding/field/static.py +9 -2
  153. django_spire/contrib/seeding/field/tests/test_base.py +18 -14
  154. django_spire/contrib/seeding/field/tests/test_callable.py +13 -9
  155. django_spire/contrib/seeding/field/tests/test_cleaners.py +51 -38
  156. django_spire/contrib/seeding/field/tests/test_static.py +13 -9
  157. django_spire/contrib/seeding/intelligence/bots/seeder_generator_bot.py +2 -0
  158. django_spire/contrib/seeding/intelligence/intel.py +5 -1
  159. django_spire/contrib/seeding/intelligence/prompts/factory.py +6 -1
  160. django_spire/contrib/seeding/intelligence/prompts/foreign_key_selection_prompt.py +6 -1
  161. django_spire/contrib/seeding/intelligence/prompts/generate_django_model_seeder_prompts.py +2 -0
  162. django_spire/contrib/seeding/intelligence/prompts/generic_relationship_selection_prompt.py +7 -1
  163. django_spire/contrib/seeding/intelligence/prompts/hierarchical_selection_prompt.py +6 -2
  164. django_spire/contrib/seeding/intelligence/prompts/model_field_choices_prompt.py +8 -2
  165. django_spire/contrib/seeding/intelligence/prompts/negation_prompt.py +2 -0
  166. django_spire/contrib/seeding/intelligence/prompts/objective_prompt.py +6 -1
  167. django_spire/contrib/seeding/management/commands/seeding.py +9 -3
  168. django_spire/contrib/seeding/management/example.py +2 -0
  169. django_spire/contrib/seeding/model/base.py +16 -7
  170. django_spire/contrib/seeding/model/config.py +31 -15
  171. django_spire/contrib/seeding/model/django/config.py +13 -13
  172. django_spire/contrib/seeding/model/django/seeder.py +4 -4
  173. django_spire/contrib/seeding/model/django/tests/test_seeder.py +34 -23
  174. django_spire/contrib/seeding/model/enums.py +2 -0
  175. django_spire/contrib/seeding/tests/test_config.py +71 -0
  176. django_spire/contrib/seeding/tests/test_custom.py +35 -0
  177. django_spire/contrib/seeding/tests/test_enums.py +40 -0
  178. django_spire/contrib/seeding/tests/test_intel.py +32 -0
  179. django_spire/contrib/seeding/tests/test_override.py +63 -0
  180. django_spire/contrib/service/__init__.py +2 -2
  181. django_spire/contrib/service/django_model_service.py +16 -15
  182. django_spire/contrib/service/exceptions.py +5 -3
  183. django_spire/contrib/service/tests/__init__.py +0 -0
  184. django_spire/contrib/service/tests/test_service.py +153 -0
  185. django_spire/contrib/session/apps.py +2 -0
  186. django_spire/contrib/session/controller.py +48 -42
  187. django_spire/contrib/session/templatetags/session_tags.py +11 -2
  188. django_spire/contrib/session/tests/test_session_controller.py +117 -53
  189. django_spire/contrib/tests/__init__.py +0 -0
  190. django_spire/contrib/tests/test_utils.py +37 -0
  191. django_spire/contrib/utils.py +4 -1
  192. django_spire/core/apps.py +2 -0
  193. django_spire/core/converters/tests/test_to_data.py +353 -0
  194. django_spire/core/converters/tests/test_to_enums.py +61 -41
  195. django_spire/core/converters/tests/test_to_pydantic.py +138 -109
  196. django_spire/core/converters/to_data.py +29 -10
  197. django_spire/core/converters/to_enums.py +4 -2
  198. django_spire/core/converters/to_pydantic.py +22 -22
  199. django_spire/core/decorators.py +19 -6
  200. django_spire/core/forms/widgets.py +4 -0
  201. django_spire/core/maps.py +3 -1
  202. django_spire/core/middleware/maintenance.py +3 -3
  203. django_spire/core/middleware.py +8 -6
  204. django_spire/core/redirect/__init__.py +5 -0
  205. django_spire/core/redirect/generic_redirect.py +1 -2
  206. django_spire/core/redirect/tests/__init__.py +0 -0
  207. django_spire/core/redirect/tests/test_generic_redirect.py +34 -0
  208. django_spire/core/{tests/tests_redirect.py → redirect/tests/test_safe_redirect.py} +55 -81
  209. django_spire/core/shortcuts.py +3 -3
  210. django_spire/core/static/django_spire/css/app-layout.css +1 -1
  211. django_spire/core/static/django_spire/css/app-navigation.css +3 -3
  212. django_spire/core/static/django_spire/css/bootstrap-override.css +4 -0
  213. django_spire/core/tag/admin.py +12 -0
  214. django_spire/core/tag/intelligence/tag_set_bot.py +2 -0
  215. django_spire/core/tag/mixins.py +2 -0
  216. django_spire/core/tag/models.py +2 -0
  217. django_spire/core/tag/querysets.py +2 -0
  218. django_spire/core/tag/service/tag_service.py +6 -3
  219. django_spire/core/tag/tests/test_intelligence.py +9 -9
  220. django_spire/core/tag/tests/test_tags.py +44 -54
  221. django_spire/core/tag/tests/test_tools.py +191 -0
  222. django_spire/core/tag/tools.py +3 -0
  223. django_spire/core/templates/django_spire/card/card.html +5 -2
  224. django_spire/core/templates/django_spire/card/title_card.html +9 -4
  225. django_spire/core/templates/django_spire/infinite_scroll/base.html +1 -0
  226. django_spire/core/templates/django_spire/navigation/side_navigation.html +19 -24
  227. django_spire/core/templates/django_spire/page/full_page.html +46 -16
  228. django_spire/core/templates/django_spire/table/base.html +4 -2
  229. django_spire/core/templatetags/json.py +6 -2
  230. django_spire/core/templatetags/message.py +13 -8
  231. django_spire/core/templatetags/string_formating.py +8 -5
  232. django_spire/core/templatetags/tests/__init__.py +0 -0
  233. django_spire/core/templatetags/tests/test_templatetags.py +427 -0
  234. django_spire/core/templatetags/variable_types.py +17 -9
  235. django_spire/core/tests/test_cases.py +1 -1
  236. django_spire/core/tests/test_conf.py +43 -0
  237. django_spire/core/tests/test_consts.py +28 -0
  238. django_spire/core/tests/test_context_processors.py +93 -0
  239. django_spire/core/tests/test_decorators.py +95 -0
  240. django_spire/core/tests/test_django_spire_utils.py +56 -0
  241. django_spire/core/tests/test_exceptions.py +37 -0
  242. django_spire/core/tests/test_models.py +54 -0
  243. django_spire/core/tests/test_settings.py +45 -0
  244. django_spire/core/tests/test_shortcuts.py +74 -0
  245. django_spire/core/tests/test_urls.py +16 -0
  246. django_spire/core/tests/test_utils.py +58 -0
  247. django_spire/core/urls.py +4 -1
  248. django_spire/core/utils.py +12 -8
  249. django_spire/exceptions.py +16 -1
  250. django_spire/file/admin.py +4 -2
  251. django_spire/file/apps.py +8 -10
  252. django_spire/file/fields.py +7 -7
  253. django_spire/file/forms.py +1 -1
  254. django_spire/file/interfaces.py +15 -15
  255. django_spire/file/mixins.py +1 -4
  256. django_spire/file/models.py +3 -5
  257. django_spire/file/tests/factories.py +59 -0
  258. django_spire/file/tests/test_admin.py +69 -0
  259. django_spire/file/tests/test_apps.py +24 -0
  260. django_spire/file/tests/test_fields.py +114 -0
  261. django_spire/file/tests/test_forms.py +20 -0
  262. django_spire/file/tests/test_interfaces.py +183 -0
  263. django_spire/file/tests/test_models.py +82 -0
  264. django_spire/file/tests/test_querysets.py +102 -0
  265. django_spire/file/tests/test_utils.py +32 -0
  266. django_spire/file/tests/test_views.py +145 -0
  267. django_spire/file/tests/test_widgets.py +82 -0
  268. django_spire/file/tools.py +8 -2
  269. django_spire/file/views.py +7 -3
  270. django_spire/file/widgets.py +12 -12
  271. django_spire/help_desk/admin.py +15 -0
  272. django_spire/help_desk/apps.py +2 -0
  273. django_spire/help_desk/auth/controller.py +2 -0
  274. django_spire/help_desk/choices.py +2 -0
  275. django_spire/help_desk/enums.py +2 -0
  276. django_spire/help_desk/exceptions.py +31 -3
  277. django_spire/help_desk/forms.py +2 -0
  278. django_spire/help_desk/models.py +2 -0
  279. django_spire/help_desk/querysets.py +4 -1
  280. django_spire/help_desk/services/notification_service.py +26 -27
  281. django_spire/help_desk/services/service.py +2 -3
  282. django_spire/help_desk/tests/factories.py +8 -3
  283. django_spire/help_desk/tests/test_admin.py +41 -0
  284. django_spire/help_desk/tests/test_apps.py +41 -0
  285. django_spire/help_desk/tests/test_choices.py +50 -0
  286. django_spire/help_desk/tests/test_controller.py +87 -0
  287. django_spire/help_desk/tests/test_enums.py +18 -0
  288. django_spire/help_desk/tests/test_exceptions.py +37 -0
  289. django_spire/help_desk/tests/test_forms.py +89 -0
  290. django_spire/help_desk/tests/test_models.py +59 -0
  291. django_spire/help_desk/tests/test_querysets.py +38 -0
  292. django_spire/help_desk/tests/test_services/test_notification_service.py +15 -8
  293. django_spire/help_desk/tests/test_services/test_service.py +92 -0
  294. django_spire/help_desk/tests/test_urls/test_form_urls.py +6 -6
  295. django_spire/help_desk/tests/test_urls/test_page_urls.py +8 -9
  296. django_spire/help_desk/tests/test_views/test_form_views.py +46 -19
  297. django_spire/help_desk/tests/test_views/test_page_views.py +32 -9
  298. django_spire/help_desk/urls/__init__.py +4 -1
  299. django_spire/help_desk/urls/form_urls.py +3 -0
  300. django_spire/help_desk/urls/page_urls.py +3 -0
  301. django_spire/help_desk/views/form_views.py +13 -5
  302. django_spire/help_desk/views/page_views.py +11 -3
  303. django_spire/history/activity/admin.py +2 -0
  304. django_spire/history/activity/apps.py +3 -1
  305. django_spire/history/activity/mixins.py +13 -7
  306. django_spire/history/activity/models.py +6 -5
  307. django_spire/history/activity/querysets.py +2 -0
  308. django_spire/history/activity/tests/__init__.py +0 -0
  309. django_spire/history/activity/tests/test_activity.py +176 -0
  310. django_spire/history/admin.py +9 -2
  311. django_spire/history/choices.py +3 -0
  312. django_spire/history/models.py +5 -5
  313. django_spire/history/tests/test_admin.py +93 -0
  314. django_spire/history/tests/test_history.py +101 -0
  315. django_spire/history/tests/test_mixins.py +84 -0
  316. django_spire/history/viewed/admin.py +3 -1
  317. django_spire/history/viewed/apps.py +3 -1
  318. django_spire/history/viewed/models.py +2 -0
  319. django_spire/history/viewed/tests/__init__.py +0 -0
  320. django_spire/history/viewed/tests/test_viewed.py +46 -0
  321. django_spire/knowledge/auth/tests/__init__.py +0 -0
  322. django_spire/knowledge/auth/tests/test_controller.py +116 -0
  323. django_spire/knowledge/collection/admin.py +5 -1
  324. django_spire/knowledge/collection/models.py +3 -1
  325. django_spire/knowledge/collection/seeding/seed.py +1 -0
  326. django_spire/knowledge/collection/services/factory_service.py +10 -11
  327. django_spire/knowledge/collection/services/ordering_service.py +1 -2
  328. django_spire/knowledge/collection/services/service.py +5 -10
  329. django_spire/knowledge/collection/services/tag_service.py +5 -2
  330. django_spire/knowledge/collection/tests/factories.py +28 -1
  331. django_spire/knowledge/collection/tests/test_models.py +48 -0
  332. django_spire/knowledge/collection/tests/test_querysets.py +93 -0
  333. django_spire/knowledge/collection/tests/test_services/test_factory_service.py +100 -0
  334. django_spire/knowledge/collection/tests/test_services/test_services.py +160 -0
  335. django_spire/knowledge/collection/tests/test_urls/test_form_urls.py +21 -3
  336. django_spire/knowledge/collection/tests/test_urls/test_json_urls.py +39 -1
  337. django_spire/knowledge/collection/tests/test_urls/test_page_urls.py +12 -4
  338. django_spire/knowledge/collection/urls/__init__.py +3 -0
  339. django_spire/knowledge/collection/urls/form_urls.py +2 -0
  340. django_spire/knowledge/collection/urls/json_urls.py +2 -0
  341. django_spire/knowledge/collection/urls/page_urls.py +2 -0
  342. django_spire/knowledge/collection/views/form_views.py +4 -4
  343. django_spire/knowledge/collection/views/json_views.py +5 -1
  344. django_spire/knowledge/collection/views/page_views.py +5 -2
  345. django_spire/knowledge/entry/admin.py +7 -1
  346. django_spire/knowledge/entry/forms.py +2 -0
  347. django_spire/knowledge/entry/models.py +2 -0
  348. django_spire/knowledge/entry/seeding/seed.py +3 -0
  349. django_spire/knowledge/entry/services/automation_service.py +5 -4
  350. django_spire/knowledge/entry/services/factory_service.py +7 -5
  351. django_spire/knowledge/entry/services/service.py +4 -7
  352. django_spire/knowledge/entry/services/tag_service.py +0 -1
  353. django_spire/knowledge/entry/services/tool_service.py +1 -0
  354. django_spire/knowledge/entry/services/transformation_services.py +1 -5
  355. django_spire/knowledge/entry/tests/factories.py +1 -2
  356. django_spire/knowledge/entry/tests/test_factory_service.py +20 -0
  357. django_spire/knowledge/entry/tests/test_models.py +41 -0
  358. django_spire/knowledge/entry/tests/test_querysets.py +71 -0
  359. django_spire/knowledge/entry/tests/test_services.py +94 -0
  360. django_spire/knowledge/entry/tests/test_urls/test_form_urls.py +9 -14
  361. django_spire/knowledge/entry/tests/test_urls/test_json_urls.py +48 -5
  362. django_spire/knowledge/entry/tests/test_urls/test_page_urls.py +6 -8
  363. django_spire/knowledge/entry/tests/test_urls/test_template_urls.py +40 -0
  364. django_spire/knowledge/entry/urls/form_urls.py +2 -0
  365. django_spire/knowledge/entry/urls/json_urls.py +2 -0
  366. django_spire/knowledge/entry/urls/page_urls.py +2 -0
  367. django_spire/knowledge/entry/urls/template_urls.py +2 -0
  368. django_spire/knowledge/entry/version/block/choices.py +2 -0
  369. django_spire/knowledge/entry/version/block/data/data.py +1 -0
  370. django_spire/knowledge/entry/version/block/data/list/data.py +8 -13
  371. django_spire/knowledge/entry/version/block/data/list/maps.py +3 -0
  372. django_spire/knowledge/entry/version/block/data/list/meta.py +1 -2
  373. django_spire/knowledge/entry/version/block/data/list/tests/__init__.py +0 -0
  374. django_spire/knowledge/entry/version/block/data/list/tests/test_maps.py +32 -0
  375. django_spire/knowledge/entry/version/block/data/list/tests/test_meta.py +58 -0
  376. django_spire/knowledge/entry/version/block/data/maps.py +3 -6
  377. django_spire/knowledge/entry/version/block/models.py +7 -5
  378. django_spire/knowledge/entry/version/block/seeding/constants.py +5 -4
  379. django_spire/knowledge/entry/version/block/services/service.py +2 -3
  380. django_spire/knowledge/entry/version/block/tests/factories.py +4 -10
  381. django_spire/knowledge/entry/version/block/tests/test_choices.py +56 -0
  382. django_spire/knowledge/entry/version/block/tests/test_data.py +90 -0
  383. django_spire/knowledge/entry/version/block/tests/test_maps.py +37 -0
  384. django_spire/knowledge/entry/version/block/tests/test_models.py +55 -0
  385. django_spire/knowledge/entry/version/block/tests/test_querysets.py +35 -0
  386. django_spire/knowledge/entry/version/block/tests/test_services.py +65 -0
  387. django_spire/knowledge/entry/version/choices.py +2 -0
  388. django_spire/knowledge/entry/version/converters/converter.py +1 -1
  389. django_spire/knowledge/entry/version/converters/docx_converter.py +4 -7
  390. django_spire/knowledge/entry/version/converters/markdown_converter.py +20 -20
  391. django_spire/knowledge/entry/version/maps.py +4 -5
  392. django_spire/knowledge/entry/version/querysets.py +1 -1
  393. django_spire/knowledge/entry/version/seeding/seeder.py +1 -2
  394. django_spire/knowledge/entry/version/services/processor_service.py +5 -4
  395. django_spire/knowledge/entry/version/services/service.py +1 -2
  396. django_spire/knowledge/entry/version/tests/factories.py +2 -2
  397. django_spire/knowledge/entry/version/tests/test_choices.py +18 -0
  398. django_spire/knowledge/entry/version/tests/test_converters/test_docx_converter.py +56 -8
  399. django_spire/knowledge/entry/version/tests/test_converters/test_markdown_converter.py +78 -0
  400. django_spire/knowledge/entry/version/tests/test_maps.py +58 -0
  401. django_spire/knowledge/entry/version/tests/test_models.py +23 -0
  402. django_spire/knowledge/entry/version/tests/test_querysets.py +26 -0
  403. django_spire/knowledge/entry/version/tests/test_services.py +62 -0
  404. django_spire/knowledge/entry/version/tests/test_urls/test_json_urls.py +27 -8
  405. django_spire/knowledge/entry/version/tests/test_urls/test_page_urls.py +15 -8
  406. django_spire/knowledge/entry/version/tests/test_urls/test_redirect_urls.py +38 -0
  407. django_spire/knowledge/entry/version/urls/__init__.py +3 -0
  408. django_spire/knowledge/entry/version/urls/json_urls.py +2 -1
  409. django_spire/knowledge/entry/version/urls/page_urls.py +2 -0
  410. django_spire/knowledge/entry/version/urls/redirect_urls.py +2 -0
  411. django_spire/knowledge/entry/version/views/json_views.py +5 -1
  412. django_spire/knowledge/entry/version/views/page_views.py +10 -3
  413. django_spire/knowledge/entry/version/views/redirect_views.py +5 -1
  414. django_spire/knowledge/entry/views/form_views.py +16 -8
  415. django_spire/knowledge/entry/views/json_views.py +3 -1
  416. django_spire/knowledge/entry/views/page_views.py +8 -2
  417. django_spire/knowledge/entry/views/template_views.py +7 -1
  418. django_spire/knowledge/exceptions.py +2 -1
  419. django_spire/knowledge/intelligence/intel/answer_intel.py +2 -1
  420. django_spire/knowledge/intelligence/intel/entry_intel.py +0 -1
  421. django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +4 -5
  422. django_spire/knowledge/models.py +1 -2
  423. django_spire/knowledge/tests/__init__.py +0 -0
  424. django_spire/knowledge/tests/test_templatetags.py +40 -0
  425. django_spire/knowledge/tests/test_urls/__init__.py +0 -0
  426. django_spire/knowledge/tests/test_urls/test_page_urls.py +24 -0
  427. django_spire/knowledge/urls/__init__.py +2 -0
  428. django_spire/knowledge/urls/page_urls.py +2 -0
  429. django_spire/knowledge/views/page_views.py +8 -3
  430. django_spire/notification/admin.py +3 -1
  431. django_spire/notification/app/admin.py +2 -0
  432. django_spire/notification/app/apps.py +3 -1
  433. django_spire/notification/app/exceptions.py +9 -2
  434. django_spire/notification/app/models.py +8 -4
  435. django_spire/notification/app/processor.py +22 -26
  436. django_spire/notification/app/querysets.py +2 -0
  437. django_spire/notification/app/tests/__init__.py +0 -0
  438. django_spire/notification/app/tests/factories.py +34 -0
  439. django_spire/notification/app/tests/test_apps.py +24 -0
  440. django_spire/notification/app/tests/test_models.py +72 -0
  441. django_spire/notification/app/tests/test_processor.py +111 -0
  442. django_spire/notification/app/tests/test_querysets.py +90 -0
  443. django_spire/notification/app/tests/test_views/__init__.py +0 -0
  444. django_spire/notification/app/tests/test_views/test_json_views.py +48 -0
  445. django_spire/notification/app/tests/test_views/test_page_views.py +19 -0
  446. django_spire/notification/app/urls/__init__.py +3 -1
  447. django_spire/notification/app/urls/json_urls.py +6 -4
  448. django_spire/notification/app/urls/page_urls.py +4 -3
  449. django_spire/notification/app/urls/template_urls.py +4 -2
  450. django_spire/notification/apps.py +4 -1
  451. django_spire/notification/email/admin.py +5 -1
  452. django_spire/notification/email/apps.py +3 -1
  453. django_spire/notification/email/exceptions.py +4 -2
  454. django_spire/notification/email/helper.py +5 -3
  455. django_spire/notification/email/models.py +4 -0
  456. django_spire/notification/email/processor.py +19 -15
  457. django_spire/notification/email/querysets.py +3 -0
  458. django_spire/notification/email/tests/__init__.py +0 -0
  459. django_spire/notification/email/tests/factories.py +35 -0
  460. django_spire/notification/email/tests/test_apps.py +24 -0
  461. django_spire/notification/email/tests/test_models.py +52 -0
  462. django_spire/notification/email/tests/test_processor.py +92 -0
  463. django_spire/notification/email/tests/test_querysets.py +43 -0
  464. django_spire/notification/exceptions.py +17 -2
  465. django_spire/notification/managers.py +7 -1
  466. django_spire/notification/maps.py +4 -1
  467. django_spire/notification/mixins.py +2 -0
  468. django_spire/notification/models.py +3 -1
  469. django_spire/notification/processors/notification.py +12 -5
  470. django_spire/notification/processors/processor.py +2 -0
  471. django_spire/notification/processors/tests/__init__.py +0 -0
  472. django_spire/notification/processors/tests/test_notification.py +106 -0
  473. django_spire/notification/push/admin.py +10 -1
  474. django_spire/notification/push/apps.py +3 -1
  475. django_spire/notification/push/models.py +2 -3
  476. django_spire/notification/push/tests/__init__.py +0 -0
  477. django_spire/notification/push/tests/test_apps.py +24 -0
  478. django_spire/notification/push/tests/test_models.py +28 -0
  479. django_spire/notification/querysets.py +7 -1
  480. django_spire/notification/sms/admin.py +2 -0
  481. django_spire/notification/sms/apps.py +4 -1
  482. django_spire/notification/sms/automations.py +2 -0
  483. django_spire/notification/sms/choices.py +2 -0
  484. django_spire/notification/sms/exceptions.py +19 -5
  485. django_spire/notification/sms/helper.py +33 -23
  486. django_spire/notification/sms/models.py +5 -1
  487. django_spire/notification/sms/processor.py +20 -20
  488. django_spire/notification/sms/querysets.py +2 -0
  489. django_spire/notification/sms/tests/factories.py +33 -0
  490. django_spire/notification/sms/tests/test_apps.py +24 -0
  491. django_spire/notification/sms/tests/test_automation.py +38 -0
  492. django_spire/notification/sms/tests/test_choices.py +15 -0
  493. django_spire/notification/sms/tests/test_consts.py +17 -0
  494. django_spire/notification/sms/tests/test_exceptions.py +27 -0
  495. django_spire/notification/sms/tests/test_helper.py +50 -0
  496. django_spire/notification/sms/tests/test_models.py +81 -0
  497. django_spire/notification/sms/tests/test_processor.py +107 -0
  498. django_spire/notification/sms/tests/test_tools.py +25 -11
  499. django_spire/notification/sms/tools.py +16 -5
  500. django_spire/notification/sms/urls/__init__.py +3 -1
  501. django_spire/notification/sms/urls/media_urls.py +2 -0
  502. django_spire/notification/sms/views/media_views.py +14 -4
  503. django_spire/notification/tests/__init__.py +0 -0
  504. django_spire/notification/tests/factories.py +26 -0
  505. django_spire/notification/tests/test_admin.py +55 -0
  506. django_spire/notification/tests/test_apps.py +30 -0
  507. django_spire/notification/tests/test_automation.py +18 -0
  508. django_spire/notification/tests/test_choices.py +59 -0
  509. django_spire/notification/tests/test_exceptions.py +58 -0
  510. django_spire/notification/tests/test_managers.py +100 -0
  511. django_spire/notification/tests/test_maps.py +31 -0
  512. django_spire/notification/tests/test_models.py +76 -0
  513. django_spire/notification/tests/test_querysets.py +184 -0
  514. django_spire/notification/tests/test_utils.py +23 -0
  515. django_spire/notification/urls.py +3 -1
  516. django_spire/notification/utils.py +3 -1
  517. django_spire/settings.py +3 -0
  518. django_spire/theme/tests/test_context_processor.py +15 -13
  519. django_spire/theme/tests/test_enums.py +2 -2
  520. django_spire/theme/tests/test_filesystem.py +2 -5
  521. django_spire/theme/tests/test_integration.py +12 -12
  522. django_spire/theme/tests/test_model.py +40 -38
  523. django_spire/theme/tests/test_views/test_json_views.py +33 -33
  524. django_spire/theme/urls/json_urls.py +3 -0
  525. django_spire/theme/urls/page_urls.py +3 -0
  526. django_spire/urls.py +19 -15
  527. django_spire/utils.py +13 -4
  528. {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/METADATA +1 -1
  529. {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/RECORD +532 -361
  530. django_spire/contrib/options/tests/test_unit.py +0 -148
  531. django_spire/contrib/progress/views.py +0 -64
  532. django_spire/contrib/seeding/tests/test_seeding.py +0 -25
  533. django_spire/core/tests/test_templatetags.py +0 -117
  534. django_spire/core/tests/tests_shortcuts.py +0 -73
  535. django_spire/history/activity/tests.py +0 -3
  536. django_spire/history/activity/views.py +0 -3
  537. django_spire/knowledge/collection/tests/test_services/test_transformation_service.py +0 -71
  538. django_spire/notification/app/tests.py +0 -3
  539. {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/WHEEL +0 -0
  540. {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/licenses/LICENSE.md +0 -0
  541. {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,179 @@
1
+ from __future__ import annotations
2
+
3
+ from unittest.mock import MagicMock
4
+
5
+ from django.core.paginator import Page
6
+ from django.test import RequestFactory, TestCase
7
+
8
+ from django_spire.contrib.pagination.pagination import paginate_list
9
+ from django_spire.contrib.pagination.templatetags.pagination_tags import (
10
+ get_elided_page_range,
11
+ pagination_url,
12
+ )
13
+
14
+
15
+ class TestPaginateList(TestCase):
16
+ def setUp(self) -> None:
17
+ self.object_list = list(range(100))
18
+
19
+ def test_custom_page_number(self) -> None:
20
+ result = paginate_list(self.object_list, page_number=2)
21
+
22
+ assert result.number == 2
23
+
24
+ def test_custom_per_page(self) -> None:
25
+ result = paginate_list(self.object_list, per_page=10)
26
+
27
+ assert len(result.object_list) == 10
28
+
29
+ def test_default_page_number(self) -> None:
30
+ result = paginate_list(self.object_list)
31
+
32
+ assert result.number == 1
33
+
34
+ def test_default_per_page(self) -> None:
35
+ result = paginate_list(self.object_list)
36
+
37
+ assert len(result.object_list) == 50
38
+
39
+ def test_empty_list(self) -> None:
40
+ result = paginate_list([])
41
+
42
+ assert len(result.object_list) == 0
43
+
44
+ def test_has_next_page(self) -> None:
45
+ result = paginate_list(self.object_list, page_number=1)
46
+
47
+ assert result.has_next() is True
48
+
49
+ def test_has_previous_page(self) -> None:
50
+ result = paginate_list(self.object_list, page_number=2)
51
+
52
+ assert result.has_previous() is True
53
+
54
+ def test_last_page_has_no_next(self) -> None:
55
+ result = paginate_list(self.object_list, page_number=2)
56
+
57
+ assert result.has_next() is False
58
+
59
+ def test_page_number_exceeds_pages_returns_last_page(self) -> None:
60
+ result = paginate_list(self.object_list, page_number=999)
61
+
62
+ assert result.number == 2
63
+
64
+ def test_returns_page_object(self) -> None:
65
+ result = paginate_list(self.object_list)
66
+
67
+ assert isinstance(result, Page)
68
+
69
+
70
+ class TestPaginationUrl(TestCase):
71
+ def setUp(self) -> None:
72
+ self.factory = RequestFactory()
73
+
74
+ def _create_context(self, request) -> MagicMock:
75
+ context = MagicMock()
76
+ context.request = request
77
+ return context
78
+
79
+ def test_custom_page_name(self) -> None:
80
+ request = self.factory.get('/')
81
+ context = self._create_context(request)
82
+
83
+ result = pagination_url(context, page_number=2, page_name='p')
84
+
85
+ assert 'p=2' in result
86
+
87
+ def test_default_page_name(self) -> None:
88
+ request = self.factory.get('/')
89
+ context = self._create_context(request)
90
+
91
+ result = pagination_url(context, page_number=1)
92
+
93
+ assert 'page=1' in result
94
+
95
+ def test_multiple_query_params(self) -> None:
96
+ request = self.factory.get('/', {'filter': 'active', 'sort': 'name'})
97
+ context = self._create_context(request)
98
+
99
+ result = pagination_url(context, page_number=3)
100
+
101
+ assert 'page=3' in result
102
+ assert 'filter=active' in result
103
+ assert 'sort=name' in result
104
+
105
+ def test_preserves_existing_query_params(self) -> None:
106
+ request = self.factory.get('/', {'search': 'test'})
107
+ context = self._create_context(request)
108
+
109
+ result = pagination_url(context, page_number=2)
110
+
111
+ assert 'search=test' in result
112
+ assert 'page=2' in result
113
+
114
+ def test_replaces_space_with_plus(self) -> None:
115
+ request = self.factory.get('/', {'search': 'hello world'})
116
+ context = self._create_context(request)
117
+
118
+ result = pagination_url(context, page_number=1)
119
+
120
+ assert 'search=hello+world' in result
121
+
122
+ def test_returns_query_string(self) -> None:
123
+ request = self.factory.get('/')
124
+ context = self._create_context(request)
125
+
126
+ result = pagination_url(context, page_number=1)
127
+
128
+ assert result.startswith('?')
129
+
130
+
131
+ class TestGetElidedPageRange(TestCase):
132
+ def test_custom_on_each_side(self) -> None:
133
+ page_obj = MagicMock()
134
+ page_obj.number = 5
135
+ page_obj.paginator.get_elided_page_range.return_value = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
136
+
137
+ get_elided_page_range(page_obj, on_each_side=3)
138
+
139
+ page_obj.paginator.get_elided_page_range.assert_called_once_with(
140
+ number=5,
141
+ on_each_side=3,
142
+ on_ends=2
143
+ )
144
+
145
+ def test_custom_on_ends(self) -> None:
146
+ page_obj = MagicMock()
147
+ page_obj.number = 5
148
+ page_obj.paginator.get_elided_page_range.return_value = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
149
+
150
+ get_elided_page_range(page_obj, on_ends=3)
151
+
152
+ page_obj.paginator.get_elided_page_range.assert_called_once_with(
153
+ number=5,
154
+ on_each_side=2,
155
+ on_ends=3
156
+ )
157
+
158
+ def test_default_parameters(self) -> None:
159
+ page_obj = MagicMock()
160
+ page_obj.number = 1
161
+ page_obj.paginator.get_elided_page_range.return_value = [1, 2, 3]
162
+
163
+ get_elided_page_range(page_obj)
164
+
165
+ page_obj.paginator.get_elided_page_range.assert_called_once_with(
166
+ number=1,
167
+ on_each_side=2,
168
+ on_ends=2
169
+ )
170
+
171
+ def test_returns_elided_page_range(self) -> None:
172
+ page_obj = MagicMock()
173
+ page_obj.number = 1
174
+ expected_range = [1, 2, 3, '...', 10]
175
+ page_obj.paginator.get_elided_page_range.return_value = expected_range
176
+
177
+ result = get_elided_page_range(page_obj)
178
+
179
+ assert result == expected_range
@@ -1,21 +1,31 @@
1
+ from __future__ import annotations
2
+
1
3
  import logging
2
4
  import time
3
5
 
6
+ from typing import Callable, ParamSpec, TypeVar
7
+
4
8
  from django.conf import settings
5
9
 
6
10
 
7
- def performance_timer(func):
8
- def wrapper(*args, **kwargs):
11
+ P = ParamSpec('P')
12
+ R = TypeVar('R')
13
+
14
+ log = logging.getLogger(__name__)
15
+
16
+
17
+ def performance_timer(func: Callable[P, R]) -> Callable[P, R]:
18
+ def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
9
19
  if settings.DEBUG:
10
20
  start_time = time.perf_counter()
11
21
  result = func(*args, **kwargs)
12
22
  end_time = time.perf_counter()
13
23
 
14
- logging.warning(f'{end_time - start_time:.4f} seconds runtime for "{func.__module__}.{func.__qualname__}"')
24
+ message = f'{end_time - start_time:.4f} seconds runtime for "{func.__module__}.{func.__qualname__}"'
25
+ log.warning(message)
15
26
 
16
27
  return result
17
28
 
18
- else:
19
- return func(*args, **kwargs)
29
+ return func(*args, **kwargs)
20
30
 
21
- return wrapper
31
+ return wrapper
File without changes
@@ -0,0 +1,107 @@
1
+ from __future__ import annotations
2
+
3
+ from unittest.mock import patch
4
+
5
+ from django.test import TestCase, override_settings
6
+
7
+ from django_spire.contrib.performance.decorators import performance_timer
8
+
9
+
10
+ class TestPerformanceTimer(TestCase):
11
+ @override_settings(DEBUG=True)
12
+ def test_calls_function_when_debug_true(self) -> None:
13
+ @performance_timer
14
+ def sample_func() -> str:
15
+ return 'result'
16
+
17
+ result = sample_func()
18
+
19
+ assert result == 'result'
20
+
21
+ @override_settings(DEBUG=False)
22
+ def test_calls_function_when_debug_false(self) -> None:
23
+ @performance_timer
24
+ def sample_func() -> str:
25
+ return 'result'
26
+
27
+ result = sample_func()
28
+
29
+ assert result == 'result'
30
+
31
+ @override_settings(DEBUG=True)
32
+ def test_logs_warning_when_debug_true(self) -> None:
33
+ @performance_timer
34
+ def sample_func() -> str:
35
+ return 'result'
36
+
37
+ with patch('django_spire.contrib.performance.decorators.log.warning') as mock_warning:
38
+ sample_func()
39
+
40
+ mock_warning.assert_called_once()
41
+
42
+ @override_settings(DEBUG=False)
43
+ def test_no_logging_when_debug_false(self) -> None:
44
+ @performance_timer
45
+ def sample_func() -> str:
46
+ return 'result'
47
+
48
+ with patch('django_spire.contrib.performance.decorators.log.warning') as mock_warning:
49
+ sample_func()
50
+
51
+ mock_warning.assert_not_called()
52
+
53
+ @override_settings(DEBUG=True)
54
+ def test_log_message_contains_runtime(self) -> None:
55
+ @performance_timer
56
+ def sample_func() -> str:
57
+ return 'result'
58
+
59
+ with patch('django_spire.contrib.performance.decorators.log.warning') as mock_warning:
60
+ sample_func()
61
+
62
+ log_message = mock_warning.call_args[0][0]
63
+
64
+ assert 'seconds runtime' in log_message
65
+
66
+ @override_settings(DEBUG=True)
67
+ def test_log_message_contains_function_name(self) -> None:
68
+ @performance_timer
69
+ def sample_func() -> str:
70
+ return 'result'
71
+
72
+ with patch('django_spire.contrib.performance.decorators.log.warning') as mock_warning:
73
+ sample_func()
74
+
75
+ log_message = mock_warning.call_args[0][0]
76
+
77
+ assert 'sample_func' in log_message
78
+
79
+ @override_settings(DEBUG=True)
80
+ def test_passes_args_to_function(self) -> None:
81
+ @performance_timer
82
+ def sample_func(a: int, b: int) -> int:
83
+ return a + b
84
+
85
+ result = sample_func(2, 3)
86
+
87
+ assert result == 5
88
+
89
+ @override_settings(DEBUG=True)
90
+ def test_passes_kwargs_to_function(self) -> None:
91
+ @performance_timer
92
+ def sample_func(a: int, b: int = 10) -> int:
93
+ return a + b
94
+
95
+ result = sample_func(5, b=20)
96
+
97
+ assert result == 25
98
+
99
+ @override_settings(DEBUG=True)
100
+ def test_returns_none_when_function_returns_none(self) -> None:
101
+ @performance_timer
102
+ def sample_func() -> None:
103
+ pass
104
+
105
+ result = sample_func()
106
+
107
+ assert result is None
@@ -4,7 +4,6 @@ from django_spire.contrib.progress.runner import TaskProgressUpdater
4
4
  from django_spire.contrib.progress.states import TaskState, TrackerState
5
5
  from django_spire.contrib.progress.task import ProgressMessages, Task, TaskResult
6
6
  from django_spire.contrib.progress.tracker import ProgressTracker
7
- from django_spire.contrib.progress.views import sse_stream_view
8
7
 
9
8
 
10
9
  __all__ = [
@@ -16,6 +15,5 @@ __all__ = [
16
15
  'TaskProgressUpdater',
17
16
  'TaskResult',
18
17
  'TaskState',
19
- 'TrackerState',
20
- 'sse_stream_view',
18
+ 'TrackerState'
21
19
  ]
@@ -1,15 +1,16 @@
1
1
  class ProgressStream {
2
2
  constructor(url, config = {}) {
3
3
  this.url = url;
4
- this.controller = null;
5
4
  this.is_running = false;
5
+ this.poll_interval = null;
6
6
 
7
7
  this.config = {
8
8
  on_update: config.on_update || (() => {}),
9
9
  on_complete: config.on_complete || (() => {}),
10
10
  on_error: config.on_error || (() => {}),
11
11
  redirect_on_complete: config.redirect_on_complete || null,
12
- redirect_delay: config.redirect_delay || 1000
12
+ redirect_delay: config.redirect_delay || 1000,
13
+ poll_interval: config.poll_interval || 1000
13
14
  };
14
15
  }
15
16
 
@@ -17,19 +18,7 @@ class ProgressStream {
17
18
  if (this.is_running) return;
18
19
 
19
20
  this.is_running = true;
20
- this.controller = new AbortController();
21
-
22
- try {
23
- let response = await this._fetch();
24
-
25
- if (!response.ok) {
26
- throw new Error(`HTTP ${response.status}`);
27
- }
28
-
29
- await this._read_stream(response);
30
- } catch (error) {
31
- this._handle_error(error);
32
- }
21
+ this._start_polling();
33
22
  }
34
23
 
35
24
  stop() {
@@ -37,86 +26,53 @@ class ProgressStream {
37
26
 
38
27
  this.is_running = false;
39
28
 
40
- if (this.controller) {
41
- this.controller.abort();
42
- this.controller = null;
43
- }
44
- }
45
-
46
- async _fetch() {
47
- return fetch(this.url, {
48
- method: 'POST',
49
- signal: this.controller.signal,
50
- headers: {
51
- 'X-CSRFToken': get_cookie('csrftoken'),
52
- }
53
- });
54
- }
55
-
56
- async _read_stream(response) {
57
- let reader = response.body.getReader();
58
- let decoder = new TextDecoder();
59
- let buffer = '';
60
-
61
- while (this.is_running) {
62
- let { done, value } = await reader.read();
63
-
64
- if (done) break;
65
-
66
- buffer += decoder.decode(value, { stream: true });
67
- buffer = this._process_buffer(buffer);
29
+ if (this.poll_interval) {
30
+ clearInterval(this.poll_interval);
31
+ this.poll_interval = null;
68
32
  }
69
33
  }
70
34
 
71
- _process_buffer(buffer) {
72
- let lines = buffer.split('\n');
73
- let remainder = lines.pop();
35
+ _start_polling() {
36
+ this._poll();
74
37
 
75
- for (let line of lines) {
76
- if (line.startsWith('data: ')) {
77
- this._handle_message(line.slice(6));
38
+ this.poll_interval = setInterval(() => {
39
+ if (this.is_running) {
40
+ this._poll();
78
41
  }
79
- }
80
-
81
- return remainder;
42
+ }, this.config.poll_interval);
82
43
  }
83
44
 
84
- _handle_message(json_string) {
85
- let data;
86
-
45
+ async _poll() {
87
46
  try {
88
- data = JSON.parse(json_string);
89
- } catch {
90
- return;
91
- }
47
+ let response = await fetch(this.url, {
48
+ method: 'POST',
49
+ headers: {
50
+ 'X-CSRFToken': get_cookie('csrftoken'),
51
+ }
52
+ });
92
53
 
93
- this.config.on_update(data);
94
-
95
- if (data.step === 'error') {
96
- this.config.on_error(data);
97
- this.stop();
98
- return;
99
- }
100
-
101
- if (data.progress >= 100) {
102
- this.config.on_complete(data);
103
- this.stop();
104
- this._redirect();
105
- }
106
- }
54
+ if (!response.ok) {
55
+ throw new Error(`HTTP ${response.status}`);
56
+ }
107
57
 
108
- _handle_error(error) {
109
- if (error.name === 'AbortError') return;
58
+ let data = await response.json();
110
59
 
111
- console.error('ProgressStream error:', error);
60
+ this.config.on_update(data);
112
61
 
113
- this.config.on_error({
114
- step: 'error',
115
- message: 'Connection error',
116
- progress: 0
117
- });
62
+ if (data.step === 'error') {
63
+ this.config.on_error(data);
64
+ this.stop();
65
+ return;
66
+ }
118
67
 
119
- this.stop();
68
+ if (data.progress >= 100) {
69
+ this.config.on_complete(data);
70
+ this.stop();
71
+ this._redirect();
72
+ }
73
+ } catch (error) {
74
+ console.warn('Poll error:', error.message);
75
+ }
120
76
  }
121
77
 
122
78
  _redirect() {
@@ -1,6 +1,8 @@
1
+ from __future__ import annotations
2
+
1
3
  from enum import StrEnum
2
4
 
3
5
 
4
6
  class SessionFilterActionEnum(StrEnum):
5
7
  CLEAR = 'Clear'
6
- FILTER = 'Filter'
8
+ FILTER = 'Filter'
@@ -1,13 +1,16 @@
1
1
  from __future__ import annotations
2
2
 
3
- from django.db.models import QuerySet
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from django.db.models import QuerySet
4
7
 
5
8
 
6
9
  def filter_by_lookup_map(
7
- queryset: QuerySet,
8
- lookup_map: dict,
9
- data: dict,
10
- extra_filters: dict | None = None
10
+ queryset: QuerySet,
11
+ lookup_map: dict,
12
+ data: dict,
13
+ extra_filters: dict | None = None
11
14
  ):
12
15
  """
13
16
  Filters a given queryset based on a lookup map and provided data. Additional filters
@@ -23,7 +26,9 @@ def filter_by_lookup_map(
23
26
 
24
27
  Returns:
25
28
  QuerySet: The filtered queryset.
29
+
26
30
  """
31
+
27
32
  if extra_filters is None:
28
33
  extra_filters = {}
29
34
 
@@ -1,25 +1,27 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import json
4
+
4
5
  from abc import abstractmethod
5
- from typing import Type
6
+ from typing import TYPE_CHECKING
6
7
 
7
- from django.core.handlers.wsgi import WSGIRequest
8
8
  from django.db.models import QuerySet
9
- from django.forms import Form
10
9
  from django_spire.contrib.form.utils import show_form_errors
11
10
  from django_spire.contrib.queryset.enums import SessionFilterActionEnum
12
11
  from django_spire.contrib.session.controller import SessionController
13
12
 
13
+ if TYPE_CHECKING:
14
+ from django.core.handlers.wsgi import WSGIRequest
15
+ from django.forms import Form
14
16
 
15
- class SessionFilterQuerySetMixin(QuerySet):
16
17
 
18
+ class SessionFilterQuerySetMixin(QuerySet):
17
19
  def process_session_filter(
18
- self,
19
- request: WSGIRequest,
20
- session_key: str,
21
- form_class: Type[Form],
22
- is_from_body: bool = False,
20
+ self,
21
+ request: WSGIRequest,
22
+ session_key: str,
23
+ form_class: type[Form],
24
+ is_from_body: bool = False
23
25
  ) -> QuerySet:
24
26
  # Session keys must match to process new queryset data
25
27
 
@@ -41,10 +43,9 @@ class SessionFilterQuerySetMixin(QuerySet):
41
43
 
42
44
  # Apply filters when the user submits the filter form
43
45
  if (
44
- action == SessionFilterActionEnum.FILTER
45
- and session_key == data.get('session_filter_key')
46
+ action == SessionFilterActionEnum.FILTER
47
+ and session_key == data.get('session_filter_key')
46
48
  ):
47
-
48
49
  # Update session data
49
50
  for key, value in form.cleaned_data.items():
50
51
  session.add_data(key, value)
@@ -56,9 +57,9 @@ class SessionFilterQuerySetMixin(QuerySet):
56
57
  # When no new filter data is applied and session is NOT yet expired,
57
58
  # return the original queryset
58
59
  return self.bulk_filter(session.data)
59
- else:
60
- show_form_errors(request, form)
61
- return self
60
+
61
+ show_form_errors(request, form)
62
+ return self
62
63
 
63
64
  @abstractmethod
64
65
  def bulk_filter(self, filter_data: dict) -> QuerySet:
@@ -66,7 +67,6 @@ class SessionFilterQuerySetMixin(QuerySet):
66
67
 
67
68
 
68
69
  class SearchQuerySetMixin(QuerySet):
69
-
70
70
  @abstractmethod
71
71
  def search(self, value: str | None) -> QuerySet:
72
72
  pass
File without changes