django-spire 0.23.7__py3-none-any.whl → 0.23.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_spire/ai/admin.py +11 -11
- django_spire/ai/chat/apps.py +1 -0
- django_spire/ai/chat/templates/django_spire/ai/chat/widget/dialog_widget.html +1 -1
- django_spire/ai/chat/tests/factories.py +15 -0
- django_spire/ai/chat/tests/test_controller.py +45 -0
- django_spire/ai/chat/tests/test_models.py +301 -0
- django_spire/ai/chat/tests/test_prompts.py +48 -0
- django_spire/ai/chat/tests/test_responses.py +208 -0
- django_spire/ai/chat/tests/test_router/test_base_chat_router.py +66 -6
- django_spire/ai/chat/tests/test_router/test_chat_workflow.py +73 -3
- django_spire/ai/chat/tests/test_router/test_integration.py +86 -6
- django_spire/ai/chat/tests/test_router/test_intent_decoder.py +93 -1
- django_spire/ai/chat/tests/test_router/test_message_intel.py +60 -1
- django_spire/ai/chat/tests/test_router/test_spire_chat_router.py +110 -0
- django_spire/ai/chat/tests/test_urls/test_json_urls.py +202 -1
- django_spire/ai/context/tests/__init__.py +0 -0
- django_spire/ai/context/tests/test_context.py +188 -0
- django_spire/ai/decorators.py +7 -6
- django_spire/ai/prompt/tests/test_bots.py +100 -10
- django_spire/ai/prompt/tests/test_prompt_intel.py +83 -0
- django_spire/ai/prompt/tests/test_prompt_tuning.py +126 -0
- django_spire/ai/sms/decorators.py +8 -2
- django_spire/ai/sms/tests/test_sms.py +240 -16
- django_spire/ai/sms/tests/test_sms_intel.py +42 -0
- django_spire/ai/sms/tests/test_webhook.py +155 -7
- django_spire/ai/sms/views.py +23 -24
- django_spire/ai/tests/test_ai.py +131 -7
- django_spire/auth/apps.py +4 -2
- django_spire/auth/controller/controller.py +36 -23
- django_spire/auth/controller/exceptions.py +9 -0
- django_spire/auth/group/admin.py +1 -0
- django_spire/auth/group/apps.py +2 -0
- django_spire/auth/group/factories.py +17 -8
- django_spire/auth/group/forms.py +7 -0
- django_spire/auth/group/tests/test_factories.py +146 -0
- django_spire/auth/group/tests/test_forms.py +282 -0
- django_spire/auth/group/tests/test_models.py +192 -0
- django_spire/auth/group/tests/test_querysets.py +98 -0
- django_spire/auth/group/tests/test_utils.py +341 -0
- django_spire/auth/group/tests/test_views.py +377 -0
- django_spire/auth/group/urls/__init__.py +3 -1
- django_spire/auth/group/urls/form_urls.py +2 -0
- django_spire/auth/group/urls/json_urls.py +3 -0
- django_spire/auth/group/urls/page_urls.py +2 -0
- django_spire/auth/group/utils.py +6 -2
- django_spire/auth/group/views/form_views.py +6 -3
- django_spire/auth/group/views/json_views.py +6 -2
- django_spire/auth/mfa/admin.py +2 -0
- django_spire/auth/mfa/apps.py +2 -0
- django_spire/auth/mfa/forms.py +1 -0
- django_spire/auth/mfa/querysets.py +9 -2
- django_spire/auth/mfa/tests/test_models.py +233 -0
- django_spire/auth/mfa/tests/test_utils.py +106 -0
- django_spire/auth/mfa/urls/__init__.py +2 -0
- django_spire/auth/mfa/urls/page_urls.py +2 -0
- django_spire/auth/mfa/urls/redirect_urls.py +2 -0
- django_spire/auth/mfa/views/page_views.py +2 -1
- django_spire/auth/permissions/consts.py +2 -2
- django_spire/auth/permissions/decorators.py +8 -8
- django_spire/auth/permissions/permissions.py +28 -35
- django_spire/auth/permissions/tests/test_decorators.py +333 -0
- django_spire/auth/permissions/tests/test_permissions.py +337 -0
- django_spire/auth/permissions/tests/test_tools.py +305 -0
- django_spire/auth/permissions/tools.py +21 -15
- django_spire/auth/seeding/seed.py +3 -0
- django_spire/auth/seeding/seeder.py +2 -0
- django_spire/auth/tests/test_controller.py +323 -0
- django_spire/auth/tests/test_url_endpoints.py +9 -9
- django_spire/auth/tests/test_views.py +406 -0
- django_spire/auth/urls/admin_urls.py +2 -0
- django_spire/auth/urls/redirect_urls.py +2 -0
- django_spire/auth/user/apps.py +2 -0
- django_spire/auth/user/forms.py +9 -0
- django_spire/auth/user/models.py +1 -1
- django_spire/auth/user/services/services.py +1 -0
- django_spire/auth/user/tests/factories.py +14 -13
- django_spire/auth/user/tests/test_factories.py +166 -2
- django_spire/auth/user/tests/test_forms.py +573 -0
- django_spire/auth/user/tests/test_models.py +257 -0
- django_spire/auth/user/tests/test_services.py +200 -0
- django_spire/auth/user/tests/test_tools.py +153 -0
- django_spire/auth/user/tests/test_user_factories.py +139 -0
- django_spire/auth/user/tests/test_views.py +363 -0
- django_spire/auth/user/tools.py +7 -1
- django_spire/auth/user/urls/form_urls.py +3 -0
- django_spire/auth/user/urls/page_urls.py +3 -0
- django_spire/auth/user/views/form_views.py +19 -10
- django_spire/auth/user/views/page_views.py +8 -2
- django_spire/auth/views/redirect_views.py +14 -9
- django_spire/comment/admin.py +2 -0
- django_spire/comment/apps.py +2 -0
- django_spire/comment/templatetags/comment_tags.py +1 -0
- django_spire/comment/tests/test_forms.py +27 -0
- django_spire/comment/tests/test_models.py +215 -0
- django_spire/comment/tests/test_querysets.py +101 -0
- django_spire/comment/tests/test_utils.py +90 -0
- django_spire/comment/urls.py +2 -0
- django_spire/comment/utils.py +22 -13
- django_spire/comment/views.py +1 -1
- django_spire/conf.py +8 -6
- django_spire/consts.py +1 -1
- django_spire/contrib/breadcrumb/apps.py +2 -0
- django_spire/contrib/breadcrumb/breadcrumbs.py +18 -18
- django_spire/contrib/breadcrumb/tests/test_breadcrumbs.py +198 -0
- django_spire/contrib/constructor/__init__.py +3 -3
- django_spire/contrib/constructor/constructor.py +15 -15
- django_spire/contrib/constructor/django_model_constructor.py +5 -4
- django_spire/contrib/constructor/exceptions.py +5 -3
- django_spire/contrib/constructor/tests/__init__.py +0 -0
- django_spire/contrib/constructor/tests/test_constructor.py +193 -0
- django_spire/contrib/form/tests/__init__.py +0 -0
- django_spire/contrib/form/tests/test_forms.py +203 -0
- django_spire/contrib/generic_views/modal_views.py +2 -1
- django_spire/contrib/generic_views/portal_views.py +20 -19
- django_spire/contrib/generic_views/tests/__init__.py +0 -0
- django_spire/contrib/generic_views/tests/test_views.py +459 -0
- django_spire/contrib/help/apps.py +2 -0
- django_spire/contrib/help/templatetags/help.py +1 -0
- django_spire/contrib/help/tests/__init__.py +0 -0
- django_spire/contrib/help/tests/test_templatetags.py +100 -0
- django_spire/contrib/options/mixins.py +6 -5
- django_spire/contrib/options/tests/factories.py +5 -1
- django_spire/contrib/options/tests/test_options.py +234 -0
- django_spire/contrib/ordering/exceptions.py +7 -3
- django_spire/contrib/ordering/mixins.py +2 -0
- django_spire/contrib/ordering/querysets.py +3 -1
- django_spire/contrib/ordering/services/processor_service.py +8 -4
- django_spire/contrib/ordering/services/service.py +1 -2
- django_spire/contrib/ordering/tests/__init__.py +0 -0
- django_spire/contrib/ordering/tests/test_ordering.py +165 -0
- django_spire/contrib/ordering/validators.py +6 -6
- django_spire/contrib/pagination/templatetags/pagination_tags.py +12 -5
- django_spire/contrib/pagination/tests/__init__.py +0 -0
- django_spire/contrib/pagination/tests/test_pagination.py +179 -0
- django_spire/contrib/performance/decorators.py +16 -6
- django_spire/contrib/performance/tests/__init__.py +0 -0
- django_spire/contrib/performance/tests/test_performance.py +107 -0
- django_spire/contrib/queryset/enums.py +3 -1
- django_spire/contrib/queryset/filter_tools.py +10 -5
- django_spire/contrib/queryset/mixins.py +16 -16
- django_spire/contrib/queryset/tests/__init__.py +0 -0
- django_spire/contrib/queryset/tests/test_queryset.py +137 -0
- django_spire/contrib/seeding/field/base.py +13 -7
- django_spire/contrib/seeding/field/callable.py +8 -1
- django_spire/contrib/seeding/field/cleaners.py +5 -5
- django_spire/contrib/seeding/field/custom.py +20 -10
- django_spire/contrib/seeding/field/django/seeder.py +8 -6
- django_spire/contrib/seeding/field/enums.py +7 -5
- django_spire/contrib/seeding/field/override.py +16 -6
- django_spire/contrib/seeding/field/static.py +9 -2
- django_spire/contrib/seeding/field/tests/test_base.py +18 -14
- django_spire/contrib/seeding/field/tests/test_callable.py +13 -9
- django_spire/contrib/seeding/field/tests/test_cleaners.py +51 -38
- django_spire/contrib/seeding/field/tests/test_static.py +13 -9
- django_spire/contrib/seeding/intelligence/bots/seeder_generator_bot.py +2 -0
- django_spire/contrib/seeding/intelligence/intel.py +5 -1
- django_spire/contrib/seeding/intelligence/prompts/factory.py +6 -1
- django_spire/contrib/seeding/intelligence/prompts/foreign_key_selection_prompt.py +6 -1
- django_spire/contrib/seeding/intelligence/prompts/generate_django_model_seeder_prompts.py +2 -0
- django_spire/contrib/seeding/intelligence/prompts/generic_relationship_selection_prompt.py +7 -1
- django_spire/contrib/seeding/intelligence/prompts/hierarchical_selection_prompt.py +6 -2
- django_spire/contrib/seeding/intelligence/prompts/model_field_choices_prompt.py +8 -2
- django_spire/contrib/seeding/intelligence/prompts/negation_prompt.py +2 -0
- django_spire/contrib/seeding/intelligence/prompts/objective_prompt.py +6 -1
- django_spire/contrib/seeding/management/commands/seeding.py +9 -3
- django_spire/contrib/seeding/management/example.py +2 -0
- django_spire/contrib/seeding/model/base.py +16 -7
- django_spire/contrib/seeding/model/config.py +31 -15
- django_spire/contrib/seeding/model/django/config.py +13 -13
- django_spire/contrib/seeding/model/django/seeder.py +4 -4
- django_spire/contrib/seeding/model/django/tests/test_seeder.py +34 -23
- django_spire/contrib/seeding/model/enums.py +2 -0
- django_spire/contrib/seeding/tests/test_config.py +71 -0
- django_spire/contrib/seeding/tests/test_custom.py +35 -0
- django_spire/contrib/seeding/tests/test_enums.py +40 -0
- django_spire/contrib/seeding/tests/test_intel.py +32 -0
- django_spire/contrib/seeding/tests/test_override.py +63 -0
- django_spire/contrib/service/__init__.py +2 -2
- django_spire/contrib/service/django_model_service.py +16 -15
- django_spire/contrib/service/exceptions.py +5 -3
- django_spire/contrib/service/tests/__init__.py +0 -0
- django_spire/contrib/service/tests/test_service.py +153 -0
- django_spire/contrib/session/apps.py +2 -0
- django_spire/contrib/session/controller.py +48 -42
- django_spire/contrib/session/templatetags/session_tags.py +11 -2
- django_spire/contrib/session/tests/test_session_controller.py +117 -53
- django_spire/contrib/tests/__init__.py +0 -0
- django_spire/contrib/tests/test_utils.py +37 -0
- django_spire/contrib/utils.py +4 -1
- django_spire/core/apps.py +2 -0
- django_spire/core/converters/tests/test_to_data.py +353 -0
- django_spire/core/converters/tests/test_to_enums.py +61 -41
- django_spire/core/converters/tests/test_to_pydantic.py +138 -109
- django_spire/core/converters/to_data.py +29 -10
- django_spire/core/converters/to_enums.py +4 -2
- django_spire/core/converters/to_pydantic.py +22 -22
- django_spire/core/decorators.py +19 -6
- django_spire/core/forms/widgets.py +4 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/apps.py.template +2 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/__init__.py.template +2 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/form_urls.py.template +2 -0
- django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/page_urls.py.template +2 -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 +12 -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 +5 -3
- django_spire/core/templatetags/json.py +6 -2
- django_spire/core/templatetags/message.py +13 -8
- django_spire/core/templatetags/string_formating.py +8 -5
- django_spire/core/templatetags/tests/__init__.py +0 -0
- django_spire/core/templatetags/tests/test_templatetags.py +427 -0
- django_spire/core/templatetags/variable_types.py +17 -9
- django_spire/core/tests/test_cases.py +1 -1
- django_spire/core/tests/test_conf.py +43 -0
- django_spire/core/tests/test_consts.py +28 -0
- django_spire/core/tests/test_context_processors.py +93 -0
- django_spire/core/tests/test_decorators.py +95 -0
- django_spire/core/tests/test_django_spire_utils.py +56 -0
- django_spire/core/tests/test_exceptions.py +37 -0
- django_spire/core/tests/test_models.py +54 -0
- django_spire/core/tests/test_settings.py +45 -0
- django_spire/core/tests/test_shortcuts.py +74 -0
- django_spire/core/tests/test_urls.py +16 -0
- django_spire/core/tests/test_utils.py +58 -0
- django_spire/core/urls.py +4 -1
- django_spire/core/utils.py +12 -8
- django_spire/exceptions.py +16 -1
- django_spire/file/admin.py +4 -2
- django_spire/file/apps.py +8 -10
- django_spire/file/fields.py +7 -7
- django_spire/file/forms.py +1 -1
- django_spire/file/interfaces.py +15 -15
- django_spire/file/mixins.py +1 -4
- django_spire/file/models.py +3 -5
- django_spire/file/tests/factories.py +59 -0
- django_spire/file/tests/test_admin.py +69 -0
- django_spire/file/tests/test_apps.py +24 -0
- django_spire/file/tests/test_fields.py +114 -0
- django_spire/file/tests/test_forms.py +20 -0
- django_spire/file/tests/test_interfaces.py +183 -0
- django_spire/file/tests/test_models.py +82 -0
- django_spire/file/tests/test_querysets.py +102 -0
- django_spire/file/tests/test_utils.py +32 -0
- django_spire/file/tests/test_views.py +145 -0
- django_spire/file/tests/test_widgets.py +82 -0
- django_spire/file/tools.py +8 -2
- django_spire/file/views.py +7 -3
- django_spire/file/widgets.py +12 -12
- django_spire/help_desk/admin.py +15 -0
- django_spire/help_desk/apps.py +2 -0
- django_spire/help_desk/auth/controller.py +2 -0
- django_spire/help_desk/choices.py +2 -0
- django_spire/help_desk/enums.py +2 -0
- django_spire/help_desk/exceptions.py +31 -3
- django_spire/help_desk/forms.py +2 -0
- django_spire/help_desk/models.py +2 -0
- django_spire/help_desk/querysets.py +4 -1
- django_spire/help_desk/services/notification_service.py +26 -27
- django_spire/help_desk/services/service.py +2 -3
- django_spire/help_desk/tests/factories.py +8 -3
- django_spire/help_desk/tests/test_admin.py +41 -0
- django_spire/help_desk/tests/test_apps.py +41 -0
- django_spire/help_desk/tests/test_choices.py +50 -0
- django_spire/help_desk/tests/test_controller.py +87 -0
- django_spire/help_desk/tests/test_enums.py +18 -0
- django_spire/help_desk/tests/test_exceptions.py +37 -0
- django_spire/help_desk/tests/test_forms.py +89 -0
- django_spire/help_desk/tests/test_models.py +59 -0
- django_spire/help_desk/tests/test_querysets.py +38 -0
- django_spire/help_desk/tests/test_services/test_notification_service.py +15 -8
- django_spire/help_desk/tests/test_services/test_service.py +92 -0
- django_spire/help_desk/tests/test_urls/test_form_urls.py +6 -6
- django_spire/help_desk/tests/test_urls/test_page_urls.py +8 -9
- django_spire/help_desk/tests/test_views/test_form_views.py +46 -19
- django_spire/help_desk/tests/test_views/test_page_views.py +32 -9
- django_spire/help_desk/urls/__init__.py +4 -1
- django_spire/help_desk/urls/form_urls.py +3 -0
- django_spire/help_desk/urls/page_urls.py +3 -0
- django_spire/help_desk/views/form_views.py +13 -5
- django_spire/help_desk/views/page_views.py +11 -3
- django_spire/history/activity/admin.py +2 -0
- django_spire/history/activity/apps.py +3 -1
- django_spire/history/activity/mixins.py +13 -7
- django_spire/history/activity/models.py +6 -5
- django_spire/history/activity/querysets.py +2 -0
- django_spire/history/activity/tests/__init__.py +0 -0
- django_spire/history/activity/tests/test_activity.py +176 -0
- django_spire/history/admin.py +9 -2
- django_spire/history/choices.py +3 -0
- django_spire/history/models.py +5 -5
- django_spire/history/tests/test_admin.py +93 -0
- django_spire/history/tests/test_history.py +101 -0
- django_spire/history/tests/test_mixins.py +84 -0
- django_spire/history/viewed/admin.py +3 -1
- django_spire/history/viewed/apps.py +3 -1
- django_spire/history/viewed/models.py +2 -0
- django_spire/history/viewed/tests/__init__.py +0 -0
- django_spire/history/viewed/tests/test_viewed.py +46 -0
- django_spire/knowledge/auth/tests/__init__.py +0 -0
- django_spire/knowledge/auth/tests/test_controller.py +116 -0
- django_spire/knowledge/collection/admin.py +5 -1
- django_spire/knowledge/collection/models.py +3 -1
- django_spire/knowledge/collection/seeding/seed.py +1 -0
- django_spire/knowledge/collection/services/factory_service.py +10 -11
- django_spire/knowledge/collection/services/ordering_service.py +1 -2
- django_spire/knowledge/collection/services/service.py +5 -10
- django_spire/knowledge/collection/services/tag_service.py +5 -2
- django_spire/knowledge/collection/tests/factories.py +28 -1
- django_spire/knowledge/collection/tests/test_models.py +48 -0
- django_spire/knowledge/collection/tests/test_querysets.py +93 -0
- django_spire/knowledge/collection/tests/test_services/test_factory_service.py +100 -0
- django_spire/knowledge/collection/tests/test_services/test_services.py +160 -0
- django_spire/knowledge/collection/tests/test_urls/test_form_urls.py +21 -3
- django_spire/knowledge/collection/tests/test_urls/test_json_urls.py +39 -1
- django_spire/knowledge/collection/tests/test_urls/test_page_urls.py +12 -4
- django_spire/knowledge/collection/urls/__init__.py +3 -0
- django_spire/knowledge/collection/urls/form_urls.py +2 -0
- django_spire/knowledge/collection/urls/json_urls.py +2 -0
- django_spire/knowledge/collection/urls/page_urls.py +2 -0
- django_spire/knowledge/collection/views/form_views.py +4 -4
- django_spire/knowledge/collection/views/json_views.py +5 -1
- django_spire/knowledge/collection/views/page_views.py +5 -2
- django_spire/knowledge/entry/admin.py +7 -1
- django_spire/knowledge/entry/forms.py +2 -0
- django_spire/knowledge/entry/models.py +2 -0
- django_spire/knowledge/entry/seeding/seed.py +3 -0
- django_spire/knowledge/entry/services/automation_service.py +5 -4
- django_spire/knowledge/entry/services/factory_service.py +7 -5
- django_spire/knowledge/entry/services/service.py +4 -7
- django_spire/knowledge/entry/services/tag_service.py +0 -1
- django_spire/knowledge/entry/services/tool_service.py +1 -0
- django_spire/knowledge/entry/services/transformation_services.py +1 -5
- django_spire/knowledge/entry/tests/factories.py +1 -2
- django_spire/knowledge/entry/tests/test_factory_service.py +20 -0
- django_spire/knowledge/entry/tests/test_models.py +41 -0
- django_spire/knowledge/entry/tests/test_querysets.py +71 -0
- django_spire/knowledge/entry/tests/test_services.py +94 -0
- django_spire/knowledge/entry/tests/test_urls/test_form_urls.py +9 -14
- django_spire/knowledge/entry/tests/test_urls/test_json_urls.py +48 -5
- django_spire/knowledge/entry/tests/test_urls/test_page_urls.py +6 -8
- django_spire/knowledge/entry/tests/test_urls/test_template_urls.py +40 -0
- django_spire/knowledge/entry/urls/form_urls.py +2 -0
- django_spire/knowledge/entry/urls/json_urls.py +2 -0
- django_spire/knowledge/entry/urls/page_urls.py +2 -0
- django_spire/knowledge/entry/urls/template_urls.py +2 -0
- django_spire/knowledge/entry/version/block/choices.py +2 -0
- django_spire/knowledge/entry/version/block/data/data.py +1 -0
- django_spire/knowledge/entry/version/block/data/list/data.py +8 -13
- django_spire/knowledge/entry/version/block/data/list/maps.py +3 -0
- django_spire/knowledge/entry/version/block/data/list/meta.py +1 -2
- django_spire/knowledge/entry/version/block/data/list/tests/__init__.py +0 -0
- django_spire/knowledge/entry/version/block/data/list/tests/test_maps.py +32 -0
- django_spire/knowledge/entry/version/block/data/list/tests/test_meta.py +58 -0
- django_spire/knowledge/entry/version/block/data/maps.py +3 -6
- django_spire/knowledge/entry/version/block/models.py +7 -5
- django_spire/knowledge/entry/version/block/seeding/constants.py +5 -4
- django_spire/knowledge/entry/version/block/services/service.py +2 -3
- django_spire/knowledge/entry/version/block/tests/factories.py +4 -10
- django_spire/knowledge/entry/version/block/tests/test_choices.py +56 -0
- django_spire/knowledge/entry/version/block/tests/test_data.py +90 -0
- django_spire/knowledge/entry/version/block/tests/test_maps.py +37 -0
- django_spire/knowledge/entry/version/block/tests/test_models.py +55 -0
- django_spire/knowledge/entry/version/block/tests/test_querysets.py +35 -0
- django_spire/knowledge/entry/version/block/tests/test_services.py +65 -0
- django_spire/knowledge/entry/version/choices.py +2 -0
- django_spire/knowledge/entry/version/converters/converter.py +1 -1
- django_spire/knowledge/entry/version/converters/docx_converter.py +4 -7
- django_spire/knowledge/entry/version/converters/markdown_converter.py +20 -20
- django_spire/knowledge/entry/version/maps.py +4 -5
- django_spire/knowledge/entry/version/querysets.py +1 -1
- django_spire/knowledge/entry/version/seeding/seeder.py +1 -2
- django_spire/knowledge/entry/version/services/processor_service.py +5 -4
- django_spire/knowledge/entry/version/services/service.py +1 -2
- django_spire/knowledge/entry/version/tests/factories.py +2 -2
- django_spire/knowledge/entry/version/tests/test_choices.py +18 -0
- django_spire/knowledge/entry/version/tests/test_converters/test_docx_converter.py +56 -8
- django_spire/knowledge/entry/version/tests/test_converters/test_markdown_converter.py +78 -0
- django_spire/knowledge/entry/version/tests/test_maps.py +58 -0
- django_spire/knowledge/entry/version/tests/test_models.py +23 -0
- django_spire/knowledge/entry/version/tests/test_querysets.py +26 -0
- django_spire/knowledge/entry/version/tests/test_services.py +62 -0
- django_spire/knowledge/entry/version/tests/test_urls/test_json_urls.py +27 -8
- django_spire/knowledge/entry/version/tests/test_urls/test_page_urls.py +15 -8
- django_spire/knowledge/entry/version/tests/test_urls/test_redirect_urls.py +38 -0
- django_spire/knowledge/entry/version/urls/__init__.py +3 -0
- django_spire/knowledge/entry/version/urls/json_urls.py +2 -1
- django_spire/knowledge/entry/version/urls/page_urls.py +2 -0
- django_spire/knowledge/entry/version/urls/redirect_urls.py +2 -0
- django_spire/knowledge/entry/version/views/json_views.py +5 -1
- django_spire/knowledge/entry/version/views/page_views.py +10 -3
- django_spire/knowledge/entry/version/views/redirect_views.py +5 -1
- django_spire/knowledge/entry/views/form_views.py +16 -8
- django_spire/knowledge/entry/views/json_views.py +3 -1
- django_spire/knowledge/entry/views/page_views.py +8 -2
- django_spire/knowledge/entry/views/template_views.py +7 -1
- django_spire/knowledge/exceptions.py +2 -1
- django_spire/knowledge/intelligence/intel/answer_intel.py +2 -1
- django_spire/knowledge/intelligence/intel/entry_intel.py +0 -1
- django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +4 -5
- django_spire/knowledge/models.py +1 -2
- django_spire/knowledge/tests/__init__.py +0 -0
- django_spire/knowledge/tests/test_templatetags.py +40 -0
- django_spire/knowledge/tests/test_urls/__init__.py +0 -0
- django_spire/knowledge/tests/test_urls/test_page_urls.py +24 -0
- django_spire/knowledge/urls/__init__.py +2 -0
- django_spire/knowledge/urls/page_urls.py +2 -0
- django_spire/knowledge/views/page_views.py +8 -3
- django_spire/notification/admin.py +3 -1
- django_spire/notification/app/admin.py +2 -0
- django_spire/notification/app/apps.py +3 -1
- django_spire/notification/app/exceptions.py +9 -2
- django_spire/notification/app/models.py +8 -4
- django_spire/notification/app/processor.py +22 -26
- django_spire/notification/app/querysets.py +2 -0
- django_spire/notification/app/tests/__init__.py +0 -0
- django_spire/notification/app/tests/factories.py +34 -0
- django_spire/notification/app/tests/test_apps.py +24 -0
- django_spire/notification/app/tests/test_models.py +72 -0
- django_spire/notification/app/tests/test_processor.py +111 -0
- django_spire/notification/app/tests/test_querysets.py +90 -0
- django_spire/notification/app/tests/test_views/__init__.py +0 -0
- django_spire/notification/app/tests/test_views/test_json_views.py +48 -0
- django_spire/notification/app/tests/test_views/test_page_views.py +19 -0
- django_spire/notification/app/urls/__init__.py +3 -1
- django_spire/notification/app/urls/json_urls.py +6 -4
- django_spire/notification/app/urls/page_urls.py +4 -3
- django_spire/notification/app/urls/template_urls.py +4 -2
- django_spire/notification/apps.py +4 -1
- django_spire/notification/email/admin.py +5 -1
- django_spire/notification/email/apps.py +3 -1
- django_spire/notification/email/exceptions.py +4 -2
- django_spire/notification/email/helper.py +5 -3
- django_spire/notification/email/models.py +4 -0
- django_spire/notification/email/processor.py +19 -15
- django_spire/notification/email/querysets.py +3 -0
- django_spire/notification/email/tests/__init__.py +0 -0
- django_spire/notification/email/tests/factories.py +35 -0
- django_spire/notification/email/tests/test_apps.py +24 -0
- django_spire/notification/email/tests/test_models.py +52 -0
- django_spire/notification/email/tests/test_processor.py +92 -0
- django_spire/notification/email/tests/test_querysets.py +43 -0
- django_spire/notification/exceptions.py +17 -2
- django_spire/notification/managers.py +7 -1
- django_spire/notification/maps.py +4 -1
- django_spire/notification/mixins.py +2 -0
- django_spire/notification/models.py +3 -1
- django_spire/notification/processors/notification.py +12 -5
- django_spire/notification/processors/processor.py +2 -0
- django_spire/notification/processors/tests/__init__.py +0 -0
- django_spire/notification/processors/tests/test_notification.py +106 -0
- django_spire/notification/push/admin.py +10 -1
- django_spire/notification/push/apps.py +3 -1
- django_spire/notification/push/models.py +2 -3
- django_spire/notification/push/tests/__init__.py +0 -0
- django_spire/notification/push/tests/test_apps.py +24 -0
- django_spire/notification/push/tests/test_models.py +28 -0
- django_spire/notification/querysets.py +7 -1
- django_spire/notification/sms/admin.py +2 -0
- django_spire/notification/sms/apps.py +4 -1
- django_spire/notification/sms/automations.py +2 -0
- django_spire/notification/sms/choices.py +2 -0
- django_spire/notification/sms/exceptions.py +19 -5
- django_spire/notification/sms/helper.py +33 -23
- django_spire/notification/sms/models.py +5 -1
- django_spire/notification/sms/processor.py +20 -20
- django_spire/notification/sms/querysets.py +2 -0
- django_spire/notification/sms/tests/factories.py +33 -0
- django_spire/notification/sms/tests/test_apps.py +24 -0
- django_spire/notification/sms/tests/test_automation.py +38 -0
- django_spire/notification/sms/tests/test_choices.py +15 -0
- django_spire/notification/sms/tests/test_consts.py +17 -0
- django_spire/notification/sms/tests/test_exceptions.py +27 -0
- django_spire/notification/sms/tests/test_helper.py +50 -0
- django_spire/notification/sms/tests/test_models.py +81 -0
- django_spire/notification/sms/tests/test_processor.py +107 -0
- django_spire/notification/sms/tests/test_tools.py +25 -11
- django_spire/notification/sms/tools.py +16 -5
- django_spire/notification/sms/urls/__init__.py +3 -1
- django_spire/notification/sms/urls/media_urls.py +2 -0
- django_spire/notification/sms/views/media_views.py +14 -4
- django_spire/notification/tests/__init__.py +0 -0
- django_spire/notification/tests/factories.py +26 -0
- django_spire/notification/tests/test_admin.py +55 -0
- django_spire/notification/tests/test_apps.py +30 -0
- django_spire/notification/tests/test_automation.py +18 -0
- django_spire/notification/tests/test_choices.py +59 -0
- django_spire/notification/tests/test_exceptions.py +58 -0
- django_spire/notification/tests/test_managers.py +100 -0
- django_spire/notification/tests/test_maps.py +31 -0
- django_spire/notification/tests/test_models.py +76 -0
- django_spire/notification/tests/test_querysets.py +184 -0
- django_spire/notification/tests/test_utils.py +23 -0
- django_spire/notification/urls.py +3 -1
- django_spire/notification/utils.py +3 -1
- django_spire/settings.py +3 -0
- django_spire/theme/tests/test_context_processor.py +15 -13
- django_spire/theme/tests/test_enums.py +2 -2
- django_spire/theme/tests/test_filesystem.py +2 -5
- django_spire/theme/tests/test_integration.py +12 -12
- django_spire/theme/tests/test_model.py +40 -38
- django_spire/theme/tests/test_views/test_json_views.py +33 -33
- django_spire/theme/urls/json_urls.py +3 -0
- django_spire/theme/urls/page_urls.py +3 -0
- django_spire/urls.py +19 -15
- django_spire/utils.py +13 -4
- {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/METADATA +2 -2
- {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/RECORD +534 -362
- {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/licenses/LICENSE.md +1 -1
- django_spire/contrib/options/tests/test_unit.py +0 -148
- django_spire/contrib/seeding/tests/test_seeding.py +0 -25
- django_spire/core/tests/test_templatetags.py +0 -117
- django_spire/core/tests/tests_shortcuts.py +0 -73
- django_spire/history/activity/tests.py +0 -3
- django_spire/history/activity/views.py +0 -3
- django_spire/knowledge/collection/tests/test_services/test_transformation_service.py +0 -71
- django_spire/notification/app/tests.py +0 -3
- {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/WHEEL +0 -0
- {django_spire-0.23.7.dist-info → django_spire-0.23.9.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from django.contrib.auth import get_user
|
|
4
|
+
from django.urls import reverse
|
|
5
|
+
|
|
6
|
+
from django_spire.auth.user.tests.factories import create_user
|
|
7
|
+
from django_spire.core.tests.test_cases import BaseTestCase
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LoginViewTestCase(BaseTestCase):
|
|
11
|
+
def setUp(self) -> None:
|
|
12
|
+
super().setUp()
|
|
13
|
+
|
|
14
|
+
self.client.logout()
|
|
15
|
+
self.user = create_user(username='testuser', email='test@example.com')
|
|
16
|
+
self.user.set_password('testpassword123')
|
|
17
|
+
self.user.save()
|
|
18
|
+
|
|
19
|
+
def test_login_page_loads(self) -> None:
|
|
20
|
+
response = self.client.get(reverse('django_spire:auth:admin:login'))
|
|
21
|
+
assert response.status_code == 200
|
|
22
|
+
|
|
23
|
+
def test_login_with_valid_credentials(self) -> None:
|
|
24
|
+
response = self.client.post(
|
|
25
|
+
reverse('django_spire:auth:admin:login'),
|
|
26
|
+
data={'username': 'testuser', 'password': 'testpassword123'}
|
|
27
|
+
)
|
|
28
|
+
assert response.status_code == 302
|
|
29
|
+
|
|
30
|
+
def test_login_with_invalid_credentials(self) -> None:
|
|
31
|
+
response = self.client.post(
|
|
32
|
+
reverse('django_spire:auth:admin:login'),
|
|
33
|
+
data={'username': 'testuser', 'password': 'wrongpassword'}
|
|
34
|
+
)
|
|
35
|
+
assert response.status_code == 200
|
|
36
|
+
|
|
37
|
+
def test_login_with_nonexistent_user(self) -> None:
|
|
38
|
+
response = self.client.post(
|
|
39
|
+
reverse('django_spire:auth:admin:login'),
|
|
40
|
+
data={'username': 'nonexistent', 'password': 'password'}
|
|
41
|
+
)
|
|
42
|
+
assert response.status_code == 200
|
|
43
|
+
|
|
44
|
+
def test_login_with_empty_username(self) -> None:
|
|
45
|
+
response = self.client.post(
|
|
46
|
+
reverse('django_spire:auth:admin:login'),
|
|
47
|
+
data={'username': '', 'password': 'testpassword123'}
|
|
48
|
+
)
|
|
49
|
+
assert response.status_code == 200
|
|
50
|
+
|
|
51
|
+
def test_login_with_empty_password(self) -> None:
|
|
52
|
+
response = self.client.post(
|
|
53
|
+
reverse('django_spire:auth:admin:login'),
|
|
54
|
+
data={'username': 'testuser', 'password': ''}
|
|
55
|
+
)
|
|
56
|
+
assert response.status_code == 200
|
|
57
|
+
|
|
58
|
+
def test_login_inactive_user(self) -> None:
|
|
59
|
+
self.user.is_active = False
|
|
60
|
+
self.user.save()
|
|
61
|
+
response = self.client.post(
|
|
62
|
+
reverse('django_spire:auth:admin:login'),
|
|
63
|
+
data={'username': 'testuser', 'password': 'testpassword123'}
|
|
64
|
+
)
|
|
65
|
+
assert response.status_code == 200
|
|
66
|
+
|
|
67
|
+
def test_login_with_whitespace_username(self) -> None:
|
|
68
|
+
response = self.client.post(
|
|
69
|
+
reverse('django_spire:auth:admin:login'),
|
|
70
|
+
data={'username': ' ', 'password': 'testpassword123'}
|
|
71
|
+
)
|
|
72
|
+
assert response.status_code == 200
|
|
73
|
+
|
|
74
|
+
def test_login_case_sensitive_username(self) -> None:
|
|
75
|
+
response = self.client.post(
|
|
76
|
+
reverse('django_spire:auth:admin:login'),
|
|
77
|
+
data={'username': 'TESTUSER', 'password': 'testpassword123'}
|
|
78
|
+
)
|
|
79
|
+
assert response.status_code == 200
|
|
80
|
+
|
|
81
|
+
def test_login_case_sensitive_password(self) -> None:
|
|
82
|
+
response = self.client.post(
|
|
83
|
+
reverse('django_spire:auth:admin:login'),
|
|
84
|
+
data={'username': 'testuser', 'password': 'TESTPASSWORD123'}
|
|
85
|
+
)
|
|
86
|
+
assert response.status_code == 200
|
|
87
|
+
|
|
88
|
+
def test_login_sets_session(self) -> None:
|
|
89
|
+
self.client.post(
|
|
90
|
+
reverse('django_spire:auth:admin:login'),
|
|
91
|
+
data={'username': 'testuser', 'password': 'testpassword123'}
|
|
92
|
+
)
|
|
93
|
+
user = get_user(self.client)
|
|
94
|
+
assert user.is_authenticated
|
|
95
|
+
|
|
96
|
+
def test_login_with_sql_injection_attempt(self) -> None:
|
|
97
|
+
response = self.client.post(
|
|
98
|
+
reverse('django_spire:auth:admin:login'),
|
|
99
|
+
data={'username': "'; DROP TABLE users; --", 'password': 'password'}
|
|
100
|
+
)
|
|
101
|
+
assert response.status_code == 200
|
|
102
|
+
|
|
103
|
+
def test_login_with_xss_attempt(self) -> None:
|
|
104
|
+
response = self.client.post(
|
|
105
|
+
reverse('django_spire:auth:admin:login'),
|
|
106
|
+
data={'username': '<script>alert("xss")</script>', 'password': 'password'}
|
|
107
|
+
)
|
|
108
|
+
assert response.status_code == 200
|
|
109
|
+
|
|
110
|
+
def test_login_with_very_long_username(self) -> None:
|
|
111
|
+
response = self.client.post(
|
|
112
|
+
reverse('django_spire:auth:admin:login'),
|
|
113
|
+
data={'username': 'a' * 1000, 'password': 'password'}
|
|
114
|
+
)
|
|
115
|
+
assert response.status_code == 200
|
|
116
|
+
|
|
117
|
+
def test_login_with_very_long_password(self) -> None:
|
|
118
|
+
response = self.client.post(
|
|
119
|
+
reverse('django_spire:auth:admin:login'),
|
|
120
|
+
data={'username': 'testuser', 'password': 'a' * 1000}
|
|
121
|
+
)
|
|
122
|
+
assert response.status_code == 200
|
|
123
|
+
|
|
124
|
+
def test_login_with_unicode_username(self) -> None:
|
|
125
|
+
response = self.client.post(
|
|
126
|
+
reverse('django_spire:auth:admin:login'),
|
|
127
|
+
data={'username': 'tëstüsér', 'password': 'password'}
|
|
128
|
+
)
|
|
129
|
+
assert response.status_code == 200
|
|
130
|
+
|
|
131
|
+
def test_login_with_unicode_password(self) -> None:
|
|
132
|
+
response = self.client.post(
|
|
133
|
+
reverse('django_spire:auth:admin:login'),
|
|
134
|
+
data={'username': 'testuser', 'password': 'pässwörd'}
|
|
135
|
+
)
|
|
136
|
+
assert response.status_code == 200
|
|
137
|
+
|
|
138
|
+
def test_login_get_request_does_not_authenticate(self) -> None:
|
|
139
|
+
response = self.client.get(
|
|
140
|
+
reverse('django_spire:auth:admin:login'),
|
|
141
|
+
data={'username': 'testuser', 'password': 'testpassword123'}
|
|
142
|
+
)
|
|
143
|
+
user = get_user(self.client)
|
|
144
|
+
assert not user.is_authenticated
|
|
145
|
+
assert response.status_code == 200
|
|
146
|
+
|
|
147
|
+
def test_login_with_null_bytes(self) -> None:
|
|
148
|
+
response = self.client.post(
|
|
149
|
+
reverse('django_spire:auth:admin:login'),
|
|
150
|
+
data={'username': 'test\x00user', 'password': 'password'}
|
|
151
|
+
)
|
|
152
|
+
assert response.status_code == 200
|
|
153
|
+
|
|
154
|
+
def test_login_with_newline_in_username(self) -> None:
|
|
155
|
+
response = self.client.post(
|
|
156
|
+
reverse('django_spire:auth:admin:login'),
|
|
157
|
+
data={'username': 'test\nuser', 'password': 'password'}
|
|
158
|
+
)
|
|
159
|
+
assert response.status_code == 200
|
|
160
|
+
|
|
161
|
+
def test_login_with_tab_in_username(self) -> None:
|
|
162
|
+
response = self.client.post(
|
|
163
|
+
reverse('django_spire:auth:admin:login'),
|
|
164
|
+
data={'username': 'test\tuser', 'password': 'password'}
|
|
165
|
+
)
|
|
166
|
+
assert response.status_code == 200
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class PasswordChangeViewTestCase(BaseTestCase):
|
|
170
|
+
def setUp(self) -> None:
|
|
171
|
+
super().setUp()
|
|
172
|
+
|
|
173
|
+
self.user = create_user(username='testuser')
|
|
174
|
+
self.user.set_password('oldpassword123')
|
|
175
|
+
self.user.save()
|
|
176
|
+
self.client.force_login(self.user)
|
|
177
|
+
|
|
178
|
+
def test_password_change_page_loads(self) -> None:
|
|
179
|
+
response = self.client.get(reverse('django_spire:auth:admin:password_change'))
|
|
180
|
+
assert response.status_code == 200
|
|
181
|
+
|
|
182
|
+
def test_password_change_success(self) -> None:
|
|
183
|
+
response = self.client.post(
|
|
184
|
+
reverse('django_spire:auth:admin:password_change'),
|
|
185
|
+
data={
|
|
186
|
+
'old_password': 'oldpassword123',
|
|
187
|
+
'new_password1': 'newpassword456',
|
|
188
|
+
'new_password2': 'newpassword456'
|
|
189
|
+
}
|
|
190
|
+
)
|
|
191
|
+
assert response.status_code == 302
|
|
192
|
+
self.user.refresh_from_db()
|
|
193
|
+
assert self.user.check_password('newpassword456')
|
|
194
|
+
|
|
195
|
+
def test_password_change_wrong_old_password(self) -> None:
|
|
196
|
+
response = self.client.post(
|
|
197
|
+
reverse('django_spire:auth:admin:password_change'),
|
|
198
|
+
data={
|
|
199
|
+
'old_password': 'wrongoldpassword',
|
|
200
|
+
'new_password1': 'newpassword456',
|
|
201
|
+
'new_password2': 'newpassword456'
|
|
202
|
+
}
|
|
203
|
+
)
|
|
204
|
+
assert response.status_code == 200
|
|
205
|
+
self.user.refresh_from_db()
|
|
206
|
+
assert self.user.check_password('oldpassword123')
|
|
207
|
+
|
|
208
|
+
def test_password_change_mismatched_passwords(self) -> None:
|
|
209
|
+
response = self.client.post(
|
|
210
|
+
reverse('django_spire:auth:admin:password_change'),
|
|
211
|
+
data={
|
|
212
|
+
'old_password': 'oldpassword123',
|
|
213
|
+
'new_password1': 'newpassword456',
|
|
214
|
+
'new_password2': 'differentpassword789'
|
|
215
|
+
}
|
|
216
|
+
)
|
|
217
|
+
assert response.status_code == 200
|
|
218
|
+
|
|
219
|
+
def test_password_change_empty_new_password(self) -> None:
|
|
220
|
+
response = self.client.post(
|
|
221
|
+
reverse('django_spire:auth:admin:password_change'),
|
|
222
|
+
data={
|
|
223
|
+
'old_password': 'oldpassword123',
|
|
224
|
+
'new_password1': '',
|
|
225
|
+
'new_password2': ''
|
|
226
|
+
}
|
|
227
|
+
)
|
|
228
|
+
assert response.status_code == 200
|
|
229
|
+
|
|
230
|
+
def test_password_change_requires_login(self) -> None:
|
|
231
|
+
self.client.logout()
|
|
232
|
+
response = self.client.get(reverse('django_spire:auth:admin:password_change'))
|
|
233
|
+
assert response.status_code == 302
|
|
234
|
+
|
|
235
|
+
def test_password_change_same_as_old(self) -> None:
|
|
236
|
+
response = self.client.post(
|
|
237
|
+
reverse('django_spire:auth:admin:password_change'),
|
|
238
|
+
data={
|
|
239
|
+
'old_password': 'oldpassword123',
|
|
240
|
+
'new_password1': 'oldpassword123',
|
|
241
|
+
'new_password2': 'oldpassword123'
|
|
242
|
+
}
|
|
243
|
+
)
|
|
244
|
+
assert response.status_code in (200, 302)
|
|
245
|
+
|
|
246
|
+
def test_password_change_with_unicode(self) -> None:
|
|
247
|
+
response = self.client.post(
|
|
248
|
+
reverse('django_spire:auth:admin:password_change'),
|
|
249
|
+
data={
|
|
250
|
+
'old_password': 'oldpassword123',
|
|
251
|
+
'new_password1': 'nëwpässwörd456',
|
|
252
|
+
'new_password2': 'nëwpässwörd456'
|
|
253
|
+
}
|
|
254
|
+
)
|
|
255
|
+
assert response.status_code == 302
|
|
256
|
+
self.user.refresh_from_db()
|
|
257
|
+
assert self.user.check_password('nëwpässwörd456')
|
|
258
|
+
|
|
259
|
+
def test_password_change_very_long_password(self) -> None:
|
|
260
|
+
long_password = 'a' * 128
|
|
261
|
+
response = self.client.post(
|
|
262
|
+
reverse('django_spire:auth:admin:password_change'),
|
|
263
|
+
data={
|
|
264
|
+
'old_password': 'oldpassword123',
|
|
265
|
+
'new_password1': long_password,
|
|
266
|
+
'new_password2': long_password
|
|
267
|
+
}
|
|
268
|
+
)
|
|
269
|
+
assert response.status_code == 302
|
|
270
|
+
|
|
271
|
+
def test_password_change_with_spaces(self) -> None:
|
|
272
|
+
response = self.client.post(
|
|
273
|
+
reverse('django_spire:auth:admin:password_change'),
|
|
274
|
+
data={
|
|
275
|
+
'old_password': 'oldpassword123',
|
|
276
|
+
'new_password1': 'new password 456',
|
|
277
|
+
'new_password2': 'new password 456'
|
|
278
|
+
}
|
|
279
|
+
)
|
|
280
|
+
assert response.status_code == 302
|
|
281
|
+
self.user.refresh_from_db()
|
|
282
|
+
assert self.user.check_password('new password 456')
|
|
283
|
+
|
|
284
|
+
def test_password_change_preserves_session(self) -> None:
|
|
285
|
+
self.client.post(
|
|
286
|
+
reverse('django_spire:auth:admin:password_change'),
|
|
287
|
+
data={
|
|
288
|
+
'old_password': 'oldpassword123',
|
|
289
|
+
'new_password1': 'newpassword456',
|
|
290
|
+
'new_password2': 'newpassword456'
|
|
291
|
+
}
|
|
292
|
+
)
|
|
293
|
+
user = get_user(self.client)
|
|
294
|
+
assert user.is_authenticated
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
class PasswordChangeDoneViewTestCase(BaseTestCase):
|
|
298
|
+
def test_password_change_done_page_loads(self) -> None:
|
|
299
|
+
response = self.client.get(reverse('django_spire:auth:admin:password_change_done'))
|
|
300
|
+
assert response.status_code == 200
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class PasswordResetViewTestCase(BaseTestCase):
|
|
304
|
+
def setUp(self) -> None:
|
|
305
|
+
super().setUp()
|
|
306
|
+
|
|
307
|
+
self.user = create_user(username='testuser', email='test@example.com')
|
|
308
|
+
|
|
309
|
+
def test_password_reset_page_loads(self) -> None:
|
|
310
|
+
response = self.client.get(reverse('django_spire:auth:admin:password_reset'))
|
|
311
|
+
assert response.status_code == 200
|
|
312
|
+
|
|
313
|
+
def test_password_reset_with_valid_email(self) -> None:
|
|
314
|
+
response = self.client.post(
|
|
315
|
+
reverse('django_spire:auth:admin:password_reset'),
|
|
316
|
+
data={'email': 'test@example.com'}
|
|
317
|
+
)
|
|
318
|
+
assert response.status_code == 302
|
|
319
|
+
|
|
320
|
+
def test_password_reset_with_nonexistent_email(self) -> None:
|
|
321
|
+
response = self.client.post(
|
|
322
|
+
reverse('django_spire:auth:admin:password_reset'),
|
|
323
|
+
data={'email': 'nonexistent@example.com'}
|
|
324
|
+
)
|
|
325
|
+
assert response.status_code == 302
|
|
326
|
+
|
|
327
|
+
def test_password_reset_with_empty_email(self) -> None:
|
|
328
|
+
response = self.client.post(
|
|
329
|
+
reverse('django_spire:auth:admin:password_reset'),
|
|
330
|
+
data={'email': ''}
|
|
331
|
+
)
|
|
332
|
+
assert response.status_code == 200
|
|
333
|
+
|
|
334
|
+
def test_password_reset_with_invalid_email(self) -> None:
|
|
335
|
+
response = self.client.post(
|
|
336
|
+
reverse('django_spire:auth:admin:password_reset'),
|
|
337
|
+
data={'email': 'invalid-email'}
|
|
338
|
+
)
|
|
339
|
+
assert response.status_code == 200
|
|
340
|
+
|
|
341
|
+
def test_password_reset_with_uppercase_email(self) -> None:
|
|
342
|
+
response = self.client.post(
|
|
343
|
+
reverse('django_spire:auth:admin:password_reset'),
|
|
344
|
+
data={'email': 'TEST@EXAMPLE.COM'}
|
|
345
|
+
)
|
|
346
|
+
assert response.status_code == 302
|
|
347
|
+
|
|
348
|
+
def test_password_reset_with_mixed_case_email(self) -> None:
|
|
349
|
+
response = self.client.post(
|
|
350
|
+
reverse('django_spire:auth:admin:password_reset'),
|
|
351
|
+
data={'email': 'Test@Example.Com'}
|
|
352
|
+
)
|
|
353
|
+
assert response.status_code == 302
|
|
354
|
+
|
|
355
|
+
def test_password_reset_with_whitespace_email(self) -> None:
|
|
356
|
+
response = self.client.post(
|
|
357
|
+
reverse('django_spire:auth:admin:password_reset'),
|
|
358
|
+
data={'email': ' test@example.com '}
|
|
359
|
+
)
|
|
360
|
+
assert response.status_code in (200, 302)
|
|
361
|
+
|
|
362
|
+
def test_password_reset_inactive_user(self) -> None:
|
|
363
|
+
self.user.is_active = False
|
|
364
|
+
self.user.save()
|
|
365
|
+
response = self.client.post(
|
|
366
|
+
reverse('django_spire:auth:admin:password_reset'),
|
|
367
|
+
data={'email': 'test@example.com'}
|
|
368
|
+
)
|
|
369
|
+
assert response.status_code == 302
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
class PasswordResetDoneViewTestCase(BaseTestCase):
|
|
373
|
+
def test_password_reset_done_page_loads(self) -> None:
|
|
374
|
+
response = self.client.get(reverse('django_spire:auth:admin:password_reset_done'))
|
|
375
|
+
assert response.status_code == 200
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
class LogoutRedirectViewTestCase(BaseTestCase):
|
|
379
|
+
def test_logout_redirects(self) -> None:
|
|
380
|
+
response = self.client.get(reverse('django_spire:auth:redirect:logout'))
|
|
381
|
+
assert response.status_code == 302
|
|
382
|
+
|
|
383
|
+
def test_logout_logs_out_user(self) -> None:
|
|
384
|
+
self.client.get(reverse('django_spire:auth:redirect:logout'))
|
|
385
|
+
response = self.client.get(reverse('django_spire:auth:admin:password_change'))
|
|
386
|
+
assert response.status_code != 200
|
|
387
|
+
|
|
388
|
+
def test_logout_redirects_to_login(self) -> None:
|
|
389
|
+
response = self.client.get(reverse('django_spire:auth:redirect:logout'))
|
|
390
|
+
assert 'login' in response.url.lower()
|
|
391
|
+
|
|
392
|
+
def test_logout_clears_session(self) -> None:
|
|
393
|
+
self.client.get(reverse('django_spire:auth:redirect:logout'))
|
|
394
|
+
user = get_user(self.client)
|
|
395
|
+
assert not user.is_authenticated
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
class LoginRedirectViewTestCase(BaseTestCase):
|
|
399
|
+
def test_login_redirect_for_authenticated_user(self) -> None:
|
|
400
|
+
response = self.client.get(reverse('django_spire:auth:redirect:login'))
|
|
401
|
+
assert response.status_code == 302
|
|
402
|
+
|
|
403
|
+
def test_login_redirect_for_unauthenticated_user(self) -> None:
|
|
404
|
+
self.client.logout()
|
|
405
|
+
response = self.client.get(reverse('django_spire:auth:redirect:login'))
|
|
406
|
+
assert response.status_code == 302
|
django_spire/auth/user/apps.py
CHANGED
django_spire/auth/user/forms.py
CHANGED
|
@@ -69,6 +69,15 @@ class EditUserForm(forms.ModelForm):
|
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
class RegisterUserForm(forms.ModelForm):
|
|
72
|
+
def clean_email(self) -> str:
|
|
73
|
+
email = self.cleaned_data.get('email')
|
|
74
|
+
|
|
75
|
+
if not email:
|
|
76
|
+
message = 'Email is required.'
|
|
77
|
+
raise forms.ValidationError(message)
|
|
78
|
+
|
|
79
|
+
return email
|
|
80
|
+
|
|
72
81
|
def clean_password(self) -> str:
|
|
73
82
|
password = self.cleaned_data.get('password')
|
|
74
83
|
|
django_spire/auth/user/models.py
CHANGED
|
@@ -14,7 +14,7 @@ class AuthUser(User, ActivityMixin):
|
|
|
14
14
|
@classmethod
|
|
15
15
|
def base_breadcrumb(cls) -> Breadcrumbs:
|
|
16
16
|
crumbs = Breadcrumbs()
|
|
17
|
-
crumbs.add_breadcrumb('Users', reverse('django_spire:auth:user:list'))
|
|
17
|
+
crumbs.add_breadcrumb('Users', reverse('django_spire:auth:user:page:list'))
|
|
18
18
|
return crumbs
|
|
19
19
|
|
|
20
20
|
def breadcrumbs(self) -> Breadcrumbs:
|
|
@@ -7,7 +7,7 @@ def create_user(
|
|
|
7
7
|
username: str,
|
|
8
8
|
**kwargs
|
|
9
9
|
) -> AuthUser:
|
|
10
|
-
user,
|
|
10
|
+
user, _ = AuthUser.objects.get_or_create(
|
|
11
11
|
username=username,
|
|
12
12
|
defaults={
|
|
13
13
|
**kwargs
|
|
@@ -18,20 +18,21 @@ def create_user(
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def create_super_user(password: str = 'stratus', **kwargs) -> AuthUser:
|
|
21
|
-
|
|
21
|
+
defaults = {
|
|
22
|
+
'email': 'bobert@stratusadv.com',
|
|
23
|
+
'first_name': 'Bob',
|
|
24
|
+
'last_name': 'Robertson',
|
|
25
|
+
'is_superuser': True,
|
|
26
|
+
'is_staff': True,
|
|
27
|
+
**kwargs
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
user, _ = AuthUser.objects.update_or_create(
|
|
22
31
|
username='stratus',
|
|
23
|
-
defaults=
|
|
24
|
-
'email': 'bobert@stratusadv.com',
|
|
25
|
-
'first_name': 'Bob',
|
|
26
|
-
'last_name': 'Robertson',
|
|
27
|
-
'is_superuser': True,
|
|
28
|
-
'is_staff': True,
|
|
29
|
-
**kwargs
|
|
30
|
-
}
|
|
32
|
+
defaults=defaults
|
|
31
33
|
)
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
user.save()
|
|
35
|
+
user.set_password(password)
|
|
36
|
+
user.save()
|
|
36
37
|
|
|
37
38
|
return user
|
|
@@ -1,7 +1,171 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from django_spire.auth.user.models import AuthUser
|
|
4
|
+
from django_spire.auth.user.tests.factories import create_super_user, create_user
|
|
3
5
|
from django_spire.core.tests.test_cases import BaseTestCase
|
|
4
6
|
|
|
5
7
|
|
|
6
|
-
class
|
|
7
|
-
|
|
8
|
+
class CreateUserTestCase(BaseTestCase):
|
|
9
|
+
def test_creates_user(self) -> None:
|
|
10
|
+
user = create_user(username='testuser')
|
|
11
|
+
assert isinstance(user, AuthUser)
|
|
12
|
+
assert AuthUser.objects.filter(username='testuser').exists()
|
|
13
|
+
|
|
14
|
+
def test_creates_user_with_username(self) -> None:
|
|
15
|
+
user = create_user(username='myuser')
|
|
16
|
+
assert user.username == 'myuser'
|
|
17
|
+
|
|
18
|
+
def test_creates_user_with_kwargs(self) -> None:
|
|
19
|
+
user = create_user(
|
|
20
|
+
username='testuser',
|
|
21
|
+
first_name='Test',
|
|
22
|
+
last_name='User',
|
|
23
|
+
email='test@example.com'
|
|
24
|
+
)
|
|
25
|
+
assert user.first_name == 'Test'
|
|
26
|
+
assert user.last_name == 'User'
|
|
27
|
+
assert user.email == 'test@example.com'
|
|
28
|
+
|
|
29
|
+
def test_returns_existing_user_if_exists(self) -> None:
|
|
30
|
+
user1 = create_user(username='existinguser')
|
|
31
|
+
user2 = create_user(username='existinguser')
|
|
32
|
+
assert user1.pk == user2.pk
|
|
33
|
+
|
|
34
|
+
def test_get_or_create_behavior(self) -> None:
|
|
35
|
+
user1 = create_user(username='getorcreate', first_name='First')
|
|
36
|
+
user2 = create_user(username='getorcreate', first_name='Different')
|
|
37
|
+
assert user1.pk == user2.pk
|
|
38
|
+
assert user1.first_name == 'First'
|
|
39
|
+
|
|
40
|
+
def test_creates_with_is_active_true(self) -> None:
|
|
41
|
+
user = create_user(username='activeuser', is_active=True)
|
|
42
|
+
assert user.is_active
|
|
43
|
+
|
|
44
|
+
def test_creates_with_is_active_false(self) -> None:
|
|
45
|
+
user = create_user(username='inactiveuser', is_active=False)
|
|
46
|
+
assert not user.is_active
|
|
47
|
+
|
|
48
|
+
def test_user_has_pk(self) -> None:
|
|
49
|
+
user = create_user(username='pkuser')
|
|
50
|
+
assert user.pk is not None
|
|
51
|
+
|
|
52
|
+
def test_creates_unique_users(self) -> None:
|
|
53
|
+
user1 = create_user(username='user1')
|
|
54
|
+
user2 = create_user(username='user2')
|
|
55
|
+
assert user1.pk != user2.pk
|
|
56
|
+
|
|
57
|
+
def test_creates_with_unicode_username(self) -> None:
|
|
58
|
+
user = create_user(username='tëstüsér')
|
|
59
|
+
assert user.username == 'tëstüsér'
|
|
60
|
+
|
|
61
|
+
def test_creates_with_email(self) -> None:
|
|
62
|
+
user = create_user(username='emailuser', email='email@example.com')
|
|
63
|
+
assert user.email == 'email@example.com'
|
|
64
|
+
|
|
65
|
+
def test_creates_with_empty_email(self) -> None:
|
|
66
|
+
user = create_user(username='noemail', email='')
|
|
67
|
+
assert user.email == ''
|
|
68
|
+
|
|
69
|
+
def test_creates_with_staff_status(self) -> None:
|
|
70
|
+
user = create_user(username='staffuser', is_staff=True)
|
|
71
|
+
assert user.is_staff
|
|
72
|
+
|
|
73
|
+
def test_creates_without_staff_status(self) -> None:
|
|
74
|
+
user = create_user(username='nonstaffuser', is_staff=False)
|
|
75
|
+
assert not user.is_staff
|
|
76
|
+
|
|
77
|
+
def test_creates_with_superuser_status(self) -> None:
|
|
78
|
+
user = create_user(username='superuser', is_superuser=True)
|
|
79
|
+
assert user.is_superuser
|
|
80
|
+
|
|
81
|
+
def test_creates_without_superuser_status(self) -> None:
|
|
82
|
+
user = create_user(username='normaluser', is_superuser=False)
|
|
83
|
+
assert not user.is_superuser
|
|
84
|
+
|
|
85
|
+
def test_user_is_persisted(self) -> None:
|
|
86
|
+
user = create_user(username='persisteduser')
|
|
87
|
+
assert AuthUser.objects.filter(pk=user.pk).exists()
|
|
88
|
+
|
|
89
|
+
def test_creates_with_long_username(self) -> None:
|
|
90
|
+
long_username = 'a' * 150
|
|
91
|
+
user = create_user(username=long_username)
|
|
92
|
+
assert user.username == long_username
|
|
93
|
+
|
|
94
|
+
def test_creates_with_special_characters_username(self) -> None:
|
|
95
|
+
user = create_user(username='test.user@example.com')
|
|
96
|
+
assert user.username == 'test.user@example.com'
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class CreateSuperUserTestCase(BaseTestCase):
|
|
100
|
+
def test_creates_superuser(self) -> None:
|
|
101
|
+
user = create_super_user()
|
|
102
|
+
assert isinstance(user, AuthUser)
|
|
103
|
+
assert user.is_superuser
|
|
104
|
+
|
|
105
|
+
def test_superuser_is_staff(self) -> None:
|
|
106
|
+
user = create_super_user()
|
|
107
|
+
assert user.is_staff
|
|
108
|
+
|
|
109
|
+
def test_superuser_default_username(self) -> None:
|
|
110
|
+
user = create_super_user()
|
|
111
|
+
assert user.username == 'stratus'
|
|
112
|
+
|
|
113
|
+
def test_superuser_default_email(self) -> None:
|
|
114
|
+
user = create_super_user()
|
|
115
|
+
assert user.email == 'bobert@stratusadv.com'
|
|
116
|
+
|
|
117
|
+
def test_superuser_default_names(self) -> None:
|
|
118
|
+
user = create_super_user()
|
|
119
|
+
assert user.first_name == 'Bob'
|
|
120
|
+
assert user.last_name == 'Robertson'
|
|
121
|
+
|
|
122
|
+
def test_superuser_default_password(self) -> None:
|
|
123
|
+
user = create_super_user()
|
|
124
|
+
assert user.check_password('stratus')
|
|
125
|
+
|
|
126
|
+
def test_superuser_custom_password(self) -> None:
|
|
127
|
+
user = create_super_user(password='custompassword') # noqa: S106
|
|
128
|
+
assert user.check_password('custompassword')
|
|
129
|
+
|
|
130
|
+
def test_superuser_returns_existing(self) -> None:
|
|
131
|
+
user1 = create_super_user()
|
|
132
|
+
user2 = create_super_user()
|
|
133
|
+
assert user1.pk == user2.pk
|
|
134
|
+
|
|
135
|
+
def test_superuser_has_pk(self) -> None:
|
|
136
|
+
user = create_super_user()
|
|
137
|
+
assert user.pk is not None
|
|
138
|
+
|
|
139
|
+
def test_superuser_is_active(self) -> None:
|
|
140
|
+
user = create_super_user()
|
|
141
|
+
assert user.is_active
|
|
142
|
+
|
|
143
|
+
def test_superuser_has_all_permissions(self) -> None:
|
|
144
|
+
user = create_super_user()
|
|
145
|
+
assert user.has_perm('any.permission')
|
|
146
|
+
|
|
147
|
+
def test_superuser_with_custom_kwargs(self) -> None:
|
|
148
|
+
user = create_super_user(first_name='Custom', last_name='Name')
|
|
149
|
+
assert user.first_name in ('Bob', 'Custom')
|
|
150
|
+
|
|
151
|
+
def test_superuser_is_persisted(self) -> None:
|
|
152
|
+
user = create_super_user()
|
|
153
|
+
assert AuthUser.objects.filter(pk=user.pk).exists()
|
|
154
|
+
|
|
155
|
+
def test_superuser_can_authenticate(self) -> None:
|
|
156
|
+
user = create_super_user()
|
|
157
|
+
assert user.check_password('stratus')
|
|
158
|
+
|
|
159
|
+
def test_superuser_password_is_hashed(self) -> None:
|
|
160
|
+
user = create_super_user()
|
|
161
|
+
assert user.password != 'stratus' # noqa: S105
|
|
162
|
+
|
|
163
|
+
def test_multiple_calls_same_user(self) -> None:
|
|
164
|
+
users = [create_super_user() for _ in range(5)]
|
|
165
|
+
pks = [u.pk for u in users]
|
|
166
|
+
assert len(set(pks)) == 1
|
|
167
|
+
|
|
168
|
+
def test_superuser_get_full_name(self) -> None:
|
|
169
|
+
user = create_super_user()
|
|
170
|
+
full_name = user.get_full_name()
|
|
171
|
+
assert 'Bob' in full_name or 'Robertson' in full_name
|