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,9 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from pydantic import constr, condecimal, create_model
|
|
2
4
|
from pydantic.fields import Field
|
|
3
5
|
|
|
4
|
-
from typing import
|
|
6
|
+
from typing import Any
|
|
5
7
|
|
|
6
8
|
from django.db import models
|
|
7
9
|
from django.core import validators
|
|
@@ -11,13 +13,14 @@ from django_spire.core.maps import MODEL_FIELD_TYPE_TO_TYPE_MAP
|
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
def django_to_pydantic_model(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
model_class: type[models.Model],
|
|
17
|
+
base_class: type | None = None,
|
|
18
|
+
include_fields: str | list| tuple | None = None,
|
|
19
|
+
exclude_fields: str | list | tuple | None = None
|
|
18
20
|
):
|
|
19
21
|
if not issubclass(model_class, models.Model):
|
|
20
|
-
|
|
22
|
+
message = 'model_class must be a subclass of django.db.models.Model'
|
|
23
|
+
raise TypeError(message)
|
|
21
24
|
|
|
22
25
|
if include_fields is None:
|
|
23
26
|
include_fields = []
|
|
@@ -47,7 +50,6 @@ def django_to_pydantic_model(
|
|
|
47
50
|
|
|
48
51
|
|
|
49
52
|
class DjangoToPydanticFieldConverter:
|
|
50
|
-
|
|
51
53
|
def __init__(self, model_field: models.Field):
|
|
52
54
|
self.model_field = model_field
|
|
53
55
|
self.kwargs = {}
|
|
@@ -60,8 +62,8 @@ class DjangoToPydanticFieldConverter:
|
|
|
60
62
|
def bool_to_json_schema(value: bool):
|
|
61
63
|
if value:
|
|
62
64
|
return "true"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
|
|
66
|
+
return "false"
|
|
65
67
|
|
|
66
68
|
@property
|
|
67
69
|
def field_handlers(self):
|
|
@@ -80,22 +82,20 @@ class DjangoToPydanticFieldConverter:
|
|
|
80
82
|
str
|
|
81
83
|
)
|
|
82
84
|
|
|
83
|
-
def _build_char_field(self) ->
|
|
85
|
+
def _build_char_field(self) -> type:
|
|
84
86
|
if self.model_field.max_length:
|
|
85
87
|
return constr(max_length=self.model_field.max_length)
|
|
86
88
|
return str
|
|
87
89
|
|
|
88
|
-
def _build_date_field(self) ->
|
|
89
|
-
self.kwargs['example'] = '2022-01-01'
|
|
90
|
+
def _build_date_field(self) -> type:
|
|
90
91
|
self.kwargs['json_schema_extra']['example'] = '2022-01-01'
|
|
91
92
|
return self._base_type()
|
|
92
93
|
|
|
93
|
-
def _build_date_time_field(self) ->
|
|
94
|
-
self.kwargs['example'] = '2022-01-01 13:37:00'
|
|
94
|
+
def _build_date_time_field(self) -> type:
|
|
95
95
|
self.kwargs['json_schema_extra']['example'] = '2022-01-01 13:37:00'
|
|
96
96
|
return self._base_type()
|
|
97
97
|
|
|
98
|
-
def _build_integer_field(self) ->
|
|
98
|
+
def _build_integer_field(self) -> type:
|
|
99
99
|
for validator in self.model_field.validators:
|
|
100
100
|
if isinstance(validator, validators.MinValueValidator):
|
|
101
101
|
self.kwargs['json_schema_extra']['greater_than'] = validator.limit_value
|
|
@@ -104,7 +104,7 @@ class DjangoToPydanticFieldConverter:
|
|
|
104
104
|
|
|
105
105
|
return self._base_type()
|
|
106
106
|
|
|
107
|
-
def _build_decimal_field(self) ->
|
|
107
|
+
def _build_decimal_field(self) -> type:
|
|
108
108
|
self.kwargs['json_schema_extra']['example'] = '0.00'
|
|
109
109
|
self.kwargs['json_schema_extra']['max_digits'] = self.model_field.max_digits
|
|
110
110
|
self.kwargs['json_schema_extra']['decimal_places'] = self.model_field.decimal_places
|
|
@@ -118,21 +118,19 @@ class DjangoToPydanticFieldConverter:
|
|
|
118
118
|
enum_name = f"{self.model_field.name.capitalize()}Enum"
|
|
119
119
|
return django_choices_to_enums(enum_name, self.model_field.choices)
|
|
120
120
|
|
|
121
|
-
def build_field(self) ->
|
|
121
|
+
def build_field(self) -> tuple[type, Any]:
|
|
122
122
|
"""Build and return the Pydantic field type and Field object."""
|
|
123
123
|
return self.field_type, Field(**self.kwargs)
|
|
124
124
|
|
|
125
125
|
def _build_metadata(self):
|
|
126
|
-
|
|
127
126
|
# Cannot set a default when providing option to the LLM
|
|
127
|
+
|
|
128
128
|
# if self.model_field.default is not models.NOT_PROVIDED:
|
|
129
129
|
# if callable(self.model_field.default):
|
|
130
130
|
# self.kwargs['default'] = self.model_field.default()
|
|
131
131
|
# else:
|
|
132
132
|
# self.kwargs['default'] = self.model_field.default
|
|
133
133
|
|
|
134
|
-
self.kwargs['required'] = not self.model_field.null
|
|
135
|
-
|
|
136
134
|
if self.model_field.null and self.model_field.default is models.NOT_PROVIDED:
|
|
137
135
|
self.kwargs['default'] = None
|
|
138
136
|
|
|
@@ -140,6 +138,7 @@ class DjangoToPydanticFieldConverter:
|
|
|
140
138
|
self.kwargs['description'] = str(self.model_field.help_text)
|
|
141
139
|
|
|
142
140
|
self.kwargs['json_schema_extra'] = {
|
|
141
|
+
'required': self.bool_to_json_schema(not self.model_field.null),
|
|
143
142
|
'is_unique': self.bool_to_json_schema(self.model_field.unique),
|
|
144
143
|
# 'is_required': self.bool_to_json_schema(not self.model_field.null),
|
|
145
144
|
'field_name': self.model_field.name
|
|
@@ -151,7 +150,7 @@ class DjangoToPydanticFieldConverter:
|
|
|
151
150
|
f"{value} ({label})" for value, label in self.model_field.choices
|
|
152
151
|
) + "."
|
|
153
152
|
|
|
154
|
-
def _get_pydantic_type(self) ->
|
|
153
|
+
def _get_pydantic_type(self) -> type:
|
|
155
154
|
if self.model_field.choices:
|
|
156
155
|
return self._build_enum_type()
|
|
157
156
|
|
|
@@ -164,6 +163,7 @@ class DjangoToPydanticFieldConverter:
|
|
|
164
163
|
|
|
165
164
|
def _wrap_nullable(self):
|
|
166
165
|
pass
|
|
166
|
+
|
|
167
167
|
# This works better for the LLM
|
|
168
168
|
# if self.model_field.null:
|
|
169
|
-
# self.field_type = Optional[self.field_type]
|
|
169
|
+
# self.field_type = Optional[self.field_type]
|
django_spire/core/decorators.py
CHANGED
|
@@ -1,25 +1,38 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
2
3
|
from functools import wraps
|
|
3
4
|
|
|
4
|
-
from
|
|
5
|
+
from typing import Callable, ParamSpec, TypeVar
|
|
6
|
+
|
|
7
|
+
from django.db import connections
|
|
8
|
+
from django.http import HttpRequest, HttpResponse, JsonResponse
|
|
9
|
+
|
|
5
10
|
|
|
11
|
+
P = ParamSpec('P')
|
|
12
|
+
T = TypeVar('T')
|
|
6
13
|
|
|
7
|
-
|
|
14
|
+
|
|
15
|
+
def close_db_connections(func: Callable[P, T]) -> Callable[P, T]:
|
|
8
16
|
@wraps(func)
|
|
9
|
-
def inner(*args, **kwargs):
|
|
17
|
+
def inner(*args: P.args, **kwargs: P.kwargs) -> T:
|
|
10
18
|
try:
|
|
11
19
|
return func(*args, **kwargs)
|
|
12
20
|
finally:
|
|
13
21
|
connections.close_all()
|
|
22
|
+
|
|
14
23
|
return inner
|
|
15
24
|
|
|
16
25
|
|
|
17
|
-
def valid_ajax_request_required(
|
|
26
|
+
def valid_ajax_request_required(
|
|
27
|
+
method: Callable[..., HttpResponse]
|
|
28
|
+
) -> Callable[..., HttpResponse]:
|
|
18
29
|
@wraps(method)
|
|
19
|
-
def wrapper(request, *args, **kwargs):
|
|
30
|
+
def wrapper(request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
|
20
31
|
if request.method != 'POST' and request.content_type != 'application/json':
|
|
21
32
|
return JsonResponse(
|
|
22
33
|
{'type': 'error', 'message': 'Invalid Request'}
|
|
23
34
|
)
|
|
35
|
+
|
|
24
36
|
return method(request, *args, **kwargs)
|
|
37
|
+
|
|
25
38
|
return wrapper
|
|
@@ -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',)
|