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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (542) hide show
  1. django_spire/ai/admin.py +11 -11
  2. django_spire/ai/chat/apps.py +1 -0
  3. django_spire/ai/chat/templates/django_spire/ai/chat/widget/dialog_widget.html +1 -1
  4. django_spire/ai/chat/tests/factories.py +15 -0
  5. django_spire/ai/chat/tests/test_controller.py +45 -0
  6. django_spire/ai/chat/tests/test_models.py +301 -0
  7. django_spire/ai/chat/tests/test_prompts.py +48 -0
  8. django_spire/ai/chat/tests/test_responses.py +208 -0
  9. django_spire/ai/chat/tests/test_router/test_base_chat_router.py +66 -6
  10. django_spire/ai/chat/tests/test_router/test_chat_workflow.py +73 -3
  11. django_spire/ai/chat/tests/test_router/test_integration.py +86 -6
  12. django_spire/ai/chat/tests/test_router/test_intent_decoder.py +93 -1
  13. django_spire/ai/chat/tests/test_router/test_message_intel.py +60 -1
  14. django_spire/ai/chat/tests/test_router/test_spire_chat_router.py +110 -0
  15. django_spire/ai/chat/tests/test_urls/test_json_urls.py +202 -1
  16. django_spire/ai/context/tests/__init__.py +0 -0
  17. django_spire/ai/context/tests/test_context.py +188 -0
  18. django_spire/ai/decorators.py +7 -6
  19. django_spire/ai/prompt/tests/test_bots.py +100 -10
  20. django_spire/ai/prompt/tests/test_prompt_intel.py +83 -0
  21. django_spire/ai/prompt/tests/test_prompt_tuning.py +126 -0
  22. django_spire/ai/sms/decorators.py +8 -2
  23. django_spire/ai/sms/tests/test_sms.py +240 -16
  24. django_spire/ai/sms/tests/test_sms_intel.py +42 -0
  25. django_spire/ai/sms/tests/test_webhook.py +155 -7
  26. django_spire/ai/sms/views.py +23 -24
  27. django_spire/ai/tests/test_ai.py +131 -7
  28. django_spire/auth/apps.py +4 -2
  29. django_spire/auth/controller/controller.py +36 -23
  30. django_spire/auth/controller/exceptions.py +9 -0
  31. django_spire/auth/group/admin.py +1 -0
  32. django_spire/auth/group/apps.py +2 -0
  33. django_spire/auth/group/factories.py +17 -8
  34. django_spire/auth/group/forms.py +7 -0
  35. django_spire/auth/group/tests/test_factories.py +146 -0
  36. django_spire/auth/group/tests/test_forms.py +282 -0
  37. django_spire/auth/group/tests/test_models.py +192 -0
  38. django_spire/auth/group/tests/test_querysets.py +98 -0
  39. django_spire/auth/group/tests/test_utils.py +341 -0
  40. django_spire/auth/group/tests/test_views.py +377 -0
  41. django_spire/auth/group/urls/__init__.py +3 -1
  42. django_spire/auth/group/urls/form_urls.py +2 -0
  43. django_spire/auth/group/urls/json_urls.py +3 -0
  44. django_spire/auth/group/urls/page_urls.py +2 -0
  45. django_spire/auth/group/utils.py +6 -2
  46. django_spire/auth/group/views/form_views.py +6 -3
  47. django_spire/auth/group/views/json_views.py +6 -2
  48. django_spire/auth/mfa/admin.py +2 -0
  49. django_spire/auth/mfa/apps.py +2 -0
  50. django_spire/auth/mfa/forms.py +1 -0
  51. django_spire/auth/mfa/querysets.py +9 -2
  52. django_spire/auth/mfa/tests/test_models.py +233 -0
  53. django_spire/auth/mfa/tests/test_utils.py +106 -0
  54. django_spire/auth/mfa/urls/__init__.py +2 -0
  55. django_spire/auth/mfa/urls/page_urls.py +2 -0
  56. django_spire/auth/mfa/urls/redirect_urls.py +2 -0
  57. django_spire/auth/mfa/views/page_views.py +2 -1
  58. django_spire/auth/permissions/consts.py +2 -2
  59. django_spire/auth/permissions/decorators.py +8 -8
  60. django_spire/auth/permissions/permissions.py +28 -35
  61. django_spire/auth/permissions/tests/test_decorators.py +333 -0
  62. django_spire/auth/permissions/tests/test_permissions.py +337 -0
  63. django_spire/auth/permissions/tests/test_tools.py +305 -0
  64. django_spire/auth/permissions/tools.py +21 -15
  65. django_spire/auth/seeding/seed.py +3 -0
  66. django_spire/auth/seeding/seeder.py +2 -0
  67. django_spire/auth/tests/test_controller.py +323 -0
  68. django_spire/auth/tests/test_url_endpoints.py +9 -9
  69. django_spire/auth/tests/test_views.py +406 -0
  70. django_spire/auth/urls/admin_urls.py +2 -0
  71. django_spire/auth/urls/redirect_urls.py +2 -0
  72. django_spire/auth/user/apps.py +2 -0
  73. django_spire/auth/user/forms.py +9 -0
  74. django_spire/auth/user/models.py +1 -1
  75. django_spire/auth/user/services/services.py +1 -0
  76. django_spire/auth/user/tests/factories.py +14 -13
  77. django_spire/auth/user/tests/test_factories.py +166 -2
  78. django_spire/auth/user/tests/test_forms.py +573 -0
  79. django_spire/auth/user/tests/test_models.py +257 -0
  80. django_spire/auth/user/tests/test_services.py +200 -0
  81. django_spire/auth/user/tests/test_tools.py +153 -0
  82. django_spire/auth/user/tests/test_user_factories.py +139 -0
  83. django_spire/auth/user/tests/test_views.py +363 -0
  84. django_spire/auth/user/tools.py +7 -1
  85. django_spire/auth/user/urls/form_urls.py +3 -0
  86. django_spire/auth/user/urls/page_urls.py +3 -0
  87. django_spire/auth/user/views/form_views.py +19 -10
  88. django_spire/auth/user/views/page_views.py +8 -2
  89. django_spire/auth/views/redirect_views.py +14 -9
  90. django_spire/comment/admin.py +2 -0
  91. django_spire/comment/apps.py +2 -0
  92. django_spire/comment/templatetags/comment_tags.py +1 -0
  93. django_spire/comment/tests/test_forms.py +27 -0
  94. django_spire/comment/tests/test_models.py +215 -0
  95. django_spire/comment/tests/test_querysets.py +101 -0
  96. django_spire/comment/tests/test_utils.py +90 -0
  97. django_spire/comment/urls.py +2 -0
  98. django_spire/comment/utils.py +22 -13
  99. django_spire/comment/views.py +1 -1
  100. django_spire/conf.py +8 -6
  101. django_spire/consts.py +1 -1
  102. django_spire/contrib/breadcrumb/apps.py +2 -0
  103. django_spire/contrib/breadcrumb/breadcrumbs.py +18 -18
  104. django_spire/contrib/breadcrumb/tests/test_breadcrumbs.py +198 -0
  105. django_spire/contrib/constructor/__init__.py +3 -3
  106. django_spire/contrib/constructor/constructor.py +15 -15
  107. django_spire/contrib/constructor/django_model_constructor.py +5 -4
  108. django_spire/contrib/constructor/exceptions.py +5 -3
  109. django_spire/contrib/constructor/tests/__init__.py +0 -0
  110. django_spire/contrib/constructor/tests/test_constructor.py +193 -0
  111. django_spire/contrib/form/tests/__init__.py +0 -0
  112. django_spire/contrib/form/tests/test_forms.py +203 -0
  113. django_spire/contrib/generic_views/modal_views.py +2 -1
  114. django_spire/contrib/generic_views/portal_views.py +20 -19
  115. django_spire/contrib/generic_views/tests/__init__.py +0 -0
  116. django_spire/contrib/generic_views/tests/test_views.py +459 -0
  117. django_spire/contrib/help/apps.py +2 -0
  118. django_spire/contrib/help/templatetags/help.py +1 -0
  119. django_spire/contrib/help/tests/__init__.py +0 -0
  120. django_spire/contrib/help/tests/test_templatetags.py +100 -0
  121. django_spire/contrib/options/mixins.py +6 -5
  122. django_spire/contrib/options/tests/factories.py +5 -1
  123. django_spire/contrib/options/tests/test_options.py +234 -0
  124. django_spire/contrib/ordering/exceptions.py +7 -3
  125. django_spire/contrib/ordering/mixins.py +2 -0
  126. django_spire/contrib/ordering/querysets.py +3 -1
  127. django_spire/contrib/ordering/services/processor_service.py +8 -4
  128. django_spire/contrib/ordering/services/service.py +1 -2
  129. django_spire/contrib/ordering/tests/__init__.py +0 -0
  130. django_spire/contrib/ordering/tests/test_ordering.py +165 -0
  131. django_spire/contrib/ordering/validators.py +6 -6
  132. django_spire/contrib/pagination/templatetags/pagination_tags.py +12 -5
  133. django_spire/contrib/pagination/tests/__init__.py +0 -0
  134. django_spire/contrib/pagination/tests/test_pagination.py +179 -0
  135. django_spire/contrib/performance/decorators.py +16 -6
  136. django_spire/contrib/performance/tests/__init__.py +0 -0
  137. django_spire/contrib/performance/tests/test_performance.py +107 -0
  138. django_spire/contrib/queryset/enums.py +3 -1
  139. django_spire/contrib/queryset/filter_tools.py +10 -5
  140. django_spire/contrib/queryset/mixins.py +16 -16
  141. django_spire/contrib/queryset/tests/__init__.py +0 -0
  142. django_spire/contrib/queryset/tests/test_queryset.py +137 -0
  143. django_spire/contrib/seeding/field/base.py +13 -7
  144. django_spire/contrib/seeding/field/callable.py +8 -1
  145. django_spire/contrib/seeding/field/cleaners.py +5 -5
  146. django_spire/contrib/seeding/field/custom.py +20 -10
  147. django_spire/contrib/seeding/field/django/seeder.py +8 -6
  148. django_spire/contrib/seeding/field/enums.py +7 -5
  149. django_spire/contrib/seeding/field/override.py +16 -6
  150. django_spire/contrib/seeding/field/static.py +9 -2
  151. django_spire/contrib/seeding/field/tests/test_base.py +18 -14
  152. django_spire/contrib/seeding/field/tests/test_callable.py +13 -9
  153. django_spire/contrib/seeding/field/tests/test_cleaners.py +51 -38
  154. django_spire/contrib/seeding/field/tests/test_static.py +13 -9
  155. django_spire/contrib/seeding/intelligence/bots/seeder_generator_bot.py +2 -0
  156. django_spire/contrib/seeding/intelligence/intel.py +5 -1
  157. django_spire/contrib/seeding/intelligence/prompts/factory.py +6 -1
  158. django_spire/contrib/seeding/intelligence/prompts/foreign_key_selection_prompt.py +6 -1
  159. django_spire/contrib/seeding/intelligence/prompts/generate_django_model_seeder_prompts.py +2 -0
  160. django_spire/contrib/seeding/intelligence/prompts/generic_relationship_selection_prompt.py +7 -1
  161. django_spire/contrib/seeding/intelligence/prompts/hierarchical_selection_prompt.py +6 -2
  162. django_spire/contrib/seeding/intelligence/prompts/model_field_choices_prompt.py +8 -2
  163. django_spire/contrib/seeding/intelligence/prompts/negation_prompt.py +2 -0
  164. django_spire/contrib/seeding/intelligence/prompts/objective_prompt.py +6 -1
  165. django_spire/contrib/seeding/management/commands/seeding.py +9 -3
  166. django_spire/contrib/seeding/management/example.py +2 -0
  167. django_spire/contrib/seeding/model/base.py +16 -7
  168. django_spire/contrib/seeding/model/config.py +31 -15
  169. django_spire/contrib/seeding/model/django/config.py +13 -13
  170. django_spire/contrib/seeding/model/django/seeder.py +4 -4
  171. django_spire/contrib/seeding/model/django/tests/test_seeder.py +34 -23
  172. django_spire/contrib/seeding/model/enums.py +2 -0
  173. django_spire/contrib/seeding/tests/test_config.py +71 -0
  174. django_spire/contrib/seeding/tests/test_custom.py +35 -0
  175. django_spire/contrib/seeding/tests/test_enums.py +40 -0
  176. django_spire/contrib/seeding/tests/test_intel.py +32 -0
  177. django_spire/contrib/seeding/tests/test_override.py +63 -0
  178. django_spire/contrib/service/__init__.py +2 -2
  179. django_spire/contrib/service/django_model_service.py +16 -15
  180. django_spire/contrib/service/exceptions.py +5 -3
  181. django_spire/contrib/service/tests/__init__.py +0 -0
  182. django_spire/contrib/service/tests/test_service.py +153 -0
  183. django_spire/contrib/session/apps.py +2 -0
  184. django_spire/contrib/session/controller.py +48 -42
  185. django_spire/contrib/session/templatetags/session_tags.py +11 -2
  186. django_spire/contrib/session/tests/test_session_controller.py +117 -53
  187. django_spire/contrib/tests/__init__.py +0 -0
  188. django_spire/contrib/tests/test_utils.py +37 -0
  189. django_spire/contrib/utils.py +4 -1
  190. django_spire/core/apps.py +2 -0
  191. django_spire/core/converters/tests/test_to_data.py +353 -0
  192. django_spire/core/converters/tests/test_to_enums.py +61 -41
  193. django_spire/core/converters/tests/test_to_pydantic.py +138 -109
  194. django_spire/core/converters/to_data.py +29 -10
  195. django_spire/core/converters/to_enums.py +4 -2
  196. django_spire/core/converters/to_pydantic.py +22 -22
  197. django_spire/core/decorators.py +19 -6
  198. django_spire/core/forms/widgets.py +4 -0
  199. django_spire/core/management/commands/spire_startapp_pkg/template/app/apps.py.template +2 -0
  200. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/__init__.py.template +2 -0
  201. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/form_urls.py.template +2 -0
  202. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/page_urls.py.template +2 -0
  203. django_spire/core/maps.py +3 -1
  204. django_spire/core/middleware/maintenance.py +3 -3
  205. django_spire/core/middleware.py +8 -6
  206. django_spire/core/redirect/__init__.py +5 -0
  207. django_spire/core/redirect/generic_redirect.py +1 -2
  208. django_spire/core/redirect/tests/__init__.py +0 -0
  209. django_spire/core/redirect/tests/test_generic_redirect.py +34 -0
  210. django_spire/core/{tests/tests_redirect.py → redirect/tests/test_safe_redirect.py} +55 -81
  211. django_spire/core/shortcuts.py +3 -3
  212. django_spire/core/static/django_spire/css/app-layout.css +1 -1
  213. django_spire/core/static/django_spire/css/app-navigation.css +3 -3
  214. django_spire/core/static/django_spire/css/bootstrap-override.css +4 -0
  215. django_spire/core/tag/admin.py +12 -0
  216. django_spire/core/tag/intelligence/tag_set_bot.py +2 -0
  217. django_spire/core/tag/mixins.py +2 -0
  218. django_spire/core/tag/models.py +2 -0
  219. django_spire/core/tag/querysets.py +2 -0
  220. django_spire/core/tag/service/tag_service.py +6 -3
  221. django_spire/core/tag/tests/test_intelligence.py +9 -9
  222. django_spire/core/tag/tests/test_tags.py +44 -54
  223. django_spire/core/tag/tests/test_tools.py +191 -0
  224. django_spire/core/tag/tools.py +3 -0
  225. django_spire/core/templates/django_spire/card/card.html +5 -2
  226. django_spire/core/templates/django_spire/card/title_card.html +12 -4
  227. django_spire/core/templates/django_spire/infinite_scroll/base.html +1 -0
  228. django_spire/core/templates/django_spire/navigation/side_navigation.html +19 -24
  229. django_spire/core/templates/django_spire/page/full_page.html +46 -16
  230. django_spire/core/templates/django_spire/table/base.html +5 -3
  231. django_spire/core/templatetags/json.py +6 -2
  232. django_spire/core/templatetags/message.py +13 -8
  233. django_spire/core/templatetags/string_formating.py +8 -5
  234. django_spire/core/templatetags/tests/__init__.py +0 -0
  235. django_spire/core/templatetags/tests/test_templatetags.py +427 -0
  236. django_spire/core/templatetags/variable_types.py +17 -9
  237. django_spire/core/tests/test_cases.py +1 -1
  238. django_spire/core/tests/test_conf.py +43 -0
  239. django_spire/core/tests/test_consts.py +28 -0
  240. django_spire/core/tests/test_context_processors.py +93 -0
  241. django_spire/core/tests/test_decorators.py +95 -0
  242. django_spire/core/tests/test_django_spire_utils.py +56 -0
  243. django_spire/core/tests/test_exceptions.py +37 -0
  244. django_spire/core/tests/test_models.py +54 -0
  245. django_spire/core/tests/test_settings.py +45 -0
  246. django_spire/core/tests/test_shortcuts.py +74 -0
  247. django_spire/core/tests/test_urls.py +16 -0
  248. django_spire/core/tests/test_utils.py +58 -0
  249. django_spire/core/urls.py +4 -1
  250. django_spire/core/utils.py +12 -8
  251. django_spire/exceptions.py +16 -1
  252. django_spire/file/admin.py +4 -2
  253. django_spire/file/apps.py +8 -10
  254. django_spire/file/fields.py +7 -7
  255. django_spire/file/forms.py +1 -1
  256. django_spire/file/interfaces.py +15 -15
  257. django_spire/file/mixins.py +1 -4
  258. django_spire/file/models.py +3 -5
  259. django_spire/file/tests/factories.py +59 -0
  260. django_spire/file/tests/test_admin.py +69 -0
  261. django_spire/file/tests/test_apps.py +24 -0
  262. django_spire/file/tests/test_fields.py +114 -0
  263. django_spire/file/tests/test_forms.py +20 -0
  264. django_spire/file/tests/test_interfaces.py +183 -0
  265. django_spire/file/tests/test_models.py +82 -0
  266. django_spire/file/tests/test_querysets.py +102 -0
  267. django_spire/file/tests/test_utils.py +32 -0
  268. django_spire/file/tests/test_views.py +145 -0
  269. django_spire/file/tests/test_widgets.py +82 -0
  270. django_spire/file/tools.py +8 -2
  271. django_spire/file/views.py +7 -3
  272. django_spire/file/widgets.py +12 -12
  273. django_spire/help_desk/admin.py +15 -0
  274. django_spire/help_desk/apps.py +2 -0
  275. django_spire/help_desk/auth/controller.py +2 -0
  276. django_spire/help_desk/choices.py +2 -0
  277. django_spire/help_desk/enums.py +2 -0
  278. django_spire/help_desk/exceptions.py +31 -3
  279. django_spire/help_desk/forms.py +2 -0
  280. django_spire/help_desk/models.py +2 -0
  281. django_spire/help_desk/querysets.py +4 -1
  282. django_spire/help_desk/services/notification_service.py +26 -27
  283. django_spire/help_desk/services/service.py +2 -3
  284. django_spire/help_desk/tests/factories.py +8 -3
  285. django_spire/help_desk/tests/test_admin.py +41 -0
  286. django_spire/help_desk/tests/test_apps.py +41 -0
  287. django_spire/help_desk/tests/test_choices.py +50 -0
  288. django_spire/help_desk/tests/test_controller.py +87 -0
  289. django_spire/help_desk/tests/test_enums.py +18 -0
  290. django_spire/help_desk/tests/test_exceptions.py +37 -0
  291. django_spire/help_desk/tests/test_forms.py +89 -0
  292. django_spire/help_desk/tests/test_models.py +59 -0
  293. django_spire/help_desk/tests/test_querysets.py +38 -0
  294. django_spire/help_desk/tests/test_services/test_notification_service.py +15 -8
  295. django_spire/help_desk/tests/test_services/test_service.py +92 -0
  296. django_spire/help_desk/tests/test_urls/test_form_urls.py +6 -6
  297. django_spire/help_desk/tests/test_urls/test_page_urls.py +8 -9
  298. django_spire/help_desk/tests/test_views/test_form_views.py +46 -19
  299. django_spire/help_desk/tests/test_views/test_page_views.py +32 -9
  300. django_spire/help_desk/urls/__init__.py +4 -1
  301. django_spire/help_desk/urls/form_urls.py +3 -0
  302. django_spire/help_desk/urls/page_urls.py +3 -0
  303. django_spire/help_desk/views/form_views.py +13 -5
  304. django_spire/help_desk/views/page_views.py +11 -3
  305. django_spire/history/activity/admin.py +2 -0
  306. django_spire/history/activity/apps.py +3 -1
  307. django_spire/history/activity/mixins.py +13 -7
  308. django_spire/history/activity/models.py +6 -5
  309. django_spire/history/activity/querysets.py +2 -0
  310. django_spire/history/activity/tests/__init__.py +0 -0
  311. django_spire/history/activity/tests/test_activity.py +176 -0
  312. django_spire/history/admin.py +9 -2
  313. django_spire/history/choices.py +3 -0
  314. django_spire/history/models.py +5 -5
  315. django_spire/history/tests/test_admin.py +93 -0
  316. django_spire/history/tests/test_history.py +101 -0
  317. django_spire/history/tests/test_mixins.py +84 -0
  318. django_spire/history/viewed/admin.py +3 -1
  319. django_spire/history/viewed/apps.py +3 -1
  320. django_spire/history/viewed/models.py +2 -0
  321. django_spire/history/viewed/tests/__init__.py +0 -0
  322. django_spire/history/viewed/tests/test_viewed.py +46 -0
  323. django_spire/knowledge/auth/tests/__init__.py +0 -0
  324. django_spire/knowledge/auth/tests/test_controller.py +116 -0
  325. django_spire/knowledge/collection/admin.py +5 -1
  326. django_spire/knowledge/collection/models.py +3 -1
  327. django_spire/knowledge/collection/seeding/seed.py +1 -0
  328. django_spire/knowledge/collection/services/factory_service.py +10 -11
  329. django_spire/knowledge/collection/services/ordering_service.py +1 -2
  330. django_spire/knowledge/collection/services/service.py +5 -10
  331. django_spire/knowledge/collection/services/tag_service.py +5 -2
  332. django_spire/knowledge/collection/tests/factories.py +28 -1
  333. django_spire/knowledge/collection/tests/test_models.py +48 -0
  334. django_spire/knowledge/collection/tests/test_querysets.py +93 -0
  335. django_spire/knowledge/collection/tests/test_services/test_factory_service.py +100 -0
  336. django_spire/knowledge/collection/tests/test_services/test_services.py +160 -0
  337. django_spire/knowledge/collection/tests/test_urls/test_form_urls.py +21 -3
  338. django_spire/knowledge/collection/tests/test_urls/test_json_urls.py +39 -1
  339. django_spire/knowledge/collection/tests/test_urls/test_page_urls.py +12 -4
  340. django_spire/knowledge/collection/urls/__init__.py +3 -0
  341. django_spire/knowledge/collection/urls/form_urls.py +2 -0
  342. django_spire/knowledge/collection/urls/json_urls.py +2 -0
  343. django_spire/knowledge/collection/urls/page_urls.py +2 -0
  344. django_spire/knowledge/collection/views/form_views.py +4 -4
  345. django_spire/knowledge/collection/views/json_views.py +5 -1
  346. django_spire/knowledge/collection/views/page_views.py +5 -2
  347. django_spire/knowledge/entry/admin.py +7 -1
  348. django_spire/knowledge/entry/forms.py +2 -0
  349. django_spire/knowledge/entry/models.py +2 -0
  350. django_spire/knowledge/entry/seeding/seed.py +3 -0
  351. django_spire/knowledge/entry/services/automation_service.py +5 -4
  352. django_spire/knowledge/entry/services/factory_service.py +7 -5
  353. django_spire/knowledge/entry/services/service.py +4 -7
  354. django_spire/knowledge/entry/services/tag_service.py +0 -1
  355. django_spire/knowledge/entry/services/tool_service.py +1 -0
  356. django_spire/knowledge/entry/services/transformation_services.py +1 -5
  357. django_spire/knowledge/entry/tests/factories.py +1 -2
  358. django_spire/knowledge/entry/tests/test_factory_service.py +20 -0
  359. django_spire/knowledge/entry/tests/test_models.py +41 -0
  360. django_spire/knowledge/entry/tests/test_querysets.py +71 -0
  361. django_spire/knowledge/entry/tests/test_services.py +94 -0
  362. django_spire/knowledge/entry/tests/test_urls/test_form_urls.py +9 -14
  363. django_spire/knowledge/entry/tests/test_urls/test_json_urls.py +48 -5
  364. django_spire/knowledge/entry/tests/test_urls/test_page_urls.py +6 -8
  365. django_spire/knowledge/entry/tests/test_urls/test_template_urls.py +40 -0
  366. django_spire/knowledge/entry/urls/form_urls.py +2 -0
  367. django_spire/knowledge/entry/urls/json_urls.py +2 -0
  368. django_spire/knowledge/entry/urls/page_urls.py +2 -0
  369. django_spire/knowledge/entry/urls/template_urls.py +2 -0
  370. django_spire/knowledge/entry/version/block/choices.py +2 -0
  371. django_spire/knowledge/entry/version/block/data/data.py +1 -0
  372. django_spire/knowledge/entry/version/block/data/list/data.py +8 -13
  373. django_spire/knowledge/entry/version/block/data/list/maps.py +3 -0
  374. django_spire/knowledge/entry/version/block/data/list/meta.py +1 -2
  375. django_spire/knowledge/entry/version/block/data/list/tests/__init__.py +0 -0
  376. django_spire/knowledge/entry/version/block/data/list/tests/test_maps.py +32 -0
  377. django_spire/knowledge/entry/version/block/data/list/tests/test_meta.py +58 -0
  378. django_spire/knowledge/entry/version/block/data/maps.py +3 -6
  379. django_spire/knowledge/entry/version/block/models.py +7 -5
  380. django_spire/knowledge/entry/version/block/seeding/constants.py +5 -4
  381. django_spire/knowledge/entry/version/block/services/service.py +2 -3
  382. django_spire/knowledge/entry/version/block/tests/factories.py +4 -10
  383. django_spire/knowledge/entry/version/block/tests/test_choices.py +56 -0
  384. django_spire/knowledge/entry/version/block/tests/test_data.py +90 -0
  385. django_spire/knowledge/entry/version/block/tests/test_maps.py +37 -0
  386. django_spire/knowledge/entry/version/block/tests/test_models.py +55 -0
  387. django_spire/knowledge/entry/version/block/tests/test_querysets.py +35 -0
  388. django_spire/knowledge/entry/version/block/tests/test_services.py +65 -0
  389. django_spire/knowledge/entry/version/choices.py +2 -0
  390. django_spire/knowledge/entry/version/converters/converter.py +1 -1
  391. django_spire/knowledge/entry/version/converters/docx_converter.py +4 -7
  392. django_spire/knowledge/entry/version/converters/markdown_converter.py +20 -20
  393. django_spire/knowledge/entry/version/maps.py +4 -5
  394. django_spire/knowledge/entry/version/querysets.py +1 -1
  395. django_spire/knowledge/entry/version/seeding/seeder.py +1 -2
  396. django_spire/knowledge/entry/version/services/processor_service.py +5 -4
  397. django_spire/knowledge/entry/version/services/service.py +1 -2
  398. django_spire/knowledge/entry/version/tests/factories.py +2 -2
  399. django_spire/knowledge/entry/version/tests/test_choices.py +18 -0
  400. django_spire/knowledge/entry/version/tests/test_converters/test_docx_converter.py +56 -8
  401. django_spire/knowledge/entry/version/tests/test_converters/test_markdown_converter.py +78 -0
  402. django_spire/knowledge/entry/version/tests/test_maps.py +58 -0
  403. django_spire/knowledge/entry/version/tests/test_models.py +23 -0
  404. django_spire/knowledge/entry/version/tests/test_querysets.py +26 -0
  405. django_spire/knowledge/entry/version/tests/test_services.py +62 -0
  406. django_spire/knowledge/entry/version/tests/test_urls/test_json_urls.py +27 -8
  407. django_spire/knowledge/entry/version/tests/test_urls/test_page_urls.py +15 -8
  408. django_spire/knowledge/entry/version/tests/test_urls/test_redirect_urls.py +38 -0
  409. django_spire/knowledge/entry/version/urls/__init__.py +3 -0
  410. django_spire/knowledge/entry/version/urls/json_urls.py +2 -1
  411. django_spire/knowledge/entry/version/urls/page_urls.py +2 -0
  412. django_spire/knowledge/entry/version/urls/redirect_urls.py +2 -0
  413. django_spire/knowledge/entry/version/views/json_views.py +5 -1
  414. django_spire/knowledge/entry/version/views/page_views.py +10 -3
  415. django_spire/knowledge/entry/version/views/redirect_views.py +5 -1
  416. django_spire/knowledge/entry/views/form_views.py +16 -8
  417. django_spire/knowledge/entry/views/json_views.py +3 -1
  418. django_spire/knowledge/entry/views/page_views.py +8 -2
  419. django_spire/knowledge/entry/views/template_views.py +7 -1
  420. django_spire/knowledge/exceptions.py +2 -1
  421. django_spire/knowledge/intelligence/intel/answer_intel.py +2 -1
  422. django_spire/knowledge/intelligence/intel/entry_intel.py +0 -1
  423. django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +4 -5
  424. django_spire/knowledge/models.py +1 -2
  425. django_spire/knowledge/tests/__init__.py +0 -0
  426. django_spire/knowledge/tests/test_templatetags.py +40 -0
  427. django_spire/knowledge/tests/test_urls/__init__.py +0 -0
  428. django_spire/knowledge/tests/test_urls/test_page_urls.py +24 -0
  429. django_spire/knowledge/urls/__init__.py +2 -0
  430. django_spire/knowledge/urls/page_urls.py +2 -0
  431. django_spire/knowledge/views/page_views.py +8 -3
  432. django_spire/notification/admin.py +3 -1
  433. django_spire/notification/app/admin.py +2 -0
  434. django_spire/notification/app/apps.py +3 -1
  435. django_spire/notification/app/exceptions.py +9 -2
  436. django_spire/notification/app/models.py +8 -4
  437. django_spire/notification/app/processor.py +22 -26
  438. django_spire/notification/app/querysets.py +2 -0
  439. django_spire/notification/app/tests/__init__.py +0 -0
  440. django_spire/notification/app/tests/factories.py +34 -0
  441. django_spire/notification/app/tests/test_apps.py +24 -0
  442. django_spire/notification/app/tests/test_models.py +72 -0
  443. django_spire/notification/app/tests/test_processor.py +111 -0
  444. django_spire/notification/app/tests/test_querysets.py +90 -0
  445. django_spire/notification/app/tests/test_views/__init__.py +0 -0
  446. django_spire/notification/app/tests/test_views/test_json_views.py +48 -0
  447. django_spire/notification/app/tests/test_views/test_page_views.py +19 -0
  448. django_spire/notification/app/urls/__init__.py +3 -1
  449. django_spire/notification/app/urls/json_urls.py +6 -4
  450. django_spire/notification/app/urls/page_urls.py +4 -3
  451. django_spire/notification/app/urls/template_urls.py +4 -2
  452. django_spire/notification/apps.py +4 -1
  453. django_spire/notification/email/admin.py +5 -1
  454. django_spire/notification/email/apps.py +3 -1
  455. django_spire/notification/email/exceptions.py +4 -2
  456. django_spire/notification/email/helper.py +5 -3
  457. django_spire/notification/email/models.py +4 -0
  458. django_spire/notification/email/processor.py +19 -15
  459. django_spire/notification/email/querysets.py +3 -0
  460. django_spire/notification/email/tests/__init__.py +0 -0
  461. django_spire/notification/email/tests/factories.py +35 -0
  462. django_spire/notification/email/tests/test_apps.py +24 -0
  463. django_spire/notification/email/tests/test_models.py +52 -0
  464. django_spire/notification/email/tests/test_processor.py +92 -0
  465. django_spire/notification/email/tests/test_querysets.py +43 -0
  466. django_spire/notification/exceptions.py +17 -2
  467. django_spire/notification/managers.py +7 -1
  468. django_spire/notification/maps.py +4 -1
  469. django_spire/notification/mixins.py +2 -0
  470. django_spire/notification/models.py +3 -1
  471. django_spire/notification/processors/notification.py +12 -5
  472. django_spire/notification/processors/processor.py +2 -0
  473. django_spire/notification/processors/tests/__init__.py +0 -0
  474. django_spire/notification/processors/tests/test_notification.py +106 -0
  475. django_spire/notification/push/admin.py +10 -1
  476. django_spire/notification/push/apps.py +3 -1
  477. django_spire/notification/push/models.py +2 -3
  478. django_spire/notification/push/tests/__init__.py +0 -0
  479. django_spire/notification/push/tests/test_apps.py +24 -0
  480. django_spire/notification/push/tests/test_models.py +28 -0
  481. django_spire/notification/querysets.py +7 -1
  482. django_spire/notification/sms/admin.py +2 -0
  483. django_spire/notification/sms/apps.py +4 -1
  484. django_spire/notification/sms/automations.py +2 -0
  485. django_spire/notification/sms/choices.py +2 -0
  486. django_spire/notification/sms/exceptions.py +19 -5
  487. django_spire/notification/sms/helper.py +33 -23
  488. django_spire/notification/sms/models.py +5 -1
  489. django_spire/notification/sms/processor.py +20 -20
  490. django_spire/notification/sms/querysets.py +2 -0
  491. django_spire/notification/sms/tests/factories.py +33 -0
  492. django_spire/notification/sms/tests/test_apps.py +24 -0
  493. django_spire/notification/sms/tests/test_automation.py +38 -0
  494. django_spire/notification/sms/tests/test_choices.py +15 -0
  495. django_spire/notification/sms/tests/test_consts.py +17 -0
  496. django_spire/notification/sms/tests/test_exceptions.py +27 -0
  497. django_spire/notification/sms/tests/test_helper.py +50 -0
  498. django_spire/notification/sms/tests/test_models.py +81 -0
  499. django_spire/notification/sms/tests/test_processor.py +107 -0
  500. django_spire/notification/sms/tests/test_tools.py +25 -11
  501. django_spire/notification/sms/tools.py +16 -5
  502. django_spire/notification/sms/urls/__init__.py +3 -1
  503. django_spire/notification/sms/urls/media_urls.py +2 -0
  504. django_spire/notification/sms/views/media_views.py +14 -4
  505. django_spire/notification/tests/__init__.py +0 -0
  506. django_spire/notification/tests/factories.py +26 -0
  507. django_spire/notification/tests/test_admin.py +55 -0
  508. django_spire/notification/tests/test_apps.py +30 -0
  509. django_spire/notification/tests/test_automation.py +18 -0
  510. django_spire/notification/tests/test_choices.py +59 -0
  511. django_spire/notification/tests/test_exceptions.py +58 -0
  512. django_spire/notification/tests/test_managers.py +100 -0
  513. django_spire/notification/tests/test_maps.py +31 -0
  514. django_spire/notification/tests/test_models.py +76 -0
  515. django_spire/notification/tests/test_querysets.py +184 -0
  516. django_spire/notification/tests/test_utils.py +23 -0
  517. django_spire/notification/urls.py +3 -1
  518. django_spire/notification/utils.py +3 -1
  519. django_spire/settings.py +3 -0
  520. django_spire/theme/tests/test_context_processor.py +15 -13
  521. django_spire/theme/tests/test_enums.py +2 -2
  522. django_spire/theme/tests/test_filesystem.py +2 -5
  523. django_spire/theme/tests/test_integration.py +12 -12
  524. django_spire/theme/tests/test_model.py +40 -38
  525. django_spire/theme/tests/test_views/test_json_views.py +33 -33
  526. django_spire/theme/urls/json_urls.py +3 -0
  527. django_spire/theme/urls/page_urls.py +3 -0
  528. django_spire/urls.py +19 -15
  529. django_spire/utils.py +13 -4
  530. {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/METADATA +2 -2
  531. {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/RECORD +534 -362
  532. {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/licenses/LICENSE.md +1 -1
  533. django_spire/contrib/options/tests/test_unit.py +0 -148
  534. django_spire/contrib/seeding/tests/test_seeding.py +0 -25
  535. django_spire/core/tests/test_templatetags.py +0 -117
  536. django_spire/core/tests/tests_shortcuts.py +0 -73
  537. django_spire/history/activity/tests.py +0 -3
  538. django_spire/history/activity/views.py +0 -3
  539. django_spire/knowledge/collection/tests/test_services/test_transformation_service.py +0 -71
  540. django_spire/notification/app/tests.py +0 -3
  541. {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/WHEEL +0 -0
  542. {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,234 @@
1
+ import pytest
2
+
3
+ from django.test import RequestFactory, TestCase
4
+ from unittest.mock import MagicMock
5
+
6
+ from django_spire.contrib.options.mixins import OptionsModelMixin
7
+ from django_spire.contrib.options.options import Option, Options, OptionSection
8
+ from django_spire.contrib.options.tests import factories
9
+
10
+
11
+ class TestOption(TestCase):
12
+ def test_init(self) -> None:
13
+ option = Option('email', True)
14
+
15
+ assert option.key == 'email'
16
+ assert option.value is True
17
+
18
+ def test_value_can_be_string(self) -> None:
19
+ option = Option('timezone', 'America/Edmonton')
20
+
21
+ assert option.value == 'America/Edmonton'
22
+
23
+ def test_value_can_be_int(self) -> None:
24
+ option = Option('volume', 50)
25
+
26
+ assert option.value == 50
27
+
28
+ def test_value_can_be_bool(self) -> None:
29
+ option = Option('enabled', False)
30
+
31
+ assert option.value is False
32
+
33
+
34
+ class TestOptionSection(TestCase):
35
+ def test_contains_existing_option(self) -> None:
36
+ section = factories.create_test_option_section()
37
+
38
+ assert 'email' in section
39
+
40
+ def test_contains_missing_option(self) -> None:
41
+ section = factories.create_test_option_section()
42
+
43
+ assert 'invalid' not in section
44
+
45
+ def test_equal(self) -> None:
46
+ section1 = factories.create_test_option_section()
47
+ section2 = factories.create_test_option_section()
48
+
49
+ assert section1 == section2
50
+
51
+ def test_getitem(self) -> None:
52
+ section = factories.create_test_option_section()
53
+
54
+ assert isinstance(section['email'], Option)
55
+ assert section['email'].value is True
56
+
57
+ def test_getitem_case_insensitive(self) -> None:
58
+ section = factories.create_test_option_section()
59
+
60
+ assert section['EMAIL'].value is True
61
+
62
+ def test_getitem_key_error(self) -> None:
63
+ section = factories.create_test_option_section()
64
+
65
+ with pytest.raises(KeyError):
66
+ section['invalid']
67
+
68
+ def test_not_equal(self) -> None:
69
+ section1 = factories.create_test_option_section()
70
+ section2 = OptionSection(
71
+ name='other',
72
+ options=[Option('different', 'value')]
73
+ )
74
+
75
+ assert section1 != section2
76
+
77
+ def test_to_dict(self) -> None:
78
+ section = factories.create_test_option_section()
79
+ expected = {'email': True, 'push': False}
80
+
81
+ assert section.to_dict() == expected
82
+
83
+
84
+ class TestOptions(TestCase):
85
+ def test_contains_existing_section(self) -> None:
86
+ options = factories.create_test_options()
87
+
88
+ assert 'notification' in options
89
+
90
+ def test_contains_missing_section(self) -> None:
91
+ options = factories.create_test_options()
92
+
93
+ assert 'invalid' not in options
94
+
95
+ def test_equal_same_structure(self) -> None:
96
+ options1 = factories.create_test_options()
97
+ options2 = factories.create_test_options()
98
+
99
+ assert options1 == options2
100
+
101
+ def test_equal_different_sections(self) -> None:
102
+ options1 = factories.create_test_options()
103
+ options2 = Options.load_dict({
104
+ 'General': {'volume': 10},
105
+ 'Notifications': {'email': True}
106
+ })
107
+
108
+ assert options1 != options2
109
+
110
+ def test_equal_different_values_same_keys(self) -> None:
111
+ options1 = Options.load_dict({
112
+ 'General': {'volume': 10, 'language': 'en'},
113
+ 'Notifications': {'email': True}
114
+ })
115
+ options2 = Options.load_dict({
116
+ 'General': {'volume': 10},
117
+ 'Notifications': {'email': True}
118
+ })
119
+
120
+ assert options1 != options2
121
+
122
+ def test_get_setting(self) -> None:
123
+ options = factories.create_test_options()
124
+
125
+ assert options.get_setting('notification', 'push') is False
126
+
127
+ def test_getitem(self) -> None:
128
+ options = factories.create_test_options()
129
+
130
+ assert isinstance(options['notification'], OptionSection)
131
+ assert isinstance(options['notification']['push'], Option)
132
+ assert options['notification']['push'].value is False
133
+
134
+ def test_getitem_case_insensitive(self) -> None:
135
+ options = factories.create_test_options()
136
+
137
+ assert options['NOTIFICATION']['PUSH'].value is False
138
+
139
+ def test_getitem_key_error(self) -> None:
140
+ options = factories.create_test_options()
141
+
142
+ with pytest.raises(KeyError):
143
+ options['invalid']
144
+
145
+ def test_load_dict(self) -> None:
146
+ options_dict = factories.create_test_options_dict()
147
+ options = Options.load_dict(options_dict)
148
+
149
+ assert options.get_setting('notification', 'push') is False
150
+ assert 'notification' in options
151
+
152
+ def test_sync_options_adds_new_sections(self) -> None:
153
+ original = Options.load_dict({
154
+ 'General': {'volume': 10},
155
+ })
156
+ new_structure = Options.load_dict({
157
+ 'General': {'volume': 5},
158
+ 'Privacy': {'location': False}
159
+ })
160
+
161
+ original.sync_options(new_structure)
162
+
163
+ assert 'Privacy' in original
164
+
165
+ def test_sync_options_adds_new_settings(self) -> None:
166
+ original = Options.load_dict({
167
+ 'General': {'volume': 10},
168
+ })
169
+ new_structure = Options.load_dict({
170
+ 'General': {'volume': 5, 'brightness': 70},
171
+ })
172
+
173
+ original.sync_options(new_structure)
174
+
175
+ assert 'brightness' in original['General']
176
+
177
+ def test_sync_options_preserves_existing_values(self) -> None:
178
+ original = Options.load_dict({
179
+ 'General': {'volume': 10},
180
+ })
181
+ new_structure = Options.load_dict({
182
+ 'General': {'volume': 5, 'brightness': 70},
183
+ })
184
+
185
+ original.sync_options(new_structure)
186
+
187
+ assert original.get_setting('General', 'volume') == 10
188
+
189
+ def test_to_dict(self) -> None:
190
+ options = factories.create_test_options()
191
+ expected = factories.create_test_options_dict()
192
+
193
+ assert options.to_dict() == expected
194
+
195
+ def test_update_setting(self) -> None:
196
+ options = factories.create_test_options()
197
+
198
+ options.update_setting('notification', 'push', True)
199
+
200
+ assert options.get_setting('notification', 'push') is True
201
+
202
+
203
+ class TestOptionsModelMixin(TestCase):
204
+ def test_get_option(self) -> None:
205
+ mixin = MagicMock(spec=OptionsModelMixin)
206
+ mixin._default_options = factories.create_test_options()
207
+ mixin.options = factories.create_test_options()
208
+ mixin._options = mixin.options.to_dict()
209
+
210
+ result = OptionsModelMixin.get_option(mixin, 'notification', 'email')
211
+
212
+ assert result is True
213
+
214
+ def test_update_option(self) -> None:
215
+ mixin = MagicMock(spec=OptionsModelMixin)
216
+ mixin._default_options = factories.create_test_options()
217
+ mixin.options = factories.create_test_options()
218
+ mixin._options = mixin.options.to_dict()
219
+
220
+ OptionsModelMixin.update_option(mixin, 'notification', 'email', False, commit=False)
221
+
222
+ assert mixin.options.get_setting('notification', 'email') is False
223
+ assert mixin._options == mixin.options.to_dict()
224
+
225
+ def test_update_session(self) -> None:
226
+ mixin = MagicMock(spec=OptionsModelMixin)
227
+ mixin.options = factories.create_test_options()
228
+
229
+ request = RequestFactory().get('/')
230
+ request.session = {}
231
+
232
+ OptionsModelMixin.update_session(mixin, request)
233
+
234
+ assert request.session['user_options'] == mixin.options.to_dict()
@@ -1,5 +1,9 @@
1
- class OrderingMixinExceptionGroup(Exception):
1
+ from __future__ import annotations
2
+
3
+
4
+ class OrderingMixinGroupError(Exception):
2
5
  pass
3
6
 
4
- class OrderingMixinException(Exception):
5
- pass
7
+
8
+ class OrderingMixinError(Exception):
9
+ pass
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.db import models
2
4
 
3
5
  from django_spire.contrib.ordering.services.service import OrderingService
@@ -1,4 +1,6 @@
1
- from django.db.models import QuerySet, F
1
+ from __future__ import annotations
2
+
3
+ from django.db.models import QuerySet
2
4
 
3
5
 
4
6
  class OrderingQuerySetMixin(QuerySet):
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING
4
4
 
5
- from django_spire.contrib.ordering.exceptions import OrderingMixinExceptionGroup
5
+ from django_spire.contrib.ordering.exceptions import OrderingMixinGroupError
6
6
  from django_spire.contrib.ordering.validators import OrderingMixinValidator
7
7
  from django_spire.contrib.service import BaseDjangoModelService
8
8
 
@@ -55,8 +55,10 @@ class OrderingProcessorService(BaseDjangoModelService['OrderingModelMixin']):
55
55
  )
56
56
 
57
57
  if not ordering_mixin_validator.validate():
58
- raise OrderingMixinExceptionGroup(
59
- 'Ordering validation failed.',
58
+ message = 'Ordering validation failed.'
59
+
60
+ raise OrderingMixinGroupError(
61
+ message,
60
62
  ordering_mixin_validator.errors
61
63
  )
62
64
 
@@ -84,8 +86,10 @@ class OrderingProcessorService(BaseDjangoModelService['OrderingModelMixin']):
84
86
  )
85
87
 
86
88
  if not ordering_mixin_validator.validate():
89
+ message = 'Ordering validation failed.'
90
+
87
91
  raise ExceptionGroup(
88
- 'Ordering validation failed.',
92
+ message,
89
93
  ordering_mixin_validator.errors
90
94
  )
91
95
 
@@ -2,8 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING
4
4
 
5
- from django_spire.contrib.ordering.services.processor_service import \
6
- OrderingProcessorService
5
+ from django_spire.contrib.ordering.services.processor_service import OrderingProcessorService
7
6
  from django_spire.contrib.service import BaseDjangoModelService
8
7
 
9
8
  if TYPE_CHECKING:
File without changes
@@ -0,0 +1,165 @@
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.contrib.ordering.exceptions import OrderingMixinError, OrderingMixinGroupError
10
+ from django_spire.contrib.ordering.mixins import OrderingModelMixin
11
+ from django_spire.contrib.ordering.services.processor_service import OrderingProcessorService
12
+ from django_spire.contrib.ordering.services.service import OrderingService
13
+ from django_spire.contrib.ordering.validators import OrderingMixinValidator
14
+
15
+
16
+ class TestOrderingMixinError(TestCase):
17
+ def test_can_be_raised(self) -> None:
18
+ with pytest.raises(OrderingMixinError):
19
+ message = 'Test'
20
+ raise OrderingMixinError(message)
21
+
22
+ def test_is_exception(self) -> None:
23
+ assert issubclass(OrderingMixinError, Exception)
24
+
25
+ def test_message(self) -> None:
26
+ error = OrderingMixinError('Test error message')
27
+
28
+ assert str(error) == 'Test error message'
29
+
30
+
31
+ class TestOrderingMixinGroupError(TestCase):
32
+ def test_can_be_raised(self) -> None:
33
+ with pytest.raises(OrderingMixinGroupError):
34
+ message = 'Test'
35
+ raise OrderingMixinGroupError(message)
36
+
37
+ def test_is_exception(self) -> None:
38
+ assert issubclass(OrderingMixinGroupError, Exception)
39
+
40
+ def test_message(self) -> None:
41
+ error = OrderingMixinGroupError('Test group error message')
42
+
43
+ assert str(error) == 'Test group error message'
44
+
45
+
46
+ class TestOrderingMixinValidator(TestCase):
47
+ def setUp(self) -> None:
48
+ self.obj = MagicMock()
49
+ self.obj.pk = 1
50
+
51
+ self.destination_objects = MagicMock()
52
+ self.destination_objects.__len__ = MagicMock(return_value=5)
53
+
54
+ self.origin_objects = MagicMock()
55
+ self.origin_objects.__len__ = MagicMock(return_value=5)
56
+
57
+ def test_errors_contain_ordering_mixin_error(self) -> None:
58
+ validator = OrderingMixinValidator(
59
+ destination_objects=self.destination_objects,
60
+ position=-1,
61
+ obj=self.obj,
62
+ origin_objects=self.origin_objects
63
+ )
64
+
65
+ validator.validate()
66
+
67
+ assert all(isinstance(error, OrderingMixinError) for error in validator.errors)
68
+
69
+ def test_errors_property_returns_list(self) -> None:
70
+ validator = OrderingMixinValidator(
71
+ destination_objects=self.destination_objects,
72
+ position=0,
73
+ obj=self.obj,
74
+ origin_objects=self.origin_objects
75
+ )
76
+
77
+ assert isinstance(validator.errors, list)
78
+
79
+ def test_validate_position_at_boundary(self) -> None:
80
+ validator = OrderingMixinValidator(
81
+ destination_objects=self.destination_objects,
82
+ position=5,
83
+ obj=self.obj,
84
+ origin_objects=self.origin_objects
85
+ )
86
+
87
+ assert validator.validate() is True
88
+
89
+ def test_validate_position_zero(self) -> None:
90
+ validator = OrderingMixinValidator(
91
+ destination_objects=self.destination_objects,
92
+ position=0,
93
+ obj=self.obj,
94
+ origin_objects=self.origin_objects
95
+ )
96
+
97
+ assert validator.validate() is True
98
+
99
+ def test_validate_returns_false_for_negative_position(self) -> None:
100
+ validator = OrderingMixinValidator(
101
+ destination_objects=self.destination_objects,
102
+ position=-1,
103
+ obj=self.obj,
104
+ origin_objects=self.origin_objects
105
+ )
106
+
107
+ assert validator.validate() is False
108
+ assert len(validator.errors) == 1
109
+
110
+ def test_validate_returns_false_for_position_greater_than_objects(self) -> None:
111
+ validator = OrderingMixinValidator(
112
+ destination_objects=self.destination_objects,
113
+ position=10,
114
+ obj=self.obj,
115
+ origin_objects=self.origin_objects
116
+ )
117
+
118
+ assert validator.validate() is False
119
+ assert len(validator.errors) == 1
120
+
121
+ def test_validate_returns_true_for_valid_position(self) -> None:
122
+ validator = OrderingMixinValidator(
123
+ destination_objects=self.destination_objects,
124
+ position=2,
125
+ obj=self.obj,
126
+ origin_objects=self.origin_objects
127
+ )
128
+
129
+ assert validator.validate() is True
130
+
131
+
132
+ class TestOrderingModelMixin(TestCase):
133
+ def test_has_order_field(self) -> None:
134
+ assert hasattr(OrderingModelMixin, 'order')
135
+
136
+ def test_has_ordering_services_in_dict(self) -> None:
137
+ assert 'ordering_services' in OrderingModelMixin.__dict__
138
+
139
+ def test_is_abstract(self) -> None:
140
+ assert OrderingModelMixin._meta.abstract is True
141
+
142
+ def test_order_default_value(self) -> None:
143
+ assert OrderingModelMixin._meta.get_field('order').default == 0
144
+
145
+
146
+ class TestOrderingProcessorService(TestCase):
147
+ def test_has_move_to_position_method(self) -> None:
148
+ assert hasattr(OrderingProcessorService, 'move_to_position')
149
+
150
+ def test_has_remove_from_objects_method(self) -> None:
151
+ assert hasattr(OrderingProcessorService, 'remove_from_objects')
152
+
153
+ def test_has_reorder_destination_and_origin_objects_method(self) -> None:
154
+ assert hasattr(OrderingProcessorService, '_reorder_destination_and_origin_objects')
155
+
156
+ def test_has_reorder_objects_method(self) -> None:
157
+ assert hasattr(OrderingProcessorService, '_reorder_objects')
158
+
159
+
160
+ class TestOrderingService(TestCase):
161
+ def test_has_processor_in_dict(self) -> None:
162
+ assert 'processor' in OrderingService.__dict__
163
+
164
+ def test_processor_in_dict_is_ordering_processor_service(self) -> None:
165
+ assert isinstance(OrderingService.__dict__['processor'], OrderingProcessorService)
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING
4
4
 
5
- from django_spire.contrib.ordering.exceptions import OrderingMixinException
5
+ from django_spire.contrib.ordering.exceptions import OrderingMixinError
6
6
 
7
7
  if TYPE_CHECKING:
8
8
  from django.db.models import Model, QuerySet
@@ -21,11 +21,11 @@ class OrderingMixinValidator:
21
21
  self._origin_objects = origin_objects
22
22
  self._obj = obj
23
23
 
24
- self._errors: list[OrderingMixinException] = []
24
+ self._errors: list[OrderingMixinError] = []
25
25
 
26
26
  @property
27
- def errors(self) -> list[OrderingMixinException]:
28
- """Returns list of OrderingMixinException errors."""
27
+ def errors(self) -> list[OrderingMixinError]:
28
+ """Returns list of OrderingMixinError errors."""
29
29
  return self._errors
30
30
 
31
31
  def validate(self) -> bool:
@@ -40,7 +40,7 @@ class OrderingMixinValidator:
40
40
  def _validate_position(self):
41
41
  """Ensure position is valid."""
42
42
  if self._position < 0 or not isinstance(self._position, int):
43
- self._errors.append(OrderingMixinException('Position must be a positive number.'))
43
+ self._errors.append(OrderingMixinError('Position must be a positive number.'))
44
44
 
45
45
  if self._position > len(self._destination_objects):
46
- self._errors.append(OrderingMixinException('Position must be less than the number of destination objects.'))
46
+ self._errors.append(OrderingMixinError('Position must be less than the number of destination objects.'))
@@ -1,18 +1,25 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from typing import TYPE_CHECKING
4
+
3
5
  from django import template
4
6
 
7
+ if TYPE_CHECKING:
8
+ from typing import Any, Iterator
9
+
10
+ from django.core.paginator import Page
11
+
5
12
 
6
13
  register = template.Library()
7
14
 
8
15
 
9
16
  @register.simple_tag(takes_context=True)
10
17
  def pagination_url(
11
- context,
18
+ context: template.Context,
12
19
  page_number: int,
13
20
  page_name: str = 'page',
14
- **kwargs
15
- ):
21
+ **kwargs: Any
22
+ ) -> str:
16
23
  updated_context = context.request.GET.copy()
17
24
  updated_context[page_name] = page_number
18
25
  query_string = '?'
@@ -33,10 +40,10 @@ def pagination_url(
33
40
 
34
41
  @register.simple_tag
35
42
  def get_elided_page_range(
36
- page_obj,
43
+ page_obj: Page[Any],
37
44
  on_each_side: int = 2,
38
45
  on_ends: int = 2
39
- ):
46
+ ) -> Iterator[int | str]:
40
47
  return page_obj.paginator.get_elided_page_range(
41
48
  number=page_obj.number,
42
49
  on_each_side=on_each_side,
File without changes