django-spire 0.23.7__py3-none-any.whl → 0.23.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_spire/ai/admin.py +11 -11
- django_spire/ai/chat/apps.py +1 -0
- django_spire/ai/chat/templates/django_spire/ai/chat/widget/dialog_widget.html +1 -1
- django_spire/ai/chat/tests/factories.py +15 -0
- django_spire/ai/chat/tests/test_controller.py +45 -0
- django_spire/ai/chat/tests/test_models.py +301 -0
- django_spire/ai/chat/tests/test_prompts.py +48 -0
- django_spire/ai/chat/tests/test_responses.py +208 -0
- django_spire/ai/chat/tests/test_router/test_base_chat_router.py +66 -6
- django_spire/ai/chat/tests/test_router/test_chat_workflow.py +73 -3
- django_spire/ai/chat/tests/test_router/test_integration.py +86 -6
- django_spire/ai/chat/tests/test_router/test_intent_decoder.py +93 -1
- django_spire/ai/chat/tests/test_router/test_message_intel.py +60 -1
- django_spire/ai/chat/tests/test_router/test_spire_chat_router.py +110 -0
- django_spire/ai/chat/tests/test_urls/test_json_urls.py +202 -1
- django_spire/ai/context/tests/__init__.py +0 -0
- django_spire/ai/context/tests/test_context.py +188 -0
- django_spire/ai/decorators.py +7 -6
- django_spire/ai/prompt/tests/test_bots.py +100 -10
- django_spire/ai/prompt/tests/test_prompt_intel.py +83 -0
- django_spire/ai/prompt/tests/test_prompt_tuning.py +126 -0
- django_spire/ai/sms/decorators.py +8 -2
- django_spire/ai/sms/tests/test_sms.py +240 -16
- django_spire/ai/sms/tests/test_sms_intel.py +42 -0
- django_spire/ai/sms/tests/test_webhook.py +155 -7
- django_spire/ai/sms/views.py +23 -24
- django_spire/ai/tests/test_ai.py +131 -7
- django_spire/auth/apps.py +4 -2
- django_spire/auth/controller/controller.py +36 -23
- django_spire/auth/controller/exceptions.py +9 -0
- django_spire/auth/group/admin.py +1 -0
- django_spire/auth/group/apps.py +2 -0
- django_spire/auth/group/factories.py +17 -8
- django_spire/auth/group/forms.py +7 -0
- django_spire/auth/group/tests/test_factories.py +146 -0
- django_spire/auth/group/tests/test_forms.py +282 -0
- django_spire/auth/group/tests/test_models.py +192 -0
- django_spire/auth/group/tests/test_querysets.py +98 -0
- django_spire/auth/group/tests/test_utils.py +341 -0
- django_spire/auth/group/tests/test_views.py +377 -0
- django_spire/auth/group/urls/__init__.py +3 -1
- django_spire/auth/group/urls/form_urls.py +2 -0
- django_spire/auth/group/urls/json_urls.py +3 -0
- django_spire/auth/group/urls/page_urls.py +2 -0
- django_spire/auth/group/utils.py +6 -2
- django_spire/auth/group/views/form_views.py +6 -3
- django_spire/auth/group/views/json_views.py +6 -2
- django_spire/auth/mfa/admin.py +2 -0
- django_spire/auth/mfa/apps.py +2 -0
- django_spire/auth/mfa/forms.py +1 -0
- django_spire/auth/mfa/querysets.py +9 -2
- django_spire/auth/mfa/tests/test_models.py +233 -0
- django_spire/auth/mfa/tests/test_utils.py +106 -0
- django_spire/auth/mfa/urls/__init__.py +2 -0
- django_spire/auth/mfa/urls/page_urls.py +2 -0
- django_spire/auth/mfa/urls/redirect_urls.py +2 -0
- django_spire/auth/mfa/views/page_views.py +2 -1
- django_spire/auth/permissions/consts.py +2 -2
- django_spire/auth/permissions/decorators.py +8 -8
- django_spire/auth/permissions/permissions.py +28 -35
- django_spire/auth/permissions/tests/test_decorators.py +333 -0
- django_spire/auth/permissions/tests/test_permissions.py +337 -0
- django_spire/auth/permissions/tests/test_tools.py +305 -0
- django_spire/auth/permissions/tools.py +21 -15
- django_spire/auth/seeding/seed.py +3 -0
- django_spire/auth/seeding/seeder.py +2 -0
- django_spire/auth/tests/test_controller.py +323 -0
- django_spire/auth/tests/test_url_endpoints.py +9 -9
- django_spire/auth/tests/test_views.py +406 -0
- django_spire/auth/urls/admin_urls.py +2 -0
- django_spire/auth/urls/redirect_urls.py +2 -0
- django_spire/auth/user/apps.py +2 -0
- django_spire/auth/user/forms.py +9 -0
- django_spire/auth/user/models.py +1 -1
- django_spire/auth/user/services/services.py +1 -0
- django_spire/auth/user/tests/factories.py +14 -13
- django_spire/auth/user/tests/test_factories.py +166 -2
- django_spire/auth/user/tests/test_forms.py +573 -0
- django_spire/auth/user/tests/test_models.py +257 -0
- django_spire/auth/user/tests/test_services.py +200 -0
- django_spire/auth/user/tests/test_tools.py +153 -0
- django_spire/auth/user/tests/test_user_factories.py +139 -0
- django_spire/auth/user/tests/test_views.py +363 -0
- django_spire/auth/user/tools.py +7 -1
- django_spire/auth/user/urls/form_urls.py +3 -0
- django_spire/auth/user/urls/page_urls.py +3 -0
- django_spire/auth/user/views/form_views.py +19 -10
- django_spire/auth/user/views/page_views.py +8 -2
- django_spire/auth/views/redirect_views.py +14 -9
- django_spire/comment/admin.py +2 -0
- django_spire/comment/apps.py +2 -0
- django_spire/comment/templatetags/comment_tags.py +1 -0
- django_spire/comment/tests/test_forms.py +27 -0
- django_spire/comment/tests/test_models.py +215 -0
- django_spire/comment/tests/test_querysets.py +101 -0
- django_spire/comment/tests/test_utils.py +90 -0
- django_spire/comment/urls.py +2 -0
- django_spire/comment/utils.py +22 -13
- django_spire/comment/views.py +1 -1
- django_spire/conf.py +8 -6
- django_spire/consts.py +1 -1
- django_spire/contrib/breadcrumb/apps.py +2 -0
- django_spire/contrib/breadcrumb/breadcrumbs.py +18 -18
- django_spire/contrib/breadcrumb/tests/test_breadcrumbs.py +198 -0
- django_spire/contrib/constructor/__init__.py +3 -3
- django_spire/contrib/constructor/constructor.py +15 -15
- django_spire/contrib/constructor/django_model_constructor.py +5 -4
- django_spire/contrib/constructor/exceptions.py +5 -3
- django_spire/contrib/constructor/tests/__init__.py +0 -0
- django_spire/contrib/constructor/tests/test_constructor.py +193 -0
- django_spire/contrib/form/tests/__init__.py +0 -0
- django_spire/contrib/form/tests/test_forms.py +203 -0
- django_spire/contrib/generic_views/modal_views.py +2 -1
- django_spire/contrib/generic_views/portal_views.py +20 -19
- django_spire/contrib/generic_views/tests/__init__.py +0 -0
- django_spire/contrib/generic_views/tests/test_views.py +459 -0
- django_spire/contrib/help/apps.py +2 -0
- django_spire/contrib/help/templatetags/help.py +1 -0
- django_spire/contrib/help/tests/__init__.py +0 -0
- django_spire/contrib/help/tests/test_templatetags.py +100 -0
- django_spire/contrib/options/mixins.py +6 -5
- django_spire/contrib/options/tests/factories.py +5 -1
- django_spire/contrib/options/tests/test_options.py +234 -0
- django_spire/contrib/ordering/exceptions.py +7 -3
- django_spire/contrib/ordering/mixins.py +2 -0
- django_spire/contrib/ordering/querysets.py +3 -1
- django_spire/contrib/ordering/services/processor_service.py +8 -4
- django_spire/contrib/ordering/services/service.py +1 -2
- django_spire/contrib/ordering/tests/__init__.py +0 -0
- django_spire/contrib/ordering/tests/test_ordering.py +165 -0
- django_spire/contrib/ordering/validators.py +6 -6
- django_spire/contrib/pagination/templatetags/pagination_tags.py +12 -5
- django_spire/contrib/pagination/tests/__init__.py +0 -0
- django_spire/contrib/pagination/tests/test_pagination.py +179 -0
- django_spire/contrib/performance/decorators.py +16 -6
- django_spire/contrib/performance/tests/__init__.py +0 -0
- django_spire/contrib/performance/tests/test_performance.py +107 -0
- django_spire/contrib/queryset/enums.py +3 -1
- django_spire/contrib/queryset/filter_tools.py +10 -5
- django_spire/contrib/queryset/mixins.py +16 -16
- django_spire/contrib/queryset/tests/__init__.py +0 -0
- django_spire/contrib/queryset/tests/test_queryset.py +137 -0
- django_spire/contrib/seeding/field/base.py +13 -7
- django_spire/contrib/seeding/field/callable.py +8 -1
- django_spire/contrib/seeding/field/cleaners.py +5 -5
- django_spire/contrib/seeding/field/custom.py +20 -10
- django_spire/contrib/seeding/field/django/seeder.py +8 -6
- django_spire/contrib/seeding/field/enums.py +7 -5
- django_spire/contrib/seeding/field/override.py +16 -6
- django_spire/contrib/seeding/field/static.py +9 -2
- django_spire/contrib/seeding/field/tests/test_base.py +18 -14
- django_spire/contrib/seeding/field/tests/test_callable.py +13 -9
- django_spire/contrib/seeding/field/tests/test_cleaners.py +51 -38
- django_spire/contrib/seeding/field/tests/test_static.py +13 -9
- django_spire/contrib/seeding/intelligence/bots/seeder_generator_bot.py +2 -0
- django_spire/contrib/seeding/intelligence/intel.py +5 -1
- django_spire/contrib/seeding/intelligence/prompts/factory.py +6 -1
- django_spire/contrib/seeding/intelligence/prompts/foreign_key_selection_prompt.py +6 -1
- django_spire/contrib/seeding/intelligence/prompts/generate_django_model_seeder_prompts.py +2 -0
- django_spire/contrib/seeding/intelligence/prompts/generic_relationship_selection_prompt.py +7 -1
- django_spire/contrib/seeding/intelligence/prompts/hierarchical_selection_prompt.py +6 -2
- django_spire/contrib/seeding/intelligence/prompts/model_field_choices_prompt.py +8 -2
- django_spire/contrib/seeding/intelligence/prompts/negation_prompt.py +2 -0
- django_spire/contrib/seeding/intelligence/prompts/objective_prompt.py +6 -1
- django_spire/contrib/seeding/management/commands/seeding.py +9 -3
- django_spire/contrib/seeding/management/example.py +2 -0
- django_spire/contrib/seeding/model/base.py +16 -7
- django_spire/contrib/seeding/model/config.py +31 -15
- django_spire/contrib/seeding/model/django/config.py +13 -13
- django_spire/contrib/seeding/model/django/seeder.py +4 -4
- django_spire/contrib/seeding/model/django/tests/test_seeder.py +34 -23
- django_spire/contrib/seeding/model/enums.py +2 -0
- django_spire/contrib/seeding/tests/test_config.py +71 -0
- django_spire/contrib/seeding/tests/test_custom.py +35 -0
- django_spire/contrib/seeding/tests/test_enums.py +40 -0
- django_spire/contrib/seeding/tests/test_intel.py +32 -0
- django_spire/contrib/seeding/tests/test_override.py +63 -0
- django_spire/contrib/service/__init__.py +2 -2
- django_spire/contrib/service/django_model_service.py +16 -15
- django_spire/contrib/service/exceptions.py +5 -3
- django_spire/contrib/service/tests/__init__.py +0 -0
- django_spire/contrib/service/tests/test_service.py +153 -0
- django_spire/contrib/session/apps.py +2 -0
- django_spire/contrib/session/controller.py +48 -42
- django_spire/contrib/session/templatetags/session_tags.py +11 -2
- django_spire/contrib/session/tests/test_session_controller.py +117 -53
- django_spire/contrib/tests/__init__.py +0 -0
- django_spire/contrib/tests/test_utils.py +37 -0
- django_spire/contrib/utils.py +4 -1
- django_spire/core/apps.py +2 -0
- django_spire/core/converters/tests/test_to_data.py +353 -0
- django_spire/core/converters/tests/test_to_enums.py +61 -41
- django_spire/core/converters/tests/test_to_pydantic.py +138 -109
- django_spire/core/converters/to_data.py +29 -10
- django_spire/core/converters/to_enums.py +4 -2
- django_spire/core/converters/to_pydantic.py +22 -22
- django_spire/core/decorators.py +19 -6
- django_spire/core/forms/widgets.py +4 -0
- django_spire/core/maps.py +3 -1
- django_spire/core/middleware/maintenance.py +3 -3
- django_spire/core/middleware.py +8 -6
- django_spire/core/redirect/__init__.py +5 -0
- django_spire/core/redirect/generic_redirect.py +1 -2
- django_spire/core/redirect/tests/__init__.py +0 -0
- django_spire/core/redirect/tests/test_generic_redirect.py +34 -0
- django_spire/core/{tests/tests_redirect.py → redirect/tests/test_safe_redirect.py} +55 -81
- django_spire/core/shortcuts.py +3 -3
- django_spire/core/static/django_spire/css/app-layout.css +1 -1
- django_spire/core/static/django_spire/css/app-navigation.css +3 -3
- django_spire/core/static/django_spire/css/bootstrap-override.css +4 -0
- django_spire/core/tag/admin.py +12 -0
- django_spire/core/tag/intelligence/tag_set_bot.py +2 -0
- django_spire/core/tag/mixins.py +2 -0
- django_spire/core/tag/models.py +2 -0
- django_spire/core/tag/querysets.py +2 -0
- django_spire/core/tag/service/tag_service.py +6 -3
- django_spire/core/tag/tests/test_intelligence.py +9 -9
- django_spire/core/tag/tests/test_tags.py +44 -54
- django_spire/core/tag/tests/test_tools.py +191 -0
- django_spire/core/tag/tools.py +3 -0
- django_spire/core/templates/django_spire/card/card.html +5 -2
- django_spire/core/templates/django_spire/card/title_card.html +9 -4
- django_spire/core/templates/django_spire/infinite_scroll/base.html +1 -0
- django_spire/core/templates/django_spire/navigation/side_navigation.html +19 -24
- django_spire/core/templates/django_spire/page/full_page.html +46 -16
- django_spire/core/templates/django_spire/table/base.html +4 -2
- django_spire/core/templatetags/json.py +6 -2
- django_spire/core/templatetags/message.py +13 -8
- django_spire/core/templatetags/string_formating.py +8 -5
- django_spire/core/templatetags/tests/__init__.py +0 -0
- django_spire/core/templatetags/tests/test_templatetags.py +427 -0
- django_spire/core/templatetags/variable_types.py +17 -9
- django_spire/core/tests/test_cases.py +1 -1
- django_spire/core/tests/test_conf.py +43 -0
- django_spire/core/tests/test_consts.py +28 -0
- django_spire/core/tests/test_context_processors.py +93 -0
- django_spire/core/tests/test_decorators.py +95 -0
- django_spire/core/tests/test_django_spire_utils.py +56 -0
- django_spire/core/tests/test_exceptions.py +37 -0
- django_spire/core/tests/test_models.py +54 -0
- django_spire/core/tests/test_settings.py +45 -0
- django_spire/core/tests/test_shortcuts.py +74 -0
- django_spire/core/tests/test_urls.py +16 -0
- django_spire/core/tests/test_utils.py +58 -0
- django_spire/core/urls.py +4 -1
- django_spire/core/utils.py +12 -8
- django_spire/exceptions.py +16 -1
- django_spire/file/admin.py +4 -2
- django_spire/file/apps.py +8 -10
- django_spire/file/fields.py +7 -7
- django_spire/file/forms.py +1 -1
- django_spire/file/interfaces.py +15 -15
- django_spire/file/mixins.py +1 -4
- django_spire/file/models.py +3 -5
- django_spire/file/tests/factories.py +59 -0
- django_spire/file/tests/test_admin.py +69 -0
- django_spire/file/tests/test_apps.py +24 -0
- django_spire/file/tests/test_fields.py +114 -0
- django_spire/file/tests/test_forms.py +20 -0
- django_spire/file/tests/test_interfaces.py +183 -0
- django_spire/file/tests/test_models.py +82 -0
- django_spire/file/tests/test_querysets.py +102 -0
- django_spire/file/tests/test_utils.py +32 -0
- django_spire/file/tests/test_views.py +145 -0
- django_spire/file/tests/test_widgets.py +82 -0
- django_spire/file/tools.py +8 -2
- django_spire/file/views.py +7 -3
- django_spire/file/widgets.py +12 -12
- django_spire/help_desk/admin.py +15 -0
- django_spire/help_desk/apps.py +2 -0
- django_spire/help_desk/auth/controller.py +2 -0
- django_spire/help_desk/choices.py +2 -0
- django_spire/help_desk/enums.py +2 -0
- django_spire/help_desk/exceptions.py +31 -3
- django_spire/help_desk/forms.py +2 -0
- django_spire/help_desk/models.py +2 -0
- django_spire/help_desk/querysets.py +4 -1
- django_spire/help_desk/services/notification_service.py +26 -27
- django_spire/help_desk/services/service.py +2 -3
- django_spire/help_desk/tests/factories.py +8 -3
- django_spire/help_desk/tests/test_admin.py +41 -0
- django_spire/help_desk/tests/test_apps.py +41 -0
- django_spire/help_desk/tests/test_choices.py +50 -0
- django_spire/help_desk/tests/test_controller.py +87 -0
- django_spire/help_desk/tests/test_enums.py +18 -0
- django_spire/help_desk/tests/test_exceptions.py +37 -0
- django_spire/help_desk/tests/test_forms.py +89 -0
- django_spire/help_desk/tests/test_models.py +59 -0
- django_spire/help_desk/tests/test_querysets.py +38 -0
- django_spire/help_desk/tests/test_services/test_notification_service.py +15 -8
- django_spire/help_desk/tests/test_services/test_service.py +92 -0
- django_spire/help_desk/tests/test_urls/test_form_urls.py +6 -6
- django_spire/help_desk/tests/test_urls/test_page_urls.py +8 -9
- django_spire/help_desk/tests/test_views/test_form_views.py +46 -19
- django_spire/help_desk/tests/test_views/test_page_views.py +32 -9
- django_spire/help_desk/urls/__init__.py +4 -1
- django_spire/help_desk/urls/form_urls.py +3 -0
- django_spire/help_desk/urls/page_urls.py +3 -0
- django_spire/help_desk/views/form_views.py +13 -5
- django_spire/help_desk/views/page_views.py +11 -3
- django_spire/history/activity/admin.py +2 -0
- django_spire/history/activity/apps.py +3 -1
- django_spire/history/activity/mixins.py +13 -7
- django_spire/history/activity/models.py +6 -5
- django_spire/history/activity/querysets.py +2 -0
- django_spire/history/activity/tests/__init__.py +0 -0
- django_spire/history/activity/tests/test_activity.py +176 -0
- django_spire/history/admin.py +9 -2
- django_spire/history/choices.py +3 -0
- django_spire/history/models.py +5 -5
- django_spire/history/tests/test_admin.py +93 -0
- django_spire/history/tests/test_history.py +101 -0
- django_spire/history/tests/test_mixins.py +84 -0
- django_spire/history/viewed/admin.py +3 -1
- django_spire/history/viewed/apps.py +3 -1
- django_spire/history/viewed/models.py +2 -0
- django_spire/history/viewed/tests/__init__.py +0 -0
- django_spire/history/viewed/tests/test_viewed.py +46 -0
- django_spire/knowledge/auth/tests/__init__.py +0 -0
- django_spire/knowledge/auth/tests/test_controller.py +116 -0
- django_spire/knowledge/collection/admin.py +5 -1
- django_spire/knowledge/collection/models.py +3 -1
- django_spire/knowledge/collection/seeding/seed.py +1 -0
- django_spire/knowledge/collection/services/factory_service.py +10 -11
- django_spire/knowledge/collection/services/ordering_service.py +1 -2
- django_spire/knowledge/collection/services/service.py +5 -10
- django_spire/knowledge/collection/services/tag_service.py +5 -2
- django_spire/knowledge/collection/tests/factories.py +28 -1
- django_spire/knowledge/collection/tests/test_models.py +48 -0
- django_spire/knowledge/collection/tests/test_querysets.py +93 -0
- django_spire/knowledge/collection/tests/test_services/test_factory_service.py +100 -0
- django_spire/knowledge/collection/tests/test_services/test_services.py +160 -0
- django_spire/knowledge/collection/tests/test_urls/test_form_urls.py +21 -3
- django_spire/knowledge/collection/tests/test_urls/test_json_urls.py +39 -1
- django_spire/knowledge/collection/tests/test_urls/test_page_urls.py +12 -4
- django_spire/knowledge/collection/urls/__init__.py +3 -0
- django_spire/knowledge/collection/urls/form_urls.py +2 -0
- django_spire/knowledge/collection/urls/json_urls.py +2 -0
- django_spire/knowledge/collection/urls/page_urls.py +2 -0
- django_spire/knowledge/collection/views/form_views.py +4 -4
- django_spire/knowledge/collection/views/json_views.py +5 -1
- django_spire/knowledge/collection/views/page_views.py +5 -2
- django_spire/knowledge/entry/admin.py +7 -1
- django_spire/knowledge/entry/forms.py +2 -0
- django_spire/knowledge/entry/models.py +2 -0
- django_spire/knowledge/entry/seeding/seed.py +3 -0
- django_spire/knowledge/entry/services/automation_service.py +5 -4
- django_spire/knowledge/entry/services/factory_service.py +7 -5
- django_spire/knowledge/entry/services/service.py +4 -7
- django_spire/knowledge/entry/services/tag_service.py +0 -1
- django_spire/knowledge/entry/services/tool_service.py +1 -0
- django_spire/knowledge/entry/services/transformation_services.py +1 -5
- django_spire/knowledge/entry/tests/factories.py +1 -2
- django_spire/knowledge/entry/tests/test_factory_service.py +20 -0
- django_spire/knowledge/entry/tests/test_models.py +41 -0
- django_spire/knowledge/entry/tests/test_querysets.py +71 -0
- django_spire/knowledge/entry/tests/test_services.py +94 -0
- django_spire/knowledge/entry/tests/test_urls/test_form_urls.py +9 -14
- django_spire/knowledge/entry/tests/test_urls/test_json_urls.py +48 -5
- django_spire/knowledge/entry/tests/test_urls/test_page_urls.py +6 -8
- django_spire/knowledge/entry/tests/test_urls/test_template_urls.py +40 -0
- django_spire/knowledge/entry/urls/form_urls.py +2 -0
- django_spire/knowledge/entry/urls/json_urls.py +2 -0
- django_spire/knowledge/entry/urls/page_urls.py +2 -0
- django_spire/knowledge/entry/urls/template_urls.py +2 -0
- django_spire/knowledge/entry/version/block/choices.py +2 -0
- django_spire/knowledge/entry/version/block/data/data.py +1 -0
- django_spire/knowledge/entry/version/block/data/list/data.py +8 -13
- django_spire/knowledge/entry/version/block/data/list/maps.py +3 -0
- django_spire/knowledge/entry/version/block/data/list/meta.py +1 -2
- django_spire/knowledge/entry/version/block/data/list/tests/__init__.py +0 -0
- django_spire/knowledge/entry/version/block/data/list/tests/test_maps.py +32 -0
- django_spire/knowledge/entry/version/block/data/list/tests/test_meta.py +58 -0
- django_spire/knowledge/entry/version/block/data/maps.py +3 -6
- django_spire/knowledge/entry/version/block/models.py +7 -5
- django_spire/knowledge/entry/version/block/seeding/constants.py +5 -4
- django_spire/knowledge/entry/version/block/services/service.py +2 -3
- django_spire/knowledge/entry/version/block/tests/factories.py +4 -10
- django_spire/knowledge/entry/version/block/tests/test_choices.py +56 -0
- django_spire/knowledge/entry/version/block/tests/test_data.py +90 -0
- django_spire/knowledge/entry/version/block/tests/test_maps.py +37 -0
- django_spire/knowledge/entry/version/block/tests/test_models.py +55 -0
- django_spire/knowledge/entry/version/block/tests/test_querysets.py +35 -0
- django_spire/knowledge/entry/version/block/tests/test_services.py +65 -0
- django_spire/knowledge/entry/version/choices.py +2 -0
- django_spire/knowledge/entry/version/converters/converter.py +1 -1
- django_spire/knowledge/entry/version/converters/docx_converter.py +4 -7
- django_spire/knowledge/entry/version/converters/markdown_converter.py +20 -20
- django_spire/knowledge/entry/version/maps.py +4 -5
- django_spire/knowledge/entry/version/querysets.py +1 -1
- django_spire/knowledge/entry/version/seeding/seeder.py +1 -2
- django_spire/knowledge/entry/version/services/processor_service.py +5 -4
- django_spire/knowledge/entry/version/services/service.py +1 -2
- django_spire/knowledge/entry/version/tests/factories.py +2 -2
- django_spire/knowledge/entry/version/tests/test_choices.py +18 -0
- django_spire/knowledge/entry/version/tests/test_converters/test_docx_converter.py +56 -8
- django_spire/knowledge/entry/version/tests/test_converters/test_markdown_converter.py +78 -0
- django_spire/knowledge/entry/version/tests/test_maps.py +58 -0
- django_spire/knowledge/entry/version/tests/test_models.py +23 -0
- django_spire/knowledge/entry/version/tests/test_querysets.py +26 -0
- django_spire/knowledge/entry/version/tests/test_services.py +62 -0
- django_spire/knowledge/entry/version/tests/test_urls/test_json_urls.py +27 -8
- django_spire/knowledge/entry/version/tests/test_urls/test_page_urls.py +15 -8
- django_spire/knowledge/entry/version/tests/test_urls/test_redirect_urls.py +38 -0
- django_spire/knowledge/entry/version/urls/__init__.py +3 -0
- django_spire/knowledge/entry/version/urls/json_urls.py +2 -1
- django_spire/knowledge/entry/version/urls/page_urls.py +2 -0
- django_spire/knowledge/entry/version/urls/redirect_urls.py +2 -0
- django_spire/knowledge/entry/version/views/json_views.py +5 -1
- django_spire/knowledge/entry/version/views/page_views.py +10 -3
- django_spire/knowledge/entry/version/views/redirect_views.py +5 -1
- django_spire/knowledge/entry/views/form_views.py +16 -8
- django_spire/knowledge/entry/views/json_views.py +3 -1
- django_spire/knowledge/entry/views/page_views.py +8 -2
- django_spire/knowledge/entry/views/template_views.py +7 -1
- django_spire/knowledge/exceptions.py +2 -1
- django_spire/knowledge/intelligence/intel/answer_intel.py +2 -1
- django_spire/knowledge/intelligence/intel/entry_intel.py +0 -1
- django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +4 -5
- django_spire/knowledge/models.py +1 -2
- django_spire/knowledge/tests/__init__.py +0 -0
- django_spire/knowledge/tests/test_templatetags.py +40 -0
- django_spire/knowledge/tests/test_urls/__init__.py +0 -0
- django_spire/knowledge/tests/test_urls/test_page_urls.py +24 -0
- django_spire/knowledge/urls/__init__.py +2 -0
- django_spire/knowledge/urls/page_urls.py +2 -0
- django_spire/knowledge/views/page_views.py +8 -3
- django_spire/notification/admin.py +3 -1
- django_spire/notification/app/admin.py +2 -0
- django_spire/notification/app/apps.py +3 -1
- django_spire/notification/app/exceptions.py +9 -2
- django_spire/notification/app/models.py +8 -4
- django_spire/notification/app/processor.py +22 -26
- django_spire/notification/app/querysets.py +2 -0
- django_spire/notification/app/tests/__init__.py +0 -0
- django_spire/notification/app/tests/factories.py +34 -0
- django_spire/notification/app/tests/test_apps.py +24 -0
- django_spire/notification/app/tests/test_models.py +72 -0
- django_spire/notification/app/tests/test_processor.py +111 -0
- django_spire/notification/app/tests/test_querysets.py +90 -0
- django_spire/notification/app/tests/test_views/__init__.py +0 -0
- django_spire/notification/app/tests/test_views/test_json_views.py +48 -0
- django_spire/notification/app/tests/test_views/test_page_views.py +19 -0
- django_spire/notification/app/urls/__init__.py +3 -1
- django_spire/notification/app/urls/json_urls.py +6 -4
- django_spire/notification/app/urls/page_urls.py +4 -3
- django_spire/notification/app/urls/template_urls.py +4 -2
- django_spire/notification/apps.py +4 -1
- django_spire/notification/email/admin.py +5 -1
- django_spire/notification/email/apps.py +3 -1
- django_spire/notification/email/exceptions.py +4 -2
- django_spire/notification/email/helper.py +5 -3
- django_spire/notification/email/models.py +4 -0
- django_spire/notification/email/processor.py +19 -15
- django_spire/notification/email/querysets.py +3 -0
- django_spire/notification/email/tests/__init__.py +0 -0
- django_spire/notification/email/tests/factories.py +35 -0
- django_spire/notification/email/tests/test_apps.py +24 -0
- django_spire/notification/email/tests/test_models.py +52 -0
- django_spire/notification/email/tests/test_processor.py +92 -0
- django_spire/notification/email/tests/test_querysets.py +43 -0
- django_spire/notification/exceptions.py +17 -2
- django_spire/notification/managers.py +7 -1
- django_spire/notification/maps.py +4 -1
- django_spire/notification/mixins.py +2 -0
- django_spire/notification/models.py +3 -1
- django_spire/notification/processors/notification.py +12 -5
- django_spire/notification/processors/processor.py +2 -0
- django_spire/notification/processors/tests/__init__.py +0 -0
- django_spire/notification/processors/tests/test_notification.py +106 -0
- django_spire/notification/push/admin.py +10 -1
- django_spire/notification/push/apps.py +3 -1
- django_spire/notification/push/models.py +2 -3
- django_spire/notification/push/tests/__init__.py +0 -0
- django_spire/notification/push/tests/test_apps.py +24 -0
- django_spire/notification/push/tests/test_models.py +28 -0
- django_spire/notification/querysets.py +7 -1
- django_spire/notification/sms/admin.py +2 -0
- django_spire/notification/sms/apps.py +4 -1
- django_spire/notification/sms/automations.py +2 -0
- django_spire/notification/sms/choices.py +2 -0
- django_spire/notification/sms/exceptions.py +19 -5
- django_spire/notification/sms/helper.py +33 -23
- django_spire/notification/sms/models.py +5 -1
- django_spire/notification/sms/processor.py +20 -20
- django_spire/notification/sms/querysets.py +2 -0
- django_spire/notification/sms/tests/factories.py +33 -0
- django_spire/notification/sms/tests/test_apps.py +24 -0
- django_spire/notification/sms/tests/test_automation.py +38 -0
- django_spire/notification/sms/tests/test_choices.py +15 -0
- django_spire/notification/sms/tests/test_consts.py +17 -0
- django_spire/notification/sms/tests/test_exceptions.py +27 -0
- django_spire/notification/sms/tests/test_helper.py +50 -0
- django_spire/notification/sms/tests/test_models.py +81 -0
- django_spire/notification/sms/tests/test_processor.py +107 -0
- django_spire/notification/sms/tests/test_tools.py +25 -11
- django_spire/notification/sms/tools.py +16 -5
- django_spire/notification/sms/urls/__init__.py +3 -1
- django_spire/notification/sms/urls/media_urls.py +2 -0
- django_spire/notification/sms/views/media_views.py +14 -4
- django_spire/notification/tests/__init__.py +0 -0
- django_spire/notification/tests/factories.py +26 -0
- django_spire/notification/tests/test_admin.py +55 -0
- django_spire/notification/tests/test_apps.py +30 -0
- django_spire/notification/tests/test_automation.py +18 -0
- django_spire/notification/tests/test_choices.py +59 -0
- django_spire/notification/tests/test_exceptions.py +58 -0
- django_spire/notification/tests/test_managers.py +100 -0
- django_spire/notification/tests/test_maps.py +31 -0
- django_spire/notification/tests/test_models.py +76 -0
- django_spire/notification/tests/test_querysets.py +184 -0
- django_spire/notification/tests/test_utils.py +23 -0
- django_spire/notification/urls.py +3 -1
- django_spire/notification/utils.py +3 -1
- django_spire/settings.py +3 -0
- django_spire/theme/tests/test_context_processor.py +15 -13
- django_spire/theme/tests/test_enums.py +2 -2
- django_spire/theme/tests/test_filesystem.py +2 -5
- django_spire/theme/tests/test_integration.py +12 -12
- django_spire/theme/tests/test_model.py +40 -38
- django_spire/theme/tests/test_views/test_json_views.py +33 -33
- django_spire/theme/urls/json_urls.py +3 -0
- django_spire/theme/urls/page_urls.py +3 -0
- django_spire/urls.py +19 -15
- django_spire/utils.py +13 -4
- {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/METADATA +1 -1
- {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/RECORD +530 -358
- django_spire/contrib/options/tests/test_unit.py +0 -148
- django_spire/contrib/seeding/tests/test_seeding.py +0 -25
- django_spire/core/tests/test_templatetags.py +0 -117
- django_spire/core/tests/tests_shortcuts.py +0 -73
- django_spire/history/activity/tests.py +0 -3
- django_spire/history/activity/views.py +0 -3
- django_spire/knowledge/collection/tests/test_services/test_transformation_service.py +0 -71
- django_spire/notification/app/tests.py +0 -3
- {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/WHEEL +0 -0
- {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/licenses/LICENSE.md +0 -0
- {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/top_level.txt +0 -0
|
@@ -3,11 +3,9 @@ from __future__ import annotations
|
|
|
3
3
|
import json
|
|
4
4
|
|
|
5
5
|
from django_spire.knowledge.entry.version.block.choices import BlockTypeChoices
|
|
6
|
-
from django_spire.knowledge.entry.version.block.data.text_data import
|
|
7
|
-
TextEditorBlockData
|
|
6
|
+
from django_spire.knowledge.entry.version.block.data.text_data import TextEditorBlockData
|
|
8
7
|
from django_spire.knowledge.entry.version.block.models import EntryVersionBlock
|
|
9
|
-
from django_spire.knowledge.entry.version.tests.factories import
|
|
10
|
-
create_test_entry_version
|
|
8
|
+
from django_spire.knowledge.entry.version.tests.factories import create_test_entry_version
|
|
11
9
|
|
|
12
10
|
|
|
13
11
|
def create_test_block_form_data(**kwargs) -> dict:
|
|
@@ -18,25 +16,21 @@ def create_test_block_form_data(**kwargs) -> dict:
|
|
|
18
16
|
'data': {'text': 'test text'},
|
|
19
17
|
'tunes': {}
|
|
20
18
|
}
|
|
21
|
-
|
|
22
19
|
data.update(kwargs)
|
|
23
20
|
return data
|
|
24
21
|
|
|
25
22
|
|
|
26
23
|
def create_test_version_block(**kwargs) -> EntryVersionBlock:
|
|
27
24
|
data = {
|
|
28
|
-
'version': create_test_entry_version(),
|
|
25
|
+
'version': kwargs.pop('version', None) or create_test_entry_version(),
|
|
29
26
|
'order': 1,
|
|
30
27
|
'type': BlockTypeChoices.TEXT,
|
|
31
28
|
'_block_data': json.dumps({}),
|
|
32
29
|
'_text_data': ''
|
|
33
30
|
}
|
|
34
|
-
|
|
35
31
|
data.update(kwargs)
|
|
36
32
|
version_block = EntryVersionBlock.objects.create(**data)
|
|
37
|
-
version_block.editor_js_block_data = TextEditorBlockData(
|
|
38
|
-
text='',
|
|
39
|
-
)
|
|
33
|
+
version_block.editor_js_block_data = TextEditorBlockData(text='')
|
|
40
34
|
version_block.save()
|
|
41
35
|
|
|
42
36
|
return version_block
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django_spire.core.tests.test_cases import BaseTestCase
|
|
4
|
+
from django_spire.knowledge.entry.version.block.choices import BlockTypeChoices
|
|
5
|
+
from django_spire.knowledge.entry.version.block.data.list.choices import (
|
|
6
|
+
ListEditorBlockDataStyle,
|
|
7
|
+
OrderedListCounterType,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BlockTypeChoicesTests(BaseTestCase):
|
|
12
|
+
def test_text_value(self):
|
|
13
|
+
assert BlockTypeChoices.TEXT == 'text'
|
|
14
|
+
|
|
15
|
+
def test_heading_value(self):
|
|
16
|
+
assert BlockTypeChoices.HEADING == 'heading'
|
|
17
|
+
|
|
18
|
+
def test_list_value(self):
|
|
19
|
+
assert BlockTypeChoices.LIST == 'list'
|
|
20
|
+
|
|
21
|
+
def test_choices_count(self):
|
|
22
|
+
assert len(BlockTypeChoices.choices) == 3
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ListEditorBlockDataStyleTests(BaseTestCase):
|
|
26
|
+
def test_unordered_value(self):
|
|
27
|
+
assert ListEditorBlockDataStyle.UNORDERED == 'unordered'
|
|
28
|
+
|
|
29
|
+
def test_ordered_value(self):
|
|
30
|
+
assert ListEditorBlockDataStyle.ORDERED == 'ordered'
|
|
31
|
+
|
|
32
|
+
def test_checklist_value(self):
|
|
33
|
+
assert ListEditorBlockDataStyle.CHECKLIST == 'checklist'
|
|
34
|
+
|
|
35
|
+
def test_choices_count(self):
|
|
36
|
+
assert len(ListEditorBlockDataStyle.choices) == 3
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class OrderedListCounterTypeTests(BaseTestCase):
|
|
40
|
+
def test_numeric_value(self):
|
|
41
|
+
assert OrderedListCounterType.NUMERIC == 'numeric'
|
|
42
|
+
|
|
43
|
+
def test_upper_roman_value(self):
|
|
44
|
+
assert OrderedListCounterType.UPPER_ROMAN == 'upper-roman'
|
|
45
|
+
|
|
46
|
+
def test_lower_roman_value(self):
|
|
47
|
+
assert OrderedListCounterType.LOWER_ROMAN == 'lower-roman'
|
|
48
|
+
|
|
49
|
+
def test_upper_alpha_value(self):
|
|
50
|
+
assert OrderedListCounterType.UPPER_ALPHA == 'upper-alpha'
|
|
51
|
+
|
|
52
|
+
def test_lower_alpha_value(self):
|
|
53
|
+
assert OrderedListCounterType.LOWER_ALPHA == 'lower-alpha'
|
|
54
|
+
|
|
55
|
+
def test_choices_count(self):
|
|
56
|
+
assert len(OrderedListCounterType.choices) == 5
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django_spire.core.tests.test_cases import BaseTestCase
|
|
4
|
+
from django_spire.knowledge.entry.version.block.data.heading_data import HeadingEditorBlockData
|
|
5
|
+
from django_spire.knowledge.entry.version.block.data.list.choices import ListEditorBlockDataStyle
|
|
6
|
+
from django_spire.knowledge.entry.version.block.data.list.data import ListEditorBlockData
|
|
7
|
+
from django_spire.knowledge.entry.version.block.data.text_data import TextEditorBlockData
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TextEditorBlockDataTests(BaseTestCase):
|
|
11
|
+
def test_render_to_text(self):
|
|
12
|
+
data = TextEditorBlockData(text='Hello World')
|
|
13
|
+
result = data.render_to_text()
|
|
14
|
+
assert 'Hello World' in result
|
|
15
|
+
|
|
16
|
+
def test_render_to_text_with_html(self):
|
|
17
|
+
data = TextEditorBlockData(text='<b>Bold</b> text')
|
|
18
|
+
result = data.render_to_text()
|
|
19
|
+
assert 'Bold' in result
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class HeadingEditorBlockDataTests(BaseTestCase):
|
|
23
|
+
def test_render_to_text_h1(self):
|
|
24
|
+
data = HeadingEditorBlockData(text='Title', level=1)
|
|
25
|
+
result = data.render_to_text()
|
|
26
|
+
assert '# Title' in result
|
|
27
|
+
|
|
28
|
+
def test_render_to_text_h2(self):
|
|
29
|
+
data = HeadingEditorBlockData(text='Subtitle', level=2)
|
|
30
|
+
result = data.render_to_text()
|
|
31
|
+
assert '## Subtitle' in result
|
|
32
|
+
|
|
33
|
+
def test_render_to_text_h3(self):
|
|
34
|
+
data = HeadingEditorBlockData(text='Section', level=3)
|
|
35
|
+
result = data.render_to_text()
|
|
36
|
+
assert '### Section' in result
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ListEditorBlockDataTests(BaseTestCase):
|
|
40
|
+
def test_render_to_text_unordered(self):
|
|
41
|
+
data = ListEditorBlockData(
|
|
42
|
+
style=ListEditorBlockDataStyle.UNORDERED,
|
|
43
|
+
items=[
|
|
44
|
+
{'content': 'Item 1', 'items': []},
|
|
45
|
+
{'content': 'Item 2', 'items': []},
|
|
46
|
+
]
|
|
47
|
+
)
|
|
48
|
+
result = data.render_to_text()
|
|
49
|
+
assert '- Item 1' in result
|
|
50
|
+
assert '- Item 2' in result
|
|
51
|
+
|
|
52
|
+
def test_render_to_text_ordered(self):
|
|
53
|
+
data = ListEditorBlockData(
|
|
54
|
+
style=ListEditorBlockDataStyle.ORDERED,
|
|
55
|
+
meta={'start': 1},
|
|
56
|
+
items=[
|
|
57
|
+
{'content': 'First', 'items': [], 'meta': {'start': 1}},
|
|
58
|
+
{'content': 'Second', 'items': [], 'meta': {'start': 1}},
|
|
59
|
+
]
|
|
60
|
+
)
|
|
61
|
+
result = data.render_to_text()
|
|
62
|
+
assert '1. First' in result
|
|
63
|
+
|
|
64
|
+
def test_render_to_text_checklist(self):
|
|
65
|
+
data = ListEditorBlockData(
|
|
66
|
+
style=ListEditorBlockDataStyle.CHECKLIST,
|
|
67
|
+
items=[
|
|
68
|
+
{'content': 'Done', 'items': [], 'meta': {'checked': True}},
|
|
69
|
+
{'content': 'Not done', 'items': [], 'meta': {'checked': False}},
|
|
70
|
+
]
|
|
71
|
+
)
|
|
72
|
+
result = data.render_to_text()
|
|
73
|
+
assert '[X] Done' in result
|
|
74
|
+
assert '[ ] Not done' in result
|
|
75
|
+
|
|
76
|
+
def test_render_to_text_nested(self):
|
|
77
|
+
data = ListEditorBlockData(
|
|
78
|
+
style=ListEditorBlockDataStyle.UNORDERED,
|
|
79
|
+
items=[
|
|
80
|
+
{
|
|
81
|
+
'content': 'Parent',
|
|
82
|
+
'items': [
|
|
83
|
+
{'content': 'Child', 'items': []}
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
]
|
|
87
|
+
)
|
|
88
|
+
result = data.render_to_text()
|
|
89
|
+
assert 'Parent' in result
|
|
90
|
+
assert 'Child' in result
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django_spire.core.tests.test_cases import BaseTestCase
|
|
4
|
+
from django_spire.knowledge.entry.version.block.choices import BlockTypeChoices
|
|
5
|
+
from django_spire.knowledge.entry.version.block.data.heading_data import HeadingEditorBlockData
|
|
6
|
+
from django_spire.knowledge.entry.version.block.data.list.data import ListEditorBlockData
|
|
7
|
+
from django_spire.knowledge.entry.version.block.data.maps import (
|
|
8
|
+
EDITOR_JS_BLOCK_DATA_MAP,
|
|
9
|
+
EDITOR_JS_BLOCK_DATA_REVERSE_MAP,
|
|
10
|
+
)
|
|
11
|
+
from django_spire.knowledge.entry.version.block.data.text_data import TextEditorBlockData
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class EditorJsBlockDataMapTests(BaseTestCase):
|
|
15
|
+
def test_map_contains_text(self):
|
|
16
|
+
assert BlockTypeChoices.TEXT in EDITOR_JS_BLOCK_DATA_MAP
|
|
17
|
+
assert EDITOR_JS_BLOCK_DATA_MAP[BlockTypeChoices.TEXT] == TextEditorBlockData
|
|
18
|
+
|
|
19
|
+
def test_map_contains_heading(self):
|
|
20
|
+
assert BlockTypeChoices.HEADING in EDITOR_JS_BLOCK_DATA_MAP
|
|
21
|
+
assert EDITOR_JS_BLOCK_DATA_MAP[BlockTypeChoices.HEADING] == HeadingEditorBlockData
|
|
22
|
+
|
|
23
|
+
def test_map_contains_list(self):
|
|
24
|
+
assert BlockTypeChoices.LIST in EDITOR_JS_BLOCK_DATA_MAP
|
|
25
|
+
assert EDITOR_JS_BLOCK_DATA_MAP[BlockTypeChoices.LIST] == ListEditorBlockData
|
|
26
|
+
|
|
27
|
+
def test_reverse_map_contains_text(self):
|
|
28
|
+
assert TextEditorBlockData in EDITOR_JS_BLOCK_DATA_REVERSE_MAP
|
|
29
|
+
assert EDITOR_JS_BLOCK_DATA_REVERSE_MAP[TextEditorBlockData] == BlockTypeChoices.TEXT
|
|
30
|
+
|
|
31
|
+
def test_reverse_map_contains_heading(self):
|
|
32
|
+
assert HeadingEditorBlockData in EDITOR_JS_BLOCK_DATA_REVERSE_MAP
|
|
33
|
+
assert EDITOR_JS_BLOCK_DATA_REVERSE_MAP[HeadingEditorBlockData] == BlockTypeChoices.HEADING
|
|
34
|
+
|
|
35
|
+
def test_reverse_map_contains_list(self):
|
|
36
|
+
assert ListEditorBlockData in EDITOR_JS_BLOCK_DATA_REVERSE_MAP
|
|
37
|
+
assert EDITOR_JS_BLOCK_DATA_REVERSE_MAP[ListEditorBlockData] == BlockTypeChoices.LIST
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django_spire.core.tests.test_cases import BaseTestCase
|
|
4
|
+
from django_spire.knowledge.entry.version.block.choices import BlockTypeChoices
|
|
5
|
+
from django_spire.knowledge.entry.version.block.data.heading_data import HeadingEditorBlockData
|
|
6
|
+
from django_spire.knowledge.entry.version.block.data.list.data import ListEditorBlockData
|
|
7
|
+
from django_spire.knowledge.entry.version.block.data.text_data import TextEditorBlockData
|
|
8
|
+
from django_spire.knowledge.entry.version.block.tests.factories import create_test_version_block
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class EntryVersionBlockModelTests(BaseTestCase):
|
|
12
|
+
def setUp(self):
|
|
13
|
+
super().setUp()
|
|
14
|
+
self.version_block = create_test_version_block()
|
|
15
|
+
|
|
16
|
+
def test_editor_js_block_data_getter_text(self):
|
|
17
|
+
self.version_block.type = BlockTypeChoices.TEXT
|
|
18
|
+
self.version_block._block_data = {'text': 'Hello'}
|
|
19
|
+
result = self.version_block.editor_js_block_data
|
|
20
|
+
assert isinstance(result, TextEditorBlockData)
|
|
21
|
+
assert result.text == 'Hello'
|
|
22
|
+
|
|
23
|
+
def test_editor_js_block_data_getter_heading(self):
|
|
24
|
+
self.version_block.type = BlockTypeChoices.HEADING
|
|
25
|
+
self.version_block._block_data = {'text': 'Title', 'level': 1}
|
|
26
|
+
result = self.version_block.editor_js_block_data
|
|
27
|
+
assert isinstance(result, HeadingEditorBlockData)
|
|
28
|
+
assert result.text == 'Title'
|
|
29
|
+
assert result.level == 1
|
|
30
|
+
|
|
31
|
+
def test_editor_js_block_data_getter_list(self):
|
|
32
|
+
self.version_block.type = BlockTypeChoices.LIST
|
|
33
|
+
self.version_block._block_data = {
|
|
34
|
+
'style': 'unordered',
|
|
35
|
+
'items': [{'content': 'Item 1', 'items': []}]
|
|
36
|
+
}
|
|
37
|
+
result = self.version_block.editor_js_block_data
|
|
38
|
+
assert isinstance(result, ListEditorBlockData)
|
|
39
|
+
|
|
40
|
+
def test_editor_js_block_data_setter(self):
|
|
41
|
+
text_data = TextEditorBlockData(text='New text')
|
|
42
|
+
self.version_block.editor_js_block_data = text_data
|
|
43
|
+
assert self.version_block._block_data == {'text': 'New text'}
|
|
44
|
+
assert 'New text' in self.version_block._text_data
|
|
45
|
+
|
|
46
|
+
def test_update_editor_js_block_data_from_dict(self):
|
|
47
|
+
self.version_block.type = BlockTypeChoices.TEXT
|
|
48
|
+
self.version_block.update_editor_js_block_data_from_dict({'text': 'Updated'})
|
|
49
|
+
assert self.version_block._block_data == {'text': 'Updated'}
|
|
50
|
+
|
|
51
|
+
def test_render_to_text(self):
|
|
52
|
+
self.version_block.type = BlockTypeChoices.TEXT
|
|
53
|
+
self.version_block._block_data = {'text': 'Hello World'}
|
|
54
|
+
result = self.version_block.render_to_text()
|
|
55
|
+
assert 'Hello World' in result
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django_spire.core.tests.test_cases import BaseTestCase
|
|
4
|
+
from django_spire.knowledge.entry.version.block.models import EntryVersionBlock
|
|
5
|
+
from django_spire.knowledge.entry.version.block.tests.factories import create_test_version_block
|
|
6
|
+
from django_spire.knowledge.entry.version.tests.factories import create_test_entry_version
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class EntryVersionBlockQuerySetTests(BaseTestCase):
|
|
10
|
+
def setUp(self):
|
|
11
|
+
super().setUp()
|
|
12
|
+
self.entry_version = create_test_entry_version()
|
|
13
|
+
self.block1 = create_test_version_block(version=self.entry_version, order=0)
|
|
14
|
+
self.block2 = create_test_version_block(version=self.entry_version, order=1)
|
|
15
|
+
|
|
16
|
+
def test_format_for_editor(self):
|
|
17
|
+
result = list(
|
|
18
|
+
EntryVersionBlock.objects
|
|
19
|
+
.filter(version=self.entry_version)
|
|
20
|
+
.format_for_editor()
|
|
21
|
+
)
|
|
22
|
+
assert len(result) == 2
|
|
23
|
+
assert 'id' in result[0]
|
|
24
|
+
assert 'type' in result[0]
|
|
25
|
+
assert 'data' in result[0]
|
|
26
|
+
assert 'tunes' in result[0]
|
|
27
|
+
|
|
28
|
+
def test_format_for_editor_ordering(self):
|
|
29
|
+
result = list(
|
|
30
|
+
EntryVersionBlock.objects
|
|
31
|
+
.filter(version=self.entry_version)
|
|
32
|
+
.format_for_editor()
|
|
33
|
+
)
|
|
34
|
+
assert result[0]['id'] == self.block1.id
|
|
35
|
+
assert result[1]['id'] == self.block2.id
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django_spire.core.tests.test_cases import BaseTestCase
|
|
4
|
+
from django_spire.knowledge.entry.version.block.choices import BlockTypeChoices
|
|
5
|
+
from django_spire.knowledge.entry.version.block.models import EntryVersionBlock
|
|
6
|
+
from django_spire.knowledge.entry.version.tests.factories import create_test_entry_version
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class EntryVersionBlockFactoryServiceTests(BaseTestCase):
|
|
10
|
+
def setUp(self):
|
|
11
|
+
super().setUp()
|
|
12
|
+
self.entry_version = create_test_entry_version()
|
|
13
|
+
|
|
14
|
+
def test_create_validated_block_text(self):
|
|
15
|
+
block = EntryVersionBlock.services.factory.create_validated_block(
|
|
16
|
+
entry_version=self.entry_version,
|
|
17
|
+
type=BlockTypeChoices.TEXT,
|
|
18
|
+
data={'text': 'Test content'},
|
|
19
|
+
order=0
|
|
20
|
+
)
|
|
21
|
+
assert block.type == BlockTypeChoices.TEXT
|
|
22
|
+
assert block.order == 0
|
|
23
|
+
assert block._block_data == {'text': 'Test content'}
|
|
24
|
+
|
|
25
|
+
def test_create_validated_block_heading(self):
|
|
26
|
+
block = EntryVersionBlock.services.factory.create_validated_block(
|
|
27
|
+
entry_version=self.entry_version,
|
|
28
|
+
type=BlockTypeChoices.HEADING,
|
|
29
|
+
data={'text': 'Title', 'level': 1},
|
|
30
|
+
order=0
|
|
31
|
+
)
|
|
32
|
+
assert block.type == BlockTypeChoices.HEADING
|
|
33
|
+
assert block._block_data['level'] == 1
|
|
34
|
+
|
|
35
|
+
def test_create_validated_block_list(self):
|
|
36
|
+
block = EntryVersionBlock.services.factory.create_validated_block(
|
|
37
|
+
entry_version=self.entry_version,
|
|
38
|
+
type=BlockTypeChoices.LIST,
|
|
39
|
+
data={
|
|
40
|
+
'style': 'unordered',
|
|
41
|
+
'items': [{'content': 'Item', 'items': []}]
|
|
42
|
+
},
|
|
43
|
+
order=0
|
|
44
|
+
)
|
|
45
|
+
assert block.type == BlockTypeChoices.LIST
|
|
46
|
+
|
|
47
|
+
def test_create_validated_block_with_tunes(self):
|
|
48
|
+
tunes = {'alignment': 'center'}
|
|
49
|
+
block = EntryVersionBlock.services.factory.create_validated_block(
|
|
50
|
+
entry_version=self.entry_version,
|
|
51
|
+
type=BlockTypeChoices.TEXT,
|
|
52
|
+
data={'text': 'Centered'},
|
|
53
|
+
order=0,
|
|
54
|
+
tunes=tunes
|
|
55
|
+
)
|
|
56
|
+
assert block.tunes == tunes
|
|
57
|
+
|
|
58
|
+
def test_create_validated_block_sets_version(self):
|
|
59
|
+
block = EntryVersionBlock.services.factory.create_validated_block(
|
|
60
|
+
entry_version=self.entry_version,
|
|
61
|
+
type=BlockTypeChoices.TEXT,
|
|
62
|
+
data={'text': 'Test'},
|
|
63
|
+
order=0
|
|
64
|
+
)
|
|
65
|
+
assert block.version == self.entry_version
|
|
@@ -7,16 +7,13 @@ from typing import TYPE_CHECKING
|
|
|
7
7
|
from markitdown import MarkItDown
|
|
8
8
|
|
|
9
9
|
from django_spire.knowledge.entry.version.consts import MARKDOWN_AI_CHUNK_SIZE
|
|
10
|
-
from django_spire.knowledge.entry.version.converters.converter import
|
|
11
|
-
|
|
12
|
-
from django_spire.knowledge.entry.version.
|
|
13
|
-
from django_spire.knowledge.entry.version.converters.markdown_converter import \
|
|
14
|
-
MarkdownConverter
|
|
15
|
-
from django_spire.knowledge.entry.version.intelligence.bots.markdown_format_llm_bot import \
|
|
16
|
-
MarkdownFormatLlmBot
|
|
10
|
+
from django_spire.knowledge.entry.version.converters.converter import BaseConverter
|
|
11
|
+
from django_spire.knowledge.entry.version.converters.markdown_converter import MarkdownConverter
|
|
12
|
+
from django_spire.knowledge.entry.version.intelligence.bots.markdown_format_llm_bot import MarkdownFormatLlmBot
|
|
17
13
|
|
|
18
14
|
if TYPE_CHECKING:
|
|
19
15
|
from django_spire.file.models import File
|
|
16
|
+
from django_spire.knowledge.entry.version.block import models
|
|
20
17
|
|
|
21
18
|
|
|
22
19
|
class DocxConverter(BaseConverter):
|
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
+
import marko
|
|
4
5
|
import re
|
|
6
|
+
|
|
5
7
|
from collections import defaultdict
|
|
6
8
|
from typing import TYPE_CHECKING
|
|
7
9
|
|
|
8
|
-
import marko
|
|
9
10
|
from bs4 import BeautifulSoup
|
|
10
11
|
from django.core.files.storage import default_storage
|
|
11
12
|
from markitdown.converters import HtmlConverter
|
|
12
13
|
from marko.block import Heading, List, ListItem, Paragraph, BlankLine
|
|
13
|
-
from marko.element import Element
|
|
14
14
|
|
|
15
15
|
from django_spire.knowledge.entry.version.block import models
|
|
16
16
|
from django_spire.knowledge.entry.version.block.choices import BlockTypeChoices
|
|
17
|
-
from django_spire.knowledge.entry.version.block.data.list.choices import
|
|
18
|
-
|
|
19
|
-
from django_spire.knowledge.entry.version.converters.converter import \
|
|
20
|
-
BaseConverter
|
|
17
|
+
from django_spire.knowledge.entry.version.block.data.list.choices import ListEditorBlockDataStyle
|
|
18
|
+
from django_spire.knowledge.entry.version.converters.converter import BaseConverter
|
|
21
19
|
|
|
22
20
|
if TYPE_CHECKING:
|
|
21
|
+
from marko.element import Element
|
|
23
22
|
from django_spire.knowledge.entry.version.models import EntryVersion
|
|
24
23
|
from django_spire.file.models import File
|
|
25
24
|
from marko.block import BlockElement
|
|
@@ -54,8 +53,8 @@ class MarkdownConverter(BaseConverter):
|
|
|
54
53
|
return self.convert_markdown_to_blocks(f.read())
|
|
55
54
|
|
|
56
55
|
def convert_markdown_to_blocks(
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
self,
|
|
57
|
+
markdown_content: str
|
|
59
58
|
) -> list[models.EntryVersionBlock]:
|
|
60
59
|
marko_blocks = marko.parse(markdown_content).children
|
|
61
60
|
|
|
@@ -124,9 +123,9 @@ class MarkdownConverter(BaseConverter):
|
|
|
124
123
|
|
|
125
124
|
@classmethod
|
|
126
125
|
def _marko_list_item_block_to_editor_block_data_dict(
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
126
|
+
cls,
|
|
127
|
+
marko_list_item_block: ListItem,
|
|
128
|
+
parent_marko_list_block: List | None = None
|
|
130
129
|
):
|
|
131
130
|
list_item_editor_block_data_kwargs = {
|
|
132
131
|
'items': [],
|
|
@@ -165,6 +164,7 @@ class MarkdownConverter(BaseConverter):
|
|
|
165
164
|
list_item_editor_block_data_kwargs['content'] = content
|
|
166
165
|
|
|
167
166
|
list_editor_block_data_style = None
|
|
167
|
+
|
|
168
168
|
if parent_marko_list_block:
|
|
169
169
|
# We determine the entire list style from the top level list items for simplicity.
|
|
170
170
|
# If parent_marko_list_block is present, it means we are processing the top level list.
|
|
@@ -182,17 +182,17 @@ class MarkdownConverter(BaseConverter):
|
|
|
182
182
|
if isinstance(marko_block, BlankLine):
|
|
183
183
|
return { 'text': '' }
|
|
184
184
|
|
|
185
|
-
|
|
185
|
+
if isinstance(marko_block, Paragraph):
|
|
186
186
|
editor_block_text_string = cls._remove_outer_html_tags(marko.render(marko_block))
|
|
187
187
|
return { 'text': editor_block_text_string }
|
|
188
188
|
|
|
189
|
-
|
|
189
|
+
if isinstance(marko_block, Heading):
|
|
190
190
|
return {
|
|
191
191
|
'text': cls._remove_outer_html_tags(marko.render(marko_block)),
|
|
192
192
|
'level': marko_block.level,
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
-
|
|
195
|
+
if isinstance(marko_block, List):
|
|
196
196
|
list_editor_block_data_dict = {
|
|
197
197
|
'items': [],
|
|
198
198
|
'style': ListEditorBlockDataStyle.UNORDERED,
|
|
@@ -208,12 +208,12 @@ class MarkdownConverter(BaseConverter):
|
|
|
208
208
|
|
|
209
209
|
return list_editor_block_data_dict
|
|
210
210
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
211
|
+
logging.warning(
|
|
212
|
+
f'Unsupported marko block type: {marko_block.__class__.__name__!r}. '
|
|
213
|
+
f'Rendering content to html and adding to markdown as a basic paragraph block.'
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
return { 'text': marko.render(marko_block) }
|
|
217
217
|
|
|
218
218
|
|
|
219
219
|
@classmethod
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from django_spire.knowledge.entry.version.converters.docx_converter import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
MarkdownConverter
|
|
3
|
+
from django_spire.knowledge.entry.version.converters.docx_converter import DocxConverter
|
|
4
|
+
from django_spire.knowledge.entry.version.converters.markdown_converter import MarkdownConverter
|
|
5
|
+
|
|
7
6
|
|
|
8
7
|
FILE_TYPE_CONVERTER_MAP = {
|
|
9
8
|
'md': MarkdownConverter,
|
|
10
9
|
'docx': DocxConverter
|
|
11
|
-
}
|
|
10
|
+
}
|
|
@@ -11,8 +11,7 @@ from django_spire.knowledge.entry.models import Entry
|
|
|
11
11
|
from django_spire.knowledge.entry.version import models
|
|
12
12
|
|
|
13
13
|
from django_spire.contrib.seeding import DjangoModelSeeder
|
|
14
|
-
from django_spire.knowledge.entry.version.block.data.maps import
|
|
15
|
-
EDITOR_JS_BLOCK_DATA_REVERSE_MAP
|
|
14
|
+
from django_spire.knowledge.entry.version.block.data.maps import EDITOR_JS_BLOCK_DATA_REVERSE_MAP
|
|
16
15
|
from django_spire.knowledge.entry.version.block.models import EntryVersionBlock
|
|
17
16
|
from django_spire.knowledge.entry.version.block.seeding.constants import SAFETY_BLOCKS
|
|
18
17
|
from django_spire.knowledge.entry.version.choices import EntryVersionStatusChoices
|
|
@@ -25,7 +25,6 @@ class EntryVersionProcessorService(BaseDjangoModelService['EntryVersion']):
|
|
|
25
25
|
|
|
26
26
|
entry_blocks_to_add = []
|
|
27
27
|
entry_blocks_to_update = []
|
|
28
|
-
entry_blocks_to_delete = []
|
|
29
28
|
|
|
30
29
|
handled_block_ids = []
|
|
31
30
|
|
|
@@ -54,9 +53,11 @@ class EntryVersionProcessorService(BaseDjangoModelService['EntryVersion']):
|
|
|
54
53
|
|
|
55
54
|
handled_block_ids.append(block_data['id'])
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
entry_blocks_to_delete = [
|
|
57
|
+
entry_block.id
|
|
58
|
+
for entry_block in old_entry_blocks
|
|
59
|
+
if entry_block.id not in handled_block_ids
|
|
60
|
+
]
|
|
60
61
|
|
|
61
62
|
with transaction.atomic():
|
|
62
63
|
EntryVersionBlock.objects.filter(id__in=entry_blocks_to_delete).delete()
|
|
@@ -3,8 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import TYPE_CHECKING
|
|
4
4
|
|
|
5
5
|
from django_spire.contrib.service import BaseDjangoModelService
|
|
6
|
-
from django_spire.knowledge.entry.version.services.processor_service import
|
|
7
|
-
EntryVersionProcessorService
|
|
6
|
+
from django_spire.knowledge.entry.version.services.processor_service import EntryVersionProcessorService
|
|
8
7
|
|
|
9
8
|
if TYPE_CHECKING:
|
|
10
9
|
from django_spire.knowledge.entry.version.models import EntryVersion
|
|
@@ -9,8 +9,8 @@ from django_spire.knowledge.entry.version.models import EntryVersion
|
|
|
9
9
|
|
|
10
10
|
def create_test_entry_version(**kwargs) -> EntryVersion:
|
|
11
11
|
data = {
|
|
12
|
-
'entry': create_test_entry(),
|
|
13
|
-
'author': create_user(username=random_string(k=10)),
|
|
12
|
+
'entry': kwargs.pop('entry', None) or create_test_entry(),
|
|
13
|
+
'author': kwargs.pop('author', None) or create_user(username=random_string(k=10)),
|
|
14
14
|
'is_deleted': False,
|
|
15
15
|
'is_active': True
|
|
16
16
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django_spire.core.tests.test_cases import BaseTestCase
|
|
4
|
+
from django_spire.knowledge.entry.version.choices import EntryVersionStatusChoices
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class EntryVersionStatusChoicesTests(BaseTestCase):
|
|
8
|
+
def test_draft_value(self):
|
|
9
|
+
assert EntryVersionStatusChoices.DRAFT == 'draft'
|
|
10
|
+
|
|
11
|
+
def test_published_value(self):
|
|
12
|
+
assert EntryVersionStatusChoices.PUBLISHED == 'published'
|
|
13
|
+
|
|
14
|
+
def test_archived_value(self):
|
|
15
|
+
assert EntryVersionStatusChoices.ARCHIVED == 'archived'
|
|
16
|
+
|
|
17
|
+
def test_choices_count(self):
|
|
18
|
+
assert len(EntryVersionStatusChoices.choices) == 3
|