django-spire 0.23.6__py3-none-any.whl → 0.23.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_spire/ai/admin.py +11 -11
- django_spire/ai/chat/apps.py +1 -0
- django_spire/ai/chat/templates/django_spire/ai/chat/widget/dialog_widget.html +1 -1
- django_spire/ai/chat/tests/factories.py +15 -0
- django_spire/ai/chat/tests/test_controller.py +45 -0
- django_spire/ai/chat/tests/test_models.py +301 -0
- django_spire/ai/chat/tests/test_prompts.py +48 -0
- django_spire/ai/chat/tests/test_responses.py +208 -0
- django_spire/ai/chat/tests/test_router/test_base_chat_router.py +66 -6
- django_spire/ai/chat/tests/test_router/test_chat_workflow.py +73 -3
- django_spire/ai/chat/tests/test_router/test_integration.py +86 -6
- django_spire/ai/chat/tests/test_router/test_intent_decoder.py +93 -1
- django_spire/ai/chat/tests/test_router/test_message_intel.py +60 -1
- django_spire/ai/chat/tests/test_router/test_spire_chat_router.py +110 -0
- django_spire/ai/chat/tests/test_urls/test_json_urls.py +202 -1
- django_spire/ai/context/tests/__init__.py +0 -0
- django_spire/ai/context/tests/test_context.py +188 -0
- django_spire/ai/decorators.py +7 -6
- django_spire/ai/prompt/tests/test_bots.py +100 -10
- django_spire/ai/prompt/tests/test_prompt_intel.py +83 -0
- django_spire/ai/prompt/tests/test_prompt_tuning.py +126 -0
- django_spire/ai/sms/decorators.py +8 -2
- django_spire/ai/sms/tests/test_sms.py +240 -16
- django_spire/ai/sms/tests/test_sms_intel.py +42 -0
- django_spire/ai/sms/tests/test_webhook.py +155 -7
- django_spire/ai/sms/views.py +23 -24
- django_spire/ai/tests/test_ai.py +131 -7
- django_spire/auth/apps.py +4 -2
- django_spire/auth/controller/controller.py +36 -23
- django_spire/auth/controller/exceptions.py +9 -0
- django_spire/auth/group/admin.py +1 -0
- django_spire/auth/group/apps.py +2 -0
- django_spire/auth/group/factories.py +17 -8
- django_spire/auth/group/forms.py +7 -0
- django_spire/auth/group/tests/test_factories.py +146 -0
- django_spire/auth/group/tests/test_forms.py +282 -0
- django_spire/auth/group/tests/test_models.py +192 -0
- django_spire/auth/group/tests/test_querysets.py +98 -0
- django_spire/auth/group/tests/test_utils.py +341 -0
- django_spire/auth/group/tests/test_views.py +377 -0
- django_spire/auth/group/urls/__init__.py +3 -1
- django_spire/auth/group/urls/form_urls.py +2 -0
- django_spire/auth/group/urls/json_urls.py +3 -0
- django_spire/auth/group/urls/page_urls.py +2 -0
- django_spire/auth/group/utils.py +6 -2
- django_spire/auth/group/views/form_views.py +6 -3
- django_spire/auth/group/views/json_views.py +6 -2
- django_spire/auth/mfa/admin.py +2 -0
- django_spire/auth/mfa/apps.py +2 -0
- django_spire/auth/mfa/forms.py +1 -0
- django_spire/auth/mfa/querysets.py +9 -2
- django_spire/auth/mfa/tests/test_models.py +233 -0
- django_spire/auth/mfa/tests/test_utils.py +106 -0
- django_spire/auth/mfa/urls/__init__.py +2 -0
- django_spire/auth/mfa/urls/page_urls.py +2 -0
- django_spire/auth/mfa/urls/redirect_urls.py +2 -0
- django_spire/auth/mfa/views/page_views.py +2 -1
- django_spire/auth/permissions/consts.py +2 -2
- django_spire/auth/permissions/decorators.py +8 -8
- django_spire/auth/permissions/permissions.py +28 -35
- django_spire/auth/permissions/tests/test_decorators.py +333 -0
- django_spire/auth/permissions/tests/test_permissions.py +337 -0
- django_spire/auth/permissions/tests/test_tools.py +305 -0
- django_spire/auth/permissions/tools.py +21 -15
- django_spire/auth/seeding/seed.py +3 -0
- django_spire/auth/seeding/seeder.py +2 -0
- django_spire/auth/tests/test_controller.py +323 -0
- django_spire/auth/tests/test_url_endpoints.py +9 -9
- django_spire/auth/tests/test_views.py +406 -0
- django_spire/auth/urls/admin_urls.py +2 -0
- django_spire/auth/urls/redirect_urls.py +2 -0
- django_spire/auth/user/apps.py +2 -0
- django_spire/auth/user/forms.py +9 -0
- django_spire/auth/user/models.py +1 -1
- django_spire/auth/user/services/services.py +1 -0
- django_spire/auth/user/tests/factories.py +14 -13
- django_spire/auth/user/tests/test_factories.py +166 -2
- django_spire/auth/user/tests/test_forms.py +573 -0
- django_spire/auth/user/tests/test_models.py +257 -0
- django_spire/auth/user/tests/test_services.py +200 -0
- django_spire/auth/user/tests/test_tools.py +153 -0
- django_spire/auth/user/tests/test_user_factories.py +139 -0
- django_spire/auth/user/tests/test_views.py +363 -0
- django_spire/auth/user/tools.py +7 -1
- django_spire/auth/user/urls/form_urls.py +3 -0
- django_spire/auth/user/urls/page_urls.py +3 -0
- django_spire/auth/user/views/form_views.py +19 -10
- django_spire/auth/user/views/page_views.py +8 -2
- django_spire/auth/views/redirect_views.py +14 -9
- django_spire/comment/admin.py +2 -0
- django_spire/comment/apps.py +2 -0
- django_spire/comment/templatetags/comment_tags.py +1 -0
- django_spire/comment/tests/test_forms.py +27 -0
- django_spire/comment/tests/test_models.py +215 -0
- django_spire/comment/tests/test_querysets.py +101 -0
- django_spire/comment/tests/test_utils.py +90 -0
- django_spire/comment/urls.py +2 -0
- django_spire/comment/utils.py +22 -13
- django_spire/comment/views.py +1 -1
- django_spire/conf.py +8 -6
- django_spire/consts.py +1 -1
- django_spire/contrib/breadcrumb/apps.py +2 -0
- django_spire/contrib/breadcrumb/breadcrumbs.py +18 -18
- django_spire/contrib/breadcrumb/tests/test_breadcrumbs.py +198 -0
- django_spire/contrib/constructor/__init__.py +3 -3
- django_spire/contrib/constructor/constructor.py +15 -15
- django_spire/contrib/constructor/django_model_constructor.py +5 -4
- django_spire/contrib/constructor/exceptions.py +5 -3
- django_spire/contrib/constructor/tests/__init__.py +0 -0
- django_spire/contrib/constructor/tests/test_constructor.py +193 -0
- django_spire/contrib/form/tests/__init__.py +0 -0
- django_spire/contrib/form/tests/test_forms.py +203 -0
- django_spire/contrib/generic_views/modal_views.py +2 -1
- django_spire/contrib/generic_views/portal_views.py +20 -19
- django_spire/contrib/generic_views/tests/__init__.py +0 -0
- django_spire/contrib/generic_views/tests/test_views.py +459 -0
- django_spire/contrib/help/apps.py +2 -0
- django_spire/contrib/help/templatetags/help.py +1 -0
- django_spire/contrib/help/tests/__init__.py +0 -0
- django_spire/contrib/help/tests/test_templatetags.py +100 -0
- django_spire/contrib/options/mixins.py +6 -5
- django_spire/contrib/options/tests/factories.py +5 -1
- django_spire/contrib/options/tests/test_options.py +234 -0
- django_spire/contrib/ordering/exceptions.py +7 -3
- django_spire/contrib/ordering/mixins.py +2 -0
- django_spire/contrib/ordering/querysets.py +3 -1
- django_spire/contrib/ordering/services/processor_service.py +8 -4
- django_spire/contrib/ordering/services/service.py +1 -2
- django_spire/contrib/ordering/tests/__init__.py +0 -0
- django_spire/contrib/ordering/tests/test_ordering.py +165 -0
- django_spire/contrib/ordering/validators.py +6 -6
- django_spire/contrib/pagination/templatetags/pagination_tags.py +12 -5
- django_spire/contrib/pagination/tests/__init__.py +0 -0
- django_spire/contrib/pagination/tests/test_pagination.py +179 -0
- django_spire/contrib/performance/decorators.py +16 -6
- django_spire/contrib/performance/tests/__init__.py +0 -0
- django_spire/contrib/performance/tests/test_performance.py +107 -0
- django_spire/contrib/progress/__init__.py +1 -3
- django_spire/contrib/progress/static/django_spire/js/contrib/progress/progress.js +38 -82
- django_spire/contrib/queryset/enums.py +3 -1
- django_spire/contrib/queryset/filter_tools.py +10 -5
- django_spire/contrib/queryset/mixins.py +16 -16
- django_spire/contrib/queryset/tests/__init__.py +0 -0
- django_spire/contrib/queryset/tests/test_queryset.py +137 -0
- django_spire/contrib/seeding/field/base.py +13 -7
- django_spire/contrib/seeding/field/callable.py +8 -1
- django_spire/contrib/seeding/field/cleaners.py +5 -5
- django_spire/contrib/seeding/field/custom.py +20 -10
- django_spire/contrib/seeding/field/django/seeder.py +8 -6
- django_spire/contrib/seeding/field/enums.py +7 -5
- django_spire/contrib/seeding/field/override.py +16 -6
- django_spire/contrib/seeding/field/static.py +9 -2
- django_spire/contrib/seeding/field/tests/test_base.py +18 -14
- django_spire/contrib/seeding/field/tests/test_callable.py +13 -9
- django_spire/contrib/seeding/field/tests/test_cleaners.py +51 -38
- django_spire/contrib/seeding/field/tests/test_static.py +13 -9
- django_spire/contrib/seeding/intelligence/bots/seeder_generator_bot.py +2 -0
- django_spire/contrib/seeding/intelligence/intel.py +5 -1
- django_spire/contrib/seeding/intelligence/prompts/factory.py +6 -1
- django_spire/contrib/seeding/intelligence/prompts/foreign_key_selection_prompt.py +6 -1
- django_spire/contrib/seeding/intelligence/prompts/generate_django_model_seeder_prompts.py +2 -0
- django_spire/contrib/seeding/intelligence/prompts/generic_relationship_selection_prompt.py +7 -1
- django_spire/contrib/seeding/intelligence/prompts/hierarchical_selection_prompt.py +6 -2
- django_spire/contrib/seeding/intelligence/prompts/model_field_choices_prompt.py +8 -2
- django_spire/contrib/seeding/intelligence/prompts/negation_prompt.py +2 -0
- django_spire/contrib/seeding/intelligence/prompts/objective_prompt.py +6 -1
- django_spire/contrib/seeding/management/commands/seeding.py +9 -3
- django_spire/contrib/seeding/management/example.py +2 -0
- django_spire/contrib/seeding/model/base.py +16 -7
- django_spire/contrib/seeding/model/config.py +31 -15
- django_spire/contrib/seeding/model/django/config.py +13 -13
- django_spire/contrib/seeding/model/django/seeder.py +4 -4
- django_spire/contrib/seeding/model/django/tests/test_seeder.py +34 -23
- django_spire/contrib/seeding/model/enums.py +2 -0
- django_spire/contrib/seeding/tests/test_config.py +71 -0
- django_spire/contrib/seeding/tests/test_custom.py +35 -0
- django_spire/contrib/seeding/tests/test_enums.py +40 -0
- django_spire/contrib/seeding/tests/test_intel.py +32 -0
- django_spire/contrib/seeding/tests/test_override.py +63 -0
- django_spire/contrib/service/__init__.py +2 -2
- django_spire/contrib/service/django_model_service.py +16 -15
- django_spire/contrib/service/exceptions.py +5 -3
- django_spire/contrib/service/tests/__init__.py +0 -0
- django_spire/contrib/service/tests/test_service.py +153 -0
- django_spire/contrib/session/apps.py +2 -0
- django_spire/contrib/session/controller.py +48 -42
- django_spire/contrib/session/templatetags/session_tags.py +11 -2
- django_spire/contrib/session/tests/test_session_controller.py +117 -53
- django_spire/contrib/tests/__init__.py +0 -0
- django_spire/contrib/tests/test_utils.py +37 -0
- django_spire/contrib/utils.py +4 -1
- django_spire/core/apps.py +2 -0
- django_spire/core/converters/tests/test_to_data.py +353 -0
- django_spire/core/converters/tests/test_to_enums.py +61 -41
- django_spire/core/converters/tests/test_to_pydantic.py +138 -109
- django_spire/core/converters/to_data.py +29 -10
- django_spire/core/converters/to_enums.py +4 -2
- django_spire/core/converters/to_pydantic.py +22 -22
- django_spire/core/decorators.py +19 -6
- django_spire/core/forms/widgets.py +4 -0
- django_spire/core/maps.py +3 -1
- django_spire/core/middleware/maintenance.py +3 -3
- django_spire/core/middleware.py +8 -6
- django_spire/core/redirect/__init__.py +5 -0
- django_spire/core/redirect/generic_redirect.py +1 -2
- django_spire/core/redirect/tests/__init__.py +0 -0
- django_spire/core/redirect/tests/test_generic_redirect.py +34 -0
- django_spire/core/{tests/tests_redirect.py → redirect/tests/test_safe_redirect.py} +55 -81
- django_spire/core/shortcuts.py +3 -3
- django_spire/core/static/django_spire/css/app-layout.css +1 -1
- django_spire/core/static/django_spire/css/app-navigation.css +3 -3
- django_spire/core/static/django_spire/css/bootstrap-override.css +4 -0
- django_spire/core/tag/admin.py +12 -0
- django_spire/core/tag/intelligence/tag_set_bot.py +2 -0
- django_spire/core/tag/mixins.py +2 -0
- django_spire/core/tag/models.py +2 -0
- django_spire/core/tag/querysets.py +2 -0
- django_spire/core/tag/service/tag_service.py +6 -3
- django_spire/core/tag/tests/test_intelligence.py +9 -9
- django_spire/core/tag/tests/test_tags.py +44 -54
- django_spire/core/tag/tests/test_tools.py +191 -0
- django_spire/core/tag/tools.py +3 -0
- django_spire/core/templates/django_spire/card/card.html +5 -2
- django_spire/core/templates/django_spire/card/title_card.html +9 -4
- django_spire/core/templates/django_spire/infinite_scroll/base.html +1 -0
- django_spire/core/templates/django_spire/navigation/side_navigation.html +19 -24
- django_spire/core/templates/django_spire/page/full_page.html +46 -16
- django_spire/core/templates/django_spire/table/base.html +4 -2
- django_spire/core/templatetags/json.py +6 -2
- django_spire/core/templatetags/message.py +13 -8
- django_spire/core/templatetags/string_formating.py +8 -5
- django_spire/core/templatetags/tests/__init__.py +0 -0
- django_spire/core/templatetags/tests/test_templatetags.py +427 -0
- django_spire/core/templatetags/variable_types.py +17 -9
- django_spire/core/tests/test_cases.py +1 -1
- django_spire/core/tests/test_conf.py +43 -0
- django_spire/core/tests/test_consts.py +28 -0
- django_spire/core/tests/test_context_processors.py +93 -0
- django_spire/core/tests/test_decorators.py +95 -0
- django_spire/core/tests/test_django_spire_utils.py +56 -0
- django_spire/core/tests/test_exceptions.py +37 -0
- django_spire/core/tests/test_models.py +54 -0
- django_spire/core/tests/test_settings.py +45 -0
- django_spire/core/tests/test_shortcuts.py +74 -0
- django_spire/core/tests/test_urls.py +16 -0
- django_spire/core/tests/test_utils.py +58 -0
- django_spire/core/urls.py +4 -1
- django_spire/core/utils.py +12 -8
- django_spire/exceptions.py +16 -1
- django_spire/file/admin.py +4 -2
- django_spire/file/apps.py +8 -10
- django_spire/file/fields.py +7 -7
- django_spire/file/forms.py +1 -1
- django_spire/file/interfaces.py +15 -15
- django_spire/file/mixins.py +1 -4
- django_spire/file/models.py +3 -5
- django_spire/file/tests/factories.py +59 -0
- django_spire/file/tests/test_admin.py +69 -0
- django_spire/file/tests/test_apps.py +24 -0
- django_spire/file/tests/test_fields.py +114 -0
- django_spire/file/tests/test_forms.py +20 -0
- django_spire/file/tests/test_interfaces.py +183 -0
- django_spire/file/tests/test_models.py +82 -0
- django_spire/file/tests/test_querysets.py +102 -0
- django_spire/file/tests/test_utils.py +32 -0
- django_spire/file/tests/test_views.py +145 -0
- django_spire/file/tests/test_widgets.py +82 -0
- django_spire/file/tools.py +8 -2
- django_spire/file/views.py +7 -3
- django_spire/file/widgets.py +12 -12
- django_spire/help_desk/admin.py +15 -0
- django_spire/help_desk/apps.py +2 -0
- django_spire/help_desk/auth/controller.py +2 -0
- django_spire/help_desk/choices.py +2 -0
- django_spire/help_desk/enums.py +2 -0
- django_spire/help_desk/exceptions.py +31 -3
- django_spire/help_desk/forms.py +2 -0
- django_spire/help_desk/models.py +2 -0
- django_spire/help_desk/querysets.py +4 -1
- django_spire/help_desk/services/notification_service.py +26 -27
- django_spire/help_desk/services/service.py +2 -3
- django_spire/help_desk/tests/factories.py +8 -3
- django_spire/help_desk/tests/test_admin.py +41 -0
- django_spire/help_desk/tests/test_apps.py +41 -0
- django_spire/help_desk/tests/test_choices.py +50 -0
- django_spire/help_desk/tests/test_controller.py +87 -0
- django_spire/help_desk/tests/test_enums.py +18 -0
- django_spire/help_desk/tests/test_exceptions.py +37 -0
- django_spire/help_desk/tests/test_forms.py +89 -0
- django_spire/help_desk/tests/test_models.py +59 -0
- django_spire/help_desk/tests/test_querysets.py +38 -0
- django_spire/help_desk/tests/test_services/test_notification_service.py +15 -8
- django_spire/help_desk/tests/test_services/test_service.py +92 -0
- django_spire/help_desk/tests/test_urls/test_form_urls.py +6 -6
- django_spire/help_desk/tests/test_urls/test_page_urls.py +8 -9
- django_spire/help_desk/tests/test_views/test_form_views.py +46 -19
- django_spire/help_desk/tests/test_views/test_page_views.py +32 -9
- django_spire/help_desk/urls/__init__.py +4 -1
- django_spire/help_desk/urls/form_urls.py +3 -0
- django_spire/help_desk/urls/page_urls.py +3 -0
- django_spire/help_desk/views/form_views.py +13 -5
- django_spire/help_desk/views/page_views.py +11 -3
- django_spire/history/activity/admin.py +2 -0
- django_spire/history/activity/apps.py +3 -1
- django_spire/history/activity/mixins.py +13 -7
- django_spire/history/activity/models.py +6 -5
- django_spire/history/activity/querysets.py +2 -0
- django_spire/history/activity/tests/__init__.py +0 -0
- django_spire/history/activity/tests/test_activity.py +176 -0
- django_spire/history/admin.py +9 -2
- django_spire/history/choices.py +3 -0
- django_spire/history/models.py +5 -5
- django_spire/history/tests/test_admin.py +93 -0
- django_spire/history/tests/test_history.py +101 -0
- django_spire/history/tests/test_mixins.py +84 -0
- django_spire/history/viewed/admin.py +3 -1
- django_spire/history/viewed/apps.py +3 -1
- django_spire/history/viewed/models.py +2 -0
- django_spire/history/viewed/tests/__init__.py +0 -0
- django_spire/history/viewed/tests/test_viewed.py +46 -0
- django_spire/knowledge/auth/tests/__init__.py +0 -0
- django_spire/knowledge/auth/tests/test_controller.py +116 -0
- django_spire/knowledge/collection/admin.py +5 -1
- django_spire/knowledge/collection/models.py +3 -1
- django_spire/knowledge/collection/seeding/seed.py +1 -0
- django_spire/knowledge/collection/services/factory_service.py +10 -11
- django_spire/knowledge/collection/services/ordering_service.py +1 -2
- django_spire/knowledge/collection/services/service.py +5 -10
- django_spire/knowledge/collection/services/tag_service.py +5 -2
- django_spire/knowledge/collection/tests/factories.py +28 -1
- django_spire/knowledge/collection/tests/test_models.py +48 -0
- django_spire/knowledge/collection/tests/test_querysets.py +93 -0
- django_spire/knowledge/collection/tests/test_services/test_factory_service.py +100 -0
- django_spire/knowledge/collection/tests/test_services/test_services.py +160 -0
- django_spire/knowledge/collection/tests/test_urls/test_form_urls.py +21 -3
- django_spire/knowledge/collection/tests/test_urls/test_json_urls.py +39 -1
- django_spire/knowledge/collection/tests/test_urls/test_page_urls.py +12 -4
- django_spire/knowledge/collection/urls/__init__.py +3 -0
- django_spire/knowledge/collection/urls/form_urls.py +2 -0
- django_spire/knowledge/collection/urls/json_urls.py +2 -0
- django_spire/knowledge/collection/urls/page_urls.py +2 -0
- django_spire/knowledge/collection/views/form_views.py +4 -4
- django_spire/knowledge/collection/views/json_views.py +5 -1
- django_spire/knowledge/collection/views/page_views.py +5 -2
- django_spire/knowledge/entry/admin.py +7 -1
- django_spire/knowledge/entry/forms.py +2 -0
- django_spire/knowledge/entry/models.py +2 -0
- django_spire/knowledge/entry/seeding/seed.py +3 -0
- django_spire/knowledge/entry/services/automation_service.py +5 -4
- django_spire/knowledge/entry/services/factory_service.py +7 -5
- django_spire/knowledge/entry/services/service.py +4 -7
- django_spire/knowledge/entry/services/tag_service.py +0 -1
- django_spire/knowledge/entry/services/tool_service.py +1 -0
- django_spire/knowledge/entry/services/transformation_services.py +1 -5
- django_spire/knowledge/entry/tests/factories.py +1 -2
- django_spire/knowledge/entry/tests/test_factory_service.py +20 -0
- django_spire/knowledge/entry/tests/test_models.py +41 -0
- django_spire/knowledge/entry/tests/test_querysets.py +71 -0
- django_spire/knowledge/entry/tests/test_services.py +94 -0
- django_spire/knowledge/entry/tests/test_urls/test_form_urls.py +9 -14
- django_spire/knowledge/entry/tests/test_urls/test_json_urls.py +48 -5
- django_spire/knowledge/entry/tests/test_urls/test_page_urls.py +6 -8
- django_spire/knowledge/entry/tests/test_urls/test_template_urls.py +40 -0
- django_spire/knowledge/entry/urls/form_urls.py +2 -0
- django_spire/knowledge/entry/urls/json_urls.py +2 -0
- django_spire/knowledge/entry/urls/page_urls.py +2 -0
- django_spire/knowledge/entry/urls/template_urls.py +2 -0
- django_spire/knowledge/entry/version/block/choices.py +2 -0
- django_spire/knowledge/entry/version/block/data/data.py +1 -0
- django_spire/knowledge/entry/version/block/data/list/data.py +8 -13
- django_spire/knowledge/entry/version/block/data/list/maps.py +3 -0
- django_spire/knowledge/entry/version/block/data/list/meta.py +1 -2
- django_spire/knowledge/entry/version/block/data/list/tests/__init__.py +0 -0
- django_spire/knowledge/entry/version/block/data/list/tests/test_maps.py +32 -0
- django_spire/knowledge/entry/version/block/data/list/tests/test_meta.py +58 -0
- django_spire/knowledge/entry/version/block/data/maps.py +3 -6
- django_spire/knowledge/entry/version/block/models.py +7 -5
- django_spire/knowledge/entry/version/block/seeding/constants.py +5 -4
- django_spire/knowledge/entry/version/block/services/service.py +2 -3
- django_spire/knowledge/entry/version/block/tests/factories.py +4 -10
- django_spire/knowledge/entry/version/block/tests/test_choices.py +56 -0
- django_spire/knowledge/entry/version/block/tests/test_data.py +90 -0
- django_spire/knowledge/entry/version/block/tests/test_maps.py +37 -0
- django_spire/knowledge/entry/version/block/tests/test_models.py +55 -0
- django_spire/knowledge/entry/version/block/tests/test_querysets.py +35 -0
- django_spire/knowledge/entry/version/block/tests/test_services.py +65 -0
- django_spire/knowledge/entry/version/choices.py +2 -0
- django_spire/knowledge/entry/version/converters/converter.py +1 -1
- django_spire/knowledge/entry/version/converters/docx_converter.py +4 -7
- django_spire/knowledge/entry/version/converters/markdown_converter.py +20 -20
- django_spire/knowledge/entry/version/maps.py +4 -5
- django_spire/knowledge/entry/version/querysets.py +1 -1
- django_spire/knowledge/entry/version/seeding/seeder.py +1 -2
- django_spire/knowledge/entry/version/services/processor_service.py +5 -4
- django_spire/knowledge/entry/version/services/service.py +1 -2
- django_spire/knowledge/entry/version/tests/factories.py +2 -2
- django_spire/knowledge/entry/version/tests/test_choices.py +18 -0
- django_spire/knowledge/entry/version/tests/test_converters/test_docx_converter.py +56 -8
- django_spire/knowledge/entry/version/tests/test_converters/test_markdown_converter.py +78 -0
- django_spire/knowledge/entry/version/tests/test_maps.py +58 -0
- django_spire/knowledge/entry/version/tests/test_models.py +23 -0
- django_spire/knowledge/entry/version/tests/test_querysets.py +26 -0
- django_spire/knowledge/entry/version/tests/test_services.py +62 -0
- django_spire/knowledge/entry/version/tests/test_urls/test_json_urls.py +27 -8
- django_spire/knowledge/entry/version/tests/test_urls/test_page_urls.py +15 -8
- django_spire/knowledge/entry/version/tests/test_urls/test_redirect_urls.py +38 -0
- django_spire/knowledge/entry/version/urls/__init__.py +3 -0
- django_spire/knowledge/entry/version/urls/json_urls.py +2 -1
- django_spire/knowledge/entry/version/urls/page_urls.py +2 -0
- django_spire/knowledge/entry/version/urls/redirect_urls.py +2 -0
- django_spire/knowledge/entry/version/views/json_views.py +5 -1
- django_spire/knowledge/entry/version/views/page_views.py +10 -3
- django_spire/knowledge/entry/version/views/redirect_views.py +5 -1
- django_spire/knowledge/entry/views/form_views.py +16 -8
- django_spire/knowledge/entry/views/json_views.py +3 -1
- django_spire/knowledge/entry/views/page_views.py +8 -2
- django_spire/knowledge/entry/views/template_views.py +7 -1
- django_spire/knowledge/exceptions.py +2 -1
- django_spire/knowledge/intelligence/intel/answer_intel.py +2 -1
- django_spire/knowledge/intelligence/intel/entry_intel.py +0 -1
- django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +4 -5
- django_spire/knowledge/models.py +1 -2
- django_spire/knowledge/tests/__init__.py +0 -0
- django_spire/knowledge/tests/test_templatetags.py +40 -0
- django_spire/knowledge/tests/test_urls/__init__.py +0 -0
- django_spire/knowledge/tests/test_urls/test_page_urls.py +24 -0
- django_spire/knowledge/urls/__init__.py +2 -0
- django_spire/knowledge/urls/page_urls.py +2 -0
- django_spire/knowledge/views/page_views.py +8 -3
- django_spire/notification/admin.py +3 -1
- django_spire/notification/app/admin.py +2 -0
- django_spire/notification/app/apps.py +3 -1
- django_spire/notification/app/exceptions.py +9 -2
- django_spire/notification/app/models.py +8 -4
- django_spire/notification/app/processor.py +22 -26
- django_spire/notification/app/querysets.py +2 -0
- django_spire/notification/app/tests/__init__.py +0 -0
- django_spire/notification/app/tests/factories.py +34 -0
- django_spire/notification/app/tests/test_apps.py +24 -0
- django_spire/notification/app/tests/test_models.py +72 -0
- django_spire/notification/app/tests/test_processor.py +111 -0
- django_spire/notification/app/tests/test_querysets.py +90 -0
- django_spire/notification/app/tests/test_views/__init__.py +0 -0
- django_spire/notification/app/tests/test_views/test_json_views.py +48 -0
- django_spire/notification/app/tests/test_views/test_page_views.py +19 -0
- django_spire/notification/app/urls/__init__.py +3 -1
- django_spire/notification/app/urls/json_urls.py +6 -4
- django_spire/notification/app/urls/page_urls.py +4 -3
- django_spire/notification/app/urls/template_urls.py +4 -2
- django_spire/notification/apps.py +4 -1
- django_spire/notification/email/admin.py +5 -1
- django_spire/notification/email/apps.py +3 -1
- django_spire/notification/email/exceptions.py +4 -2
- django_spire/notification/email/helper.py +5 -3
- django_spire/notification/email/models.py +4 -0
- django_spire/notification/email/processor.py +19 -15
- django_spire/notification/email/querysets.py +3 -0
- django_spire/notification/email/tests/__init__.py +0 -0
- django_spire/notification/email/tests/factories.py +35 -0
- django_spire/notification/email/tests/test_apps.py +24 -0
- django_spire/notification/email/tests/test_models.py +52 -0
- django_spire/notification/email/tests/test_processor.py +92 -0
- django_spire/notification/email/tests/test_querysets.py +43 -0
- django_spire/notification/exceptions.py +17 -2
- django_spire/notification/managers.py +7 -1
- django_spire/notification/maps.py +4 -1
- django_spire/notification/mixins.py +2 -0
- django_spire/notification/models.py +3 -1
- django_spire/notification/processors/notification.py +12 -5
- django_spire/notification/processors/processor.py +2 -0
- django_spire/notification/processors/tests/__init__.py +0 -0
- django_spire/notification/processors/tests/test_notification.py +106 -0
- django_spire/notification/push/admin.py +10 -1
- django_spire/notification/push/apps.py +3 -1
- django_spire/notification/push/models.py +2 -3
- django_spire/notification/push/tests/__init__.py +0 -0
- django_spire/notification/push/tests/test_apps.py +24 -0
- django_spire/notification/push/tests/test_models.py +28 -0
- django_spire/notification/querysets.py +7 -1
- django_spire/notification/sms/admin.py +2 -0
- django_spire/notification/sms/apps.py +4 -1
- django_spire/notification/sms/automations.py +2 -0
- django_spire/notification/sms/choices.py +2 -0
- django_spire/notification/sms/exceptions.py +19 -5
- django_spire/notification/sms/helper.py +33 -23
- django_spire/notification/sms/models.py +5 -1
- django_spire/notification/sms/processor.py +20 -20
- django_spire/notification/sms/querysets.py +2 -0
- django_spire/notification/sms/tests/factories.py +33 -0
- django_spire/notification/sms/tests/test_apps.py +24 -0
- django_spire/notification/sms/tests/test_automation.py +38 -0
- django_spire/notification/sms/tests/test_choices.py +15 -0
- django_spire/notification/sms/tests/test_consts.py +17 -0
- django_spire/notification/sms/tests/test_exceptions.py +27 -0
- django_spire/notification/sms/tests/test_helper.py +50 -0
- django_spire/notification/sms/tests/test_models.py +81 -0
- django_spire/notification/sms/tests/test_processor.py +107 -0
- django_spire/notification/sms/tests/test_tools.py +25 -11
- django_spire/notification/sms/tools.py +16 -5
- django_spire/notification/sms/urls/__init__.py +3 -1
- django_spire/notification/sms/urls/media_urls.py +2 -0
- django_spire/notification/sms/views/media_views.py +14 -4
- django_spire/notification/tests/__init__.py +0 -0
- django_spire/notification/tests/factories.py +26 -0
- django_spire/notification/tests/test_admin.py +55 -0
- django_spire/notification/tests/test_apps.py +30 -0
- django_spire/notification/tests/test_automation.py +18 -0
- django_spire/notification/tests/test_choices.py +59 -0
- django_spire/notification/tests/test_exceptions.py +58 -0
- django_spire/notification/tests/test_managers.py +100 -0
- django_spire/notification/tests/test_maps.py +31 -0
- django_spire/notification/tests/test_models.py +76 -0
- django_spire/notification/tests/test_querysets.py +184 -0
- django_spire/notification/tests/test_utils.py +23 -0
- django_spire/notification/urls.py +3 -1
- django_spire/notification/utils.py +3 -1
- django_spire/settings.py +3 -0
- django_spire/theme/tests/test_context_processor.py +15 -13
- django_spire/theme/tests/test_enums.py +2 -2
- django_spire/theme/tests/test_filesystem.py +2 -5
- django_spire/theme/tests/test_integration.py +12 -12
- django_spire/theme/tests/test_model.py +40 -38
- django_spire/theme/tests/test_views/test_json_views.py +33 -33
- django_spire/theme/urls/json_urls.py +3 -0
- django_spire/theme/urls/page_urls.py +3 -0
- django_spire/urls.py +19 -15
- django_spire/utils.py +13 -4
- {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/METADATA +1 -1
- {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/RECORD +532 -361
- django_spire/contrib/options/tests/test_unit.py +0 -148
- django_spire/contrib/progress/views.py +0 -64
- django_spire/contrib/seeding/tests/test_seeding.py +0 -25
- django_spire/core/tests/test_templatetags.py +0 -117
- django_spire/core/tests/tests_shortcuts.py +0 -73
- django_spire/history/activity/tests.py +0 -3
- django_spire/history/activity/views.py +0 -3
- django_spire/knowledge/collection/tests/test_services/test_transformation_service.py +0 -71
- django_spire/notification/app/tests.py +0 -3
- {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/WHEEL +0 -0
- {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/licenses/LICENSE.md +0 -0
- {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from unittest.mock import MagicMock
|
|
4
|
+
|
|
5
|
+
from django.contrib.sessions.middleware import SessionMiddleware
|
|
6
|
+
from django.db.models import QuerySet
|
|
7
|
+
from django.test import RequestFactory, TestCase
|
|
8
|
+
|
|
9
|
+
from django_spire.contrib.queryset.enums import SessionFilterActionEnum
|
|
10
|
+
from django_spire.contrib.queryset.filter_tools import filter_by_lookup_map
|
|
11
|
+
from django_spire.contrib.queryset.mixins import SearchQuerySetMixin, SessionFilterQuerySetMixin
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestSessionFilterActionEnum(TestCase):
|
|
15
|
+
def test_clear_value(self) -> None:
|
|
16
|
+
assert SessionFilterActionEnum.CLEAR == 'Clear'
|
|
17
|
+
|
|
18
|
+
def test_filter_value(self) -> None:
|
|
19
|
+
assert SessionFilterActionEnum.FILTER == 'Filter'
|
|
20
|
+
|
|
21
|
+
def test_is_str_enum(self) -> None:
|
|
22
|
+
assert isinstance(SessionFilterActionEnum.CLEAR, str)
|
|
23
|
+
assert isinstance(SessionFilterActionEnum.FILTER, str)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TestFilterByLookupMap(TestCase):
|
|
27
|
+
def setUp(self) -> None:
|
|
28
|
+
self.queryset = MagicMock(spec=QuerySet)
|
|
29
|
+
self.queryset.filter.return_value = self.queryset
|
|
30
|
+
|
|
31
|
+
def test_applies_filter_from_lookup_map(self) -> None:
|
|
32
|
+
lookup_map = {'name': 'name__icontains'}
|
|
33
|
+
data = {'name': 'test'}
|
|
34
|
+
|
|
35
|
+
filter_by_lookup_map(self.queryset, lookup_map, data)
|
|
36
|
+
|
|
37
|
+
self.queryset.filter.assert_called_once_with(name__icontains='test')
|
|
38
|
+
|
|
39
|
+
def test_extra_filters_applied(self) -> None:
|
|
40
|
+
lookup_map = {'name': 'name__icontains'}
|
|
41
|
+
data = {'name': 'test'}
|
|
42
|
+
extra_filters = {'is_active': True}
|
|
43
|
+
|
|
44
|
+
filter_by_lookup_map(self.queryset, lookup_map, data, extra_filters)
|
|
45
|
+
|
|
46
|
+
self.queryset.filter.assert_called_once_with(
|
|
47
|
+
name__icontains='test',
|
|
48
|
+
is_active=True
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def test_extra_filters_defaults_to_empty_dict(self) -> None:
|
|
52
|
+
lookup_map = {}
|
|
53
|
+
data = {}
|
|
54
|
+
|
|
55
|
+
filter_by_lookup_map(self.queryset, lookup_map, data)
|
|
56
|
+
|
|
57
|
+
self.queryset.filter.assert_called_once_with()
|
|
58
|
+
|
|
59
|
+
def test_ignores_empty_string_values(self) -> None:
|
|
60
|
+
lookup_map = {'name': 'name__icontains', 'email': 'email__icontains'}
|
|
61
|
+
data = {'name': 'test', 'email': ''}
|
|
62
|
+
|
|
63
|
+
filter_by_lookup_map(self.queryset, lookup_map, data)
|
|
64
|
+
|
|
65
|
+
self.queryset.filter.assert_called_once_with(name__icontains='test')
|
|
66
|
+
|
|
67
|
+
def test_ignores_keys_not_in_lookup_map(self) -> None:
|
|
68
|
+
lookup_map = {'name': 'name__icontains'}
|
|
69
|
+
data = {'name': 'test', 'other': 'value'}
|
|
70
|
+
|
|
71
|
+
filter_by_lookup_map(self.queryset, lookup_map, data)
|
|
72
|
+
|
|
73
|
+
self.queryset.filter.assert_called_once_with(name__icontains='test')
|
|
74
|
+
|
|
75
|
+
def test_ignores_none_values(self) -> None:
|
|
76
|
+
lookup_map = {'name': 'name__icontains', 'email': 'email__icontains'}
|
|
77
|
+
data = {'name': 'test', 'email': None}
|
|
78
|
+
|
|
79
|
+
filter_by_lookup_map(self.queryset, lookup_map, data)
|
|
80
|
+
|
|
81
|
+
self.queryset.filter.assert_called_once_with(name__icontains='test')
|
|
82
|
+
|
|
83
|
+
def test_ignores_empty_list_values(self) -> None:
|
|
84
|
+
lookup_map = {'name': 'name__icontains', 'tags': 'tags__in'}
|
|
85
|
+
data = {'name': 'test', 'tags': []}
|
|
86
|
+
|
|
87
|
+
filter_by_lookup_map(self.queryset, lookup_map, data)
|
|
88
|
+
|
|
89
|
+
self.queryset.filter.assert_called_once_with(name__icontains='test')
|
|
90
|
+
|
|
91
|
+
def test_multiple_filters_applied(self) -> None:
|
|
92
|
+
lookup_map = {'name': 'name__icontains', 'status': 'status'}
|
|
93
|
+
data = {'name': 'test', 'status': 'active'}
|
|
94
|
+
|
|
95
|
+
filter_by_lookup_map(self.queryset, lookup_map, data)
|
|
96
|
+
|
|
97
|
+
self.queryset.filter.assert_called_once_with(
|
|
98
|
+
name__icontains='test',
|
|
99
|
+
status='active'
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
def test_returns_filtered_queryset(self) -> None:
|
|
103
|
+
lookup_map = {'name': 'name__icontains'}
|
|
104
|
+
data = {'name': 'test'}
|
|
105
|
+
|
|
106
|
+
result = filter_by_lookup_map(self.queryset, lookup_map, data)
|
|
107
|
+
|
|
108
|
+
assert result == self.queryset
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class TestSessionFilterQuerySetMixin(TestCase):
|
|
112
|
+
def setUp(self) -> None:
|
|
113
|
+
self.factory = RequestFactory()
|
|
114
|
+
|
|
115
|
+
def _create_request_with_session(self, path: str = '/', data: dict | None = None):
|
|
116
|
+
request = self.factory.get(path, data or {})
|
|
117
|
+
middleware = SessionMiddleware(lambda req: None)
|
|
118
|
+
middleware.process_request(request)
|
|
119
|
+
request.session.save()
|
|
120
|
+
return request
|
|
121
|
+
|
|
122
|
+
def test_has_bulk_filter_abstract_method(self) -> None:
|
|
123
|
+
assert hasattr(SessionFilterQuerySetMixin, 'bulk_filter')
|
|
124
|
+
|
|
125
|
+
def test_has_process_session_filter_method(self) -> None:
|
|
126
|
+
assert hasattr(SessionFilterQuerySetMixin, 'process_session_filter')
|
|
127
|
+
|
|
128
|
+
def test_is_queryset_subclass(self) -> None:
|
|
129
|
+
assert issubclass(SessionFilterQuerySetMixin, QuerySet)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class TestSearchQuerySetMixin(TestCase):
|
|
133
|
+
def test_has_search_abstract_method(self) -> None:
|
|
134
|
+
assert hasattr(SearchQuerySetMixin, 'search')
|
|
135
|
+
|
|
136
|
+
def test_is_queryset_subclass(self) -> None:
|
|
137
|
+
assert issubclass(SearchQuerySetMixin, QuerySet)
|
|
@@ -1,24 +1,30 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
2
5
|
|
|
3
6
|
from django_spire.contrib.seeding.field.cleaners import normalize_seeder_fields
|
|
4
7
|
from django_spire.contrib.seeding.field.enums import FieldSeederTypesEnum
|
|
5
8
|
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
6
12
|
|
|
7
13
|
class BaseFieldSeeder(ABC):
|
|
8
14
|
keyword: str = None
|
|
9
15
|
seed_keywords = FieldSeederTypesEnum._value2member_map_.keys()
|
|
10
16
|
|
|
11
17
|
def __init__(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
):
|
|
18
|
+
self,
|
|
19
|
+
fields: dict | None = None,
|
|
20
|
+
default_to: str = 'llm'
|
|
21
|
+
) -> None:
|
|
16
22
|
|
|
17
23
|
self.fields = self._normalize_fields(fields or {})
|
|
18
24
|
self.default_to = default_to
|
|
19
25
|
|
|
20
26
|
|
|
21
|
-
def __init_subclass__(cls, **kwargs):
|
|
27
|
+
def __init_subclass__(cls, **kwargs: Any) -> None:
|
|
22
28
|
super().__init_subclass__(**kwargs)
|
|
23
29
|
|
|
24
30
|
if cls.keyword is None:
|
|
@@ -35,9 +41,9 @@ class BaseFieldSeeder(ABC):
|
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
@property
|
|
38
|
-
def seeder_fields(self):
|
|
44
|
+
def seeder_fields(self) -> dict:
|
|
39
45
|
return self.filter_fields(self.keyword)
|
|
40
46
|
|
|
41
47
|
@abstractmethod
|
|
42
|
-
def seed(self, model_seeder_cls, count: int):
|
|
48
|
+
def seed(self, model_seeder_cls: Any, count: int) -> list[dict]:
|
|
43
49
|
pass
|
|
@@ -1,11 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
1
5
|
from django_spire.contrib.seeding.field.base import BaseFieldSeeder
|
|
2
6
|
from django_spire.contrib.seeding.field.enums import FieldSeederTypesEnum
|
|
3
7
|
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
4
11
|
|
|
5
12
|
class CallableFieldSeeder(BaseFieldSeeder):
|
|
6
13
|
keyword = FieldSeederTypesEnum.CALLABLE
|
|
7
14
|
|
|
8
|
-
def seed(self, manager = None, count = 1) -> list[dict]:
|
|
15
|
+
def seed(self, manager: Any = None, count: int = 1) -> list[dict]:
|
|
9
16
|
return [
|
|
10
17
|
{field_name: func[1]() for field_name, func in self.seeder_fields.items()}
|
|
11
18
|
for _ in range(count)
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import copy
|
|
4
4
|
|
|
5
|
+
from django_spire.contrib.seeding.field.enums import FieldSeederTypesEnum
|
|
6
|
+
|
|
7
|
+
|
|
5
8
|
def normalize_seeder_fields(fields: dict) -> dict:
|
|
6
9
|
normalized = {}
|
|
7
10
|
|
|
@@ -21,14 +24,11 @@ def normalize_seeder_fields(fields: dict) -> dict:
|
|
|
21
24
|
else:
|
|
22
25
|
extra = (extra_value,)
|
|
23
26
|
|
|
24
|
-
normalized[k] = (v[0],
|
|
25
|
-
|
|
27
|
+
normalized[k] = (v[0], *v[1:2], *extra)
|
|
26
28
|
elif callable(v):
|
|
27
29
|
normalized[k] = ("callable", v)
|
|
28
|
-
|
|
29
30
|
elif isinstance(v, str) and v.lower() in FieldSeederTypesEnum._value2member_map_:
|
|
30
31
|
normalized[k] = (v.lower(),)
|
|
31
|
-
|
|
32
32
|
else:
|
|
33
33
|
normalized[k] = ("static", v)
|
|
34
34
|
|
|
@@ -1,36 +1,46 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
1
5
|
from django.utils import timezone
|
|
2
6
|
from faker import Faker
|
|
3
7
|
|
|
4
8
|
from django_spire.contrib.seeding.field.base import BaseFieldSeeder
|
|
5
9
|
from django_spire.contrib.seeding.field.enums import FieldSeederTypesEnum
|
|
6
10
|
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from django.db.models import Model
|
|
16
|
+
|
|
7
17
|
|
|
8
18
|
class CustomFieldSeeder(BaseFieldSeeder):
|
|
9
19
|
keyword = FieldSeederTypesEnum.CUSTOM
|
|
10
20
|
|
|
11
|
-
def in_order(self, values: list, index: int) ->
|
|
21
|
+
def in_order(self, values: list, index: int) -> Any:
|
|
12
22
|
if not values:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
index = index % len(values)
|
|
23
|
+
message = 'Cannot select from empty values list. Make sure the related model has existing records before seeding foreign keys.'
|
|
24
|
+
raise ValueError(message)
|
|
25
|
+
|
|
26
|
+
# Index loops back on itself
|
|
27
|
+
index = index % len(values)
|
|
18
28
|
return values[index]
|
|
19
29
|
|
|
20
|
-
def date_time_between(self, start_date: str, end_date: str):
|
|
30
|
+
def date_time_between(self, start_date: str, end_date: str) -> datetime:
|
|
21
31
|
faker = Faker()
|
|
22
32
|
naive_dt = faker.date_time_between(start_date=start_date, end_date=end_date)
|
|
23
33
|
return timezone.make_aware(naive_dt)
|
|
24
34
|
|
|
25
|
-
def fk_random(self, model_class, ids: list[int]):
|
|
35
|
+
def fk_random(self, model_class: type[Model], ids: list[int]) -> int:
|
|
26
36
|
faker = Faker()
|
|
27
37
|
return faker.random_element(elements=ids)
|
|
28
38
|
|
|
29
|
-
def fk_in_order(self, model_class, index: int, ids: list[int]):
|
|
39
|
+
def fk_in_order(self, model_class: type[Model], index: int, ids: list[int]) -> int:
|
|
30
40
|
"""Takes a queryset, calls it then returns a random id"""
|
|
31
41
|
return self.in_order(values=ids, index=index)
|
|
32
42
|
|
|
33
|
-
def seed(self, manager, count) -> list[dict]:
|
|
43
|
+
def seed(self, manager: Any, count: int) -> list[dict]:
|
|
34
44
|
data = []
|
|
35
45
|
|
|
36
46
|
foreign_keys = {}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
3
5
|
from dandy import BaseIntel, Prompt
|
|
4
6
|
|
|
5
7
|
from django_spire.core.converters import django_to_pydantic_model, fake_model_field_value
|
|
@@ -7,11 +9,14 @@ from django_spire.contrib.seeding.field.base import BaseFieldSeeder
|
|
|
7
9
|
from django_spire.contrib.seeding.field.enums import FieldSeederTypesEnum
|
|
8
10
|
from django_spire.contrib.seeding.intelligence.bots.field_seeding_bots import LlmFieldSeedingBot
|
|
9
11
|
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
10
15
|
|
|
11
16
|
class DjangoFieldLlmSeeder(BaseFieldSeeder):
|
|
12
17
|
keyword = FieldSeederTypesEnum.LLM
|
|
13
18
|
|
|
14
|
-
def seed(self, model_seeder, count: int = 1
|
|
19
|
+
def seed(self, model_seeder: Any, count: int = 1) -> list[dict]:
|
|
15
20
|
|
|
16
21
|
include_fields = list(self.seeder_fields.keys())
|
|
17
22
|
|
|
@@ -31,10 +36,7 @@ class DjangoFieldLlmSeeder(BaseFieldSeeder):
|
|
|
31
36
|
base_prompt = (
|
|
32
37
|
Prompt()
|
|
33
38
|
.heading('General Seeding Rules')
|
|
34
|
-
.list(
|
|
35
|
-
[
|
|
36
|
-
'Create data for each field provided.'
|
|
37
|
-
])
|
|
39
|
+
.list(['Create data for each field provided.'])
|
|
38
40
|
.heading('Field Rules & Context')
|
|
39
41
|
.prompt(self.field_prompt)
|
|
40
42
|
)
|
|
@@ -115,7 +117,7 @@ class DjangoFieldLlmSeeder(BaseFieldSeeder):
|
|
|
115
117
|
class DjangoFieldFakerSeeder(BaseFieldSeeder):
|
|
116
118
|
keyword = FieldSeederTypesEnum.FAKER
|
|
117
119
|
|
|
118
|
-
def seed(self, model_seeder, count = 1) -> list[dict]:
|
|
120
|
+
def seed(self, model_seeder: Any, count: int = 1) -> list[dict]:
|
|
119
121
|
data = []
|
|
120
122
|
|
|
121
123
|
for _ in range(count):
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from enum import Enum
|
|
2
4
|
|
|
3
5
|
|
|
4
6
|
class FieldSeederTypesEnum(str, Enum):
|
|
5
|
-
LLM =
|
|
6
|
-
FAKER =
|
|
7
|
-
STATIC =
|
|
8
|
-
CALLABLE =
|
|
9
|
-
CUSTOM =
|
|
7
|
+
LLM = 'llm'
|
|
8
|
+
FAKER = 'faker'
|
|
9
|
+
STATIC = 'static'
|
|
10
|
+
CALLABLE = 'callable'
|
|
11
|
+
CUSTOM = 'custom'
|
|
@@ -1,18 +1,28 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
1
9
|
class FieldOverride:
|
|
2
|
-
def __init__(self, seeder_class):
|
|
10
|
+
def __init__(self, seeder_class: Any) -> None:
|
|
3
11
|
self.seeder_class = seeder_class
|
|
4
12
|
self.overrides = {}
|
|
5
13
|
|
|
6
|
-
def __getattr__(self, name):
|
|
14
|
+
def __getattr__(self, name: str) -> Any:
|
|
7
15
|
"""
|
|
8
16
|
Delegate attribute lookup to self.seeder_class if the attribute isn't found
|
|
9
17
|
in this FieldOverride instance.
|
|
10
18
|
"""
|
|
11
19
|
|
|
12
20
|
attr = getattr(self.seeder_class, name)
|
|
21
|
+
|
|
13
22
|
if callable(attr):
|
|
14
|
-
def wrapper(*args, **kwargs):
|
|
23
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
15
24
|
# TODO: Error here if fields is passed as an arg.
|
|
25
|
+
|
|
16
26
|
# Pass the overrides field to the seed method
|
|
17
27
|
if 'fields' in kwargs and isinstance(kwargs['fields'], dict):
|
|
18
28
|
kwargs['fields'] = {**kwargs['fields'], **self.overrides}
|
|
@@ -22,12 +32,12 @@ class FieldOverride:
|
|
|
22
32
|
return attr(*args, **kwargs)
|
|
23
33
|
|
|
24
34
|
return wrapper
|
|
35
|
+
|
|
25
36
|
return attr
|
|
26
37
|
|
|
27
|
-
def filter(self, **kwargs):
|
|
38
|
+
def filter(self, **kwargs: Any) -> FieldOverride:
|
|
28
39
|
self.overrides.update(kwargs)
|
|
29
40
|
return self
|
|
30
41
|
|
|
31
|
-
def seed(self, count=1):
|
|
42
|
+
def seed(self, count: int = 1) -> list:
|
|
32
43
|
return self.seeder_class.seed(count=count, fields=self.overrides)
|
|
33
|
-
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
1
5
|
from django_spire.contrib.seeding.field.base import BaseFieldSeeder
|
|
2
6
|
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
3
10
|
|
|
4
11
|
class StaticFieldSeeder(BaseFieldSeeder):
|
|
5
|
-
keyword =
|
|
12
|
+
keyword = 'static'
|
|
6
13
|
|
|
7
|
-
def seed(self, manager = None, count = 1) -> list:
|
|
14
|
+
def seed(self, manager: Any = None, count: int = 1) -> list[dict]:
|
|
8
15
|
return [
|
|
9
16
|
{field_name: value[1] for field_name, value in self.seeder_fields.items()}
|
|
10
17
|
for _ in range(count)
|
|
@@ -1,24 +1,28 @@
|
|
|
1
|
-
import
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django.test import TestCase
|
|
4
|
+
|
|
2
5
|
from django_spire.contrib.seeding.field.base import BaseFieldSeeder
|
|
3
6
|
|
|
4
7
|
|
|
5
8
|
class DummyFieldSeeder(BaseFieldSeeder):
|
|
6
|
-
keyword =
|
|
7
|
-
|
|
8
|
-
def seed(self, model_seeder_cls=None, count: int = 1):
|
|
9
|
-
return [{"fake": True} for _ in range(count)]
|
|
9
|
+
keyword = 'faker'
|
|
10
10
|
|
|
11
|
+
def seed(self, model_seeder_cls=None, count: int = 1) -> list[dict]:
|
|
12
|
+
return [{'fake': True} for _ in range(count)]
|
|
11
13
|
|
|
12
|
-
class TestBaseFieldSeeder(unittest.TestCase):
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
class TestBaseFieldSeeder(TestCase):
|
|
16
|
+
def test_filter_fields(self) -> None:
|
|
15
17
|
fields = {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
'name': ('faker', 'name'),
|
|
19
|
+
'email': ('faker', 'email'),
|
|
20
|
+
'description': ('llm', 'prompt')
|
|
19
21
|
}
|
|
20
22
|
seeder = DummyFieldSeeder(fields=fields)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
|
|
24
|
+
faker_fields = seeder.filter_fields('faker')
|
|
25
|
+
|
|
26
|
+
assert 'name' in faker_fields
|
|
27
|
+
assert 'email' in faker_fields
|
|
28
|
+
assert 'description' not in faker_fields
|
|
@@ -1,18 +1,22 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django.test import TestCase
|
|
3
4
|
|
|
5
|
+
from django_spire.contrib.seeding.field.callable import CallableFieldSeeder
|
|
4
6
|
|
|
5
|
-
class TestCallableFieldSeeder(unittest.TestCase):
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
class TestCallableFieldSeeder(TestCase):
|
|
9
|
+
def test_seeds_callable_fields(self) -> None:
|
|
8
10
|
fields = {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
'timestamp': lambda: '2024-01-01 12:00:00',
|
|
12
|
+
'status': lambda: 'active'
|
|
11
13
|
}
|
|
12
14
|
seeder = CallableFieldSeeder(fields=fields)
|
|
15
|
+
|
|
13
16
|
result = seeder.seed(None, count=3)
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
assert len(result) == 3
|
|
19
|
+
|
|
16
20
|
for row in result:
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
assert row['timestamp'] == '2024-01-01 12:00:00'
|
|
22
|
+
assert row['status'] == 'active'
|
|
@@ -1,61 +1,74 @@
|
|
|
1
|
-
import
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
2
3
|
from copy import copy
|
|
4
|
+
|
|
5
|
+
from django.test import TestCase
|
|
6
|
+
|
|
3
7
|
from django_spire.contrib.seeding.field.cleaners import normalize_seeder_fields
|
|
4
8
|
|
|
5
9
|
|
|
6
10
|
FIELDS = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
'faker_tuple': ('faker', 'name'),
|
|
12
|
+
'llm_type': 'llm',
|
|
13
|
+
'faker_type': 'faker',
|
|
14
|
+
'static_type': 'static',
|
|
15
|
+
'callable_type': 'callable',
|
|
16
|
+
'custom_type': 'custom',
|
|
17
|
+
'static_bool': True,
|
|
18
|
+
'static_str': 'approved',
|
|
19
|
+
'static_int': 10,
|
|
20
|
+
'callable_func': lambda: 'now',
|
|
21
|
+
'exclude_str': 'exclude',
|
|
22
|
+
'exclude_tuple': ('exclude',)
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
|
|
22
|
-
class TestNormalizeSeederFields(
|
|
26
|
+
class TestNormalizeSeederFields(TestCase):
|
|
27
|
+
def test_excludes_fields(self) -> None:
|
|
28
|
+
fields = copy(FIELDS)
|
|
23
29
|
|
|
24
|
-
def test_normalizes_faker_tuple(self):
|
|
25
|
-
fields = {"faker_tuple": FIELDS["faker_tuple"]}
|
|
26
30
|
normalized = normalize_seeder_fields(fields)
|
|
27
|
-
self.assertEqual(normalized["faker_tuple"], ("faker", "name"))
|
|
28
31
|
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
assert 'exclude_str' not in normalized
|
|
33
|
+
assert 'exclude_tuple' not in normalized
|
|
34
|
+
assert 'static_str' in normalized
|
|
35
|
+
|
|
36
|
+
def test_normalizes_callable(self) -> None:
|
|
37
|
+
fields = {'callable_func': FIELDS['callable_func']}
|
|
38
|
+
|
|
39
|
+
normalized = normalize_seeder_fields(fields)
|
|
40
|
+
|
|
41
|
+
assert normalized['callable_func'][0] == 'callable'
|
|
42
|
+
assert callable(normalized['callable_func'][1])
|
|
43
|
+
|
|
44
|
+
def test_normalizes_faker_tuple(self) -> None:
|
|
45
|
+
fields = {'faker_tuple': FIELDS['faker_tuple']}
|
|
46
|
+
|
|
31
47
|
normalized = normalize_seeder_fields(fields)
|
|
32
|
-
self.assertNotIn("exclude_str", normalized)
|
|
33
|
-
self.assertNotIn("exclude_tuple", normalized)
|
|
34
|
-
self.assertIn("static_str", normalized)
|
|
35
48
|
|
|
36
|
-
|
|
49
|
+
assert normalized['faker_tuple'] == ('faker', 'name')
|
|
50
|
+
|
|
51
|
+
def test_normalizes_single_value_strings_to_tuples(self) -> None:
|
|
37
52
|
fields = {
|
|
38
53
|
k: FIELDS[k] for k in (
|
|
39
|
-
|
|
54
|
+
'llm_type', 'faker_type', 'static_type', 'callable_type', 'custom_type'
|
|
40
55
|
)
|
|
41
56
|
}
|
|
42
|
-
normalized = normalize_seeder_fields(fields)
|
|
43
|
-
for key, value in fields.items():
|
|
44
|
-
self.assertEqual(normalized[key], (value,))
|
|
45
57
|
|
|
46
|
-
def test_normalizes_callable(self):
|
|
47
|
-
fields = {"callable_func": FIELDS["callable_func"]}
|
|
48
58
|
normalized = normalize_seeder_fields(fields)
|
|
49
|
-
self.assertEqual(normalized["callable_func"][0], "callable")
|
|
50
|
-
self.assertTrue(callable(normalized["callable_func"][1]))
|
|
51
59
|
|
|
52
|
-
|
|
60
|
+
for key, value in fields.items():
|
|
61
|
+
assert normalized[key] == (value,)
|
|
62
|
+
|
|
63
|
+
def test_normalizes_static_values(self) -> None:
|
|
53
64
|
fields = {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
65
|
+
'static_bool': FIELDS['static_bool'],
|
|
66
|
+
'static_str': FIELDS['static_str'],
|
|
67
|
+
'static_int': FIELDS['static_int']
|
|
57
68
|
}
|
|
69
|
+
|
|
58
70
|
normalized = normalize_seeder_fields(fields)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
71
|
+
|
|
72
|
+
assert normalized['static_bool'] == ('static', True)
|
|
73
|
+
assert normalized['static_str'] == ('static', 'approved')
|
|
74
|
+
assert normalized['static_int'] == ('static', 10)
|
|
@@ -1,18 +1,22 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django.test import TestCase
|
|
3
4
|
|
|
5
|
+
from django_spire.contrib.seeding.field.static import StaticFieldSeeder
|
|
4
6
|
|
|
5
|
-
class TestStaticFieldSeeder(unittest.TestCase):
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
class TestStaticFieldSeeder(TestCase):
|
|
9
|
+
def test_seeds_static_fields(self) -> None:
|
|
8
10
|
fields = {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
'category': ('static', 'books'),
|
|
12
|
+
'in_stock': ('static', True)
|
|
11
13
|
}
|
|
12
14
|
seeder = StaticFieldSeeder(fields=fields)
|
|
15
|
+
|
|
13
16
|
result = seeder.seed(count=3)
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
assert len(result) == 3
|
|
19
|
+
|
|
16
20
|
for row in result:
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
assert row['category'] == 'books'
|
|
22
|
+
assert row['in_stock'] is True
|