django-spire 0.23.6__py3-none-any.whl → 0.23.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_spire/ai/admin.py +11 -11
- django_spire/ai/chat/apps.py +1 -0
- django_spire/ai/chat/templates/django_spire/ai/chat/widget/dialog_widget.html +1 -1
- django_spire/ai/chat/tests/factories.py +15 -0
- django_spire/ai/chat/tests/test_controller.py +45 -0
- django_spire/ai/chat/tests/test_models.py +301 -0
- django_spire/ai/chat/tests/test_prompts.py +48 -0
- django_spire/ai/chat/tests/test_responses.py +208 -0
- django_spire/ai/chat/tests/test_router/test_base_chat_router.py +66 -6
- django_spire/ai/chat/tests/test_router/test_chat_workflow.py +73 -3
- django_spire/ai/chat/tests/test_router/test_integration.py +86 -6
- django_spire/ai/chat/tests/test_router/test_intent_decoder.py +93 -1
- django_spire/ai/chat/tests/test_router/test_message_intel.py +60 -1
- django_spire/ai/chat/tests/test_router/test_spire_chat_router.py +110 -0
- django_spire/ai/chat/tests/test_urls/test_json_urls.py +202 -1
- django_spire/ai/context/tests/__init__.py +0 -0
- django_spire/ai/context/tests/test_context.py +188 -0
- django_spire/ai/decorators.py +7 -6
- django_spire/ai/prompt/tests/test_bots.py +100 -10
- django_spire/ai/prompt/tests/test_prompt_intel.py +83 -0
- django_spire/ai/prompt/tests/test_prompt_tuning.py +126 -0
- django_spire/ai/sms/decorators.py +8 -2
- django_spire/ai/sms/tests/test_sms.py +240 -16
- django_spire/ai/sms/tests/test_sms_intel.py +42 -0
- django_spire/ai/sms/tests/test_webhook.py +155 -7
- django_spire/ai/sms/views.py +23 -24
- django_spire/ai/tests/test_ai.py +131 -7
- django_spire/auth/apps.py +4 -2
- django_spire/auth/controller/controller.py +36 -23
- django_spire/auth/controller/exceptions.py +9 -0
- django_spire/auth/group/admin.py +1 -0
- django_spire/auth/group/apps.py +2 -0
- django_spire/auth/group/factories.py +17 -8
- django_spire/auth/group/forms.py +7 -0
- django_spire/auth/group/tests/test_factories.py +146 -0
- django_spire/auth/group/tests/test_forms.py +282 -0
- django_spire/auth/group/tests/test_models.py +192 -0
- django_spire/auth/group/tests/test_querysets.py +98 -0
- django_spire/auth/group/tests/test_utils.py +341 -0
- django_spire/auth/group/tests/test_views.py +377 -0
- django_spire/auth/group/urls/__init__.py +3 -1
- django_spire/auth/group/urls/form_urls.py +2 -0
- django_spire/auth/group/urls/json_urls.py +3 -0
- django_spire/auth/group/urls/page_urls.py +2 -0
- django_spire/auth/group/utils.py +6 -2
- django_spire/auth/group/views/form_views.py +6 -3
- django_spire/auth/group/views/json_views.py +6 -2
- django_spire/auth/mfa/admin.py +2 -0
- django_spire/auth/mfa/apps.py +2 -0
- django_spire/auth/mfa/forms.py +1 -0
- django_spire/auth/mfa/querysets.py +9 -2
- django_spire/auth/mfa/tests/test_models.py +233 -0
- django_spire/auth/mfa/tests/test_utils.py +106 -0
- django_spire/auth/mfa/urls/__init__.py +2 -0
- django_spire/auth/mfa/urls/page_urls.py +2 -0
- django_spire/auth/mfa/urls/redirect_urls.py +2 -0
- django_spire/auth/mfa/views/page_views.py +2 -1
- django_spire/auth/permissions/consts.py +2 -2
- django_spire/auth/permissions/decorators.py +8 -8
- django_spire/auth/permissions/permissions.py +28 -35
- django_spire/auth/permissions/tests/test_decorators.py +333 -0
- django_spire/auth/permissions/tests/test_permissions.py +337 -0
- django_spire/auth/permissions/tests/test_tools.py +305 -0
- django_spire/auth/permissions/tools.py +21 -15
- django_spire/auth/seeding/seed.py +3 -0
- django_spire/auth/seeding/seeder.py +2 -0
- django_spire/auth/tests/test_controller.py +323 -0
- django_spire/auth/tests/test_url_endpoints.py +9 -9
- django_spire/auth/tests/test_views.py +406 -0
- django_spire/auth/urls/admin_urls.py +2 -0
- django_spire/auth/urls/redirect_urls.py +2 -0
- django_spire/auth/user/apps.py +2 -0
- django_spire/auth/user/forms.py +9 -0
- django_spire/auth/user/models.py +1 -1
- django_spire/auth/user/services/services.py +1 -0
- django_spire/auth/user/tests/factories.py +14 -13
- django_spire/auth/user/tests/test_factories.py +166 -2
- django_spire/auth/user/tests/test_forms.py +573 -0
- django_spire/auth/user/tests/test_models.py +257 -0
- django_spire/auth/user/tests/test_services.py +200 -0
- django_spire/auth/user/tests/test_tools.py +153 -0
- django_spire/auth/user/tests/test_user_factories.py +139 -0
- django_spire/auth/user/tests/test_views.py +363 -0
- django_spire/auth/user/tools.py +7 -1
- django_spire/auth/user/urls/form_urls.py +3 -0
- django_spire/auth/user/urls/page_urls.py +3 -0
- django_spire/auth/user/views/form_views.py +19 -10
- django_spire/auth/user/views/page_views.py +8 -2
- django_spire/auth/views/redirect_views.py +14 -9
- django_spire/comment/admin.py +2 -0
- django_spire/comment/apps.py +2 -0
- django_spire/comment/templatetags/comment_tags.py +1 -0
- django_spire/comment/tests/test_forms.py +27 -0
- django_spire/comment/tests/test_models.py +215 -0
- django_spire/comment/tests/test_querysets.py +101 -0
- django_spire/comment/tests/test_utils.py +90 -0
- django_spire/comment/urls.py +2 -0
- django_spire/comment/utils.py +22 -13
- django_spire/comment/views.py +1 -1
- django_spire/conf.py +8 -6
- django_spire/consts.py +1 -1
- django_spire/contrib/breadcrumb/apps.py +2 -0
- django_spire/contrib/breadcrumb/breadcrumbs.py +18 -18
- django_spire/contrib/breadcrumb/tests/test_breadcrumbs.py +198 -0
- django_spire/contrib/constructor/__init__.py +3 -3
- django_spire/contrib/constructor/constructor.py +15 -15
- django_spire/contrib/constructor/django_model_constructor.py +5 -4
- django_spire/contrib/constructor/exceptions.py +5 -3
- django_spire/contrib/constructor/tests/__init__.py +0 -0
- django_spire/contrib/constructor/tests/test_constructor.py +193 -0
- django_spire/contrib/form/tests/__init__.py +0 -0
- django_spire/contrib/form/tests/test_forms.py +203 -0
- django_spire/contrib/generic_views/modal_views.py +2 -1
- django_spire/contrib/generic_views/portal_views.py +20 -19
- django_spire/contrib/generic_views/tests/__init__.py +0 -0
- django_spire/contrib/generic_views/tests/test_views.py +459 -0
- django_spire/contrib/help/apps.py +2 -0
- django_spire/contrib/help/templatetags/help.py +1 -0
- django_spire/contrib/help/tests/__init__.py +0 -0
- django_spire/contrib/help/tests/test_templatetags.py +100 -0
- django_spire/contrib/options/mixins.py +6 -5
- django_spire/contrib/options/tests/factories.py +5 -1
- django_spire/contrib/options/tests/test_options.py +234 -0
- django_spire/contrib/ordering/exceptions.py +7 -3
- django_spire/contrib/ordering/mixins.py +2 -0
- django_spire/contrib/ordering/querysets.py +3 -1
- django_spire/contrib/ordering/services/processor_service.py +8 -4
- django_spire/contrib/ordering/services/service.py +1 -2
- django_spire/contrib/ordering/tests/__init__.py +0 -0
- django_spire/contrib/ordering/tests/test_ordering.py +165 -0
- django_spire/contrib/ordering/validators.py +6 -6
- django_spire/contrib/pagination/templatetags/pagination_tags.py +12 -5
- django_spire/contrib/pagination/tests/__init__.py +0 -0
- django_spire/contrib/pagination/tests/test_pagination.py +179 -0
- django_spire/contrib/performance/decorators.py +16 -6
- django_spire/contrib/performance/tests/__init__.py +0 -0
- django_spire/contrib/performance/tests/test_performance.py +107 -0
- django_spire/contrib/progress/__init__.py +1 -3
- django_spire/contrib/progress/static/django_spire/js/contrib/progress/progress.js +38 -82
- django_spire/contrib/queryset/enums.py +3 -1
- django_spire/contrib/queryset/filter_tools.py +10 -5
- django_spire/contrib/queryset/mixins.py +16 -16
- django_spire/contrib/queryset/tests/__init__.py +0 -0
- django_spire/contrib/queryset/tests/test_queryset.py +137 -0
- django_spire/contrib/seeding/field/base.py +13 -7
- django_spire/contrib/seeding/field/callable.py +8 -1
- django_spire/contrib/seeding/field/cleaners.py +5 -5
- django_spire/contrib/seeding/field/custom.py +20 -10
- django_spire/contrib/seeding/field/django/seeder.py +8 -6
- django_spire/contrib/seeding/field/enums.py +7 -5
- django_spire/contrib/seeding/field/override.py +16 -6
- django_spire/contrib/seeding/field/static.py +9 -2
- django_spire/contrib/seeding/field/tests/test_base.py +18 -14
- django_spire/contrib/seeding/field/tests/test_callable.py +13 -9
- django_spire/contrib/seeding/field/tests/test_cleaners.py +51 -38
- django_spire/contrib/seeding/field/tests/test_static.py +13 -9
- django_spire/contrib/seeding/intelligence/bots/seeder_generator_bot.py +2 -0
- django_spire/contrib/seeding/intelligence/intel.py +5 -1
- django_spire/contrib/seeding/intelligence/prompts/factory.py +6 -1
- django_spire/contrib/seeding/intelligence/prompts/foreign_key_selection_prompt.py +6 -1
- django_spire/contrib/seeding/intelligence/prompts/generate_django_model_seeder_prompts.py +2 -0
- django_spire/contrib/seeding/intelligence/prompts/generic_relationship_selection_prompt.py +7 -1
- django_spire/contrib/seeding/intelligence/prompts/hierarchical_selection_prompt.py +6 -2
- django_spire/contrib/seeding/intelligence/prompts/model_field_choices_prompt.py +8 -2
- django_spire/contrib/seeding/intelligence/prompts/negation_prompt.py +2 -0
- django_spire/contrib/seeding/intelligence/prompts/objective_prompt.py +6 -1
- django_spire/contrib/seeding/management/commands/seeding.py +9 -3
- django_spire/contrib/seeding/management/example.py +2 -0
- django_spire/contrib/seeding/model/base.py +16 -7
- django_spire/contrib/seeding/model/config.py +31 -15
- django_spire/contrib/seeding/model/django/config.py +13 -13
- django_spire/contrib/seeding/model/django/seeder.py +4 -4
- django_spire/contrib/seeding/model/django/tests/test_seeder.py +34 -23
- django_spire/contrib/seeding/model/enums.py +2 -0
- django_spire/contrib/seeding/tests/test_config.py +71 -0
- django_spire/contrib/seeding/tests/test_custom.py +35 -0
- django_spire/contrib/seeding/tests/test_enums.py +40 -0
- django_spire/contrib/seeding/tests/test_intel.py +32 -0
- django_spire/contrib/seeding/tests/test_override.py +63 -0
- django_spire/contrib/service/__init__.py +2 -2
- django_spire/contrib/service/django_model_service.py +16 -15
- django_spire/contrib/service/exceptions.py +5 -3
- django_spire/contrib/service/tests/__init__.py +0 -0
- django_spire/contrib/service/tests/test_service.py +153 -0
- django_spire/contrib/session/apps.py +2 -0
- django_spire/contrib/session/controller.py +48 -42
- django_spire/contrib/session/templatetags/session_tags.py +11 -2
- django_spire/contrib/session/tests/test_session_controller.py +117 -53
- django_spire/contrib/tests/__init__.py +0 -0
- django_spire/contrib/tests/test_utils.py +37 -0
- django_spire/contrib/utils.py +4 -1
- django_spire/core/apps.py +2 -0
- django_spire/core/converters/tests/test_to_data.py +353 -0
- django_spire/core/converters/tests/test_to_enums.py +61 -41
- django_spire/core/converters/tests/test_to_pydantic.py +138 -109
- django_spire/core/converters/to_data.py +29 -10
- django_spire/core/converters/to_enums.py +4 -2
- django_spire/core/converters/to_pydantic.py +22 -22
- django_spire/core/decorators.py +19 -6
- django_spire/core/forms/widgets.py +4 -0
- django_spire/core/maps.py +3 -1
- django_spire/core/middleware/maintenance.py +3 -3
- django_spire/core/middleware.py +8 -6
- django_spire/core/redirect/__init__.py +5 -0
- django_spire/core/redirect/generic_redirect.py +1 -2
- django_spire/core/redirect/tests/__init__.py +0 -0
- django_spire/core/redirect/tests/test_generic_redirect.py +34 -0
- django_spire/core/{tests/tests_redirect.py → redirect/tests/test_safe_redirect.py} +55 -81
- django_spire/core/shortcuts.py +3 -3
- django_spire/core/static/django_spire/css/app-layout.css +1 -1
- django_spire/core/static/django_spire/css/app-navigation.css +3 -3
- django_spire/core/static/django_spire/css/bootstrap-override.css +4 -0
- django_spire/core/tag/admin.py +12 -0
- django_spire/core/tag/intelligence/tag_set_bot.py +2 -0
- django_spire/core/tag/mixins.py +2 -0
- django_spire/core/tag/models.py +2 -0
- django_spire/core/tag/querysets.py +2 -0
- django_spire/core/tag/service/tag_service.py +6 -3
- django_spire/core/tag/tests/test_intelligence.py +9 -9
- django_spire/core/tag/tests/test_tags.py +44 -54
- django_spire/core/tag/tests/test_tools.py +191 -0
- django_spire/core/tag/tools.py +3 -0
- django_spire/core/templates/django_spire/card/card.html +5 -2
- django_spire/core/templates/django_spire/card/title_card.html +9 -4
- django_spire/core/templates/django_spire/infinite_scroll/base.html +1 -0
- django_spire/core/templates/django_spire/navigation/side_navigation.html +19 -24
- django_spire/core/templates/django_spire/page/full_page.html +46 -16
- django_spire/core/templates/django_spire/table/base.html +4 -2
- django_spire/core/templatetags/json.py +6 -2
- django_spire/core/templatetags/message.py +13 -8
- django_spire/core/templatetags/string_formating.py +8 -5
- django_spire/core/templatetags/tests/__init__.py +0 -0
- django_spire/core/templatetags/tests/test_templatetags.py +427 -0
- django_spire/core/templatetags/variable_types.py +17 -9
- django_spire/core/tests/test_cases.py +1 -1
- django_spire/core/tests/test_conf.py +43 -0
- django_spire/core/tests/test_consts.py +28 -0
- django_spire/core/tests/test_context_processors.py +93 -0
- django_spire/core/tests/test_decorators.py +95 -0
- django_spire/core/tests/test_django_spire_utils.py +56 -0
- django_spire/core/tests/test_exceptions.py +37 -0
- django_spire/core/tests/test_models.py +54 -0
- django_spire/core/tests/test_settings.py +45 -0
- django_spire/core/tests/test_shortcuts.py +74 -0
- django_spire/core/tests/test_urls.py +16 -0
- django_spire/core/tests/test_utils.py +58 -0
- django_spire/core/urls.py +4 -1
- django_spire/core/utils.py +12 -8
- django_spire/exceptions.py +16 -1
- django_spire/file/admin.py +4 -2
- django_spire/file/apps.py +8 -10
- django_spire/file/fields.py +7 -7
- django_spire/file/forms.py +1 -1
- django_spire/file/interfaces.py +15 -15
- django_spire/file/mixins.py +1 -4
- django_spire/file/models.py +3 -5
- django_spire/file/tests/factories.py +59 -0
- django_spire/file/tests/test_admin.py +69 -0
- django_spire/file/tests/test_apps.py +24 -0
- django_spire/file/tests/test_fields.py +114 -0
- django_spire/file/tests/test_forms.py +20 -0
- django_spire/file/tests/test_interfaces.py +183 -0
- django_spire/file/tests/test_models.py +82 -0
- django_spire/file/tests/test_querysets.py +102 -0
- django_spire/file/tests/test_utils.py +32 -0
- django_spire/file/tests/test_views.py +145 -0
- django_spire/file/tests/test_widgets.py +82 -0
- django_spire/file/tools.py +8 -2
- django_spire/file/views.py +7 -3
- django_spire/file/widgets.py +12 -12
- django_spire/help_desk/admin.py +15 -0
- django_spire/help_desk/apps.py +2 -0
- django_spire/help_desk/auth/controller.py +2 -0
- django_spire/help_desk/choices.py +2 -0
- django_spire/help_desk/enums.py +2 -0
- django_spire/help_desk/exceptions.py +31 -3
- django_spire/help_desk/forms.py +2 -0
- django_spire/help_desk/models.py +2 -0
- django_spire/help_desk/querysets.py +4 -1
- django_spire/help_desk/services/notification_service.py +26 -27
- django_spire/help_desk/services/service.py +2 -3
- django_spire/help_desk/tests/factories.py +8 -3
- django_spire/help_desk/tests/test_admin.py +41 -0
- django_spire/help_desk/tests/test_apps.py +41 -0
- django_spire/help_desk/tests/test_choices.py +50 -0
- django_spire/help_desk/tests/test_controller.py +87 -0
- django_spire/help_desk/tests/test_enums.py +18 -0
- django_spire/help_desk/tests/test_exceptions.py +37 -0
- django_spire/help_desk/tests/test_forms.py +89 -0
- django_spire/help_desk/tests/test_models.py +59 -0
- django_spire/help_desk/tests/test_querysets.py +38 -0
- django_spire/help_desk/tests/test_services/test_notification_service.py +15 -8
- django_spire/help_desk/tests/test_services/test_service.py +92 -0
- django_spire/help_desk/tests/test_urls/test_form_urls.py +6 -6
- django_spire/help_desk/tests/test_urls/test_page_urls.py +8 -9
- django_spire/help_desk/tests/test_views/test_form_views.py +46 -19
- django_spire/help_desk/tests/test_views/test_page_views.py +32 -9
- django_spire/help_desk/urls/__init__.py +4 -1
- django_spire/help_desk/urls/form_urls.py +3 -0
- django_spire/help_desk/urls/page_urls.py +3 -0
- django_spire/help_desk/views/form_views.py +13 -5
- django_spire/help_desk/views/page_views.py +11 -3
- django_spire/history/activity/admin.py +2 -0
- django_spire/history/activity/apps.py +3 -1
- django_spire/history/activity/mixins.py +13 -7
- django_spire/history/activity/models.py +6 -5
- django_spire/history/activity/querysets.py +2 -0
- django_spire/history/activity/tests/__init__.py +0 -0
- django_spire/history/activity/tests/test_activity.py +176 -0
- django_spire/history/admin.py +9 -2
- django_spire/history/choices.py +3 -0
- django_spire/history/models.py +5 -5
- django_spire/history/tests/test_admin.py +93 -0
- django_spire/history/tests/test_history.py +101 -0
- django_spire/history/tests/test_mixins.py +84 -0
- django_spire/history/viewed/admin.py +3 -1
- django_spire/history/viewed/apps.py +3 -1
- django_spire/history/viewed/models.py +2 -0
- django_spire/history/viewed/tests/__init__.py +0 -0
- django_spire/history/viewed/tests/test_viewed.py +46 -0
- django_spire/knowledge/auth/tests/__init__.py +0 -0
- django_spire/knowledge/auth/tests/test_controller.py +116 -0
- django_spire/knowledge/collection/admin.py +5 -1
- django_spire/knowledge/collection/models.py +3 -1
- django_spire/knowledge/collection/seeding/seed.py +1 -0
- django_spire/knowledge/collection/services/factory_service.py +10 -11
- django_spire/knowledge/collection/services/ordering_service.py +1 -2
- django_spire/knowledge/collection/services/service.py +5 -10
- django_spire/knowledge/collection/services/tag_service.py +5 -2
- django_spire/knowledge/collection/tests/factories.py +28 -1
- django_spire/knowledge/collection/tests/test_models.py +48 -0
- django_spire/knowledge/collection/tests/test_querysets.py +93 -0
- django_spire/knowledge/collection/tests/test_services/test_factory_service.py +100 -0
- django_spire/knowledge/collection/tests/test_services/test_services.py +160 -0
- django_spire/knowledge/collection/tests/test_urls/test_form_urls.py +21 -3
- django_spire/knowledge/collection/tests/test_urls/test_json_urls.py +39 -1
- django_spire/knowledge/collection/tests/test_urls/test_page_urls.py +12 -4
- django_spire/knowledge/collection/urls/__init__.py +3 -0
- django_spire/knowledge/collection/urls/form_urls.py +2 -0
- django_spire/knowledge/collection/urls/json_urls.py +2 -0
- django_spire/knowledge/collection/urls/page_urls.py +2 -0
- django_spire/knowledge/collection/views/form_views.py +4 -4
- django_spire/knowledge/collection/views/json_views.py +5 -1
- django_spire/knowledge/collection/views/page_views.py +5 -2
- django_spire/knowledge/entry/admin.py +7 -1
- django_spire/knowledge/entry/forms.py +2 -0
- django_spire/knowledge/entry/models.py +2 -0
- django_spire/knowledge/entry/seeding/seed.py +3 -0
- django_spire/knowledge/entry/services/automation_service.py +5 -4
- django_spire/knowledge/entry/services/factory_service.py +7 -5
- django_spire/knowledge/entry/services/service.py +4 -7
- django_spire/knowledge/entry/services/tag_service.py +0 -1
- django_spire/knowledge/entry/services/tool_service.py +1 -0
- django_spire/knowledge/entry/services/transformation_services.py +1 -5
- django_spire/knowledge/entry/tests/factories.py +1 -2
- django_spire/knowledge/entry/tests/test_factory_service.py +20 -0
- django_spire/knowledge/entry/tests/test_models.py +41 -0
- django_spire/knowledge/entry/tests/test_querysets.py +71 -0
- django_spire/knowledge/entry/tests/test_services.py +94 -0
- django_spire/knowledge/entry/tests/test_urls/test_form_urls.py +9 -14
- django_spire/knowledge/entry/tests/test_urls/test_json_urls.py +48 -5
- django_spire/knowledge/entry/tests/test_urls/test_page_urls.py +6 -8
- django_spire/knowledge/entry/tests/test_urls/test_template_urls.py +40 -0
- django_spire/knowledge/entry/urls/form_urls.py +2 -0
- django_spire/knowledge/entry/urls/json_urls.py +2 -0
- django_spire/knowledge/entry/urls/page_urls.py +2 -0
- django_spire/knowledge/entry/urls/template_urls.py +2 -0
- django_spire/knowledge/entry/version/block/choices.py +2 -0
- django_spire/knowledge/entry/version/block/data/data.py +1 -0
- django_spire/knowledge/entry/version/block/data/list/data.py +8 -13
- django_spire/knowledge/entry/version/block/data/list/maps.py +3 -0
- django_spire/knowledge/entry/version/block/data/list/meta.py +1 -2
- django_spire/knowledge/entry/version/block/data/list/tests/__init__.py +0 -0
- django_spire/knowledge/entry/version/block/data/list/tests/test_maps.py +32 -0
- django_spire/knowledge/entry/version/block/data/list/tests/test_meta.py +58 -0
- django_spire/knowledge/entry/version/block/data/maps.py +3 -6
- django_spire/knowledge/entry/version/block/models.py +7 -5
- django_spire/knowledge/entry/version/block/seeding/constants.py +5 -4
- django_spire/knowledge/entry/version/block/services/service.py +2 -3
- django_spire/knowledge/entry/version/block/tests/factories.py +4 -10
- django_spire/knowledge/entry/version/block/tests/test_choices.py +56 -0
- django_spire/knowledge/entry/version/block/tests/test_data.py +90 -0
- django_spire/knowledge/entry/version/block/tests/test_maps.py +37 -0
- django_spire/knowledge/entry/version/block/tests/test_models.py +55 -0
- django_spire/knowledge/entry/version/block/tests/test_querysets.py +35 -0
- django_spire/knowledge/entry/version/block/tests/test_services.py +65 -0
- django_spire/knowledge/entry/version/choices.py +2 -0
- django_spire/knowledge/entry/version/converters/converter.py +1 -1
- django_spire/knowledge/entry/version/converters/docx_converter.py +4 -7
- django_spire/knowledge/entry/version/converters/markdown_converter.py +20 -20
- django_spire/knowledge/entry/version/maps.py +4 -5
- django_spire/knowledge/entry/version/querysets.py +1 -1
- django_spire/knowledge/entry/version/seeding/seeder.py +1 -2
- django_spire/knowledge/entry/version/services/processor_service.py +5 -4
- django_spire/knowledge/entry/version/services/service.py +1 -2
- django_spire/knowledge/entry/version/tests/factories.py +2 -2
- django_spire/knowledge/entry/version/tests/test_choices.py +18 -0
- django_spire/knowledge/entry/version/tests/test_converters/test_docx_converter.py +56 -8
- django_spire/knowledge/entry/version/tests/test_converters/test_markdown_converter.py +78 -0
- django_spire/knowledge/entry/version/tests/test_maps.py +58 -0
- django_spire/knowledge/entry/version/tests/test_models.py +23 -0
- django_spire/knowledge/entry/version/tests/test_querysets.py +26 -0
- django_spire/knowledge/entry/version/tests/test_services.py +62 -0
- django_spire/knowledge/entry/version/tests/test_urls/test_json_urls.py +27 -8
- django_spire/knowledge/entry/version/tests/test_urls/test_page_urls.py +15 -8
- django_spire/knowledge/entry/version/tests/test_urls/test_redirect_urls.py +38 -0
- django_spire/knowledge/entry/version/urls/__init__.py +3 -0
- django_spire/knowledge/entry/version/urls/json_urls.py +2 -1
- django_spire/knowledge/entry/version/urls/page_urls.py +2 -0
- django_spire/knowledge/entry/version/urls/redirect_urls.py +2 -0
- django_spire/knowledge/entry/version/views/json_views.py +5 -1
- django_spire/knowledge/entry/version/views/page_views.py +10 -3
- django_spire/knowledge/entry/version/views/redirect_views.py +5 -1
- django_spire/knowledge/entry/views/form_views.py +16 -8
- django_spire/knowledge/entry/views/json_views.py +3 -1
- django_spire/knowledge/entry/views/page_views.py +8 -2
- django_spire/knowledge/entry/views/template_views.py +7 -1
- django_spire/knowledge/exceptions.py +2 -1
- django_spire/knowledge/intelligence/intel/answer_intel.py +2 -1
- django_spire/knowledge/intelligence/intel/entry_intel.py +0 -1
- django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +4 -5
- django_spire/knowledge/models.py +1 -2
- django_spire/knowledge/tests/__init__.py +0 -0
- django_spire/knowledge/tests/test_templatetags.py +40 -0
- django_spire/knowledge/tests/test_urls/__init__.py +0 -0
- django_spire/knowledge/tests/test_urls/test_page_urls.py +24 -0
- django_spire/knowledge/urls/__init__.py +2 -0
- django_spire/knowledge/urls/page_urls.py +2 -0
- django_spire/knowledge/views/page_views.py +8 -3
- django_spire/notification/admin.py +3 -1
- django_spire/notification/app/admin.py +2 -0
- django_spire/notification/app/apps.py +3 -1
- django_spire/notification/app/exceptions.py +9 -2
- django_spire/notification/app/models.py +8 -4
- django_spire/notification/app/processor.py +22 -26
- django_spire/notification/app/querysets.py +2 -0
- django_spire/notification/app/tests/__init__.py +0 -0
- django_spire/notification/app/tests/factories.py +34 -0
- django_spire/notification/app/tests/test_apps.py +24 -0
- django_spire/notification/app/tests/test_models.py +72 -0
- django_spire/notification/app/tests/test_processor.py +111 -0
- django_spire/notification/app/tests/test_querysets.py +90 -0
- django_spire/notification/app/tests/test_views/__init__.py +0 -0
- django_spire/notification/app/tests/test_views/test_json_views.py +48 -0
- django_spire/notification/app/tests/test_views/test_page_views.py +19 -0
- django_spire/notification/app/urls/__init__.py +3 -1
- django_spire/notification/app/urls/json_urls.py +6 -4
- django_spire/notification/app/urls/page_urls.py +4 -3
- django_spire/notification/app/urls/template_urls.py +4 -2
- django_spire/notification/apps.py +4 -1
- django_spire/notification/email/admin.py +5 -1
- django_spire/notification/email/apps.py +3 -1
- django_spire/notification/email/exceptions.py +4 -2
- django_spire/notification/email/helper.py +5 -3
- django_spire/notification/email/models.py +4 -0
- django_spire/notification/email/processor.py +19 -15
- django_spire/notification/email/querysets.py +3 -0
- django_spire/notification/email/tests/__init__.py +0 -0
- django_spire/notification/email/tests/factories.py +35 -0
- django_spire/notification/email/tests/test_apps.py +24 -0
- django_spire/notification/email/tests/test_models.py +52 -0
- django_spire/notification/email/tests/test_processor.py +92 -0
- django_spire/notification/email/tests/test_querysets.py +43 -0
- django_spire/notification/exceptions.py +17 -2
- django_spire/notification/managers.py +7 -1
- django_spire/notification/maps.py +4 -1
- django_spire/notification/mixins.py +2 -0
- django_spire/notification/models.py +3 -1
- django_spire/notification/processors/notification.py +12 -5
- django_spire/notification/processors/processor.py +2 -0
- django_spire/notification/processors/tests/__init__.py +0 -0
- django_spire/notification/processors/tests/test_notification.py +106 -0
- django_spire/notification/push/admin.py +10 -1
- django_spire/notification/push/apps.py +3 -1
- django_spire/notification/push/models.py +2 -3
- django_spire/notification/push/tests/__init__.py +0 -0
- django_spire/notification/push/tests/test_apps.py +24 -0
- django_spire/notification/push/tests/test_models.py +28 -0
- django_spire/notification/querysets.py +7 -1
- django_spire/notification/sms/admin.py +2 -0
- django_spire/notification/sms/apps.py +4 -1
- django_spire/notification/sms/automations.py +2 -0
- django_spire/notification/sms/choices.py +2 -0
- django_spire/notification/sms/exceptions.py +19 -5
- django_spire/notification/sms/helper.py +33 -23
- django_spire/notification/sms/models.py +5 -1
- django_spire/notification/sms/processor.py +20 -20
- django_spire/notification/sms/querysets.py +2 -0
- django_spire/notification/sms/tests/factories.py +33 -0
- django_spire/notification/sms/tests/test_apps.py +24 -0
- django_spire/notification/sms/tests/test_automation.py +38 -0
- django_spire/notification/sms/tests/test_choices.py +15 -0
- django_spire/notification/sms/tests/test_consts.py +17 -0
- django_spire/notification/sms/tests/test_exceptions.py +27 -0
- django_spire/notification/sms/tests/test_helper.py +50 -0
- django_spire/notification/sms/tests/test_models.py +81 -0
- django_spire/notification/sms/tests/test_processor.py +107 -0
- django_spire/notification/sms/tests/test_tools.py +25 -11
- django_spire/notification/sms/tools.py +16 -5
- django_spire/notification/sms/urls/__init__.py +3 -1
- django_spire/notification/sms/urls/media_urls.py +2 -0
- django_spire/notification/sms/views/media_views.py +14 -4
- django_spire/notification/tests/__init__.py +0 -0
- django_spire/notification/tests/factories.py +26 -0
- django_spire/notification/tests/test_admin.py +55 -0
- django_spire/notification/tests/test_apps.py +30 -0
- django_spire/notification/tests/test_automation.py +18 -0
- django_spire/notification/tests/test_choices.py +59 -0
- django_spire/notification/tests/test_exceptions.py +58 -0
- django_spire/notification/tests/test_managers.py +100 -0
- django_spire/notification/tests/test_maps.py +31 -0
- django_spire/notification/tests/test_models.py +76 -0
- django_spire/notification/tests/test_querysets.py +184 -0
- django_spire/notification/tests/test_utils.py +23 -0
- django_spire/notification/urls.py +3 -1
- django_spire/notification/utils.py +3 -1
- django_spire/settings.py +3 -0
- django_spire/theme/tests/test_context_processor.py +15 -13
- django_spire/theme/tests/test_enums.py +2 -2
- django_spire/theme/tests/test_filesystem.py +2 -5
- django_spire/theme/tests/test_integration.py +12 -12
- django_spire/theme/tests/test_model.py +40 -38
- django_spire/theme/tests/test_views/test_json_views.py +33 -33
- django_spire/theme/urls/json_urls.py +3 -0
- django_spire/theme/urls/page_urls.py +3 -0
- django_spire/urls.py +19 -15
- django_spire/utils.py +13 -4
- {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/METADATA +1 -1
- {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/RECORD +532 -361
- django_spire/contrib/options/tests/test_unit.py +0 -148
- django_spire/contrib/progress/views.py +0 -64
- django_spire/contrib/seeding/tests/test_seeding.py +0 -25
- django_spire/core/tests/test_templatetags.py +0 -117
- django_spire/core/tests/tests_shortcuts.py +0 -73
- django_spire/history/activity/tests.py +0 -3
- django_spire/history/activity/views.py +0 -3
- django_spire/knowledge/collection/tests/test_services/test_transformation_service.py +0 -71
- django_spire/notification/app/tests.py +0 -3
- {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/WHEEL +0 -0
- {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/licenses/LICENSE.md +0 -0
- {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from django.test import override_settings
|
|
6
|
+
|
|
7
|
+
from django_spire.core.tests.test_cases import BaseTestCase
|
|
8
|
+
from django_spire.file.tests.factories import create_test_file
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
STORAGES_OVERRIDE = {
|
|
12
|
+
'default': {
|
|
13
|
+
'BACKEND': 'django.core.files.storage.FileSystemStorage',
|
|
14
|
+
},
|
|
15
|
+
'staticfiles': {
|
|
16
|
+
'BACKEND': 'django.contrib.staticfiles.storage.StaticFilesStorage',
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@override_settings(STORAGES=STORAGES_OVERRIDE)
|
|
22
|
+
class FileModelTests(BaseTestCase):
|
|
23
|
+
def setUp(self):
|
|
24
|
+
super().setUp()
|
|
25
|
+
|
|
26
|
+
self.file = create_test_file()
|
|
27
|
+
|
|
28
|
+
def test_str(self):
|
|
29
|
+
assert str(self.file) == self.file.name
|
|
30
|
+
|
|
31
|
+
def test_default_is_active(self):
|
|
32
|
+
assert self.file.is_active is True
|
|
33
|
+
|
|
34
|
+
def test_default_is_deleted(self):
|
|
35
|
+
assert self.file.is_deleted is False
|
|
36
|
+
|
|
37
|
+
def test_name_field(self):
|
|
38
|
+
assert self.file.name == 'test_file'
|
|
39
|
+
|
|
40
|
+
def test_type_field(self):
|
|
41
|
+
assert self.file.type == 'pdf'
|
|
42
|
+
|
|
43
|
+
def test_size_field(self):
|
|
44
|
+
assert self.file.size == '1.5 Mb'
|
|
45
|
+
|
|
46
|
+
def test_related_field_default(self):
|
|
47
|
+
assert self.file.related_field is None
|
|
48
|
+
|
|
49
|
+
def test_related_field_set(self):
|
|
50
|
+
file = create_test_file(related_field='abc')
|
|
51
|
+
assert file.related_field == 'abc'
|
|
52
|
+
|
|
53
|
+
def test_content_type_null(self):
|
|
54
|
+
assert self.file.content_type is None
|
|
55
|
+
|
|
56
|
+
def test_object_id_null(self):
|
|
57
|
+
assert self.file.object_id is None
|
|
58
|
+
|
|
59
|
+
def test_created_datetime_auto_set(self):
|
|
60
|
+
assert self.file.created_datetime is not None
|
|
61
|
+
|
|
62
|
+
def test_to_dict(self):
|
|
63
|
+
result = self.file.to_dict()
|
|
64
|
+
|
|
65
|
+
assert result['name'] == self.file.name
|
|
66
|
+
assert result['id'] == self.file.id
|
|
67
|
+
assert 'url' in result
|
|
68
|
+
|
|
69
|
+
def test_to_dict_keys(self):
|
|
70
|
+
result = self.file.to_dict()
|
|
71
|
+
|
|
72
|
+
assert set(result.keys()) == {'name', 'url', 'id'}
|
|
73
|
+
|
|
74
|
+
def test_to_json(self):
|
|
75
|
+
result = self.file.to_json()
|
|
76
|
+
parsed = json.loads(result)
|
|
77
|
+
|
|
78
|
+
assert parsed['name'] == self.file.name
|
|
79
|
+
assert parsed['id'] == self.file.id
|
|
80
|
+
|
|
81
|
+
def test_file_field_url(self):
|
|
82
|
+
assert self.file.file.url is not None
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django.test import override_settings
|
|
4
|
+
|
|
5
|
+
from django_spire.core.tests.test_cases import BaseTestCase
|
|
6
|
+
from django_spire.file.models import File
|
|
7
|
+
from django_spire.file.tests.factories import create_test_file
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
STORAGES_OVERRIDE = {
|
|
11
|
+
'default': {
|
|
12
|
+
'BACKEND': 'django.core.files.storage.FileSystemStorage',
|
|
13
|
+
},
|
|
14
|
+
'staticfiles': {
|
|
15
|
+
'BACKEND': 'django.contrib.staticfiles.storage.StaticFilesStorage',
|
|
16
|
+
},
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@override_settings(STORAGES=STORAGES_OVERRIDE)
|
|
21
|
+
class FileQuerySetTests(BaseTestCase):
|
|
22
|
+
def setUp(self):
|
|
23
|
+
super().setUp()
|
|
24
|
+
|
|
25
|
+
self.file1 = create_test_file(name='File 1')
|
|
26
|
+
self.file2 = create_test_file(name='File 2')
|
|
27
|
+
|
|
28
|
+
def test_active_returns_non_deleted(self):
|
|
29
|
+
result = File.objects.active()
|
|
30
|
+
|
|
31
|
+
assert self.file1 in result
|
|
32
|
+
assert self.file2 in result
|
|
33
|
+
|
|
34
|
+
def test_active_excludes_deleted(self):
|
|
35
|
+
self.file1.is_deleted = True
|
|
36
|
+
self.file1.save()
|
|
37
|
+
|
|
38
|
+
result = File.objects.active()
|
|
39
|
+
|
|
40
|
+
assert self.file1 not in result
|
|
41
|
+
assert self.file2 in result
|
|
42
|
+
|
|
43
|
+
def test_active_excludes_inactive(self):
|
|
44
|
+
self.file1.is_active = False
|
|
45
|
+
self.file1.save()
|
|
46
|
+
|
|
47
|
+
result = File.objects.active()
|
|
48
|
+
|
|
49
|
+
assert self.file1 not in result
|
|
50
|
+
assert self.file2 in result
|
|
51
|
+
|
|
52
|
+
def test_active_excludes_deleted_and_inactive(self):
|
|
53
|
+
self.file1.is_deleted = True
|
|
54
|
+
self.file1.is_active = False
|
|
55
|
+
self.file1.save()
|
|
56
|
+
|
|
57
|
+
result = File.objects.active()
|
|
58
|
+
|
|
59
|
+
assert self.file1 not in result
|
|
60
|
+
assert self.file2 in result
|
|
61
|
+
|
|
62
|
+
def test_related_field_filters_correctly(self):
|
|
63
|
+
self.file1.related_field = 'abc'
|
|
64
|
+
self.file1.save()
|
|
65
|
+
self.file2.related_field = 'xyz'
|
|
66
|
+
self.file2.save()
|
|
67
|
+
|
|
68
|
+
result = File.objects.related_field('abc')
|
|
69
|
+
|
|
70
|
+
assert self.file1 in result
|
|
71
|
+
assert self.file2 not in result
|
|
72
|
+
|
|
73
|
+
def test_related_field_returns_empty_when_no_match(self):
|
|
74
|
+
self.file1.related_field = 'abc'
|
|
75
|
+
self.file1.save()
|
|
76
|
+
|
|
77
|
+
result = File.objects.related_field('xyz')
|
|
78
|
+
|
|
79
|
+
assert result.count() == 0
|
|
80
|
+
|
|
81
|
+
def test_related_field_filters_null_values(self):
|
|
82
|
+
self.file1.related_field = None
|
|
83
|
+
self.file1.save()
|
|
84
|
+
self.file2.related_field = 'abc'
|
|
85
|
+
self.file2.save()
|
|
86
|
+
|
|
87
|
+
result = File.objects.related_field(None)
|
|
88
|
+
|
|
89
|
+
assert self.file1 in result
|
|
90
|
+
assert self.file2 not in result
|
|
91
|
+
|
|
92
|
+
def test_chaining_active_and_related_field(self):
|
|
93
|
+
self.file1.related_field = 'abc'
|
|
94
|
+
self.file1.save()
|
|
95
|
+
self.file2.related_field = 'abc'
|
|
96
|
+
self.file2.is_deleted = True
|
|
97
|
+
self.file2.save()
|
|
98
|
+
|
|
99
|
+
result = File.objects.active().related_field('abc')
|
|
100
|
+
|
|
101
|
+
assert self.file1 in result
|
|
102
|
+
assert self.file2 not in result
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django_spire.core.tests.test_cases import BaseTestCase
|
|
4
|
+
from django_spire.file.utils import random_64_char_token
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Random64CharTokenTests(BaseTestCase):
|
|
8
|
+
def test_returns_string(self):
|
|
9
|
+
result = random_64_char_token()
|
|
10
|
+
|
|
11
|
+
assert isinstance(result, str)
|
|
12
|
+
|
|
13
|
+
def test_returns_64_characters(self):
|
|
14
|
+
result = random_64_char_token()
|
|
15
|
+
|
|
16
|
+
assert len(result) == 64
|
|
17
|
+
|
|
18
|
+
def test_returns_hexadecimal(self):
|
|
19
|
+
result = random_64_char_token()
|
|
20
|
+
|
|
21
|
+
int(result, 16)
|
|
22
|
+
|
|
23
|
+
def test_returns_unique_values(self):
|
|
24
|
+
result1 = random_64_char_token()
|
|
25
|
+
result2 = random_64_char_token()
|
|
26
|
+
|
|
27
|
+
assert result1 != result2
|
|
28
|
+
|
|
29
|
+
def test_returns_lowercase(self):
|
|
30
|
+
result = random_64_char_token()
|
|
31
|
+
|
|
32
|
+
assert result == result.lower()
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from django.test import RequestFactory, override_settings
|
|
6
|
+
|
|
7
|
+
from django_spire.core.tests.test_cases import BaseTestCase
|
|
8
|
+
from django_spire.file.models import File
|
|
9
|
+
from django_spire.file.tests.factories import create_test_in_memory_uploaded_file
|
|
10
|
+
from django_spire.file.views import file_multiple_upload_ajax, file_single_upload_ajax
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
STORAGES_OVERRIDE = {
|
|
14
|
+
'default': {
|
|
15
|
+
'BACKEND': 'django.core.files.storage.FileSystemStorage',
|
|
16
|
+
},
|
|
17
|
+
'staticfiles': {
|
|
18
|
+
'BACKEND': 'django.contrib.staticfiles.storage.StaticFilesStorage',
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@override_settings(STORAGES=STORAGES_OVERRIDE)
|
|
24
|
+
class FileMultipleUploadAjaxViewTests(BaseTestCase):
|
|
25
|
+
def setUp(self):
|
|
26
|
+
super().setUp()
|
|
27
|
+
|
|
28
|
+
self.factory = RequestFactory()
|
|
29
|
+
|
|
30
|
+
def test_post_creates_files(self):
|
|
31
|
+
initial_count = File.objects.count()
|
|
32
|
+
uploaded_file = create_test_in_memory_uploaded_file()
|
|
33
|
+
|
|
34
|
+
request = self.factory.post(
|
|
35
|
+
'/upload/multiple/ajax',
|
|
36
|
+
data={'related_field': ''},
|
|
37
|
+
)
|
|
38
|
+
request.FILES['file'] = uploaded_file
|
|
39
|
+
request.user = self.super_user
|
|
40
|
+
|
|
41
|
+
file_multiple_upload_ajax(request)
|
|
42
|
+
|
|
43
|
+
assert File.objects.count() == initial_count + 1
|
|
44
|
+
|
|
45
|
+
def test_post_returns_json_response(self):
|
|
46
|
+
uploaded_file = create_test_in_memory_uploaded_file()
|
|
47
|
+
|
|
48
|
+
request = self.factory.post(
|
|
49
|
+
'/upload/multiple/ajax',
|
|
50
|
+
data={'related_field': ''},
|
|
51
|
+
)
|
|
52
|
+
request.FILES['file'] = uploaded_file
|
|
53
|
+
request.user = self.super_user
|
|
54
|
+
|
|
55
|
+
response = file_multiple_upload_ajax(request)
|
|
56
|
+
|
|
57
|
+
assert response.status_code == 200
|
|
58
|
+
assert response['Content-Type'] == 'application/json'
|
|
59
|
+
|
|
60
|
+
def test_post_returns_files_in_response(self):
|
|
61
|
+
uploaded_file = create_test_in_memory_uploaded_file(name='test_upload')
|
|
62
|
+
|
|
63
|
+
request = self.factory.post(
|
|
64
|
+
'/upload/multiple/ajax',
|
|
65
|
+
data={'related_field': ''},
|
|
66
|
+
)
|
|
67
|
+
request.FILES['file'] = uploaded_file
|
|
68
|
+
request.user = self.super_user
|
|
69
|
+
|
|
70
|
+
response = file_multiple_upload_ajax(request)
|
|
71
|
+
data = json.loads(response.content)
|
|
72
|
+
|
|
73
|
+
assert 'files' in data
|
|
74
|
+
assert len(data['files']) == 1
|
|
75
|
+
assert data['files'][0]['name'] == 'test_upload'
|
|
76
|
+
|
|
77
|
+
def test_get_returns_none(self):
|
|
78
|
+
request = self.factory.get('/upload/multiple/ajax')
|
|
79
|
+
request.user = self.super_user
|
|
80
|
+
|
|
81
|
+
response = file_multiple_upload_ajax(request)
|
|
82
|
+
|
|
83
|
+
assert response is None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@override_settings(STORAGES=STORAGES_OVERRIDE)
|
|
87
|
+
class FileSingleUploadAjaxViewTests(BaseTestCase):
|
|
88
|
+
def setUp(self):
|
|
89
|
+
super().setUp()
|
|
90
|
+
|
|
91
|
+
self.factory = RequestFactory()
|
|
92
|
+
|
|
93
|
+
def test_post_creates_file(self):
|
|
94
|
+
initial_count = File.objects.count()
|
|
95
|
+
uploaded_file = create_test_in_memory_uploaded_file()
|
|
96
|
+
|
|
97
|
+
request = self.factory.post(
|
|
98
|
+
'/upload/single/ajax',
|
|
99
|
+
data={'related_field': ''},
|
|
100
|
+
)
|
|
101
|
+
request.FILES['file'] = uploaded_file
|
|
102
|
+
request.user = self.super_user
|
|
103
|
+
|
|
104
|
+
file_single_upload_ajax(request)
|
|
105
|
+
|
|
106
|
+
assert File.objects.count() == initial_count + 1
|
|
107
|
+
|
|
108
|
+
def test_post_returns_json_response(self):
|
|
109
|
+
uploaded_file = create_test_in_memory_uploaded_file()
|
|
110
|
+
|
|
111
|
+
request = self.factory.post(
|
|
112
|
+
'/upload/single/ajax',
|
|
113
|
+
data={'related_field': ''},
|
|
114
|
+
)
|
|
115
|
+
request.FILES['file'] = uploaded_file
|
|
116
|
+
request.user = self.super_user
|
|
117
|
+
|
|
118
|
+
response = file_single_upload_ajax(request)
|
|
119
|
+
|
|
120
|
+
assert response.status_code == 200
|
|
121
|
+
assert response['Content-Type'] == 'application/json'
|
|
122
|
+
|
|
123
|
+
def test_post_returns_file_in_response(self):
|
|
124
|
+
uploaded_file = create_test_in_memory_uploaded_file(name='single_upload')
|
|
125
|
+
|
|
126
|
+
request = self.factory.post(
|
|
127
|
+
'/upload/single/ajax',
|
|
128
|
+
data={'related_field': ''},
|
|
129
|
+
)
|
|
130
|
+
request.FILES['file'] = uploaded_file
|
|
131
|
+
request.user = self.super_user
|
|
132
|
+
|
|
133
|
+
response = file_single_upload_ajax(request)
|
|
134
|
+
data = json.loads(response.content)
|
|
135
|
+
|
|
136
|
+
assert 'file' in data
|
|
137
|
+
assert data['file']['name'] == 'single_upload'
|
|
138
|
+
|
|
139
|
+
def test_get_returns_none(self):
|
|
140
|
+
request = self.factory.get('/upload/single/ajax')
|
|
141
|
+
request.user = self.super_user
|
|
142
|
+
|
|
143
|
+
response = file_single_upload_ajax(request)
|
|
144
|
+
|
|
145
|
+
assert response is None
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from django_spire.core.tests.test_cases import BaseTestCase
|
|
6
|
+
from django_spire.file.widgets import MultipleWidget, SingleFileWidget
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class MultipleWidgetTests(BaseTestCase):
|
|
10
|
+
def setUp(self):
|
|
11
|
+
super().setUp()
|
|
12
|
+
|
|
13
|
+
self.widget = MultipleWidget()
|
|
14
|
+
|
|
15
|
+
def test_needs_multipart_form(self):
|
|
16
|
+
assert self.widget.needs_multipart_form is True
|
|
17
|
+
|
|
18
|
+
def test_template_name(self):
|
|
19
|
+
assert self.widget.template_name == 'django_spire/file/widget/multiple_file_widget.html'
|
|
20
|
+
|
|
21
|
+
def test_value_from_datadict(self):
|
|
22
|
+
data = {
|
|
23
|
+
'files_data': json.dumps([{'id': 1, 'name': 'test'}])
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
result = self.widget.value_from_datadict(data, None, 'files')
|
|
27
|
+
|
|
28
|
+
assert result == [{'id': 1, 'name': 'test'}]
|
|
29
|
+
|
|
30
|
+
def test_value_from_datadict_empty(self):
|
|
31
|
+
data = {
|
|
32
|
+
'files_data': json.dumps([])
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
result = self.widget.value_from_datadict(data, None, 'files')
|
|
36
|
+
|
|
37
|
+
assert result == []
|
|
38
|
+
|
|
39
|
+
def test_value_from_datadict_multiple_files(self):
|
|
40
|
+
data = {
|
|
41
|
+
'files_data': json.dumps([
|
|
42
|
+
{'id': 1, 'name': 'file1'},
|
|
43
|
+
{'id': 2, 'name': 'file2'},
|
|
44
|
+
])
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
result = self.widget.value_from_datadict(data, None, 'files')
|
|
48
|
+
|
|
49
|
+
assert len(result) == 2
|
|
50
|
+
assert result[0]['name'] == 'file1'
|
|
51
|
+
assert result[1]['name'] == 'file2'
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class SingleFileWidgetTests(BaseTestCase):
|
|
55
|
+
def setUp(self):
|
|
56
|
+
super().setUp()
|
|
57
|
+
|
|
58
|
+
self.widget = SingleFileWidget()
|
|
59
|
+
|
|
60
|
+
def test_needs_multipart_form(self):
|
|
61
|
+
assert self.widget.needs_multipart_form is True
|
|
62
|
+
|
|
63
|
+
def test_template_name(self):
|
|
64
|
+
assert self.widget.template_name == 'django_spire/file/widget/single_file_widget.html'
|
|
65
|
+
|
|
66
|
+
def test_value_from_datadict(self):
|
|
67
|
+
data = {
|
|
68
|
+
'file_data': json.dumps({'id': 1, 'name': 'test'})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
result = self.widget.value_from_datadict(data, None, 'file')
|
|
72
|
+
|
|
73
|
+
assert result == {'id': 1, 'name': 'test'}
|
|
74
|
+
|
|
75
|
+
def test_value_from_datadict_null(self):
|
|
76
|
+
data = {
|
|
77
|
+
'file_data': json.dumps(None)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
result = self.widget.value_from_datadict(data, None, 'file')
|
|
81
|
+
|
|
82
|
+
assert result is None
|
django_spire/file/tools.py
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
2
4
|
|
|
3
5
|
from django.contrib.contenttypes.models import ContentType
|
|
4
6
|
|
|
5
7
|
from django_spire.file.models import File
|
|
6
8
|
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from django.db import models
|
|
11
|
+
|
|
7
12
|
|
|
8
|
-
def copy_files_from_source_to_target_model_object(source:
|
|
13
|
+
def copy_files_from_source_to_target_model_object(source: models.Model, target: models.Model) -> list[File]:
|
|
9
14
|
target_class = target.__class__
|
|
10
15
|
target_content_type = ContentType.objects.get_for_model(target_class)
|
|
11
16
|
|
|
@@ -15,6 +20,7 @@ def copy_files_from_source_to_target_model_object(source: Any, target: Any) -> l
|
|
|
15
20
|
return []
|
|
16
21
|
|
|
17
22
|
files_to_create = []
|
|
23
|
+
|
|
18
24
|
for file in file_list:
|
|
19
25
|
new_file = File(
|
|
20
26
|
content_type=target_content_type,
|
django_spire/file/views.py
CHANGED
|
@@ -12,7 +12,7 @@ if TYPE_CHECKING:
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
@login_required()
|
|
15
|
-
def file_multiple_upload_ajax(request: WSGIRequest) -> JsonResponse:
|
|
15
|
+
def file_multiple_upload_ajax(request: WSGIRequest) -> JsonResponse | None:
|
|
16
16
|
if request.method == 'POST':
|
|
17
17
|
file_uploader = MultiFileUploader(request.POST.get('related_field', ''))
|
|
18
18
|
files = file_uploader.upload(list(request.FILES.values()))
|
|
@@ -21,13 +21,17 @@ def file_multiple_upload_ajax(request: WSGIRequest) -> JsonResponse:
|
|
|
21
21
|
'files': [file.to_dict() for file in files]
|
|
22
22
|
})
|
|
23
23
|
|
|
24
|
+
return None
|
|
25
|
+
|
|
24
26
|
|
|
25
27
|
@login_required()
|
|
26
|
-
def file_single_upload_ajax(request: WSGIRequest) -> JsonResponse:
|
|
28
|
+
def file_single_upload_ajax(request: WSGIRequest) -> JsonResponse | None:
|
|
27
29
|
if request.method == 'POST':
|
|
28
30
|
file_uploader = SingleFileUploader(request.POST.get('related_field', ''))
|
|
29
|
-
file = file_uploader.upload(
|
|
31
|
+
file = file_uploader.upload(next(iter(request.FILES.values())))
|
|
30
32
|
|
|
31
33
|
return JsonResponse({
|
|
32
34
|
'file': file.to_dict()
|
|
33
35
|
})
|
|
36
|
+
|
|
37
|
+
return None
|
django_spire/file/widgets.py
CHANGED
|
@@ -2,38 +2,38 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
|
|
5
|
-
from django.template import loader
|
|
6
|
-
from django.utils.safestring import mark_safe
|
|
7
5
|
from django.forms import Widget
|
|
6
|
+
from django.template import loader
|
|
7
|
+
from django.utils.safestring import SafeString, mark_safe
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class MultipleWidget(Widget):
|
|
11
11
|
needs_multipart_form = True
|
|
12
12
|
template_name = 'django_spire/file/widget/multiple_file_widget.html'
|
|
13
13
|
|
|
14
|
-
def __init__(self, *args, **kwargs):
|
|
14
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
15
15
|
super().__init__(*args, **kwargs)
|
|
16
16
|
|
|
17
|
-
def
|
|
18
|
-
return json.loads(data.get(f'{name}_data'))
|
|
19
|
-
|
|
20
|
-
def render(self, name, value, attrs=None, renderer=None):
|
|
17
|
+
def render(self, name: str, value, attrs: dict | None = None, renderer=None) -> SafeString:
|
|
21
18
|
context = self.get_context(name, value, attrs)
|
|
22
19
|
template = loader.get_template(self.template_name).render(context)
|
|
23
20
|
return mark_safe(template)
|
|
24
21
|
|
|
22
|
+
def value_from_datadict(self, data: dict, files, name: str) -> list[dict]:
|
|
23
|
+
return json.loads(data.get(f'{name}_data'))
|
|
24
|
+
|
|
25
25
|
|
|
26
26
|
class SingleFileWidget(Widget):
|
|
27
27
|
needs_multipart_form = True
|
|
28
28
|
template_name = 'django_spire/file/widget/single_file_widget.html'
|
|
29
29
|
|
|
30
|
-
def __init__(self, *args, **kwargs):
|
|
30
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
31
31
|
super().__init__(*args, **kwargs)
|
|
32
32
|
|
|
33
|
-
def
|
|
34
|
-
return json.loads(data.get(f'{name}_data'))
|
|
35
|
-
|
|
36
|
-
def render(self, name, value, attrs=None, renderer=None):
|
|
33
|
+
def render(self, name: str, value, attrs: dict | None = None, renderer=None) -> SafeString:
|
|
37
34
|
context = self.get_context(name, value, attrs)
|
|
38
35
|
template = loader.get_template(self.template_name).render(context)
|
|
39
36
|
return mark_safe(template)
|
|
37
|
+
|
|
38
|
+
def value_from_datadict(self, data: dict, files, name: str) -> dict:
|
|
39
|
+
return json.loads(data.get(f'{name}_data'))
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django.contrib import admin
|
|
4
|
+
|
|
5
|
+
from django_spire.help_desk.models import HelpDeskTicket
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@admin.register(HelpDeskTicket)
|
|
9
|
+
class HelpDeskTicketAdmin(admin.ModelAdmin):
|
|
10
|
+
list_display = ('pk', 'purpose', 'priority', 'status', 'created_by', 'created_datetime')
|
|
11
|
+
list_filter = ('priority', 'purpose', 'status', 'is_active', 'is_deleted')
|
|
12
|
+
ordering = ('-created_datetime',)
|
|
13
|
+
raw_id_fields = ('created_by',)
|
|
14
|
+
readonly_fields = ('created_by', 'created_datetime', 'is_active', 'is_deleted')
|
|
15
|
+
search_fields = ('description', 'created_by__username', 'created_by__email')
|
django_spire/help_desk/apps.py
CHANGED
django_spire/help_desk/enums.py
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from django_spire.exceptions import DjangoSpireError
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from django_spire.help_desk.enums import TicketEventType
|
|
9
|
+
from django_spire.notification.choices import NotificationTypeChoices
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class HelpDeskError(DjangoSpireError):
|
|
2
13
|
pass
|
|
3
14
|
|
|
4
|
-
|
|
5
|
-
|
|
15
|
+
|
|
16
|
+
class TicketEventNotificationTypeNotSupportedError(HelpDeskError, TypeError):
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
event_type: TicketEventType | None,
|
|
20
|
+
notification_type: NotificationTypeChoices
|
|
21
|
+
) -> None:
|
|
22
|
+
if event_type is None:
|
|
23
|
+
super().__init__(f'Notification type not supported: {notification_type}')
|
|
24
|
+
else:
|
|
25
|
+
super().__init__(
|
|
26
|
+
f'Combination of event type and notification type not supported: '
|
|
27
|
+
f'Event type {event_type} - Notification type {notification_type}'
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class HelpDeskNotificationRecipientMissingEmailError(HelpDeskError, ValueError):
|
|
32
|
+
def __init__(self, recipient: str) -> None:
|
|
33
|
+
super().__init__(f'Recipient is missing an email address: {recipient}')
|
django_spire/help_desk/forms.py
CHANGED
django_spire/help_desk/models.py
CHANGED