django-spire 0.23.7__py3-none-any.whl → 0.23.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_spire/ai/admin.py +11 -11
- django_spire/ai/chat/apps.py +1 -0
- django_spire/ai/chat/templates/django_spire/ai/chat/widget/dialog_widget.html +1 -1
- django_spire/ai/chat/tests/factories.py +15 -0
- django_spire/ai/chat/tests/test_controller.py +45 -0
- django_spire/ai/chat/tests/test_models.py +301 -0
- django_spire/ai/chat/tests/test_prompts.py +48 -0
- django_spire/ai/chat/tests/test_responses.py +208 -0
- django_spire/ai/chat/tests/test_router/test_base_chat_router.py +66 -6
- django_spire/ai/chat/tests/test_router/test_chat_workflow.py +73 -3
- django_spire/ai/chat/tests/test_router/test_integration.py +86 -6
- django_spire/ai/chat/tests/test_router/test_intent_decoder.py +93 -1
- django_spire/ai/chat/tests/test_router/test_message_intel.py +60 -1
- django_spire/ai/chat/tests/test_router/test_spire_chat_router.py +110 -0
- django_spire/ai/chat/tests/test_urls/test_json_urls.py +202 -1
- django_spire/ai/context/tests/__init__.py +0 -0
- django_spire/ai/context/tests/test_context.py +188 -0
- django_spire/ai/decorators.py +7 -6
- django_spire/ai/prompt/tests/test_bots.py +100 -10
- django_spire/ai/prompt/tests/test_prompt_intel.py +83 -0
- django_spire/ai/prompt/tests/test_prompt_tuning.py +126 -0
- django_spire/ai/sms/decorators.py +8 -2
- django_spire/ai/sms/tests/test_sms.py +240 -16
- django_spire/ai/sms/tests/test_sms_intel.py +42 -0
- django_spire/ai/sms/tests/test_webhook.py +155 -7
- django_spire/ai/sms/views.py +23 -24
- django_spire/ai/tests/test_ai.py +131 -7
- django_spire/auth/apps.py +4 -2
- django_spire/auth/controller/controller.py +36 -23
- django_spire/auth/controller/exceptions.py +9 -0
- django_spire/auth/group/admin.py +1 -0
- django_spire/auth/group/apps.py +2 -0
- django_spire/auth/group/factories.py +17 -8
- django_spire/auth/group/forms.py +7 -0
- django_spire/auth/group/tests/test_factories.py +146 -0
- django_spire/auth/group/tests/test_forms.py +282 -0
- django_spire/auth/group/tests/test_models.py +192 -0
- django_spire/auth/group/tests/test_querysets.py +98 -0
- django_spire/auth/group/tests/test_utils.py +341 -0
- django_spire/auth/group/tests/test_views.py +377 -0
- django_spire/auth/group/urls/__init__.py +3 -1
- django_spire/auth/group/urls/form_urls.py +2 -0
- django_spire/auth/group/urls/json_urls.py +3 -0
- django_spire/auth/group/urls/page_urls.py +2 -0
- django_spire/auth/group/utils.py +6 -2
- django_spire/auth/group/views/form_views.py +6 -3
- django_spire/auth/group/views/json_views.py +6 -2
- django_spire/auth/mfa/admin.py +2 -0
- django_spire/auth/mfa/apps.py +2 -0
- django_spire/auth/mfa/forms.py +1 -0
- django_spire/auth/mfa/querysets.py +9 -2
- django_spire/auth/mfa/tests/test_models.py +233 -0
- django_spire/auth/mfa/tests/test_utils.py +106 -0
- django_spire/auth/mfa/urls/__init__.py +2 -0
- django_spire/auth/mfa/urls/page_urls.py +2 -0
- django_spire/auth/mfa/urls/redirect_urls.py +2 -0
- django_spire/auth/mfa/views/page_views.py +2 -1
- django_spire/auth/permissions/consts.py +2 -2
- django_spire/auth/permissions/decorators.py +8 -8
- django_spire/auth/permissions/permissions.py +28 -35
- django_spire/auth/permissions/tests/test_decorators.py +333 -0
- django_spire/auth/permissions/tests/test_permissions.py +337 -0
- django_spire/auth/permissions/tests/test_tools.py +305 -0
- django_spire/auth/permissions/tools.py +21 -15
- django_spire/auth/seeding/seed.py +3 -0
- django_spire/auth/seeding/seeder.py +2 -0
- django_spire/auth/tests/test_controller.py +323 -0
- django_spire/auth/tests/test_url_endpoints.py +9 -9
- django_spire/auth/tests/test_views.py +406 -0
- django_spire/auth/urls/admin_urls.py +2 -0
- django_spire/auth/urls/redirect_urls.py +2 -0
- django_spire/auth/user/apps.py +2 -0
- django_spire/auth/user/forms.py +9 -0
- django_spire/auth/user/models.py +1 -1
- django_spire/auth/user/services/services.py +1 -0
- django_spire/auth/user/tests/factories.py +14 -13
- django_spire/auth/user/tests/test_factories.py +166 -2
- django_spire/auth/user/tests/test_forms.py +573 -0
- django_spire/auth/user/tests/test_models.py +257 -0
- django_spire/auth/user/tests/test_services.py +200 -0
- django_spire/auth/user/tests/test_tools.py +153 -0
- django_spire/auth/user/tests/test_user_factories.py +139 -0
- django_spire/auth/user/tests/test_views.py +363 -0
- django_spire/auth/user/tools.py +7 -1
- django_spire/auth/user/urls/form_urls.py +3 -0
- django_spire/auth/user/urls/page_urls.py +3 -0
- django_spire/auth/user/views/form_views.py +19 -10
- django_spire/auth/user/views/page_views.py +8 -2
- django_spire/auth/views/redirect_views.py +14 -9
- django_spire/comment/admin.py +2 -0
- django_spire/comment/apps.py +2 -0
- django_spire/comment/templatetags/comment_tags.py +1 -0
- django_spire/comment/tests/test_forms.py +27 -0
- django_spire/comment/tests/test_models.py +215 -0
- django_spire/comment/tests/test_querysets.py +101 -0
- django_spire/comment/tests/test_utils.py +90 -0
- django_spire/comment/urls.py +2 -0
- django_spire/comment/utils.py +22 -13
- django_spire/comment/views.py +1 -1
- django_spire/conf.py +8 -6
- django_spire/consts.py +1 -1
- django_spire/contrib/breadcrumb/apps.py +2 -0
- django_spire/contrib/breadcrumb/breadcrumbs.py +18 -18
- django_spire/contrib/breadcrumb/tests/test_breadcrumbs.py +198 -0
- django_spire/contrib/constructor/__init__.py +3 -3
- django_spire/contrib/constructor/constructor.py +15 -15
- django_spire/contrib/constructor/django_model_constructor.py +5 -4
- django_spire/contrib/constructor/exceptions.py +5 -3
- django_spire/contrib/constructor/tests/__init__.py +0 -0
- django_spire/contrib/constructor/tests/test_constructor.py +193 -0
- django_spire/contrib/form/tests/__init__.py +0 -0
- django_spire/contrib/form/tests/test_forms.py +203 -0
- django_spire/contrib/generic_views/modal_views.py +2 -1
- django_spire/contrib/generic_views/portal_views.py +20 -19
- django_spire/contrib/generic_views/tests/__init__.py +0 -0
- django_spire/contrib/generic_views/tests/test_views.py +459 -0
- django_spire/contrib/help/apps.py +2 -0
- django_spire/contrib/help/templatetags/help.py +1 -0
- django_spire/contrib/help/tests/__init__.py +0 -0
- django_spire/contrib/help/tests/test_templatetags.py +100 -0
- django_spire/contrib/options/mixins.py +6 -5
- django_spire/contrib/options/tests/factories.py +5 -1
- django_spire/contrib/options/tests/test_options.py +234 -0
- django_spire/contrib/ordering/exceptions.py +7 -3
- django_spire/contrib/ordering/mixins.py +2 -0
- django_spire/contrib/ordering/querysets.py +3 -1
- django_spire/contrib/ordering/services/processor_service.py +8 -4
- django_spire/contrib/ordering/services/service.py +1 -2
- django_spire/contrib/ordering/tests/__init__.py +0 -0
- django_spire/contrib/ordering/tests/test_ordering.py +165 -0
- django_spire/contrib/ordering/validators.py +6 -6
- django_spire/contrib/pagination/templatetags/pagination_tags.py +12 -5
- django_spire/contrib/pagination/tests/__init__.py +0 -0
- django_spire/contrib/pagination/tests/test_pagination.py +179 -0
- django_spire/contrib/performance/decorators.py +16 -6
- django_spire/contrib/performance/tests/__init__.py +0 -0
- django_spire/contrib/performance/tests/test_performance.py +107 -0
- django_spire/contrib/queryset/enums.py +3 -1
- django_spire/contrib/queryset/filter_tools.py +10 -5
- django_spire/contrib/queryset/mixins.py +16 -16
- django_spire/contrib/queryset/tests/__init__.py +0 -0
- django_spire/contrib/queryset/tests/test_queryset.py +137 -0
- django_spire/contrib/seeding/field/base.py +13 -7
- django_spire/contrib/seeding/field/callable.py +8 -1
- django_spire/contrib/seeding/field/cleaners.py +5 -5
- django_spire/contrib/seeding/field/custom.py +20 -10
- django_spire/contrib/seeding/field/django/seeder.py +8 -6
- django_spire/contrib/seeding/field/enums.py +7 -5
- django_spire/contrib/seeding/field/override.py +16 -6
- django_spire/contrib/seeding/field/static.py +9 -2
- django_spire/contrib/seeding/field/tests/test_base.py +18 -14
- django_spire/contrib/seeding/field/tests/test_callable.py +13 -9
- django_spire/contrib/seeding/field/tests/test_cleaners.py +51 -38
- django_spire/contrib/seeding/field/tests/test_static.py +13 -9
- django_spire/contrib/seeding/intelligence/bots/seeder_generator_bot.py +2 -0
- django_spire/contrib/seeding/intelligence/intel.py +5 -1
- django_spire/contrib/seeding/intelligence/prompts/factory.py +6 -1
- django_spire/contrib/seeding/intelligence/prompts/foreign_key_selection_prompt.py +6 -1
- django_spire/contrib/seeding/intelligence/prompts/generate_django_model_seeder_prompts.py +2 -0
- django_spire/contrib/seeding/intelligence/prompts/generic_relationship_selection_prompt.py +7 -1
- django_spire/contrib/seeding/intelligence/prompts/hierarchical_selection_prompt.py +6 -2
- django_spire/contrib/seeding/intelligence/prompts/model_field_choices_prompt.py +8 -2
- django_spire/contrib/seeding/intelligence/prompts/negation_prompt.py +2 -0
- django_spire/contrib/seeding/intelligence/prompts/objective_prompt.py +6 -1
- django_spire/contrib/seeding/management/commands/seeding.py +9 -3
- django_spire/contrib/seeding/management/example.py +2 -0
- django_spire/contrib/seeding/model/base.py +16 -7
- django_spire/contrib/seeding/model/config.py +31 -15
- django_spire/contrib/seeding/model/django/config.py +13 -13
- django_spire/contrib/seeding/model/django/seeder.py +4 -4
- django_spire/contrib/seeding/model/django/tests/test_seeder.py +34 -23
- django_spire/contrib/seeding/model/enums.py +2 -0
- django_spire/contrib/seeding/tests/test_config.py +71 -0
- django_spire/contrib/seeding/tests/test_custom.py +35 -0
- django_spire/contrib/seeding/tests/test_enums.py +40 -0
- django_spire/contrib/seeding/tests/test_intel.py +32 -0
- django_spire/contrib/seeding/tests/test_override.py +63 -0
- django_spire/contrib/service/__init__.py +2 -2
- django_spire/contrib/service/django_model_service.py +16 -15
- django_spire/contrib/service/exceptions.py +5 -3
- django_spire/contrib/service/tests/__init__.py +0 -0
- django_spire/contrib/service/tests/test_service.py +153 -0
- django_spire/contrib/session/apps.py +2 -0
- django_spire/contrib/session/controller.py +48 -42
- django_spire/contrib/session/templatetags/session_tags.py +11 -2
- django_spire/contrib/session/tests/test_session_controller.py +117 -53
- django_spire/contrib/tests/__init__.py +0 -0
- django_spire/contrib/tests/test_utils.py +37 -0
- django_spire/contrib/utils.py +4 -1
- django_spire/core/apps.py +2 -0
- django_spire/core/converters/tests/test_to_data.py +353 -0
- django_spire/core/converters/tests/test_to_enums.py +61 -41
- django_spire/core/converters/tests/test_to_pydantic.py +138 -109
- django_spire/core/converters/to_data.py +29 -10
- django_spire/core/converters/to_enums.py +4 -2
- django_spire/core/converters/to_pydantic.py +22 -22
- django_spire/core/decorators.py +19 -6
- django_spire/core/forms/widgets.py +4 -0
- django_spire/core/maps.py +3 -1
- django_spire/core/middleware/maintenance.py +3 -3
- django_spire/core/middleware.py +8 -6
- django_spire/core/redirect/__init__.py +5 -0
- django_spire/core/redirect/generic_redirect.py +1 -2
- django_spire/core/redirect/tests/__init__.py +0 -0
- django_spire/core/redirect/tests/test_generic_redirect.py +34 -0
- django_spire/core/{tests/tests_redirect.py → redirect/tests/test_safe_redirect.py} +55 -81
- django_spire/core/shortcuts.py +3 -3
- django_spire/core/static/django_spire/css/app-layout.css +1 -1
- django_spire/core/static/django_spire/css/app-navigation.css +3 -3
- django_spire/core/static/django_spire/css/bootstrap-override.css +4 -0
- django_spire/core/tag/admin.py +12 -0
- django_spire/core/tag/intelligence/tag_set_bot.py +2 -0
- django_spire/core/tag/mixins.py +2 -0
- django_spire/core/tag/models.py +2 -0
- django_spire/core/tag/querysets.py +2 -0
- django_spire/core/tag/service/tag_service.py +6 -3
- django_spire/core/tag/tests/test_intelligence.py +9 -9
- django_spire/core/tag/tests/test_tags.py +44 -54
- django_spire/core/tag/tests/test_tools.py +191 -0
- django_spire/core/tag/tools.py +3 -0
- django_spire/core/templates/django_spire/card/card.html +5 -2
- django_spire/core/templates/django_spire/card/title_card.html +9 -4
- django_spire/core/templates/django_spire/infinite_scroll/base.html +1 -0
- django_spire/core/templates/django_spire/navigation/side_navigation.html +19 -24
- django_spire/core/templates/django_spire/page/full_page.html +46 -16
- django_spire/core/templates/django_spire/table/base.html +4 -2
- django_spire/core/templatetags/json.py +6 -2
- django_spire/core/templatetags/message.py +13 -8
- django_spire/core/templatetags/string_formating.py +8 -5
- django_spire/core/templatetags/tests/__init__.py +0 -0
- django_spire/core/templatetags/tests/test_templatetags.py +427 -0
- django_spire/core/templatetags/variable_types.py +17 -9
- django_spire/core/tests/test_cases.py +1 -1
- django_spire/core/tests/test_conf.py +43 -0
- django_spire/core/tests/test_consts.py +28 -0
- django_spire/core/tests/test_context_processors.py +93 -0
- django_spire/core/tests/test_decorators.py +95 -0
- django_spire/core/tests/test_django_spire_utils.py +56 -0
- django_spire/core/tests/test_exceptions.py +37 -0
- django_spire/core/tests/test_models.py +54 -0
- django_spire/core/tests/test_settings.py +45 -0
- django_spire/core/tests/test_shortcuts.py +74 -0
- django_spire/core/tests/test_urls.py +16 -0
- django_spire/core/tests/test_utils.py +58 -0
- django_spire/core/urls.py +4 -1
- django_spire/core/utils.py +12 -8
- django_spire/exceptions.py +16 -1
- django_spire/file/admin.py +4 -2
- django_spire/file/apps.py +8 -10
- django_spire/file/fields.py +7 -7
- django_spire/file/forms.py +1 -1
- django_spire/file/interfaces.py +15 -15
- django_spire/file/mixins.py +1 -4
- django_spire/file/models.py +3 -5
- django_spire/file/tests/factories.py +59 -0
- django_spire/file/tests/test_admin.py +69 -0
- django_spire/file/tests/test_apps.py +24 -0
- django_spire/file/tests/test_fields.py +114 -0
- django_spire/file/tests/test_forms.py +20 -0
- django_spire/file/tests/test_interfaces.py +183 -0
- django_spire/file/tests/test_models.py +82 -0
- django_spire/file/tests/test_querysets.py +102 -0
- django_spire/file/tests/test_utils.py +32 -0
- django_spire/file/tests/test_views.py +145 -0
- django_spire/file/tests/test_widgets.py +82 -0
- django_spire/file/tools.py +8 -2
- django_spire/file/views.py +7 -3
- django_spire/file/widgets.py +12 -12
- django_spire/help_desk/admin.py +15 -0
- django_spire/help_desk/apps.py +2 -0
- django_spire/help_desk/auth/controller.py +2 -0
- django_spire/help_desk/choices.py +2 -0
- django_spire/help_desk/enums.py +2 -0
- django_spire/help_desk/exceptions.py +31 -3
- django_spire/help_desk/forms.py +2 -0
- django_spire/help_desk/models.py +2 -0
- django_spire/help_desk/querysets.py +4 -1
- django_spire/help_desk/services/notification_service.py +26 -27
- django_spire/help_desk/services/service.py +2 -3
- django_spire/help_desk/tests/factories.py +8 -3
- django_spire/help_desk/tests/test_admin.py +41 -0
- django_spire/help_desk/tests/test_apps.py +41 -0
- django_spire/help_desk/tests/test_choices.py +50 -0
- django_spire/help_desk/tests/test_controller.py +87 -0
- django_spire/help_desk/tests/test_enums.py +18 -0
- django_spire/help_desk/tests/test_exceptions.py +37 -0
- django_spire/help_desk/tests/test_forms.py +89 -0
- django_spire/help_desk/tests/test_models.py +59 -0
- django_spire/help_desk/tests/test_querysets.py +38 -0
- django_spire/help_desk/tests/test_services/test_notification_service.py +15 -8
- django_spire/help_desk/tests/test_services/test_service.py +92 -0
- django_spire/help_desk/tests/test_urls/test_form_urls.py +6 -6
- django_spire/help_desk/tests/test_urls/test_page_urls.py +8 -9
- django_spire/help_desk/tests/test_views/test_form_views.py +46 -19
- django_spire/help_desk/tests/test_views/test_page_views.py +32 -9
- django_spire/help_desk/urls/__init__.py +4 -1
- django_spire/help_desk/urls/form_urls.py +3 -0
- django_spire/help_desk/urls/page_urls.py +3 -0
- django_spire/help_desk/views/form_views.py +13 -5
- django_spire/help_desk/views/page_views.py +11 -3
- django_spire/history/activity/admin.py +2 -0
- django_spire/history/activity/apps.py +3 -1
- django_spire/history/activity/mixins.py +13 -7
- django_spire/history/activity/models.py +6 -5
- django_spire/history/activity/querysets.py +2 -0
- django_spire/history/activity/tests/__init__.py +0 -0
- django_spire/history/activity/tests/test_activity.py +176 -0
- django_spire/history/admin.py +9 -2
- django_spire/history/choices.py +3 -0
- django_spire/history/models.py +5 -5
- django_spire/history/tests/test_admin.py +93 -0
- django_spire/history/tests/test_history.py +101 -0
- django_spire/history/tests/test_mixins.py +84 -0
- django_spire/history/viewed/admin.py +3 -1
- django_spire/history/viewed/apps.py +3 -1
- django_spire/history/viewed/models.py +2 -0
- django_spire/history/viewed/tests/__init__.py +0 -0
- django_spire/history/viewed/tests/test_viewed.py +46 -0
- django_spire/knowledge/auth/tests/__init__.py +0 -0
- django_spire/knowledge/auth/tests/test_controller.py +116 -0
- django_spire/knowledge/collection/admin.py +5 -1
- django_spire/knowledge/collection/models.py +3 -1
- django_spire/knowledge/collection/seeding/seed.py +1 -0
- django_spire/knowledge/collection/services/factory_service.py +10 -11
- django_spire/knowledge/collection/services/ordering_service.py +1 -2
- django_spire/knowledge/collection/services/service.py +5 -10
- django_spire/knowledge/collection/services/tag_service.py +5 -2
- django_spire/knowledge/collection/tests/factories.py +28 -1
- django_spire/knowledge/collection/tests/test_models.py +48 -0
- django_spire/knowledge/collection/tests/test_querysets.py +93 -0
- django_spire/knowledge/collection/tests/test_services/test_factory_service.py +100 -0
- django_spire/knowledge/collection/tests/test_services/test_services.py +160 -0
- django_spire/knowledge/collection/tests/test_urls/test_form_urls.py +21 -3
- django_spire/knowledge/collection/tests/test_urls/test_json_urls.py +39 -1
- django_spire/knowledge/collection/tests/test_urls/test_page_urls.py +12 -4
- django_spire/knowledge/collection/urls/__init__.py +3 -0
- django_spire/knowledge/collection/urls/form_urls.py +2 -0
- django_spire/knowledge/collection/urls/json_urls.py +2 -0
- django_spire/knowledge/collection/urls/page_urls.py +2 -0
- django_spire/knowledge/collection/views/form_views.py +4 -4
- django_spire/knowledge/collection/views/json_views.py +5 -1
- django_spire/knowledge/collection/views/page_views.py +5 -2
- django_spire/knowledge/entry/admin.py +7 -1
- django_spire/knowledge/entry/forms.py +2 -0
- django_spire/knowledge/entry/models.py +2 -0
- django_spire/knowledge/entry/seeding/seed.py +3 -0
- django_spire/knowledge/entry/services/automation_service.py +5 -4
- django_spire/knowledge/entry/services/factory_service.py +7 -5
- django_spire/knowledge/entry/services/service.py +4 -7
- django_spire/knowledge/entry/services/tag_service.py +0 -1
- django_spire/knowledge/entry/services/tool_service.py +1 -0
- django_spire/knowledge/entry/services/transformation_services.py +1 -5
- django_spire/knowledge/entry/tests/factories.py +1 -2
- django_spire/knowledge/entry/tests/test_factory_service.py +20 -0
- django_spire/knowledge/entry/tests/test_models.py +41 -0
- django_spire/knowledge/entry/tests/test_querysets.py +71 -0
- django_spire/knowledge/entry/tests/test_services.py +94 -0
- django_spire/knowledge/entry/tests/test_urls/test_form_urls.py +9 -14
- django_spire/knowledge/entry/tests/test_urls/test_json_urls.py +48 -5
- django_spire/knowledge/entry/tests/test_urls/test_page_urls.py +6 -8
- django_spire/knowledge/entry/tests/test_urls/test_template_urls.py +40 -0
- django_spire/knowledge/entry/urls/form_urls.py +2 -0
- django_spire/knowledge/entry/urls/json_urls.py +2 -0
- django_spire/knowledge/entry/urls/page_urls.py +2 -0
- django_spire/knowledge/entry/urls/template_urls.py +2 -0
- django_spire/knowledge/entry/version/block/choices.py +2 -0
- django_spire/knowledge/entry/version/block/data/data.py +1 -0
- django_spire/knowledge/entry/version/block/data/list/data.py +8 -13
- django_spire/knowledge/entry/version/block/data/list/maps.py +3 -0
- django_spire/knowledge/entry/version/block/data/list/meta.py +1 -2
- django_spire/knowledge/entry/version/block/data/list/tests/__init__.py +0 -0
- django_spire/knowledge/entry/version/block/data/list/tests/test_maps.py +32 -0
- django_spire/knowledge/entry/version/block/data/list/tests/test_meta.py +58 -0
- django_spire/knowledge/entry/version/block/data/maps.py +3 -6
- django_spire/knowledge/entry/version/block/models.py +7 -5
- django_spire/knowledge/entry/version/block/seeding/constants.py +5 -4
- django_spire/knowledge/entry/version/block/services/service.py +2 -3
- django_spire/knowledge/entry/version/block/tests/factories.py +4 -10
- django_spire/knowledge/entry/version/block/tests/test_choices.py +56 -0
- django_spire/knowledge/entry/version/block/tests/test_data.py +90 -0
- django_spire/knowledge/entry/version/block/tests/test_maps.py +37 -0
- django_spire/knowledge/entry/version/block/tests/test_models.py +55 -0
- django_spire/knowledge/entry/version/block/tests/test_querysets.py +35 -0
- django_spire/knowledge/entry/version/block/tests/test_services.py +65 -0
- django_spire/knowledge/entry/version/choices.py +2 -0
- django_spire/knowledge/entry/version/converters/converter.py +1 -1
- django_spire/knowledge/entry/version/converters/docx_converter.py +4 -7
- django_spire/knowledge/entry/version/converters/markdown_converter.py +20 -20
- django_spire/knowledge/entry/version/maps.py +4 -5
- django_spire/knowledge/entry/version/querysets.py +1 -1
- django_spire/knowledge/entry/version/seeding/seeder.py +1 -2
- django_spire/knowledge/entry/version/services/processor_service.py +5 -4
- django_spire/knowledge/entry/version/services/service.py +1 -2
- django_spire/knowledge/entry/version/tests/factories.py +2 -2
- django_spire/knowledge/entry/version/tests/test_choices.py +18 -0
- django_spire/knowledge/entry/version/tests/test_converters/test_docx_converter.py +56 -8
- django_spire/knowledge/entry/version/tests/test_converters/test_markdown_converter.py +78 -0
- django_spire/knowledge/entry/version/tests/test_maps.py +58 -0
- django_spire/knowledge/entry/version/tests/test_models.py +23 -0
- django_spire/knowledge/entry/version/tests/test_querysets.py +26 -0
- django_spire/knowledge/entry/version/tests/test_services.py +62 -0
- django_spire/knowledge/entry/version/tests/test_urls/test_json_urls.py +27 -8
- django_spire/knowledge/entry/version/tests/test_urls/test_page_urls.py +15 -8
- django_spire/knowledge/entry/version/tests/test_urls/test_redirect_urls.py +38 -0
- django_spire/knowledge/entry/version/urls/__init__.py +3 -0
- django_spire/knowledge/entry/version/urls/json_urls.py +2 -1
- django_spire/knowledge/entry/version/urls/page_urls.py +2 -0
- django_spire/knowledge/entry/version/urls/redirect_urls.py +2 -0
- django_spire/knowledge/entry/version/views/json_views.py +5 -1
- django_spire/knowledge/entry/version/views/page_views.py +10 -3
- django_spire/knowledge/entry/version/views/redirect_views.py +5 -1
- django_spire/knowledge/entry/views/form_views.py +16 -8
- django_spire/knowledge/entry/views/json_views.py +3 -1
- django_spire/knowledge/entry/views/page_views.py +8 -2
- django_spire/knowledge/entry/views/template_views.py +7 -1
- django_spire/knowledge/exceptions.py +2 -1
- django_spire/knowledge/intelligence/intel/answer_intel.py +2 -1
- django_spire/knowledge/intelligence/intel/entry_intel.py +0 -1
- django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +4 -5
- django_spire/knowledge/models.py +1 -2
- django_spire/knowledge/tests/__init__.py +0 -0
- django_spire/knowledge/tests/test_templatetags.py +40 -0
- django_spire/knowledge/tests/test_urls/__init__.py +0 -0
- django_spire/knowledge/tests/test_urls/test_page_urls.py +24 -0
- django_spire/knowledge/urls/__init__.py +2 -0
- django_spire/knowledge/urls/page_urls.py +2 -0
- django_spire/knowledge/views/page_views.py +8 -3
- django_spire/notification/admin.py +3 -1
- django_spire/notification/app/admin.py +2 -0
- django_spire/notification/app/apps.py +3 -1
- django_spire/notification/app/exceptions.py +9 -2
- django_spire/notification/app/models.py +8 -4
- django_spire/notification/app/processor.py +22 -26
- django_spire/notification/app/querysets.py +2 -0
- django_spire/notification/app/tests/__init__.py +0 -0
- django_spire/notification/app/tests/factories.py +34 -0
- django_spire/notification/app/tests/test_apps.py +24 -0
- django_spire/notification/app/tests/test_models.py +72 -0
- django_spire/notification/app/tests/test_processor.py +111 -0
- django_spire/notification/app/tests/test_querysets.py +90 -0
- django_spire/notification/app/tests/test_views/__init__.py +0 -0
- django_spire/notification/app/tests/test_views/test_json_views.py +48 -0
- django_spire/notification/app/tests/test_views/test_page_views.py +19 -0
- django_spire/notification/app/urls/__init__.py +3 -1
- django_spire/notification/app/urls/json_urls.py +6 -4
- django_spire/notification/app/urls/page_urls.py +4 -3
- django_spire/notification/app/urls/template_urls.py +4 -2
- django_spire/notification/apps.py +4 -1
- django_spire/notification/email/admin.py +5 -1
- django_spire/notification/email/apps.py +3 -1
- django_spire/notification/email/exceptions.py +4 -2
- django_spire/notification/email/helper.py +5 -3
- django_spire/notification/email/models.py +4 -0
- django_spire/notification/email/processor.py +19 -15
- django_spire/notification/email/querysets.py +3 -0
- django_spire/notification/email/tests/__init__.py +0 -0
- django_spire/notification/email/tests/factories.py +35 -0
- django_spire/notification/email/tests/test_apps.py +24 -0
- django_spire/notification/email/tests/test_models.py +52 -0
- django_spire/notification/email/tests/test_processor.py +92 -0
- django_spire/notification/email/tests/test_querysets.py +43 -0
- django_spire/notification/exceptions.py +17 -2
- django_spire/notification/managers.py +7 -1
- django_spire/notification/maps.py +4 -1
- django_spire/notification/mixins.py +2 -0
- django_spire/notification/models.py +3 -1
- django_spire/notification/processors/notification.py +12 -5
- django_spire/notification/processors/processor.py +2 -0
- django_spire/notification/processors/tests/__init__.py +0 -0
- django_spire/notification/processors/tests/test_notification.py +106 -0
- django_spire/notification/push/admin.py +10 -1
- django_spire/notification/push/apps.py +3 -1
- django_spire/notification/push/models.py +2 -3
- django_spire/notification/push/tests/__init__.py +0 -0
- django_spire/notification/push/tests/test_apps.py +24 -0
- django_spire/notification/push/tests/test_models.py +28 -0
- django_spire/notification/querysets.py +7 -1
- django_spire/notification/sms/admin.py +2 -0
- django_spire/notification/sms/apps.py +4 -1
- django_spire/notification/sms/automations.py +2 -0
- django_spire/notification/sms/choices.py +2 -0
- django_spire/notification/sms/exceptions.py +19 -5
- django_spire/notification/sms/helper.py +33 -23
- django_spire/notification/sms/models.py +5 -1
- django_spire/notification/sms/processor.py +20 -20
- django_spire/notification/sms/querysets.py +2 -0
- django_spire/notification/sms/tests/factories.py +33 -0
- django_spire/notification/sms/tests/test_apps.py +24 -0
- django_spire/notification/sms/tests/test_automation.py +38 -0
- django_spire/notification/sms/tests/test_choices.py +15 -0
- django_spire/notification/sms/tests/test_consts.py +17 -0
- django_spire/notification/sms/tests/test_exceptions.py +27 -0
- django_spire/notification/sms/tests/test_helper.py +50 -0
- django_spire/notification/sms/tests/test_models.py +81 -0
- django_spire/notification/sms/tests/test_processor.py +107 -0
- django_spire/notification/sms/tests/test_tools.py +25 -11
- django_spire/notification/sms/tools.py +16 -5
- django_spire/notification/sms/urls/__init__.py +3 -1
- django_spire/notification/sms/urls/media_urls.py +2 -0
- django_spire/notification/sms/views/media_views.py +14 -4
- django_spire/notification/tests/__init__.py +0 -0
- django_spire/notification/tests/factories.py +26 -0
- django_spire/notification/tests/test_admin.py +55 -0
- django_spire/notification/tests/test_apps.py +30 -0
- django_spire/notification/tests/test_automation.py +18 -0
- django_spire/notification/tests/test_choices.py +59 -0
- django_spire/notification/tests/test_exceptions.py +58 -0
- django_spire/notification/tests/test_managers.py +100 -0
- django_spire/notification/tests/test_maps.py +31 -0
- django_spire/notification/tests/test_models.py +76 -0
- django_spire/notification/tests/test_querysets.py +184 -0
- django_spire/notification/tests/test_utils.py +23 -0
- django_spire/notification/urls.py +3 -1
- django_spire/notification/utils.py +3 -1
- django_spire/settings.py +3 -0
- django_spire/theme/tests/test_context_processor.py +15 -13
- django_spire/theme/tests/test_enums.py +2 -2
- django_spire/theme/tests/test_filesystem.py +2 -5
- django_spire/theme/tests/test_integration.py +12 -12
- django_spire/theme/tests/test_model.py +40 -38
- django_spire/theme/tests/test_views/test_json_views.py +33 -33
- django_spire/theme/urls/json_urls.py +3 -0
- django_spire/theme/urls/page_urls.py +3 -0
- django_spire/urls.py +19 -15
- django_spire/utils.py +13 -4
- {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/METADATA +1 -1
- {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/RECORD +530 -358
- django_spire/contrib/options/tests/test_unit.py +0 -148
- django_spire/contrib/seeding/tests/test_seeding.py +0 -25
- django_spire/core/tests/test_templatetags.py +0 -117
- django_spire/core/tests/tests_shortcuts.py +0 -73
- django_spire/history/activity/tests.py +0 -3
- django_spire/history/activity/views.py +0 -3
- django_spire/knowledge/collection/tests/test_services/test_transformation_service.py +0 -71
- django_spire/notification/app/tests.py +0 -3
- {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/WHEEL +0 -0
- {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/licenses/LICENSE.md +0 -0
- {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/top_level.txt +0 -0
|
@@ -1,42 +1,44 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import enum
|
|
4
|
+
|
|
5
|
+
from typing import get_args, get_origin, Union
|
|
1
6
|
from unittest import TestCase
|
|
2
|
-
from typing import get_origin, get_args, Union
|
|
3
7
|
|
|
4
8
|
from pydantic import BaseModel
|
|
5
9
|
|
|
6
|
-
from django.core.validators import
|
|
10
|
+
from django.core.validators import MaxValueValidator, MinValueValidator
|
|
7
11
|
from django.db import models
|
|
8
|
-
import enum
|
|
9
12
|
|
|
10
13
|
from django_spire.core.converters.to_pydantic import DjangoToPydanticFieldConverter, django_to_pydantic_model
|
|
11
14
|
|
|
12
15
|
|
|
13
|
-
def is_optional(
|
|
14
|
-
return get_origin(
|
|
16
|
+
def is_optional(t: type) -> bool:
|
|
17
|
+
return get_origin(t) is Union and type(None) in get_args(t)
|
|
15
18
|
|
|
16
19
|
|
|
17
20
|
class TestDjangoModelToPydanticModel(TestCase):
|
|
18
|
-
def test_comprehensive_model_conversion(self):
|
|
21
|
+
def test_comprehensive_model_conversion(self) -> None:
|
|
19
22
|
class ComprehensiveModel(models.Model):
|
|
20
|
-
# Django auto-adds an "id" primary key field.
|
|
21
23
|
char_field_with_choices = models.CharField(
|
|
22
24
|
max_length=10,
|
|
23
|
-
choices=[(
|
|
24
|
-
default=
|
|
25
|
-
help_text=
|
|
25
|
+
choices=[('A', 'Active'), ('I', 'Inactive')],
|
|
26
|
+
default='A',
|
|
27
|
+
help_text='Status of the item',
|
|
26
28
|
unique=True,
|
|
27
29
|
null=False
|
|
28
30
|
)
|
|
29
31
|
char_field_no_choices = models.CharField(
|
|
30
32
|
max_length=20,
|
|
31
|
-
default=
|
|
32
|
-
help_text=
|
|
33
|
+
default='default',
|
|
34
|
+
help_text='Description',
|
|
33
35
|
unique=False,
|
|
34
36
|
null=True
|
|
35
37
|
)
|
|
36
38
|
int_field = models.IntegerField(
|
|
37
39
|
validators=[MinValueValidator(0), MaxValueValidator(100)],
|
|
38
40
|
default=50,
|
|
39
|
-
help_text=
|
|
41
|
+
help_text='Count',
|
|
40
42
|
unique=False,
|
|
41
43
|
null=True
|
|
42
44
|
)
|
|
@@ -44,191 +46,218 @@ class TestDjangoModelToPydanticModel(TestCase):
|
|
|
44
46
|
max_digits=5,
|
|
45
47
|
decimal_places=2,
|
|
46
48
|
default=0.0,
|
|
47
|
-
help_text=
|
|
49
|
+
help_text='Price',
|
|
48
50
|
unique=False,
|
|
49
51
|
null=False
|
|
50
52
|
)
|
|
51
53
|
boolean_field = models.BooleanField(
|
|
52
54
|
default=True,
|
|
53
|
-
help_text=
|
|
55
|
+
help_text='Active',
|
|
54
56
|
null=False
|
|
55
57
|
)
|
|
56
58
|
|
|
57
59
|
class Meta:
|
|
58
60
|
managed = False
|
|
59
61
|
|
|
62
|
+
def __str__(self) -> str:
|
|
63
|
+
return 'model'
|
|
64
|
+
|
|
60
65
|
PydanticModel = django_to_pydantic_model(ComprehensiveModel)
|
|
61
|
-
|
|
66
|
+
assert PydanticModel.__name__ == 'ComprehensiveModel'
|
|
62
67
|
|
|
63
|
-
def test_conversion_with_custom_base_class(self):
|
|
68
|
+
def test_conversion_with_custom_base_class(self) -> None:
|
|
64
69
|
class CustomBase(BaseModel):
|
|
65
|
-
extra_field: str =
|
|
70
|
+
extra_field: str = 'extra'
|
|
66
71
|
|
|
67
72
|
class CustomModel(models.Model):
|
|
68
|
-
char_field = models.CharField(max_length=10, default=
|
|
73
|
+
char_field = models.CharField(max_length=10, default='custom')
|
|
74
|
+
|
|
69
75
|
class Meta:
|
|
70
76
|
managed = False
|
|
71
77
|
|
|
78
|
+
def __str__(self) -> str:
|
|
79
|
+
return 'model'
|
|
80
|
+
|
|
72
81
|
PydanticModel = django_to_pydantic_model(CustomModel, base_class=CustomBase)
|
|
73
|
-
instance = PydanticModel(id=1, char_field=
|
|
82
|
+
instance = PydanticModel(id=1, char_field='custom', extra_field='extra')
|
|
74
83
|
|
|
75
|
-
|
|
76
|
-
|
|
84
|
+
assert instance.char_field == 'custom'
|
|
85
|
+
assert instance.extra_field == 'extra'
|
|
77
86
|
|
|
78
|
-
def
|
|
79
|
-
class
|
|
80
|
-
field1 = models.CharField(max_length=50, default=
|
|
87
|
+
def test_exclude_fields(self) -> None:
|
|
88
|
+
class SimpleModel2(models.Model):
|
|
89
|
+
field1 = models.CharField(max_length=50, default='value1')
|
|
81
90
|
field2 = models.IntegerField(default=10)
|
|
82
91
|
field3 = models.BooleanField(default=True)
|
|
92
|
+
|
|
83
93
|
class Meta:
|
|
84
94
|
managed = False
|
|
85
95
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
self.assertIn("id", fields)
|
|
89
|
-
self.assertIn("field1", fields)
|
|
90
|
-
self.assertIn("field3", fields)
|
|
91
|
-
self.assertNotIn("field2", fields)
|
|
96
|
+
def __str__(self) -> str:
|
|
97
|
+
return 'model'
|
|
92
98
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
99
|
+
PydanticModel = django_to_pydantic_model(SimpleModel2, exclude_fields=['field2'])
|
|
100
|
+
fields = PydanticModel.model_fields
|
|
101
|
+
assert 'id' in fields
|
|
102
|
+
assert 'field1' in fields
|
|
103
|
+
assert 'field3' in fields
|
|
104
|
+
assert 'field2' not in fields
|
|
105
|
+
|
|
106
|
+
def test_include_and_exclude(self) -> None:
|
|
107
|
+
class SimpleModel3(models.Model):
|
|
108
|
+
field1 = models.CharField(max_length=50, default='value1')
|
|
96
109
|
field2 = models.IntegerField(default=10)
|
|
97
110
|
field3 = models.BooleanField(default=True)
|
|
111
|
+
field4 = models.CharField(max_length=100, default='value4')
|
|
112
|
+
|
|
98
113
|
class Meta:
|
|
99
114
|
managed = False
|
|
100
115
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
self.assertIn("id", fields)
|
|
104
|
-
self.assertIn("field1", fields)
|
|
105
|
-
self.assertIn("field3", fields)
|
|
106
|
-
self.assertNotIn("field2", fields)
|
|
116
|
+
def __str__(self) -> str:
|
|
117
|
+
return 'model'
|
|
107
118
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
119
|
+
PydanticModel = django_to_pydantic_model(
|
|
120
|
+
SimpleModel3,
|
|
121
|
+
include_fields=['id', 'field1', 'field2', 'field3'],
|
|
122
|
+
exclude_fields=['field2']
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
fields = PydanticModel.model_fields
|
|
126
|
+
assert 'id' in fields
|
|
127
|
+
assert 'field1' in fields
|
|
128
|
+
assert 'field3' in fields
|
|
129
|
+
assert 'field2' not in fields
|
|
130
|
+
assert 'field4' not in fields
|
|
131
|
+
|
|
132
|
+
def test_include_fields_only(self) -> None:
|
|
133
|
+
class SimpleModel1(models.Model):
|
|
134
|
+
field1 = models.CharField(max_length=50, default='value1')
|
|
112
135
|
field2 = models.IntegerField(default=10)
|
|
113
136
|
field3 = models.BooleanField(default=True)
|
|
114
|
-
|
|
137
|
+
|
|
115
138
|
class Meta:
|
|
116
139
|
managed = False
|
|
117
140
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
self.assertNotIn("field2", fields)
|
|
128
|
-
self.assertNotIn("field4", fields)
|
|
141
|
+
def __str__(self) -> str:
|
|
142
|
+
return 'model'
|
|
143
|
+
|
|
144
|
+
PydanticModel = django_to_pydantic_model(SimpleModel1, include_fields=['id', 'field1', 'field3'])
|
|
145
|
+
fields = PydanticModel.model_fields
|
|
146
|
+
assert 'id' in fields
|
|
147
|
+
assert 'field1' in fields
|
|
148
|
+
assert 'field3' in fields
|
|
149
|
+
assert 'field2' not in fields
|
|
129
150
|
|
|
130
151
|
|
|
131
152
|
class TestDjangoToPydanticFieldConverter(TestCase):
|
|
132
153
|
# def test_nullable_field_is_optional(self):
|
|
133
154
|
# class Model(models.Model):
|
|
134
155
|
# char_field = models.CharField(max_length=10, null=True)
|
|
135
|
-
|
|
156
|
+
|
|
136
157
|
# class Meta:
|
|
137
158
|
# managed = False
|
|
138
|
-
|
|
159
|
+
|
|
160
|
+
# def __str__(self) -> str:
|
|
161
|
+
# return 'model'
|
|
162
|
+
|
|
139
163
|
# field = Model._meta.get_field('char_field')
|
|
140
164
|
# field_type, _ = DjangoToPydanticFieldConverter(field).build_field()
|
|
141
|
-
#
|
|
142
|
-
# self.assertTrue(is_optional(field_type))
|
|
143
165
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
166
|
+
# assert is_optional(field_type)
|
|
167
|
+
|
|
168
|
+
def test_charfield_constraints_applied(self) -> None:
|
|
169
|
+
class Model4(models.Model):
|
|
170
|
+
name = models.CharField(max_length=30)
|
|
147
171
|
|
|
148
172
|
class Meta:
|
|
149
173
|
managed = False
|
|
150
174
|
|
|
151
|
-
|
|
152
|
-
|
|
175
|
+
def __str__(self) -> str:
|
|
176
|
+
return 'model'
|
|
153
177
|
|
|
154
|
-
|
|
178
|
+
field = Model4._meta.get_field('name')
|
|
179
|
+
field_type, _ = DjangoToPydanticFieldConverter(field).build_field()
|
|
155
180
|
|
|
156
|
-
|
|
157
|
-
# class Model(models.Model):
|
|
158
|
-
# char_field = models.CharField(max_length=10, default="abc")
|
|
159
|
-
#
|
|
160
|
-
# class Meta:
|
|
161
|
-
# managed = False
|
|
162
|
-
#
|
|
163
|
-
# field = Model._meta.get_field('char_field')
|
|
164
|
-
# _, field_info = DjangoToPydanticFieldConverter(field).build_field()
|
|
165
|
-
#
|
|
166
|
-
# self.assertEqual(field_info.default, "abc")
|
|
181
|
+
assert 'max_length=30' in str(field_type)
|
|
167
182
|
|
|
168
|
-
def
|
|
169
|
-
class
|
|
170
|
-
|
|
183
|
+
def test_decimalfield_constraints_applied(self) -> None:
|
|
184
|
+
class Model6(models.Model):
|
|
185
|
+
amount = models.DecimalField(max_digits=5, decimal_places=2)
|
|
171
186
|
|
|
172
187
|
class Meta:
|
|
173
188
|
managed = False
|
|
174
189
|
|
|
175
|
-
|
|
176
|
-
|
|
190
|
+
def __str__(self) -> str:
|
|
191
|
+
return 'model'
|
|
177
192
|
|
|
178
|
-
|
|
193
|
+
field = Model6._meta.get_field('amount')
|
|
194
|
+
field_type, _ = DjangoToPydanticFieldConverter(field).build_field()
|
|
195
|
+
|
|
196
|
+
assert 'max_digits=5' in str(field_type)
|
|
197
|
+
assert 'decimal_places=2' in str(field_type)
|
|
179
198
|
|
|
180
|
-
def test_enum_created_for_choices(self):
|
|
199
|
+
def test_enum_created_for_choices(self) -> None:
|
|
181
200
|
class Model3(models.Model):
|
|
182
201
|
status = models.CharField(
|
|
183
202
|
max_length=10,
|
|
184
|
-
choices=[(
|
|
185
|
-
default=
|
|
203
|
+
choices=[('A', 'Active'), ('I', 'Inactive')],
|
|
204
|
+
default='A'
|
|
186
205
|
)
|
|
187
206
|
|
|
188
207
|
class Meta:
|
|
189
208
|
managed = False
|
|
190
209
|
|
|
210
|
+
def __str__(self) -> str:
|
|
211
|
+
return 'model'
|
|
212
|
+
|
|
191
213
|
field = Model3._meta.get_field('status')
|
|
192
214
|
field_type, _ = DjangoToPydanticFieldConverter(field).build_field()
|
|
193
215
|
|
|
194
|
-
|
|
195
|
-
|
|
216
|
+
assert issubclass(field_type, enum.Enum)
|
|
217
|
+
assert set(field_type.__members__) == {'A', 'I'}
|
|
196
218
|
|
|
197
|
-
def
|
|
198
|
-
class
|
|
199
|
-
|
|
219
|
+
def test_field_with_no_default_sets_none(self) -> None:
|
|
220
|
+
class Model1(models.Model):
|
|
221
|
+
char_field = models.CharField(max_length=10, null=True)
|
|
200
222
|
|
|
201
223
|
class Meta:
|
|
202
224
|
managed = False
|
|
203
225
|
|
|
204
|
-
|
|
205
|
-
|
|
226
|
+
def __str__(self) -> str:
|
|
227
|
+
return 'model'
|
|
206
228
|
|
|
207
|
-
|
|
229
|
+
field = Model1._meta.get_field('char_field')
|
|
230
|
+
_, field_info = DjangoToPydanticFieldConverter(field).build_field()
|
|
208
231
|
|
|
209
|
-
|
|
210
|
-
# Todo: test needs to be corrected.
|
|
211
|
-
from django.core.validators import MinValueValidator, MaxValueValidator
|
|
232
|
+
assert field_info.default is None
|
|
212
233
|
|
|
213
|
-
|
|
214
|
-
|
|
234
|
+
def test_help_text_becomes_description(self) -> None:
|
|
235
|
+
class Model2(models.Model):
|
|
236
|
+
char_field = models.CharField(max_length=10, help_text='hello')
|
|
215
237
|
|
|
216
238
|
class Meta:
|
|
217
239
|
managed = False
|
|
218
240
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
self.assertIn("int", str(field_type))
|
|
241
|
+
def __str__(self) -> str:
|
|
242
|
+
return 'model'
|
|
222
243
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
244
|
+
field = Model2._meta.get_field('char_field')
|
|
245
|
+
_, field_info = DjangoToPydanticFieldConverter(field).build_field()
|
|
246
|
+
|
|
247
|
+
assert field_info.description == 'hello'
|
|
248
|
+
|
|
249
|
+
def test_integerfield_validators_become_constraints(self) -> None:
|
|
250
|
+
# TODO(Nathan): test needs to be corrected.
|
|
251
|
+
|
|
252
|
+
class Model5(models.Model):
|
|
253
|
+
count = models.IntegerField(validators=[MinValueValidator(1), MaxValueValidator(10)])
|
|
226
254
|
|
|
227
255
|
class Meta:
|
|
228
256
|
managed = False
|
|
229
257
|
|
|
230
|
-
|
|
231
|
-
|
|
258
|
+
def __str__(self) -> str:
|
|
259
|
+
return 'model'
|
|
232
260
|
|
|
233
|
-
|
|
234
|
-
|
|
261
|
+
field = Model5._meta.get_field('count')
|
|
262
|
+
field_type, _ = DjangoToPydanticFieldConverter(field).build_field()
|
|
263
|
+
assert 'int' in str(field_type)
|
|
@@ -1,18 +1,28 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import random
|
|
5
|
+
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
2
8
|
from django.core.validators import MinValueValidator, MaxValueValidator
|
|
3
9
|
from faker import Faker
|
|
4
|
-
|
|
5
|
-
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from django.db import models
|
|
6
15
|
|
|
7
16
|
|
|
8
17
|
class DjangoFieldToFakerData:
|
|
9
18
|
def __init__(
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
19
|
+
self,
|
|
20
|
+
model_field: models.Field,
|
|
21
|
+
faker_method: tuple | None = None
|
|
13
22
|
):
|
|
14
23
|
if isinstance(faker_method, str):
|
|
15
24
|
faker_method = (faker_method,)
|
|
25
|
+
|
|
16
26
|
self.model_field = model_field
|
|
17
27
|
self.faker_method = faker_method
|
|
18
28
|
self.faker = Faker()
|
|
@@ -40,22 +50,28 @@ class DjangoFieldToFakerData:
|
|
|
40
50
|
def convert(self):
|
|
41
51
|
if self.faker_method:
|
|
42
52
|
result = self._use_faker_with_method()
|
|
53
|
+
|
|
43
54
|
if result is not None:
|
|
44
55
|
return result
|
|
45
56
|
|
|
46
57
|
converter = self.field_converters.get(self.model_field.get_internal_type())
|
|
58
|
+
|
|
47
59
|
if converter is None:
|
|
48
|
-
|
|
60
|
+
message = f'No handler for {self.model_field.get_internal_type()}'
|
|
61
|
+
raise Exception(message)
|
|
62
|
+
|
|
49
63
|
return converter()
|
|
50
64
|
|
|
51
65
|
def _get_min_max_from_validators(self):
|
|
52
66
|
min_value = 0
|
|
53
67
|
max_value = 10000
|
|
68
|
+
|
|
54
69
|
for validator in self.model_field.validators:
|
|
55
70
|
if isinstance(validator, MinValueValidator):
|
|
56
71
|
min_value = validator.limit_value
|
|
57
72
|
elif isinstance(validator, MaxValueValidator):
|
|
58
73
|
max_value = validator.limit_value
|
|
74
|
+
|
|
59
75
|
return min_value, max_value
|
|
60
76
|
|
|
61
77
|
def _use_faker_with_method(self):
|
|
@@ -64,6 +80,7 @@ class DjangoFieldToFakerData:
|
|
|
64
80
|
kwargs = self.faker_method[1] if len(self.faker_method) > 1 and isinstance(self.faker_method[1], dict) else {}
|
|
65
81
|
|
|
66
82
|
method = getattr(self.faker, method_name, None)
|
|
83
|
+
|
|
67
84
|
if callable(method):
|
|
68
85
|
return method(**kwargs)
|
|
69
86
|
|
|
@@ -79,8 +96,6 @@ class DjangoFieldToFakerData:
|
|
|
79
96
|
choices = [choice[0] for choice in self.model_field.choices]
|
|
80
97
|
return self.faker.random_element(elements=choices)
|
|
81
98
|
|
|
82
|
-
|
|
83
|
-
|
|
84
99
|
return self.faker.text(max_nb_chars=max_length)
|
|
85
100
|
|
|
86
101
|
def _integer_field_data(self):
|
|
@@ -126,6 +141,10 @@ class DjangoFieldToFakerData:
|
|
|
126
141
|
return os.urandom(16)
|
|
127
142
|
|
|
128
143
|
|
|
129
|
-
def fake_model_field_value(
|
|
144
|
+
def fake_model_field_value(
|
|
145
|
+
model_class: type[models.Model],
|
|
146
|
+
field_name: str,
|
|
147
|
+
faker_method: str | tuple | None = None
|
|
148
|
+
) -> Any:
|
|
130
149
|
field = model_class._meta.get_field(field_name)
|
|
131
150
|
return DjangoFieldToFakerData(field, faker_method).convert()
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from enum import Enum
|
|
2
4
|
|
|
3
5
|
|
|
4
6
|
def django_choices_to_enums(
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
class_name: str,
|
|
8
|
+
choices: list[tuple[str, str]]
|
|
7
9
|
):
|
|
8
10
|
choices_dict = {str(k).upper(): k for k, _ in choices}
|
|
9
11
|
return Enum(class_name, choices_dict)
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from pydantic import constr, condecimal, create_model
|
|
2
4
|
from pydantic.fields import Field
|
|
3
5
|
|
|
4
|
-
from typing import
|
|
6
|
+
from typing import Any
|
|
5
7
|
|
|
6
8
|
from django.db import models
|
|
7
9
|
from django.core import validators
|
|
@@ -11,13 +13,14 @@ from django_spire.core.maps import MODEL_FIELD_TYPE_TO_TYPE_MAP
|
|
|
11
13
|
|
|
12
14
|
|
|
13
15
|
def django_to_pydantic_model(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
model_class: type[models.Model],
|
|
17
|
+
base_class: type | None = None,
|
|
18
|
+
include_fields: str | list| tuple | None = None,
|
|
19
|
+
exclude_fields: str | list | tuple | None = None
|
|
18
20
|
):
|
|
19
21
|
if not issubclass(model_class, models.Model):
|
|
20
|
-
|
|
22
|
+
message = 'model_class must be a subclass of django.db.models.Model'
|
|
23
|
+
raise TypeError(message)
|
|
21
24
|
|
|
22
25
|
if include_fields is None:
|
|
23
26
|
include_fields = []
|
|
@@ -47,7 +50,6 @@ def django_to_pydantic_model(
|
|
|
47
50
|
|
|
48
51
|
|
|
49
52
|
class DjangoToPydanticFieldConverter:
|
|
50
|
-
|
|
51
53
|
def __init__(self, model_field: models.Field):
|
|
52
54
|
self.model_field = model_field
|
|
53
55
|
self.kwargs = {}
|
|
@@ -60,8 +62,8 @@ class DjangoToPydanticFieldConverter:
|
|
|
60
62
|
def bool_to_json_schema(value: bool):
|
|
61
63
|
if value:
|
|
62
64
|
return "true"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
|
|
66
|
+
return "false"
|
|
65
67
|
|
|
66
68
|
@property
|
|
67
69
|
def field_handlers(self):
|
|
@@ -80,22 +82,20 @@ class DjangoToPydanticFieldConverter:
|
|
|
80
82
|
str
|
|
81
83
|
)
|
|
82
84
|
|
|
83
|
-
def _build_char_field(self) ->
|
|
85
|
+
def _build_char_field(self) -> type:
|
|
84
86
|
if self.model_field.max_length:
|
|
85
87
|
return constr(max_length=self.model_field.max_length)
|
|
86
88
|
return str
|
|
87
89
|
|
|
88
|
-
def _build_date_field(self) ->
|
|
89
|
-
self.kwargs['example'] = '2022-01-01'
|
|
90
|
+
def _build_date_field(self) -> type:
|
|
90
91
|
self.kwargs['json_schema_extra']['example'] = '2022-01-01'
|
|
91
92
|
return self._base_type()
|
|
92
93
|
|
|
93
|
-
def _build_date_time_field(self) ->
|
|
94
|
-
self.kwargs['example'] = '2022-01-01 13:37:00'
|
|
94
|
+
def _build_date_time_field(self) -> type:
|
|
95
95
|
self.kwargs['json_schema_extra']['example'] = '2022-01-01 13:37:00'
|
|
96
96
|
return self._base_type()
|
|
97
97
|
|
|
98
|
-
def _build_integer_field(self) ->
|
|
98
|
+
def _build_integer_field(self) -> type:
|
|
99
99
|
for validator in self.model_field.validators:
|
|
100
100
|
if isinstance(validator, validators.MinValueValidator):
|
|
101
101
|
self.kwargs['json_schema_extra']['greater_than'] = validator.limit_value
|
|
@@ -104,7 +104,7 @@ class DjangoToPydanticFieldConverter:
|
|
|
104
104
|
|
|
105
105
|
return self._base_type()
|
|
106
106
|
|
|
107
|
-
def _build_decimal_field(self) ->
|
|
107
|
+
def _build_decimal_field(self) -> type:
|
|
108
108
|
self.kwargs['json_schema_extra']['example'] = '0.00'
|
|
109
109
|
self.kwargs['json_schema_extra']['max_digits'] = self.model_field.max_digits
|
|
110
110
|
self.kwargs['json_schema_extra']['decimal_places'] = self.model_field.decimal_places
|
|
@@ -118,21 +118,19 @@ class DjangoToPydanticFieldConverter:
|
|
|
118
118
|
enum_name = f"{self.model_field.name.capitalize()}Enum"
|
|
119
119
|
return django_choices_to_enums(enum_name, self.model_field.choices)
|
|
120
120
|
|
|
121
|
-
def build_field(self) ->
|
|
121
|
+
def build_field(self) -> tuple[type, Any]:
|
|
122
122
|
"""Build and return the Pydantic field type and Field object."""
|
|
123
123
|
return self.field_type, Field(**self.kwargs)
|
|
124
124
|
|
|
125
125
|
def _build_metadata(self):
|
|
126
|
-
|
|
127
126
|
# Cannot set a default when providing option to the LLM
|
|
127
|
+
|
|
128
128
|
# if self.model_field.default is not models.NOT_PROVIDED:
|
|
129
129
|
# if callable(self.model_field.default):
|
|
130
130
|
# self.kwargs['default'] = self.model_field.default()
|
|
131
131
|
# else:
|
|
132
132
|
# self.kwargs['default'] = self.model_field.default
|
|
133
133
|
|
|
134
|
-
self.kwargs['required'] = not self.model_field.null
|
|
135
|
-
|
|
136
134
|
if self.model_field.null and self.model_field.default is models.NOT_PROVIDED:
|
|
137
135
|
self.kwargs['default'] = None
|
|
138
136
|
|
|
@@ -140,6 +138,7 @@ class DjangoToPydanticFieldConverter:
|
|
|
140
138
|
self.kwargs['description'] = str(self.model_field.help_text)
|
|
141
139
|
|
|
142
140
|
self.kwargs['json_schema_extra'] = {
|
|
141
|
+
'required': self.bool_to_json_schema(not self.model_field.null),
|
|
143
142
|
'is_unique': self.bool_to_json_schema(self.model_field.unique),
|
|
144
143
|
# 'is_required': self.bool_to_json_schema(not self.model_field.null),
|
|
145
144
|
'field_name': self.model_field.name
|
|
@@ -151,7 +150,7 @@ class DjangoToPydanticFieldConverter:
|
|
|
151
150
|
f"{value} ({label})" for value, label in self.model_field.choices
|
|
152
151
|
) + "."
|
|
153
152
|
|
|
154
|
-
def _get_pydantic_type(self) ->
|
|
153
|
+
def _get_pydantic_type(self) -> type:
|
|
155
154
|
if self.model_field.choices:
|
|
156
155
|
return self._build_enum_type()
|
|
157
156
|
|
|
@@ -164,6 +163,7 @@ class DjangoToPydanticFieldConverter:
|
|
|
164
163
|
|
|
165
164
|
def _wrap_nullable(self):
|
|
166
165
|
pass
|
|
166
|
+
|
|
167
167
|
# This works better for the LLM
|
|
168
168
|
# if self.model_field.null:
|
|
169
|
-
# self.field_type = Optional[self.field_type]
|
|
169
|
+
# self.field_type = Optional[self.field_type]
|
django_spire/core/decorators.py
CHANGED
|
@@ -1,25 +1,38 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
2
3
|
from functools import wraps
|
|
3
4
|
|
|
4
|
-
from
|
|
5
|
+
from typing import Callable, ParamSpec, TypeVar
|
|
6
|
+
|
|
7
|
+
from django.db import connections
|
|
8
|
+
from django.http import HttpRequest, HttpResponse, JsonResponse
|
|
9
|
+
|
|
5
10
|
|
|
11
|
+
P = ParamSpec('P')
|
|
12
|
+
T = TypeVar('T')
|
|
6
13
|
|
|
7
|
-
|
|
14
|
+
|
|
15
|
+
def close_db_connections(func: Callable[P, T]) -> Callable[P, T]:
|
|
8
16
|
@wraps(func)
|
|
9
|
-
def inner(*args, **kwargs):
|
|
17
|
+
def inner(*args: P.args, **kwargs: P.kwargs) -> T:
|
|
10
18
|
try:
|
|
11
19
|
return func(*args, **kwargs)
|
|
12
20
|
finally:
|
|
13
21
|
connections.close_all()
|
|
22
|
+
|
|
14
23
|
return inner
|
|
15
24
|
|
|
16
25
|
|
|
17
|
-
def valid_ajax_request_required(
|
|
26
|
+
def valid_ajax_request_required(
|
|
27
|
+
method: Callable[..., HttpResponse]
|
|
28
|
+
) -> Callable[..., HttpResponse]:
|
|
18
29
|
@wraps(method)
|
|
19
|
-
def wrapper(request, *args, **kwargs):
|
|
30
|
+
def wrapper(request: HttpRequest, *args, **kwargs) -> HttpResponse:
|
|
20
31
|
if request.method != 'POST' and request.content_type != 'application/json':
|
|
21
32
|
return JsonResponse(
|
|
22
33
|
{'type': 'error', 'message': 'Invalid Request'}
|
|
23
34
|
)
|
|
35
|
+
|
|
24
36
|
return method(request, *args, **kwargs)
|
|
37
|
+
|
|
25
38
|
return wrapper
|