django-spire 0.23.7__py3-none-any.whl → 0.23.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/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/management/commands/spire_startapp_pkg/template/app/apps.py.template +2 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/__init__.py.template +2 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/form_urls.py.template +2 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/page_urls.py.template +2 -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 +12 -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 +5 -3
- 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.7.dist-info → django_spire-0.23.9.dist-info}/METADATA +2 -2
- {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/RECORD +534 -362
- {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/licenses/LICENSE.md +1 -1
- django_spire/contrib/options/tests/test_unit.py +0 -148
- 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.7.dist-info → django_spire-0.23.9.dist-info}/WHEEL +0 -0
- {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/top_level.txt +0 -0
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from django.urls import reverse
|
|
6
|
+
|
|
3
7
|
from django_spire.ai.chat.tests.test_urls.factories import create_test_chat
|
|
4
8
|
from django_spire.core.tests.test_cases import BaseTestCase
|
|
5
|
-
from django.urls import reverse
|
|
6
9
|
|
|
7
10
|
|
|
8
11
|
class ChatJsonUrlTests(BaseTestCase):
|
|
@@ -21,3 +24,201 @@ class ChatJsonUrlTests(BaseTestCase):
|
|
|
21
24
|
)
|
|
22
25
|
|
|
23
26
|
assert response.status_code == 200
|
|
27
|
+
|
|
28
|
+
def test_delete_view_success_response(self) -> None:
|
|
29
|
+
response = self.client.post(
|
|
30
|
+
reverse(
|
|
31
|
+
'django_spire:ai:chat:json:delete',
|
|
32
|
+
kwargs={'pk': self.test_chat.pk}
|
|
33
|
+
),
|
|
34
|
+
content_type='application/json'
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
data = response.json()
|
|
38
|
+
|
|
39
|
+
assert data['type'] == 'success'
|
|
40
|
+
assert 'deleted' in data['message'].lower()
|
|
41
|
+
|
|
42
|
+
def test_delete_view_sets_deleted_flag(self) -> None:
|
|
43
|
+
self.client.post(
|
|
44
|
+
reverse(
|
|
45
|
+
'django_spire:ai:chat:json:delete',
|
|
46
|
+
kwargs={'pk': self.test_chat.pk}
|
|
47
|
+
),
|
|
48
|
+
content_type='application/json'
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
self.test_chat.refresh_from_db()
|
|
52
|
+
|
|
53
|
+
assert self.test_chat.is_deleted
|
|
54
|
+
|
|
55
|
+
def test_delete_view_nonexistent_chat(self) -> None:
|
|
56
|
+
response = self.client.post(
|
|
57
|
+
reverse(
|
|
58
|
+
'django_spire:ai:chat:json:delete',
|
|
59
|
+
kwargs={'pk': 99999}
|
|
60
|
+
),
|
|
61
|
+
content_type='application/json'
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
data = response.json()
|
|
65
|
+
|
|
66
|
+
assert data['type'] == 'error'
|
|
67
|
+
|
|
68
|
+
def test_rename_view_url_path(self) -> None:
|
|
69
|
+
response = self.client.post(
|
|
70
|
+
reverse(
|
|
71
|
+
'django_spire:ai:chat:json:rename',
|
|
72
|
+
kwargs={'pk': self.test_chat.pk}
|
|
73
|
+
),
|
|
74
|
+
data=json.dumps({'new_name': 'New Chat Name'}),
|
|
75
|
+
content_type='application/json'
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
assert response.status_code == 200
|
|
79
|
+
|
|
80
|
+
def test_rename_view_success_response(self) -> None:
|
|
81
|
+
response = self.client.post(
|
|
82
|
+
reverse(
|
|
83
|
+
'django_spire:ai:chat:json:rename',
|
|
84
|
+
kwargs={'pk': self.test_chat.pk}
|
|
85
|
+
),
|
|
86
|
+
data=json.dumps({'new_name': 'Updated Name'}),
|
|
87
|
+
content_type='application/json'
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
data = response.json()
|
|
91
|
+
|
|
92
|
+
assert data['type'] == 'success'
|
|
93
|
+
|
|
94
|
+
def test_rename_view_updates_name(self) -> None:
|
|
95
|
+
new_name = 'Brand New Name'
|
|
96
|
+
|
|
97
|
+
self.client.post(
|
|
98
|
+
reverse(
|
|
99
|
+
'django_spire:ai:chat:json:rename',
|
|
100
|
+
kwargs={'pk': self.test_chat.pk}
|
|
101
|
+
),
|
|
102
|
+
data=json.dumps({'new_name': new_name}),
|
|
103
|
+
content_type='application/json'
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
self.test_chat.refresh_from_db()
|
|
107
|
+
|
|
108
|
+
assert self.test_chat.name == new_name
|
|
109
|
+
|
|
110
|
+
def test_rename_view_empty_name(self) -> None:
|
|
111
|
+
response = self.client.post(
|
|
112
|
+
reverse(
|
|
113
|
+
'django_spire:ai:chat:json:rename',
|
|
114
|
+
kwargs={'pk': self.test_chat.pk}
|
|
115
|
+
),
|
|
116
|
+
data=json.dumps({'new_name': ''}),
|
|
117
|
+
content_type='application/json'
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
data = response.json()
|
|
121
|
+
|
|
122
|
+
assert data['type'] == 'error'
|
|
123
|
+
|
|
124
|
+
def test_rename_view_name_too_long(self) -> None:
|
|
125
|
+
long_name = 'A' * 200
|
|
126
|
+
|
|
127
|
+
response = self.client.post(
|
|
128
|
+
reverse(
|
|
129
|
+
'django_spire:ai:chat:json:rename',
|
|
130
|
+
kwargs={'pk': self.test_chat.pk}
|
|
131
|
+
),
|
|
132
|
+
data=json.dumps({'new_name': long_name}),
|
|
133
|
+
content_type='application/json'
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
data = response.json()
|
|
137
|
+
|
|
138
|
+
assert data['type'] == 'error'
|
|
139
|
+
|
|
140
|
+
def test_rename_view_nonexistent_chat(self) -> None:
|
|
141
|
+
response = self.client.post(
|
|
142
|
+
reverse(
|
|
143
|
+
'django_spire:ai:chat:json:rename',
|
|
144
|
+
kwargs={'pk': 99999}
|
|
145
|
+
),
|
|
146
|
+
data=json.dumps({'new_name': 'New Name'}),
|
|
147
|
+
content_type='application/json'
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
data = response.json()
|
|
151
|
+
|
|
152
|
+
assert data['type'] == 'error'
|
|
153
|
+
|
|
154
|
+
def test_rename_view_max_length_name(self) -> None:
|
|
155
|
+
max_length_name = 'A' * 128
|
|
156
|
+
|
|
157
|
+
response = self.client.post(
|
|
158
|
+
reverse(
|
|
159
|
+
'django_spire:ai:chat:json:rename',
|
|
160
|
+
kwargs={'pk': self.test_chat.pk}
|
|
161
|
+
),
|
|
162
|
+
data=json.dumps({'new_name': max_length_name}),
|
|
163
|
+
content_type='application/json'
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
data = response.json()
|
|
167
|
+
|
|
168
|
+
assert data['type'] == 'success'
|
|
169
|
+
|
|
170
|
+
self.test_chat.refresh_from_db()
|
|
171
|
+
assert self.test_chat.name == max_length_name
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class ChatJsonUrlAdditionalTests(BaseTestCase):
|
|
175
|
+
def test_delete_multiple_chats(self) -> None:
|
|
176
|
+
chat1 = create_test_chat(user=self.super_user)
|
|
177
|
+
chat2 = create_test_chat(user=self.super_user)
|
|
178
|
+
|
|
179
|
+
self.client.post(
|
|
180
|
+
reverse('django_spire:ai:chat:json:delete', kwargs={'pk': chat1.pk}),
|
|
181
|
+
content_type='application/json'
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
self.client.post(
|
|
185
|
+
reverse('django_spire:ai:chat:json:delete', kwargs={'pk': chat2.pk}),
|
|
186
|
+
content_type='application/json'
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
chat1.refresh_from_db()
|
|
190
|
+
chat2.refresh_from_db()
|
|
191
|
+
|
|
192
|
+
assert chat1.is_deleted
|
|
193
|
+
assert chat2.is_deleted
|
|
194
|
+
|
|
195
|
+
def test_rename_with_special_characters(self) -> None:
|
|
196
|
+
chat = create_test_chat(user=self.super_user)
|
|
197
|
+
special_name = "Chat with 'quotes' and \"double quotes\""
|
|
198
|
+
|
|
199
|
+
response = self.client.post(
|
|
200
|
+
reverse('django_spire:ai:chat:json:rename', kwargs={'pk': chat.pk}),
|
|
201
|
+
data=json.dumps({'new_name': special_name}),
|
|
202
|
+
content_type='application/json'
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
data = response.json()
|
|
206
|
+
|
|
207
|
+
assert data['type'] == 'success'
|
|
208
|
+
|
|
209
|
+
def test_rename_with_unicode(self) -> None:
|
|
210
|
+
chat = create_test_chat(user=self.super_user)
|
|
211
|
+
unicode_name = 'Chat 日本語 🎉'
|
|
212
|
+
|
|
213
|
+
response = self.client.post(
|
|
214
|
+
reverse('django_spire:ai:chat:json:rename', kwargs={'pk': chat.pk}),
|
|
215
|
+
data=json.dumps({'new_name': unicode_name}),
|
|
216
|
+
content_type='application/json'
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
data = response.json()
|
|
220
|
+
|
|
221
|
+
assert data['type'] == 'success'
|
|
222
|
+
|
|
223
|
+
chat.refresh_from_db()
|
|
224
|
+
assert chat.name == unicode_name
|
|
File without changes
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from django.core.exceptions import ValidationError
|
|
6
|
+
|
|
7
|
+
from django_spire.ai.context.choices import PersonRoleChoices
|
|
8
|
+
from django_spire.ai.context.intelligence.prompts.organization_prompts import organization_info_prompt
|
|
9
|
+
from django_spire.ai.context.models import Organization, Person
|
|
10
|
+
from django_spire.core.tests.test_cases import BaseTestCase
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class OrganizationModelTests(BaseTestCase):
|
|
14
|
+
def test_organization_creation(self) -> None:
|
|
15
|
+
org = Organization.objects.create(
|
|
16
|
+
name='Test Organization',
|
|
17
|
+
legal_name='Test Organization Inc.'
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
assert org.name == 'Test Organization'
|
|
21
|
+
assert org.legal_name == 'Test Organization Inc.'
|
|
22
|
+
|
|
23
|
+
def test_organization_only_one_allowed(self) -> None:
|
|
24
|
+
Organization.objects.create(name='First Org')
|
|
25
|
+
|
|
26
|
+
with pytest.raises(ValidationError):
|
|
27
|
+
Organization.objects.create(name='Second Org')
|
|
28
|
+
|
|
29
|
+
def test_organization_all_fields(self) -> None:
|
|
30
|
+
org = Organization.objects.create(
|
|
31
|
+
name='Test Org',
|
|
32
|
+
legal_name='Test Org LLC',
|
|
33
|
+
description='A test organization',
|
|
34
|
+
sector='Technology',
|
|
35
|
+
sub_sector='Software',
|
|
36
|
+
website='https://example.com',
|
|
37
|
+
street_address='123 Main St',
|
|
38
|
+
unit_number='Suite 100',
|
|
39
|
+
city='New York',
|
|
40
|
+
province='NY',
|
|
41
|
+
postal_code='10001',
|
|
42
|
+
country='USA',
|
|
43
|
+
phone='555-1234',
|
|
44
|
+
email='info@example.com'
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
assert org.name == 'Test Org'
|
|
48
|
+
assert org.sector == 'Technology'
|
|
49
|
+
assert org.city == 'New York'
|
|
50
|
+
assert org.email == 'info@example.com'
|
|
51
|
+
|
|
52
|
+
def test_organization_nullable_fields(self) -> None:
|
|
53
|
+
org = Organization.objects.create()
|
|
54
|
+
|
|
55
|
+
assert org.name is None
|
|
56
|
+
assert org.legal_name is None
|
|
57
|
+
assert org.description is None
|
|
58
|
+
|
|
59
|
+
def test_organization_get_only_or_none_exists(self) -> None:
|
|
60
|
+
Organization.objects.create(name='Test Org')
|
|
61
|
+
|
|
62
|
+
result = Organization.objects.get_only_or_none()
|
|
63
|
+
|
|
64
|
+
assert result is not None
|
|
65
|
+
assert result.name == 'Test Org'
|
|
66
|
+
|
|
67
|
+
def test_organization_get_only_or_none_not_exists(self) -> None:
|
|
68
|
+
result = Organization.objects.get_only_or_none()
|
|
69
|
+
|
|
70
|
+
assert result is None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class PersonModelTests(BaseTestCase):
|
|
74
|
+
def test_person_creation(self) -> None:
|
|
75
|
+
person = Person.objects.create(
|
|
76
|
+
first_name='John',
|
|
77
|
+
last_name='Doe',
|
|
78
|
+
role=PersonRoleChoices.ADMIN
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
assert person.first_name == 'John'
|
|
82
|
+
assert person.last_name == 'Doe'
|
|
83
|
+
assert person.role == PersonRoleChoices.ADMIN
|
|
84
|
+
|
|
85
|
+
def test_person_default_role(self) -> None:
|
|
86
|
+
person = Person.objects.create(
|
|
87
|
+
first_name='Jane',
|
|
88
|
+
last_name='Smith'
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
assert person.role == PersonRoleChoices.ADMIN
|
|
92
|
+
|
|
93
|
+
def test_person_default_is_internal(self) -> None:
|
|
94
|
+
person = Person.objects.create(
|
|
95
|
+
first_name='Test',
|
|
96
|
+
last_name='User'
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
assert person.is_internal_to_organization is True
|
|
100
|
+
|
|
101
|
+
def test_person_all_roles(self) -> None:
|
|
102
|
+
for role_choice in PersonRoleChoices:
|
|
103
|
+
person = Person.objects.create(
|
|
104
|
+
first_name=f'Test_{role_choice.value}',
|
|
105
|
+
last_name='User',
|
|
106
|
+
role=role_choice
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
assert person.role == role_choice
|
|
110
|
+
|
|
111
|
+
def test_person_with_user(self) -> None:
|
|
112
|
+
person = Person.objects.create(
|
|
113
|
+
user=self.super_user,
|
|
114
|
+
first_name='Super',
|
|
115
|
+
last_name='User'
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
assert person.user == self.super_user
|
|
119
|
+
|
|
120
|
+
def test_person_nullable_fields(self) -> None:
|
|
121
|
+
person = Person.objects.create()
|
|
122
|
+
|
|
123
|
+
assert person.first_name is None
|
|
124
|
+
assert person.last_name is None
|
|
125
|
+
assert person.phone is None
|
|
126
|
+
assert person.email is None
|
|
127
|
+
assert person.role_details is None
|
|
128
|
+
|
|
129
|
+
def test_person_external_to_organization(self) -> None:
|
|
130
|
+
person = Person.objects.create(
|
|
131
|
+
first_name='External',
|
|
132
|
+
last_name='Person',
|
|
133
|
+
is_internal_to_organization=False
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
assert person.is_internal_to_organization is False
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class PersonRoleChoicesTests(BaseTestCase):
|
|
140
|
+
def test_admin_choice(self) -> None:
|
|
141
|
+
assert PersonRoleChoices.ADMIN.value == 'admin'
|
|
142
|
+
assert PersonRoleChoices.ADMIN.label == 'Admin'
|
|
143
|
+
|
|
144
|
+
def test_human_resources_choice(self) -> None:
|
|
145
|
+
assert PersonRoleChoices.HUMAN_RESOURCES.value == 'human_resources'
|
|
146
|
+
assert PersonRoleChoices.HUMAN_RESOURCES.label == 'Human Resources'
|
|
147
|
+
|
|
148
|
+
def test_sales_choice(self) -> None:
|
|
149
|
+
assert PersonRoleChoices.SALES.value == 'sales'
|
|
150
|
+
assert PersonRoleChoices.SALES.label == 'Sales'
|
|
151
|
+
|
|
152
|
+
def test_manager_choice(self) -> None:
|
|
153
|
+
assert PersonRoleChoices.MANAGER.value == 'manager'
|
|
154
|
+
assert PersonRoleChoices.MANAGER.label == 'Manager'
|
|
155
|
+
|
|
156
|
+
def test_marketing_choice(self) -> None:
|
|
157
|
+
assert PersonRoleChoices.MARKETING.value == 'marketing'
|
|
158
|
+
assert PersonRoleChoices.MARKETING.label == 'Marketing'
|
|
159
|
+
|
|
160
|
+
def test_technical_choice(self) -> None:
|
|
161
|
+
assert PersonRoleChoices.TECHNICAL.value == 'technical'
|
|
162
|
+
assert PersonRoleChoices.TECHNICAL.label == 'Technical'
|
|
163
|
+
|
|
164
|
+
def test_it_support_choice(self) -> None:
|
|
165
|
+
assert PersonRoleChoices.IT_SUPPORT.value == 'it_support'
|
|
166
|
+
assert PersonRoleChoices.IT_SUPPORT.label == 'IT Support'
|
|
167
|
+
|
|
168
|
+
def test_all_choices_count(self) -> None:
|
|
169
|
+
assert len(PersonRoleChoices) == 7
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class OrganizationPromptTests(BaseTestCase):
|
|
173
|
+
def test_organization_info_prompt_with_org(self) -> None:
|
|
174
|
+
Organization.objects.create(
|
|
175
|
+
name='Test Org',
|
|
176
|
+
legal_name='Test Org Inc.'
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
prompt = organization_info_prompt()
|
|
180
|
+
|
|
181
|
+
assert prompt is not None
|
|
182
|
+
assert 'Organization Information' in prompt.to_str()
|
|
183
|
+
|
|
184
|
+
def test_organization_info_prompt_without_org(self) -> None:
|
|
185
|
+
prompt = organization_info_prompt()
|
|
186
|
+
|
|
187
|
+
assert prompt is not None
|
|
188
|
+
assert 'no organization information available' in prompt.to_str()
|
django_spire/ai/decorators.py
CHANGED
|
@@ -4,7 +4,7 @@ import json
|
|
|
4
4
|
import traceback
|
|
5
5
|
import uuid
|
|
6
6
|
|
|
7
|
-
from typing import TYPE_CHECKING
|
|
7
|
+
from typing import TYPE_CHECKING, Callable, TypeVar
|
|
8
8
|
|
|
9
9
|
from dandy import Recorder
|
|
10
10
|
from django.utils.timezone import now
|
|
@@ -15,16 +15,19 @@ if TYPE_CHECKING:
|
|
|
15
15
|
from django.contrib.auth.models import AbstractBaseUser
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
T = TypeVar('T')
|
|
19
|
+
|
|
20
|
+
|
|
18
21
|
def log_ai_interaction_from_recorder(
|
|
19
22
|
user: AbstractBaseUser | None = None,
|
|
20
23
|
actor: str | None = None
|
|
21
|
-
):
|
|
24
|
+
) -> Callable[[Callable[..., T]], Callable[..., T]]:
|
|
22
25
|
if user is None and actor is None:
|
|
23
26
|
message = 'user or actor must be provided'
|
|
24
27
|
raise ValueError(message)
|
|
25
28
|
|
|
26
|
-
def decorator(func):
|
|
27
|
-
def wrapper(*args, **kwargs):
|
|
29
|
+
def decorator(func: Callable[..., T]) -> Callable[..., T]:
|
|
30
|
+
def wrapper(*args, **kwargs) -> T:
|
|
28
31
|
recording_uuid = f'Recording-{uuid.uuid4()}'
|
|
29
32
|
|
|
30
33
|
ai_usage, _ = AiUsage.objects.get_or_create(
|
|
@@ -41,7 +44,6 @@ def log_ai_interaction_from_recorder(
|
|
|
41
44
|
try:
|
|
42
45
|
Recorder.start_recording(recording_uuid)
|
|
43
46
|
return func(*args, **kwargs)
|
|
44
|
-
|
|
45
47
|
except Exception as e:
|
|
46
48
|
ai_usage.was_successful = False
|
|
47
49
|
|
|
@@ -55,7 +57,6 @@ def log_ai_interaction_from_recorder(
|
|
|
55
57
|
ai_interaction.stack_trace = stack_trace
|
|
56
58
|
|
|
57
59
|
raise
|
|
58
|
-
|
|
59
60
|
finally:
|
|
60
61
|
Recorder.stop_recording(recording_uuid)
|
|
61
62
|
|
|
@@ -1,31 +1,121 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from django_spire.ai.prompt.system import bots
|
|
4
|
+
from django_spire.ai.prompt.system.intel import SystemPromptIntel, SystemPromptResultIntel
|
|
4
5
|
from django_spire.core.tests.test_cases import BaseTestCase
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class PromptBotTestCase(BaseTestCase):
|
|
8
|
-
def
|
|
9
|
+
def test_role_system_prompt_bot(self) -> None:
|
|
9
10
|
ISSUE_PROMPT = (
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
'I am using jira to plan developer workloads And I want to be able to build very clear issues that '
|
|
12
|
+
'developers will be able to read understand and take action on to complete the goal. '
|
|
13
|
+
'I will give you details about a software development task that we have to achieve and I want you '
|
|
14
|
+
'to return a short one sentence summary that the developer can read and understand the scope of the issue '
|
|
15
|
+
'before they get into the details of it '
|
|
15
16
|
)
|
|
16
17
|
|
|
17
18
|
role_bot = bots.RoleSystemPromptBot()
|
|
18
19
|
role_result = role_bot.process(ISSUE_PROMPT)
|
|
19
|
-
|
|
20
|
+
|
|
21
|
+
assert role_result is not None
|
|
22
|
+
assert role_result.result is not None
|
|
23
|
+
assert len(role_result.result) > 0
|
|
24
|
+
|
|
25
|
+
def test_task_system_prompt_bot(self) -> None:
|
|
26
|
+
ISSUE_PROMPT = (
|
|
27
|
+
'I am using jira to plan developer workloads And I want to be able to build very clear issues that '
|
|
28
|
+
'developers will be able to read understand and take action on to complete the goal.'
|
|
29
|
+
)
|
|
20
30
|
|
|
21
31
|
task_bot = bots.TaskSystemPromptBot()
|
|
22
32
|
task_result = task_bot.process(ISSUE_PROMPT)
|
|
23
|
-
|
|
33
|
+
|
|
34
|
+
assert task_result is not None
|
|
35
|
+
assert task_result.result is not None
|
|
36
|
+
assert len(task_result.result) > 0
|
|
37
|
+
|
|
38
|
+
def test_guidelines_system_prompt_bot(self) -> None:
|
|
39
|
+
ISSUE_PROMPT = (
|
|
40
|
+
'I want to create a system that helps users write better emails.'
|
|
41
|
+
)
|
|
24
42
|
|
|
25
43
|
guidelines_bot = bots.GuidelinesSystemPromptBot()
|
|
26
44
|
guidelines_result = guidelines_bot.process(ISSUE_PROMPT)
|
|
27
|
-
|
|
45
|
+
|
|
46
|
+
assert guidelines_result is not None
|
|
47
|
+
assert guidelines_result.result is not None
|
|
48
|
+
assert len(guidelines_result.result) > 0
|
|
49
|
+
|
|
50
|
+
def test_output_format_system_prompt_bot(self) -> None:
|
|
51
|
+
ISSUE_PROMPT = (
|
|
52
|
+
'I want to create a system that returns a summary of a document.'
|
|
53
|
+
)
|
|
28
54
|
|
|
29
55
|
output_format_bot = bots.OutputFormatSystemPromptBot()
|
|
30
56
|
output_format_result = output_format_bot.process(ISSUE_PROMPT)
|
|
31
|
-
|
|
57
|
+
|
|
58
|
+
assert output_format_result is not None
|
|
59
|
+
assert output_format_result.result is not None
|
|
60
|
+
|
|
61
|
+
def test_system_prompt_bot_full_workflow(self) -> None:
|
|
62
|
+
ISSUE_PROMPT = (
|
|
63
|
+
'I am using jira to plan developer workloads And I want to be able to build very clear issues that '
|
|
64
|
+
'developers will be able to read understand and take action on to complete the goal. '
|
|
65
|
+
'I will give you details about a software development task that we have to achieve and I want you '
|
|
66
|
+
'to return a short one sentence summary that the developer can read and understand the scope of the issue '
|
|
67
|
+
'before they get into the details of it '
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
role_bot = bots.RoleSystemPromptBot()
|
|
71
|
+
role_result = role_bot.process(ISSUE_PROMPT)
|
|
72
|
+
assert role_result is not None
|
|
73
|
+
|
|
74
|
+
task_bot = bots.TaskSystemPromptBot()
|
|
75
|
+
task_result = task_bot.process(ISSUE_PROMPT)
|
|
76
|
+
assert task_result is not None
|
|
77
|
+
|
|
78
|
+
guidelines_bot = bots.GuidelinesSystemPromptBot()
|
|
79
|
+
guidelines_result = guidelines_bot.process(ISSUE_PROMPT)
|
|
80
|
+
assert guidelines_result is not None
|
|
81
|
+
|
|
82
|
+
output_format_bot = bots.OutputFormatSystemPromptBot()
|
|
83
|
+
output_format_result = output_format_bot.process(ISSUE_PROMPT)
|
|
84
|
+
assert output_format_result is not None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class SystemPromptIntelTestCase(BaseTestCase):
|
|
88
|
+
def test_system_prompt_intel_to_string(self) -> None:
|
|
89
|
+
intel = SystemPromptIntel(
|
|
90
|
+
role='Test Role',
|
|
91
|
+
task='Test Task',
|
|
92
|
+
guidelines='Test Guidelines',
|
|
93
|
+
output_format='Test Output Format'
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
result = intel.to_string()
|
|
97
|
+
|
|
98
|
+
assert 'Role: Test Role' in result
|
|
99
|
+
assert 'Task: Test Task' in result
|
|
100
|
+
assert 'Guidelines: Test Guidelines' in result
|
|
101
|
+
assert 'Output Format: Test Output Format' in result
|
|
102
|
+
|
|
103
|
+
def test_system_prompt_intel_to_string_without_output_format(self) -> None:
|
|
104
|
+
intel = SystemPromptIntel(
|
|
105
|
+
role='Test Role',
|
|
106
|
+
task='Test Task',
|
|
107
|
+
guidelines='Test Guidelines',
|
|
108
|
+
output_format=None
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
result = intel.to_string()
|
|
112
|
+
|
|
113
|
+
assert 'Role: Test Role' in result
|
|
114
|
+
assert 'Task: Test Task' in result
|
|
115
|
+
assert 'Guidelines: Test Guidelines' in result
|
|
116
|
+
assert 'Output Format' not in result
|
|
117
|
+
|
|
118
|
+
def test_system_prompt_result_intel(self) -> None:
|
|
119
|
+
intel = SystemPromptResultIntel(result='Test result')
|
|
120
|
+
|
|
121
|
+
assert intel.result == 'Test result'
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django_spire.ai.prompt.intel import DandyPromptPythonFileIntel, TextToMarkdownIntel
|
|
4
|
+
from django_spire.core.tests.test_cases import BaseTestCase
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DandyPromptPythonFileIntelTests(BaseTestCase):
|
|
8
|
+
def test_dandy_prompt_python_file_intel_creation(self) -> None:
|
|
9
|
+
intel = DandyPromptPythonFileIntel(
|
|
10
|
+
source_code='print("hello")',
|
|
11
|
+
file_name='test.py'
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
assert intel.source_code == 'print("hello")'
|
|
15
|
+
assert intel.file_name == 'test.py'
|
|
16
|
+
|
|
17
|
+
def test_dandy_prompt_python_file_intel_empty_source(self) -> None:
|
|
18
|
+
intel = DandyPromptPythonFileIntel(
|
|
19
|
+
source_code='',
|
|
20
|
+
file_name='empty.py'
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
assert intel.source_code == ''
|
|
24
|
+
|
|
25
|
+
def test_dandy_prompt_python_file_intel_long_source(self) -> None:
|
|
26
|
+
long_source = 'x = 1\n' * 1000
|
|
27
|
+
intel = DandyPromptPythonFileIntel(
|
|
28
|
+
source_code=long_source,
|
|
29
|
+
file_name='long.py'
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
assert intel.source_code == long_source
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TextToMarkdownIntelTests(BaseTestCase):
|
|
36
|
+
def test_text_to_markdown_intel_creation(self) -> None:
|
|
37
|
+
intel = TextToMarkdownIntel(
|
|
38
|
+
markdown_content='# Hello World',
|
|
39
|
+
file_name='test.md'
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
assert intel.markdown_content == '# Hello World'
|
|
43
|
+
assert intel.file_name == 'test.md'
|
|
44
|
+
|
|
45
|
+
def test_text_to_markdown_intel_empty_content(self) -> None:
|
|
46
|
+
intel = TextToMarkdownIntel(
|
|
47
|
+
markdown_content='',
|
|
48
|
+
file_name='empty.md'
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
assert intel.markdown_content == ''
|
|
52
|
+
|
|
53
|
+
def test_text_to_markdown_intel_complex_markdown(self) -> None:
|
|
54
|
+
complex_md = """
|
|
55
|
+
# Title
|
|
56
|
+
|
|
57
|
+
## Section 1
|
|
58
|
+
|
|
59
|
+
- Item 1
|
|
60
|
+
- Item 2
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
print("code")
|
|
64
|
+
```
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
intel = TextToMarkdownIntel(
|
|
68
|
+
markdown_content=complex_md,
|
|
69
|
+
file_name='complex.md'
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
assert intel.markdown_content == complex_md
|
|
73
|
+
|
|
74
|
+
def test_text_to_markdown_intel_model_dump(self) -> None:
|
|
75
|
+
intel = TextToMarkdownIntel(
|
|
76
|
+
markdown_content='Test',
|
|
77
|
+
file_name='test.md'
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
dump = intel.model_dump()
|
|
81
|
+
|
|
82
|
+
assert 'markdown_content' in dump
|
|
83
|
+
assert 'file_name' in dump
|