django-spire 0.23.7__py3-none-any.whl → 0.23.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_spire/ai/admin.py +11 -11
- django_spire/ai/chat/apps.py +1 -0
- django_spire/ai/chat/templates/django_spire/ai/chat/widget/dialog_widget.html +1 -1
- django_spire/ai/chat/tests/factories.py +15 -0
- django_spire/ai/chat/tests/test_controller.py +45 -0
- django_spire/ai/chat/tests/test_models.py +301 -0
- django_spire/ai/chat/tests/test_prompts.py +48 -0
- django_spire/ai/chat/tests/test_responses.py +208 -0
- django_spire/ai/chat/tests/test_router/test_base_chat_router.py +66 -6
- django_spire/ai/chat/tests/test_router/test_chat_workflow.py +73 -3
- django_spire/ai/chat/tests/test_router/test_integration.py +86 -6
- django_spire/ai/chat/tests/test_router/test_intent_decoder.py +93 -1
- django_spire/ai/chat/tests/test_router/test_message_intel.py +60 -1
- django_spire/ai/chat/tests/test_router/test_spire_chat_router.py +110 -0
- django_spire/ai/chat/tests/test_urls/test_json_urls.py +202 -1
- django_spire/ai/context/tests/__init__.py +0 -0
- django_spire/ai/context/tests/test_context.py +188 -0
- django_spire/ai/decorators.py +7 -6
- django_spire/ai/prompt/tests/test_bots.py +100 -10
- django_spire/ai/prompt/tests/test_prompt_intel.py +83 -0
- django_spire/ai/prompt/tests/test_prompt_tuning.py +126 -0
- django_spire/ai/sms/decorators.py +8 -2
- django_spire/ai/sms/tests/test_sms.py +240 -16
- django_spire/ai/sms/tests/test_sms_intel.py +42 -0
- django_spire/ai/sms/tests/test_webhook.py +155 -7
- django_spire/ai/sms/views.py +23 -24
- django_spire/ai/tests/test_ai.py +131 -7
- django_spire/auth/apps.py +4 -2
- django_spire/auth/controller/controller.py +36 -23
- django_spire/auth/controller/exceptions.py +9 -0
- django_spire/auth/group/admin.py +1 -0
- django_spire/auth/group/apps.py +2 -0
- django_spire/auth/group/factories.py +17 -8
- django_spire/auth/group/forms.py +7 -0
- django_spire/auth/group/tests/test_factories.py +146 -0
- django_spire/auth/group/tests/test_forms.py +282 -0
- django_spire/auth/group/tests/test_models.py +192 -0
- django_spire/auth/group/tests/test_querysets.py +98 -0
- django_spire/auth/group/tests/test_utils.py +341 -0
- django_spire/auth/group/tests/test_views.py +377 -0
- django_spire/auth/group/urls/__init__.py +3 -1
- django_spire/auth/group/urls/form_urls.py +2 -0
- django_spire/auth/group/urls/json_urls.py +3 -0
- django_spire/auth/group/urls/page_urls.py +2 -0
- django_spire/auth/group/utils.py +6 -2
- django_spire/auth/group/views/form_views.py +6 -3
- django_spire/auth/group/views/json_views.py +6 -2
- django_spire/auth/mfa/admin.py +2 -0
- django_spire/auth/mfa/apps.py +2 -0
- django_spire/auth/mfa/forms.py +1 -0
- django_spire/auth/mfa/querysets.py +9 -2
- django_spire/auth/mfa/tests/test_models.py +233 -0
- django_spire/auth/mfa/tests/test_utils.py +106 -0
- django_spire/auth/mfa/urls/__init__.py +2 -0
- django_spire/auth/mfa/urls/page_urls.py +2 -0
- django_spire/auth/mfa/urls/redirect_urls.py +2 -0
- django_spire/auth/mfa/views/page_views.py +2 -1
- django_spire/auth/permissions/consts.py +2 -2
- django_spire/auth/permissions/decorators.py +8 -8
- django_spire/auth/permissions/permissions.py +28 -35
- django_spire/auth/permissions/tests/test_decorators.py +333 -0
- django_spire/auth/permissions/tests/test_permissions.py +337 -0
- django_spire/auth/permissions/tests/test_tools.py +305 -0
- django_spire/auth/permissions/tools.py +21 -15
- django_spire/auth/seeding/seed.py +3 -0
- django_spire/auth/seeding/seeder.py +2 -0
- django_spire/auth/tests/test_controller.py +323 -0
- django_spire/auth/tests/test_url_endpoints.py +9 -9
- django_spire/auth/tests/test_views.py +406 -0
- django_spire/auth/urls/admin_urls.py +2 -0
- django_spire/auth/urls/redirect_urls.py +2 -0
- django_spire/auth/user/apps.py +2 -0
- django_spire/auth/user/forms.py +9 -0
- django_spire/auth/user/models.py +1 -1
- django_spire/auth/user/services/services.py +1 -0
- django_spire/auth/user/tests/factories.py +14 -13
- django_spire/auth/user/tests/test_factories.py +166 -2
- django_spire/auth/user/tests/test_forms.py +573 -0
- django_spire/auth/user/tests/test_models.py +257 -0
- django_spire/auth/user/tests/test_services.py +200 -0
- django_spire/auth/user/tests/test_tools.py +153 -0
- django_spire/auth/user/tests/test_user_factories.py +139 -0
- django_spire/auth/user/tests/test_views.py +363 -0
- django_spire/auth/user/tools.py +7 -1
- django_spire/auth/user/urls/form_urls.py +3 -0
- django_spire/auth/user/urls/page_urls.py +3 -0
- django_spire/auth/user/views/form_views.py +19 -10
- django_spire/auth/user/views/page_views.py +8 -2
- django_spire/auth/views/redirect_views.py +14 -9
- django_spire/comment/admin.py +2 -0
- django_spire/comment/apps.py +2 -0
- django_spire/comment/templatetags/comment_tags.py +1 -0
- django_spire/comment/tests/test_forms.py +27 -0
- django_spire/comment/tests/test_models.py +215 -0
- django_spire/comment/tests/test_querysets.py +101 -0
- django_spire/comment/tests/test_utils.py +90 -0
- django_spire/comment/urls.py +2 -0
- django_spire/comment/utils.py +22 -13
- django_spire/comment/views.py +1 -1
- django_spire/conf.py +8 -6
- django_spire/consts.py +1 -1
- django_spire/contrib/breadcrumb/apps.py +2 -0
- django_spire/contrib/breadcrumb/breadcrumbs.py +18 -18
- django_spire/contrib/breadcrumb/tests/test_breadcrumbs.py +198 -0
- django_spire/contrib/constructor/__init__.py +3 -3
- django_spire/contrib/constructor/constructor.py +15 -15
- django_spire/contrib/constructor/django_model_constructor.py +5 -4
- django_spire/contrib/constructor/exceptions.py +5 -3
- django_spire/contrib/constructor/tests/__init__.py +0 -0
- django_spire/contrib/constructor/tests/test_constructor.py +193 -0
- django_spire/contrib/form/tests/__init__.py +0 -0
- django_spire/contrib/form/tests/test_forms.py +203 -0
- django_spire/contrib/generic_views/modal_views.py +2 -1
- django_spire/contrib/generic_views/portal_views.py +20 -19
- django_spire/contrib/generic_views/tests/__init__.py +0 -0
- django_spire/contrib/generic_views/tests/test_views.py +459 -0
- django_spire/contrib/help/apps.py +2 -0
- django_spire/contrib/help/templatetags/help.py +1 -0
- django_spire/contrib/help/tests/__init__.py +0 -0
- django_spire/contrib/help/tests/test_templatetags.py +100 -0
- django_spire/contrib/options/mixins.py +6 -5
- django_spire/contrib/options/tests/factories.py +5 -1
- django_spire/contrib/options/tests/test_options.py +234 -0
- django_spire/contrib/ordering/exceptions.py +7 -3
- django_spire/contrib/ordering/mixins.py +2 -0
- django_spire/contrib/ordering/querysets.py +3 -1
- django_spire/contrib/ordering/services/processor_service.py +8 -4
- django_spire/contrib/ordering/services/service.py +1 -2
- django_spire/contrib/ordering/tests/__init__.py +0 -0
- django_spire/contrib/ordering/tests/test_ordering.py +165 -0
- django_spire/contrib/ordering/validators.py +6 -6
- django_spire/contrib/pagination/templatetags/pagination_tags.py +12 -5
- django_spire/contrib/pagination/tests/__init__.py +0 -0
- django_spire/contrib/pagination/tests/test_pagination.py +179 -0
- django_spire/contrib/performance/decorators.py +16 -6
- django_spire/contrib/performance/tests/__init__.py +0 -0
- django_spire/contrib/performance/tests/test_performance.py +107 -0
- django_spire/contrib/queryset/enums.py +3 -1
- django_spire/contrib/queryset/filter_tools.py +10 -5
- django_spire/contrib/queryset/mixins.py +16 -16
- django_spire/contrib/queryset/tests/__init__.py +0 -0
- django_spire/contrib/queryset/tests/test_queryset.py +137 -0
- django_spire/contrib/seeding/field/base.py +13 -7
- django_spire/contrib/seeding/field/callable.py +8 -1
- django_spire/contrib/seeding/field/cleaners.py +5 -5
- django_spire/contrib/seeding/field/custom.py +20 -10
- django_spire/contrib/seeding/field/django/seeder.py +8 -6
- django_spire/contrib/seeding/field/enums.py +7 -5
- django_spire/contrib/seeding/field/override.py +16 -6
- django_spire/contrib/seeding/field/static.py +9 -2
- django_spire/contrib/seeding/field/tests/test_base.py +18 -14
- django_spire/contrib/seeding/field/tests/test_callable.py +13 -9
- django_spire/contrib/seeding/field/tests/test_cleaners.py +51 -38
- django_spire/contrib/seeding/field/tests/test_static.py +13 -9
- django_spire/contrib/seeding/intelligence/bots/seeder_generator_bot.py +2 -0
- django_spire/contrib/seeding/intelligence/intel.py +5 -1
- django_spire/contrib/seeding/intelligence/prompts/factory.py +6 -1
- django_spire/contrib/seeding/intelligence/prompts/foreign_key_selection_prompt.py +6 -1
- django_spire/contrib/seeding/intelligence/prompts/generate_django_model_seeder_prompts.py +2 -0
- django_spire/contrib/seeding/intelligence/prompts/generic_relationship_selection_prompt.py +7 -1
- django_spire/contrib/seeding/intelligence/prompts/hierarchical_selection_prompt.py +6 -2
- django_spire/contrib/seeding/intelligence/prompts/model_field_choices_prompt.py +8 -2
- django_spire/contrib/seeding/intelligence/prompts/negation_prompt.py +2 -0
- django_spire/contrib/seeding/intelligence/prompts/objective_prompt.py +6 -1
- django_spire/contrib/seeding/management/commands/seeding.py +9 -3
- django_spire/contrib/seeding/management/example.py +2 -0
- django_spire/contrib/seeding/model/base.py +16 -7
- django_spire/contrib/seeding/model/config.py +31 -15
- django_spire/contrib/seeding/model/django/config.py +13 -13
- django_spire/contrib/seeding/model/django/seeder.py +4 -4
- django_spire/contrib/seeding/model/django/tests/test_seeder.py +34 -23
- django_spire/contrib/seeding/model/enums.py +2 -0
- django_spire/contrib/seeding/tests/test_config.py +71 -0
- django_spire/contrib/seeding/tests/test_custom.py +35 -0
- django_spire/contrib/seeding/tests/test_enums.py +40 -0
- django_spire/contrib/seeding/tests/test_intel.py +32 -0
- django_spire/contrib/seeding/tests/test_override.py +63 -0
- django_spire/contrib/service/__init__.py +2 -2
- django_spire/contrib/service/django_model_service.py +16 -15
- django_spire/contrib/service/exceptions.py +5 -3
- django_spire/contrib/service/tests/__init__.py +0 -0
- django_spire/contrib/service/tests/test_service.py +153 -0
- django_spire/contrib/session/apps.py +2 -0
- django_spire/contrib/session/controller.py +48 -42
- django_spire/contrib/session/templatetags/session_tags.py +11 -2
- django_spire/contrib/session/tests/test_session_controller.py +117 -53
- django_spire/contrib/tests/__init__.py +0 -0
- django_spire/contrib/tests/test_utils.py +37 -0
- django_spire/contrib/utils.py +4 -1
- django_spire/core/apps.py +2 -0
- django_spire/core/converters/tests/test_to_data.py +353 -0
- django_spire/core/converters/tests/test_to_enums.py +61 -41
- django_spire/core/converters/tests/test_to_pydantic.py +138 -109
- django_spire/core/converters/to_data.py +29 -10
- django_spire/core/converters/to_enums.py +4 -2
- django_spire/core/converters/to_pydantic.py +22 -22
- django_spire/core/decorators.py +19 -6
- django_spire/core/forms/widgets.py +4 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/apps.py.template +2 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/__init__.py.template +2 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/form_urls.py.template +2 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/page_urls.py.template +2 -0
- django_spire/core/maps.py +3 -1
- django_spire/core/middleware/maintenance.py +3 -3
- django_spire/core/middleware.py +8 -6
- django_spire/core/redirect/__init__.py +5 -0
- django_spire/core/redirect/generic_redirect.py +1 -2
- django_spire/core/redirect/tests/__init__.py +0 -0
- django_spire/core/redirect/tests/test_generic_redirect.py +34 -0
- django_spire/core/{tests/tests_redirect.py → redirect/tests/test_safe_redirect.py} +55 -81
- django_spire/core/shortcuts.py +3 -3
- django_spire/core/static/django_spire/css/app-layout.css +1 -1
- django_spire/core/static/django_spire/css/app-navigation.css +3 -3
- django_spire/core/static/django_spire/css/bootstrap-override.css +4 -0
- django_spire/core/tag/admin.py +12 -0
- django_spire/core/tag/intelligence/tag_set_bot.py +2 -0
- django_spire/core/tag/mixins.py +2 -0
- django_spire/core/tag/models.py +2 -0
- django_spire/core/tag/querysets.py +2 -0
- django_spire/core/tag/service/tag_service.py +6 -3
- django_spire/core/tag/tests/test_intelligence.py +9 -9
- django_spire/core/tag/tests/test_tags.py +44 -54
- django_spire/core/tag/tests/test_tools.py +191 -0
- django_spire/core/tag/tools.py +3 -0
- django_spire/core/templates/django_spire/card/card.html +5 -2
- django_spire/core/templates/django_spire/card/title_card.html +12 -4
- django_spire/core/templates/django_spire/infinite_scroll/base.html +1 -0
- django_spire/core/templates/django_spire/navigation/side_navigation.html +19 -24
- django_spire/core/templates/django_spire/page/full_page.html +46 -16
- django_spire/core/templates/django_spire/table/base.html +5 -3
- django_spire/core/templatetags/json.py +6 -2
- django_spire/core/templatetags/message.py +13 -8
- django_spire/core/templatetags/string_formating.py +8 -5
- django_spire/core/templatetags/tests/__init__.py +0 -0
- django_spire/core/templatetags/tests/test_templatetags.py +427 -0
- django_spire/core/templatetags/variable_types.py +17 -9
- django_spire/core/tests/test_cases.py +1 -1
- django_spire/core/tests/test_conf.py +43 -0
- django_spire/core/tests/test_consts.py +28 -0
- django_spire/core/tests/test_context_processors.py +93 -0
- django_spire/core/tests/test_decorators.py +95 -0
- django_spire/core/tests/test_django_spire_utils.py +56 -0
- django_spire/core/tests/test_exceptions.py +37 -0
- django_spire/core/tests/test_models.py +54 -0
- django_spire/core/tests/test_settings.py +45 -0
- django_spire/core/tests/test_shortcuts.py +74 -0
- django_spire/core/tests/test_urls.py +16 -0
- django_spire/core/tests/test_utils.py +58 -0
- django_spire/core/urls.py +4 -1
- django_spire/core/utils.py +12 -8
- django_spire/exceptions.py +16 -1
- django_spire/file/admin.py +4 -2
- django_spire/file/apps.py +8 -10
- django_spire/file/fields.py +7 -7
- django_spire/file/forms.py +1 -1
- django_spire/file/interfaces.py +15 -15
- django_spire/file/mixins.py +1 -4
- django_spire/file/models.py +3 -5
- django_spire/file/tests/factories.py +59 -0
- django_spire/file/tests/test_admin.py +69 -0
- django_spire/file/tests/test_apps.py +24 -0
- django_spire/file/tests/test_fields.py +114 -0
- django_spire/file/tests/test_forms.py +20 -0
- django_spire/file/tests/test_interfaces.py +183 -0
- django_spire/file/tests/test_models.py +82 -0
- django_spire/file/tests/test_querysets.py +102 -0
- django_spire/file/tests/test_utils.py +32 -0
- django_spire/file/tests/test_views.py +145 -0
- django_spire/file/tests/test_widgets.py +82 -0
- django_spire/file/tools.py +8 -2
- django_spire/file/views.py +7 -3
- django_spire/file/widgets.py +12 -12
- django_spire/help_desk/admin.py +15 -0
- django_spire/help_desk/apps.py +2 -0
- django_spire/help_desk/auth/controller.py +2 -0
- django_spire/help_desk/choices.py +2 -0
- django_spire/help_desk/enums.py +2 -0
- django_spire/help_desk/exceptions.py +31 -3
- django_spire/help_desk/forms.py +2 -0
- django_spire/help_desk/models.py +2 -0
- django_spire/help_desk/querysets.py +4 -1
- django_spire/help_desk/services/notification_service.py +26 -27
- django_spire/help_desk/services/service.py +2 -3
- django_spire/help_desk/tests/factories.py +8 -3
- django_spire/help_desk/tests/test_admin.py +41 -0
- django_spire/help_desk/tests/test_apps.py +41 -0
- django_spire/help_desk/tests/test_choices.py +50 -0
- django_spire/help_desk/tests/test_controller.py +87 -0
- django_spire/help_desk/tests/test_enums.py +18 -0
- django_spire/help_desk/tests/test_exceptions.py +37 -0
- django_spire/help_desk/tests/test_forms.py +89 -0
- django_spire/help_desk/tests/test_models.py +59 -0
- django_spire/help_desk/tests/test_querysets.py +38 -0
- django_spire/help_desk/tests/test_services/test_notification_service.py +15 -8
- django_spire/help_desk/tests/test_services/test_service.py +92 -0
- django_spire/help_desk/tests/test_urls/test_form_urls.py +6 -6
- django_spire/help_desk/tests/test_urls/test_page_urls.py +8 -9
- django_spire/help_desk/tests/test_views/test_form_views.py +46 -19
- django_spire/help_desk/tests/test_views/test_page_views.py +32 -9
- django_spire/help_desk/urls/__init__.py +4 -1
- django_spire/help_desk/urls/form_urls.py +3 -0
- django_spire/help_desk/urls/page_urls.py +3 -0
- django_spire/help_desk/views/form_views.py +13 -5
- django_spire/help_desk/views/page_views.py +11 -3
- django_spire/history/activity/admin.py +2 -0
- django_spire/history/activity/apps.py +3 -1
- django_spire/history/activity/mixins.py +13 -7
- django_spire/history/activity/models.py +6 -5
- django_spire/history/activity/querysets.py +2 -0
- django_spire/history/activity/tests/__init__.py +0 -0
- django_spire/history/activity/tests/test_activity.py +176 -0
- django_spire/history/admin.py +9 -2
- django_spire/history/choices.py +3 -0
- django_spire/history/models.py +5 -5
- django_spire/history/tests/test_admin.py +93 -0
- django_spire/history/tests/test_history.py +101 -0
- django_spire/history/tests/test_mixins.py +84 -0
- django_spire/history/viewed/admin.py +3 -1
- django_spire/history/viewed/apps.py +3 -1
- django_spire/history/viewed/models.py +2 -0
- django_spire/history/viewed/tests/__init__.py +0 -0
- django_spire/history/viewed/tests/test_viewed.py +46 -0
- django_spire/knowledge/auth/tests/__init__.py +0 -0
- django_spire/knowledge/auth/tests/test_controller.py +116 -0
- django_spire/knowledge/collection/admin.py +5 -1
- django_spire/knowledge/collection/models.py +3 -1
- django_spire/knowledge/collection/seeding/seed.py +1 -0
- django_spire/knowledge/collection/services/factory_service.py +10 -11
- django_spire/knowledge/collection/services/ordering_service.py +1 -2
- django_spire/knowledge/collection/services/service.py +5 -10
- django_spire/knowledge/collection/services/tag_service.py +5 -2
- django_spire/knowledge/collection/tests/factories.py +28 -1
- django_spire/knowledge/collection/tests/test_models.py +48 -0
- django_spire/knowledge/collection/tests/test_querysets.py +93 -0
- django_spire/knowledge/collection/tests/test_services/test_factory_service.py +100 -0
- django_spire/knowledge/collection/tests/test_services/test_services.py +160 -0
- django_spire/knowledge/collection/tests/test_urls/test_form_urls.py +21 -3
- django_spire/knowledge/collection/tests/test_urls/test_json_urls.py +39 -1
- django_spire/knowledge/collection/tests/test_urls/test_page_urls.py +12 -4
- django_spire/knowledge/collection/urls/__init__.py +3 -0
- django_spire/knowledge/collection/urls/form_urls.py +2 -0
- django_spire/knowledge/collection/urls/json_urls.py +2 -0
- django_spire/knowledge/collection/urls/page_urls.py +2 -0
- django_spire/knowledge/collection/views/form_views.py +4 -4
- django_spire/knowledge/collection/views/json_views.py +5 -1
- django_spire/knowledge/collection/views/page_views.py +5 -2
- django_spire/knowledge/entry/admin.py +7 -1
- django_spire/knowledge/entry/forms.py +2 -0
- django_spire/knowledge/entry/models.py +2 -0
- django_spire/knowledge/entry/seeding/seed.py +3 -0
- django_spire/knowledge/entry/services/automation_service.py +5 -4
- django_spire/knowledge/entry/services/factory_service.py +7 -5
- django_spire/knowledge/entry/services/service.py +4 -7
- django_spire/knowledge/entry/services/tag_service.py +0 -1
- django_spire/knowledge/entry/services/tool_service.py +1 -0
- django_spire/knowledge/entry/services/transformation_services.py +1 -5
- django_spire/knowledge/entry/tests/factories.py +1 -2
- django_spire/knowledge/entry/tests/test_factory_service.py +20 -0
- django_spire/knowledge/entry/tests/test_models.py +41 -0
- django_spire/knowledge/entry/tests/test_querysets.py +71 -0
- django_spire/knowledge/entry/tests/test_services.py +94 -0
- django_spire/knowledge/entry/tests/test_urls/test_form_urls.py +9 -14
- django_spire/knowledge/entry/tests/test_urls/test_json_urls.py +48 -5
- django_spire/knowledge/entry/tests/test_urls/test_page_urls.py +6 -8
- django_spire/knowledge/entry/tests/test_urls/test_template_urls.py +40 -0
- django_spire/knowledge/entry/urls/form_urls.py +2 -0
- django_spire/knowledge/entry/urls/json_urls.py +2 -0
- django_spire/knowledge/entry/urls/page_urls.py +2 -0
- django_spire/knowledge/entry/urls/template_urls.py +2 -0
- django_spire/knowledge/entry/version/block/choices.py +2 -0
- django_spire/knowledge/entry/version/block/data/data.py +1 -0
- django_spire/knowledge/entry/version/block/data/list/data.py +8 -13
- django_spire/knowledge/entry/version/block/data/list/maps.py +3 -0
- django_spire/knowledge/entry/version/block/data/list/meta.py +1 -2
- django_spire/knowledge/entry/version/block/data/list/tests/__init__.py +0 -0
- django_spire/knowledge/entry/version/block/data/list/tests/test_maps.py +32 -0
- django_spire/knowledge/entry/version/block/data/list/tests/test_meta.py +58 -0
- django_spire/knowledge/entry/version/block/data/maps.py +3 -6
- django_spire/knowledge/entry/version/block/models.py +7 -5
- django_spire/knowledge/entry/version/block/seeding/constants.py +5 -4
- django_spire/knowledge/entry/version/block/services/service.py +2 -3
- django_spire/knowledge/entry/version/block/tests/factories.py +4 -10
- django_spire/knowledge/entry/version/block/tests/test_choices.py +56 -0
- django_spire/knowledge/entry/version/block/tests/test_data.py +90 -0
- django_spire/knowledge/entry/version/block/tests/test_maps.py +37 -0
- django_spire/knowledge/entry/version/block/tests/test_models.py +55 -0
- django_spire/knowledge/entry/version/block/tests/test_querysets.py +35 -0
- django_spire/knowledge/entry/version/block/tests/test_services.py +65 -0
- django_spire/knowledge/entry/version/choices.py +2 -0
- django_spire/knowledge/entry/version/converters/converter.py +1 -1
- django_spire/knowledge/entry/version/converters/docx_converter.py +4 -7
- django_spire/knowledge/entry/version/converters/markdown_converter.py +20 -20
- django_spire/knowledge/entry/version/maps.py +4 -5
- django_spire/knowledge/entry/version/querysets.py +1 -1
- django_spire/knowledge/entry/version/seeding/seeder.py +1 -2
- django_spire/knowledge/entry/version/services/processor_service.py +5 -4
- django_spire/knowledge/entry/version/services/service.py +1 -2
- django_spire/knowledge/entry/version/tests/factories.py +2 -2
- django_spire/knowledge/entry/version/tests/test_choices.py +18 -0
- django_spire/knowledge/entry/version/tests/test_converters/test_docx_converter.py +56 -8
- django_spire/knowledge/entry/version/tests/test_converters/test_markdown_converter.py +78 -0
- django_spire/knowledge/entry/version/tests/test_maps.py +58 -0
- django_spire/knowledge/entry/version/tests/test_models.py +23 -0
- django_spire/knowledge/entry/version/tests/test_querysets.py +26 -0
- django_spire/knowledge/entry/version/tests/test_services.py +62 -0
- django_spire/knowledge/entry/version/tests/test_urls/test_json_urls.py +27 -8
- django_spire/knowledge/entry/version/tests/test_urls/test_page_urls.py +15 -8
- django_spire/knowledge/entry/version/tests/test_urls/test_redirect_urls.py +38 -0
- django_spire/knowledge/entry/version/urls/__init__.py +3 -0
- django_spire/knowledge/entry/version/urls/json_urls.py +2 -1
- django_spire/knowledge/entry/version/urls/page_urls.py +2 -0
- django_spire/knowledge/entry/version/urls/redirect_urls.py +2 -0
- django_spire/knowledge/entry/version/views/json_views.py +5 -1
- django_spire/knowledge/entry/version/views/page_views.py +10 -3
- django_spire/knowledge/entry/version/views/redirect_views.py +5 -1
- django_spire/knowledge/entry/views/form_views.py +16 -8
- django_spire/knowledge/entry/views/json_views.py +3 -1
- django_spire/knowledge/entry/views/page_views.py +8 -2
- django_spire/knowledge/entry/views/template_views.py +7 -1
- django_spire/knowledge/exceptions.py +2 -1
- django_spire/knowledge/intelligence/intel/answer_intel.py +2 -1
- django_spire/knowledge/intelligence/intel/entry_intel.py +0 -1
- django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +4 -5
- django_spire/knowledge/models.py +1 -2
- django_spire/knowledge/tests/__init__.py +0 -0
- django_spire/knowledge/tests/test_templatetags.py +40 -0
- django_spire/knowledge/tests/test_urls/__init__.py +0 -0
- django_spire/knowledge/tests/test_urls/test_page_urls.py +24 -0
- django_spire/knowledge/urls/__init__.py +2 -0
- django_spire/knowledge/urls/page_urls.py +2 -0
- django_spire/knowledge/views/page_views.py +8 -3
- django_spire/notification/admin.py +3 -1
- django_spire/notification/app/admin.py +2 -0
- django_spire/notification/app/apps.py +3 -1
- django_spire/notification/app/exceptions.py +9 -2
- django_spire/notification/app/models.py +8 -4
- django_spire/notification/app/processor.py +22 -26
- django_spire/notification/app/querysets.py +2 -0
- django_spire/notification/app/tests/__init__.py +0 -0
- django_spire/notification/app/tests/factories.py +34 -0
- django_spire/notification/app/tests/test_apps.py +24 -0
- django_spire/notification/app/tests/test_models.py +72 -0
- django_spire/notification/app/tests/test_processor.py +111 -0
- django_spire/notification/app/tests/test_querysets.py +90 -0
- django_spire/notification/app/tests/test_views/__init__.py +0 -0
- django_spire/notification/app/tests/test_views/test_json_views.py +48 -0
- django_spire/notification/app/tests/test_views/test_page_views.py +19 -0
- django_spire/notification/app/urls/__init__.py +3 -1
- django_spire/notification/app/urls/json_urls.py +6 -4
- django_spire/notification/app/urls/page_urls.py +4 -3
- django_spire/notification/app/urls/template_urls.py +4 -2
- django_spire/notification/apps.py +4 -1
- django_spire/notification/email/admin.py +5 -1
- django_spire/notification/email/apps.py +3 -1
- django_spire/notification/email/exceptions.py +4 -2
- django_spire/notification/email/helper.py +5 -3
- django_spire/notification/email/models.py +4 -0
- django_spire/notification/email/processor.py +19 -15
- django_spire/notification/email/querysets.py +3 -0
- django_spire/notification/email/tests/__init__.py +0 -0
- django_spire/notification/email/tests/factories.py +35 -0
- django_spire/notification/email/tests/test_apps.py +24 -0
- django_spire/notification/email/tests/test_models.py +52 -0
- django_spire/notification/email/tests/test_processor.py +92 -0
- django_spire/notification/email/tests/test_querysets.py +43 -0
- django_spire/notification/exceptions.py +17 -2
- django_spire/notification/managers.py +7 -1
- django_spire/notification/maps.py +4 -1
- django_spire/notification/mixins.py +2 -0
- django_spire/notification/models.py +3 -1
- django_spire/notification/processors/notification.py +12 -5
- django_spire/notification/processors/processor.py +2 -0
- django_spire/notification/processors/tests/__init__.py +0 -0
- django_spire/notification/processors/tests/test_notification.py +106 -0
- django_spire/notification/push/admin.py +10 -1
- django_spire/notification/push/apps.py +3 -1
- django_spire/notification/push/models.py +2 -3
- django_spire/notification/push/tests/__init__.py +0 -0
- django_spire/notification/push/tests/test_apps.py +24 -0
- django_spire/notification/push/tests/test_models.py +28 -0
- django_spire/notification/querysets.py +7 -1
- django_spire/notification/sms/admin.py +2 -0
- django_spire/notification/sms/apps.py +4 -1
- django_spire/notification/sms/automations.py +2 -0
- django_spire/notification/sms/choices.py +2 -0
- django_spire/notification/sms/exceptions.py +19 -5
- django_spire/notification/sms/helper.py +33 -23
- django_spire/notification/sms/models.py +5 -1
- django_spire/notification/sms/processor.py +20 -20
- django_spire/notification/sms/querysets.py +2 -0
- django_spire/notification/sms/tests/factories.py +33 -0
- django_spire/notification/sms/tests/test_apps.py +24 -0
- django_spire/notification/sms/tests/test_automation.py +38 -0
- django_spire/notification/sms/tests/test_choices.py +15 -0
- django_spire/notification/sms/tests/test_consts.py +17 -0
- django_spire/notification/sms/tests/test_exceptions.py +27 -0
- django_spire/notification/sms/tests/test_helper.py +50 -0
- django_spire/notification/sms/tests/test_models.py +81 -0
- django_spire/notification/sms/tests/test_processor.py +107 -0
- django_spire/notification/sms/tests/test_tools.py +25 -11
- django_spire/notification/sms/tools.py +16 -5
- django_spire/notification/sms/urls/__init__.py +3 -1
- django_spire/notification/sms/urls/media_urls.py +2 -0
- django_spire/notification/sms/views/media_views.py +14 -4
- django_spire/notification/tests/__init__.py +0 -0
- django_spire/notification/tests/factories.py +26 -0
- django_spire/notification/tests/test_admin.py +55 -0
- django_spire/notification/tests/test_apps.py +30 -0
- django_spire/notification/tests/test_automation.py +18 -0
- django_spire/notification/tests/test_choices.py +59 -0
- django_spire/notification/tests/test_exceptions.py +58 -0
- django_spire/notification/tests/test_managers.py +100 -0
- django_spire/notification/tests/test_maps.py +31 -0
- django_spire/notification/tests/test_models.py +76 -0
- django_spire/notification/tests/test_querysets.py +184 -0
- django_spire/notification/tests/test_utils.py +23 -0
- django_spire/notification/urls.py +3 -1
- django_spire/notification/utils.py +3 -1
- django_spire/settings.py +3 -0
- django_spire/theme/tests/test_context_processor.py +15 -13
- django_spire/theme/tests/test_enums.py +2 -2
- django_spire/theme/tests/test_filesystem.py +2 -5
- django_spire/theme/tests/test_integration.py +12 -12
- django_spire/theme/tests/test_model.py +40 -38
- django_spire/theme/tests/test_views/test_json_views.py +33 -33
- django_spire/theme/urls/json_urls.py +3 -0
- django_spire/theme/urls/page_urls.py +3 -0
- django_spire/urls.py +19 -15
- django_spire/utils.py +13 -4
- {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/METADATA +2 -2
- {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/RECORD +534 -362
- {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/licenses/LICENSE.md +1 -1
- django_spire/contrib/options/tests/test_unit.py +0 -148
- django_spire/contrib/seeding/tests/test_seeding.py +0 -25
- django_spire/core/tests/test_templatetags.py +0 -117
- django_spire/core/tests/tests_shortcuts.py +0 -73
- django_spire/history/activity/tests.py +0 -3
- django_spire/history/activity/views.py +0 -3
- django_spire/knowledge/collection/tests/test_services/test_transformation_service.py +0 -71
- django_spire/notification/app/tests.py +0 -3
- {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/WHEEL +0 -0
- {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/top_level.txt +0 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import json
|
|
2
4
|
import uuid
|
|
3
5
|
|
|
@@ -7,12 +9,14 @@ from django import forms
|
|
|
7
9
|
class JsonTreeWidget(forms.Textarea):
|
|
8
10
|
def __init__(self, *args, **kwargs):
|
|
9
11
|
super().__init__(*args, **kwargs)
|
|
12
|
+
|
|
10
13
|
self.template_name = 'django_spire/forms/widgets/json_tree_widget.html'
|
|
11
14
|
|
|
12
15
|
def get_context(self, *args, **kwargs):
|
|
13
16
|
context = super().get_context(*args, **kwargs)
|
|
14
17
|
context['open_dropdowns'] = True
|
|
15
18
|
context['widget_render_uuid'] = uuid.uuid4()
|
|
19
|
+
|
|
16
20
|
if context['widget']['value'] is None:
|
|
17
21
|
context['json_tree_dict'] = {}
|
|
18
22
|
else:
|
django_spire/core/maps.py
CHANGED
|
@@ -2,10 +2,10 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
|
-
from django.conf import settings
|
|
6
|
-
from django.template.response import TemplateResponse
|
|
5
|
+
# from django.conf import settings
|
|
6
|
+
# from django.template.response import TemplateResponse
|
|
7
7
|
|
|
8
|
-
from django_spire.consts import MAINTENANCE_MODE_SETTINGS_NAME
|
|
8
|
+
# from django_spire.consts import MAINTENANCE_MODE_SETTINGS_NAME
|
|
9
9
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
11
11
|
from django.core.handlers.wsgi import WSGIRequest
|
django_spire/core/middleware.py
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
3
|
+
from typing import TYPE_CHECKING, Callable
|
|
4
4
|
|
|
5
|
-
from django.conf import settings
|
|
6
|
-
from django.template.response import TemplateResponse
|
|
5
|
+
# from django.conf import settings
|
|
6
|
+
# from django.template.response import TemplateResponse
|
|
7
7
|
|
|
8
|
-
from django_spire.consts import MAINTENANCE_MODE_SETTINGS_NAME
|
|
8
|
+
# from django_spire.consts import MAINTENANCE_MODE_SETTINGS_NAME
|
|
9
9
|
|
|
10
10
|
if TYPE_CHECKING:
|
|
11
11
|
from django.core.handlers.wsgi import WSGIRequest
|
|
12
|
+
from django.http import HttpResponse
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
class MaintenanceMiddleware:
|
|
15
|
-
def __init__(self, get_response):
|
|
16
|
+
def __init__(self, get_response: Callable[[WSGIRequest], HttpResponse]) -> None:
|
|
16
17
|
self.get_response = get_response
|
|
17
18
|
|
|
18
|
-
def __call__(self, request: WSGIRequest):
|
|
19
|
+
def __call__(self, request: WSGIRequest) -> HttpResponse:
|
|
19
20
|
# maintenance_mode = getattr(settings, MAINTENANCE_MODE_SETTINGS_NAME)
|
|
20
21
|
#
|
|
21
22
|
# if maintenance_mode is None:
|
|
@@ -27,4 +28,5 @@ class MaintenanceMiddleware:
|
|
|
27
28
|
# template='django_spire/page/maintenance_page.html',
|
|
28
29
|
# )
|
|
29
30
|
#
|
|
31
|
+
|
|
30
32
|
return self.get_response(request)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from operator import attrgetter
|
|
3
4
|
from typing import Any
|
|
4
5
|
|
|
5
6
|
from django.http import HttpResponse
|
|
@@ -7,8 +8,6 @@ from django.urls import reverse
|
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
def reverse_generic_relation(content_object: Any, **kwargs) -> HttpResponse | None:
|
|
10
|
-
from operator import attrgetter
|
|
11
|
-
|
|
12
11
|
model_name = content_object.__class__.__name__.lower()
|
|
13
12
|
|
|
14
13
|
CONTENT_OBJECT_URL_MAP = {
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from unittest.mock import MagicMock
|
|
6
|
+
|
|
7
|
+
from django.test import TestCase
|
|
8
|
+
|
|
9
|
+
from django_spire.core.redirect.generic_redirect import reverse_generic_relation
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestReverseGenericRelation(TestCase):
|
|
13
|
+
def test_model_not_in_map_raises_key_error(self) -> None:
|
|
14
|
+
"""
|
|
15
|
+
Since CONTENT_OBJECT_URL_MAP is empty by default,
|
|
16
|
+
any model lookup raises KeyError.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
content_object = MagicMock()
|
|
20
|
+
content_object.__class__.__name__ = 'UnknownModel'
|
|
21
|
+
|
|
22
|
+
with pytest.raises(KeyError):
|
|
23
|
+
reverse_generic_relation(content_object)
|
|
24
|
+
|
|
25
|
+
def test_uses_lowercase_model_name(self) -> None:
|
|
26
|
+
"""Verifies the model name is lowercased for map lookup."""
|
|
27
|
+
|
|
28
|
+
content_object = MagicMock()
|
|
29
|
+
content_object.__class__.__name__ = 'MyModel'
|
|
30
|
+
|
|
31
|
+
with pytest.raises(KeyError) as exc_info:
|
|
32
|
+
reverse_generic_relation(content_object)
|
|
33
|
+
|
|
34
|
+
assert exc_info.value.args[0] == 'mymodel'
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from django.test import override_settings, RequestFactory
|
|
4
|
-
from django.urls import reverse
|
|
5
4
|
|
|
6
5
|
from django_spire.core.redirect.safe_redirect import (
|
|
7
6
|
is_url_valid_and_safe,
|
|
@@ -15,53 +14,28 @@ class UrlsTestCase(BaseTestCase):
|
|
|
15
14
|
def setUp(self) -> None:
|
|
16
15
|
super().setUp()
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
# resolved_url = resolve_url(valid_url)
|
|
21
|
-
# self.assertEqual(resolved_url, reverse(valid_url))
|
|
17
|
+
def test_is_url_valid_and_safe_empty_url(self) -> None:
|
|
18
|
+
assert is_url_valid_and_safe('', {'localhost'}) is False
|
|
22
19
|
|
|
23
|
-
def
|
|
24
|
-
|
|
25
|
-
resolved_url = resolve_url(invalid_url)
|
|
26
|
-
self.assertEqual(resolved_url, invalid_url)
|
|
20
|
+
def test_is_url_valid_and_safe_invalid_host(self) -> None:
|
|
21
|
+
url = 'http://invalid.com'
|
|
27
22
|
|
|
28
|
-
|
|
29
|
-
self.assertFalse(
|
|
30
|
-
is_url_valid_and_safe(
|
|
31
|
-
'',
|
|
32
|
-
{'localhost'}
|
|
33
|
-
)
|
|
34
|
-
)
|
|
23
|
+
assert is_url_valid_and_safe(url, {'example.com'}) is False
|
|
35
24
|
|
|
36
25
|
def test_is_url_valid_and_safe_invalid_scheme(self) -> None:
|
|
37
26
|
url = 'ftp://example.com'
|
|
38
27
|
|
|
39
|
-
|
|
40
|
-
is_url_valid_and_safe(
|
|
41
|
-
url,
|
|
42
|
-
{'example.com'}
|
|
43
|
-
)
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
def test_is_url_valid_and_safe_invalid_host(self) -> None:
|
|
47
|
-
url = 'http://invalid.com'
|
|
48
|
-
|
|
49
|
-
self.assertFalse(
|
|
50
|
-
is_url_valid_and_safe(
|
|
51
|
-
url,
|
|
52
|
-
{'example.com'}
|
|
53
|
-
)
|
|
54
|
-
)
|
|
28
|
+
assert is_url_valid_and_safe(url, {'example.com'}) is False
|
|
55
29
|
|
|
56
30
|
def test_is_url_valid_and_safe_valid_url(self) -> None:
|
|
57
31
|
url = 'http://example.com'
|
|
58
32
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
33
|
+
assert is_url_valid_and_safe(url, {'example.com'}) is True
|
|
34
|
+
|
|
35
|
+
def test_resolve_invalid_url(self) -> None:
|
|
36
|
+
invalid_url = 'invalid:url'
|
|
37
|
+
resolved_url = resolve_url(invalid_url)
|
|
38
|
+
assert resolved_url == invalid_url
|
|
65
39
|
|
|
66
40
|
|
|
67
41
|
@override_settings(ALLOWED_HOSTS=['example.com'])
|
|
@@ -71,25 +45,25 @@ class ReturnUrlTestCase(BaseTestCase):
|
|
|
71
45
|
|
|
72
46
|
self.request_factory = RequestFactory()
|
|
73
47
|
|
|
74
|
-
def
|
|
48
|
+
def test_safe_redirect_url_invalid_return_url(self) -> None:
|
|
75
49
|
request = self.request_factory.get(
|
|
76
50
|
'/some_path',
|
|
77
|
-
{'return_url': 'http://
|
|
51
|
+
{'return_url': 'http://invalid.com'}
|
|
78
52
|
)
|
|
79
53
|
|
|
80
54
|
request.META['HTTP_HOST'] = 'example.com'
|
|
81
55
|
response = safe_redirect_url(request)
|
|
82
|
-
|
|
56
|
+
assert response == '/'
|
|
83
57
|
|
|
84
|
-
def
|
|
58
|
+
def test_safe_redirect_url_return_url_with_encoded_characters(self) -> None:
|
|
85
59
|
request = self.request_factory.get(
|
|
86
60
|
'/some_path',
|
|
87
|
-
{'return_url': 'http://
|
|
61
|
+
{'return_url': 'http://example.com/valid%20path'}
|
|
88
62
|
)
|
|
89
63
|
|
|
90
64
|
request.META['HTTP_HOST'] = 'example.com'
|
|
91
65
|
response = safe_redirect_url(request)
|
|
92
|
-
|
|
66
|
+
assert response == 'http://example.com/valid%20path'
|
|
93
67
|
|
|
94
68
|
def test_safe_redirect_url_return_url_with_invalid_characters(self) -> None:
|
|
95
69
|
request = self.request_factory.get(
|
|
@@ -99,17 +73,17 @@ class ReturnUrlTestCase(BaseTestCase):
|
|
|
99
73
|
|
|
100
74
|
request.META['HTTP_HOST'] = 'example.com'
|
|
101
75
|
response = safe_redirect_url(request)
|
|
102
|
-
|
|
76
|
+
assert response == '/'
|
|
103
77
|
|
|
104
|
-
def
|
|
78
|
+
def test_safe_redirect_url_return_url_with_javascript_code(self) -> None:
|
|
105
79
|
request = self.request_factory.get(
|
|
106
80
|
'/some_path',
|
|
107
|
-
{'return_url': '
|
|
81
|
+
{'return_url': 'javascript:alert("XSS")'}
|
|
108
82
|
)
|
|
109
83
|
|
|
110
84
|
request.META['HTTP_HOST'] = 'example.com'
|
|
111
85
|
response = safe_redirect_url(request)
|
|
112
|
-
|
|
86
|
+
assert response == '/'
|
|
113
87
|
|
|
114
88
|
def test_safe_redirect_url_return_url_with_utf8_characters(self) -> None:
|
|
115
89
|
request = self.request_factory.get(
|
|
@@ -119,17 +93,17 @@ class ReturnUrlTestCase(BaseTestCase):
|
|
|
119
93
|
|
|
120
94
|
request.META['HTTP_HOST'] = 'example.com'
|
|
121
95
|
response = safe_redirect_url(request)
|
|
122
|
-
|
|
96
|
+
assert response == 'http://example.com/ümlaut'
|
|
123
97
|
|
|
124
|
-
def
|
|
98
|
+
def test_safe_redirect_url_valid_return_url(self) -> None:
|
|
125
99
|
request = self.request_factory.get(
|
|
126
100
|
'/some_path',
|
|
127
|
-
{'return_url': '
|
|
101
|
+
{'return_url': 'http://example.com/valid'}
|
|
128
102
|
)
|
|
129
103
|
|
|
130
104
|
request.META['HTTP_HOST'] = 'example.com'
|
|
131
105
|
response = safe_redirect_url(request)
|
|
132
|
-
|
|
106
|
+
assert response == 'http://example.com/valid'
|
|
133
107
|
|
|
134
108
|
|
|
135
109
|
@override_settings(ALLOWED_HOSTS=['example.com'])
|
|
@@ -139,15 +113,13 @@ class RefererTestCase(BaseTestCase):
|
|
|
139
113
|
|
|
140
114
|
self.request_factory = RequestFactory()
|
|
141
115
|
|
|
142
|
-
def
|
|
143
|
-
request = self.request_factory.get(
|
|
144
|
-
'/some_path',
|
|
145
|
-
{'return_url': 'http://example.com/valid'}
|
|
146
|
-
)
|
|
147
|
-
|
|
116
|
+
def test_safe_redirect_url_invalid_referer(self) -> None:
|
|
117
|
+
request = self.request_factory.get('/some_path')
|
|
148
118
|
request.META['HTTP_HOST'] = 'example.com'
|
|
119
|
+
request.META['HTTP_REFERER'] = 'http://invalid.com'
|
|
120
|
+
|
|
149
121
|
response = safe_redirect_url(request)
|
|
150
|
-
|
|
122
|
+
assert response == '/'
|
|
151
123
|
|
|
152
124
|
def test_safe_redirect_url_invalid_return_url(self) -> None:
|
|
153
125
|
request = self.request_factory.get(
|
|
@@ -157,30 +129,22 @@ class RefererTestCase(BaseTestCase):
|
|
|
157
129
|
|
|
158
130
|
request.META['HTTP_HOST'] = 'example.com'
|
|
159
131
|
response = safe_redirect_url(request)
|
|
160
|
-
|
|
132
|
+
assert response == '/'
|
|
161
133
|
|
|
162
|
-
def
|
|
163
|
-
request = self.request_factory.get('/some_path')
|
|
164
|
-
request.META['HTTP_HOST'] = 'example.com'
|
|
165
|
-
request.META['HTTP_REFERER'] = 'http://example.com/valid'
|
|
166
|
-
|
|
167
|
-
response = safe_redirect_url(request)
|
|
168
|
-
self.assertEqual(response, 'http://example.com/valid')
|
|
169
|
-
|
|
170
|
-
def test_safe_redirect_url_invalid_referer(self) -> None:
|
|
134
|
+
def test_safe_redirect_url_no_return_url_or_referer(self) -> None:
|
|
171
135
|
request = self.request_factory.get('/some_path')
|
|
172
136
|
request.META['HTTP_HOST'] = 'example.com'
|
|
173
|
-
request.META['HTTP_REFERER'] = 'http://invalid.com'
|
|
174
137
|
|
|
175
138
|
response = safe_redirect_url(request)
|
|
176
|
-
|
|
139
|
+
assert response == '/'
|
|
177
140
|
|
|
178
|
-
def
|
|
141
|
+
def test_safe_redirect_url_referer_with_encoded_characters(self) -> None:
|
|
179
142
|
request = self.request_factory.get('/some_path')
|
|
180
143
|
request.META['HTTP_HOST'] = 'example.com'
|
|
144
|
+
request.META['HTTP_REFERER'] = 'http://example.com/valid%20path'
|
|
181
145
|
|
|
182
146
|
response = safe_redirect_url(request)
|
|
183
|
-
|
|
147
|
+
assert response == 'http://example.com/valid%20path'
|
|
184
148
|
|
|
185
149
|
def test_safe_redirect_url_referer_with_invalid_characters(self) -> None:
|
|
186
150
|
request = self.request_factory.get('/some_path')
|
|
@@ -188,7 +152,7 @@ class RefererTestCase(BaseTestCase):
|
|
|
188
152
|
request.META['HTTP_REFERER'] = 'http://example.com/invalid<>chars'
|
|
189
153
|
|
|
190
154
|
response = safe_redirect_url(request)
|
|
191
|
-
|
|
155
|
+
assert response == '/'
|
|
192
156
|
|
|
193
157
|
def test_safe_redirect_url_referer_with_javascript_code(self) -> None:
|
|
194
158
|
request = self.request_factory.get('/some_path')
|
|
@@ -196,20 +160,30 @@ class RefererTestCase(BaseTestCase):
|
|
|
196
160
|
request.META['HTTP_REFERER'] = 'javascript:alert("XSS")'
|
|
197
161
|
|
|
198
162
|
response = safe_redirect_url(request)
|
|
199
|
-
|
|
163
|
+
assert response == '/'
|
|
200
164
|
|
|
201
|
-
def
|
|
165
|
+
def test_safe_redirect_url_referer_with_utf8_characters(self) -> None:
|
|
202
166
|
request = self.request_factory.get('/some_path')
|
|
203
167
|
request.META['HTTP_HOST'] = 'example.com'
|
|
204
|
-
request.META['HTTP_REFERER'] = 'http://example.com
|
|
168
|
+
request.META['HTTP_REFERER'] = 'http://example.com/ümlaut'
|
|
205
169
|
|
|
206
170
|
response = safe_redirect_url(request)
|
|
207
|
-
|
|
171
|
+
assert response == 'http://example.com/ümlaut'
|
|
208
172
|
|
|
209
|
-
def
|
|
173
|
+
def test_safe_redirect_url_valid_referer(self) -> None:
|
|
210
174
|
request = self.request_factory.get('/some_path')
|
|
211
175
|
request.META['HTTP_HOST'] = 'example.com'
|
|
212
|
-
request.META['HTTP_REFERER'] = 'http://example.com
|
|
176
|
+
request.META['HTTP_REFERER'] = 'http://example.com/valid'
|
|
213
177
|
|
|
214
178
|
response = safe_redirect_url(request)
|
|
215
|
-
|
|
179
|
+
assert response == 'http://example.com/valid'
|
|
180
|
+
|
|
181
|
+
def test_safe_redirect_url_valid_return_url(self) -> None:
|
|
182
|
+
request = self.request_factory.get(
|
|
183
|
+
'/some_path',
|
|
184
|
+
{'return_url': 'http://example.com/valid'}
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
request.META['HTTP_HOST'] = 'example.com'
|
|
188
|
+
response = safe_redirect_url(request)
|
|
189
|
+
assert response == 'http://example.com/valid'
|
django_spire/core/shortcuts.py
CHANGED
|
@@ -56,7 +56,7 @@ def get_object_or_none(model: type[T], pk: int, **kwargs) -> Model:
|
|
|
56
56
|
return None
|
|
57
57
|
|
|
58
58
|
|
|
59
|
-
def process_request_body(request: HttpRequest, key='data') -> Any:
|
|
59
|
+
def process_request_body(request: HttpRequest, key: str = 'data') -> Any:
|
|
60
60
|
"""
|
|
61
61
|
Processes the HTTP request body and returns the 'data' field from the parsed JSON.
|
|
62
62
|
|
|
@@ -68,8 +68,8 @@ def process_request_body(request: HttpRequest, key='data') -> Any:
|
|
|
68
68
|
|
|
69
69
|
if key:
|
|
70
70
|
return json.loads(body_unicode)[key]
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
|
|
72
|
+
return json.loads(body_unicode)
|
|
73
73
|
|
|
74
74
|
|
|
75
75
|
def model_object_from_app_label(
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
--app-footer-height: 70px;
|
|
3
3
|
--app-mobile-navigation-width: 250px;
|
|
4
4
|
--app-side-navigation-width: 250px;
|
|
5
|
-
--app-side-navigation-top-content-height:
|
|
5
|
+
--app-side-navigation-top-content-height: 100px;
|
|
6
6
|
--app-top-navigation-height: 80px;
|
|
7
7
|
|
|
8
8
|
--app-content-height: calc(100vh - var(--app-top-navigation-height) );
|
|
@@ -82,13 +82,13 @@
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
.side-navigation-fade-bottom {
|
|
85
|
-
background: linear-gradient(to top, var(--app-side-navigation-bg-color) 0%, transparent
|
|
86
|
-
height:
|
|
85
|
+
background: linear-gradient(to top, var(--app-side-navigation-bg-color) 0%, var(--app-side-navigation-bg-color) 20%, transparent 75%);
|
|
86
|
+
height: 20%;
|
|
87
87
|
pointer-events: none;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
.side-navigation-fade-top {
|
|
91
|
-
background: linear-gradient(to bottom, var(--app-side-navigation-bg-color) 0%, transparent 100%);
|
|
91
|
+
background: linear-gradient(to bottom, var(--app-side-navigation-bg-color) 0%, var(--app-side-navigation-bg-color) 20%, transparent 100%);
|
|
92
92
|
height: 60px;
|
|
93
93
|
pointer-events: none;
|
|
94
94
|
}
|
|
@@ -287,6 +287,10 @@
|
|
|
287
287
|
color: var(--app-default-text-color);
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
+
.table-container thead.sticky-top {
|
|
291
|
+
z-index: 1;
|
|
292
|
+
}
|
|
293
|
+
|
|
290
294
|
.ce-block__content [data-placeholder]:empty::before,
|
|
291
295
|
.ce-block__content [data-placeholder][data-empty="true"]::before {
|
|
292
296
|
color: var(--app-primary) !important;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django.contrib import admin
|
|
4
|
+
|
|
5
|
+
from django_spire.core.tag.models import Tag
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@admin.register(Tag)
|
|
9
|
+
class TagAdmin(admin.ModelAdmin):
|
|
10
|
+
list_display = ('name',)
|
|
11
|
+
ordering = ('name',)
|
|
12
|
+
search_fields = ('name',)
|
django_spire/core/tag/mixins.py
CHANGED
django_spire/core/tag/models.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
from
|
|
2
|
-
from typing import Generic
|
|
1
|
+
from __future__ import annotations
|
|
3
2
|
|
|
4
|
-
from
|
|
3
|
+
from abc import abstractmethod, ABC
|
|
4
|
+
from typing import Generic, TYPE_CHECKING
|
|
5
5
|
|
|
6
6
|
from django_spire.contrib.constructor.django_model_constructor import TypeDjangoModel
|
|
7
7
|
from django_spire.contrib.service import BaseDjangoModelService
|
|
@@ -9,6 +9,9 @@ from django_spire.core.tag import tools
|
|
|
9
9
|
from django_spire.core.tag.models import Tag
|
|
10
10
|
from django_spire.core.tag.tools import get_score_percentage_from_tag_set_weighted
|
|
11
11
|
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from django.db.models import QuerySet
|
|
14
|
+
|
|
12
15
|
|
|
13
16
|
class BaseTagService(
|
|
14
17
|
BaseDjangoModelService[TypeDjangoModel],
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from unittest import TestCase
|
|
2
4
|
|
|
3
5
|
from django_spire.core.tag.intelligence.tag_set_bot import TagSetBot
|
|
4
6
|
|
|
7
|
+
|
|
5
8
|
TEST_INPUT = """
|
|
6
9
|
I love reading books about science fiction, fantasy, and adventure, especially ones
|
|
7
10
|
that feature artificial intelligence, machine learning, and natural language processing.
|
|
@@ -13,16 +16,13 @@ TEST_INPUT = """
|
|
|
13
16
|
|
|
14
17
|
|
|
15
18
|
class TestTagIntelligence(TestCase):
|
|
16
|
-
def setUp(self):
|
|
17
|
-
pass
|
|
18
|
-
|
|
19
19
|
def test_tag_set_bot(self):
|
|
20
20
|
tag_set = TagSetBot().process(TEST_INPUT)
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
assert 'science' in tag_set
|
|
23
|
+
assert 'artificial' in tag_set
|
|
24
|
+
assert 'fantasy' in tag_set
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
assert 'camping' not in tag_set
|
|
27
|
+
assert 'art' not in tag_set
|
|
28
|
+
assert 'hate' not in tag_set
|