django-spire 0.23.6__py3-none-any.whl → 0.23.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_spire/ai/admin.py +11 -11
- django_spire/ai/chat/apps.py +1 -0
- django_spire/ai/chat/templates/django_spire/ai/chat/widget/dialog_widget.html +1 -1
- django_spire/ai/chat/tests/factories.py +15 -0
- django_spire/ai/chat/tests/test_controller.py +45 -0
- django_spire/ai/chat/tests/test_models.py +301 -0
- django_spire/ai/chat/tests/test_prompts.py +48 -0
- django_spire/ai/chat/tests/test_responses.py +208 -0
- django_spire/ai/chat/tests/test_router/test_base_chat_router.py +66 -6
- django_spire/ai/chat/tests/test_router/test_chat_workflow.py +73 -3
- django_spire/ai/chat/tests/test_router/test_integration.py +86 -6
- django_spire/ai/chat/tests/test_router/test_intent_decoder.py +93 -1
- django_spire/ai/chat/tests/test_router/test_message_intel.py +60 -1
- django_spire/ai/chat/tests/test_router/test_spire_chat_router.py +110 -0
- django_spire/ai/chat/tests/test_urls/test_json_urls.py +202 -1
- django_spire/ai/context/tests/__init__.py +0 -0
- django_spire/ai/context/tests/test_context.py +188 -0
- django_spire/ai/decorators.py +7 -6
- django_spire/ai/prompt/tests/test_bots.py +100 -10
- django_spire/ai/prompt/tests/test_prompt_intel.py +83 -0
- django_spire/ai/prompt/tests/test_prompt_tuning.py +126 -0
- django_spire/ai/sms/decorators.py +8 -2
- django_spire/ai/sms/tests/test_sms.py +240 -16
- django_spire/ai/sms/tests/test_sms_intel.py +42 -0
- django_spire/ai/sms/tests/test_webhook.py +155 -7
- django_spire/ai/sms/views.py +23 -24
- django_spire/ai/tests/test_ai.py +131 -7
- django_spire/auth/apps.py +4 -2
- django_spire/auth/controller/controller.py +36 -23
- django_spire/auth/controller/exceptions.py +9 -0
- django_spire/auth/group/admin.py +1 -0
- django_spire/auth/group/apps.py +2 -0
- django_spire/auth/group/factories.py +17 -8
- django_spire/auth/group/forms.py +7 -0
- django_spire/auth/group/tests/test_factories.py +146 -0
- django_spire/auth/group/tests/test_forms.py +282 -0
- django_spire/auth/group/tests/test_models.py +192 -0
- django_spire/auth/group/tests/test_querysets.py +98 -0
- django_spire/auth/group/tests/test_utils.py +341 -0
- django_spire/auth/group/tests/test_views.py +377 -0
- django_spire/auth/group/urls/__init__.py +3 -1
- django_spire/auth/group/urls/form_urls.py +2 -0
- django_spire/auth/group/urls/json_urls.py +3 -0
- django_spire/auth/group/urls/page_urls.py +2 -0
- django_spire/auth/group/utils.py +6 -2
- django_spire/auth/group/views/form_views.py +6 -3
- django_spire/auth/group/views/json_views.py +6 -2
- django_spire/auth/mfa/admin.py +2 -0
- django_spire/auth/mfa/apps.py +2 -0
- django_spire/auth/mfa/forms.py +1 -0
- django_spire/auth/mfa/querysets.py +9 -2
- django_spire/auth/mfa/tests/test_models.py +233 -0
- django_spire/auth/mfa/tests/test_utils.py +106 -0
- django_spire/auth/mfa/urls/__init__.py +2 -0
- django_spire/auth/mfa/urls/page_urls.py +2 -0
- django_spire/auth/mfa/urls/redirect_urls.py +2 -0
- django_spire/auth/mfa/views/page_views.py +2 -1
- django_spire/auth/permissions/consts.py +2 -2
- django_spire/auth/permissions/decorators.py +8 -8
- django_spire/auth/permissions/permissions.py +28 -35
- django_spire/auth/permissions/tests/test_decorators.py +333 -0
- django_spire/auth/permissions/tests/test_permissions.py +337 -0
- django_spire/auth/permissions/tests/test_tools.py +305 -0
- django_spire/auth/permissions/tools.py +21 -15
- django_spire/auth/seeding/seed.py +3 -0
- django_spire/auth/seeding/seeder.py +2 -0
- django_spire/auth/tests/test_controller.py +323 -0
- django_spire/auth/tests/test_url_endpoints.py +9 -9
- django_spire/auth/tests/test_views.py +406 -0
- django_spire/auth/urls/admin_urls.py +2 -0
- django_spire/auth/urls/redirect_urls.py +2 -0
- django_spire/auth/user/apps.py +2 -0
- django_spire/auth/user/forms.py +9 -0
- django_spire/auth/user/models.py +1 -1
- django_spire/auth/user/services/services.py +1 -0
- django_spire/auth/user/tests/factories.py +14 -13
- django_spire/auth/user/tests/test_factories.py +166 -2
- django_spire/auth/user/tests/test_forms.py +573 -0
- django_spire/auth/user/tests/test_models.py +257 -0
- django_spire/auth/user/tests/test_services.py +200 -0
- django_spire/auth/user/tests/test_tools.py +153 -0
- django_spire/auth/user/tests/test_user_factories.py +139 -0
- django_spire/auth/user/tests/test_views.py +363 -0
- django_spire/auth/user/tools.py +7 -1
- django_spire/auth/user/urls/form_urls.py +3 -0
- django_spire/auth/user/urls/page_urls.py +3 -0
- django_spire/auth/user/views/form_views.py +19 -10
- django_spire/auth/user/views/page_views.py +8 -2
- django_spire/auth/views/redirect_views.py +14 -9
- django_spire/comment/admin.py +2 -0
- django_spire/comment/apps.py +2 -0
- django_spire/comment/templatetags/comment_tags.py +1 -0
- django_spire/comment/tests/test_forms.py +27 -0
- django_spire/comment/tests/test_models.py +215 -0
- django_spire/comment/tests/test_querysets.py +101 -0
- django_spire/comment/tests/test_utils.py +90 -0
- django_spire/comment/urls.py +2 -0
- django_spire/comment/utils.py +22 -13
- django_spire/comment/views.py +1 -1
- django_spire/conf.py +8 -6
- django_spire/consts.py +1 -1
- django_spire/contrib/breadcrumb/apps.py +2 -0
- django_spire/contrib/breadcrumb/breadcrumbs.py +18 -18
- django_spire/contrib/breadcrumb/tests/test_breadcrumbs.py +198 -0
- django_spire/contrib/constructor/__init__.py +3 -3
- django_spire/contrib/constructor/constructor.py +15 -15
- django_spire/contrib/constructor/django_model_constructor.py +5 -4
- django_spire/contrib/constructor/exceptions.py +5 -3
- django_spire/contrib/constructor/tests/__init__.py +0 -0
- django_spire/contrib/constructor/tests/test_constructor.py +193 -0
- django_spire/contrib/form/tests/__init__.py +0 -0
- django_spire/contrib/form/tests/test_forms.py +203 -0
- django_spire/contrib/generic_views/modal_views.py +2 -1
- django_spire/contrib/generic_views/portal_views.py +20 -19
- django_spire/contrib/generic_views/tests/__init__.py +0 -0
- django_spire/contrib/generic_views/tests/test_views.py +459 -0
- django_spire/contrib/help/apps.py +2 -0
- django_spire/contrib/help/templatetags/help.py +1 -0
- django_spire/contrib/help/tests/__init__.py +0 -0
- django_spire/contrib/help/tests/test_templatetags.py +100 -0
- django_spire/contrib/options/mixins.py +6 -5
- django_spire/contrib/options/tests/factories.py +5 -1
- django_spire/contrib/options/tests/test_options.py +234 -0
- django_spire/contrib/ordering/exceptions.py +7 -3
- django_spire/contrib/ordering/mixins.py +2 -0
- django_spire/contrib/ordering/querysets.py +3 -1
- django_spire/contrib/ordering/services/processor_service.py +8 -4
- django_spire/contrib/ordering/services/service.py +1 -2
- django_spire/contrib/ordering/tests/__init__.py +0 -0
- django_spire/contrib/ordering/tests/test_ordering.py +165 -0
- django_spire/contrib/ordering/validators.py +6 -6
- django_spire/contrib/pagination/templatetags/pagination_tags.py +12 -5
- django_spire/contrib/pagination/tests/__init__.py +0 -0
- django_spire/contrib/pagination/tests/test_pagination.py +179 -0
- django_spire/contrib/performance/decorators.py +16 -6
- django_spire/contrib/performance/tests/__init__.py +0 -0
- django_spire/contrib/performance/tests/test_performance.py +107 -0
- django_spire/contrib/progress/__init__.py +1 -3
- django_spire/contrib/progress/static/django_spire/js/contrib/progress/progress.js +38 -82
- django_spire/contrib/queryset/enums.py +3 -1
- django_spire/contrib/queryset/filter_tools.py +10 -5
- django_spire/contrib/queryset/mixins.py +16 -16
- django_spire/contrib/queryset/tests/__init__.py +0 -0
- django_spire/contrib/queryset/tests/test_queryset.py +137 -0
- django_spire/contrib/seeding/field/base.py +13 -7
- django_spire/contrib/seeding/field/callable.py +8 -1
- django_spire/contrib/seeding/field/cleaners.py +5 -5
- django_spire/contrib/seeding/field/custom.py +20 -10
- django_spire/contrib/seeding/field/django/seeder.py +8 -6
- django_spire/contrib/seeding/field/enums.py +7 -5
- django_spire/contrib/seeding/field/override.py +16 -6
- django_spire/contrib/seeding/field/static.py +9 -2
- django_spire/contrib/seeding/field/tests/test_base.py +18 -14
- django_spire/contrib/seeding/field/tests/test_callable.py +13 -9
- django_spire/contrib/seeding/field/tests/test_cleaners.py +51 -38
- django_spire/contrib/seeding/field/tests/test_static.py +13 -9
- django_spire/contrib/seeding/intelligence/bots/seeder_generator_bot.py +2 -0
- django_spire/contrib/seeding/intelligence/intel.py +5 -1
- django_spire/contrib/seeding/intelligence/prompts/factory.py +6 -1
- django_spire/contrib/seeding/intelligence/prompts/foreign_key_selection_prompt.py +6 -1
- django_spire/contrib/seeding/intelligence/prompts/generate_django_model_seeder_prompts.py +2 -0
- django_spire/contrib/seeding/intelligence/prompts/generic_relationship_selection_prompt.py +7 -1
- django_spire/contrib/seeding/intelligence/prompts/hierarchical_selection_prompt.py +6 -2
- django_spire/contrib/seeding/intelligence/prompts/model_field_choices_prompt.py +8 -2
- django_spire/contrib/seeding/intelligence/prompts/negation_prompt.py +2 -0
- django_spire/contrib/seeding/intelligence/prompts/objective_prompt.py +6 -1
- django_spire/contrib/seeding/management/commands/seeding.py +9 -3
- django_spire/contrib/seeding/management/example.py +2 -0
- django_spire/contrib/seeding/model/base.py +16 -7
- django_spire/contrib/seeding/model/config.py +31 -15
- django_spire/contrib/seeding/model/django/config.py +13 -13
- django_spire/contrib/seeding/model/django/seeder.py +4 -4
- django_spire/contrib/seeding/model/django/tests/test_seeder.py +34 -23
- django_spire/contrib/seeding/model/enums.py +2 -0
- django_spire/contrib/seeding/tests/test_config.py +71 -0
- django_spire/contrib/seeding/tests/test_custom.py +35 -0
- django_spire/contrib/seeding/tests/test_enums.py +40 -0
- django_spire/contrib/seeding/tests/test_intel.py +32 -0
- django_spire/contrib/seeding/tests/test_override.py +63 -0
- django_spire/contrib/service/__init__.py +2 -2
- django_spire/contrib/service/django_model_service.py +16 -15
- django_spire/contrib/service/exceptions.py +5 -3
- django_spire/contrib/service/tests/__init__.py +0 -0
- django_spire/contrib/service/tests/test_service.py +153 -0
- django_spire/contrib/session/apps.py +2 -0
- django_spire/contrib/session/controller.py +48 -42
- django_spire/contrib/session/templatetags/session_tags.py +11 -2
- django_spire/contrib/session/tests/test_session_controller.py +117 -53
- django_spire/contrib/tests/__init__.py +0 -0
- django_spire/contrib/tests/test_utils.py +37 -0
- django_spire/contrib/utils.py +4 -1
- django_spire/core/apps.py +2 -0
- django_spire/core/converters/tests/test_to_data.py +353 -0
- django_spire/core/converters/tests/test_to_enums.py +61 -41
- django_spire/core/converters/tests/test_to_pydantic.py +138 -109
- django_spire/core/converters/to_data.py +29 -10
- django_spire/core/converters/to_enums.py +4 -2
- django_spire/core/converters/to_pydantic.py +22 -22
- django_spire/core/decorators.py +19 -6
- django_spire/core/forms/widgets.py +4 -0
- django_spire/core/maps.py +3 -1
- django_spire/core/middleware/maintenance.py +3 -3
- django_spire/core/middleware.py +8 -6
- django_spire/core/redirect/__init__.py +5 -0
- django_spire/core/redirect/generic_redirect.py +1 -2
- django_spire/core/redirect/tests/__init__.py +0 -0
- django_spire/core/redirect/tests/test_generic_redirect.py +34 -0
- django_spire/core/{tests/tests_redirect.py → redirect/tests/test_safe_redirect.py} +55 -81
- django_spire/core/shortcuts.py +3 -3
- django_spire/core/static/django_spire/css/app-layout.css +1 -1
- django_spire/core/static/django_spire/css/app-navigation.css +3 -3
- django_spire/core/static/django_spire/css/bootstrap-override.css +4 -0
- django_spire/core/tag/admin.py +12 -0
- django_spire/core/tag/intelligence/tag_set_bot.py +2 -0
- django_spire/core/tag/mixins.py +2 -0
- django_spire/core/tag/models.py +2 -0
- django_spire/core/tag/querysets.py +2 -0
- django_spire/core/tag/service/tag_service.py +6 -3
- django_spire/core/tag/tests/test_intelligence.py +9 -9
- django_spire/core/tag/tests/test_tags.py +44 -54
- django_spire/core/tag/tests/test_tools.py +191 -0
- django_spire/core/tag/tools.py +3 -0
- django_spire/core/templates/django_spire/card/card.html +5 -2
- django_spire/core/templates/django_spire/card/title_card.html +9 -4
- django_spire/core/templates/django_spire/infinite_scroll/base.html +1 -0
- django_spire/core/templates/django_spire/navigation/side_navigation.html +19 -24
- django_spire/core/templates/django_spire/page/full_page.html +46 -16
- django_spire/core/templates/django_spire/table/base.html +4 -2
- django_spire/core/templatetags/json.py +6 -2
- django_spire/core/templatetags/message.py +13 -8
- django_spire/core/templatetags/string_formating.py +8 -5
- django_spire/core/templatetags/tests/__init__.py +0 -0
- django_spire/core/templatetags/tests/test_templatetags.py +427 -0
- django_spire/core/templatetags/variable_types.py +17 -9
- django_spire/core/tests/test_cases.py +1 -1
- django_spire/core/tests/test_conf.py +43 -0
- django_spire/core/tests/test_consts.py +28 -0
- django_spire/core/tests/test_context_processors.py +93 -0
- django_spire/core/tests/test_decorators.py +95 -0
- django_spire/core/tests/test_django_spire_utils.py +56 -0
- django_spire/core/tests/test_exceptions.py +37 -0
- django_spire/core/tests/test_models.py +54 -0
- django_spire/core/tests/test_settings.py +45 -0
- django_spire/core/tests/test_shortcuts.py +74 -0
- django_spire/core/tests/test_urls.py +16 -0
- django_spire/core/tests/test_utils.py +58 -0
- django_spire/core/urls.py +4 -1
- django_spire/core/utils.py +12 -8
- django_spire/exceptions.py +16 -1
- django_spire/file/admin.py +4 -2
- django_spire/file/apps.py +8 -10
- django_spire/file/fields.py +7 -7
- django_spire/file/forms.py +1 -1
- django_spire/file/interfaces.py +15 -15
- django_spire/file/mixins.py +1 -4
- django_spire/file/models.py +3 -5
- django_spire/file/tests/factories.py +59 -0
- django_spire/file/tests/test_admin.py +69 -0
- django_spire/file/tests/test_apps.py +24 -0
- django_spire/file/tests/test_fields.py +114 -0
- django_spire/file/tests/test_forms.py +20 -0
- django_spire/file/tests/test_interfaces.py +183 -0
- django_spire/file/tests/test_models.py +82 -0
- django_spire/file/tests/test_querysets.py +102 -0
- django_spire/file/tests/test_utils.py +32 -0
- django_spire/file/tests/test_views.py +145 -0
- django_spire/file/tests/test_widgets.py +82 -0
- django_spire/file/tools.py +8 -2
- django_spire/file/views.py +7 -3
- django_spire/file/widgets.py +12 -12
- django_spire/help_desk/admin.py +15 -0
- django_spire/help_desk/apps.py +2 -0
- django_spire/help_desk/auth/controller.py +2 -0
- django_spire/help_desk/choices.py +2 -0
- django_spire/help_desk/enums.py +2 -0
- django_spire/help_desk/exceptions.py +31 -3
- django_spire/help_desk/forms.py +2 -0
- django_spire/help_desk/models.py +2 -0
- django_spire/help_desk/querysets.py +4 -1
- django_spire/help_desk/services/notification_service.py +26 -27
- django_spire/help_desk/services/service.py +2 -3
- django_spire/help_desk/tests/factories.py +8 -3
- django_spire/help_desk/tests/test_admin.py +41 -0
- django_spire/help_desk/tests/test_apps.py +41 -0
- django_spire/help_desk/tests/test_choices.py +50 -0
- django_spire/help_desk/tests/test_controller.py +87 -0
- django_spire/help_desk/tests/test_enums.py +18 -0
- django_spire/help_desk/tests/test_exceptions.py +37 -0
- django_spire/help_desk/tests/test_forms.py +89 -0
- django_spire/help_desk/tests/test_models.py +59 -0
- django_spire/help_desk/tests/test_querysets.py +38 -0
- django_spire/help_desk/tests/test_services/test_notification_service.py +15 -8
- django_spire/help_desk/tests/test_services/test_service.py +92 -0
- django_spire/help_desk/tests/test_urls/test_form_urls.py +6 -6
- django_spire/help_desk/tests/test_urls/test_page_urls.py +8 -9
- django_spire/help_desk/tests/test_views/test_form_views.py +46 -19
- django_spire/help_desk/tests/test_views/test_page_views.py +32 -9
- django_spire/help_desk/urls/__init__.py +4 -1
- django_spire/help_desk/urls/form_urls.py +3 -0
- django_spire/help_desk/urls/page_urls.py +3 -0
- django_spire/help_desk/views/form_views.py +13 -5
- django_spire/help_desk/views/page_views.py +11 -3
- django_spire/history/activity/admin.py +2 -0
- django_spire/history/activity/apps.py +3 -1
- django_spire/history/activity/mixins.py +13 -7
- django_spire/history/activity/models.py +6 -5
- django_spire/history/activity/querysets.py +2 -0
- django_spire/history/activity/tests/__init__.py +0 -0
- django_spire/history/activity/tests/test_activity.py +176 -0
- django_spire/history/admin.py +9 -2
- django_spire/history/choices.py +3 -0
- django_spire/history/models.py +5 -5
- django_spire/history/tests/test_admin.py +93 -0
- django_spire/history/tests/test_history.py +101 -0
- django_spire/history/tests/test_mixins.py +84 -0
- django_spire/history/viewed/admin.py +3 -1
- django_spire/history/viewed/apps.py +3 -1
- django_spire/history/viewed/models.py +2 -0
- django_spire/history/viewed/tests/__init__.py +0 -0
- django_spire/history/viewed/tests/test_viewed.py +46 -0
- django_spire/knowledge/auth/tests/__init__.py +0 -0
- django_spire/knowledge/auth/tests/test_controller.py +116 -0
- django_spire/knowledge/collection/admin.py +5 -1
- django_spire/knowledge/collection/models.py +3 -1
- django_spire/knowledge/collection/seeding/seed.py +1 -0
- django_spire/knowledge/collection/services/factory_service.py +10 -11
- django_spire/knowledge/collection/services/ordering_service.py +1 -2
- django_spire/knowledge/collection/services/service.py +5 -10
- django_spire/knowledge/collection/services/tag_service.py +5 -2
- django_spire/knowledge/collection/tests/factories.py +28 -1
- django_spire/knowledge/collection/tests/test_models.py +48 -0
- django_spire/knowledge/collection/tests/test_querysets.py +93 -0
- django_spire/knowledge/collection/tests/test_services/test_factory_service.py +100 -0
- django_spire/knowledge/collection/tests/test_services/test_services.py +160 -0
- django_spire/knowledge/collection/tests/test_urls/test_form_urls.py +21 -3
- django_spire/knowledge/collection/tests/test_urls/test_json_urls.py +39 -1
- django_spire/knowledge/collection/tests/test_urls/test_page_urls.py +12 -4
- django_spire/knowledge/collection/urls/__init__.py +3 -0
- django_spire/knowledge/collection/urls/form_urls.py +2 -0
- django_spire/knowledge/collection/urls/json_urls.py +2 -0
- django_spire/knowledge/collection/urls/page_urls.py +2 -0
- django_spire/knowledge/collection/views/form_views.py +4 -4
- django_spire/knowledge/collection/views/json_views.py +5 -1
- django_spire/knowledge/collection/views/page_views.py +5 -2
- django_spire/knowledge/entry/admin.py +7 -1
- django_spire/knowledge/entry/forms.py +2 -0
- django_spire/knowledge/entry/models.py +2 -0
- django_spire/knowledge/entry/seeding/seed.py +3 -0
- django_spire/knowledge/entry/services/automation_service.py +5 -4
- django_spire/knowledge/entry/services/factory_service.py +7 -5
- django_spire/knowledge/entry/services/service.py +4 -7
- django_spire/knowledge/entry/services/tag_service.py +0 -1
- django_spire/knowledge/entry/services/tool_service.py +1 -0
- django_spire/knowledge/entry/services/transformation_services.py +1 -5
- django_spire/knowledge/entry/tests/factories.py +1 -2
- django_spire/knowledge/entry/tests/test_factory_service.py +20 -0
- django_spire/knowledge/entry/tests/test_models.py +41 -0
- django_spire/knowledge/entry/tests/test_querysets.py +71 -0
- django_spire/knowledge/entry/tests/test_services.py +94 -0
- django_spire/knowledge/entry/tests/test_urls/test_form_urls.py +9 -14
- django_spire/knowledge/entry/tests/test_urls/test_json_urls.py +48 -5
- django_spire/knowledge/entry/tests/test_urls/test_page_urls.py +6 -8
- django_spire/knowledge/entry/tests/test_urls/test_template_urls.py +40 -0
- django_spire/knowledge/entry/urls/form_urls.py +2 -0
- django_spire/knowledge/entry/urls/json_urls.py +2 -0
- django_spire/knowledge/entry/urls/page_urls.py +2 -0
- django_spire/knowledge/entry/urls/template_urls.py +2 -0
- django_spire/knowledge/entry/version/block/choices.py +2 -0
- django_spire/knowledge/entry/version/block/data/data.py +1 -0
- django_spire/knowledge/entry/version/block/data/list/data.py +8 -13
- django_spire/knowledge/entry/version/block/data/list/maps.py +3 -0
- django_spire/knowledge/entry/version/block/data/list/meta.py +1 -2
- django_spire/knowledge/entry/version/block/data/list/tests/__init__.py +0 -0
- django_spire/knowledge/entry/version/block/data/list/tests/test_maps.py +32 -0
- django_spire/knowledge/entry/version/block/data/list/tests/test_meta.py +58 -0
- django_spire/knowledge/entry/version/block/data/maps.py +3 -6
- django_spire/knowledge/entry/version/block/models.py +7 -5
- django_spire/knowledge/entry/version/block/seeding/constants.py +5 -4
- django_spire/knowledge/entry/version/block/services/service.py +2 -3
- django_spire/knowledge/entry/version/block/tests/factories.py +4 -10
- django_spire/knowledge/entry/version/block/tests/test_choices.py +56 -0
- django_spire/knowledge/entry/version/block/tests/test_data.py +90 -0
- django_spire/knowledge/entry/version/block/tests/test_maps.py +37 -0
- django_spire/knowledge/entry/version/block/tests/test_models.py +55 -0
- django_spire/knowledge/entry/version/block/tests/test_querysets.py +35 -0
- django_spire/knowledge/entry/version/block/tests/test_services.py +65 -0
- django_spire/knowledge/entry/version/choices.py +2 -0
- django_spire/knowledge/entry/version/converters/converter.py +1 -1
- django_spire/knowledge/entry/version/converters/docx_converter.py +4 -7
- django_spire/knowledge/entry/version/converters/markdown_converter.py +20 -20
- django_spire/knowledge/entry/version/maps.py +4 -5
- django_spire/knowledge/entry/version/querysets.py +1 -1
- django_spire/knowledge/entry/version/seeding/seeder.py +1 -2
- django_spire/knowledge/entry/version/services/processor_service.py +5 -4
- django_spire/knowledge/entry/version/services/service.py +1 -2
- django_spire/knowledge/entry/version/tests/factories.py +2 -2
- django_spire/knowledge/entry/version/tests/test_choices.py +18 -0
- django_spire/knowledge/entry/version/tests/test_converters/test_docx_converter.py +56 -8
- django_spire/knowledge/entry/version/tests/test_converters/test_markdown_converter.py +78 -0
- django_spire/knowledge/entry/version/tests/test_maps.py +58 -0
- django_spire/knowledge/entry/version/tests/test_models.py +23 -0
- django_spire/knowledge/entry/version/tests/test_querysets.py +26 -0
- django_spire/knowledge/entry/version/tests/test_services.py +62 -0
- django_spire/knowledge/entry/version/tests/test_urls/test_json_urls.py +27 -8
- django_spire/knowledge/entry/version/tests/test_urls/test_page_urls.py +15 -8
- django_spire/knowledge/entry/version/tests/test_urls/test_redirect_urls.py +38 -0
- django_spire/knowledge/entry/version/urls/__init__.py +3 -0
- django_spire/knowledge/entry/version/urls/json_urls.py +2 -1
- django_spire/knowledge/entry/version/urls/page_urls.py +2 -0
- django_spire/knowledge/entry/version/urls/redirect_urls.py +2 -0
- django_spire/knowledge/entry/version/views/json_views.py +5 -1
- django_spire/knowledge/entry/version/views/page_views.py +10 -3
- django_spire/knowledge/entry/version/views/redirect_views.py +5 -1
- django_spire/knowledge/entry/views/form_views.py +16 -8
- django_spire/knowledge/entry/views/json_views.py +3 -1
- django_spire/knowledge/entry/views/page_views.py +8 -2
- django_spire/knowledge/entry/views/template_views.py +7 -1
- django_spire/knowledge/exceptions.py +2 -1
- django_spire/knowledge/intelligence/intel/answer_intel.py +2 -1
- django_spire/knowledge/intelligence/intel/entry_intel.py +0 -1
- django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +4 -5
- django_spire/knowledge/models.py +1 -2
- django_spire/knowledge/tests/__init__.py +0 -0
- django_spire/knowledge/tests/test_templatetags.py +40 -0
- django_spire/knowledge/tests/test_urls/__init__.py +0 -0
- django_spire/knowledge/tests/test_urls/test_page_urls.py +24 -0
- django_spire/knowledge/urls/__init__.py +2 -0
- django_spire/knowledge/urls/page_urls.py +2 -0
- django_spire/knowledge/views/page_views.py +8 -3
- django_spire/notification/admin.py +3 -1
- django_spire/notification/app/admin.py +2 -0
- django_spire/notification/app/apps.py +3 -1
- django_spire/notification/app/exceptions.py +9 -2
- django_spire/notification/app/models.py +8 -4
- django_spire/notification/app/processor.py +22 -26
- django_spire/notification/app/querysets.py +2 -0
- django_spire/notification/app/tests/__init__.py +0 -0
- django_spire/notification/app/tests/factories.py +34 -0
- django_spire/notification/app/tests/test_apps.py +24 -0
- django_spire/notification/app/tests/test_models.py +72 -0
- django_spire/notification/app/tests/test_processor.py +111 -0
- django_spire/notification/app/tests/test_querysets.py +90 -0
- django_spire/notification/app/tests/test_views/__init__.py +0 -0
- django_spire/notification/app/tests/test_views/test_json_views.py +48 -0
- django_spire/notification/app/tests/test_views/test_page_views.py +19 -0
- django_spire/notification/app/urls/__init__.py +3 -1
- django_spire/notification/app/urls/json_urls.py +6 -4
- django_spire/notification/app/urls/page_urls.py +4 -3
- django_spire/notification/app/urls/template_urls.py +4 -2
- django_spire/notification/apps.py +4 -1
- django_spire/notification/email/admin.py +5 -1
- django_spire/notification/email/apps.py +3 -1
- django_spire/notification/email/exceptions.py +4 -2
- django_spire/notification/email/helper.py +5 -3
- django_spire/notification/email/models.py +4 -0
- django_spire/notification/email/processor.py +19 -15
- django_spire/notification/email/querysets.py +3 -0
- django_spire/notification/email/tests/__init__.py +0 -0
- django_spire/notification/email/tests/factories.py +35 -0
- django_spire/notification/email/tests/test_apps.py +24 -0
- django_spire/notification/email/tests/test_models.py +52 -0
- django_spire/notification/email/tests/test_processor.py +92 -0
- django_spire/notification/email/tests/test_querysets.py +43 -0
- django_spire/notification/exceptions.py +17 -2
- django_spire/notification/managers.py +7 -1
- django_spire/notification/maps.py +4 -1
- django_spire/notification/mixins.py +2 -0
- django_spire/notification/models.py +3 -1
- django_spire/notification/processors/notification.py +12 -5
- django_spire/notification/processors/processor.py +2 -0
- django_spire/notification/processors/tests/__init__.py +0 -0
- django_spire/notification/processors/tests/test_notification.py +106 -0
- django_spire/notification/push/admin.py +10 -1
- django_spire/notification/push/apps.py +3 -1
- django_spire/notification/push/models.py +2 -3
- django_spire/notification/push/tests/__init__.py +0 -0
- django_spire/notification/push/tests/test_apps.py +24 -0
- django_spire/notification/push/tests/test_models.py +28 -0
- django_spire/notification/querysets.py +7 -1
- django_spire/notification/sms/admin.py +2 -0
- django_spire/notification/sms/apps.py +4 -1
- django_spire/notification/sms/automations.py +2 -0
- django_spire/notification/sms/choices.py +2 -0
- django_spire/notification/sms/exceptions.py +19 -5
- django_spire/notification/sms/helper.py +33 -23
- django_spire/notification/sms/models.py +5 -1
- django_spire/notification/sms/processor.py +20 -20
- django_spire/notification/sms/querysets.py +2 -0
- django_spire/notification/sms/tests/factories.py +33 -0
- django_spire/notification/sms/tests/test_apps.py +24 -0
- django_spire/notification/sms/tests/test_automation.py +38 -0
- django_spire/notification/sms/tests/test_choices.py +15 -0
- django_spire/notification/sms/tests/test_consts.py +17 -0
- django_spire/notification/sms/tests/test_exceptions.py +27 -0
- django_spire/notification/sms/tests/test_helper.py +50 -0
- django_spire/notification/sms/tests/test_models.py +81 -0
- django_spire/notification/sms/tests/test_processor.py +107 -0
- django_spire/notification/sms/tests/test_tools.py +25 -11
- django_spire/notification/sms/tools.py +16 -5
- django_spire/notification/sms/urls/__init__.py +3 -1
- django_spire/notification/sms/urls/media_urls.py +2 -0
- django_spire/notification/sms/views/media_views.py +14 -4
- django_spire/notification/tests/__init__.py +0 -0
- django_spire/notification/tests/factories.py +26 -0
- django_spire/notification/tests/test_admin.py +55 -0
- django_spire/notification/tests/test_apps.py +30 -0
- django_spire/notification/tests/test_automation.py +18 -0
- django_spire/notification/tests/test_choices.py +59 -0
- django_spire/notification/tests/test_exceptions.py +58 -0
- django_spire/notification/tests/test_managers.py +100 -0
- django_spire/notification/tests/test_maps.py +31 -0
- django_spire/notification/tests/test_models.py +76 -0
- django_spire/notification/tests/test_querysets.py +184 -0
- django_spire/notification/tests/test_utils.py +23 -0
- django_spire/notification/urls.py +3 -1
- django_spire/notification/utils.py +3 -1
- django_spire/settings.py +3 -0
- django_spire/theme/tests/test_context_processor.py +15 -13
- django_spire/theme/tests/test_enums.py +2 -2
- django_spire/theme/tests/test_filesystem.py +2 -5
- django_spire/theme/tests/test_integration.py +12 -12
- django_spire/theme/tests/test_model.py +40 -38
- django_spire/theme/tests/test_views/test_json_views.py +33 -33
- django_spire/theme/urls/json_urls.py +3 -0
- django_spire/theme/urls/page_urls.py +3 -0
- django_spire/urls.py +19 -15
- django_spire/utils.py +13 -4
- {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/METADATA +1 -1
- {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/RECORD +532 -361
- django_spire/contrib/options/tests/test_unit.py +0 -148
- django_spire/contrib/progress/views.py +0 -64
- django_spire/contrib/seeding/tests/test_seeding.py +0 -25
- django_spire/core/tests/test_templatetags.py +0 -117
- django_spire/core/tests/tests_shortcuts.py +0 -73
- django_spire/history/activity/tests.py +0 -3
- django_spire/history/activity/views.py +0 -3
- django_spire/knowledge/collection/tests/test_services/test_transformation_service.py +0 -71
- django_spire/notification/app/tests.py +0 -3
- {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/WHEEL +0 -0
- {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/licenses/LICENSE.md +0 -0
- {django_spire-0.23.6.dist-info → django_spire-0.23.8.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from django.db import IntegrityError
|
|
6
|
+
|
|
7
|
+
from django_spire.auth.group.models import AuthGroup
|
|
8
|
+
from django_spire.auth.user.models import AuthUser
|
|
9
|
+
from django_spire.auth.user.tests.factories import create_user
|
|
10
|
+
from django_spire.core.tests.test_cases import BaseTestCase
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AuthUserModelTestCase(BaseTestCase):
|
|
14
|
+
def setUp(self) -> None:
|
|
15
|
+
super().setUp()
|
|
16
|
+
|
|
17
|
+
self.user = create_user(
|
|
18
|
+
username='testuser',
|
|
19
|
+
first_name='Test',
|
|
20
|
+
last_name='User',
|
|
21
|
+
email='test@example.com'
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
def test_meta_proxy(self) -> None:
|
|
25
|
+
assert AuthUser._meta.proxy
|
|
26
|
+
|
|
27
|
+
def test_meta_verbose_name(self) -> None:
|
|
28
|
+
assert AuthUser._meta.verbose_name == 'Auth User'
|
|
29
|
+
assert AuthUser._meta.verbose_name_plural == 'Auth Users'
|
|
30
|
+
|
|
31
|
+
def test_get_full_name(self) -> None:
|
|
32
|
+
assert self.user.get_full_name() == 'Test User'
|
|
33
|
+
|
|
34
|
+
def test_get_full_name_first_only(self) -> None:
|
|
35
|
+
user = create_user(username='firstonly', first_name='First', last_name='')
|
|
36
|
+
assert 'First' in user.get_full_name()
|
|
37
|
+
|
|
38
|
+
def test_get_full_name_last_only(self) -> None:
|
|
39
|
+
user = create_user(username='lastonly', first_name='', last_name='Last')
|
|
40
|
+
assert 'Last' in user.get_full_name()
|
|
41
|
+
|
|
42
|
+
def test_get_full_name_empty(self) -> None:
|
|
43
|
+
user = create_user(username='noname', first_name='', last_name='')
|
|
44
|
+
assert user.get_full_name() == ''
|
|
45
|
+
|
|
46
|
+
def test_services_class_attribute(self) -> None:
|
|
47
|
+
assert AuthUser.services is not None
|
|
48
|
+
|
|
49
|
+
def test_user_creation(self) -> None:
|
|
50
|
+
user = create_user(
|
|
51
|
+
username='newuser',
|
|
52
|
+
first_name='New',
|
|
53
|
+
last_name='User',
|
|
54
|
+
email='new@example.com'
|
|
55
|
+
)
|
|
56
|
+
assert user.pk is not None
|
|
57
|
+
assert AuthUser.objects.filter(pk=user.pk).exists()
|
|
58
|
+
|
|
59
|
+
def test_user_update(self) -> None:
|
|
60
|
+
self.user.first_name = 'Updated'
|
|
61
|
+
self.user.save()
|
|
62
|
+
self.user.refresh_from_db()
|
|
63
|
+
assert self.user.first_name == 'Updated'
|
|
64
|
+
|
|
65
|
+
def test_user_deletion(self) -> None:
|
|
66
|
+
user_pk = self.user.pk
|
|
67
|
+
self.user.delete()
|
|
68
|
+
assert not AuthUser.objects.filter(pk=user_pk).exists()
|
|
69
|
+
|
|
70
|
+
def test_user_email(self) -> None:
|
|
71
|
+
assert self.user.email == 'test@example.com'
|
|
72
|
+
|
|
73
|
+
def test_user_is_active_default(self) -> None:
|
|
74
|
+
user = create_user(username='activeuser')
|
|
75
|
+
assert user.is_active
|
|
76
|
+
|
|
77
|
+
def test_user_is_active_false(self) -> None:
|
|
78
|
+
user = create_user(username='inactiveuser', is_active=False)
|
|
79
|
+
assert not user.is_active
|
|
80
|
+
|
|
81
|
+
def test_user_groups_relationship(self) -> None:
|
|
82
|
+
group = AuthGroup.objects.create(name='Test Group')
|
|
83
|
+
self.user.groups.add(group)
|
|
84
|
+
assert group in self.user.groups.all()
|
|
85
|
+
|
|
86
|
+
def test_user_multiple_groups(self) -> None:
|
|
87
|
+
group1 = AuthGroup.objects.create(name='Group 1')
|
|
88
|
+
group2 = AuthGroup.objects.create(name='Group 2')
|
|
89
|
+
self.user.groups.add(group1, group2)
|
|
90
|
+
assert self.user.groups.count() == 2
|
|
91
|
+
|
|
92
|
+
def test_user_remove_from_group(self) -> None:
|
|
93
|
+
group = AuthGroup.objects.create(name='Test Group')
|
|
94
|
+
self.user.groups.add(group)
|
|
95
|
+
self.user.groups.remove(group)
|
|
96
|
+
assert group not in self.user.groups.all()
|
|
97
|
+
|
|
98
|
+
def test_user_clear_groups(self) -> None:
|
|
99
|
+
group1 = AuthGroup.objects.create(name='Group 1')
|
|
100
|
+
group2 = AuthGroup.objects.create(name='Group 2')
|
|
101
|
+
self.user.groups.add(group1, group2)
|
|
102
|
+
self.user.groups.clear()
|
|
103
|
+
assert self.user.groups.count() == 0
|
|
104
|
+
|
|
105
|
+
def test_user_username_unique(self) -> None:
|
|
106
|
+
with pytest.raises(IntegrityError):
|
|
107
|
+
AuthUser.objects.create(username='testuser')
|
|
108
|
+
|
|
109
|
+
def test_user_password_hashing(self) -> None:
|
|
110
|
+
user = create_user(username='passworduser')
|
|
111
|
+
user.set_password('testpassword123')
|
|
112
|
+
user.save()
|
|
113
|
+
assert user.check_password('testpassword123')
|
|
114
|
+
assert not user.check_password('wrongpassword')
|
|
115
|
+
|
|
116
|
+
def test_user_last_login_initially_none(self) -> None:
|
|
117
|
+
user = create_user(username='nologinuser')
|
|
118
|
+
assert user.last_login is None
|
|
119
|
+
|
|
120
|
+
def test_breadcrumbs_returns_breadcrumbs(self) -> None:
|
|
121
|
+
crumbs = self.user.breadcrumbs()
|
|
122
|
+
assert crumbs is not None
|
|
123
|
+
|
|
124
|
+
def test_breadcrumbs_without_pk(self) -> None:
|
|
125
|
+
unsaved_user = AuthUser(username='unsaved', first_name='Unsaved')
|
|
126
|
+
crumbs = unsaved_user.breadcrumbs()
|
|
127
|
+
assert crumbs is not None
|
|
128
|
+
|
|
129
|
+
def test_breadcrumbs_contains_user_name(self) -> None:
|
|
130
|
+
crumbs = self.user.breadcrumbs()
|
|
131
|
+
names = [c.name for c in crumbs.data]
|
|
132
|
+
assert 'Test User' in names
|
|
133
|
+
|
|
134
|
+
def test_base_breadcrumb_returns_breadcrumbs(self) -> None:
|
|
135
|
+
crumbs = AuthUser.base_breadcrumb()
|
|
136
|
+
assert crumbs is not None
|
|
137
|
+
|
|
138
|
+
def test_base_breadcrumb_contains_users(self) -> None:
|
|
139
|
+
crumbs = AuthUser.base_breadcrumb()
|
|
140
|
+
names = [c.name for c in crumbs.data]
|
|
141
|
+
assert 'Users' in names
|
|
142
|
+
|
|
143
|
+
def test_user_inheritance_from_django_user(self) -> None:
|
|
144
|
+
from django.contrib.auth.models import User
|
|
145
|
+
assert issubclass(AuthUser, User)
|
|
146
|
+
|
|
147
|
+
def test_activity_mixin(self) -> None:
|
|
148
|
+
assert hasattr(self.user, 'add_activity')
|
|
149
|
+
|
|
150
|
+
def test_user_get_short_name(self) -> None:
|
|
151
|
+
assert self.user.get_short_name() == 'Test'
|
|
152
|
+
|
|
153
|
+
def test_user_str_representation(self) -> None:
|
|
154
|
+
assert str(self.user) == 'testuser'
|
|
155
|
+
|
|
156
|
+
def test_user_email_user(self) -> None:
|
|
157
|
+
assert hasattr(self.user, 'email_user')
|
|
158
|
+
|
|
159
|
+
def test_user_has_perm(self) -> None:
|
|
160
|
+
assert hasattr(self.user, 'has_perm')
|
|
161
|
+
|
|
162
|
+
def test_user_has_perms(self) -> None:
|
|
163
|
+
assert hasattr(self.user, 'has_perms')
|
|
164
|
+
|
|
165
|
+
def test_user_has_module_perms(self) -> None:
|
|
166
|
+
assert hasattr(self.user, 'has_module_perms')
|
|
167
|
+
|
|
168
|
+
def test_user_is_anonymous(self) -> None:
|
|
169
|
+
assert not self.user.is_anonymous
|
|
170
|
+
|
|
171
|
+
def test_user_is_authenticated(self) -> None:
|
|
172
|
+
assert self.user.is_authenticated
|
|
173
|
+
|
|
174
|
+
def test_user_date_joined(self) -> None:
|
|
175
|
+
assert self.user.date_joined is not None
|
|
176
|
+
|
|
177
|
+
def test_user_username_max_length(self) -> None:
|
|
178
|
+
max_length = AuthUser._meta.get_field('username').max_length
|
|
179
|
+
assert max_length == 150
|
|
180
|
+
|
|
181
|
+
def test_user_first_name_max_length(self) -> None:
|
|
182
|
+
max_length = AuthUser._meta.get_field('first_name').max_length
|
|
183
|
+
assert max_length == 150
|
|
184
|
+
|
|
185
|
+
def test_user_last_name_max_length(self) -> None:
|
|
186
|
+
max_length = AuthUser._meta.get_field('last_name').max_length
|
|
187
|
+
assert max_length == 150
|
|
188
|
+
|
|
189
|
+
def test_user_email_max_length(self) -> None:
|
|
190
|
+
max_length = AuthUser._meta.get_field('email').max_length
|
|
191
|
+
assert max_length == 254
|
|
192
|
+
|
|
193
|
+
def test_user_with_unicode_names(self) -> None:
|
|
194
|
+
user = create_user(
|
|
195
|
+
username='unicodeuser',
|
|
196
|
+
first_name='Tëst',
|
|
197
|
+
last_name='Üsér'
|
|
198
|
+
)
|
|
199
|
+
assert user.first_name == 'Tëst'
|
|
200
|
+
assert user.last_name == 'Üsér'
|
|
201
|
+
assert 'Tëst' in user.get_full_name()
|
|
202
|
+
|
|
203
|
+
def test_user_refresh_from_db(self) -> None:
|
|
204
|
+
original_name = self.user.first_name
|
|
205
|
+
AuthUser.objects.filter(pk=self.user.pk).update(first_name='Changed')
|
|
206
|
+
self.user.refresh_from_db()
|
|
207
|
+
assert self.user.first_name == 'Changed'
|
|
208
|
+
assert self.user.first_name != original_name
|
|
209
|
+
|
|
210
|
+
def test_user_get_or_create(self) -> None:
|
|
211
|
+
user, created = AuthUser.objects.get_or_create(
|
|
212
|
+
username='getorcreate',
|
|
213
|
+
defaults={'first_name': 'Get', 'last_name': 'OrCreate'}
|
|
214
|
+
)
|
|
215
|
+
assert created
|
|
216
|
+
user2, created2 = AuthUser.objects.get_or_create(
|
|
217
|
+
username='getorcreate',
|
|
218
|
+
defaults={'first_name': 'Different', 'last_name': 'Name'}
|
|
219
|
+
)
|
|
220
|
+
assert not created2
|
|
221
|
+
assert user.pk == user2.pk
|
|
222
|
+
|
|
223
|
+
def test_user_exists(self) -> None:
|
|
224
|
+
assert AuthUser.objects.filter(username='testuser').exists()
|
|
225
|
+
assert not AuthUser.objects.filter(username='nonexistent').exists()
|
|
226
|
+
|
|
227
|
+
def test_user_count(self) -> None:
|
|
228
|
+
initial_count = AuthUser.objects.count()
|
|
229
|
+
create_user(username='countuser1')
|
|
230
|
+
create_user(username='countuser2')
|
|
231
|
+
assert AuthUser.objects.count() == initial_count + 2
|
|
232
|
+
|
|
233
|
+
def test_user_filter_by_is_active(self) -> None:
|
|
234
|
+
create_user(username='active1', is_active=True)
|
|
235
|
+
create_user(username='inactive1', is_active=False)
|
|
236
|
+
active_users = AuthUser.objects.filter(is_active=True)
|
|
237
|
+
inactive_users = AuthUser.objects.filter(is_active=False)
|
|
238
|
+
assert active_users.filter(username='active1').exists()
|
|
239
|
+
assert inactive_users.filter(username='inactive1').exists()
|
|
240
|
+
|
|
241
|
+
def test_user_order_by_username(self) -> None:
|
|
242
|
+
create_user(username='zebra')
|
|
243
|
+
create_user(username='alpha')
|
|
244
|
+
users = AuthUser.objects.order_by('username')
|
|
245
|
+
usernames = list(users.values_list('username', flat=True))
|
|
246
|
+
assert usernames == sorted(usernames)
|
|
247
|
+
|
|
248
|
+
def test_user_permissions_relationship(self) -> None:
|
|
249
|
+
assert hasattr(self.user, 'user_permissions')
|
|
250
|
+
|
|
251
|
+
def test_user_set_unusable_password(self) -> None:
|
|
252
|
+
self.user.set_unusable_password()
|
|
253
|
+
assert not self.user.has_usable_password()
|
|
254
|
+
|
|
255
|
+
def test_user_check_password_with_unusable(self) -> None:
|
|
256
|
+
self.user.set_unusable_password()
|
|
257
|
+
assert not self.user.check_password('anypassword')
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django_spire.auth.group.models import AuthGroup
|
|
4
|
+
from django_spire.auth.user.models import AuthUser
|
|
5
|
+
from django_spire.auth.user.tests.factories import create_user
|
|
6
|
+
from django_spire.core.tests.test_cases import BaseTestCase
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AuthUserServiceTestCase(BaseTestCase):
|
|
10
|
+
def setUp(self) -> None:
|
|
11
|
+
super().setUp()
|
|
12
|
+
|
|
13
|
+
self.user1 = create_user(
|
|
14
|
+
username='user1',
|
|
15
|
+
first_name='Alice',
|
|
16
|
+
last_name='Smith',
|
|
17
|
+
is_active=True
|
|
18
|
+
)
|
|
19
|
+
self.user2 = create_user(
|
|
20
|
+
username='user2',
|
|
21
|
+
first_name='Bob',
|
|
22
|
+
last_name='Jones',
|
|
23
|
+
is_active=True
|
|
24
|
+
)
|
|
25
|
+
self.inactive_user = create_user(
|
|
26
|
+
username='inactive',
|
|
27
|
+
first_name='Charlie',
|
|
28
|
+
last_name='Brown',
|
|
29
|
+
is_active=False
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
def test_get_user_choices_returns_list(self) -> None:
|
|
33
|
+
choices = AuthUser.services.get_user_choices()
|
|
34
|
+
assert isinstance(choices, list)
|
|
35
|
+
|
|
36
|
+
def test_get_user_choices_format(self) -> None:
|
|
37
|
+
choices = AuthUser.services.get_user_choices()
|
|
38
|
+
for choice in choices:
|
|
39
|
+
assert len(choice) == 2
|
|
40
|
+
assert isinstance(choice[0], int)
|
|
41
|
+
assert isinstance(choice[1], str)
|
|
42
|
+
|
|
43
|
+
def test_get_user_choices_excludes_inactive(self) -> None:
|
|
44
|
+
choices = AuthUser.services.get_user_choices()
|
|
45
|
+
user_ids = [c[0] for c in choices]
|
|
46
|
+
assert self.inactive_user.pk not in user_ids
|
|
47
|
+
|
|
48
|
+
def test_get_user_choices_includes_active(self) -> None:
|
|
49
|
+
choices = AuthUser.services.get_user_choices()
|
|
50
|
+
user_ids = [c[0] for c in choices]
|
|
51
|
+
assert self.user1.pk in user_ids
|
|
52
|
+
assert self.user2.pk in user_ids
|
|
53
|
+
|
|
54
|
+
def test_get_user_choices_by_group(self) -> None:
|
|
55
|
+
group = AuthGroup.objects.create(name='Test Group')
|
|
56
|
+
self.user1.groups.add(group)
|
|
57
|
+
|
|
58
|
+
choices = AuthUser.services.get_user_choices_by_group(group)
|
|
59
|
+
user_ids = [c[0] for c in choices]
|
|
60
|
+
|
|
61
|
+
assert self.user1.pk in user_ids
|
|
62
|
+
assert self.user2.pk not in user_ids
|
|
63
|
+
|
|
64
|
+
def test_get_user_choices_by_group_empty(self) -> None:
|
|
65
|
+
group = AuthGroup.objects.create(name='Empty Group')
|
|
66
|
+
choices = AuthUser.services.get_user_choices_by_group(group)
|
|
67
|
+
assert len(choices) == 0
|
|
68
|
+
|
|
69
|
+
def test_get_user_choices_by_group_multiple_users(self) -> None:
|
|
70
|
+
group = AuthGroup.objects.create(name='Multi User Group')
|
|
71
|
+
self.user1.groups.add(group)
|
|
72
|
+
self.user2.groups.add(group)
|
|
73
|
+
|
|
74
|
+
choices = AuthUser.services.get_user_choices_by_group(group)
|
|
75
|
+
user_ids = [c[0] for c in choices]
|
|
76
|
+
|
|
77
|
+
assert self.user1.pk in user_ids
|
|
78
|
+
assert self.user2.pk in user_ids
|
|
79
|
+
|
|
80
|
+
def test_get_user_choices_by_group_excludes_inactive(self) -> None:
|
|
81
|
+
group = AuthGroup.objects.create(name='Mixed Group')
|
|
82
|
+
self.user1.groups.add(group)
|
|
83
|
+
self.inactive_user.groups.add(group)
|
|
84
|
+
|
|
85
|
+
choices = AuthUser.services.get_user_choices_by_group(group)
|
|
86
|
+
user_ids = [c[0] for c in choices]
|
|
87
|
+
|
|
88
|
+
assert self.user1.pk in user_ids
|
|
89
|
+
assert self.inactive_user.pk not in user_ids
|
|
90
|
+
|
|
91
|
+
def test_get_user_choices_returns_full_name(self) -> None:
|
|
92
|
+
choices = AuthUser.services.get_user_choices()
|
|
93
|
+
user1_choice = next((c for c in choices if c[0] == self.user1.pk), None)
|
|
94
|
+
assert user1_choice is not None
|
|
95
|
+
assert user1_choice[1] == 'Alice Smith'
|
|
96
|
+
|
|
97
|
+
def test_get_user_choices_with_no_active_users(self) -> None:
|
|
98
|
+
AuthUser.objects.filter(is_active=True).update(is_active=False)
|
|
99
|
+
choices = AuthUser.services.get_user_choices()
|
|
100
|
+
assert len(choices) == 0
|
|
101
|
+
|
|
102
|
+
def test_get_user_choices_user_in_multiple_groups(self) -> None:
|
|
103
|
+
group1 = AuthGroup.objects.create(name='Group 1')
|
|
104
|
+
group2 = AuthGroup.objects.create(name='Group 2')
|
|
105
|
+
self.user1.groups.add(group1, group2)
|
|
106
|
+
|
|
107
|
+
choices1 = AuthUser.services.get_user_choices_by_group(group1)
|
|
108
|
+
choices2 = AuthUser.services.get_user_choices_by_group(group2)
|
|
109
|
+
|
|
110
|
+
assert any(c[0] == self.user1.pk for c in choices1)
|
|
111
|
+
assert any(c[0] == self.user1.pk for c in choices2)
|
|
112
|
+
|
|
113
|
+
def test_get_user_choices_empty_name(self) -> None:
|
|
114
|
+
user = create_user(username='emptyname', first_name='', last_name='')
|
|
115
|
+
choices = AuthUser.services.get_user_choices()
|
|
116
|
+
user_choice = next((c for c in choices if c[0] == user.pk), None)
|
|
117
|
+
assert user_choice is not None
|
|
118
|
+
assert user_choice[1] == ''
|
|
119
|
+
|
|
120
|
+
def test_get_user_choices_unicode_name(self) -> None:
|
|
121
|
+
user = create_user(username='unicode', first_name='Tëst', last_name='Üsér')
|
|
122
|
+
choices = AuthUser.services.get_user_choices()
|
|
123
|
+
user_choice = next((c for c in choices if c[0] == user.pk), None)
|
|
124
|
+
assert user_choice is not None
|
|
125
|
+
assert 'Tëst' in user_choice[1]
|
|
126
|
+
assert 'Üsér' in user_choice[1]
|
|
127
|
+
|
|
128
|
+
def test_get_user_choices_first_name_only(self) -> None:
|
|
129
|
+
user = create_user(username='firstonly', first_name='First', last_name='')
|
|
130
|
+
choices = AuthUser.services.get_user_choices()
|
|
131
|
+
user_choice = next((c for c in choices if c[0] == user.pk), None)
|
|
132
|
+
assert user_choice is not None
|
|
133
|
+
assert 'First' in user_choice[1]
|
|
134
|
+
|
|
135
|
+
def test_get_user_choices_last_name_only(self) -> None:
|
|
136
|
+
user = create_user(username='lastonly', first_name='', last_name='Last')
|
|
137
|
+
choices = AuthUser.services.get_user_choices()
|
|
138
|
+
user_choice = next((c for c in choices if c[0] == user.pk), None)
|
|
139
|
+
assert user_choice is not None
|
|
140
|
+
assert 'Last' in user_choice[1]
|
|
141
|
+
|
|
142
|
+
def test_get_user_choices_by_group_nonexistent_group(self) -> None:
|
|
143
|
+
group = AuthGroup.objects.create(name='Temp Group')
|
|
144
|
+
group_pk = group.pk
|
|
145
|
+
group.delete()
|
|
146
|
+
new_group = AuthGroup(pk=group_pk, name='Fake')
|
|
147
|
+
choices = AuthUser.services.get_user_choices_by_group(new_group)
|
|
148
|
+
assert len(choices) == 0
|
|
149
|
+
|
|
150
|
+
def test_get_user_choices_many_users(self) -> None:
|
|
151
|
+
for i in range(50):
|
|
152
|
+
create_user(username=f'manyuser{i}', first_name=f'User{i}', last_name='Test')
|
|
153
|
+
choices = AuthUser.services.get_user_choices()
|
|
154
|
+
assert len(choices) >= 50
|
|
155
|
+
|
|
156
|
+
def test_get_user_choices_by_group_after_user_removal(self) -> None:
|
|
157
|
+
group = AuthGroup.objects.create(name='Removal Group')
|
|
158
|
+
self.user1.groups.add(group)
|
|
159
|
+
|
|
160
|
+
choices_before = AuthUser.services.get_user_choices_by_group(group)
|
|
161
|
+
assert any(c[0] == self.user1.pk for c in choices_before)
|
|
162
|
+
|
|
163
|
+
self.user1.groups.remove(group)
|
|
164
|
+
|
|
165
|
+
choices_after = AuthUser.services.get_user_choices_by_group(group)
|
|
166
|
+
assert not any(c[0] == self.user1.pk for c in choices_after)
|
|
167
|
+
|
|
168
|
+
def test_get_user_choices_by_group_after_user_deactivation(self) -> None:
|
|
169
|
+
group = AuthGroup.objects.create(name='Deactivation Group')
|
|
170
|
+
self.user1.groups.add(group)
|
|
171
|
+
|
|
172
|
+
choices_before = AuthUser.services.get_user_choices_by_group(group)
|
|
173
|
+
assert any(c[0] == self.user1.pk for c in choices_before)
|
|
174
|
+
|
|
175
|
+
self.user1.is_active = False
|
|
176
|
+
self.user1.save()
|
|
177
|
+
|
|
178
|
+
choices_after = AuthUser.services.get_user_choices_by_group(group)
|
|
179
|
+
assert not any(c[0] == self.user1.pk for c in choices_after)
|
|
180
|
+
|
|
181
|
+
def test_service_obj_class_is_auth_user(self) -> None:
|
|
182
|
+
assert AuthUser.services.obj_class == AuthUser
|
|
183
|
+
|
|
184
|
+
def test_get_user_choices_order(self) -> None:
|
|
185
|
+
choices = AuthUser.services.get_user_choices()
|
|
186
|
+
assert len(choices) > 0
|
|
187
|
+
|
|
188
|
+
def test_get_user_choices_by_group_returns_list(self) -> None:
|
|
189
|
+
group = AuthGroup.objects.create(name='List Group')
|
|
190
|
+
choices = AuthUser.services.get_user_choices_by_group(group)
|
|
191
|
+
assert isinstance(choices, list)
|
|
192
|
+
|
|
193
|
+
def test_get_user_choices_by_group_format(self) -> None:
|
|
194
|
+
group = AuthGroup.objects.create(name='Format Group')
|
|
195
|
+
self.user1.groups.add(group)
|
|
196
|
+
choices = AuthUser.services.get_user_choices_by_group(group)
|
|
197
|
+
for choice in choices:
|
|
198
|
+
assert len(choice) == 2
|
|
199
|
+
assert isinstance(choice[0], int)
|
|
200
|
+
assert isinstance(choice[1], str)
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django.contrib.auth.models import Group
|
|
4
|
+
|
|
5
|
+
from django_spire.auth.user.tests.factories import create_user
|
|
6
|
+
from django_spire.auth.user.tools import add_user_to_all_user_group
|
|
7
|
+
from django_spire.core.tests.test_cases import BaseTestCase
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AddUserToAllUserGroupTestCase(BaseTestCase):
|
|
11
|
+
def setUp(self) -> None:
|
|
12
|
+
super().setUp()
|
|
13
|
+
|
|
14
|
+
self.user = create_user(username='testuser')
|
|
15
|
+
|
|
16
|
+
def test_creates_all_users_group_if_not_exists(self) -> None:
|
|
17
|
+
Group.objects.filter(name='All Users').delete()
|
|
18
|
+
add_user_to_all_user_group(self.user)
|
|
19
|
+
assert Group.objects.filter(name='All Users').exists()
|
|
20
|
+
|
|
21
|
+
def test_adds_user_to_existing_group(self) -> None:
|
|
22
|
+
group = Group.objects.create(name='All Users')
|
|
23
|
+
add_user_to_all_user_group(self.user)
|
|
24
|
+
assert self.user in group.user_set.all()
|
|
25
|
+
|
|
26
|
+
def test_user_in_all_users_group(self) -> None:
|
|
27
|
+
add_user_to_all_user_group(self.user)
|
|
28
|
+
group = Group.objects.get(name='All Users')
|
|
29
|
+
assert self.user in group.user_set.all()
|
|
30
|
+
|
|
31
|
+
def test_idempotent_add(self) -> None:
|
|
32
|
+
add_user_to_all_user_group(self.user)
|
|
33
|
+
add_user_to_all_user_group(self.user)
|
|
34
|
+
group = Group.objects.get(name='All Users')
|
|
35
|
+
assert group.user_set.filter(pk=self.user.pk).count() == 1
|
|
36
|
+
|
|
37
|
+
def test_multiple_users_added(self) -> None:
|
|
38
|
+
user2 = create_user(username='testuser2')
|
|
39
|
+
add_user_to_all_user_group(self.user)
|
|
40
|
+
add_user_to_all_user_group(user2)
|
|
41
|
+
group = Group.objects.get(name='All Users')
|
|
42
|
+
assert group.user_set.count() >= 2
|
|
43
|
+
assert self.user in group.user_set.all()
|
|
44
|
+
assert user2 in group.user_set.all()
|
|
45
|
+
|
|
46
|
+
def test_user_remains_in_other_groups(self) -> None:
|
|
47
|
+
other_group = Group.objects.create(name='Other Group')
|
|
48
|
+
self.user.groups.add(other_group)
|
|
49
|
+
add_user_to_all_user_group(self.user)
|
|
50
|
+
assert other_group in self.user.groups.all()
|
|
51
|
+
|
|
52
|
+
def test_group_created_with_exact_name(self) -> None:
|
|
53
|
+
Group.objects.filter(name='All Users').delete()
|
|
54
|
+
add_user_to_all_user_group(self.user)
|
|
55
|
+
group = Group.objects.get(name='All Users')
|
|
56
|
+
assert group.name == 'All Users'
|
|
57
|
+
|
|
58
|
+
def test_inactive_user_can_be_added(self) -> None:
|
|
59
|
+
inactive_user = create_user(username='inactive', is_active=False)
|
|
60
|
+
add_user_to_all_user_group(inactive_user)
|
|
61
|
+
group = Group.objects.get(name='All Users')
|
|
62
|
+
assert inactive_user in group.user_set.all()
|
|
63
|
+
|
|
64
|
+
def test_does_not_affect_other_groups(self) -> None:
|
|
65
|
+
other_group = Group.objects.create(name='Other Group')
|
|
66
|
+
initial_count = other_group.user_set.count()
|
|
67
|
+
add_user_to_all_user_group(self.user)
|
|
68
|
+
assert other_group.user_set.count() == initial_count
|
|
69
|
+
|
|
70
|
+
def test_user_not_added_to_other_groups(self) -> None:
|
|
71
|
+
other_group = Group.objects.create(name='Other Group')
|
|
72
|
+
add_user_to_all_user_group(self.user)
|
|
73
|
+
assert self.user not in other_group.user_set.all()
|
|
74
|
+
|
|
75
|
+
def test_multiple_calls_single_group_entry(self) -> None:
|
|
76
|
+
for _ in range(5):
|
|
77
|
+
add_user_to_all_user_group(self.user)
|
|
78
|
+
group = Group.objects.get(name='All Users')
|
|
79
|
+
assert group.user_set.filter(pk=self.user.pk).count() == 1
|
|
80
|
+
|
|
81
|
+
def test_group_persists_after_function_call(self) -> None:
|
|
82
|
+
Group.objects.filter(name='All Users').delete()
|
|
83
|
+
add_user_to_all_user_group(self.user)
|
|
84
|
+
assert Group.objects.filter(name='All Users').exists()
|
|
85
|
+
|
|
86
|
+
def test_user_membership_persists(self) -> None:
|
|
87
|
+
add_user_to_all_user_group(self.user)
|
|
88
|
+
self.user.refresh_from_db()
|
|
89
|
+
group = Group.objects.get(name='All Users')
|
|
90
|
+
assert group in self.user.groups.all()
|
|
91
|
+
|
|
92
|
+
def test_does_not_remove_existing_members(self) -> None:
|
|
93
|
+
existing_user = create_user(username='existinguser')
|
|
94
|
+
group = Group.objects.create(name='All Users')
|
|
95
|
+
group.user_set.add(existing_user)
|
|
96
|
+
|
|
97
|
+
add_user_to_all_user_group(self.user)
|
|
98
|
+
|
|
99
|
+
assert existing_user in group.user_set.all()
|
|
100
|
+
assert self.user in group.user_set.all()
|
|
101
|
+
|
|
102
|
+
def test_works_with_staff_user(self) -> None:
|
|
103
|
+
staff_user = create_user(username='staffuser', is_staff=True)
|
|
104
|
+
add_user_to_all_user_group(staff_user)
|
|
105
|
+
group = Group.objects.get(name='All Users')
|
|
106
|
+
assert staff_user in group.user_set.all()
|
|
107
|
+
|
|
108
|
+
def test_works_with_superuser(self) -> None:
|
|
109
|
+
superuser = create_user(username='superuser', is_superuser=True)
|
|
110
|
+
add_user_to_all_user_group(superuser)
|
|
111
|
+
group = Group.objects.get(name='All Users')
|
|
112
|
+
assert superuser in group.user_set.all()
|
|
113
|
+
|
|
114
|
+
def test_case_sensitive_group_name(self) -> None:
|
|
115
|
+
Group.objects.create(name='all users')
|
|
116
|
+
Group.objects.filter(name='All Users').delete()
|
|
117
|
+
add_user_to_all_user_group(self.user)
|
|
118
|
+
assert Group.objects.filter(name='All Users').exists()
|
|
119
|
+
assert Group.objects.filter(name='all users').exists()
|
|
120
|
+
|
|
121
|
+
def test_many_users_added(self) -> None:
|
|
122
|
+
users = [create_user(username=f'manyuser{i}') for i in range(20)]
|
|
123
|
+
for user in users:
|
|
124
|
+
add_user_to_all_user_group(user)
|
|
125
|
+
group = Group.objects.get(name='All Users')
|
|
126
|
+
for user in users:
|
|
127
|
+
assert user in group.user_set.all()
|
|
128
|
+
|
|
129
|
+
def test_user_groups_query(self) -> None:
|
|
130
|
+
add_user_to_all_user_group(self.user)
|
|
131
|
+
groups = self.user.groups.all()
|
|
132
|
+
group_names = [g.name for g in groups]
|
|
133
|
+
assert 'All Users' in group_names
|
|
134
|
+
|
|
135
|
+
def test_group_user_count_increments(self) -> None:
|
|
136
|
+
Group.objects.filter(name='All Users').delete()
|
|
137
|
+
group = Group.objects.create(name='All Users')
|
|
138
|
+
initial_count = group.user_set.count()
|
|
139
|
+
|
|
140
|
+
add_user_to_all_user_group(self.user)
|
|
141
|
+
group.refresh_from_db()
|
|
142
|
+
|
|
143
|
+
assert group.user_set.count() == initial_count + 1
|
|
144
|
+
|
|
145
|
+
def test_does_not_create_duplicate_groups(self) -> None:
|
|
146
|
+
Group.objects.filter(name='All Users').delete()
|
|
147
|
+
user1 = create_user(username='user1')
|
|
148
|
+
user2 = create_user(username='user2')
|
|
149
|
+
|
|
150
|
+
add_user_to_all_user_group(user1)
|
|
151
|
+
add_user_to_all_user_group(user2)
|
|
152
|
+
|
|
153
|
+
assert Group.objects.filter(name='All Users').count() == 1
|