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
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
1
5
|
from typing import Sequence
|
|
2
6
|
|
|
3
7
|
from django import template
|
|
4
|
-
|
|
8
|
+
|
|
5
9
|
|
|
6
10
|
register = template.Library()
|
|
7
11
|
|
|
@@ -10,5 +14,5 @@ register = template.Library()
|
|
|
10
14
|
def to_json(value: dict | Sequence) -> str:
|
|
11
15
|
try:
|
|
12
16
|
return json.dumps(value)
|
|
13
|
-
except (TypeError, ValueError)
|
|
17
|
+
except (TypeError, ValueError):
|
|
14
18
|
return ''
|
|
@@ -4,21 +4,26 @@ import json
|
|
|
4
4
|
import random
|
|
5
5
|
import string
|
|
6
6
|
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
7
9
|
from django import template
|
|
8
10
|
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from typing import Iterable
|
|
13
|
+
|
|
14
|
+
from django.contrib.messages.storage.base import Message
|
|
15
|
+
|
|
9
16
|
|
|
10
17
|
register = template.Library()
|
|
11
18
|
|
|
12
19
|
|
|
13
20
|
@register.simple_tag()
|
|
14
|
-
def django_messages_to_json(messages):
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
for message in messages:
|
|
18
|
-
message_list.append({
|
|
21
|
+
def django_messages_to_json(messages: Iterable[Message]) -> str:
|
|
22
|
+
return json.dumps([
|
|
23
|
+
{
|
|
19
24
|
'id': ''.join(random.choice(string.ascii_letters) for _ in range(8)),
|
|
20
25
|
'message': message.message,
|
|
21
26
|
'type': message.level_tag
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
}
|
|
28
|
+
for message in messages
|
|
29
|
+
])
|
|
@@ -1,23 +1,26 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from django import template
|
|
2
4
|
|
|
5
|
+
|
|
3
6
|
register = template.Library()
|
|
4
7
|
|
|
5
8
|
|
|
6
9
|
@register.filter
|
|
7
|
-
def dashes_to_underscore(value):
|
|
10
|
+
def dashes_to_underscore(value: str) -> str:
|
|
8
11
|
return value.replace('-', '_')
|
|
9
12
|
|
|
10
13
|
|
|
11
14
|
@register.filter
|
|
12
|
-
def spaces_to_underscore(value):
|
|
15
|
+
def spaces_to_underscore(value: str) -> str:
|
|
13
16
|
return value.replace(' ', '_')
|
|
14
17
|
|
|
15
18
|
|
|
16
19
|
@register.filter
|
|
17
|
-
def dashes_and_spaces_to_underscore(value):
|
|
20
|
+
def dashes_and_spaces_to_underscore(value: str) -> str:
|
|
18
21
|
return spaces_to_underscore(dashes_to_underscore(value))
|
|
19
22
|
|
|
20
23
|
|
|
21
24
|
@register.filter
|
|
22
|
-
def underscores_to_spaces(value):
|
|
23
|
-
return value.replace('_', ' ')
|
|
25
|
+
def underscores_to_spaces(value: str) -> str:
|
|
26
|
+
return value.replace('_', ' ')
|
|
File without changes
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import string
|
|
5
|
+
|
|
6
|
+
from unittest.mock import MagicMock, patch
|
|
7
|
+
|
|
8
|
+
from django.template import Context, RequestContext, Template
|
|
9
|
+
from django.test import RequestFactory, TestCase
|
|
10
|
+
|
|
11
|
+
from django_spire.core.templatetags.json import to_json
|
|
12
|
+
from django_spire.core.templatetags.message import django_messages_to_json
|
|
13
|
+
from django_spire.core.templatetags.spire_core_tags import (
|
|
14
|
+
add_str,
|
|
15
|
+
content_type_url,
|
|
16
|
+
generate_id,
|
|
17
|
+
in_list,
|
|
18
|
+
index,
|
|
19
|
+
is_path,
|
|
20
|
+
not_in_list,
|
|
21
|
+
query_param_url,
|
|
22
|
+
safe_dict_items,
|
|
23
|
+
to_snake_case,
|
|
24
|
+
)
|
|
25
|
+
from django_spire.core.templatetags.string_formating import (
|
|
26
|
+
dashes_and_spaces_to_underscore,
|
|
27
|
+
dashes_to_underscore,
|
|
28
|
+
spaces_to_underscore,
|
|
29
|
+
underscores_to_spaces,
|
|
30
|
+
)
|
|
31
|
+
from django_spire.core.templatetags.variable_types import (
|
|
32
|
+
is_dict,
|
|
33
|
+
is_list,
|
|
34
|
+
is_list_or_tuple,
|
|
35
|
+
is_not_dict,
|
|
36
|
+
is_not_list,
|
|
37
|
+
is_not_list_or_tuple,
|
|
38
|
+
is_not_tuple,
|
|
39
|
+
is_tuple,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class TestAddStr(TestCase):
|
|
44
|
+
def test_concatenates_strings(self) -> None:
|
|
45
|
+
assert add_str('Hello', 'World') == 'HelloWorld'
|
|
46
|
+
|
|
47
|
+
def test_empty_strings(self) -> None:
|
|
48
|
+
assert add_str('', '') == ''
|
|
49
|
+
|
|
50
|
+
def test_with_spaces(self) -> None:
|
|
51
|
+
assert add_str('Hello ', 'World') == 'Hello World'
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class TestContentTypeUrl(TestCase):
|
|
55
|
+
def test_constructs_url_with_metadata(self) -> None:
|
|
56
|
+
class Dummy:
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
dummy = Dummy()
|
|
60
|
+
dummy._meta = type(
|
|
61
|
+
'meta',
|
|
62
|
+
(),
|
|
63
|
+
{'app_label': 'myapp', 'model_name': 'dummy'}
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
func = 'django_spire.core.templatetags.spire_core_tags.reverse'
|
|
67
|
+
return_value = 'http://example.com/dummy'
|
|
68
|
+
|
|
69
|
+
with patch(func, return_value=return_value) as mock_reverse:
|
|
70
|
+
url = content_type_url('dummy_url', dummy)
|
|
71
|
+
|
|
72
|
+
mock_reverse.assert_called_once_with(
|
|
73
|
+
'dummy_url',
|
|
74
|
+
kwargs={'app_label': 'myapp', 'model_name': 'dummy'}
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
assert url == 'http://example.com/dummy'
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class TestDashesAndSpacesToUnderscore(TestCase):
|
|
81
|
+
def test_dashes_and_spaces(self) -> None:
|
|
82
|
+
assert dashes_and_spaces_to_underscore('hello-world test') == 'hello_world_test'
|
|
83
|
+
|
|
84
|
+
def test_no_changes_needed(self) -> None:
|
|
85
|
+
assert dashes_and_spaces_to_underscore('hello_world') == 'hello_world'
|
|
86
|
+
|
|
87
|
+
def test_only_dashes(self) -> None:
|
|
88
|
+
assert dashes_and_spaces_to_underscore('hello-world') == 'hello_world'
|
|
89
|
+
|
|
90
|
+
def test_only_spaces(self) -> None:
|
|
91
|
+
assert dashes_and_spaces_to_underscore('hello world') == 'hello_world'
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class TestDashesToUnderscore(TestCase):
|
|
95
|
+
def test_multiple_dashes(self) -> None:
|
|
96
|
+
assert dashes_to_underscore('hello-world-test') == 'hello_world_test'
|
|
97
|
+
|
|
98
|
+
def test_no_dashes(self) -> None:
|
|
99
|
+
assert dashes_to_underscore('hello') == 'hello'
|
|
100
|
+
|
|
101
|
+
def test_single_dash(self) -> None:
|
|
102
|
+
assert dashes_to_underscore('hello-world') == 'hello_world'
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class TestDjangoMessagesToJson(TestCase):
|
|
106
|
+
def test_empty_messages(self) -> None:
|
|
107
|
+
result = django_messages_to_json([])
|
|
108
|
+
assert json.loads(result) == []
|
|
109
|
+
|
|
110
|
+
def test_multiple_messages(self) -> None:
|
|
111
|
+
messages = [
|
|
112
|
+
MagicMock(message='First', level_tag='info'),
|
|
113
|
+
MagicMock(message='Second', level_tag='error'),
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
result = django_messages_to_json(messages)
|
|
117
|
+
parsed = json.loads(result)
|
|
118
|
+
|
|
119
|
+
assert len(parsed) == 2
|
|
120
|
+
assert parsed[0]['message'] == 'First'
|
|
121
|
+
assert parsed[0]['type'] == 'info'
|
|
122
|
+
assert parsed[1]['message'] == 'Second'
|
|
123
|
+
assert parsed[1]['type'] == 'error'
|
|
124
|
+
|
|
125
|
+
def test_single_message(self) -> None:
|
|
126
|
+
messages = [MagicMock(message='Test message', level_tag='success')]
|
|
127
|
+
|
|
128
|
+
result = django_messages_to_json(messages)
|
|
129
|
+
parsed = json.loads(result)
|
|
130
|
+
|
|
131
|
+
assert len(parsed) == 1
|
|
132
|
+
assert parsed[0]['message'] == 'Test message'
|
|
133
|
+
assert parsed[0]['type'] == 'success'
|
|
134
|
+
assert len(parsed[0]['id']) == 8
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class TestGenerateId(TestCase):
|
|
138
|
+
def test_generates_8_characters(self) -> None:
|
|
139
|
+
identifier = generate_id()
|
|
140
|
+
assert len(identifier) == 8
|
|
141
|
+
|
|
142
|
+
def test_only_ascii_letters(self) -> None:
|
|
143
|
+
identifier = generate_id()
|
|
144
|
+
assert all(ch in string.ascii_letters for ch in identifier)
|
|
145
|
+
|
|
146
|
+
def test_unique_ids(self) -> None:
|
|
147
|
+
ids = {generate_id() for _ in range(100)}
|
|
148
|
+
assert len(ids) == 100
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class TestInList(TestCase):
|
|
152
|
+
def test_empty_list(self) -> None:
|
|
153
|
+
assert in_list('a', '') is False
|
|
154
|
+
|
|
155
|
+
def test_value_in_list(self) -> None:
|
|
156
|
+
assert in_list('a', 'a,b,c') is True
|
|
157
|
+
|
|
158
|
+
def test_value_not_in_list(self) -> None:
|
|
159
|
+
assert in_list('d', 'a,b,c') is False
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class TestIndex(TestCase):
|
|
163
|
+
def test_index_out_of_bounds(self) -> None:
|
|
164
|
+
items = [10, 20, 30]
|
|
165
|
+
assert index(items, 5) == items
|
|
166
|
+
|
|
167
|
+
def test_negative_index(self) -> None:
|
|
168
|
+
items = [10, 20, 30]
|
|
169
|
+
assert index(items, -1) == 30
|
|
170
|
+
|
|
171
|
+
def test_valid_index(self) -> None:
|
|
172
|
+
items = [10, 20, 30]
|
|
173
|
+
assert index(items, 1) == 20
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class TestIsDict(TestCase):
|
|
177
|
+
def test_dict(self) -> None:
|
|
178
|
+
assert is_dict({'key': 'value'}) is True
|
|
179
|
+
|
|
180
|
+
def test_empty_dict(self) -> None:
|
|
181
|
+
assert is_dict({}) is True
|
|
182
|
+
|
|
183
|
+
def test_list(self) -> None:
|
|
184
|
+
assert is_dict([]) is False
|
|
185
|
+
|
|
186
|
+
def test_string(self) -> None:
|
|
187
|
+
assert is_dict('string') is False
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class TestIsList(TestCase):
|
|
191
|
+
def test_dict(self) -> None:
|
|
192
|
+
assert is_list({}) is False
|
|
193
|
+
|
|
194
|
+
def test_empty_list(self) -> None:
|
|
195
|
+
assert is_list([]) is True
|
|
196
|
+
|
|
197
|
+
def test_list(self) -> None:
|
|
198
|
+
assert is_list([1, 2, 3]) is True
|
|
199
|
+
|
|
200
|
+
def test_tuple(self) -> None:
|
|
201
|
+
assert is_list((1, 2, 3)) is False
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class TestIsListOrTuple(TestCase):
|
|
205
|
+
def test_dict(self) -> None:
|
|
206
|
+
assert is_list_or_tuple({}) is False
|
|
207
|
+
|
|
208
|
+
def test_list(self) -> None:
|
|
209
|
+
assert is_list_or_tuple([1, 2, 3]) is True
|
|
210
|
+
|
|
211
|
+
def test_string(self) -> None:
|
|
212
|
+
assert is_list_or_tuple('string') is False
|
|
213
|
+
|
|
214
|
+
def test_tuple(self) -> None:
|
|
215
|
+
assert is_list_or_tuple((1, 2, 3)) is True
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class TestIsNotDict(TestCase):
|
|
219
|
+
def test_dict(self) -> None:
|
|
220
|
+
assert is_not_dict({'key': 'value'}) is False
|
|
221
|
+
|
|
222
|
+
def test_list(self) -> None:
|
|
223
|
+
assert is_not_dict([]) is True
|
|
224
|
+
|
|
225
|
+
def test_string(self) -> None:
|
|
226
|
+
assert is_not_dict('string') is True
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class TestIsNotList(TestCase):
|
|
230
|
+
def test_dict(self) -> None:
|
|
231
|
+
assert is_not_list({}) is True
|
|
232
|
+
|
|
233
|
+
def test_list(self) -> None:
|
|
234
|
+
assert is_not_list([1, 2, 3]) is False
|
|
235
|
+
|
|
236
|
+
def test_tuple(self) -> None:
|
|
237
|
+
assert is_not_list((1, 2, 3)) is True
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
class TestIsNotListOrTuple(TestCase):
|
|
241
|
+
def test_dict(self) -> None:
|
|
242
|
+
assert is_not_list_or_tuple({}) is True
|
|
243
|
+
|
|
244
|
+
def test_list(self) -> None:
|
|
245
|
+
assert is_not_list_or_tuple([1, 2, 3]) is False
|
|
246
|
+
|
|
247
|
+
def test_string(self) -> None:
|
|
248
|
+
assert is_not_list_or_tuple('string') is True
|
|
249
|
+
|
|
250
|
+
def test_tuple(self) -> None:
|
|
251
|
+
assert is_not_list_or_tuple((1, 2, 3)) is False
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class TestIsNotTuple(TestCase):
|
|
255
|
+
def test_list(self) -> None:
|
|
256
|
+
assert is_not_tuple([1, 2, 3]) is True
|
|
257
|
+
|
|
258
|
+
def test_tuple(self) -> None:
|
|
259
|
+
assert is_not_tuple((1, 2, 3)) is False
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
class TestIsPath(TestCase):
|
|
263
|
+
def test_empty_current(self) -> None:
|
|
264
|
+
assert is_path('', '/test') is False
|
|
265
|
+
|
|
266
|
+
def test_empty_url(self) -> None:
|
|
267
|
+
assert is_path('/test', '') is False
|
|
268
|
+
|
|
269
|
+
def test_exact_match(self) -> None:
|
|
270
|
+
assert is_path('/test', '/test') is True
|
|
271
|
+
|
|
272
|
+
def test_root_url(self) -> None:
|
|
273
|
+
assert is_path('/test', '/') is False
|
|
274
|
+
|
|
275
|
+
def test_starts_with(self) -> None:
|
|
276
|
+
assert is_path('/test/page', '/test') is True
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
class TestIsTuple(TestCase):
|
|
280
|
+
def test_list(self) -> None:
|
|
281
|
+
assert is_tuple([1, 2, 3]) is False
|
|
282
|
+
|
|
283
|
+
def test_tuple(self) -> None:
|
|
284
|
+
assert is_tuple((1, 2, 3)) is True
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
class TestNotInList(TestCase):
|
|
288
|
+
def test_empty_list(self) -> None:
|
|
289
|
+
assert not_in_list('a', '') is True
|
|
290
|
+
|
|
291
|
+
def test_value_in_list(self) -> None:
|
|
292
|
+
assert not_in_list('a', 'a,b,c') is False
|
|
293
|
+
|
|
294
|
+
def test_value_not_in_list(self) -> None:
|
|
295
|
+
assert not_in_list('x', 'a,b,c') is True
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
class TestQueryParamUrl(TestCase):
|
|
299
|
+
def setUp(self) -> None:
|
|
300
|
+
super().setUp()
|
|
301
|
+
|
|
302
|
+
self.factory = RequestFactory()
|
|
303
|
+
|
|
304
|
+
def test_multiple_params(self) -> None:
|
|
305
|
+
request = self.factory.get('/some_path', {'foo': 'bar', 'baz': 'qux'})
|
|
306
|
+
context = RequestContext(request, {})
|
|
307
|
+
|
|
308
|
+
func = 'django_spire.core.templatetags.spire_core_tags.reverse'
|
|
309
|
+
return_value = 'http://example.com/dummy'
|
|
310
|
+
|
|
311
|
+
with patch(func, return_value=return_value) as mock_reverse:
|
|
312
|
+
url = query_param_url(context, 'dummy_url')
|
|
313
|
+
mock_reverse.assert_called_once_with('dummy_url', kwargs={})
|
|
314
|
+
|
|
315
|
+
assert url.startswith('http://example.com/dummy?')
|
|
316
|
+
assert 'foo=bar' in url
|
|
317
|
+
assert 'baz=qux' in url
|
|
318
|
+
|
|
319
|
+
def test_no_params(self) -> None:
|
|
320
|
+
request = self.factory.get('/some_path')
|
|
321
|
+
context = RequestContext(request, {})
|
|
322
|
+
|
|
323
|
+
func = 'django_spire.core.templatetags.spire_core_tags.reverse'
|
|
324
|
+
return_value = 'http://example.com/dummy'
|
|
325
|
+
|
|
326
|
+
with patch(func, return_value=return_value):
|
|
327
|
+
url = query_param_url(context, 'dummy_url')
|
|
328
|
+
|
|
329
|
+
assert url == 'http://example.com/dummy?'
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
class TestSafeDictItems(TestCase):
|
|
333
|
+
def test_dict_with_items_key(self) -> None:
|
|
334
|
+
d = {'items': 'value', 'other': 'data'}
|
|
335
|
+
result = list(safe_dict_items(d))
|
|
336
|
+
|
|
337
|
+
assert ('items', 'value') in result
|
|
338
|
+
assert ('other', 'data') in result
|
|
339
|
+
|
|
340
|
+
def test_no_items_attribute(self) -> None:
|
|
341
|
+
result = safe_dict_items('not a dict')
|
|
342
|
+
assert result == []
|
|
343
|
+
|
|
344
|
+
def test_normal_dict(self) -> None:
|
|
345
|
+
d = {'key': 'value'}
|
|
346
|
+
result = list(safe_dict_items(d))
|
|
347
|
+
|
|
348
|
+
assert result == [('key', 'value')]
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
class TestSpacesToUnderscore(TestCase):
|
|
352
|
+
def test_multiple_spaces(self) -> None:
|
|
353
|
+
assert spaces_to_underscore('hello world test') == 'hello_world_test'
|
|
354
|
+
|
|
355
|
+
def test_no_spaces(self) -> None:
|
|
356
|
+
assert spaces_to_underscore('hello') == 'hello'
|
|
357
|
+
|
|
358
|
+
def test_single_space(self) -> None:
|
|
359
|
+
assert spaces_to_underscore('hello world') == 'hello_world'
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
class TestToJson(TestCase):
|
|
363
|
+
def test_dict(self) -> None:
|
|
364
|
+
result = to_json({'key': 'value'})
|
|
365
|
+
assert result == '{"key": "value"}'
|
|
366
|
+
|
|
367
|
+
def test_invalid_value(self) -> None:
|
|
368
|
+
result = to_json(object())
|
|
369
|
+
assert result == ''
|
|
370
|
+
|
|
371
|
+
def test_list(self) -> None:
|
|
372
|
+
result = to_json([1, 2, 3])
|
|
373
|
+
assert result == '[1, 2, 3]'
|
|
374
|
+
|
|
375
|
+
def test_nested_structure(self) -> None:
|
|
376
|
+
result = to_json({'list': [1, 2, 3], 'nested': {'key': 'value'}})
|
|
377
|
+
parsed = json.loads(result)
|
|
378
|
+
|
|
379
|
+
assert parsed['list'] == [1, 2, 3]
|
|
380
|
+
assert parsed['nested'] == {'key': 'value'}
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
class TestToSnakeCase(TestCase):
|
|
384
|
+
def test_already_snake_case(self) -> None:
|
|
385
|
+
assert to_snake_case('hello_world') == 'hello_world'
|
|
386
|
+
|
|
387
|
+
def test_multiple_spaces(self) -> None:
|
|
388
|
+
assert to_snake_case('Hello World Test') == 'hello_world_test'
|
|
389
|
+
|
|
390
|
+
def test_simple_conversion(self) -> None:
|
|
391
|
+
assert to_snake_case('Hello World') == 'hello_world'
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
class TestUnderscoresToSpaces(TestCase):
|
|
395
|
+
def test_multiple_underscores(self) -> None:
|
|
396
|
+
assert underscores_to_spaces('hello_world_test') == 'hello world test'
|
|
397
|
+
|
|
398
|
+
def test_no_underscores(self) -> None:
|
|
399
|
+
assert underscores_to_spaces('hello') == 'hello'
|
|
400
|
+
|
|
401
|
+
def test_single_underscore(self) -> None:
|
|
402
|
+
assert underscores_to_spaces('hello_world') == 'hello world'
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
class TemplateRenderingTests(TestCase):
|
|
406
|
+
def test_render_add_str_filter(self) -> None:
|
|
407
|
+
template_code = """
|
|
408
|
+
{% load spire_core_tags %}
|
|
409
|
+
|
|
410
|
+
{{ "Hello" | add_str:" World" }}
|
|
411
|
+
"""
|
|
412
|
+
|
|
413
|
+
tmpl = Template(template_code)
|
|
414
|
+
rendered = tmpl.render(Context())
|
|
415
|
+
assert 'Hello World' in rendered
|
|
416
|
+
|
|
417
|
+
def test_render_to_snake_case_tag(self) -> None:
|
|
418
|
+
template_code = """
|
|
419
|
+
{% load spire_core_tags %}
|
|
420
|
+
|
|
421
|
+
{% to_snake_case "Hello World" as snake_case %}
|
|
422
|
+
{{ snake_case }}
|
|
423
|
+
"""
|
|
424
|
+
|
|
425
|
+
tmpl = Template(template_code)
|
|
426
|
+
rendered = tmpl.render(Context())
|
|
427
|
+
assert 'hello_world' in rendered
|
|
@@ -1,43 +1,51 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
1
5
|
from django import template
|
|
2
6
|
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
|
|
3
11
|
register = template.Library()
|
|
4
12
|
|
|
5
13
|
|
|
6
14
|
@register.filter
|
|
7
|
-
def is_dict(value):
|
|
15
|
+
def is_dict(value: Any) -> bool:
|
|
8
16
|
return isinstance(value, dict)
|
|
9
17
|
|
|
10
18
|
|
|
11
19
|
@register.filter
|
|
12
|
-
def is_not_dict(value):
|
|
20
|
+
def is_not_dict(value: Any) -> bool:
|
|
13
21
|
return not is_dict(value)
|
|
14
22
|
|
|
15
23
|
|
|
16
24
|
@register.filter
|
|
17
|
-
def is_list(value):
|
|
25
|
+
def is_list(value: Any) -> bool:
|
|
18
26
|
return isinstance(value, list)
|
|
19
27
|
|
|
20
28
|
|
|
21
29
|
@register.filter
|
|
22
|
-
def is_not_list(value):
|
|
30
|
+
def is_not_list(value: Any) -> bool:
|
|
23
31
|
return not is_list(value)
|
|
24
32
|
|
|
25
33
|
|
|
26
34
|
@register.filter
|
|
27
|
-
def is_list_or_tuple(value):
|
|
35
|
+
def is_list_or_tuple(value: Any) -> bool:
|
|
28
36
|
return isinstance(value, (list, tuple))
|
|
29
37
|
|
|
30
38
|
|
|
31
39
|
@register.filter
|
|
32
|
-
def is_not_list_or_tuple(value):
|
|
40
|
+
def is_not_list_or_tuple(value: Any) -> bool:
|
|
33
41
|
return not is_list_or_tuple(value)
|
|
34
42
|
|
|
35
43
|
|
|
36
44
|
@register.filter
|
|
37
|
-
def is_tuple(value):
|
|
45
|
+
def is_tuple(value: Any) -> bool:
|
|
38
46
|
return isinstance(value, tuple)
|
|
39
47
|
|
|
40
48
|
|
|
41
49
|
@register.filter
|
|
42
|
-
def is_not_tuple(value):
|
|
43
|
-
return not is_tuple(value)
|
|
50
|
+
def is_not_tuple(value: Any) -> bool:
|
|
51
|
+
return not is_tuple(value)
|
|
@@ -5,7 +5,6 @@ import tempfile
|
|
|
5
5
|
from django.test import Client, override_settings, TestCase
|
|
6
6
|
|
|
7
7
|
from django_spire.auth.user.models import AuthUser
|
|
8
|
-
from django_spire.auth.user.tests.factories import create_super_user
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
TEMP_MEDIA_ROOT = tempfile.mkdtemp()
|
|
@@ -19,4 +18,5 @@ class BaseTestCase(TestCase):
|
|
|
19
18
|
self.super_user = AuthUser.objects.create_superuser(
|
|
20
19
|
username='stratus',
|
|
21
20
|
)
|
|
21
|
+
|
|
22
22
|
self.client.force_login(self.super_user)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django.test import TestCase, override_settings
|
|
4
|
+
|
|
5
|
+
from django_spire.conf import Settings, settings
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestSettings(TestCase):
|
|
9
|
+
def test_getattr_returns_django_setting(self) -> None:
|
|
10
|
+
assert settings.DEBUG is not None
|
|
11
|
+
|
|
12
|
+
def test_getattr_returns_django_spire_default(self) -> None:
|
|
13
|
+
result = settings.DJANGO_SPIRE_DEFAULT_THEME
|
|
14
|
+
|
|
15
|
+
assert result == 'default-light'
|
|
16
|
+
|
|
17
|
+
def test_getattr_returns_none_for_unknown_setting(self) -> None:
|
|
18
|
+
result = settings.NONEXISTENT_SETTING_NAME
|
|
19
|
+
|
|
20
|
+
assert result is None
|
|
21
|
+
|
|
22
|
+
@override_settings(DJANGO_SPIRE_DEFAULT_THEME='custom-dark')
|
|
23
|
+
def test_django_setting_overrides_default(self) -> None:
|
|
24
|
+
fresh_settings = Settings()
|
|
25
|
+
|
|
26
|
+
result = fresh_settings.DJANGO_SPIRE_DEFAULT_THEME
|
|
27
|
+
|
|
28
|
+
assert result == 'custom-dark'
|
|
29
|
+
|
|
30
|
+
@override_settings(
|
|
31
|
+
DJANGO_SPIRE_AUTH_CONTROLLERS={'custom_app': 'custom.path.Controller'}
|
|
32
|
+
)
|
|
33
|
+
def test_auth_controllers_merges_dicts(self) -> None:
|
|
34
|
+
fresh_settings = Settings()
|
|
35
|
+
|
|
36
|
+
result = fresh_settings.DJANGO_SPIRE_AUTH_CONTROLLERS
|
|
37
|
+
|
|
38
|
+
assert 'custom_app' in result
|
|
39
|
+
assert 'ai_chat' in result
|
|
40
|
+
|
|
41
|
+
def test_settings_instance_exists(self) -> None:
|
|
42
|
+
assert settings is not None
|
|
43
|
+
assert isinstance(settings, Settings)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django.test import TestCase
|
|
4
|
+
|
|
5
|
+
from django_spire.consts import (
|
|
6
|
+
__VERSION__,
|
|
7
|
+
MAINTENANCE_MODE_SETTINGS_NAME,
|
|
8
|
+
NOTIFICATION_THROTTLE_RATE_PER_MINUTE_SETTINGS_NAME,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestConsts(TestCase):
|
|
13
|
+
def test_maintenance_mode_settings_name(self) -> None:
|
|
14
|
+
assert MAINTENANCE_MODE_SETTINGS_NAME == 'MAINTENANCE_MODE'
|
|
15
|
+
|
|
16
|
+
def test_notification_throttle_rate_settings_name(self) -> None:
|
|
17
|
+
assert NOTIFICATION_THROTTLE_RATE_PER_MINUTE_SETTINGS_NAME == 'NOTIFICATION_THROTTLE_RATE_PER_MINUTE'
|
|
18
|
+
|
|
19
|
+
def test_version_is_string(self) -> None:
|
|
20
|
+
assert isinstance(__VERSION__, str)
|
|
21
|
+
|
|
22
|
+
def test_version_format(self) -> None:
|
|
23
|
+
parts = __VERSION__.split('.')
|
|
24
|
+
|
|
25
|
+
assert len(parts) == 3
|
|
26
|
+
|
|
27
|
+
for part in parts:
|
|
28
|
+
assert part.isdigit()
|