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,282 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django_spire.auth.group.forms import GroupForm, GroupNamesForm, GroupUserForm
|
|
4
|
+
from django_spire.auth.group.models import AuthGroup
|
|
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 GroupFormTestCase(BaseTestCase):
|
|
10
|
+
def test_valid_form(self) -> None:
|
|
11
|
+
form = GroupForm(data={'name': 'Test Group'})
|
|
12
|
+
assert form.is_valid()
|
|
13
|
+
|
|
14
|
+
def test_reserved_name_all_users(self) -> None:
|
|
15
|
+
form = GroupForm(data={'name': 'All Users'})
|
|
16
|
+
assert not form.is_valid()
|
|
17
|
+
assert 'name' in form.errors
|
|
18
|
+
|
|
19
|
+
def test_reserved_name_all_users_case_insensitive(self) -> None:
|
|
20
|
+
form = GroupForm(data={'name': 'all users'})
|
|
21
|
+
assert not form.is_valid()
|
|
22
|
+
assert 'name' in form.errors
|
|
23
|
+
|
|
24
|
+
def test_reserved_name_all_users_mixed_case(self) -> None:
|
|
25
|
+
form = GroupForm(data={'name': 'ALL USERS'})
|
|
26
|
+
assert not form.is_valid()
|
|
27
|
+
assert 'name' in form.errors
|
|
28
|
+
|
|
29
|
+
def test_reserved_name_all_users_with_extra_spaces(self) -> None:
|
|
30
|
+
form = GroupForm(data={'name': 'All Users'})
|
|
31
|
+
assert form.is_valid()
|
|
32
|
+
|
|
33
|
+
def test_empty_name(self) -> None:
|
|
34
|
+
form = GroupForm(data={'name': ''})
|
|
35
|
+
assert not form.is_valid()
|
|
36
|
+
|
|
37
|
+
def test_whitespace_only_name(self) -> None:
|
|
38
|
+
form = GroupForm(data={'name': ' '})
|
|
39
|
+
assert not form.is_valid()
|
|
40
|
+
|
|
41
|
+
def test_save_creates_group(self) -> None:
|
|
42
|
+
form = GroupForm(data={'name': 'New Group'})
|
|
43
|
+
assert form.is_valid()
|
|
44
|
+
group = form.save()
|
|
45
|
+
assert group.name == 'New Group'
|
|
46
|
+
assert AuthGroup.objects.filter(name='New Group').exists()
|
|
47
|
+
|
|
48
|
+
def test_save_updates_existing_group(self) -> None:
|
|
49
|
+
group = AuthGroup.objects.create(name='Original Name')
|
|
50
|
+
form = GroupForm(data={'name': 'Updated Name'}, instance=group)
|
|
51
|
+
assert form.is_valid()
|
|
52
|
+
updated_group = form.save()
|
|
53
|
+
assert updated_group.pk == group.pk
|
|
54
|
+
assert updated_group.name == 'Updated Name'
|
|
55
|
+
|
|
56
|
+
def test_name_with_special_characters(self) -> None:
|
|
57
|
+
form = GroupForm(data={'name': 'Test & Group <Special>'})
|
|
58
|
+
assert form.is_valid()
|
|
59
|
+
|
|
60
|
+
def test_name_with_unicode(self) -> None:
|
|
61
|
+
form = GroupForm(data={'name': 'Tëst Grøup 日本語'})
|
|
62
|
+
assert form.is_valid()
|
|
63
|
+
|
|
64
|
+
def test_name_max_length(self) -> None:
|
|
65
|
+
long_name = 'A' * 150
|
|
66
|
+
form = GroupForm(data={'name': long_name})
|
|
67
|
+
assert form.is_valid()
|
|
68
|
+
|
|
69
|
+
def test_name_exceeds_max_length(self) -> None:
|
|
70
|
+
long_name = 'A' * 151
|
|
71
|
+
form = GroupForm(data={'name': long_name})
|
|
72
|
+
assert not form.is_valid()
|
|
73
|
+
|
|
74
|
+
def test_duplicate_name_fails(self) -> None:
|
|
75
|
+
AuthGroup.objects.create(name='Existing Group')
|
|
76
|
+
form = GroupForm(data={'name': 'Existing Group'})
|
|
77
|
+
assert not form.is_valid()
|
|
78
|
+
|
|
79
|
+
def test_duplicate_name_case_sensitive(self) -> None:
|
|
80
|
+
AuthGroup.objects.create(name='Existing Group')
|
|
81
|
+
form = GroupForm(data={'name': 'existing group'})
|
|
82
|
+
assert form.is_valid()
|
|
83
|
+
|
|
84
|
+
def test_reserved_name_error_message(self) -> None:
|
|
85
|
+
form = GroupForm(data={'name': 'All Users'})
|
|
86
|
+
form.is_valid()
|
|
87
|
+
assert 'reserved' in form.errors['name'][0].lower()
|
|
88
|
+
|
|
89
|
+
def test_form_excludes_permissions_field(self) -> None:
|
|
90
|
+
form = GroupForm()
|
|
91
|
+
assert 'permissions' not in form.fields
|
|
92
|
+
|
|
93
|
+
def test_update_to_reserved_name_fails(self) -> None:
|
|
94
|
+
group = AuthGroup.objects.create(name='Original')
|
|
95
|
+
form = GroupForm(data={'name': 'All Users'}, instance=group)
|
|
96
|
+
assert not form.is_valid()
|
|
97
|
+
|
|
98
|
+
def test_name_with_leading_trailing_whitespace(self) -> None:
|
|
99
|
+
form = GroupForm(data={'name': ' Test Group '})
|
|
100
|
+
assert form.is_valid()
|
|
101
|
+
|
|
102
|
+
def test_name_with_newline(self) -> None:
|
|
103
|
+
form = GroupForm(data={'name': 'Test\nGroup'})
|
|
104
|
+
assert form.is_valid()
|
|
105
|
+
|
|
106
|
+
def test_name_with_tab(self) -> None:
|
|
107
|
+
form = GroupForm(data={'name': 'Test\tGroup'})
|
|
108
|
+
assert form.is_valid()
|
|
109
|
+
|
|
110
|
+
def test_numeric_name(self) -> None:
|
|
111
|
+
form = GroupForm(data={'name': '12345'})
|
|
112
|
+
assert form.is_valid()
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class GroupNamesFormTestCase(BaseTestCase):
|
|
116
|
+
def test_valid_json_groups(self) -> None:
|
|
117
|
+
form = GroupNamesForm(data={'groups': '["Group A", "Group B"]'})
|
|
118
|
+
assert form.is_valid()
|
|
119
|
+
assert form.cleaned_data['groups'] == ['Group A', 'Group B']
|
|
120
|
+
|
|
121
|
+
def test_empty_groups(self) -> None:
|
|
122
|
+
form = GroupNamesForm(data={'groups': '[]'})
|
|
123
|
+
assert form.is_valid()
|
|
124
|
+
assert form.cleaned_data['groups'] == []
|
|
125
|
+
|
|
126
|
+
def test_save_creates_groups(self) -> None:
|
|
127
|
+
form = GroupNamesForm(data={'groups': '["Test A", "Test B"]'})
|
|
128
|
+
assert form.is_valid()
|
|
129
|
+
groups = form.save()
|
|
130
|
+
assert len(groups) == 2
|
|
131
|
+
|
|
132
|
+
def test_single_group(self) -> None:
|
|
133
|
+
form = GroupNamesForm(data={'groups': '["Single Group"]'})
|
|
134
|
+
assert form.is_valid()
|
|
135
|
+
assert form.cleaned_data['groups'] == ['Single Group']
|
|
136
|
+
|
|
137
|
+
def test_groups_with_special_characters(self) -> None:
|
|
138
|
+
form = GroupNamesForm(data={'groups': '["Group & Co", "Test <Group>"]'})
|
|
139
|
+
assert form.is_valid()
|
|
140
|
+
assert len(form.cleaned_data['groups']) == 2
|
|
141
|
+
|
|
142
|
+
def test_save_skips_existing_groups(self) -> None:
|
|
143
|
+
AuthGroup.objects.create(name='Existing')
|
|
144
|
+
form = GroupNamesForm(data={'groups': '["Existing", "New"]'})
|
|
145
|
+
assert form.is_valid()
|
|
146
|
+
groups = form.save()
|
|
147
|
+
assert len(groups) == 1
|
|
148
|
+
assert groups[0].name == 'New'
|
|
149
|
+
|
|
150
|
+
def test_many_groups(self) -> None:
|
|
151
|
+
group_names = [f'Group {i}' for i in range(20)]
|
|
152
|
+
form = GroupNamesForm(data={'groups': str(group_names).replace("'", '"')})
|
|
153
|
+
assert form.is_valid()
|
|
154
|
+
assert len(form.cleaned_data['groups']) == 20
|
|
155
|
+
|
|
156
|
+
def test_unicode_group_names(self) -> None:
|
|
157
|
+
form = GroupNamesForm(data={'groups': '["Tëst", "日本語"]'})
|
|
158
|
+
assert form.is_valid()
|
|
159
|
+
assert len(form.cleaned_data['groups']) == 2
|
|
160
|
+
|
|
161
|
+
def test_empty_string_in_list(self) -> None:
|
|
162
|
+
form = GroupNamesForm(data={'groups': '["", "Valid"]'})
|
|
163
|
+
assert form.is_valid()
|
|
164
|
+
assert '' in form.cleaned_data['groups']
|
|
165
|
+
|
|
166
|
+
def test_whitespace_group_names(self) -> None:
|
|
167
|
+
form = GroupNamesForm(data={'groups': '[" Spaces ", "Normal"]'})
|
|
168
|
+
assert form.is_valid()
|
|
169
|
+
|
|
170
|
+
def test_nested_json_fails(self) -> None:
|
|
171
|
+
form = GroupNamesForm(data={'groups': '[["nested"]]'})
|
|
172
|
+
assert form.is_valid()
|
|
173
|
+
|
|
174
|
+
def test_null_in_list(self) -> None:
|
|
175
|
+
form = GroupNamesForm(data={'groups': '[null, "Valid"]'})
|
|
176
|
+
assert form.is_valid()
|
|
177
|
+
|
|
178
|
+
def test_number_in_list(self) -> None:
|
|
179
|
+
form = GroupNamesForm(data={'groups': '[123, "Valid"]'})
|
|
180
|
+
assert form.is_valid()
|
|
181
|
+
|
|
182
|
+
def test_duplicate_names_in_list(self) -> None:
|
|
183
|
+
form = GroupNamesForm(data={'groups': '["Dup", "Dup", "Dup"]'})
|
|
184
|
+
assert form.is_valid()
|
|
185
|
+
|
|
186
|
+
def test_groups_field_not_required(self) -> None:
|
|
187
|
+
form = GroupNamesForm(data={})
|
|
188
|
+
assert not form.is_valid()
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class GroupUserFormTestCase(BaseTestCase):
|
|
192
|
+
def setUp(self) -> None:
|
|
193
|
+
super().setUp()
|
|
194
|
+
|
|
195
|
+
self.user1 = create_user(username='user1', first_name='User', last_name='One')
|
|
196
|
+
self.user2 = create_user(username='user2', first_name='User', last_name='Two')
|
|
197
|
+
self.user3 = create_user(username='user3', first_name='User', last_name='Three')
|
|
198
|
+
|
|
199
|
+
def test_valid_form_with_users(self) -> None:
|
|
200
|
+
form = GroupUserForm(data={'users': [self.user1.pk, self.user2.pk]})
|
|
201
|
+
assert form.is_valid()
|
|
202
|
+
|
|
203
|
+
def test_valid_form_empty_users(self) -> None:
|
|
204
|
+
form = GroupUserForm(data={'users': []})
|
|
205
|
+
assert form.is_valid()
|
|
206
|
+
|
|
207
|
+
def test_user_label(self) -> None:
|
|
208
|
+
label = GroupUserForm.user_label(self.user1)
|
|
209
|
+
assert label == 'User One'
|
|
210
|
+
|
|
211
|
+
def test_single_user(self) -> None:
|
|
212
|
+
form = GroupUserForm(data={'users': [self.user1.pk]})
|
|
213
|
+
assert form.is_valid()
|
|
214
|
+
assert len(form.cleaned_data['users']) == 1
|
|
215
|
+
|
|
216
|
+
def test_all_users(self) -> None:
|
|
217
|
+
form = GroupUserForm(data={'users': [self.user1.pk, self.user2.pk, self.user3.pk]})
|
|
218
|
+
assert form.is_valid()
|
|
219
|
+
assert len(form.cleaned_data['users']) == 3
|
|
220
|
+
|
|
221
|
+
def test_invalid_user_id(self) -> None:
|
|
222
|
+
form = GroupUserForm(data={'users': [99999]})
|
|
223
|
+
assert not form.is_valid()
|
|
224
|
+
|
|
225
|
+
def test_inactive_user_excluded_from_queryset(self) -> None:
|
|
226
|
+
inactive_user = create_user(username='inactive', is_active=False)
|
|
227
|
+
form = GroupUserForm()
|
|
228
|
+
user_ids = list(form.fields['users'].queryset.values_list('id', flat=True))
|
|
229
|
+
assert inactive_user.pk not in user_ids
|
|
230
|
+
|
|
231
|
+
def test_user_label_with_empty_names(self) -> None:
|
|
232
|
+
user = create_user(username='noname', first_name='', last_name='')
|
|
233
|
+
label = GroupUserForm.user_label(user)
|
|
234
|
+
assert label == ''
|
|
235
|
+
|
|
236
|
+
def test_user_label_first_name_only(self) -> None:
|
|
237
|
+
user = create_user(username='firstonly', first_name='First', last_name='')
|
|
238
|
+
label = GroupUserForm.user_label(user)
|
|
239
|
+
assert 'First' in label
|
|
240
|
+
|
|
241
|
+
def test_user_label_last_name_only(self) -> None:
|
|
242
|
+
user = create_user(username='lastonly', first_name='', last_name='Last')
|
|
243
|
+
label = GroupUserForm.user_label(user)
|
|
244
|
+
assert 'Last' in label
|
|
245
|
+
|
|
246
|
+
def test_mixed_valid_invalid_user_ids(self) -> None:
|
|
247
|
+
form = GroupUserForm(data={'users': [self.user1.pk, 99999]})
|
|
248
|
+
assert not form.is_valid()
|
|
249
|
+
|
|
250
|
+
def test_duplicate_user_ids(self) -> None:
|
|
251
|
+
form = GroupUserForm(data={'users': [self.user1.pk, self.user1.pk]})
|
|
252
|
+
assert form.is_valid()
|
|
253
|
+
assert len(form.cleaned_data['users']) == 1
|
|
254
|
+
|
|
255
|
+
def test_negative_user_id(self) -> None:
|
|
256
|
+
form = GroupUserForm(data={'users': [-1]})
|
|
257
|
+
assert not form.is_valid()
|
|
258
|
+
|
|
259
|
+
def test_string_user_id(self) -> None:
|
|
260
|
+
form = GroupUserForm(data={'users': ['invalid']})
|
|
261
|
+
assert not form.is_valid()
|
|
262
|
+
|
|
263
|
+
def test_queryset_excludes_inactive_users(self) -> None:
|
|
264
|
+
create_user(username='inactive1', is_active=False)
|
|
265
|
+
create_user(username='inactive2', is_active=False)
|
|
266
|
+
form = GroupUserForm()
|
|
267
|
+
queryset = form.fields['users'].queryset
|
|
268
|
+
assert not queryset.filter(is_active=False).exists()
|
|
269
|
+
|
|
270
|
+
def test_form_users_field_not_required(self) -> None:
|
|
271
|
+
form = GroupUserForm(data={})
|
|
272
|
+
assert form.is_valid()
|
|
273
|
+
|
|
274
|
+
def test_user_label_with_unicode(self) -> None:
|
|
275
|
+
user = create_user(username='unicode', first_name='Tëst', last_name='Üsér')
|
|
276
|
+
label = GroupUserForm.user_label(user)
|
|
277
|
+
assert 'Tëst' in label
|
|
278
|
+
assert 'Üsér' in label
|
|
279
|
+
|
|
280
|
+
def test_zero_user_id(self) -> None:
|
|
281
|
+
form = GroupUserForm(data={'users': [0]})
|
|
282
|
+
assert not form.is_valid()
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django.contrib.auth.models import Permission
|
|
4
|
+
from django.contrib.contenttypes.models import ContentType
|
|
5
|
+
|
|
6
|
+
from django_spire.auth.group.models import AuthGroup
|
|
7
|
+
from django_spire.auth.user.tests.factories import create_user
|
|
8
|
+
from django_spire.core.tests.test_cases import BaseTestCase
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AuthGroupModelTestCase(BaseTestCase):
|
|
12
|
+
def setUp(self) -> None:
|
|
13
|
+
super().setUp()
|
|
14
|
+
|
|
15
|
+
self.group = AuthGroup.objects.create(name='Test Group')
|
|
16
|
+
|
|
17
|
+
def test_str_representation(self) -> None:
|
|
18
|
+
assert str(self.group) == 'Test Group'
|
|
19
|
+
|
|
20
|
+
def test_str_representation_with_special_characters(self) -> None:
|
|
21
|
+
group = AuthGroup.objects.create(name='Test & Group <Special>')
|
|
22
|
+
assert str(group) == 'Test & Group <Special>'
|
|
23
|
+
|
|
24
|
+
def test_base_breadcrumb_returns_breadcrumbs(self) -> None:
|
|
25
|
+
crumbs = AuthGroup.base_breadcrumb()
|
|
26
|
+
assert crumbs is not None
|
|
27
|
+
|
|
28
|
+
def test_breadcrumbs_with_pk_returns_breadcrumbs(self) -> None:
|
|
29
|
+
crumbs = self.group.breadcrumbs()
|
|
30
|
+
assert crumbs is not None
|
|
31
|
+
|
|
32
|
+
def test_breadcrumbs_without_pk_returns_breadcrumbs(self) -> None:
|
|
33
|
+
unsaved_group = AuthGroup(name='Unsaved Group')
|
|
34
|
+
crumbs = unsaved_group.breadcrumbs()
|
|
35
|
+
assert crumbs is not None
|
|
36
|
+
|
|
37
|
+
def test_meta_proxy(self) -> None:
|
|
38
|
+
assert AuthGroup._meta.proxy
|
|
39
|
+
|
|
40
|
+
def test_meta_verbose_name(self) -> None:
|
|
41
|
+
assert AuthGroup._meta.verbose_name == 'Auth Group'
|
|
42
|
+
assert AuthGroup._meta.verbose_name_plural == 'Auth Groups'
|
|
43
|
+
|
|
44
|
+
def test_group_creation(self) -> None:
|
|
45
|
+
group = AuthGroup.objects.create(name='New Test Group')
|
|
46
|
+
assert group.pk is not None
|
|
47
|
+
assert AuthGroup.objects.filter(pk=group.pk).exists()
|
|
48
|
+
|
|
49
|
+
def test_group_update(self) -> None:
|
|
50
|
+
self.group.name = 'Updated Group Name'
|
|
51
|
+
self.group.save()
|
|
52
|
+
self.group.refresh_from_db()
|
|
53
|
+
assert self.group.name == 'Updated Group Name'
|
|
54
|
+
|
|
55
|
+
def test_group_deletion(self) -> None:
|
|
56
|
+
group_pk = self.group.pk
|
|
57
|
+
self.group.delete()
|
|
58
|
+
assert not AuthGroup.objects.filter(pk=group_pk).exists()
|
|
59
|
+
|
|
60
|
+
def test_group_user_relationship(self) -> None:
|
|
61
|
+
user = create_user(username='groupuser')
|
|
62
|
+
self.group.user_set.add(user)
|
|
63
|
+
assert user in self.group.user_set.all()
|
|
64
|
+
assert self.group in user.groups.all()
|
|
65
|
+
|
|
66
|
+
def test_group_permissions_relationship(self) -> None:
|
|
67
|
+
assert hasattr(self.group, 'permissions')
|
|
68
|
+
|
|
69
|
+
def test_group_name_max_length(self) -> None:
|
|
70
|
+
max_length = AuthGroup._meta.get_field('name').max_length
|
|
71
|
+
assert max_length == 150
|
|
72
|
+
|
|
73
|
+
def test_multiple_groups_creation(self) -> None:
|
|
74
|
+
groups = [
|
|
75
|
+
AuthGroup.objects.create(name=f'Group {i}')
|
|
76
|
+
for i in range(5)
|
|
77
|
+
]
|
|
78
|
+
assert len(groups) == 5
|
|
79
|
+
assert AuthGroup.objects.count() >= 5
|
|
80
|
+
|
|
81
|
+
def test_group_queryset_ordering(self) -> None:
|
|
82
|
+
AuthGroup.objects.create(name='Zebra Group')
|
|
83
|
+
AuthGroup.objects.create(name='Alpha Group')
|
|
84
|
+
groups = AuthGroup.objects.all().order_by('name')
|
|
85
|
+
names = list(groups.values_list('name', flat=True))
|
|
86
|
+
assert names == sorted(names)
|
|
87
|
+
|
|
88
|
+
def test_group_add_multiple_users(self) -> None:
|
|
89
|
+
user1 = create_user(username='user1')
|
|
90
|
+
user2 = create_user(username='user2')
|
|
91
|
+
self.group.user_set.add(user1, user2)
|
|
92
|
+
assert self.group.user_set.count() == 2
|
|
93
|
+
|
|
94
|
+
def test_group_remove_user(self) -> None:
|
|
95
|
+
user = create_user(username='removeuser')
|
|
96
|
+
self.group.user_set.add(user)
|
|
97
|
+
self.group.user_set.remove(user)
|
|
98
|
+
assert user not in self.group.user_set.all()
|
|
99
|
+
|
|
100
|
+
def test_group_clear_users(self) -> None:
|
|
101
|
+
user1 = create_user(username='clearuser1')
|
|
102
|
+
user2 = create_user(username='clearuser2')
|
|
103
|
+
self.group.user_set.add(user1, user2)
|
|
104
|
+
self.group.user_set.clear()
|
|
105
|
+
assert self.group.user_set.count() == 0
|
|
106
|
+
|
|
107
|
+
def test_group_add_permission(self) -> None:
|
|
108
|
+
content_type = ContentType.objects.get_for_model(AuthGroup)
|
|
109
|
+
permission = Permission.objects.filter(content_type=content_type).first()
|
|
110
|
+
if permission:
|
|
111
|
+
self.group.permissions.add(permission)
|
|
112
|
+
assert permission in self.group.permissions.all()
|
|
113
|
+
|
|
114
|
+
def test_group_remove_permission(self) -> None:
|
|
115
|
+
content_type = ContentType.objects.get_for_model(AuthGroup)
|
|
116
|
+
permission = Permission.objects.filter(content_type=content_type).first()
|
|
117
|
+
if permission:
|
|
118
|
+
self.group.permissions.add(permission)
|
|
119
|
+
self.group.permissions.remove(permission)
|
|
120
|
+
assert permission not in self.group.permissions.all()
|
|
121
|
+
|
|
122
|
+
def test_group_with_unicode_name(self) -> None:
|
|
123
|
+
group = AuthGroup.objects.create(name='Tëst Grøup 日本語')
|
|
124
|
+
assert group.name == 'Tëst Grøup 日本語'
|
|
125
|
+
assert str(group) == 'Tëst Grøup 日本語'
|
|
126
|
+
|
|
127
|
+
def test_group_name_with_max_length(self) -> None:
|
|
128
|
+
long_name = 'A' * 150
|
|
129
|
+
group = AuthGroup.objects.create(name=long_name)
|
|
130
|
+
assert group.name == long_name
|
|
131
|
+
|
|
132
|
+
def test_breadcrumbs_contains_group_name(self) -> None:
|
|
133
|
+
crumbs = self.group.breadcrumbs()
|
|
134
|
+
names = [c['name'] for c in crumbs]
|
|
135
|
+
assert 'Test Group' in names
|
|
136
|
+
|
|
137
|
+
def test_base_breadcrumb_contains_groups(self) -> None:
|
|
138
|
+
crumbs = AuthGroup.base_breadcrumb()
|
|
139
|
+
names = [c['name'] for c in crumbs]
|
|
140
|
+
assert 'Groups' in names
|
|
141
|
+
|
|
142
|
+
def test_group_user_set_query(self) -> None:
|
|
143
|
+
user = create_user(username='queryuser')
|
|
144
|
+
self.group.user_set.add(user)
|
|
145
|
+
queried_users = self.group.user_set.filter(username='queryuser')
|
|
146
|
+
assert queried_users.count() == 1
|
|
147
|
+
|
|
148
|
+
def test_group_inheritance_from_django_group(self) -> None:
|
|
149
|
+
from django.contrib.auth.models import Group
|
|
150
|
+
assert issubclass(AuthGroup, Group)
|
|
151
|
+
|
|
152
|
+
def test_group_activity_mixin(self) -> None:
|
|
153
|
+
assert hasattr(self.group, 'add_activity')
|
|
154
|
+
|
|
155
|
+
def test_group_empty_name_constraint(self) -> None:
|
|
156
|
+
group = AuthGroup.objects.create(name='')
|
|
157
|
+
assert group.pk is not None
|
|
158
|
+
|
|
159
|
+
def test_group_whitespace_name(self) -> None:
|
|
160
|
+
group = AuthGroup.objects.create(name=' ')
|
|
161
|
+
assert group.name == ' '
|
|
162
|
+
|
|
163
|
+
def test_group_special_characters_name(self) -> None:
|
|
164
|
+
special_name = '!@#$%^&*()_+-=[]{}|;:,.<>?'
|
|
165
|
+
group = AuthGroup.objects.create(name=special_name)
|
|
166
|
+
assert group.name == special_name
|
|
167
|
+
|
|
168
|
+
def test_group_refresh_from_db(self) -> None:
|
|
169
|
+
original_name = self.group.name
|
|
170
|
+
AuthGroup.objects.filter(pk=self.group.pk).update(name='Changed Name')
|
|
171
|
+
self.group.refresh_from_db()
|
|
172
|
+
assert self.group.name == 'Changed Name'
|
|
173
|
+
assert self.group.name != original_name
|
|
174
|
+
|
|
175
|
+
def test_group_get_or_create(self) -> None:
|
|
176
|
+
group, created = AuthGroup.objects.get_or_create(name='Get Or Create Group')
|
|
177
|
+
assert created
|
|
178
|
+
group2, created2 = AuthGroup.objects.get_or_create(name='Get Or Create Group')
|
|
179
|
+
assert not created2
|
|
180
|
+
assert group.pk == group2.pk
|
|
181
|
+
|
|
182
|
+
def test_group_bulk_create(self) -> None:
|
|
183
|
+
groups = AuthGroup.objects.bulk_create([
|
|
184
|
+
AuthGroup(name='Bulk 1'),
|
|
185
|
+
AuthGroup(name='Bulk 2'),
|
|
186
|
+
AuthGroup(name='Bulk 3'),
|
|
187
|
+
])
|
|
188
|
+
assert len(groups) == 3
|
|
189
|
+
|
|
190
|
+
def test_group_exists(self) -> None:
|
|
191
|
+
assert AuthGroup.objects.filter(name='Test Group').exists()
|
|
192
|
+
assert not AuthGroup.objects.filter(name='Nonexistent Group').exists()
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django_spire.auth.group.models import AuthGroup
|
|
4
|
+
from django_spire.auth.group.querysets import GroupQuerySet
|
|
5
|
+
from django_spire.core.tests.test_cases import BaseTestCase
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GroupQuerySetTestCase(BaseTestCase):
|
|
9
|
+
def setUp(self) -> None:
|
|
10
|
+
super().setUp()
|
|
11
|
+
|
|
12
|
+
self.group_c = AuthGroup.objects.create(name='Charlie Group')
|
|
13
|
+
self.group_a = AuthGroup.objects.create(name='Alpha Group')
|
|
14
|
+
self.group_b = AuthGroup.objects.create(name='Beta Group')
|
|
15
|
+
|
|
16
|
+
def test_active_returns_ordered_by_name(self) -> None:
|
|
17
|
+
queryset = AuthGroup.objects.all()
|
|
18
|
+
queryset.__class__ = GroupQuerySet
|
|
19
|
+
result = queryset.active()
|
|
20
|
+
names = list(result.values_list('name', flat=True))
|
|
21
|
+
assert names == ['Alpha Group', 'Beta Group', 'Charlie Group']
|
|
22
|
+
|
|
23
|
+
def test_active_returns_all_groups(self) -> None:
|
|
24
|
+
queryset = AuthGroup.objects.all()
|
|
25
|
+
queryset.__class__ = GroupQuerySet
|
|
26
|
+
result = queryset.active()
|
|
27
|
+
assert result.count() == 3
|
|
28
|
+
|
|
29
|
+
def test_active_empty_queryset(self) -> None:
|
|
30
|
+
AuthGroup.objects.all().delete()
|
|
31
|
+
queryset = AuthGroup.objects.all()
|
|
32
|
+
queryset.__class__ = GroupQuerySet
|
|
33
|
+
result = queryset.active()
|
|
34
|
+
assert result.count() == 0
|
|
35
|
+
|
|
36
|
+
def test_active_single_group(self) -> None:
|
|
37
|
+
AuthGroup.objects.all().delete()
|
|
38
|
+
AuthGroup.objects.create(name='Single Group')
|
|
39
|
+
queryset = AuthGroup.objects.all()
|
|
40
|
+
queryset.__class__ = GroupQuerySet
|
|
41
|
+
result = queryset.active()
|
|
42
|
+
assert result.count() == 1
|
|
43
|
+
|
|
44
|
+
def test_active_ordering_with_numbers(self) -> None:
|
|
45
|
+
group1 = AuthGroup.objects.create(name='AAA First')
|
|
46
|
+
group2 = AuthGroup.objects.create(name='BBB Second')
|
|
47
|
+
group3 = AuthGroup.objects.create(name='CCC Third')
|
|
48
|
+
queryset = AuthGroup.objects.filter(pk__in=[group1.pk, group2.pk, group3.pk])
|
|
49
|
+
queryset.__class__ = GroupQuerySet
|
|
50
|
+
result = queryset.active()
|
|
51
|
+
names = list(result.values_list('name', flat=True))
|
|
52
|
+
assert names == ['AAA First', 'BBB Second', 'CCC Third']
|
|
53
|
+
|
|
54
|
+
def test_active_ordering_with_special_characters(self) -> None:
|
|
55
|
+
AuthGroup.objects.create(name='!Special')
|
|
56
|
+
AuthGroup.objects.create(name='@Another')
|
|
57
|
+
queryset = AuthGroup.objects.all()
|
|
58
|
+
queryset.__class__ = GroupQuerySet
|
|
59
|
+
result = queryset.active()
|
|
60
|
+
assert result.count() >= 2
|
|
61
|
+
|
|
62
|
+
def test_active_ordering_case_sensitive(self) -> None:
|
|
63
|
+
AuthGroup.objects.all().delete()
|
|
64
|
+
AuthGroup.objects.create(name='alpha')
|
|
65
|
+
AuthGroup.objects.create(name='Alpha')
|
|
66
|
+
AuthGroup.objects.create(name='ALPHA')
|
|
67
|
+
queryset = AuthGroup.objects.all()
|
|
68
|
+
queryset.__class__ = GroupQuerySet
|
|
69
|
+
result = queryset.active()
|
|
70
|
+
names = list(result.values_list('name', flat=True))
|
|
71
|
+
assert len(names) == 3
|
|
72
|
+
|
|
73
|
+
def test_active_ordering_with_unicode(self) -> None:
|
|
74
|
+
AuthGroup.objects.create(name='Ñame')
|
|
75
|
+
AuthGroup.objects.create(name='Ääkkönen')
|
|
76
|
+
queryset = AuthGroup.objects.all()
|
|
77
|
+
queryset.__class__ = GroupQuerySet
|
|
78
|
+
result = queryset.active()
|
|
79
|
+
assert result.count() >= 2
|
|
80
|
+
|
|
81
|
+
def test_active_is_chainable(self) -> None:
|
|
82
|
+
queryset = AuthGroup.objects.all()
|
|
83
|
+
queryset.__class__ = GroupQuerySet
|
|
84
|
+
result = queryset.active().filter(name__contains='Group')
|
|
85
|
+
assert result.count() == 3
|
|
86
|
+
|
|
87
|
+
def test_active_with_prefetch(self) -> None:
|
|
88
|
+
queryset = AuthGroup.objects.all().prefetch_related('permissions')
|
|
89
|
+
queryset.__class__ = GroupQuerySet
|
|
90
|
+
result = queryset.active()
|
|
91
|
+
assert result.count() == 3
|
|
92
|
+
|
|
93
|
+
def test_active_returns_queryset(self) -> None:
|
|
94
|
+
queryset = AuthGroup.objects.all()
|
|
95
|
+
queryset.__class__ = GroupQuerySet
|
|
96
|
+
result = queryset.active()
|
|
97
|
+
assert hasattr(result, 'filter')
|
|
98
|
+
assert hasattr(result, 'exclude')
|