django-spire 0.23.6__py3-none-any.whl → 0.23.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_spire/ai/admin.py +11 -11
- django_spire/ai/chat/apps.py +1 -0
- django_spire/ai/chat/templates/django_spire/ai/chat/widget/dialog_widget.html +1 -1
- django_spire/ai/chat/tests/factories.py +15 -0
- django_spire/ai/chat/tests/test_controller.py +45 -0
- django_spire/ai/chat/tests/test_models.py +301 -0
- django_spire/ai/chat/tests/test_prompts.py +48 -0
- django_spire/ai/chat/tests/test_responses.py +208 -0
- django_spire/ai/chat/tests/test_router/test_base_chat_router.py +66 -6
- django_spire/ai/chat/tests/test_router/test_chat_workflow.py +73 -3
- django_spire/ai/chat/tests/test_router/test_integration.py +86 -6
- django_spire/ai/chat/tests/test_router/test_intent_decoder.py +93 -1
- django_spire/ai/chat/tests/test_router/test_message_intel.py +60 -1
- django_spire/ai/chat/tests/test_router/test_spire_chat_router.py +110 -0
- django_spire/ai/chat/tests/test_urls/test_json_urls.py +202 -1
- django_spire/ai/context/tests/__init__.py +0 -0
- django_spire/ai/context/tests/test_context.py +188 -0
- django_spire/ai/decorators.py +7 -6
- django_spire/ai/prompt/tests/test_bots.py +100 -10
- django_spire/ai/prompt/tests/test_prompt_intel.py +83 -0
- django_spire/ai/prompt/tests/test_prompt_tuning.py +126 -0
- django_spire/ai/sms/decorators.py +8 -2
- django_spire/ai/sms/tests/test_sms.py +240 -16
- django_spire/ai/sms/tests/test_sms_intel.py +42 -0
- django_spire/ai/sms/tests/test_webhook.py +155 -7
- django_spire/ai/sms/views.py +23 -24
- django_spire/ai/tests/test_ai.py +131 -7
- django_spire/auth/apps.py +4 -2
- django_spire/auth/controller/controller.py +36 -23
- django_spire/auth/controller/exceptions.py +9 -0
- django_spire/auth/group/admin.py +1 -0
- django_spire/auth/group/apps.py +2 -0
- django_spire/auth/group/factories.py +17 -8
- django_spire/auth/group/forms.py +7 -0
- django_spire/auth/group/tests/test_factories.py +146 -0
- django_spire/auth/group/tests/test_forms.py +282 -0
- django_spire/auth/group/tests/test_models.py +192 -0
- django_spire/auth/group/tests/test_querysets.py +98 -0
- django_spire/auth/group/tests/test_utils.py +341 -0
- django_spire/auth/group/tests/test_views.py +377 -0
- django_spire/auth/group/urls/__init__.py +3 -1
- django_spire/auth/group/urls/form_urls.py +2 -0
- django_spire/auth/group/urls/json_urls.py +3 -0
- django_spire/auth/group/urls/page_urls.py +2 -0
- django_spire/auth/group/utils.py +6 -2
- django_spire/auth/group/views/form_views.py +6 -3
- django_spire/auth/group/views/json_views.py +6 -2
- django_spire/auth/mfa/admin.py +2 -0
- django_spire/auth/mfa/apps.py +2 -0
- django_spire/auth/mfa/forms.py +1 -0
- django_spire/auth/mfa/querysets.py +9 -2
- django_spire/auth/mfa/tests/test_models.py +233 -0
- django_spire/auth/mfa/tests/test_utils.py +106 -0
- django_spire/auth/mfa/urls/__init__.py +2 -0
- django_spire/auth/mfa/urls/page_urls.py +2 -0
- django_spire/auth/mfa/urls/redirect_urls.py +2 -0
- django_spire/auth/mfa/views/page_views.py +2 -1
- django_spire/auth/permissions/consts.py +2 -2
- django_spire/auth/permissions/decorators.py +8 -8
- django_spire/auth/permissions/permissions.py +28 -35
- django_spire/auth/permissions/tests/test_decorators.py +333 -0
- django_spire/auth/permissions/tests/test_permissions.py +337 -0
- django_spire/auth/permissions/tests/test_tools.py +305 -0
- django_spire/auth/permissions/tools.py +21 -15
- django_spire/auth/seeding/seed.py +3 -0
- django_spire/auth/seeding/seeder.py +2 -0
- django_spire/auth/tests/test_controller.py +323 -0
- django_spire/auth/tests/test_url_endpoints.py +9 -9
- django_spire/auth/tests/test_views.py +406 -0
- django_spire/auth/urls/admin_urls.py +2 -0
- django_spire/auth/urls/redirect_urls.py +2 -0
- django_spire/auth/user/apps.py +2 -0
- django_spire/auth/user/forms.py +9 -0
- django_spire/auth/user/models.py +1 -1
- django_spire/auth/user/services/services.py +1 -0
- django_spire/auth/user/tests/factories.py +14 -13
- django_spire/auth/user/tests/test_factories.py +166 -2
- django_spire/auth/user/tests/test_forms.py +573 -0
- django_spire/auth/user/tests/test_models.py +257 -0
- django_spire/auth/user/tests/test_services.py +200 -0
- django_spire/auth/user/tests/test_tools.py +153 -0
- django_spire/auth/user/tests/test_user_factories.py +139 -0
- django_spire/auth/user/tests/test_views.py +363 -0
- django_spire/auth/user/tools.py +7 -1
- django_spire/auth/user/urls/form_urls.py +3 -0
- django_spire/auth/user/urls/page_urls.py +3 -0
- django_spire/auth/user/views/form_views.py +19 -10
- django_spire/auth/user/views/page_views.py +8 -2
- django_spire/auth/views/redirect_views.py +14 -9
- django_spire/comment/admin.py +2 -0
- django_spire/comment/apps.py +2 -0
- django_spire/comment/templatetags/comment_tags.py +1 -0
- django_spire/comment/tests/test_forms.py +27 -0
- django_spire/comment/tests/test_models.py +215 -0
- django_spire/comment/tests/test_querysets.py +101 -0
- django_spire/comment/tests/test_utils.py +90 -0
- django_spire/comment/urls.py +2 -0
- django_spire/comment/utils.py +22 -13
- django_spire/comment/views.py +1 -1
- django_spire/conf.py +8 -6
- django_spire/consts.py +1 -1
- django_spire/contrib/breadcrumb/apps.py +2 -0
- django_spire/contrib/breadcrumb/breadcrumbs.py +18 -18
- django_spire/contrib/breadcrumb/tests/test_breadcrumbs.py +198 -0
- django_spire/contrib/constructor/__init__.py +3 -3
- django_spire/contrib/constructor/constructor.py +15 -15
- django_spire/contrib/constructor/django_model_constructor.py +5 -4
- django_spire/contrib/constructor/exceptions.py +5 -3
- django_spire/contrib/constructor/tests/__init__.py +0 -0
- django_spire/contrib/constructor/tests/test_constructor.py +193 -0
- django_spire/contrib/form/tests/__init__.py +0 -0
- django_spire/contrib/form/tests/test_forms.py +203 -0
- django_spire/contrib/generic_views/modal_views.py +2 -1
- django_spire/contrib/generic_views/portal_views.py +20 -19
- django_spire/contrib/generic_views/tests/__init__.py +0 -0
- django_spire/contrib/generic_views/tests/test_views.py +459 -0
- django_spire/contrib/help/apps.py +2 -0
- django_spire/contrib/help/templatetags/help.py +1 -0
- django_spire/contrib/help/tests/__init__.py +0 -0
- django_spire/contrib/help/tests/test_templatetags.py +100 -0
- django_spire/contrib/options/mixins.py +6 -5
- django_spire/contrib/options/tests/factories.py +5 -1
- django_spire/contrib/options/tests/test_options.py +234 -0
- django_spire/contrib/ordering/exceptions.py +7 -3
- django_spire/contrib/ordering/mixins.py +2 -0
- django_spire/contrib/ordering/querysets.py +3 -1
- django_spire/contrib/ordering/services/processor_service.py +8 -4
- django_spire/contrib/ordering/services/service.py +1 -2
- django_spire/contrib/ordering/tests/__init__.py +0 -0
- django_spire/contrib/ordering/tests/test_ordering.py +165 -0
- django_spire/contrib/ordering/validators.py +6 -6
- django_spire/contrib/pagination/templatetags/pagination_tags.py +12 -5
- django_spire/contrib/pagination/tests/__init__.py +0 -0
- django_spire/contrib/pagination/tests/test_pagination.py +179 -0
- django_spire/contrib/performance/decorators.py +16 -6
- django_spire/contrib/performance/tests/__init__.py +0 -0
- django_spire/contrib/performance/tests/test_performance.py +107 -0
- django_spire/contrib/progress/__init__.py +1 -3
- django_spire/contrib/progress/static/django_spire/js/contrib/progress/progress.js +38 -82
- django_spire/contrib/queryset/enums.py +3 -1
- django_spire/contrib/queryset/filter_tools.py +10 -5
- django_spire/contrib/queryset/mixins.py +16 -16
- django_spire/contrib/queryset/tests/__init__.py +0 -0
- django_spire/contrib/queryset/tests/test_queryset.py +137 -0
- django_spire/contrib/seeding/field/base.py +13 -7
- django_spire/contrib/seeding/field/callable.py +8 -1
- django_spire/contrib/seeding/field/cleaners.py +5 -5
- django_spire/contrib/seeding/field/custom.py +20 -10
- django_spire/contrib/seeding/field/django/seeder.py +8 -6
- django_spire/contrib/seeding/field/enums.py +7 -5
- django_spire/contrib/seeding/field/override.py +16 -6
- django_spire/contrib/seeding/field/static.py +9 -2
- django_spire/contrib/seeding/field/tests/test_base.py +18 -14
- django_spire/contrib/seeding/field/tests/test_callable.py +13 -9
- django_spire/contrib/seeding/field/tests/test_cleaners.py +51 -38
- django_spire/contrib/seeding/field/tests/test_static.py +13 -9
- django_spire/contrib/seeding/intelligence/bots/seeder_generator_bot.py +2 -0
- django_spire/contrib/seeding/intelligence/intel.py +5 -1
- django_spire/contrib/seeding/intelligence/prompts/factory.py +6 -1
- django_spire/contrib/seeding/intelligence/prompts/foreign_key_selection_prompt.py +6 -1
- django_spire/contrib/seeding/intelligence/prompts/generate_django_model_seeder_prompts.py +2 -0
- django_spire/contrib/seeding/intelligence/prompts/generic_relationship_selection_prompt.py +7 -1
- django_spire/contrib/seeding/intelligence/prompts/hierarchical_selection_prompt.py +6 -2
- django_spire/contrib/seeding/intelligence/prompts/model_field_choices_prompt.py +8 -2
- django_spire/contrib/seeding/intelligence/prompts/negation_prompt.py +2 -0
- django_spire/contrib/seeding/intelligence/prompts/objective_prompt.py +6 -1
- django_spire/contrib/seeding/management/commands/seeding.py +9 -3
- django_spire/contrib/seeding/management/example.py +2 -0
- django_spire/contrib/seeding/model/base.py +16 -7
- django_spire/contrib/seeding/model/config.py +31 -15
- django_spire/contrib/seeding/model/django/config.py +13 -13
- django_spire/contrib/seeding/model/django/seeder.py +4 -4
- django_spire/contrib/seeding/model/django/tests/test_seeder.py +34 -23
- django_spire/contrib/seeding/model/enums.py +2 -0
- django_spire/contrib/seeding/tests/test_config.py +71 -0
- django_spire/contrib/seeding/tests/test_custom.py +35 -0
- django_spire/contrib/seeding/tests/test_enums.py +40 -0
- django_spire/contrib/seeding/tests/test_intel.py +32 -0
- django_spire/contrib/seeding/tests/test_override.py +63 -0
- django_spire/contrib/service/__init__.py +2 -2
- django_spire/contrib/service/django_model_service.py +16 -15
- django_spire/contrib/service/exceptions.py +5 -3
- django_spire/contrib/service/tests/__init__.py +0 -0
- django_spire/contrib/service/tests/test_service.py +153 -0
- django_spire/contrib/session/apps.py +2 -0
- django_spire/contrib/session/controller.py +48 -42
- django_spire/contrib/session/templatetags/session_tags.py +11 -2
- django_spire/contrib/session/tests/test_session_controller.py +117 -53
- django_spire/contrib/tests/__init__.py +0 -0
- django_spire/contrib/tests/test_utils.py +37 -0
- django_spire/contrib/utils.py +4 -1
- django_spire/core/apps.py +2 -0
- django_spire/core/converters/tests/test_to_data.py +353 -0
- django_spire/core/converters/tests/test_to_enums.py +61 -41
- django_spire/core/converters/tests/test_to_pydantic.py +138 -109
- django_spire/core/converters/to_data.py +29 -10
- django_spire/core/converters/to_enums.py +4 -2
- django_spire/core/converters/to_pydantic.py +22 -22
- django_spire/core/decorators.py +19 -6
- django_spire/core/forms/widgets.py +4 -0
- django_spire/core/maps.py +3 -1
- django_spire/core/middleware/maintenance.py +3 -3
- django_spire/core/middleware.py +8 -6
- django_spire/core/redirect/__init__.py +5 -0
- django_spire/core/redirect/generic_redirect.py +1 -2
- django_spire/core/redirect/tests/__init__.py +0 -0
- django_spire/core/redirect/tests/test_generic_redirect.py +34 -0
- django_spire/core/{tests/tests_redirect.py → redirect/tests/test_safe_redirect.py} +55 -81
- django_spire/core/shortcuts.py +3 -3
- django_spire/core/static/django_spire/css/app-layout.css +1 -1
- django_spire/core/static/django_spire/css/app-navigation.css +3 -3
- django_spire/core/static/django_spire/css/bootstrap-override.css +4 -0
- django_spire/core/tag/admin.py +12 -0
- django_spire/core/tag/intelligence/tag_set_bot.py +2 -0
- django_spire/core/tag/mixins.py +2 -0
- django_spire/core/tag/models.py +2 -0
- django_spire/core/tag/querysets.py +2 -0
- django_spire/core/tag/service/tag_service.py +6 -3
- django_spire/core/tag/tests/test_intelligence.py +9 -9
- django_spire/core/tag/tests/test_tags.py +44 -54
- django_spire/core/tag/tests/test_tools.py +191 -0
- django_spire/core/tag/tools.py +3 -0
- django_spire/core/templates/django_spire/card/card.html +5 -2
- django_spire/core/templates/django_spire/card/title_card.html +9 -4
- django_spire/core/templates/django_spire/infinite_scroll/base.html +1 -0
- django_spire/core/templates/django_spire/navigation/side_navigation.html +19 -24
- django_spire/core/templates/django_spire/page/full_page.html +46 -16
- django_spire/core/templates/django_spire/table/base.html +4 -2
- django_spire/core/templatetags/json.py +6 -2
- django_spire/core/templatetags/message.py +13 -8
- django_spire/core/templatetags/string_formating.py +8 -5
- django_spire/core/templatetags/tests/__init__.py +0 -0
- django_spire/core/templatetags/tests/test_templatetags.py +427 -0
- django_spire/core/templatetags/variable_types.py +17 -9
- django_spire/core/tests/test_cases.py +1 -1
- django_spire/core/tests/test_conf.py +43 -0
- django_spire/core/tests/test_consts.py +28 -0
- django_spire/core/tests/test_context_processors.py +93 -0
- django_spire/core/tests/test_decorators.py +95 -0
- django_spire/core/tests/test_django_spire_utils.py +56 -0
- django_spire/core/tests/test_exceptions.py +37 -0
- django_spire/core/tests/test_models.py +54 -0
- django_spire/core/tests/test_settings.py +45 -0
- django_spire/core/tests/test_shortcuts.py +74 -0
- django_spire/core/tests/test_urls.py +16 -0
- django_spire/core/tests/test_utils.py +58 -0
- django_spire/core/urls.py +4 -1
- django_spire/core/utils.py +12 -8
- django_spire/exceptions.py +16 -1
- django_spire/file/admin.py +4 -2
- django_spire/file/apps.py +8 -10
- django_spire/file/fields.py +7 -7
- django_spire/file/forms.py +1 -1
- django_spire/file/interfaces.py +15 -15
- django_spire/file/mixins.py +1 -4
- django_spire/file/models.py +3 -5
- django_spire/file/tests/factories.py +59 -0
- django_spire/file/tests/test_admin.py +69 -0
- django_spire/file/tests/test_apps.py +24 -0
- django_spire/file/tests/test_fields.py +114 -0
- django_spire/file/tests/test_forms.py +20 -0
- django_spire/file/tests/test_interfaces.py +183 -0
- django_spire/file/tests/test_models.py +82 -0
- django_spire/file/tests/test_querysets.py +102 -0
- django_spire/file/tests/test_utils.py +32 -0
- django_spire/file/tests/test_views.py +145 -0
- django_spire/file/tests/test_widgets.py +82 -0
- django_spire/file/tools.py +8 -2
- django_spire/file/views.py +7 -3
- django_spire/file/widgets.py +12 -12
- django_spire/help_desk/admin.py +15 -0
- django_spire/help_desk/apps.py +2 -0
- django_spire/help_desk/auth/controller.py +2 -0
- django_spire/help_desk/choices.py +2 -0
- django_spire/help_desk/enums.py +2 -0
- django_spire/help_desk/exceptions.py +31 -3
- django_spire/help_desk/forms.py +2 -0
- django_spire/help_desk/models.py +2 -0
- django_spire/help_desk/querysets.py +4 -1
- django_spire/help_desk/services/notification_service.py +26 -27
- django_spire/help_desk/services/service.py +2 -3
- django_spire/help_desk/tests/factories.py +8 -3
- django_spire/help_desk/tests/test_admin.py +41 -0
- django_spire/help_desk/tests/test_apps.py +41 -0
- django_spire/help_desk/tests/test_choices.py +50 -0
- django_spire/help_desk/tests/test_controller.py +87 -0
- django_spire/help_desk/tests/test_enums.py +18 -0
- django_spire/help_desk/tests/test_exceptions.py +37 -0
- django_spire/help_desk/tests/test_forms.py +89 -0
- django_spire/help_desk/tests/test_models.py +59 -0
- django_spire/help_desk/tests/test_querysets.py +38 -0
- django_spire/help_desk/tests/test_services/test_notification_service.py +15 -8
- django_spire/help_desk/tests/test_services/test_service.py +92 -0
- django_spire/help_desk/tests/test_urls/test_form_urls.py +6 -6
- django_spire/help_desk/tests/test_urls/test_page_urls.py +8 -9
- django_spire/help_desk/tests/test_views/test_form_views.py +46 -19
- django_spire/help_desk/tests/test_views/test_page_views.py +32 -9
- django_spire/help_desk/urls/__init__.py +4 -1
- django_spire/help_desk/urls/form_urls.py +3 -0
- django_spire/help_desk/urls/page_urls.py +3 -0
- django_spire/help_desk/views/form_views.py +13 -5
- django_spire/help_desk/views/page_views.py +11 -3
- django_spire/history/activity/admin.py +2 -0
- django_spire/history/activity/apps.py +3 -1
- django_spire/history/activity/mixins.py +13 -7
- django_spire/history/activity/models.py +6 -5
- django_spire/history/activity/querysets.py +2 -0
- django_spire/history/activity/tests/__init__.py +0 -0
- django_spire/history/activity/tests/test_activity.py +176 -0
- django_spire/history/admin.py +9 -2
- django_spire/history/choices.py +3 -0
- django_spire/history/models.py +5 -5
- django_spire/history/tests/test_admin.py +93 -0
- django_spire/history/tests/test_history.py +101 -0
- django_spire/history/tests/test_mixins.py +84 -0
- django_spire/history/viewed/admin.py +3 -1
- django_spire/history/viewed/apps.py +3 -1
- django_spire/history/viewed/models.py +2 -0
- django_spire/history/viewed/tests/__init__.py +0 -0
- django_spire/history/viewed/tests/test_viewed.py +46 -0
- django_spire/knowledge/auth/tests/__init__.py +0 -0
- django_spire/knowledge/auth/tests/test_controller.py +116 -0
- django_spire/knowledge/collection/admin.py +5 -1
- django_spire/knowledge/collection/models.py +3 -1
- django_spire/knowledge/collection/seeding/seed.py +1 -0
- django_spire/knowledge/collection/services/factory_service.py +10 -11
- django_spire/knowledge/collection/services/ordering_service.py +1 -2
- django_spire/knowledge/collection/services/service.py +5 -10
- django_spire/knowledge/collection/services/tag_service.py +5 -2
- django_spire/knowledge/collection/tests/factories.py +28 -1
- django_spire/knowledge/collection/tests/test_models.py +48 -0
- django_spire/knowledge/collection/tests/test_querysets.py +93 -0
- django_spire/knowledge/collection/tests/test_services/test_factory_service.py +100 -0
- django_spire/knowledge/collection/tests/test_services/test_services.py +160 -0
- django_spire/knowledge/collection/tests/test_urls/test_form_urls.py +21 -3
- django_spire/knowledge/collection/tests/test_urls/test_json_urls.py +39 -1
- django_spire/knowledge/collection/tests/test_urls/test_page_urls.py +12 -4
- django_spire/knowledge/collection/urls/__init__.py +3 -0
- django_spire/knowledge/collection/urls/form_urls.py +2 -0
- django_spire/knowledge/collection/urls/json_urls.py +2 -0
- django_spire/knowledge/collection/urls/page_urls.py +2 -0
- django_spire/knowledge/collection/views/form_views.py +4 -4
- django_spire/knowledge/collection/views/json_views.py +5 -1
- django_spire/knowledge/collection/views/page_views.py +5 -2
- django_spire/knowledge/entry/admin.py +7 -1
- django_spire/knowledge/entry/forms.py +2 -0
- django_spire/knowledge/entry/models.py +2 -0
- django_spire/knowledge/entry/seeding/seed.py +3 -0
- django_spire/knowledge/entry/services/automation_service.py +5 -4
- django_spire/knowledge/entry/services/factory_service.py +7 -5
- django_spire/knowledge/entry/services/service.py +4 -7
- django_spire/knowledge/entry/services/tag_service.py +0 -1
- django_spire/knowledge/entry/services/tool_service.py +1 -0
- django_spire/knowledge/entry/services/transformation_services.py +1 -5
- django_spire/knowledge/entry/tests/factories.py +1 -2
- django_spire/knowledge/entry/tests/test_factory_service.py +20 -0
- django_spire/knowledge/entry/tests/test_models.py +41 -0
- django_spire/knowledge/entry/tests/test_querysets.py +71 -0
- django_spire/knowledge/entry/tests/test_services.py +94 -0
- django_spire/knowledge/entry/tests/test_urls/test_form_urls.py +9 -14
- django_spire/knowledge/entry/tests/test_urls/test_json_urls.py +48 -5
- django_spire/knowledge/entry/tests/test_urls/test_page_urls.py +6 -8
- django_spire/knowledge/entry/tests/test_urls/test_template_urls.py +40 -0
- django_spire/knowledge/entry/urls/form_urls.py +2 -0
- django_spire/knowledge/entry/urls/json_urls.py +2 -0
- django_spire/knowledge/entry/urls/page_urls.py +2 -0
- django_spire/knowledge/entry/urls/template_urls.py +2 -0
- django_spire/knowledge/entry/version/block/choices.py +2 -0
- django_spire/knowledge/entry/version/block/data/data.py +1 -0
- django_spire/knowledge/entry/version/block/data/list/data.py +8 -13
- django_spire/knowledge/entry/version/block/data/list/maps.py +3 -0
- django_spire/knowledge/entry/version/block/data/list/meta.py +1 -2
- django_spire/knowledge/entry/version/block/data/list/tests/__init__.py +0 -0
- django_spire/knowledge/entry/version/block/data/list/tests/test_maps.py +32 -0
- django_spire/knowledge/entry/version/block/data/list/tests/test_meta.py +58 -0
- django_spire/knowledge/entry/version/block/data/maps.py +3 -6
- django_spire/knowledge/entry/version/block/models.py +7 -5
- django_spire/knowledge/entry/version/block/seeding/constants.py +5 -4
- django_spire/knowledge/entry/version/block/services/service.py +2 -3
- django_spire/knowledge/entry/version/block/tests/factories.py +4 -10
- django_spire/knowledge/entry/version/block/tests/test_choices.py +56 -0
- django_spire/knowledge/entry/version/block/tests/test_data.py +90 -0
- django_spire/knowledge/entry/version/block/tests/test_maps.py +37 -0
- django_spire/knowledge/entry/version/block/tests/test_models.py +55 -0
- django_spire/knowledge/entry/version/block/tests/test_querysets.py +35 -0
- django_spire/knowledge/entry/version/block/tests/test_services.py +65 -0
- django_spire/knowledge/entry/version/choices.py +2 -0
- django_spire/knowledge/entry/version/converters/converter.py +1 -1
- django_spire/knowledge/entry/version/converters/docx_converter.py +4 -7
- django_spire/knowledge/entry/version/converters/markdown_converter.py +20 -20
- django_spire/knowledge/entry/version/maps.py +4 -5
- django_spire/knowledge/entry/version/querysets.py +1 -1
- django_spire/knowledge/entry/version/seeding/seeder.py +1 -2
- django_spire/knowledge/entry/version/services/processor_service.py +5 -4
- django_spire/knowledge/entry/version/services/service.py +1 -2
- django_spire/knowledge/entry/version/tests/factories.py +2 -2
- django_spire/knowledge/entry/version/tests/test_choices.py +18 -0
- django_spire/knowledge/entry/version/tests/test_converters/test_docx_converter.py +56 -8
- django_spire/knowledge/entry/version/tests/test_converters/test_markdown_converter.py +78 -0
- django_spire/knowledge/entry/version/tests/test_maps.py +58 -0
- django_spire/knowledge/entry/version/tests/test_models.py +23 -0
- django_spire/knowledge/entry/version/tests/test_querysets.py +26 -0
- django_spire/knowledge/entry/version/tests/test_services.py +62 -0
- django_spire/knowledge/entry/version/tests/test_urls/test_json_urls.py +27 -8
- django_spire/knowledge/entry/version/tests/test_urls/test_page_urls.py +15 -8
- django_spire/knowledge/entry/version/tests/test_urls/test_redirect_urls.py +38 -0
- django_spire/knowledge/entry/version/urls/__init__.py +3 -0
- django_spire/knowledge/entry/version/urls/json_urls.py +2 -1
- django_spire/knowledge/entry/version/urls/page_urls.py +2 -0
- django_spire/knowledge/entry/version/urls/redirect_urls.py +2 -0
- django_spire/knowledge/entry/version/views/json_views.py +5 -1
- django_spire/knowledge/entry/version/views/page_views.py +10 -3
- django_spire/knowledge/entry/version/views/redirect_views.py +5 -1
- django_spire/knowledge/entry/views/form_views.py +16 -8
- django_spire/knowledge/entry/views/json_views.py +3 -1
- django_spire/knowledge/entry/views/page_views.py +8 -2
- django_spire/knowledge/entry/views/template_views.py +7 -1
- django_spire/knowledge/exceptions.py +2 -1
- django_spire/knowledge/intelligence/intel/answer_intel.py +2 -1
- django_spire/knowledge/intelligence/intel/entry_intel.py +0 -1
- django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +4 -5
- django_spire/knowledge/models.py +1 -2
- django_spire/knowledge/tests/__init__.py +0 -0
- django_spire/knowledge/tests/test_templatetags.py +40 -0
- django_spire/knowledge/tests/test_urls/__init__.py +0 -0
- django_spire/knowledge/tests/test_urls/test_page_urls.py +24 -0
- django_spire/knowledge/urls/__init__.py +2 -0
- django_spire/knowledge/urls/page_urls.py +2 -0
- django_spire/knowledge/views/page_views.py +8 -3
- django_spire/notification/admin.py +3 -1
- django_spire/notification/app/admin.py +2 -0
- django_spire/notification/app/apps.py +3 -1
- django_spire/notification/app/exceptions.py +9 -2
- django_spire/notification/app/models.py +8 -4
- django_spire/notification/app/processor.py +22 -26
- django_spire/notification/app/querysets.py +2 -0
- django_spire/notification/app/tests/__init__.py +0 -0
- django_spire/notification/app/tests/factories.py +34 -0
- django_spire/notification/app/tests/test_apps.py +24 -0
- django_spire/notification/app/tests/test_models.py +72 -0
- django_spire/notification/app/tests/test_processor.py +111 -0
- django_spire/notification/app/tests/test_querysets.py +90 -0
- django_spire/notification/app/tests/test_views/__init__.py +0 -0
- django_spire/notification/app/tests/test_views/test_json_views.py +48 -0
- django_spire/notification/app/tests/test_views/test_page_views.py +19 -0
- django_spire/notification/app/urls/__init__.py +3 -1
- django_spire/notification/app/urls/json_urls.py +6 -4
- django_spire/notification/app/urls/page_urls.py +4 -3
- django_spire/notification/app/urls/template_urls.py +4 -2
- django_spire/notification/apps.py +4 -1
- django_spire/notification/email/admin.py +5 -1
- django_spire/notification/email/apps.py +3 -1
- django_spire/notification/email/exceptions.py +4 -2
- django_spire/notification/email/helper.py +5 -3
- django_spire/notification/email/models.py +4 -0
- django_spire/notification/email/processor.py +19 -15
- django_spire/notification/email/querysets.py +3 -0
- django_spire/notification/email/tests/__init__.py +0 -0
- django_spire/notification/email/tests/factories.py +35 -0
- django_spire/notification/email/tests/test_apps.py +24 -0
- django_spire/notification/email/tests/test_models.py +52 -0
- django_spire/notification/email/tests/test_processor.py +92 -0
- django_spire/notification/email/tests/test_querysets.py +43 -0
- django_spire/notification/exceptions.py +17 -2
- django_spire/notification/managers.py +7 -1
- django_spire/notification/maps.py +4 -1
- django_spire/notification/mixins.py +2 -0
- django_spire/notification/models.py +3 -1
- django_spire/notification/processors/notification.py +12 -5
- django_spire/notification/processors/processor.py +2 -0
- django_spire/notification/processors/tests/__init__.py +0 -0
- django_spire/notification/processors/tests/test_notification.py +106 -0
- django_spire/notification/push/admin.py +10 -1
- django_spire/notification/push/apps.py +3 -1
- django_spire/notification/push/models.py +2 -3
- django_spire/notification/push/tests/__init__.py +0 -0
- django_spire/notification/push/tests/test_apps.py +24 -0
- django_spire/notification/push/tests/test_models.py +28 -0
- django_spire/notification/querysets.py +7 -1
- django_spire/notification/sms/admin.py +2 -0
- django_spire/notification/sms/apps.py +4 -1
- django_spire/notification/sms/automations.py +2 -0
- django_spire/notification/sms/choices.py +2 -0
- django_spire/notification/sms/exceptions.py +19 -5
- django_spire/notification/sms/helper.py +33 -23
- django_spire/notification/sms/models.py +5 -1
- django_spire/notification/sms/processor.py +20 -20
- django_spire/notification/sms/querysets.py +2 -0
- django_spire/notification/sms/tests/factories.py +33 -0
- django_spire/notification/sms/tests/test_apps.py +24 -0
- django_spire/notification/sms/tests/test_automation.py +38 -0
- django_spire/notification/sms/tests/test_choices.py +15 -0
- django_spire/notification/sms/tests/test_consts.py +17 -0
- django_spire/notification/sms/tests/test_exceptions.py +27 -0
- django_spire/notification/sms/tests/test_helper.py +50 -0
- django_spire/notification/sms/tests/test_models.py +81 -0
- django_spire/notification/sms/tests/test_processor.py +107 -0
- django_spire/notification/sms/tests/test_tools.py +25 -11
- django_spire/notification/sms/tools.py +16 -5
- django_spire/notification/sms/urls/__init__.py +3 -1
- django_spire/notification/sms/urls/media_urls.py +2 -0
- django_spire/notification/sms/views/media_views.py +14 -4
- django_spire/notification/tests/__init__.py +0 -0
- django_spire/notification/tests/factories.py +26 -0
- django_spire/notification/tests/test_admin.py +55 -0
- django_spire/notification/tests/test_apps.py +30 -0
- django_spire/notification/tests/test_automation.py +18 -0
- django_spire/notification/tests/test_choices.py +59 -0
- django_spire/notification/tests/test_exceptions.py +58 -0
- django_spire/notification/tests/test_managers.py +100 -0
- django_spire/notification/tests/test_maps.py +31 -0
- django_spire/notification/tests/test_models.py +76 -0
- django_spire/notification/tests/test_querysets.py +184 -0
- django_spire/notification/tests/test_utils.py +23 -0
- django_spire/notification/urls.py +3 -1
- django_spire/notification/utils.py +3 -1
- django_spire/settings.py +3 -0
- django_spire/theme/tests/test_context_processor.py +15 -13
- django_spire/theme/tests/test_enums.py +2 -2
- django_spire/theme/tests/test_filesystem.py +2 -5
- django_spire/theme/tests/test_integration.py +12 -12
- django_spire/theme/tests/test_model.py +40 -38
- django_spire/theme/tests/test_views/test_json_views.py +33 -33
- django_spire/theme/urls/json_urls.py +3 -0
- django_spire/theme/urls/page_urls.py +3 -0
- django_spire/urls.py +19 -15
- django_spire/utils.py +13 -4
- {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/METADATA +1 -1
- {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/RECORD +532 -361
- django_spire/contrib/options/tests/test_unit.py +0 -148
- django_spire/contrib/progress/views.py +0 -64
- django_spire/contrib/seeding/tests/test_seeding.py +0 -25
- django_spire/core/tests/test_templatetags.py +0 -117
- django_spire/core/tests/tests_shortcuts.py +0 -73
- django_spire/history/activity/tests.py +0 -3
- django_spire/history/activity/views.py +0 -3
- django_spire/knowledge/collection/tests/test_services/test_transformation_service.py +0 -71
- django_spire/notification/app/tests.py +0 -3
- {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/WHEEL +0 -0
- {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/licenses/LICENSE.md +0 -0
- {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/top_level.txt +0 -0
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Iterator
|
|
4
|
+
|
|
1
5
|
from dandy import BaseIntel
|
|
2
6
|
|
|
3
7
|
|
|
4
8
|
class SeedingIntel(BaseIntel):
|
|
5
9
|
items: list[dict]
|
|
6
10
|
|
|
7
|
-
def __iter__(self):
|
|
11
|
+
def __iter__(self) -> Iterator[dict]:
|
|
8
12
|
return iter(self.items)
|
|
9
13
|
|
|
10
14
|
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
2
4
|
|
|
3
5
|
from dandy import Prompt
|
|
4
6
|
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from django.db.models.base import Model
|
|
9
|
+
|
|
5
10
|
|
|
6
11
|
class SeedingModelClassPromptFactory:
|
|
7
12
|
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
2
4
|
|
|
3
5
|
from dandy import Prompt
|
|
4
6
|
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from django.db.models import Model
|
|
9
|
+
|
|
5
10
|
|
|
6
11
|
def foreign_key_selection_prompt(
|
|
7
12
|
model_class: type[Model],
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
1
5
|
from django.contrib.contenttypes.models import ContentType
|
|
2
|
-
from django.db.models import Model
|
|
3
6
|
|
|
4
7
|
from dandy import Prompt
|
|
5
8
|
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from django.db.models import Model
|
|
11
|
+
|
|
6
12
|
|
|
7
13
|
def generic_relationship_selection_prompt(
|
|
8
14
|
model_class: type[Model],
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from random import shuffle
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
4
5
|
|
|
5
6
|
from dandy import Prompt
|
|
6
7
|
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from django.db.models import Model
|
|
10
|
+
|
|
7
11
|
|
|
8
12
|
def hierarchical_selection_prompt(
|
|
9
13
|
model_class: type[Model],
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from dandy import Prompt
|
|
2
|
-
|
|
3
|
-
from
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from django.db.models.fields import Field
|
|
9
|
+
from django.db.models.enums import TextChoices
|
|
4
10
|
|
|
5
11
|
|
|
6
12
|
def model_field_choices_prompt(
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
2
4
|
|
|
3
5
|
from dandy import Prompt
|
|
4
6
|
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from django.db.models import Model
|
|
9
|
+
|
|
5
10
|
|
|
6
11
|
def objective_prompt(
|
|
7
12
|
model_class: type[Model],
|
|
@@ -1,12 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from pathlib import Path
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
2
5
|
|
|
3
6
|
from django.core.management.base import BaseCommand, CommandError
|
|
4
7
|
|
|
5
8
|
from dandy.conf import settings
|
|
6
|
-
from dandy.recorder import recorder_to_html_file
|
|
7
9
|
|
|
8
10
|
from django_spire.contrib.seeding.intelligence.bots.seeder_generator_bot import SeederGeneratorBot
|
|
9
11
|
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from argparse import ArgumentParser
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
10
16
|
|
|
11
17
|
_SEEDING_OUTPUT_PATH = Path(settings.BASE_PATH, '.seeding_generator_output')
|
|
12
18
|
|
|
@@ -14,7 +20,7 @@ _SEEDING_OUTPUT_PATH = Path(settings.BASE_PATH, '.seeding_generator_output')
|
|
|
14
20
|
class Command(BaseCommand):
|
|
15
21
|
help = 'Generate a Seeder'
|
|
16
22
|
|
|
17
|
-
def add_arguments(self, parser):
|
|
23
|
+
def add_arguments(self, parser: ArgumentParser) -> None:
|
|
18
24
|
parser.add_argument(
|
|
19
25
|
'model_import',
|
|
20
26
|
type=str,
|
|
@@ -29,7 +35,7 @@ class Command(BaseCommand):
|
|
|
29
35
|
)
|
|
30
36
|
|
|
31
37
|
# @recorder_to_html_file('seeding_generator')
|
|
32
|
-
def handle(self, *args, **kwargs):
|
|
38
|
+
def handle(self, *args: Any, **kwargs: Any) -> None:
|
|
33
39
|
if not kwargs['model_import'] or not kwargs['model_description']:
|
|
34
40
|
message = 'You must provide a model import path and a model description'
|
|
35
41
|
raise CommandError(message)
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
2
5
|
|
|
3
6
|
from dandy.recorder import recorder_to_html_file
|
|
4
7
|
from dandy import SqliteCache, generate_cache_key
|
|
@@ -6,12 +9,15 @@ from dandy import SqliteCache, generate_cache_key
|
|
|
6
9
|
from django_spire.contrib.seeding.field.override import FieldOverride
|
|
7
10
|
from django_spire.contrib.seeding.model.config import FieldsConfig
|
|
8
11
|
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
9
15
|
|
|
10
16
|
class classproperty:
|
|
11
|
-
def __init__(self, fget):
|
|
17
|
+
def __init__(self, fget: Any) -> None:
|
|
12
18
|
self.fget = fget
|
|
13
19
|
|
|
14
|
-
def __get__(self, instance, owner):
|
|
20
|
+
def __get__(self, instance: Any, owner: Any) -> Any:
|
|
15
21
|
return self.fget(owner)
|
|
16
22
|
|
|
17
23
|
|
|
@@ -33,30 +39,33 @@ class BaseModelSeeder(ABC):
|
|
|
33
39
|
_field_config: FieldsConfig = None
|
|
34
40
|
|
|
35
41
|
@classproperty
|
|
36
|
-
def override(cls):
|
|
42
|
+
def override(cls) -> FieldOverride:
|
|
37
43
|
return cls.override_class(seeder_class=cls)
|
|
38
44
|
|
|
39
45
|
@classmethod
|
|
40
46
|
def get_field_config(cls) -> FieldsConfig:
|
|
41
47
|
if cls._field_config is None:
|
|
42
48
|
if cls.model_class is None:
|
|
43
|
-
|
|
49
|
+
message = 'model_class must be defined before using seeder.'
|
|
50
|
+
raise ValueError(message)
|
|
44
51
|
|
|
45
52
|
raw_fields = cls.__dict__.get('fields', {})
|
|
53
|
+
|
|
46
54
|
cls._field_config = cls.field_config_class(
|
|
47
55
|
raw_fields=raw_fields,
|
|
48
56
|
field_names=cls.field_names(),
|
|
49
57
|
default_to=cls.default_to,
|
|
50
58
|
model_class=cls.model_class,
|
|
51
59
|
)
|
|
60
|
+
|
|
52
61
|
return cls._field_config
|
|
53
62
|
|
|
54
63
|
@classmethod
|
|
55
|
-
def resolved_fields(cls):
|
|
64
|
+
def resolved_fields(cls) -> dict:
|
|
56
65
|
return cls.get_field_config().fields
|
|
57
66
|
|
|
58
67
|
@classmethod
|
|
59
|
-
def clear_cache(cls):
|
|
68
|
+
def clear_cache(cls) -> None:
|
|
60
69
|
SqliteCache.clear(cache_name=cls.cache_name)
|
|
61
70
|
|
|
62
71
|
@classmethod
|
|
@@ -68,7 +77,7 @@ class BaseModelSeeder(ABC):
|
|
|
68
77
|
@recorder_to_html_file('model_seeder')
|
|
69
78
|
def seed_data(
|
|
70
79
|
cls,
|
|
71
|
-
count=1,
|
|
80
|
+
count: int = 1,
|
|
72
81
|
fields: dict | None = None,
|
|
73
82
|
) -> list[dict]:
|
|
74
83
|
field_config = (
|
|
@@ -1,15 +1,22 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
1
5
|
from django_spire.contrib.seeding.field.cleaners import normalize_seeder_fields
|
|
2
6
|
from django_spire.contrib.seeding.model.enums import ModelSeederDefaultsEnum
|
|
3
7
|
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from typing import Any, Self
|
|
10
|
+
|
|
4
11
|
|
|
5
12
|
class FieldsConfig:
|
|
6
13
|
def __init__(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
):
|
|
14
|
+
self,
|
|
15
|
+
raw_fields: dict[str, Any],
|
|
16
|
+
field_names: list[str],
|
|
17
|
+
default_to: str,
|
|
18
|
+
model_class: Any
|
|
19
|
+
) -> None:
|
|
13
20
|
self.raw_fields = raw_fields or {}
|
|
14
21
|
self.default_to = default_to
|
|
15
22
|
self.field_names = set(field_names)
|
|
@@ -17,7 +24,7 @@ class FieldsConfig:
|
|
|
17
24
|
|
|
18
25
|
self._excluded = {
|
|
19
26
|
k for k, v in self.raw_fields.items()
|
|
20
|
-
if v
|
|
27
|
+
if v in ("exclude", ("exclude",))
|
|
21
28
|
}
|
|
22
29
|
|
|
23
30
|
self.fields = normalize_seeder_fields({
|
|
@@ -26,14 +33,18 @@ class FieldsConfig:
|
|
|
26
33
|
|
|
27
34
|
self._validate()
|
|
28
35
|
self._assign_defaults()
|
|
29
|
-
self._order_fields() # Fields need to be in order for caching hash
|
|
30
36
|
|
|
31
|
-
|
|
37
|
+
# Fields need to be in order for caching hash
|
|
38
|
+
self._order_fields()
|
|
39
|
+
|
|
40
|
+
def _validate(self) -> None:
|
|
32
41
|
unknown = set(self.fields.keys()) - self.field_names
|
|
42
|
+
|
|
33
43
|
if unknown:
|
|
34
|
-
|
|
44
|
+
message = f"Invalid field name(s): {', '.join(unknown)}"
|
|
45
|
+
raise ValueError(message)
|
|
35
46
|
|
|
36
|
-
def _assign_defaults(self):
|
|
47
|
+
def _assign_defaults(self) -> None:
|
|
37
48
|
if self.default_to == ModelSeederDefaultsEnum.INCLUDED:
|
|
38
49
|
return
|
|
39
50
|
|
|
@@ -43,7 +54,7 @@ class FieldsConfig:
|
|
|
43
54
|
if name not in self.fields and name not in self._excluded:
|
|
44
55
|
self.fields[name] = (method,)
|
|
45
56
|
|
|
46
|
-
def override(self, overrides: dict):
|
|
57
|
+
def override(self, overrides: dict) -> Self:
|
|
47
58
|
merged = {**self.fields, **normalize_seeder_fields(overrides)}
|
|
48
59
|
new_raw = {
|
|
49
60
|
**{k: v for k, v in self.raw_fields.items() if k in self._excluded},
|
|
@@ -56,9 +67,14 @@ class FieldsConfig:
|
|
|
56
67
|
model_class=self.model_class
|
|
57
68
|
)
|
|
58
69
|
|
|
59
|
-
def _order_fields(self):
|
|
60
|
-
self.fields =
|
|
70
|
+
def _order_fields(self) -> None:
|
|
71
|
+
self.fields = dict(
|
|
72
|
+
sorted(
|
|
73
|
+
self.fields.items(),
|
|
74
|
+
key=lambda x: x[0]
|
|
75
|
+
)
|
|
76
|
+
)
|
|
61
77
|
|
|
62
78
|
@property
|
|
63
|
-
def excluded(self):
|
|
79
|
+
def excluded(self) -> set:
|
|
64
80
|
return self._excluded
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from django_spire.contrib.seeding.model.config import FieldsConfig
|
|
2
4
|
|
|
3
5
|
|
|
4
6
|
class DjangoModelFieldsConfig(FieldsConfig):
|
|
5
|
-
|
|
6
|
-
def _validate(self):
|
|
7
|
+
def _validate(self) -> None:
|
|
7
8
|
valid_field_names = set(self.field_names)
|
|
8
9
|
|
|
9
10
|
valid_field_names.update(f.attname for f in self.model_class._meta.fields)
|
|
@@ -11,18 +12,18 @@ class DjangoModelFieldsConfig(FieldsConfig):
|
|
|
11
12
|
unknown = set(self.fields.keys()) - valid_field_names
|
|
12
13
|
|
|
13
14
|
if unknown:
|
|
14
|
-
|
|
15
|
+
message = f"Invalid field name(s): {', '.join(unknown)}"
|
|
16
|
+
raise ValueError(message)
|
|
15
17
|
|
|
16
18
|
# fk_fields = {
|
|
17
19
|
# f.name: f.attname # f.attname = "user_id"
|
|
18
20
|
# for f in self.model_class._meta.fields
|
|
19
21
|
# if isinstance(f, ForeignKey)
|
|
20
22
|
# }
|
|
21
|
-
|
|
23
|
+
|
|
22
24
|
# for name, value in self.fields.items():
|
|
23
25
|
# if name in fk_fields:
|
|
24
26
|
# valid_types = [int, None]
|
|
25
|
-
#
|
|
26
27
|
|
|
27
28
|
# def map_foreign_keys(self):
|
|
28
29
|
# fk_fields = {
|
|
@@ -30,19 +31,20 @@ class DjangoModelFieldsConfig(FieldsConfig):
|
|
|
30
31
|
# for f in self.model_class._meta.fields
|
|
31
32
|
# if isinstance(f, ForeignKey)
|
|
32
33
|
# }
|
|
33
|
-
|
|
34
|
+
|
|
34
35
|
# # If asset_id and asset are passed, should I raise an error?
|
|
35
|
-
|
|
36
|
+
|
|
36
37
|
# {
|
|
37
38
|
# 'asset' : ('custom', 'in_order', {'values': [a.id for a in assets]}),
|
|
38
39
|
# 'asset_id': ('custom', 'in_order', {'values': [a.id for a in assets]}),
|
|
39
40
|
# }
|
|
40
|
-
|
|
41
|
+
|
|
41
42
|
# translated_fields = {}
|
|
43
|
+
|
|
42
44
|
# for name, value in self.fields.items():
|
|
43
45
|
# if name in fk_fields:
|
|
44
46
|
# translated_name = fk_fields[name]
|
|
45
|
-
|
|
47
|
+
|
|
46
48
|
# if translated_name in self.field_names:
|
|
47
49
|
# # Do not want to override asset_id if the user has already pass it in.
|
|
48
50
|
# continue
|
|
@@ -50,9 +52,7 @@ class DjangoModelFieldsConfig(FieldsConfig):
|
|
|
50
52
|
# translated_fields[translated_name] = value
|
|
51
53
|
# else:
|
|
52
54
|
# translated_fields[name] = value
|
|
53
|
-
|
|
55
|
+
|
|
54
56
|
# return translated_fields
|
|
55
|
-
|
|
57
|
+
|
|
56
58
|
# # Update self.fields.
|
|
57
|
-
#
|
|
58
|
-
#
|
|
@@ -2,8 +2,8 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TypeVar, TYPE_CHECKING
|
|
4
4
|
|
|
5
|
-
from django.db.models import ForeignKey
|
|
6
5
|
from django.db.models.base import Model
|
|
6
|
+
from django.db.models import ForeignKey
|
|
7
7
|
|
|
8
8
|
from django_spire.contrib.seeding.field.callable import CallableFieldSeeder
|
|
9
9
|
from django_spire.contrib.seeding.field.custom import CustomFieldSeeder
|
|
@@ -37,11 +37,11 @@ class DjangoModelSeeder(BaseModelSeeder):
|
|
|
37
37
|
super().__init_subclass__(**kwargs)
|
|
38
38
|
|
|
39
39
|
if cls.model_class is None:
|
|
40
|
-
message =
|
|
40
|
+
message = 'Seeds must have a model class'
|
|
41
41
|
raise ValueError(message)
|
|
42
42
|
|
|
43
43
|
if cls.fields is None:
|
|
44
|
-
message =
|
|
44
|
+
message = 'Seeds must have fields'
|
|
45
45
|
raise ValueError(message)
|
|
46
46
|
|
|
47
47
|
@classmethod
|
|
@@ -55,7 +55,7 @@ class DjangoModelSeeder(BaseModelSeeder):
|
|
|
55
55
|
@classmethod
|
|
56
56
|
def seed_database(
|
|
57
57
|
cls,
|
|
58
|
-
count=1,
|
|
58
|
+
count: int = 1,
|
|
59
59
|
fields: dict | None = None
|
|
60
60
|
) -> list[TypeModel]:
|
|
61
61
|
model_objects = cls.seed(count, fields)
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
2
5
|
from django.db import models
|
|
6
|
+
from django.test import TestCase
|
|
3
7
|
from django.utils.timezone import now
|
|
4
8
|
|
|
5
9
|
from django_spire.contrib.seeding import DjangoModelSeeder
|
|
@@ -15,39 +19,46 @@ class Product(models.Model):
|
|
|
15
19
|
updated_at = models.DateTimeField(null=True, blank=True)
|
|
16
20
|
|
|
17
21
|
class Meta:
|
|
18
|
-
app_label =
|
|
22
|
+
app_label = 'tests'
|
|
19
23
|
managed = False
|
|
20
24
|
|
|
25
|
+
def __str__(self) -> str:
|
|
26
|
+
return 'Product'
|
|
27
|
+
|
|
21
28
|
|
|
22
29
|
class ProductSeeder(DjangoModelSeeder):
|
|
23
30
|
model_class = Product
|
|
24
31
|
fields = {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
'name': ('llm', 'Clothing store'),
|
|
33
|
+
'description': ('static', 'A great product for testing.'),
|
|
34
|
+
'price': ('faker', 'pydecimal', {'left_digits': 2, 'right_digits': 2, 'positive': True}),
|
|
35
|
+
'in_stock': True,
|
|
36
|
+
'created_at': ('callable', now),
|
|
37
|
+
'updated_at': 'exclude',
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
|
|
34
|
-
class TestProductSeeder(
|
|
41
|
+
class TestProductSeeder(TestCase):
|
|
42
|
+
def test_assign_defaults(self) -> None:
|
|
43
|
+
assert 'sku' in ProductSeeder.resolved_fields()
|
|
44
|
+
|
|
45
|
+
def test_field_override_applies(self) -> None:
|
|
46
|
+
override_time = now()
|
|
47
|
+
|
|
48
|
+
result = ProductSeeder.seed(count=1, fields={'updated_at': ('static', override_time)})
|
|
35
49
|
|
|
36
|
-
|
|
37
|
-
self.assertIn('sku', ProductSeeder.resolved_fields().keys())
|
|
50
|
+
assert result[0].updated_at == override_time
|
|
38
51
|
|
|
39
|
-
def
|
|
52
|
+
def test_raises_for_invalid_field(self) -> None:
|
|
53
|
+
with pytest.raises(Exception) as exc_info:
|
|
54
|
+
ProductSeeder.seed(count=1, fields={'not_a_field': ('static', 'value')})
|
|
55
|
+
|
|
56
|
+
assert 'not_a_field' in str(exc_info.value)
|
|
57
|
+
|
|
58
|
+
def test_seeds_three_objects(self) -> None:
|
|
40
59
|
result = ProductSeeder.seed(count=3)
|
|
41
|
-
self.assertEqual(len(result), 3)
|
|
42
|
-
for obj in result:
|
|
43
|
-
self.assertIsInstance(obj, Product)
|
|
44
60
|
|
|
45
|
-
|
|
46
|
-
with self.assertRaises(Exception) as context:
|
|
47
|
-
ProductSeeder.seed(count=1, fields={"not_a_field": ("static", "value")})
|
|
48
|
-
self.assertIn("not_a_field", str(context.exception))
|
|
61
|
+
assert len(result) == 3
|
|
49
62
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
result = ProductSeeder.seed(count=1, fields={"updated_at": ("static", override_time)})
|
|
53
|
-
self.assertEqual(result[0].updated_at, override_time)
|
|
63
|
+
for obj in result:
|
|
64
|
+
assert isinstance(obj, Product)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from unittest.mock import MagicMock
|
|
6
|
+
|
|
7
|
+
from django.test import TestCase
|
|
8
|
+
|
|
9
|
+
from django_spire.contrib.seeding.model.config import FieldsConfig
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestFieldsConfig(TestCase):
|
|
13
|
+
def setUp(self) -> None:
|
|
14
|
+
self.model_class = MagicMock()
|
|
15
|
+
|
|
16
|
+
def test_assign_defaults_adds_missing_fields(self) -> None:
|
|
17
|
+
config = FieldsConfig(
|
|
18
|
+
raw_fields={'name': ('static', 'test')},
|
|
19
|
+
field_names=['name', 'email'],
|
|
20
|
+
default_to='llm',
|
|
21
|
+
model_class=self.model_class
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
assert 'email' in config.fields
|
|
25
|
+
assert config.fields['email'] == ('llm',)
|
|
26
|
+
|
|
27
|
+
def test_excluded_property(self) -> None:
|
|
28
|
+
config = FieldsConfig(
|
|
29
|
+
raw_fields={'name': 'exclude', 'email': ('exclude',)},
|
|
30
|
+
field_names=['name', 'email', 'status'],
|
|
31
|
+
default_to='llm',
|
|
32
|
+
model_class=self.model_class
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
assert 'name' in config.excluded
|
|
36
|
+
assert 'email' in config.excluded
|
|
37
|
+
assert 'status' not in config.excluded
|
|
38
|
+
|
|
39
|
+
def test_fields_are_ordered(self) -> None:
|
|
40
|
+
config = FieldsConfig(
|
|
41
|
+
raw_fields={'zebra': ('static', 'z'), 'apple': ('static', 'a')},
|
|
42
|
+
field_names=['zebra', 'apple'],
|
|
43
|
+
default_to='included',
|
|
44
|
+
model_class=self.model_class
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
keys = list(config.fields.keys())
|
|
48
|
+
|
|
49
|
+
assert keys == ['apple', 'zebra']
|
|
50
|
+
|
|
51
|
+
def test_override_creates_new_config(self) -> None:
|
|
52
|
+
config = FieldsConfig(
|
|
53
|
+
raw_fields={'name': ('static', 'original')},
|
|
54
|
+
field_names=['name', 'email'],
|
|
55
|
+
default_to='llm',
|
|
56
|
+
model_class=self.model_class
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
new_config = config.override({'name': ('static', 'overridden')})
|
|
60
|
+
|
|
61
|
+
assert new_config is not config
|
|
62
|
+
assert new_config.fields['name'] == ('static', 'overridden')
|
|
63
|
+
|
|
64
|
+
def test_validate_raises_for_invalid_fields(self) -> None:
|
|
65
|
+
with pytest.raises(ValueError, match='Invalid field name'):
|
|
66
|
+
FieldsConfig(
|
|
67
|
+
raw_fields={'invalid_field': ('static', 'test')},
|
|
68
|
+
field_names=['name', 'email'],
|
|
69
|
+
default_to='llm',
|
|
70
|
+
model_class=self.model_class
|
|
71
|
+
)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from django.test import TestCase
|
|
6
|
+
from django.utils import timezone
|
|
7
|
+
|
|
8
|
+
from django_spire.contrib.seeding.field.custom import CustomFieldSeeder
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestCustomFieldSeeder(TestCase):
|
|
12
|
+
def test_date_time_between_returns_aware_datetime(self) -> None:
|
|
13
|
+
seeder = CustomFieldSeeder(fields={})
|
|
14
|
+
|
|
15
|
+
result = seeder.date_time_between(start_date='-30d', end_date='now')
|
|
16
|
+
|
|
17
|
+
assert timezone.is_aware(result)
|
|
18
|
+
|
|
19
|
+
def test_in_order_cycles_through_values(self) -> None:
|
|
20
|
+
seeder = CustomFieldSeeder(fields={})
|
|
21
|
+
values = ['a', 'b', 'c']
|
|
22
|
+
|
|
23
|
+
assert seeder.in_order(values, index=0) == 'a'
|
|
24
|
+
assert seeder.in_order(values, index=1) == 'b'
|
|
25
|
+
assert seeder.in_order(values, index=2) == 'c'
|
|
26
|
+
assert seeder.in_order(values, index=3) == 'a'
|
|
27
|
+
|
|
28
|
+
def test_in_order_raises_for_empty_list(self) -> None:
|
|
29
|
+
seeder = CustomFieldSeeder(fields={})
|
|
30
|
+
|
|
31
|
+
with pytest.raises(ValueError, match='Cannot select from empty values list'):
|
|
32
|
+
seeder.in_order(values=[], index=0)
|
|
33
|
+
|
|
34
|
+
def test_keyword_is_custom(self) -> None:
|
|
35
|
+
assert CustomFieldSeeder.keyword == 'custom'
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django.test import TestCase
|
|
4
|
+
|
|
5
|
+
from django_spire.contrib.seeding.field.enums import FieldSeederTypesEnum
|
|
6
|
+
from django_spire.contrib.seeding.model.enums import ModelSeederDefaultsEnum
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestFieldSeederTypesEnum(TestCase):
|
|
10
|
+
def test_callable_value(self) -> None:
|
|
11
|
+
assert FieldSeederTypesEnum.CALLABLE == 'callable'
|
|
12
|
+
|
|
13
|
+
def test_custom_value(self) -> None:
|
|
14
|
+
assert FieldSeederTypesEnum.CUSTOM == 'custom'
|
|
15
|
+
|
|
16
|
+
def test_faker_value(self) -> None:
|
|
17
|
+
assert FieldSeederTypesEnum.FAKER == 'faker'
|
|
18
|
+
|
|
19
|
+
def test_is_str_enum(self) -> None:
|
|
20
|
+
assert isinstance(FieldSeederTypesEnum.LLM, str)
|
|
21
|
+
|
|
22
|
+
def test_llm_value(self) -> None:
|
|
23
|
+
assert FieldSeederTypesEnum.LLM == 'llm'
|
|
24
|
+
|
|
25
|
+
def test_static_value(self) -> None:
|
|
26
|
+
assert FieldSeederTypesEnum.STATIC == 'static'
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TestModelSeederDefaultsEnum(TestCase):
|
|
30
|
+
def test_faker_value(self) -> None:
|
|
31
|
+
assert ModelSeederDefaultsEnum.FAKER == 'faker'
|
|
32
|
+
|
|
33
|
+
def test_included_value(self) -> None:
|
|
34
|
+
assert ModelSeederDefaultsEnum.INCLUDED == 'included'
|
|
35
|
+
|
|
36
|
+
def test_is_str_enum(self) -> None:
|
|
37
|
+
assert isinstance(ModelSeederDefaultsEnum.LLM, str)
|
|
38
|
+
|
|
39
|
+
def test_llm_value(self) -> None:
|
|
40
|
+
assert ModelSeederDefaultsEnum.LLM == 'llm'
|