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.
- django_spire/ai/admin.py +11 -11
- django_spire/ai/chat/apps.py +1 -0
- django_spire/ai/chat/templates/django_spire/ai/chat/widget/dialog_widget.html +1 -1
- django_spire/ai/chat/tests/factories.py +15 -0
- django_spire/ai/chat/tests/test_controller.py +45 -0
- django_spire/ai/chat/tests/test_models.py +301 -0
- django_spire/ai/chat/tests/test_prompts.py +48 -0
- django_spire/ai/chat/tests/test_responses.py +208 -0
- django_spire/ai/chat/tests/test_router/test_base_chat_router.py +66 -6
- django_spire/ai/chat/tests/test_router/test_chat_workflow.py +73 -3
- django_spire/ai/chat/tests/test_router/test_integration.py +86 -6
- django_spire/ai/chat/tests/test_router/test_intent_decoder.py +93 -1
- django_spire/ai/chat/tests/test_router/test_message_intel.py +60 -1
- django_spire/ai/chat/tests/test_router/test_spire_chat_router.py +110 -0
- django_spire/ai/chat/tests/test_urls/test_json_urls.py +202 -1
- django_spire/ai/context/tests/__init__.py +0 -0
- django_spire/ai/context/tests/test_context.py +188 -0
- django_spire/ai/decorators.py +7 -6
- django_spire/ai/prompt/tests/test_bots.py +100 -10
- django_spire/ai/prompt/tests/test_prompt_intel.py +83 -0
- django_spire/ai/prompt/tests/test_prompt_tuning.py +126 -0
- django_spire/ai/sms/decorators.py +8 -2
- django_spire/ai/sms/tests/test_sms.py +240 -16
- django_spire/ai/sms/tests/test_sms_intel.py +42 -0
- django_spire/ai/sms/tests/test_webhook.py +155 -7
- django_spire/ai/sms/views.py +23 -24
- django_spire/ai/tests/test_ai.py +131 -7
- django_spire/auth/apps.py +4 -2
- django_spire/auth/controller/controller.py +36 -23
- django_spire/auth/controller/exceptions.py +9 -0
- django_spire/auth/group/admin.py +1 -0
- django_spire/auth/group/apps.py +2 -0
- django_spire/auth/group/factories.py +17 -8
- django_spire/auth/group/forms.py +7 -0
- django_spire/auth/group/tests/test_factories.py +146 -0
- django_spire/auth/group/tests/test_forms.py +282 -0
- django_spire/auth/group/tests/test_models.py +192 -0
- django_spire/auth/group/tests/test_querysets.py +98 -0
- django_spire/auth/group/tests/test_utils.py +341 -0
- django_spire/auth/group/tests/test_views.py +377 -0
- django_spire/auth/group/urls/__init__.py +3 -1
- django_spire/auth/group/urls/form_urls.py +2 -0
- django_spire/auth/group/urls/json_urls.py +3 -0
- django_spire/auth/group/urls/page_urls.py +2 -0
- django_spire/auth/group/utils.py +6 -2
- django_spire/auth/group/views/form_views.py +6 -3
- django_spire/auth/group/views/json_views.py +6 -2
- django_spire/auth/mfa/admin.py +2 -0
- django_spire/auth/mfa/apps.py +2 -0
- django_spire/auth/mfa/forms.py +1 -0
- django_spire/auth/mfa/querysets.py +9 -2
- django_spire/auth/mfa/tests/test_models.py +233 -0
- django_spire/auth/mfa/tests/test_utils.py +106 -0
- django_spire/auth/mfa/urls/__init__.py +2 -0
- django_spire/auth/mfa/urls/page_urls.py +2 -0
- django_spire/auth/mfa/urls/redirect_urls.py +2 -0
- django_spire/auth/mfa/views/page_views.py +2 -1
- django_spire/auth/permissions/consts.py +2 -2
- django_spire/auth/permissions/decorators.py +8 -8
- django_spire/auth/permissions/permissions.py +28 -35
- django_spire/auth/permissions/tests/test_decorators.py +333 -0
- django_spire/auth/permissions/tests/test_permissions.py +337 -0
- django_spire/auth/permissions/tests/test_tools.py +305 -0
- django_spire/auth/permissions/tools.py +21 -15
- django_spire/auth/seeding/seed.py +3 -0
- django_spire/auth/seeding/seeder.py +2 -0
- django_spire/auth/tests/test_controller.py +323 -0
- django_spire/auth/tests/test_url_endpoints.py +9 -9
- django_spire/auth/tests/test_views.py +406 -0
- django_spire/auth/urls/admin_urls.py +2 -0
- django_spire/auth/urls/redirect_urls.py +2 -0
- django_spire/auth/user/apps.py +2 -0
- django_spire/auth/user/forms.py +9 -0
- django_spire/auth/user/models.py +1 -1
- django_spire/auth/user/services/services.py +1 -0
- django_spire/auth/user/tests/factories.py +14 -13
- django_spire/auth/user/tests/test_factories.py +166 -2
- django_spire/auth/user/tests/test_forms.py +573 -0
- django_spire/auth/user/tests/test_models.py +257 -0
- django_spire/auth/user/tests/test_services.py +200 -0
- django_spire/auth/user/tests/test_tools.py +153 -0
- django_spire/auth/user/tests/test_user_factories.py +139 -0
- django_spire/auth/user/tests/test_views.py +363 -0
- django_spire/auth/user/tools.py +7 -1
- django_spire/auth/user/urls/form_urls.py +3 -0
- django_spire/auth/user/urls/page_urls.py +3 -0
- django_spire/auth/user/views/form_views.py +19 -10
- django_spire/auth/user/views/page_views.py +8 -2
- django_spire/auth/views/redirect_views.py +14 -9
- django_spire/comment/admin.py +2 -0
- django_spire/comment/apps.py +2 -0
- django_spire/comment/templatetags/comment_tags.py +1 -0
- django_spire/comment/tests/test_forms.py +27 -0
- django_spire/comment/tests/test_models.py +215 -0
- django_spire/comment/tests/test_querysets.py +101 -0
- django_spire/comment/tests/test_utils.py +90 -0
- django_spire/comment/urls.py +2 -0
- django_spire/comment/utils.py +22 -13
- django_spire/comment/views.py +1 -1
- django_spire/conf.py +8 -6
- django_spire/consts.py +1 -1
- django_spire/contrib/breadcrumb/apps.py +2 -0
- django_spire/contrib/breadcrumb/breadcrumbs.py +18 -18
- django_spire/contrib/breadcrumb/tests/test_breadcrumbs.py +198 -0
- django_spire/contrib/constructor/__init__.py +3 -3
- django_spire/contrib/constructor/constructor.py +15 -15
- django_spire/contrib/constructor/django_model_constructor.py +5 -4
- django_spire/contrib/constructor/exceptions.py +5 -3
- django_spire/contrib/constructor/tests/__init__.py +0 -0
- django_spire/contrib/constructor/tests/test_constructor.py +193 -0
- django_spire/contrib/form/tests/__init__.py +0 -0
- django_spire/contrib/form/tests/test_forms.py +203 -0
- django_spire/contrib/generic_views/modal_views.py +2 -1
- django_spire/contrib/generic_views/portal_views.py +20 -19
- django_spire/contrib/generic_views/tests/__init__.py +0 -0
- django_spire/contrib/generic_views/tests/test_views.py +459 -0
- django_spire/contrib/help/apps.py +2 -0
- django_spire/contrib/help/templatetags/help.py +1 -0
- django_spire/contrib/help/tests/__init__.py +0 -0
- django_spire/contrib/help/tests/test_templatetags.py +100 -0
- django_spire/contrib/options/mixins.py +6 -5
- django_spire/contrib/options/tests/factories.py +5 -1
- django_spire/contrib/options/tests/test_options.py +234 -0
- django_spire/contrib/ordering/exceptions.py +7 -3
- django_spire/contrib/ordering/mixins.py +2 -0
- django_spire/contrib/ordering/querysets.py +3 -1
- django_spire/contrib/ordering/services/processor_service.py +8 -4
- django_spire/contrib/ordering/services/service.py +1 -2
- django_spire/contrib/ordering/tests/__init__.py +0 -0
- django_spire/contrib/ordering/tests/test_ordering.py +165 -0
- django_spire/contrib/ordering/validators.py +6 -6
- django_spire/contrib/pagination/templatetags/pagination_tags.py +12 -5
- django_spire/contrib/pagination/tests/__init__.py +0 -0
- django_spire/contrib/pagination/tests/test_pagination.py +179 -0
- django_spire/contrib/performance/decorators.py +16 -6
- django_spire/contrib/performance/tests/__init__.py +0 -0
- django_spire/contrib/performance/tests/test_performance.py +107 -0
- django_spire/contrib/progress/__init__.py +1 -3
- django_spire/contrib/progress/static/django_spire/js/contrib/progress/progress.js +38 -82
- django_spire/contrib/queryset/enums.py +3 -1
- django_spire/contrib/queryset/filter_tools.py +10 -5
- django_spire/contrib/queryset/mixins.py +16 -16
- django_spire/contrib/queryset/tests/__init__.py +0 -0
- django_spire/contrib/queryset/tests/test_queryset.py +137 -0
- django_spire/contrib/seeding/field/base.py +13 -7
- django_spire/contrib/seeding/field/callable.py +8 -1
- django_spire/contrib/seeding/field/cleaners.py +5 -5
- django_spire/contrib/seeding/field/custom.py +20 -10
- django_spire/contrib/seeding/field/django/seeder.py +8 -6
- django_spire/contrib/seeding/field/enums.py +7 -5
- django_spire/contrib/seeding/field/override.py +16 -6
- django_spire/contrib/seeding/field/static.py +9 -2
- django_spire/contrib/seeding/field/tests/test_base.py +18 -14
- django_spire/contrib/seeding/field/tests/test_callable.py +13 -9
- django_spire/contrib/seeding/field/tests/test_cleaners.py +51 -38
- django_spire/contrib/seeding/field/tests/test_static.py +13 -9
- django_spire/contrib/seeding/intelligence/bots/seeder_generator_bot.py +2 -0
- django_spire/contrib/seeding/intelligence/intel.py +5 -1
- django_spire/contrib/seeding/intelligence/prompts/factory.py +6 -1
- django_spire/contrib/seeding/intelligence/prompts/foreign_key_selection_prompt.py +6 -1
- django_spire/contrib/seeding/intelligence/prompts/generate_django_model_seeder_prompts.py +2 -0
- django_spire/contrib/seeding/intelligence/prompts/generic_relationship_selection_prompt.py +7 -1
- django_spire/contrib/seeding/intelligence/prompts/hierarchical_selection_prompt.py +6 -2
- django_spire/contrib/seeding/intelligence/prompts/model_field_choices_prompt.py +8 -2
- django_spire/contrib/seeding/intelligence/prompts/negation_prompt.py +2 -0
- django_spire/contrib/seeding/intelligence/prompts/objective_prompt.py +6 -1
- django_spire/contrib/seeding/management/commands/seeding.py +9 -3
- django_spire/contrib/seeding/management/example.py +2 -0
- django_spire/contrib/seeding/model/base.py +16 -7
- django_spire/contrib/seeding/model/config.py +31 -15
- django_spire/contrib/seeding/model/django/config.py +13 -13
- django_spire/contrib/seeding/model/django/seeder.py +4 -4
- django_spire/contrib/seeding/model/django/tests/test_seeder.py +34 -23
- django_spire/contrib/seeding/model/enums.py +2 -0
- django_spire/contrib/seeding/tests/test_config.py +71 -0
- django_spire/contrib/seeding/tests/test_custom.py +35 -0
- django_spire/contrib/seeding/tests/test_enums.py +40 -0
- django_spire/contrib/seeding/tests/test_intel.py +32 -0
- django_spire/contrib/seeding/tests/test_override.py +63 -0
- django_spire/contrib/service/__init__.py +2 -2
- django_spire/contrib/service/django_model_service.py +16 -15
- django_spire/contrib/service/exceptions.py +5 -3
- django_spire/contrib/service/tests/__init__.py +0 -0
- django_spire/contrib/service/tests/test_service.py +153 -0
- django_spire/contrib/session/apps.py +2 -0
- django_spire/contrib/session/controller.py +48 -42
- django_spire/contrib/session/templatetags/session_tags.py +11 -2
- django_spire/contrib/session/tests/test_session_controller.py +117 -53
- django_spire/contrib/tests/__init__.py +0 -0
- django_spire/contrib/tests/test_utils.py +37 -0
- django_spire/contrib/utils.py +4 -1
- django_spire/core/apps.py +2 -0
- django_spire/core/converters/tests/test_to_data.py +353 -0
- django_spire/core/converters/tests/test_to_enums.py +61 -41
- django_spire/core/converters/tests/test_to_pydantic.py +138 -109
- django_spire/core/converters/to_data.py +29 -10
- django_spire/core/converters/to_enums.py +4 -2
- django_spire/core/converters/to_pydantic.py +22 -22
- django_spire/core/decorators.py +19 -6
- django_spire/core/forms/widgets.py +4 -0
- django_spire/core/maps.py +3 -1
- django_spire/core/middleware/maintenance.py +3 -3
- django_spire/core/middleware.py +8 -6
- django_spire/core/redirect/__init__.py +5 -0
- django_spire/core/redirect/generic_redirect.py +1 -2
- django_spire/core/redirect/tests/__init__.py +0 -0
- django_spire/core/redirect/tests/test_generic_redirect.py +34 -0
- django_spire/core/{tests/tests_redirect.py → redirect/tests/test_safe_redirect.py} +55 -81
- django_spire/core/shortcuts.py +3 -3
- django_spire/core/static/django_spire/css/app-layout.css +1 -1
- django_spire/core/static/django_spire/css/app-navigation.css +3 -3
- django_spire/core/static/django_spire/css/bootstrap-override.css +4 -0
- django_spire/core/tag/admin.py +12 -0
- django_spire/core/tag/intelligence/tag_set_bot.py +2 -0
- django_spire/core/tag/mixins.py +2 -0
- django_spire/core/tag/models.py +2 -0
- django_spire/core/tag/querysets.py +2 -0
- django_spire/core/tag/service/tag_service.py +6 -3
- django_spire/core/tag/tests/test_intelligence.py +9 -9
- django_spire/core/tag/tests/test_tags.py +44 -54
- django_spire/core/tag/tests/test_tools.py +191 -0
- django_spire/core/tag/tools.py +3 -0
- django_spire/core/templates/django_spire/card/card.html +5 -2
- django_spire/core/templates/django_spire/card/title_card.html +9 -4
- django_spire/core/templates/django_spire/infinite_scroll/base.html +1 -0
- django_spire/core/templates/django_spire/navigation/side_navigation.html +19 -24
- django_spire/core/templates/django_spire/page/full_page.html +46 -16
- django_spire/core/templates/django_spire/table/base.html +4 -2
- django_spire/core/templatetags/json.py +6 -2
- django_spire/core/templatetags/message.py +13 -8
- django_spire/core/templatetags/string_formating.py +8 -5
- django_spire/core/templatetags/tests/__init__.py +0 -0
- django_spire/core/templatetags/tests/test_templatetags.py +427 -0
- django_spire/core/templatetags/variable_types.py +17 -9
- django_spire/core/tests/test_cases.py +1 -1
- django_spire/core/tests/test_conf.py +43 -0
- django_spire/core/tests/test_consts.py +28 -0
- django_spire/core/tests/test_context_processors.py +93 -0
- django_spire/core/tests/test_decorators.py +95 -0
- django_spire/core/tests/test_django_spire_utils.py +56 -0
- django_spire/core/tests/test_exceptions.py +37 -0
- django_spire/core/tests/test_models.py +54 -0
- django_spire/core/tests/test_settings.py +45 -0
- django_spire/core/tests/test_shortcuts.py +74 -0
- django_spire/core/tests/test_urls.py +16 -0
- django_spire/core/tests/test_utils.py +58 -0
- django_spire/core/urls.py +4 -1
- django_spire/core/utils.py +12 -8
- django_spire/exceptions.py +16 -1
- django_spire/file/admin.py +4 -2
- django_spire/file/apps.py +8 -10
- django_spire/file/fields.py +7 -7
- django_spire/file/forms.py +1 -1
- django_spire/file/interfaces.py +15 -15
- django_spire/file/mixins.py +1 -4
- django_spire/file/models.py +3 -5
- django_spire/file/tests/factories.py +59 -0
- django_spire/file/tests/test_admin.py +69 -0
- django_spire/file/tests/test_apps.py +24 -0
- django_spire/file/tests/test_fields.py +114 -0
- django_spire/file/tests/test_forms.py +20 -0
- django_spire/file/tests/test_interfaces.py +183 -0
- django_spire/file/tests/test_models.py +82 -0
- django_spire/file/tests/test_querysets.py +102 -0
- django_spire/file/tests/test_utils.py +32 -0
- django_spire/file/tests/test_views.py +145 -0
- django_spire/file/tests/test_widgets.py +82 -0
- django_spire/file/tools.py +8 -2
- django_spire/file/views.py +7 -3
- django_spire/file/widgets.py +12 -12
- django_spire/help_desk/admin.py +15 -0
- django_spire/help_desk/apps.py +2 -0
- django_spire/help_desk/auth/controller.py +2 -0
- django_spire/help_desk/choices.py +2 -0
- django_spire/help_desk/enums.py +2 -0
- django_spire/help_desk/exceptions.py +31 -3
- django_spire/help_desk/forms.py +2 -0
- django_spire/help_desk/models.py +2 -0
- django_spire/help_desk/querysets.py +4 -1
- django_spire/help_desk/services/notification_service.py +26 -27
- django_spire/help_desk/services/service.py +2 -3
- django_spire/help_desk/tests/factories.py +8 -3
- django_spire/help_desk/tests/test_admin.py +41 -0
- django_spire/help_desk/tests/test_apps.py +41 -0
- django_spire/help_desk/tests/test_choices.py +50 -0
- django_spire/help_desk/tests/test_controller.py +87 -0
- django_spire/help_desk/tests/test_enums.py +18 -0
- django_spire/help_desk/tests/test_exceptions.py +37 -0
- django_spire/help_desk/tests/test_forms.py +89 -0
- django_spire/help_desk/tests/test_models.py +59 -0
- django_spire/help_desk/tests/test_querysets.py +38 -0
- django_spire/help_desk/tests/test_services/test_notification_service.py +15 -8
- django_spire/help_desk/tests/test_services/test_service.py +92 -0
- django_spire/help_desk/tests/test_urls/test_form_urls.py +6 -6
- django_spire/help_desk/tests/test_urls/test_page_urls.py +8 -9
- django_spire/help_desk/tests/test_views/test_form_views.py +46 -19
- django_spire/help_desk/tests/test_views/test_page_views.py +32 -9
- django_spire/help_desk/urls/__init__.py +4 -1
- django_spire/help_desk/urls/form_urls.py +3 -0
- django_spire/help_desk/urls/page_urls.py +3 -0
- django_spire/help_desk/views/form_views.py +13 -5
- django_spire/help_desk/views/page_views.py +11 -3
- django_spire/history/activity/admin.py +2 -0
- django_spire/history/activity/apps.py +3 -1
- django_spire/history/activity/mixins.py +13 -7
- django_spire/history/activity/models.py +6 -5
- django_spire/history/activity/querysets.py +2 -0
- django_spire/history/activity/tests/__init__.py +0 -0
- django_spire/history/activity/tests/test_activity.py +176 -0
- django_spire/history/admin.py +9 -2
- django_spire/history/choices.py +3 -0
- django_spire/history/models.py +5 -5
- django_spire/history/tests/test_admin.py +93 -0
- django_spire/history/tests/test_history.py +101 -0
- django_spire/history/tests/test_mixins.py +84 -0
- django_spire/history/viewed/admin.py +3 -1
- django_spire/history/viewed/apps.py +3 -1
- django_spire/history/viewed/models.py +2 -0
- django_spire/history/viewed/tests/__init__.py +0 -0
- django_spire/history/viewed/tests/test_viewed.py +46 -0
- django_spire/knowledge/auth/tests/__init__.py +0 -0
- django_spire/knowledge/auth/tests/test_controller.py +116 -0
- django_spire/knowledge/collection/admin.py +5 -1
- django_spire/knowledge/collection/models.py +3 -1
- django_spire/knowledge/collection/seeding/seed.py +1 -0
- django_spire/knowledge/collection/services/factory_service.py +10 -11
- django_spire/knowledge/collection/services/ordering_service.py +1 -2
- django_spire/knowledge/collection/services/service.py +5 -10
- django_spire/knowledge/collection/services/tag_service.py +5 -2
- django_spire/knowledge/collection/tests/factories.py +28 -1
- django_spire/knowledge/collection/tests/test_models.py +48 -0
- django_spire/knowledge/collection/tests/test_querysets.py +93 -0
- django_spire/knowledge/collection/tests/test_services/test_factory_service.py +100 -0
- django_spire/knowledge/collection/tests/test_services/test_services.py +160 -0
- django_spire/knowledge/collection/tests/test_urls/test_form_urls.py +21 -3
- django_spire/knowledge/collection/tests/test_urls/test_json_urls.py +39 -1
- django_spire/knowledge/collection/tests/test_urls/test_page_urls.py +12 -4
- django_spire/knowledge/collection/urls/__init__.py +3 -0
- django_spire/knowledge/collection/urls/form_urls.py +2 -0
- django_spire/knowledge/collection/urls/json_urls.py +2 -0
- django_spire/knowledge/collection/urls/page_urls.py +2 -0
- django_spire/knowledge/collection/views/form_views.py +4 -4
- django_spire/knowledge/collection/views/json_views.py +5 -1
- django_spire/knowledge/collection/views/page_views.py +5 -2
- django_spire/knowledge/entry/admin.py +7 -1
- django_spire/knowledge/entry/forms.py +2 -0
- django_spire/knowledge/entry/models.py +2 -0
- django_spire/knowledge/entry/seeding/seed.py +3 -0
- django_spire/knowledge/entry/services/automation_service.py +5 -4
- django_spire/knowledge/entry/services/factory_service.py +7 -5
- django_spire/knowledge/entry/services/service.py +4 -7
- django_spire/knowledge/entry/services/tag_service.py +0 -1
- django_spire/knowledge/entry/services/tool_service.py +1 -0
- django_spire/knowledge/entry/services/transformation_services.py +1 -5
- django_spire/knowledge/entry/tests/factories.py +1 -2
- django_spire/knowledge/entry/tests/test_factory_service.py +20 -0
- django_spire/knowledge/entry/tests/test_models.py +41 -0
- django_spire/knowledge/entry/tests/test_querysets.py +71 -0
- django_spire/knowledge/entry/tests/test_services.py +94 -0
- django_spire/knowledge/entry/tests/test_urls/test_form_urls.py +9 -14
- django_spire/knowledge/entry/tests/test_urls/test_json_urls.py +48 -5
- django_spire/knowledge/entry/tests/test_urls/test_page_urls.py +6 -8
- django_spire/knowledge/entry/tests/test_urls/test_template_urls.py +40 -0
- django_spire/knowledge/entry/urls/form_urls.py +2 -0
- django_spire/knowledge/entry/urls/json_urls.py +2 -0
- django_spire/knowledge/entry/urls/page_urls.py +2 -0
- django_spire/knowledge/entry/urls/template_urls.py +2 -0
- django_spire/knowledge/entry/version/block/choices.py +2 -0
- django_spire/knowledge/entry/version/block/data/data.py +1 -0
- django_spire/knowledge/entry/version/block/data/list/data.py +8 -13
- django_spire/knowledge/entry/version/block/data/list/maps.py +3 -0
- django_spire/knowledge/entry/version/block/data/list/meta.py +1 -2
- django_spire/knowledge/entry/version/block/data/list/tests/__init__.py +0 -0
- django_spire/knowledge/entry/version/block/data/list/tests/test_maps.py +32 -0
- django_spire/knowledge/entry/version/block/data/list/tests/test_meta.py +58 -0
- django_spire/knowledge/entry/version/block/data/maps.py +3 -6
- django_spire/knowledge/entry/version/block/models.py +7 -5
- django_spire/knowledge/entry/version/block/seeding/constants.py +5 -4
- django_spire/knowledge/entry/version/block/services/service.py +2 -3
- django_spire/knowledge/entry/version/block/tests/factories.py +4 -10
- django_spire/knowledge/entry/version/block/tests/test_choices.py +56 -0
- django_spire/knowledge/entry/version/block/tests/test_data.py +90 -0
- django_spire/knowledge/entry/version/block/tests/test_maps.py +37 -0
- django_spire/knowledge/entry/version/block/tests/test_models.py +55 -0
- django_spire/knowledge/entry/version/block/tests/test_querysets.py +35 -0
- django_spire/knowledge/entry/version/block/tests/test_services.py +65 -0
- django_spire/knowledge/entry/version/choices.py +2 -0
- django_spire/knowledge/entry/version/converters/converter.py +1 -1
- django_spire/knowledge/entry/version/converters/docx_converter.py +4 -7
- django_spire/knowledge/entry/version/converters/markdown_converter.py +20 -20
- django_spire/knowledge/entry/version/maps.py +4 -5
- django_spire/knowledge/entry/version/querysets.py +1 -1
- django_spire/knowledge/entry/version/seeding/seeder.py +1 -2
- django_spire/knowledge/entry/version/services/processor_service.py +5 -4
- django_spire/knowledge/entry/version/services/service.py +1 -2
- django_spire/knowledge/entry/version/tests/factories.py +2 -2
- django_spire/knowledge/entry/version/tests/test_choices.py +18 -0
- django_spire/knowledge/entry/version/tests/test_converters/test_docx_converter.py +56 -8
- django_spire/knowledge/entry/version/tests/test_converters/test_markdown_converter.py +78 -0
- django_spire/knowledge/entry/version/tests/test_maps.py +58 -0
- django_spire/knowledge/entry/version/tests/test_models.py +23 -0
- django_spire/knowledge/entry/version/tests/test_querysets.py +26 -0
- django_spire/knowledge/entry/version/tests/test_services.py +62 -0
- django_spire/knowledge/entry/version/tests/test_urls/test_json_urls.py +27 -8
- django_spire/knowledge/entry/version/tests/test_urls/test_page_urls.py +15 -8
- django_spire/knowledge/entry/version/tests/test_urls/test_redirect_urls.py +38 -0
- django_spire/knowledge/entry/version/urls/__init__.py +3 -0
- django_spire/knowledge/entry/version/urls/json_urls.py +2 -1
- django_spire/knowledge/entry/version/urls/page_urls.py +2 -0
- django_spire/knowledge/entry/version/urls/redirect_urls.py +2 -0
- django_spire/knowledge/entry/version/views/json_views.py +5 -1
- django_spire/knowledge/entry/version/views/page_views.py +10 -3
- django_spire/knowledge/entry/version/views/redirect_views.py +5 -1
- django_spire/knowledge/entry/views/form_views.py +16 -8
- django_spire/knowledge/entry/views/json_views.py +3 -1
- django_spire/knowledge/entry/views/page_views.py +8 -2
- django_spire/knowledge/entry/views/template_views.py +7 -1
- django_spire/knowledge/exceptions.py +2 -1
- django_spire/knowledge/intelligence/intel/answer_intel.py +2 -1
- django_spire/knowledge/intelligence/intel/entry_intel.py +0 -1
- django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +4 -5
- django_spire/knowledge/models.py +1 -2
- django_spire/knowledge/tests/__init__.py +0 -0
- django_spire/knowledge/tests/test_templatetags.py +40 -0
- django_spire/knowledge/tests/test_urls/__init__.py +0 -0
- django_spire/knowledge/tests/test_urls/test_page_urls.py +24 -0
- django_spire/knowledge/urls/__init__.py +2 -0
- django_spire/knowledge/urls/page_urls.py +2 -0
- django_spire/knowledge/views/page_views.py +8 -3
- django_spire/notification/admin.py +3 -1
- django_spire/notification/app/admin.py +2 -0
- django_spire/notification/app/apps.py +3 -1
- django_spire/notification/app/exceptions.py +9 -2
- django_spire/notification/app/models.py +8 -4
- django_spire/notification/app/processor.py +22 -26
- django_spire/notification/app/querysets.py +2 -0
- django_spire/notification/app/tests/__init__.py +0 -0
- django_spire/notification/app/tests/factories.py +34 -0
- django_spire/notification/app/tests/test_apps.py +24 -0
- django_spire/notification/app/tests/test_models.py +72 -0
- django_spire/notification/app/tests/test_processor.py +111 -0
- django_spire/notification/app/tests/test_querysets.py +90 -0
- django_spire/notification/app/tests/test_views/__init__.py +0 -0
- django_spire/notification/app/tests/test_views/test_json_views.py +48 -0
- django_spire/notification/app/tests/test_views/test_page_views.py +19 -0
- django_spire/notification/app/urls/__init__.py +3 -1
- django_spire/notification/app/urls/json_urls.py +6 -4
- django_spire/notification/app/urls/page_urls.py +4 -3
- django_spire/notification/app/urls/template_urls.py +4 -2
- django_spire/notification/apps.py +4 -1
- django_spire/notification/email/admin.py +5 -1
- django_spire/notification/email/apps.py +3 -1
- django_spire/notification/email/exceptions.py +4 -2
- django_spire/notification/email/helper.py +5 -3
- django_spire/notification/email/models.py +4 -0
- django_spire/notification/email/processor.py +19 -15
- django_spire/notification/email/querysets.py +3 -0
- django_spire/notification/email/tests/__init__.py +0 -0
- django_spire/notification/email/tests/factories.py +35 -0
- django_spire/notification/email/tests/test_apps.py +24 -0
- django_spire/notification/email/tests/test_models.py +52 -0
- django_spire/notification/email/tests/test_processor.py +92 -0
- django_spire/notification/email/tests/test_querysets.py +43 -0
- django_spire/notification/exceptions.py +17 -2
- django_spire/notification/managers.py +7 -1
- django_spire/notification/maps.py +4 -1
- django_spire/notification/mixins.py +2 -0
- django_spire/notification/models.py +3 -1
- django_spire/notification/processors/notification.py +12 -5
- django_spire/notification/processors/processor.py +2 -0
- django_spire/notification/processors/tests/__init__.py +0 -0
- django_spire/notification/processors/tests/test_notification.py +106 -0
- django_spire/notification/push/admin.py +10 -1
- django_spire/notification/push/apps.py +3 -1
- django_spire/notification/push/models.py +2 -3
- django_spire/notification/push/tests/__init__.py +0 -0
- django_spire/notification/push/tests/test_apps.py +24 -0
- django_spire/notification/push/tests/test_models.py +28 -0
- django_spire/notification/querysets.py +7 -1
- django_spire/notification/sms/admin.py +2 -0
- django_spire/notification/sms/apps.py +4 -1
- django_spire/notification/sms/automations.py +2 -0
- django_spire/notification/sms/choices.py +2 -0
- django_spire/notification/sms/exceptions.py +19 -5
- django_spire/notification/sms/helper.py +33 -23
- django_spire/notification/sms/models.py +5 -1
- django_spire/notification/sms/processor.py +20 -20
- django_spire/notification/sms/querysets.py +2 -0
- django_spire/notification/sms/tests/factories.py +33 -0
- django_spire/notification/sms/tests/test_apps.py +24 -0
- django_spire/notification/sms/tests/test_automation.py +38 -0
- django_spire/notification/sms/tests/test_choices.py +15 -0
- django_spire/notification/sms/tests/test_consts.py +17 -0
- django_spire/notification/sms/tests/test_exceptions.py +27 -0
- django_spire/notification/sms/tests/test_helper.py +50 -0
- django_spire/notification/sms/tests/test_models.py +81 -0
- django_spire/notification/sms/tests/test_processor.py +107 -0
- django_spire/notification/sms/tests/test_tools.py +25 -11
- django_spire/notification/sms/tools.py +16 -5
- django_spire/notification/sms/urls/__init__.py +3 -1
- django_spire/notification/sms/urls/media_urls.py +2 -0
- django_spire/notification/sms/views/media_views.py +14 -4
- django_spire/notification/tests/__init__.py +0 -0
- django_spire/notification/tests/factories.py +26 -0
- django_spire/notification/tests/test_admin.py +55 -0
- django_spire/notification/tests/test_apps.py +30 -0
- django_spire/notification/tests/test_automation.py +18 -0
- django_spire/notification/tests/test_choices.py +59 -0
- django_spire/notification/tests/test_exceptions.py +58 -0
- django_spire/notification/tests/test_managers.py +100 -0
- django_spire/notification/tests/test_maps.py +31 -0
- django_spire/notification/tests/test_models.py +76 -0
- django_spire/notification/tests/test_querysets.py +184 -0
- django_spire/notification/tests/test_utils.py +23 -0
- django_spire/notification/urls.py +3 -1
- django_spire/notification/utils.py +3 -1
- django_spire/settings.py +3 -0
- django_spire/theme/tests/test_context_processor.py +15 -13
- django_spire/theme/tests/test_enums.py +2 -2
- django_spire/theme/tests/test_filesystem.py +2 -5
- django_spire/theme/tests/test_integration.py +12 -12
- django_spire/theme/tests/test_model.py +40 -38
- django_spire/theme/tests/test_views/test_json_views.py +33 -33
- django_spire/theme/urls/json_urls.py +3 -0
- django_spire/theme/urls/page_urls.py +3 -0
- django_spire/urls.py +19 -15
- django_spire/utils.py +13 -4
- {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/METADATA +1 -1
- {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/RECORD +532 -361
- django_spire/contrib/options/tests/test_unit.py +0 -148
- django_spire/contrib/progress/views.py +0 -64
- django_spire/contrib/seeding/tests/test_seeding.py +0 -25
- django_spire/core/tests/test_templatetags.py +0 -117
- django_spire/core/tests/tests_shortcuts.py +0 -73
- django_spire/history/activity/tests.py +0 -3
- django_spire/history/activity/views.py +0 -3
- django_spire/knowledge/collection/tests/test_services/test_transformation_service.py +0 -71
- django_spire/notification/app/tests.py +0 -3
- {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/WHEEL +0 -0
- {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/licenses/LICENSE.md +0 -0
- {django_spire-0.23.6.dist-info → django_spire-0.23.8.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()
|
|
@@ -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
|
|
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
|
-
|
|
59
|
-
|
|
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
|
-
|
|
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
|
|
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[
|
|
24
|
+
self._errors: list[OrderingMixinError] = []
|
|
25
25
|
|
|
26
26
|
@property
|
|
27
|
-
def errors(self) -> list[
|
|
28
|
-
"""Returns list of
|
|
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(
|
|
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(
|
|
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
|