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,93 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from unittest.mock import MagicMock, patch
|
|
4
|
+
|
|
5
|
+
from django.test import RequestFactory, TestCase, override_settings
|
|
6
|
+
|
|
7
|
+
from django_spire.core.context_processors import django_spire, theme_context
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestDjangoSpireContextProcessor(TestCase):
|
|
11
|
+
def setUp(self) -> None:
|
|
12
|
+
super().setUp()
|
|
13
|
+
|
|
14
|
+
self.factory = RequestFactory()
|
|
15
|
+
|
|
16
|
+
@override_settings(DJANGO_SPIRE_AUTH_CONTROLLERS=[])
|
|
17
|
+
def test_empty_auth_controllers(self) -> None:
|
|
18
|
+
request = self.factory.get('/')
|
|
19
|
+
result = django_spire(request)
|
|
20
|
+
|
|
21
|
+
assert 'DJANGO_SPIRE_VERSION' in result
|
|
22
|
+
assert 'AuthController' in result
|
|
23
|
+
assert result['AuthController'] == {}
|
|
24
|
+
|
|
25
|
+
@override_settings(DJANGO_SPIRE_AUTH_CONTROLLERS=['test_app'])
|
|
26
|
+
@patch('django_spire.core.context_processors.AppAuthController')
|
|
27
|
+
def test_with_auth_controllers(self, mock_controller: MagicMock) -> None:
|
|
28
|
+
mock_instance = MagicMock()
|
|
29
|
+
mock_controller.return_value = mock_instance
|
|
30
|
+
|
|
31
|
+
request = self.factory.get('/')
|
|
32
|
+
result = django_spire(request)
|
|
33
|
+
|
|
34
|
+
assert 'AuthController' in result
|
|
35
|
+
assert 'test_app' in result['AuthController']
|
|
36
|
+
mock_controller.assert_called_once_with('test_app', request=request)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class TestThemeContextProcessor(TestCase):
|
|
40
|
+
def setUp(self) -> None:
|
|
41
|
+
super().setUp()
|
|
42
|
+
|
|
43
|
+
self.factory = RequestFactory()
|
|
44
|
+
|
|
45
|
+
def test_default_theme_applied(self) -> None:
|
|
46
|
+
request = self.factory.get('/')
|
|
47
|
+
request.COOKIES = {}
|
|
48
|
+
result = theme_context(request)
|
|
49
|
+
|
|
50
|
+
assert 'DJANGO_SPIRE_DEFAULT_THEME' in result
|
|
51
|
+
assert 'DJANGO_SPIRE_THEME_COOKIE_NAME' in result
|
|
52
|
+
assert 'DJANGO_SPIRE_THEME_PATH' in result
|
|
53
|
+
assert 'theme' in result
|
|
54
|
+
|
|
55
|
+
@override_settings(DJANGO_SPIRE_DEFAULT_THEME='default-dark')
|
|
56
|
+
def test_custom_default_theme(self) -> None:
|
|
57
|
+
request = self.factory.get('/')
|
|
58
|
+
request.COOKIES = {}
|
|
59
|
+
result = theme_context(request)
|
|
60
|
+
|
|
61
|
+
assert result['DJANGO_SPIRE_DEFAULT_THEME'] == 'default-dark'
|
|
62
|
+
|
|
63
|
+
@override_settings(DJANGO_SPIRE_THEME_PATH='/custom/path/{family}/app-{mode}.css')
|
|
64
|
+
def test_custom_theme_path(self) -> None:
|
|
65
|
+
request = self.factory.get('/')
|
|
66
|
+
request.COOKIES = {}
|
|
67
|
+
result = theme_context(request)
|
|
68
|
+
|
|
69
|
+
assert result['DJANGO_SPIRE_THEME_PATH'] == '/custom/path/{family}/app-{mode}.css'
|
|
70
|
+
|
|
71
|
+
@patch('django_spire.core.context_processors.get_theme_cookie_name')
|
|
72
|
+
@patch('django_spire.core.context_processors.Theme')
|
|
73
|
+
def test_theme_from_cookie(self, mock_theme: MagicMock, mock_cookie_name: MagicMock) -> None:
|
|
74
|
+
mock_cookie_name.return_value = 'theme_cookie'
|
|
75
|
+
mock_theme_instance = MagicMock()
|
|
76
|
+
mock_theme_instance.value = 'custom-dark'
|
|
77
|
+
mock_theme_instance.to_dict.return_value = {'family': 'custom', 'mode': 'dark'}
|
|
78
|
+
mock_theme.from_string.return_value = mock_theme_instance
|
|
79
|
+
mock_theme.get_default.return_value = mock_theme_instance
|
|
80
|
+
|
|
81
|
+
request = self.factory.get('/')
|
|
82
|
+
request.COOKIES = {'theme_cookie': 'custom-dark'}
|
|
83
|
+
result = theme_context(request)
|
|
84
|
+
|
|
85
|
+
assert result['theme'] == {'family': 'custom', 'mode': 'dark'}
|
|
86
|
+
|
|
87
|
+
def test_theme_result_contains_required_keys(self) -> None:
|
|
88
|
+
request = self.factory.get('/')
|
|
89
|
+
request.COOKIES = {}
|
|
90
|
+
result = theme_context(request)
|
|
91
|
+
|
|
92
|
+
assert 'theme' in result
|
|
93
|
+
assert isinstance(result['theme'], dict)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from unittest.mock import MagicMock, patch
|
|
6
|
+
|
|
7
|
+
from django.http import HttpRequest, JsonResponse
|
|
8
|
+
from django.test import RequestFactory, TestCase
|
|
9
|
+
|
|
10
|
+
from django_spire.core.decorators import close_db_connections, valid_ajax_request_required
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestCloseDbConnections(TestCase):
|
|
14
|
+
@patch('django_spire.core.decorators.connections')
|
|
15
|
+
def test_closes_connections_after_exception(self, mock_connections: MagicMock) -> None:
|
|
16
|
+
@close_db_connections
|
|
17
|
+
def sample_func():
|
|
18
|
+
raise ValueError
|
|
19
|
+
|
|
20
|
+
with pytest.raises(ValueError):
|
|
21
|
+
sample_func()
|
|
22
|
+
|
|
23
|
+
mock_connections.close_all.assert_called_once()
|
|
24
|
+
|
|
25
|
+
@patch('django_spire.core.decorators.connections')
|
|
26
|
+
def test_closes_connections_after_success(self, mock_connections: MagicMock) -> None:
|
|
27
|
+
@close_db_connections
|
|
28
|
+
def sample_func():
|
|
29
|
+
return 'result'
|
|
30
|
+
|
|
31
|
+
result = sample_func()
|
|
32
|
+
|
|
33
|
+
assert result == 'result'
|
|
34
|
+
mock_connections.close_all.assert_called_once()
|
|
35
|
+
|
|
36
|
+
@patch('django_spire.core.decorators.connections')
|
|
37
|
+
def test_preserves_function_metadata(self, _mock_connections: MagicMock) -> None:
|
|
38
|
+
@close_db_connections
|
|
39
|
+
def sample_func():
|
|
40
|
+
"""Sample docstring"""
|
|
41
|
+
|
|
42
|
+
assert sample_func.__name__ == 'sample_func'
|
|
43
|
+
assert sample_func.__doc__ == 'Sample docstring'
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class TestValidAjaxRequestRequired(TestCase):
|
|
47
|
+
def setUp(self) -> None:
|
|
48
|
+
super().setUp()
|
|
49
|
+
|
|
50
|
+
self.factory = RequestFactory()
|
|
51
|
+
|
|
52
|
+
def test_get_request_returns_error(self) -> None:
|
|
53
|
+
@valid_ajax_request_required
|
|
54
|
+
def sample_view(_request: HttpRequest) -> dict:
|
|
55
|
+
return {'success': True}
|
|
56
|
+
|
|
57
|
+
request = self.factory.get('/test/')
|
|
58
|
+
response = sample_view(request)
|
|
59
|
+
|
|
60
|
+
assert response.status_code == 200
|
|
61
|
+
assert b'error' in response.content
|
|
62
|
+
|
|
63
|
+
def test_post_request_with_json_content_type_succeeds(self) -> None:
|
|
64
|
+
@valid_ajax_request_required
|
|
65
|
+
def sample_view(_request: HttpRequest) -> JsonResponse:
|
|
66
|
+
return JsonResponse({'success': True})
|
|
67
|
+
|
|
68
|
+
request = self.factory.post(
|
|
69
|
+
'/test/',
|
|
70
|
+
data='{}',
|
|
71
|
+
content_type='application/json'
|
|
72
|
+
)
|
|
73
|
+
response = sample_view(request)
|
|
74
|
+
|
|
75
|
+
assert response.status_code == 200
|
|
76
|
+
assert b'success' in response.content
|
|
77
|
+
|
|
78
|
+
def test_post_request_without_json_content_type_passes(self) -> None:
|
|
79
|
+
@valid_ajax_request_required
|
|
80
|
+
def sample_view(_request: HttpRequest) -> JsonResponse:
|
|
81
|
+
return JsonResponse({'success': True})
|
|
82
|
+
|
|
83
|
+
request = self.factory.post('/test/', data={})
|
|
84
|
+
response = sample_view(request)
|
|
85
|
+
|
|
86
|
+
assert response.status_code == 200
|
|
87
|
+
assert b'success' in response.content
|
|
88
|
+
|
|
89
|
+
def test_preserves_function_metadata(self) -> None:
|
|
90
|
+
@valid_ajax_request_required
|
|
91
|
+
def sample_view(_request: HttpRequest) -> None:
|
|
92
|
+
"""Sample docstring"""
|
|
93
|
+
|
|
94
|
+
assert sample_view.__name__ == 'sample_view'
|
|
95
|
+
assert sample_view.__doc__ == 'Sample docstring'
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from django.test import TestCase
|
|
6
|
+
|
|
7
|
+
from django_spire.utils import (
|
|
8
|
+
app_is_installed,
|
|
9
|
+
get_class_from_string,
|
|
10
|
+
get_class_name_from_class,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestAppIsInstalled(TestCase):
|
|
15
|
+
def test_installed_app_returns_true(self) -> None:
|
|
16
|
+
result = app_is_installed('auth')
|
|
17
|
+
|
|
18
|
+
assert result is True
|
|
19
|
+
|
|
20
|
+
def test_not_installed_app_returns_false(self) -> None:
|
|
21
|
+
result = app_is_installed('nonexistent_app')
|
|
22
|
+
|
|
23
|
+
assert result is False
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TestGetClassFromString(TestCase):
|
|
27
|
+
def test_imports_class(self) -> None:
|
|
28
|
+
result = get_class_from_string('django.test.TestCase')
|
|
29
|
+
|
|
30
|
+
assert result is TestCase
|
|
31
|
+
|
|
32
|
+
def test_invalid_class_string_raises_exception(self) -> None:
|
|
33
|
+
with pytest.raises(Exception, match='not a valid class string'):
|
|
34
|
+
get_class_from_string('InvalidString')
|
|
35
|
+
|
|
36
|
+
def test_nonexistent_module_raises_exception(self) -> None:
|
|
37
|
+
with pytest.raises(ModuleNotFoundError):
|
|
38
|
+
get_class_from_string('nonexistent.module.ClassName')
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class TestGetClassNameFromClass(TestCase):
|
|
42
|
+
def test_returns_full_class_path(self) -> None:
|
|
43
|
+
result = get_class_name_from_class(TestCase)
|
|
44
|
+
|
|
45
|
+
assert result == 'django.test.testcases.TestCase'
|
|
46
|
+
|
|
47
|
+
def test_returns_string(self) -> None:
|
|
48
|
+
result = get_class_name_from_class(TestCase)
|
|
49
|
+
|
|
50
|
+
assert isinstance(result, str)
|
|
51
|
+
|
|
52
|
+
def test_contains_module_and_class_name(self) -> None:
|
|
53
|
+
result = get_class_name_from_class(TestCase)
|
|
54
|
+
|
|
55
|
+
assert 'TestCase' in result
|
|
56
|
+
assert 'django' in result
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from django.test import TestCase
|
|
6
|
+
|
|
7
|
+
from django_spire.exceptions import DjangoSpireConfigurationError, DjangoSpireError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestDjangoSpireError(TestCase):
|
|
11
|
+
def test_can_be_raised(self) -> None:
|
|
12
|
+
with pytest.raises(DjangoSpireError):
|
|
13
|
+
message = 'Test error'
|
|
14
|
+
raise DjangoSpireError(message)
|
|
15
|
+
|
|
16
|
+
def test_is_exception_subclass(self) -> None:
|
|
17
|
+
assert issubclass(DjangoSpireError, Exception)
|
|
18
|
+
|
|
19
|
+
def test_message(self) -> None:
|
|
20
|
+
error = DjangoSpireError('Test error message')
|
|
21
|
+
|
|
22
|
+
assert str(error) == 'Test error message'
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TestDjangoSpireConfigurationError(TestCase):
|
|
26
|
+
def test_can_be_raised(self) -> None:
|
|
27
|
+
with pytest.raises(DjangoSpireConfigurationError):
|
|
28
|
+
message = 'Test error'
|
|
29
|
+
raise DjangoSpireConfigurationError(message)
|
|
30
|
+
|
|
31
|
+
def test_is_django_spire_error_subclass(self) -> None:
|
|
32
|
+
assert issubclass(DjangoSpireConfigurationError, DjangoSpireError)
|
|
33
|
+
|
|
34
|
+
def test_message(self) -> None:
|
|
35
|
+
error = DjangoSpireConfigurationError('Configuration error message')
|
|
36
|
+
|
|
37
|
+
assert str(error) == 'Configuration error message'
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django.db import connection, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DummyModel(models.Model):
|
|
7
|
+
name = models.CharField(max_length=255, default='')
|
|
8
|
+
|
|
9
|
+
class Meta:
|
|
10
|
+
app_label = 'django_spire_core'
|
|
11
|
+
db_table = 'django_spire_core_dummymodel'
|
|
12
|
+
|
|
13
|
+
def __str__(self) -> str:
|
|
14
|
+
return 'dummy'
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def create_table(cls) -> None:
|
|
18
|
+
vendor = connection.vendor
|
|
19
|
+
|
|
20
|
+
with connection.cursor() as cursor:
|
|
21
|
+
if vendor == 'sqlite':
|
|
22
|
+
query = '''
|
|
23
|
+
CREATE TABLE IF NOT EXISTS django_spire_core_dummymodel (
|
|
24
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
25
|
+
name VARCHAR(255) DEFAULT ''
|
|
26
|
+
)
|
|
27
|
+
'''
|
|
28
|
+
else:
|
|
29
|
+
query = '''
|
|
30
|
+
CREATE TABLE IF NOT EXISTS django_spire_core_dummymodel (
|
|
31
|
+
id SERIAL PRIMARY KEY,
|
|
32
|
+
name VARCHAR(255) DEFAULT ''
|
|
33
|
+
)
|
|
34
|
+
'''
|
|
35
|
+
|
|
36
|
+
cursor.execute(query)
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def drop_table(cls) -> None:
|
|
40
|
+
with connection.cursor() as cursor:
|
|
41
|
+
cursor.execute('DROP TABLE IF EXISTS django_spire_core_dummymodel')
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class DummyModelMixin:
|
|
45
|
+
@classmethod
|
|
46
|
+
def setUpClass(cls) -> None:
|
|
47
|
+
super().setUpClass()
|
|
48
|
+
|
|
49
|
+
DummyModel.create_table()
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def tearDownClass(cls) -> None:
|
|
53
|
+
DummyModel.drop_table()
|
|
54
|
+
super().tearDownClass()
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django.test import TestCase
|
|
4
|
+
|
|
5
|
+
from django_spire import settings
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestDefaultSettings(TestCase):
|
|
9
|
+
def test_ai_chat_routers_contains_spire(self) -> None:
|
|
10
|
+
assert 'SPIRE' in settings.DJANGO_SPIRE_AI_CHAT_ROUTERS
|
|
11
|
+
|
|
12
|
+
def test_ai_default_chat_router(self) -> None:
|
|
13
|
+
assert settings.DJANGO_SPIRE_AI_DEFAULT_CHAT_ROUTER == 'SPIRE'
|
|
14
|
+
|
|
15
|
+
def test_ai_intent_chat_routers_contains_knowledge_search(self) -> None:
|
|
16
|
+
assert 'KNOWLEDGE_SEARCH' in settings.DJANGO_SPIRE_AI_INTENT_CHAT_ROUTERS
|
|
17
|
+
|
|
18
|
+
def test_ai_intent_chat_routers_structure(self) -> None:
|
|
19
|
+
knowledge_search = settings.DJANGO_SPIRE_AI_INTENT_CHAT_ROUTERS['KNOWLEDGE_SEARCH']
|
|
20
|
+
|
|
21
|
+
assert 'INTENT_DESCRIPTION' in knowledge_search
|
|
22
|
+
assert 'REQUIRED_PERMISSION' in knowledge_search
|
|
23
|
+
assert 'CHAT_ROUTER' in knowledge_search
|
|
24
|
+
|
|
25
|
+
def test_ai_persona_name(self) -> None:
|
|
26
|
+
assert settings.DJANGO_SPIRE_AI_PERSONA_NAME == 'AI Assistant'
|
|
27
|
+
|
|
28
|
+
def test_auth_controllers_contains_ai_chat(self) -> None:
|
|
29
|
+
assert 'ai_chat' in settings.DJANGO_SPIRE_AUTH_CONTROLLERS
|
|
30
|
+
|
|
31
|
+
def test_auth_controllers_contains_help_desk(self) -> None:
|
|
32
|
+
assert 'help_desk' in settings.DJANGO_SPIRE_AUTH_CONTROLLERS
|
|
33
|
+
|
|
34
|
+
def test_auth_controllers_contains_knowledge(self) -> None:
|
|
35
|
+
assert 'knowledge' in settings.DJANGO_SPIRE_AUTH_CONTROLLERS
|
|
36
|
+
|
|
37
|
+
def test_auth_controllers_is_dict(self) -> None:
|
|
38
|
+
assert isinstance(settings.DJANGO_SPIRE_AUTH_CONTROLLERS, dict)
|
|
39
|
+
|
|
40
|
+
def test_default_theme(self) -> None:
|
|
41
|
+
assert settings.DJANGO_SPIRE_DEFAULT_THEME == 'default-light'
|
|
42
|
+
|
|
43
|
+
def test_theme_path_contains_placeholders(self) -> None:
|
|
44
|
+
assert '{family}' in settings.DJANGO_SPIRE_THEME_PATH
|
|
45
|
+
assert '{mode}' in settings.DJANGO_SPIRE_THEME_PATH
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from django.contrib.auth.models import User
|
|
6
|
+
from django.test import RequestFactory
|
|
7
|
+
|
|
8
|
+
from django_spire.core.shortcuts import (
|
|
9
|
+
get_object_or_none,
|
|
10
|
+
get_object_or_null_obj,
|
|
11
|
+
model_object_from_app_label,
|
|
12
|
+
process_request_body
|
|
13
|
+
)
|
|
14
|
+
from django_spire.core.tests.test_cases import BaseTestCase
|
|
15
|
+
from django_spire.core.tests.test_models import DummyModel, DummyModelMixin
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ShortcutsTestCase(DummyModelMixin, BaseTestCase):
|
|
19
|
+
def setUp(self) -> None:
|
|
20
|
+
super().setUp()
|
|
21
|
+
|
|
22
|
+
self.dummy = DummyModel.objects.create(name='test')
|
|
23
|
+
self.user = User.objects.create_user(username='testuser', password='testpass') # noqa: S106
|
|
24
|
+
self.request_factory = RequestFactory()
|
|
25
|
+
|
|
26
|
+
def test_get_object_or_none_found(self) -> None:
|
|
27
|
+
obj = get_object_or_none(DummyModel, pk=self.dummy.pk)
|
|
28
|
+
assert obj == self.dummy
|
|
29
|
+
|
|
30
|
+
def test_get_object_or_none_not_found(self) -> None:
|
|
31
|
+
obj = get_object_or_none(DummyModel, pk=9999)
|
|
32
|
+
assert obj is None
|
|
33
|
+
|
|
34
|
+
def test_get_object_or_null_obj_model_found(self) -> None:
|
|
35
|
+
obj = get_object_or_null_obj(DummyModel, name='test')
|
|
36
|
+
assert obj == self.dummy
|
|
37
|
+
|
|
38
|
+
def test_get_object_or_null_obj_model_not_found(self) -> None:
|
|
39
|
+
obj = get_object_or_null_obj(DummyModel, name='nonexistent')
|
|
40
|
+
assert obj.pk is None
|
|
41
|
+
assert obj.name == ''
|
|
42
|
+
|
|
43
|
+
def test_get_object_or_null_obj_queryset_found(self) -> None:
|
|
44
|
+
obj = get_object_or_null_obj(DummyModel.objects.all(), name='test')
|
|
45
|
+
assert obj == self.dummy
|
|
46
|
+
|
|
47
|
+
def test_get_object_or_null_obj_queryset_not_found(self) -> None:
|
|
48
|
+
obj = get_object_or_null_obj(DummyModel.objects.all(), name='nonexistent')
|
|
49
|
+
assert obj.pk is None
|
|
50
|
+
assert obj.name == ''
|
|
51
|
+
|
|
52
|
+
def test_model_object_from_app_label_contenttype_not_found(self) -> None:
|
|
53
|
+
obj = model_object_from_app_label('nonexistent', 'nonexistent', 1)
|
|
54
|
+
assert obj is None
|
|
55
|
+
|
|
56
|
+
def test_model_object_from_app_label_found(self) -> None:
|
|
57
|
+
obj = model_object_from_app_label('auth', 'user', self.user.pk)
|
|
58
|
+
assert obj == self.user
|
|
59
|
+
|
|
60
|
+
def test_model_object_from_app_label_not_found(self) -> None:
|
|
61
|
+
obj = model_object_from_app_label('auth', 'user', 9999)
|
|
62
|
+
assert obj is None
|
|
63
|
+
|
|
64
|
+
def test_process_request_body(self) -> None:
|
|
65
|
+
data = {'data': {'key': 'value'}}
|
|
66
|
+
|
|
67
|
+
request = self.request_factory.post(
|
|
68
|
+
'/dummy-url/',
|
|
69
|
+
data=json.dumps(data),
|
|
70
|
+
content_type='application/json'
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
result = process_request_body(request)
|
|
74
|
+
assert result == data['data']
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django.test import TestCase
|
|
4
|
+
|
|
5
|
+
from django_spire import urls
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestUrls(TestCase):
|
|
9
|
+
def test_app_name_is_django_spire(self) -> None:
|
|
10
|
+
assert urls.app_name == 'django_spire'
|
|
11
|
+
|
|
12
|
+
def test_urlpatterns_is_list(self) -> None:
|
|
13
|
+
assert isinstance(urls.urlpatterns, list)
|
|
14
|
+
|
|
15
|
+
def test_urlpatterns_is_not_empty(self) -> None:
|
|
16
|
+
assert len(urls.urlpatterns) > 0
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from django.test import TestCase
|
|
6
|
+
|
|
7
|
+
from django_spire.core.utils import (
|
|
8
|
+
get_callable_from_module_string_and_validate_arguments,
|
|
9
|
+
get_object_from_module_string
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestGetObjectFromModuleString(TestCase):
|
|
14
|
+
def test_import_class(self) -> None:
|
|
15
|
+
obj = get_object_from_module_string('django.test.TestCase')
|
|
16
|
+
assert obj is TestCase
|
|
17
|
+
|
|
18
|
+
def test_import_function(self) -> None:
|
|
19
|
+
obj = get_object_from_module_string('os.path.join')
|
|
20
|
+
assert callable(obj)
|
|
21
|
+
|
|
22
|
+
def test_import_invalid_module(self) -> None:
|
|
23
|
+
with pytest.raises(ImportError, match='Could not import module'):
|
|
24
|
+
get_object_from_module_string('nonexistent.module.Object')
|
|
25
|
+
|
|
26
|
+
def test_import_module_constant(self) -> None:
|
|
27
|
+
obj = get_object_from_module_string('os.sep')
|
|
28
|
+
assert isinstance(obj, str)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TestGetCallableFromModuleStringAndValidateArguments(TestCase):
|
|
32
|
+
def test_callable_with_missing_argument(self) -> None:
|
|
33
|
+
with pytest.raises(TypeError, match='missing required argument'):
|
|
34
|
+
get_callable_from_module_string_and_validate_arguments(
|
|
35
|
+
'json.dumps',
|
|
36
|
+
['nonexistent_arg']
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def test_callable_with_valid_arguments(self) -> None:
|
|
40
|
+
callable_ = get_callable_from_module_string_and_validate_arguments(
|
|
41
|
+
'json.dumps',
|
|
42
|
+
['obj']
|
|
43
|
+
)
|
|
44
|
+
assert callable(callable_)
|
|
45
|
+
|
|
46
|
+
def test_invalid_module(self) -> None:
|
|
47
|
+
with pytest.raises(ImportError, match='Could not import module'):
|
|
48
|
+
get_callable_from_module_string_and_validate_arguments(
|
|
49
|
+
'nonexistent.module.func',
|
|
50
|
+
[]
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def test_non_callable_object(self) -> None:
|
|
54
|
+
with pytest.raises(TypeError, match='is not callable'):
|
|
55
|
+
get_callable_from_module_string_and_validate_arguments(
|
|
56
|
+
'os.sep',
|
|
57
|
+
[]
|
|
58
|
+
)
|
django_spire/core/urls.py
CHANGED
django_spire/core/utils.py
CHANGED
|
@@ -1,25 +1,29 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import inspect
|
|
2
|
-
|
|
3
|
-
from typing import
|
|
4
|
+
|
|
5
|
+
from typing import Any, Callable, Sequence
|
|
4
6
|
|
|
5
7
|
|
|
6
8
|
def get_object_from_module_string(module_string: str) -> Any:
|
|
7
9
|
try:
|
|
8
10
|
module_string, object_name = module_string.rsplit('.', 1)
|
|
9
11
|
module = __import__(module_string, fromlist=[object_name])
|
|
10
|
-
except ImportError:
|
|
11
|
-
|
|
12
|
+
except ImportError as e:
|
|
13
|
+
message = f'Could not import module: {module_string}'
|
|
14
|
+
raise ImportError(message) from e
|
|
12
15
|
|
|
13
16
|
return getattr(module, object_name)
|
|
14
17
|
|
|
15
18
|
def get_callable_from_module_string_and_validate_arguments(
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
module_string: str,
|
|
20
|
+
valid_args: Sequence[str]
|
|
18
21
|
) -> Callable:
|
|
19
22
|
callable_ = get_object_from_module_string(module_string)
|
|
20
23
|
|
|
21
24
|
if not callable(callable_):
|
|
22
|
-
|
|
25
|
+
message = f'Object {module_string} is not callable'
|
|
26
|
+
raise TypeError(message)
|
|
23
27
|
|
|
24
28
|
sig = inspect.signature(callable_)
|
|
25
29
|
params = sig.parameters
|
|
@@ -29,4 +33,4 @@ def get_callable_from_module_string_and_validate_arguments(
|
|
|
29
33
|
message = f'{callable_.__qualname__} is missing required argument: {name}'
|
|
30
34
|
raise TypeError(message)
|
|
31
35
|
|
|
32
|
-
return callable_
|
|
36
|
+
return callable_
|
django_spire/exceptions.py
CHANGED
|
@@ -1,2 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class DjangoSpireError(Exception):
|
|
5
|
+
pass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DjangoSpireConfigurationError(DjangoSpireError):
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DjangoSpireInvalidClassStringError(DjangoSpireError):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DjangoSpireMissingRequiredAppError(DjangoSpireError):
|
|
2
17
|
pass
|
django_spire/file/admin.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from django.contrib import admin
|
|
2
4
|
from django.urls import reverse
|
|
3
5
|
from django.utils.html import format_html
|
|
@@ -21,13 +23,13 @@ class FileAdmin(admin.ModelAdmin):
|
|
|
21
23
|
args=[file.object_id]
|
|
22
24
|
)
|
|
23
25
|
|
|
24
|
-
return format_html(
|
|
26
|
+
return format_html('<a href="{}">{}</a>', url, file.content_object)
|
|
25
27
|
|
|
26
28
|
return 'No Related Object'
|
|
27
29
|
|
|
28
30
|
content_object_link.short_description = 'Content Object'
|
|
29
31
|
|
|
30
32
|
def file_link(self, file: models.File) -> str:
|
|
31
|
-
return format_html(
|
|
33
|
+
return format_html('<a href="{}" download>{}</a>', file.file.url, file.name)
|
|
32
34
|
|
|
33
35
|
file_link.short_description = 'File Download Link'
|
django_spire/file/apps.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from django.apps import AppConfig
|
|
2
4
|
from django.conf import settings
|
|
3
5
|
|
|
@@ -15,15 +17,11 @@ class FileConfig(AppConfig):
|
|
|
15
17
|
|
|
16
18
|
def ready(self) -> None:
|
|
17
19
|
if not hasattr(settings, 'BASE_FOLDER_NAME'):
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
raise ValueError(
|
|
25
|
-
f'"BASE_FOLDER_NAME" must be a string in the django settings when '
|
|
26
|
-
f'using "{self.label}".'
|
|
27
|
-
)
|
|
20
|
+
message = f'"BASE_FOLDER_NAME" must be set in the django settings when using "{self.label}".'
|
|
21
|
+
raise ValueError(message)
|
|
22
|
+
|
|
23
|
+
if not isinstance(settings.BASE_FOLDER_NAME, str):
|
|
24
|
+
message = f'"BASE_FOLDER_NAME" must be a string in the django settings when using "{self.label}".'
|
|
25
|
+
raise TypeError(message)
|
|
28
26
|
|
|
29
27
|
check_required_apps(self.label)
|