django-spire 0.23.7__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/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.7.dist-info → django_spire-0.23.8.dist-info}/METADATA +1 -1
- {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/RECORD +530 -358
- 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.8.dist-info}/WHEEL +0 -0
- {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/licenses/LICENSE.md +0 -0
- {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/top_level.txt +0 -0
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
2
5
|
from django.shortcuts import get_object_or_404
|
|
3
|
-
from django.template.response import TemplateResponse
|
|
4
6
|
from django.urls import reverse
|
|
5
7
|
|
|
6
8
|
from django_spire.auth.controller.controller import AppAuthController
|
|
7
9
|
from django_spire.contrib.generic_views import portal_views
|
|
8
10
|
from django_spire.knowledge.entry.models import Entry
|
|
9
11
|
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from django.core.handlers.wsgi import WSGIRequest
|
|
14
|
+
from django.template.response import TemplateResponse
|
|
15
|
+
|
|
10
16
|
|
|
11
17
|
@AppAuthController('knowledge').permission_required('can_delete')
|
|
12
18
|
def delete_view(request: WSGIRequest, pk: int) -> TemplateResponse:
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
2
5
|
from django.template.response import TemplateResponse
|
|
3
6
|
from django.urls import reverse
|
|
4
7
|
|
|
@@ -7,6 +10,9 @@ from django_spire.contrib import Breadcrumbs
|
|
|
7
10
|
from django_spire.knowledge.collection.models import Collection
|
|
8
11
|
from django_spire.knowledge.entry.models import Entry
|
|
9
12
|
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from django.core.handlers.wsgi import WSGIRequest
|
|
15
|
+
|
|
10
16
|
|
|
11
17
|
@AppAuthController('knowledge').permission_required('can_view')
|
|
12
18
|
def file_list_view(request: WSGIRequest, collection_pk: int = 0) -> TemplateResponse:
|
|
@@ -15,15 +15,16 @@ if TYPE_CHECKING:
|
|
|
15
15
|
from django.core.handlers.wsgi import WSGIRequest
|
|
16
16
|
from dandy.llm.request.message import MessageHistory
|
|
17
17
|
|
|
18
|
+
|
|
18
19
|
NO_KNOWLEDGE_MESSAGE_INTEL = DefaultMessageIntel(
|
|
19
20
|
text='Sorry, I could not find any information on that.'
|
|
20
21
|
)
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
def knowledge_search_workflow(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
request: WSGIRequest,
|
|
26
|
+
user_input: str,
|
|
27
|
+
message_history: MessageHistory | None = None
|
|
27
28
|
) -> BaseMessageIntel | None:
|
|
28
29
|
user_tag_set = TagSetBot().process(user_input)
|
|
29
30
|
|
|
@@ -76,8 +77,6 @@ def knowledge_search_workflow(
|
|
|
76
77
|
entries=entries
|
|
77
78
|
)
|
|
78
79
|
|
|
79
|
-
print(entries_intel_future.result)
|
|
80
|
-
|
|
81
80
|
return KnowledgeMessageIntel(
|
|
82
81
|
answer_intel=answer_intel_future.result,
|
|
83
82
|
entries_intel=entries_intel_future.result,
|
django_spire/knowledge/models.py
CHANGED
|
@@ -4,8 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from django_spire.knowledge.entry import models as entry_models
|
|
6
6
|
from django_spire.knowledge.entry.version import models as entry_version_models
|
|
7
|
-
from django_spire.knowledge.entry.version.block import models as
|
|
8
|
-
entry_version_block_models
|
|
7
|
+
from django_spire.knowledge.entry.version.block import models as entry_version_block_models
|
|
9
8
|
from django_spire.knowledge.collection import models as collection_models
|
|
10
9
|
|
|
11
10
|
# Must assert to stop unused import removals.
|
|
File without changes
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django_spire.core.tests.test_cases import BaseTestCase
|
|
4
|
+
from django_spire.knowledge.templatetags.spire_knowledge_tags import format_to_html
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SpireKnowledgeTagsTests(BaseTestCase):
|
|
8
|
+
def test_format_to_html_empty_string(self):
|
|
9
|
+
result = format_to_html('')
|
|
10
|
+
assert result == ' '
|
|
11
|
+
|
|
12
|
+
def test_format_to_html_none(self):
|
|
13
|
+
result = format_to_html(None)
|
|
14
|
+
assert result == ' '
|
|
15
|
+
|
|
16
|
+
def test_format_to_html_line_breaks(self):
|
|
17
|
+
result = format_to_html('Line1\nLine2')
|
|
18
|
+
assert '<br>' in result
|
|
19
|
+
|
|
20
|
+
def test_format_to_html_bold(self):
|
|
21
|
+
result = format_to_html('**bold text**')
|
|
22
|
+
assert '<span class="fw-bold">bold text</span>' in result
|
|
23
|
+
|
|
24
|
+
def test_format_to_html_italic(self):
|
|
25
|
+
result = format_to_html('*italic text*')
|
|
26
|
+
assert '<span class="fst-italic">italic text</span>' in result
|
|
27
|
+
|
|
28
|
+
def test_format_to_html_strikethrough(self):
|
|
29
|
+
result = format_to_html('~~strikethrough~~')
|
|
30
|
+
assert '<span class="text-decoration-line-through">strikethrough</span>' in result
|
|
31
|
+
|
|
32
|
+
def test_format_to_html_mixed(self):
|
|
33
|
+
result = format_to_html('**bold** and *italic* and ~~strike~~')
|
|
34
|
+
assert '<span class="fw-bold">bold</span>' in result
|
|
35
|
+
assert '<span class="fst-italic">italic</span>' in result
|
|
36
|
+
assert '<span class="text-decoration-line-through">strike</span>' in result
|
|
37
|
+
|
|
38
|
+
def test_format_to_html_plain_text(self):
|
|
39
|
+
result = format_to_html('plain text')
|
|
40
|
+
assert result == 'plain text'
|
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django.urls import reverse
|
|
4
|
+
|
|
5
|
+
from django_spire.core.tests.test_cases import BaseTestCase
|
|
6
|
+
from django_spire.knowledge.collection.tests.factories import create_test_collection
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class KnowledgePageUrlsTests(BaseTestCase):
|
|
10
|
+
def setUp(self):
|
|
11
|
+
super().setUp()
|
|
12
|
+
self.collection = create_test_collection()
|
|
13
|
+
|
|
14
|
+
def test_home_view_url_path(self):
|
|
15
|
+
response = self.client.get(
|
|
16
|
+
reverse('django_spire:knowledge:page:home')
|
|
17
|
+
)
|
|
18
|
+
assert response.status_code == 200
|
|
19
|
+
|
|
20
|
+
def test_home_view_contains_collections(self):
|
|
21
|
+
response = self.client.get(
|
|
22
|
+
reverse('django_spire:knowledge:page:home')
|
|
23
|
+
)
|
|
24
|
+
assert 'collections' in response.context
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
from django.template.response import TemplateResponse
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
5
4
|
|
|
6
5
|
from django_spire.auth.controller.controller import AppAuthController
|
|
7
6
|
from django_spire.contrib.generic_views import portal_views
|
|
8
7
|
from django_spire.knowledge.collection.models import Collection
|
|
9
8
|
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from django.template.response import TemplateResponse
|
|
11
|
+
from django.core.handlers.wsgi import WSGIRequest
|
|
12
|
+
|
|
13
|
+
from django_spire.contrib.breadcrumb import Breadcrumbs
|
|
14
|
+
|
|
10
15
|
|
|
11
16
|
@AppAuthController('knowledge').permission_required('can_view')
|
|
12
17
|
def home_view(request: WSGIRequest) -> TemplateResponse:
|
|
13
|
-
def breadcrumbs_func(breadcrumbs):
|
|
18
|
+
def breadcrumbs_func(breadcrumbs: Breadcrumbs):
|
|
14
19
|
breadcrumbs.add_breadcrumb(name='Knowledge')
|
|
15
20
|
|
|
16
21
|
return portal_views.list_view(
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from django.contrib import admin
|
|
2
4
|
from django.utils.html import format_html
|
|
3
5
|
|
|
@@ -25,7 +27,7 @@ class NotificationAdmin(admin.ModelAdmin):
|
|
|
25
27
|
|
|
26
28
|
def url_link(self, notification: models.Notification) -> str:
|
|
27
29
|
if notification.url:
|
|
28
|
-
return format_html(
|
|
30
|
+
return format_html('<a href="{}" target="_blank">Link</a>', notification.url)
|
|
29
31
|
|
|
30
32
|
return 'No URL'
|
|
31
33
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from django.apps import AppConfig
|
|
2
4
|
|
|
3
5
|
from django_spire.utils import check_required_apps
|
|
@@ -11,4 +13,4 @@ class NotificationAppConfig(AppConfig):
|
|
|
11
13
|
REQUIRED_APPS = ('django_spire_core', 'django_spire_notification')
|
|
12
14
|
|
|
13
15
|
def ready(self) -> None:
|
|
14
|
-
check_required_apps(self.label)
|
|
16
|
+
check_required_apps(self.label)
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from django_spire.exceptions import DjangoSpireError
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
|
|
6
|
+
class AppNotificationError(DjangoSpireError):
|
|
5
7
|
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MissingUserError(AppNotificationError):
|
|
11
|
+
def __init__(self) -> None:
|
|
12
|
+
super().__init__('AppNotifications must have a user associated with them')
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import json
|
|
2
4
|
|
|
3
5
|
from django.db import models
|
|
@@ -36,12 +38,14 @@ class AppNotification(ViewedModelMixin, HistoryModelMixin):
|
|
|
36
38
|
|
|
37
39
|
if days > 0:
|
|
38
40
|
return f"{int(days)} day{'s' if days != 1 else ''} ago"
|
|
39
|
-
|
|
41
|
+
|
|
42
|
+
if hours > 0:
|
|
40
43
|
return f"{int(hours)} hour{'s' if hours != 1 else ''} ago"
|
|
41
|
-
|
|
44
|
+
|
|
45
|
+
if minutes > 0:
|
|
42
46
|
return f"{int(minutes)} minute{'s' if minutes != 1 else ''} ago"
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
|
|
48
|
+
return 'just now'
|
|
45
49
|
|
|
46
50
|
def as_dict(self) -> dict:
|
|
47
51
|
return {
|
|
@@ -1,32 +1,36 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from django.utils.timezone import now
|
|
2
4
|
|
|
3
|
-
from django_spire.notification.app.exceptions import
|
|
4
|
-
from django_spire.notification.choices import
|
|
5
|
+
from django_spire.notification.app.exceptions import MissingUserError
|
|
6
|
+
from django_spire.notification.choices import (
|
|
7
|
+
NotificationTypeChoices,
|
|
5
8
|
NotificationStatusChoices
|
|
6
|
-
|
|
9
|
+
)
|
|
10
|
+
from django_spire.notification.exceptions import InvalidNotificationTypeError
|
|
7
11
|
from django_spire.notification.models import Notification
|
|
8
12
|
from django_spire.notification.processors.processor import BaseNotificationProcessor
|
|
9
13
|
|
|
10
14
|
|
|
11
15
|
class AppNotificationProcessor(BaseNotificationProcessor):
|
|
12
|
-
def
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
raise NotificationException(
|
|
16
|
-
f'AppNotificationProcessor only processes '
|
|
17
|
-
f'App notifications. Was provided {notification.type}'
|
|
18
|
-
)
|
|
16
|
+
def _validate_notification_type(self, notification: Notification):
|
|
17
|
+
if notification.type != NotificationTypeChoices.APP:
|
|
18
|
+
raise InvalidNotificationTypeError(NotificationTypeChoices.APP, notification.type)
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
)
|
|
20
|
+
def _validate_user(self, notification: Notification):
|
|
21
|
+
if notification.user_id is None:
|
|
22
|
+
raise MissingUserError
|
|
24
23
|
|
|
24
|
+
def process(self, notification: Notification):
|
|
25
|
+
try:
|
|
26
|
+
self._validate_notification_type(notification)
|
|
27
|
+
self._validate_user(notification)
|
|
25
28
|
except Exception as e:
|
|
26
29
|
notification.status = NotificationStatusChoices.ERRORED
|
|
27
30
|
notification.status_message = str(e)
|
|
28
31
|
notification.save()
|
|
29
|
-
|
|
32
|
+
|
|
33
|
+
raise
|
|
30
34
|
|
|
31
35
|
notification.status = NotificationStatusChoices.SENT
|
|
32
36
|
notification.sent_datetime = now()
|
|
@@ -38,16 +42,8 @@ class AppNotificationProcessor(BaseNotificationProcessor):
|
|
|
38
42
|
|
|
39
43
|
for notification in notifications:
|
|
40
44
|
try:
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
f'AppNotificationProcessor only processes '
|
|
44
|
-
f'App notifications. Was provided {notification.type}'
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
if notification.user_id is None:
|
|
48
|
-
raise AppNotificationException(
|
|
49
|
-
'AppNotifications must have a user associated with them'
|
|
50
|
-
)
|
|
45
|
+
self._validate_notification_type(notification)
|
|
46
|
+
self._validate_user(notification)
|
|
51
47
|
|
|
52
48
|
notification.status = NotificationStatusChoices.SENT
|
|
53
49
|
notification.sent_datetime = now()
|
|
@@ -60,7 +56,7 @@ class AppNotificationProcessor(BaseNotificationProcessor):
|
|
|
60
56
|
notifications,
|
|
61
57
|
['status', 'sent_datetime', 'status_message']
|
|
62
58
|
)
|
|
63
|
-
raise
|
|
59
|
+
raise
|
|
64
60
|
|
|
65
61
|
Notification.objects.bulk_update(
|
|
66
62
|
notifications,
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django.contrib.auth.models import User
|
|
4
|
+
from django.utils.timezone import now
|
|
5
|
+
|
|
6
|
+
from django_spire.notification.app.models import AppNotification
|
|
7
|
+
from django_spire.notification.choices import (
|
|
8
|
+
NotificationStatusChoices,
|
|
9
|
+
NotificationTypeChoices,
|
|
10
|
+
)
|
|
11
|
+
from django_spire.notification.models import Notification
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def create_test_app_notification(**kwargs) -> AppNotification:
|
|
15
|
+
if 'notification' not in kwargs:
|
|
16
|
+
user = kwargs.pop('user', None) or User.objects.first()
|
|
17
|
+
notification = Notification.objects.create(
|
|
18
|
+
user=user,
|
|
19
|
+
type=NotificationTypeChoices.APP,
|
|
20
|
+
title=kwargs.pop('title', 'Test App Notification'),
|
|
21
|
+
body=kwargs.pop('body', 'This is a test app notification.'),
|
|
22
|
+
url=kwargs.pop('url', 'https://example.com'),
|
|
23
|
+
status=kwargs.pop('status', NotificationStatusChoices.SENT),
|
|
24
|
+
priority=kwargs.pop('priority', 'low'),
|
|
25
|
+
sent_datetime=kwargs.pop('sent_datetime', now()),
|
|
26
|
+
)
|
|
27
|
+
kwargs['notification'] = notification
|
|
28
|
+
|
|
29
|
+
data = {
|
|
30
|
+
'template': 'django_spire/notification/app/item/notification_item.html',
|
|
31
|
+
'context_data': {},
|
|
32
|
+
}
|
|
33
|
+
data.update(kwargs)
|
|
34
|
+
return AppNotification.objects.create(**data)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django.apps import apps
|
|
4
|
+
|
|
5
|
+
from django_spire.core.tests.test_cases import BaseTestCase
|
|
6
|
+
from django_spire.notification.app.apps import NotificationAppConfig
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class NotificationAppConfigTests(BaseTestCase):
|
|
10
|
+
def test_app_name(self):
|
|
11
|
+
assert NotificationAppConfig.name == 'django_spire.notification.app'
|
|
12
|
+
|
|
13
|
+
def test_app_label(self):
|
|
14
|
+
assert NotificationAppConfig.label == 'django_spire_notification_app'
|
|
15
|
+
|
|
16
|
+
def test_default_auto_field(self):
|
|
17
|
+
assert NotificationAppConfig.default_auto_field == 'django.db.models.BigAutoField'
|
|
18
|
+
|
|
19
|
+
def test_required_apps(self):
|
|
20
|
+
expected = ('django_spire_core', 'django_spire_notification')
|
|
21
|
+
assert expected == NotificationAppConfig.REQUIRED_APPS
|
|
22
|
+
|
|
23
|
+
def test_app_is_installed(self):
|
|
24
|
+
assert apps.is_installed('django_spire.notification.app')
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django.utils.timezone import now, timedelta
|
|
4
|
+
|
|
5
|
+
from django_spire.core.tests.test_cases import BaseTestCase
|
|
6
|
+
from django_spire.notification.app.tests.factories import create_test_app_notification
|
|
7
|
+
from django_spire.notification.models import Notification
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AppNotificationModelTests(BaseTestCase):
|
|
11
|
+
def setUp(self):
|
|
12
|
+
super().setUp()
|
|
13
|
+
self.app_notification = create_test_app_notification()
|
|
14
|
+
|
|
15
|
+
def test_str(self):
|
|
16
|
+
assert str(self.app_notification) == self.app_notification.notification.title
|
|
17
|
+
|
|
18
|
+
def test_verbose_time_since_delivered_just_now(self):
|
|
19
|
+
self.app_notification.notification.sent_datetime = now()
|
|
20
|
+
self.app_notification.notification.save()
|
|
21
|
+
|
|
22
|
+
assert self.app_notification.verbose_time_since_delivered == 'just now'
|
|
23
|
+
|
|
24
|
+
def test_verbose_time_since_delivered_minutes(self):
|
|
25
|
+
self.app_notification.notification.sent_datetime = now() - timedelta(minutes=5)
|
|
26
|
+
self.app_notification.notification.save()
|
|
27
|
+
|
|
28
|
+
assert '5 minutes ago' in self.app_notification.verbose_time_since_delivered
|
|
29
|
+
|
|
30
|
+
def test_verbose_time_since_delivered_hours(self):
|
|
31
|
+
self.app_notification.notification.sent_datetime = now() - timedelta(hours=3)
|
|
32
|
+
self.app_notification.notification.save()
|
|
33
|
+
|
|
34
|
+
assert '3 hours ago' in self.app_notification.verbose_time_since_delivered
|
|
35
|
+
|
|
36
|
+
def test_verbose_time_since_delivered_days(self):
|
|
37
|
+
self.app_notification.notification.sent_datetime = now() - timedelta(days=2)
|
|
38
|
+
self.app_notification.notification.save()
|
|
39
|
+
|
|
40
|
+
assert '2 days ago' in self.app_notification.verbose_time_since_delivered
|
|
41
|
+
|
|
42
|
+
def test_verbose_time_since_delivered_singular_day(self):
|
|
43
|
+
self.app_notification.notification.sent_datetime = now() - timedelta(days=1)
|
|
44
|
+
self.app_notification.notification.save()
|
|
45
|
+
|
|
46
|
+
assert '1 day ago' in self.app_notification.verbose_time_since_delivered
|
|
47
|
+
|
|
48
|
+
def test_as_dict(self):
|
|
49
|
+
result = self.app_notification.as_dict()
|
|
50
|
+
|
|
51
|
+
assert result['id'] == self.app_notification.id
|
|
52
|
+
assert result['title'] == self.app_notification.notification.title
|
|
53
|
+
assert result['body'] == self.app_notification.notification.body
|
|
54
|
+
assert result['url'] == self.app_notification.notification.url
|
|
55
|
+
assert result['priority'] == self.app_notification.notification.priority
|
|
56
|
+
assert 'time_since_delivered' in result
|
|
57
|
+
assert 'context_data' in result
|
|
58
|
+
|
|
59
|
+
def test_as_json(self):
|
|
60
|
+
result = self.app_notification.as_json()
|
|
61
|
+
assert isinstance(result, str)
|
|
62
|
+
assert self.app_notification.notification.title in result
|
|
63
|
+
|
|
64
|
+
def test_notification_relationship(self):
|
|
65
|
+
assert self.app_notification.notification is not None
|
|
66
|
+
assert isinstance(self.app_notification.notification, Notification)
|
|
67
|
+
|
|
68
|
+
def test_default_template(self):
|
|
69
|
+
assert self.app_notification.template == 'django_spire/notification/app/item/notification_item.html'
|
|
70
|
+
|
|
71
|
+
def test_default_context_data(self):
|
|
72
|
+
assert self.app_notification.context_data == {}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from django_spire.auth.user.tests.factories import create_user
|
|
6
|
+
from django_spire.core.tests.test_cases import BaseTestCase
|
|
7
|
+
from django_spire.notification.app.exceptions import AppNotificationError
|
|
8
|
+
from django_spire.notification.app.models import AppNotification
|
|
9
|
+
from django_spire.notification.app.processor import AppNotificationProcessor
|
|
10
|
+
from django_spire.notification.choices import (
|
|
11
|
+
NotificationStatusChoices,
|
|
12
|
+
NotificationTypeChoices,
|
|
13
|
+
)
|
|
14
|
+
from django_spire.notification.exceptions import NotificationError
|
|
15
|
+
from django_spire.notification.models import Notification
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AppNotificationProcessorTests(BaseTestCase):
|
|
19
|
+
def setUp(self):
|
|
20
|
+
super().setUp()
|
|
21
|
+
self.user = create_user(username='test_processor_user')
|
|
22
|
+
self.processor = AppNotificationProcessor()
|
|
23
|
+
|
|
24
|
+
def _create_notification(self, **kwargs) -> Notification:
|
|
25
|
+
data = {
|
|
26
|
+
'user': self.user,
|
|
27
|
+
'type': NotificationTypeChoices.APP,
|
|
28
|
+
'title': 'Test Notification',
|
|
29
|
+
'body': 'Test body',
|
|
30
|
+
'status': NotificationStatusChoices.PENDING,
|
|
31
|
+
}
|
|
32
|
+
data.update(kwargs)
|
|
33
|
+
notification = Notification.objects.create(**data)
|
|
34
|
+
AppNotification.objects.create(notification=notification)
|
|
35
|
+
return notification
|
|
36
|
+
|
|
37
|
+
def test_process_sets_status_to_sent(self):
|
|
38
|
+
notification = self._create_notification()
|
|
39
|
+
|
|
40
|
+
self.processor.process(notification)
|
|
41
|
+
|
|
42
|
+
notification.refresh_from_db()
|
|
43
|
+
assert notification.status == NotificationStatusChoices.SENT
|
|
44
|
+
|
|
45
|
+
def test_process_sets_sent_datetime(self):
|
|
46
|
+
notification = self._create_notification()
|
|
47
|
+
|
|
48
|
+
self.processor.process(notification)
|
|
49
|
+
|
|
50
|
+
notification.refresh_from_db()
|
|
51
|
+
assert notification.sent_datetime is not None
|
|
52
|
+
|
|
53
|
+
def test_process_raises_error_for_wrong_type(self):
|
|
54
|
+
notification = self._create_notification(type=NotificationTypeChoices.EMAIL)
|
|
55
|
+
|
|
56
|
+
with pytest.raises(NotificationError):
|
|
57
|
+
self.processor.process(notification)
|
|
58
|
+
|
|
59
|
+
def test_process_raises_error_for_missing_user(self):
|
|
60
|
+
notification = Notification.objects.create(
|
|
61
|
+
user=None,
|
|
62
|
+
type=NotificationTypeChoices.APP,
|
|
63
|
+
title='Test',
|
|
64
|
+
body='Test',
|
|
65
|
+
status=NotificationStatusChoices.PENDING,
|
|
66
|
+
)
|
|
67
|
+
AppNotification.objects.create(notification=notification)
|
|
68
|
+
|
|
69
|
+
with pytest.raises(AppNotificationError):
|
|
70
|
+
self.processor.process(notification)
|
|
71
|
+
|
|
72
|
+
def test_process_list(self):
|
|
73
|
+
notifications = [self._create_notification() for _ in range(3)]
|
|
74
|
+
|
|
75
|
+
self.processor.process_list(notifications)
|
|
76
|
+
|
|
77
|
+
for notification in notifications:
|
|
78
|
+
notification.refresh_from_db()
|
|
79
|
+
assert notification.status == NotificationStatusChoices.SENT
|
|
80
|
+
assert notification.sent_datetime is not None
|
|
81
|
+
|
|
82
|
+
def test_process_list_raises_error_for_wrong_type(self):
|
|
83
|
+
notifications = [
|
|
84
|
+
self._create_notification(),
|
|
85
|
+
self._create_notification(type=NotificationTypeChoices.EMAIL),
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
with pytest.raises(NotificationError):
|
|
89
|
+
self.processor.process_list(notifications)
|
|
90
|
+
|
|
91
|
+
def test_process_ready(self):
|
|
92
|
+
pending_notification = self._create_notification()
|
|
93
|
+
|
|
94
|
+
sent_notification = self._create_notification(
|
|
95
|
+
status=NotificationStatusChoices.SENT
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
self.processor.process_ready()
|
|
99
|
+
|
|
100
|
+
pending_notification.refresh_from_db()
|
|
101
|
+
assert pending_notification.status == NotificationStatusChoices.SENT
|
|
102
|
+
|
|
103
|
+
def test_process_errored(self):
|
|
104
|
+
errored_notification = self._create_notification(
|
|
105
|
+
status=NotificationStatusChoices.ERRORED
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
self.processor.process_errored()
|
|
109
|
+
|
|
110
|
+
errored_notification.refresh_from_db()
|
|
111
|
+
assert errored_notification.status == NotificationStatusChoices.SENT
|