django-pfx 1.7.2.dev14__tar.gz → 1.7.3.dev2__tar.gz
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_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/PKG-INFO +1 -1
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/django_pfx.egg-info/PKG-INFO +1 -1
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/django_pfx.egg-info/SOURCES.txt +2 -0
- django_pfx-1.7.3.dev2/pfx/pfxcore/locale/fr/LC_MESSAGES/django.mo +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.po +34 -22
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/models/mfa_user_mixin.py +41 -12
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/views/authentication_views.py +6 -13
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/views/fields.py +10 -3
- django_pfx-1.7.3.dev2/tests/migrations/0006_rename_otp_enabled_user_mfa_authenticator_enabled_and_more.py +29 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/tests/__init__.py +2 -1
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/tests/test_auth_api.py +24 -23
- django_pfx-1.7.3.dev2/tests/tests/test_fields_time.py +133 -0
- django_pfx-1.7.2.dev14/pfx/pfxcore/locale/fr/LC_MESSAGES/django.mo +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/.gitignore +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/.gitlab-ci.yml +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/.pre-commit-config.yaml +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/LICENSE +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/MANIFEST.in +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/README.md +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/django_pfx.egg-info/dependency_links.txt +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/django_pfx.egg-info/requires.txt +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/django_pfx.egg-info/top_level.txt +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/doc/Makefile +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/doc/conf.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/doc/index.rst +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/doc/source/api.views.rst +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/doc/source/authentication.md +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/doc/source/decorator.md +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/doc/source/generate_openapi.md +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/doc/source/getting_started.md +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/doc/source/internationalisation.md +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/doc/source/model.md +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/doc/source/pfx_views.md +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/doc/source/profiling.md +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/doc/source/settings.md +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/doc/source/testing.md +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/img/pfx.png +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/img/pfx.svg +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/make_messages +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/manage.py +0 -0
- {django_pfx-1.7.2.dev14/tests_custom_user/settings → django_pfx-1.7.3.dev2/pfx}/__init__.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/__init__.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/apidoc/__init__.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/apidoc/parameters.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/apidoc/schema.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/apidoc/tags.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/apps.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/decorator/__init__.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/decorator/rest.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/default_settings.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/exceptions.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/fields/__init__.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/fields/decimal_field.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/fields/media_field.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/fields/minutes_duration_field.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/fields/rich_text_field.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/http/__init__.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/http/json_response.py +0 -0
- {django_pfx-1.7.2.dev14/tests_custom_user/migrations → django_pfx-1.7.3.dev2/pfx/pfxcore/management}/__init__.py +0 -0
- {django_pfx-1.7.2.dev14/tests_custom_user → django_pfx-1.7.3.dev2/pfx/pfxcore/management/commands}/__init__.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/management/commands/makeapidoc.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/management/commands/profile.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/middleware/__init__.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/middleware/authentication.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/middleware/locale.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/middleware/profiling.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/migrations/0001_initial.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/migrations/0002_pfxpermissionsuser.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/migrations/0003_delete_pfxpermissionsuser.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/migrations/0004_alter_loginban_failed_counter_and_more.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/migrations/__init__.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/migrations/operations/__init__.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/migrations/operations/permissions.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/models/__init__.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/models/abstract_pfx_base_user.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/models/cache_mixins.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/models/login_ban.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/models/not_null_fields.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/models/ordered_model_mixin.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/models/pfx_models.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/models/pfx_user.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/models/user_filtered_queryset_mixin.py +0 -0
- {django_pfx-1.7.2.dev14/tests_base_user/settings → django_pfx-1.7.3.dev2/pfx/pfxcore/serializers}/__init__.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/serializers/json.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/settings.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/shortcuts.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/sms/__init__.py +0 -0
- {django_pfx-1.7.2.dev14/tests_base_user/migrations → django_pfx-1.7.3.dev2/pfx/pfxcore/sms/backends}/__init__.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/sms/backends/base.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/sms/backends/console.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/storage/__init__.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/storage/exceptions.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/storage/local_storage.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/storage/s3_storage.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/templates/registration/otp_code_email.txt +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/templates/registration/otp_code_subject.txt +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/templates/registration/password_reset_email.txt +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/templates/registration/password_reset_subject.txt +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/templates/registration/welcome_email.txt +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/templates/registration/welcome_subject.txt +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/test.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/urls.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/views/__init__.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/views/filters_views.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/views/locale_views.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/views/media_rest_view_mixin.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/views/ordered_rest_view_mixin.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/views/parameters/__init__.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/views/parameters/date_format.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/views/parameters/groups.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/views/parameters/list_count.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/views/parameters/list_items.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/views/parameters/list_mode.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/views/parameters/list_order.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/views/parameters/list_search.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/views/parameters/media_redirect.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/views/parameters/meta_fields.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/views/parameters/meta_filters.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/views/parameters/meta_orders.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/views/parameters/subset.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/views/parameters/subset_limit.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/views/parameters/subset_offset.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/views/parameters/subset_page.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/views/parameters/subset_page_size.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/views/parameters/subset_page_subset.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/views/rest_views.py +0 -0
- {django_pfx-1.7.2.dev14/tests_base_user → django_pfx-1.7.3.dev2/pfx/settings}/__init__.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/settings/dev.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pyproject.toml +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/requirements.txt +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/serve-doc +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/setup.cfg +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/setup.py +0 -0
- {django_pfx-1.7.2.dev14/tests/settings → django_pfx-1.7.3.dev2/tests}/__init__.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/apps.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/locale/fr/LC_MESSAGES/django.po +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/migrations/0001_initial.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/migrations/0002_alter_book_cover.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/migrations/0003_book_local_file.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/migrations/0004_mfausermixin_fields.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/migrations/0005_mfausermixin_fields_fix.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/migrations/__init__.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/models.py +0 -0
- {django_pfx-1.7.2.dev14/tests → django_pfx-1.7.3.dev2/tests/settings}/__init__.py +0 -0
- {django_pfx-1.7.2.dev14/tests_custom_user → django_pfx-1.7.3.dev2/tests}/settings/ci.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/settings/common.py +0 -0
- {django_pfx-1.7.2.dev14/tests_custom_user → django_pfx-1.7.3.dev2/tests}/settings/dev.py +0 -0
- {django_pfx-1.7.2.dev14/tests_custom_user → django_pfx-1.7.3.dev2/tests}/settings/dev_custom_example.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/settings/dev_default.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/tests/basic_api_errors.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/tests/basic_api_test.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/tests/test_api_doc.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/tests/test_api_doc_search.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/tests/test_body_mixin.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/tests/test_cache.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/tests/test_client.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/tests/test_fields_choices.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/tests/test_fields_date.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/tests/test_fields_decimal.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/tests/test_fields_minutes_duration.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/tests/test_fields_one2many.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/tests/test_fields_rich_text.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/tests/test_filters.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/tests/test_locale_api.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/tests/test_ordered_rest_view_mixin.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/tests/test_perm_tests.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/tests/test_permissions.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/tests/test_perms_api.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/tests/test_post_migrate_groups_update.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/tests/test_profiling_middleware.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/tests/test_settings.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/tests/test_shortcuts.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/tests/test_timezone_middleware.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/tests/test_tools.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/tests/test_user_queryset.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/tests/test_view_decorators.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/tests/test_view_fields.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/urls.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests/views.py +0 -0
- {django_pfx-1.7.2.dev14/pfx/settings → django_pfx-1.7.3.dev2/tests_base_user}/__init__.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests_base_user/migrations/0001_initial.py +0 -0
- {django_pfx-1.7.2.dev14/pfx/pfxcore/sms/backends → django_pfx-1.7.3.dev2/tests_base_user/migrations}/__init__.py +0 -0
- {django_pfx-1.7.2.dev14/pfx/pfxcore/serializers → django_pfx-1.7.3.dev2/tests_base_user/settings}/__init__.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests_base_user/settings/ci.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests_base_user/settings/common.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests_base_user/settings/dev.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests_base_user/settings/dev_custom_example.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests_base_user/settings/dev_default.py +0 -0
- {django_pfx-1.7.2.dev14/tests_custom_user → django_pfx-1.7.3.dev2/tests_base_user}/tests/__init__.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests_base_user/tests/test_api.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests_base_user/tests/test_auth_api.py +0 -0
- {django_pfx-1.7.2.dev14/tests_custom_user → django_pfx-1.7.3.dev2/tests_base_user}/urls.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests_base_user/views.py +0 -0
- {django_pfx-1.7.2.dev14/pfx/pfxcore/management/commands → django_pfx-1.7.3.dev2/tests_custom_user}/__init__.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests_custom_user/migrations/0001_initial.py +0 -0
- {django_pfx-1.7.2.dev14/pfx/pfxcore/management → django_pfx-1.7.3.dev2/tests_custom_user/migrations}/__init__.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests_custom_user/models.py +0 -0
- {django_pfx-1.7.2.dev14/pfx → django_pfx-1.7.3.dev2/tests_custom_user/settings}/__init__.py +0 -0
- {django_pfx-1.7.2.dev14/tests → django_pfx-1.7.3.dev2/tests_custom_user}/settings/ci.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests_custom_user/settings/common.py +0 -0
- {django_pfx-1.7.2.dev14/tests → django_pfx-1.7.3.dev2/tests_custom_user}/settings/dev.py +0 -0
- {django_pfx-1.7.2.dev14/tests → django_pfx-1.7.3.dev2/tests_custom_user}/settings/dev_custom_example.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests_custom_user/settings/dev_default.py +0 -0
- {django_pfx-1.7.2.dev14/tests_base_user → django_pfx-1.7.3.dev2/tests_custom_user}/tests/__init__.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests_custom_user/tests/test_api.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests_custom_user/tests/test_auth_api.py +0 -0
- {django_pfx-1.7.2.dev14/tests_base_user → django_pfx-1.7.3.dev2/tests_custom_user}/urls.py +0 -0
- {django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/tests_custom_user/views.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-pfx
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.3.dev2
|
|
4
4
|
Summary: Django PFX is a toolkit designed to streamline the development of RESTful APIs using the Django framework.
|
|
5
5
|
Author: Hervé Martinet
|
|
6
6
|
Author-email: herve.martinet@gmail.com
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-pfx
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.3.dev2
|
|
4
4
|
Summary: Django PFX is a toolkit designed to streamline the development of RESTful APIs using the Django framework.
|
|
5
5
|
Author: Hervé Martinet
|
|
6
6
|
Author-email: herve.martinet@gmail.com
|
|
@@ -136,6 +136,7 @@ tests/migrations/0002_alter_book_cover.py
|
|
|
136
136
|
tests/migrations/0003_book_local_file.py
|
|
137
137
|
tests/migrations/0004_mfausermixin_fields.py
|
|
138
138
|
tests/migrations/0005_mfausermixin_fields_fix.py
|
|
139
|
+
tests/migrations/0006_rename_otp_enabled_user_mfa_authenticator_enabled_and_more.py
|
|
139
140
|
tests/migrations/__init__.py
|
|
140
141
|
tests/settings/__init__.py
|
|
141
142
|
tests/settings/ci.py
|
|
@@ -158,6 +159,7 @@ tests/tests/test_fields_decimal.py
|
|
|
158
159
|
tests/tests/test_fields_minutes_duration.py
|
|
159
160
|
tests/tests/test_fields_one2many.py
|
|
160
161
|
tests/tests/test_fields_rich_text.py
|
|
162
|
+
tests/tests/test_fields_time.py
|
|
161
163
|
tests/tests/test_filters.py
|
|
162
164
|
tests/tests/test_locale_api.py
|
|
163
165
|
tests/tests/test_ordered_rest_view_mixin.py
|
|
Binary file
|
{django_pfx-1.7.2.dev14 → django_pfx-1.7.3.dev2}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.po
RENAMED
|
@@ -7,7 +7,7 @@ msgid ""
|
|
|
7
7
|
msgstr ""
|
|
8
8
|
"Project-Id-Version: \n"
|
|
9
9
|
"Report-Msgid-Bugs-To: \n"
|
|
10
|
-
"POT-Creation-Date: 2026-04-
|
|
10
|
+
"POT-Creation-Date: 2026-04-23 14:57+0200\n"
|
|
11
11
|
"PO-Revision-Date: 2021-06-22 23:31+0200\n"
|
|
12
12
|
"Last-Translator: \n"
|
|
13
13
|
"Language-Team: \n"
|
|
@@ -61,11 +61,11 @@ msgstr ""
|
|
|
61
61
|
|
|
62
62
|
#: models/abstract_pfx_base_user.py:52
|
|
63
63
|
msgid "user"
|
|
64
|
-
msgstr ""
|
|
64
|
+
msgstr "utilisateur"
|
|
65
65
|
|
|
66
66
|
#: models/abstract_pfx_base_user.py:53
|
|
67
67
|
msgid "users"
|
|
68
|
-
msgstr ""
|
|
68
|
+
msgstr "utilisateurs"
|
|
69
69
|
|
|
70
70
|
#: models/login_ban.py:54
|
|
71
71
|
msgid "Username"
|
|
@@ -115,21 +115,29 @@ msgstr "Numéro de téléphone temporaire (SMS)"
|
|
|
115
115
|
msgid "SMS MFA enabled"
|
|
116
116
|
msgstr "MFA SMS activé"
|
|
117
117
|
|
|
118
|
-
#: models/mfa_user_mixin.py:
|
|
118
|
+
#: models/mfa_user_mixin.py:38
|
|
119
119
|
msgid "Email MFA enabled"
|
|
120
120
|
msgstr "MFA par email activé"
|
|
121
121
|
|
|
122
|
-
#: models/mfa_user_mixin.py:
|
|
122
|
+
#: models/mfa_user_mixin.py:41
|
|
123
123
|
msgid "MFA setup dismissed"
|
|
124
124
|
msgstr "Configuration MFA annulée"
|
|
125
125
|
|
|
126
|
-
#: models/mfa_user_mixin.py:
|
|
126
|
+
#: models/mfa_user_mixin.py:44
|
|
127
127
|
msgid "Authenticator MFA enabled"
|
|
128
128
|
msgstr "Application MFA activée"
|
|
129
129
|
|
|
130
|
-
#: models/mfa_user_mixin.py:
|
|
131
|
-
msgid "
|
|
132
|
-
msgstr "
|
|
130
|
+
#: models/mfa_user_mixin.py:61
|
|
131
|
+
msgid "MFA enabled"
|
|
132
|
+
msgstr "MFA activé"
|
|
133
|
+
|
|
134
|
+
#: models/mfa_user_mixin.py:68
|
|
135
|
+
msgid "MFA setup required"
|
|
136
|
+
msgstr "Configuration MFA requise"
|
|
137
|
+
|
|
138
|
+
#: models/mfa_user_mixin.py:74
|
|
139
|
+
msgid "Is MFA forced"
|
|
140
|
+
msgstr "MFA est forcé"
|
|
133
141
|
|
|
134
142
|
#: models/pfx_models.py:14
|
|
135
143
|
#, python-format
|
|
@@ -247,51 +255,55 @@ msgstr ""
|
|
|
247
255
|
"Votre connexion est temporairement désactivée après plusieurs tentatives "
|
|
248
256
|
"infructueuses, veuillez réessayer dans {seconds} secondes."
|
|
249
257
|
|
|
250
|
-
#: views/authentication_views.py:
|
|
258
|
+
#: views/authentication_views.py:209
|
|
251
259
|
msgid "Successful login"
|
|
252
260
|
msgstr "Connexion réussie"
|
|
253
261
|
|
|
254
|
-
#: views/authentication_views.py:
|
|
262
|
+
#: views/authentication_views.py:386
|
|
255
263
|
msgid "This field is required."
|
|
256
264
|
msgstr "Ce champ est requis."
|
|
257
265
|
|
|
258
|
-
#: views/authentication_views.py:
|
|
266
|
+
#: views/authentication_views.py:397
|
|
259
267
|
msgid "Code sent."
|
|
260
268
|
msgstr "Code envoyé."
|
|
261
269
|
|
|
262
|
-
#: views/authentication_views.py:
|
|
270
|
+
#: views/authentication_views.py:460 views/authentication_views.py:628
|
|
263
271
|
msgid "password updated successfully"
|
|
264
272
|
msgstr "le mot de passe a été mis à jour avec succès"
|
|
265
273
|
|
|
266
|
-
#: views/authentication_views.py:
|
|
274
|
+
#: views/authentication_views.py:465
|
|
267
275
|
msgid "Incorrect password"
|
|
268
276
|
msgstr "Mot de passe incorrect"
|
|
269
277
|
|
|
270
|
-
#: views/authentication_views.py:
|
|
278
|
+
#: views/authentication_views.py:468 views/authentication_views.py:637
|
|
271
279
|
msgid "Empty password is not allowed"
|
|
272
280
|
msgstr "Un mot de passe vide n’est pas autorisé"
|
|
273
281
|
|
|
274
|
-
#: views/authentication_views.py:
|
|
282
|
+
#: views/authentication_views.py:557
|
|
275
283
|
msgid "User and token are valid"
|
|
276
284
|
msgstr "L'utilisateur et le token sont valides"
|
|
277
285
|
|
|
278
|
-
#: views/authentication_views.py:
|
|
286
|
+
#: views/authentication_views.py:559
|
|
279
287
|
msgid "User or token is invalid"
|
|
280
288
|
msgstr "L'utilisateur ou le token est invalide"
|
|
281
289
|
|
|
282
|
-
#: views/authentication_views.py:
|
|
290
|
+
#: views/authentication_views.py:671
|
|
283
291
|
msgid "OTP is already enabled"
|
|
284
292
|
msgstr "OTP est déjà activé"
|
|
285
293
|
|
|
286
|
-
#: views/authentication_views.py:
|
|
294
|
+
#: views/authentication_views.py:711
|
|
295
|
+
msgid "OTP enabled"
|
|
296
|
+
msgstr "OTP activé"
|
|
297
|
+
|
|
298
|
+
#: views/authentication_views.py:712 views/authentication_views.py:748
|
|
287
299
|
msgid "Invalid code"
|
|
288
300
|
msgstr "Code invalide"
|
|
289
301
|
|
|
290
|
-
#: views/authentication_views.py:
|
|
302
|
+
#: views/authentication_views.py:747
|
|
291
303
|
msgid "OTP disabled"
|
|
292
304
|
msgstr "OTP désactivé"
|
|
293
305
|
|
|
294
|
-
#: views/authentication_views.py:
|
|
306
|
+
#: views/authentication_views.py:1008
|
|
295
307
|
msgid ""
|
|
296
308
|
"If the email address you entered is correct, you will receive an email from "
|
|
297
309
|
"us with instructions to reset your password."
|
|
@@ -300,7 +312,7 @@ msgstr ""
|
|
|
300
312
|
"un courrier électronique de notre part contenant des instructions pour "
|
|
301
313
|
"réinitialiser votre mot de passe."
|
|
302
314
|
|
|
303
|
-
#: views/authentication_views.py:
|
|
315
|
+
#: views/authentication_views.py:1082
|
|
304
316
|
msgid "A new authentication code has been sent by email."
|
|
305
317
|
msgstr "Un nouveau code d'authentification a été envoyé par e-mail."
|
|
306
318
|
|
|
@@ -32,24 +32,51 @@ class MFAUserMixin(models.Model):
|
|
|
32
32
|
sms_phone_number_tmp = models.CharField(
|
|
33
33
|
_("Temporary SMS phone number"), max_length=32, null=True, blank=True)
|
|
34
34
|
#: SMS MFA enabled.
|
|
35
|
-
|
|
35
|
+
mfa_sms_enabled = models.BooleanField(_("SMS MFA enabled"), default=False)
|
|
36
36
|
#: Email MFA enabled.
|
|
37
|
-
|
|
37
|
+
mfa_email_enabled = models.BooleanField(
|
|
38
|
+
_("Email MFA enabled"), default=False)
|
|
38
39
|
#: User has dismissed or completed the MFA setup screen.
|
|
39
40
|
mfa_setup_dismissed = models.BooleanField(
|
|
40
41
|
_("MFA setup dismissed"), default=False)
|
|
41
42
|
#: Authenticator app MFA explicitly enabled by the user.
|
|
42
|
-
|
|
43
|
+
mfa_authenticator_enabled = models.BooleanField(
|
|
43
44
|
_("Authenticator MFA enabled"), default=False)
|
|
44
45
|
|
|
45
46
|
class Meta:
|
|
46
47
|
abstract = True
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
def auth_json_repr(self, **kw):
|
|
50
|
+
res = super().auth_json_repr(
|
|
51
|
+
is_mfa=self.is_mfa,
|
|
52
|
+
mfa_authenticator_enabled=self.mfa_authenticator_enabled,
|
|
53
|
+
mfa_sms_enabled=self.mfa_sms_enabled,
|
|
54
|
+
mfa_email_enabled=self.mfa_email_enabled,
|
|
55
|
+
mfa_setup_dismissed=self.mfa_setup_dismissed,
|
|
56
|
+
mfa_setup_required=self.mfa_setup_required,
|
|
57
|
+
mfa_forced=self.mfa_forced,
|
|
58
|
+
**kw)
|
|
59
|
+
return res
|
|
60
|
+
|
|
61
|
+
@rest_property(_("MFA enabled"), "BooleanField")
|
|
62
|
+
def is_mfa(self):
|
|
63
|
+
return (
|
|
64
|
+
self.mfa_authenticator_enabled
|
|
65
|
+
or self.mfa_sms_enabled
|
|
66
|
+
or self.mfa_email_enabled)
|
|
67
|
+
|
|
68
|
+
@rest_property(_("MFA setup required"), "BooleanField")
|
|
69
|
+
def mfa_setup_required(self):
|
|
70
|
+
return (
|
|
71
|
+
self.mfa_forced and
|
|
72
|
+
not self.mfa_setup_dismissed)
|
|
73
|
+
|
|
74
|
+
@rest_property(_("Is MFA forced"), "BooleanField")
|
|
75
|
+
def mfa_forced(self):
|
|
76
|
+
"""Return True if MFA is forced for this user.
|
|
77
|
+
|
|
78
|
+
Can be overridden to implement per-organisation logic."""
|
|
79
|
+
return settings.PFX_MFA_FORCE
|
|
53
80
|
|
|
54
81
|
def need_otp_response_extra(self):
|
|
55
82
|
"""Add extra attribute to response when need_otp=True."""
|
|
@@ -77,9 +104,10 @@ class MFAUserMixin(models.Model):
|
|
|
77
104
|
if self.is_otp_valid(otp_code, tmp=True):
|
|
78
105
|
self.otp_secret_token = self.otp_secret_token_tmp
|
|
79
106
|
self.otp_secret_token_tmp = None
|
|
80
|
-
self.
|
|
107
|
+
self.mfa_authenticator_enabled = True
|
|
81
108
|
self.save(update_fields=[
|
|
82
|
-
'otp_secret_token', 'otp_secret_token_tmp',
|
|
109
|
+
'otp_secret_token', 'otp_secret_token_tmp',
|
|
110
|
+
'mfa_authenticator_enabled'])
|
|
83
111
|
return True
|
|
84
112
|
return False
|
|
85
113
|
|
|
@@ -89,8 +117,9 @@ class MFAUserMixin(models.Model):
|
|
|
89
117
|
Remove the OTP secret token.
|
|
90
118
|
"""
|
|
91
119
|
self.otp_secret_token = None
|
|
92
|
-
self.
|
|
93
|
-
self.save(update_fields=[
|
|
120
|
+
self.mfa_authenticator_enabled = False
|
|
121
|
+
self.save(update_fields=[
|
|
122
|
+
'otp_secret_token', 'mfa_authenticator_enabled'])
|
|
94
123
|
|
|
95
124
|
def get_otp_setup_uri(self, tmp=False, with_color=True):
|
|
96
125
|
"""Return the setup URL for OTP activation.
|
|
@@ -190,10 +190,6 @@ class AuthenticationView(
|
|
|
190
190
|
user.last_login = tz.now()
|
|
191
191
|
user.save(update_fields=['last_login'])
|
|
192
192
|
token = self._prepare_token(user, mode, remember_me)
|
|
193
|
-
mfa_setup_required = (
|
|
194
|
-
isinstance(user, MFAUserMixin) and
|
|
195
|
-
self.is_mfa_forced(user) and
|
|
196
|
-
not user.mfa_setup_dismissed)
|
|
197
193
|
if mode == 'cookie':
|
|
198
194
|
if remember_me:
|
|
199
195
|
expires = datetime.now(
|
|
@@ -203,7 +199,6 @@ class AuthenticationView(
|
|
|
203
199
|
|
|
204
200
|
res = JsonResponse(dict(
|
|
205
201
|
need_otp=False,
|
|
206
|
-
mfa_setup_required=mfa_setup_required,
|
|
207
202
|
user=self.get_user_information(user)))
|
|
208
203
|
res.set_cookie(
|
|
209
204
|
'token', token, secure=settings.PFX_COOKIE_SECURE,
|
|
@@ -213,7 +208,6 @@ class AuthenticationView(
|
|
|
213
208
|
return JsonResponse(dict(
|
|
214
209
|
message=message or _("Successful login"),
|
|
215
210
|
need_otp=False,
|
|
216
|
-
mfa_setup_required=mfa_setup_required,
|
|
217
211
|
token=token,
|
|
218
212
|
user=self.get_user_information(user)))
|
|
219
213
|
|
|
@@ -259,11 +253,12 @@ class AuthenticationView(
|
|
|
259
253
|
return []
|
|
260
254
|
result = []
|
|
261
255
|
for backend_id in settings.PFX_MFA_BACKENDS:
|
|
262
|
-
if backend_id == 'authenticator'
|
|
256
|
+
if (backend_id == 'authenticator'
|
|
257
|
+
and user.mfa_authenticator_enabled):
|
|
263
258
|
result.append(backend_id)
|
|
264
|
-
elif backend_id == 'sms' and user.
|
|
259
|
+
elif backend_id == 'sms' and user.mfa_sms_enabled:
|
|
265
260
|
result.append(backend_id)
|
|
266
|
-
elif backend_id == 'email' and user.
|
|
261
|
+
elif backend_id == 'email' and user.mfa_email_enabled:
|
|
267
262
|
result.append(backend_id)
|
|
268
263
|
return result
|
|
269
264
|
|
|
@@ -271,7 +266,7 @@ class AuthenticationView(
|
|
|
271
266
|
"""Return True if MFA is forced for this user.
|
|
272
267
|
|
|
273
268
|
Can be overridden to implement per-organisation logic."""
|
|
274
|
-
return
|
|
269
|
+
return isinstance(user, MFAUserMixin) and user.mfa_forced
|
|
275
270
|
|
|
276
271
|
def get_active_mfa_backend(self, user):
|
|
277
272
|
"""Return the highest-priority active MFA backend ID for this user.
|
|
@@ -509,8 +504,6 @@ class AuthenticationView(
|
|
|
509
504
|
|
|
510
505
|
:param user: The user"""
|
|
511
506
|
info = user.auth_json_repr()
|
|
512
|
-
if isinstance(user, MFAUserMixin):
|
|
513
|
-
info.update(is_otp=user.is_otp)
|
|
514
507
|
return info
|
|
515
508
|
|
|
516
509
|
@classmethod
|
|
@@ -673,7 +666,7 @@ class AuthenticationView(
|
|
|
673
666
|
if not isinstance(self.request.user, MFAUserMixin):
|
|
674
667
|
logger.error("User must inherit MFAUserMixin to use MFA")
|
|
675
668
|
raise NotFoundError()
|
|
676
|
-
if self.request.user.
|
|
669
|
+
if self.request.user.mfa_authenticator_enabled:
|
|
677
670
|
return JsonResponse(
|
|
678
671
|
dict(message=_("OTP is already enabled")), status=400)
|
|
679
672
|
self.request.user.enable_otp()
|
|
@@ -39,6 +39,7 @@ class FieldType:
|
|
|
39
39
|
FloatField = "FloatField"
|
|
40
40
|
DecimalField = "DecimalField"
|
|
41
41
|
DateField = "DateField"
|
|
42
|
+
TimeField = "TimeField"
|
|
42
43
|
DateTimeField = "DateTimeField"
|
|
43
44
|
MinutesDurationField = "MinutesDurationField"
|
|
44
45
|
MediaField = "MediaField"
|
|
@@ -57,6 +58,7 @@ class FieldType:
|
|
|
57
58
|
(models.DecimalField, DecimalField),
|
|
58
59
|
(models.DateTimeField, DateTimeField),
|
|
59
60
|
(models.DateField, DateField),
|
|
61
|
+
(models.TimeField, TimeField),
|
|
60
62
|
(models.TextField, TextField),
|
|
61
63
|
(models.CharField, CharField),
|
|
62
64
|
(models.URLField, CharField),
|
|
@@ -78,6 +80,7 @@ class FieldType:
|
|
|
78
80
|
FloatField: "number",
|
|
79
81
|
DecimalField: "string",
|
|
80
82
|
DateField: "string",
|
|
83
|
+
TimeField: "string",
|
|
81
84
|
DateTimeField: "string",
|
|
82
85
|
MinutesDurationField: "object",
|
|
83
86
|
MediaField: "object",
|
|
@@ -305,9 +308,11 @@ class ViewField:
|
|
|
305
308
|
def to_apidoc(self, request=False):
|
|
306
309
|
res = dict(type=FieldType.to_apidoc(self.field_type))
|
|
307
310
|
if self.field_type == FieldType.DateField:
|
|
308
|
-
res['format'] = 'date'
|
|
311
|
+
res['format'] = 'date' # Official specification name.
|
|
312
|
+
elif self.field_type == FieldType.TimeField:
|
|
313
|
+
res['format'] = 'time' # Custom name, does no exists.
|
|
309
314
|
elif self.field_type == FieldType.DateTimeField:
|
|
310
|
-
res['format'] = 'date-time'
|
|
315
|
+
res['format'] = 'date-time' # Official specification name.
|
|
311
316
|
elif self.field_type == FieldType.ModelObject:
|
|
312
317
|
properties = {}
|
|
313
318
|
if self.related_model:
|
|
@@ -442,7 +447,9 @@ class ViewModelField(ViewField):
|
|
|
442
447
|
value = (value['value']
|
|
443
448
|
if isinstance(value, dict) and 'value' in value
|
|
444
449
|
else value)
|
|
445
|
-
elif self.field_type in (
|
|
450
|
+
elif self.field_type in (
|
|
451
|
+
FieldType.DateField, FieldType.TimeField,
|
|
452
|
+
FieldType.DateTimeField):
|
|
446
453
|
try:
|
|
447
454
|
value = value and field.to_python(value) or None
|
|
448
455
|
except ValidationError as e:
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Generated by Django 4.2.30 on 2026-04-23 07:33
|
|
2
|
+
# flake8: noqa
|
|
3
|
+
|
|
4
|
+
from django.db import migrations
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
('tests', '0005_mfausermixin_fields_fix'),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.RenameField(
|
|
15
|
+
model_name='user',
|
|
16
|
+
old_name='otp_enabled',
|
|
17
|
+
new_name='mfa_authenticator_enabled',
|
|
18
|
+
),
|
|
19
|
+
migrations.RenameField(
|
|
20
|
+
model_name='user',
|
|
21
|
+
old_name='email_enabled',
|
|
22
|
+
new_name='mfa_email_enabled',
|
|
23
|
+
),
|
|
24
|
+
migrations.RenameField(
|
|
25
|
+
model_name='user',
|
|
26
|
+
old_name='sms_enabled',
|
|
27
|
+
new_name='mfa_sms_enabled',
|
|
28
|
+
),
|
|
29
|
+
]
|
|
@@ -2,7 +2,7 @@ from .basic_api_errors import BasicAPIErrorTest
|
|
|
2
2
|
from .basic_api_test import BasicAPITest
|
|
3
3
|
from .test_api_doc import ApiDocTest
|
|
4
4
|
from .test_api_doc_search import TestAPIDocSearch
|
|
5
|
-
from .test_auth_api import AuthAPITest
|
|
5
|
+
from .test_auth_api import AuthAPITest, MFALoginTest
|
|
6
6
|
from .test_body_mixin import TestBodyMixin
|
|
7
7
|
from .test_cache import TestCache
|
|
8
8
|
from .test_client import TestApiClient
|
|
@@ -12,6 +12,7 @@ from .test_fields_decimal import TestFieldsDecimal
|
|
|
12
12
|
from .test_fields_minutes_duration import TestFieldsMinutesDuration
|
|
13
13
|
from .test_fields_one2many import TestFieldsOne2Many
|
|
14
14
|
from .test_fields_rich_text import TestFieldsRichText
|
|
15
|
+
from .test_fields_time import TestFieldsTime
|
|
15
16
|
from .test_filters import FiltersTest
|
|
16
17
|
from .test_locale_api import LocaleAPITest
|
|
17
18
|
from .test_ordered_rest_view_mixin import TestOrderedRestViewMixin
|
|
@@ -924,7 +924,7 @@ class MFALoginTest(TestAssertMixin, TransactionTestCase):
|
|
|
924
924
|
|
|
925
925
|
def enable_otp(self, user):
|
|
926
926
|
user.otp_secret_token = pyotp.random_base32()
|
|
927
|
-
user.
|
|
927
|
+
user.mfa_authenticator_enabled = True
|
|
928
928
|
user.save()
|
|
929
929
|
user.refresh_from_db()
|
|
930
930
|
return pyotp.totp.TOTP(user.otp_secret_token)
|
|
@@ -949,10 +949,10 @@ class MFALoginTest(TestAssertMixin, TransactionTestCase):
|
|
|
949
949
|
@override_settings(
|
|
950
950
|
PFX_OTP_IMAGE="https://example.org/fake.png",
|
|
951
951
|
PFX_OTP_COLOR="FF0000")
|
|
952
|
-
def
|
|
952
|
+
def test_mfa_authenticator_enabled(self):
|
|
953
953
|
self.client.login(username='jrr.tolkien', password='RIGHT PASSWORD')
|
|
954
954
|
|
|
955
|
-
self.assertEqual(self.user1.
|
|
955
|
+
self.assertEqual(self.user1.is_mfa, False)
|
|
956
956
|
response = self.client.get('/api/auth/otp/setup-uri')
|
|
957
957
|
self.assertRC(response, 200)
|
|
958
958
|
self.assertJIn(response, 'setup_uri', "otpauth://totp/")
|
|
@@ -979,9 +979,9 @@ class MFALoginTest(TestAssertMixin, TransactionTestCase):
|
|
|
979
979
|
self.user1.refresh_from_db()
|
|
980
980
|
self.assertEqual(len(self.user1.otp_secret_token), 32)
|
|
981
981
|
self.assertIsNone(self.user1.otp_secret_token_tmp)
|
|
982
|
-
self.assertEqual(self.user1.
|
|
982
|
+
self.assertEqual(self.user1.is_mfa, True)
|
|
983
983
|
|
|
984
|
-
def
|
|
984
|
+
def test_mfa_authenticator_enabled_bad_code(self):
|
|
985
985
|
self.client.login(username='jrr.tolkien', password='RIGHT PASSWORD')
|
|
986
986
|
|
|
987
987
|
response = self.client.get('/api/auth/otp/setup-uri')
|
|
@@ -1010,7 +1010,7 @@ class MFALoginTest(TestAssertMixin, TransactionTestCase):
|
|
|
1010
1010
|
def test_otp_disable(self):
|
|
1011
1011
|
totp = self.enable_otp(self.user1)
|
|
1012
1012
|
|
|
1013
|
-
self.assertEqual(self.user1.
|
|
1013
|
+
self.assertEqual(self.user1.is_mfa, True)
|
|
1014
1014
|
self.otp_login(self.user1, 'RIGHT PASSWORD')
|
|
1015
1015
|
response = self.client.put('/api/auth/otp/disable', dict(
|
|
1016
1016
|
otp_code=totp.now()))
|
|
@@ -1019,7 +1019,7 @@ class MFALoginTest(TestAssertMixin, TransactionTestCase):
|
|
|
1019
1019
|
self.user1.refresh_from_db()
|
|
1020
1020
|
self.assertIsNone(self.user1.otp_secret_token)
|
|
1021
1021
|
self.assertIsNone(self.user1.otp_secret_token_tmp)
|
|
1022
|
-
self.assertEqual(self.user1.
|
|
1022
|
+
self.assertEqual(self.user1.is_mfa, False)
|
|
1023
1023
|
|
|
1024
1024
|
@override_settings(
|
|
1025
1025
|
PFX_HOTP_CODE_VALIDITY=15,
|
|
@@ -1091,8 +1091,8 @@ class MFALoginTest(TestAssertMixin, TransactionTestCase):
|
|
|
1091
1091
|
token=token,
|
|
1092
1092
|
otp_code=totp.now()))
|
|
1093
1093
|
self.assertRC(response, 200)
|
|
1094
|
-
self.assertJE(response, 'user.
|
|
1095
|
-
self.assertJE(response, 'mfa_setup_required', False)
|
|
1094
|
+
self.assertJE(response, 'user.is_mfa', True)
|
|
1095
|
+
self.assertJE(response, 'user.mfa_setup_required', False)
|
|
1096
1096
|
headers = jwt.get_unverified_header(token)
|
|
1097
1097
|
self.assertEqual(headers['pfx_user_pk'], self.user1.pk)
|
|
1098
1098
|
decoded = jwt.decode(
|
|
@@ -1104,6 +1104,7 @@ class MFALoginTest(TestAssertMixin, TransactionTestCase):
|
|
|
1104
1104
|
self.assertEqual(
|
|
1105
1105
|
datetime.fromtimestamp(decoded['exp']),
|
|
1106
1106
|
datetime(2023, 5, 1, 8, 40))
|
|
1107
|
+
response.print()
|
|
1107
1108
|
|
|
1108
1109
|
@override_settings(PFX_TOKEN_LONG_VALIDITY={'days': 1})
|
|
1109
1110
|
def test_otp_login_remember_me(self):
|
|
@@ -1445,7 +1446,7 @@ class MFALoginTest(TestAssertMixin, TransactionTestCase):
|
|
|
1445
1446
|
"""Login with email+authenticator:
|
|
1446
1447
|
email (highest priority) is active backend."""
|
|
1447
1448
|
self.enable_otp(self.user1)
|
|
1448
|
-
self.user1.
|
|
1449
|
+
self.user1.mfa_email_enabled = True
|
|
1449
1450
|
self.user1.save()
|
|
1450
1451
|
with patch(
|
|
1451
1452
|
'pfx.pfxcore.views.authentication_views'
|
|
@@ -1459,19 +1460,19 @@ class MFALoginTest(TestAssertMixin, TransactionTestCase):
|
|
|
1459
1460
|
self.assertJE(response, 'available_backends.@0.id', 'email')
|
|
1460
1461
|
self.assertJE(response, 'available_backends.@1.id', 'authenticator')
|
|
1461
1462
|
|
|
1462
|
-
# --- mfa_setup_required ---
|
|
1463
|
+
# --- user.mfa_setup_required ---
|
|
1463
1464
|
|
|
1464
1465
|
def test_login_success_mfa_setup_required_false_by_default(self):
|
|
1465
1466
|
"""Direct login, no MFA force: mfa_setup_required=False."""
|
|
1466
1467
|
response = self.do_login()
|
|
1467
1468
|
self.assertRC(response, 200)
|
|
1468
1469
|
self.assertJE(response, 'need_otp', False)
|
|
1469
|
-
self.assertJE(response, 'mfa_setup_required', False)
|
|
1470
|
+
self.assertJE(response, 'user.mfa_setup_required', False)
|
|
1470
1471
|
|
|
1471
1472
|
@override_settings(PFX_MFA_FORCE=True)
|
|
1472
1473
|
def test_login_success_mfa_setup_required_true(self):
|
|
1473
1474
|
"""After OTP login, MFA forced, setup not dismissed:
|
|
1474
|
-
mfa_setup_required=True."""
|
|
1475
|
+
user.mfa_setup_required=True."""
|
|
1475
1476
|
totp = self.enable_otp(self.user1)
|
|
1476
1477
|
response = self.do_login()
|
|
1477
1478
|
self.assertRC(response, 200)
|
|
@@ -1479,12 +1480,12 @@ class MFALoginTest(TestAssertMixin, TransactionTestCase):
|
|
|
1479
1480
|
response = self.client.post('/api/auth/otp/login', dict(
|
|
1480
1481
|
token=otp_token, otp_code=totp.now()))
|
|
1481
1482
|
self.assertRC(response, 200)
|
|
1482
|
-
self.assertJE(response, 'mfa_setup_required', True)
|
|
1483
|
+
self.assertJE(response, 'user.mfa_setup_required', True)
|
|
1483
1484
|
|
|
1484
1485
|
@override_settings(PFX_MFA_FORCE=True)
|
|
1485
1486
|
def test_login_success_mfa_setup_required_false_when_dismissed(self):
|
|
1486
1487
|
"""After OTP login, MFA forced, setup dismissed:
|
|
1487
|
-
mfa_setup_required=False."""
|
|
1488
|
+
user.mfa_setup_required=False."""
|
|
1488
1489
|
totp = self.enable_otp(self.user1)
|
|
1489
1490
|
self.user1.mfa_setup_dismissed = True
|
|
1490
1491
|
self.user1.save()
|
|
@@ -1494,14 +1495,14 @@ class MFALoginTest(TestAssertMixin, TransactionTestCase):
|
|
|
1494
1495
|
response = self.client.post('/api/auth/otp/login', dict(
|
|
1495
1496
|
token=otp_token, otp_code=totp.now()))
|
|
1496
1497
|
self.assertRC(response, 200)
|
|
1497
|
-
self.assertJE(response, 'mfa_setup_required', False)
|
|
1498
|
+
self.assertJE(response, 'user.mfa_setup_required', False)
|
|
1498
1499
|
|
|
1499
1500
|
# --- email backend ---
|
|
1500
1501
|
|
|
1501
1502
|
@override_settings(PFX_MFA_BACKENDS=['email'])
|
|
1502
1503
|
def test_login_email_backend_sends_challenge(self):
|
|
1503
1504
|
"""Login with email backend: sends email and returns is_oob=True."""
|
|
1504
|
-
self.user1.
|
|
1505
|
+
self.user1.mfa_email_enabled = True
|
|
1505
1506
|
self.user1.otp_secret_token = pyotp.random_base32()
|
|
1506
1507
|
self.user1.save()
|
|
1507
1508
|
response = self.do_login()
|
|
@@ -1516,7 +1517,7 @@ class MFALoginTest(TestAssertMixin, TransactionTestCase):
|
|
|
1516
1517
|
def test_login_email_backend_generates_token_if_missing(self):
|
|
1517
1518
|
"""Login with email backend auto-generates
|
|
1518
1519
|
otp_secret_token if absent."""
|
|
1519
|
-
self.user1.
|
|
1520
|
+
self.user1.mfa_email_enabled = True
|
|
1520
1521
|
self.user1.save()
|
|
1521
1522
|
self.assertIsNone(self.user1.otp_secret_token)
|
|
1522
1523
|
response = self.do_login()
|
|
@@ -1532,7 +1533,7 @@ class MFALoginTest(TestAssertMixin, TransactionTestCase):
|
|
|
1532
1533
|
def test_login_sms_backend_calls_send_sms(self):
|
|
1533
1534
|
"""Login with SMS backend: calls send_mfa_sms_challenge,
|
|
1534
1535
|
returns is_oob=True."""
|
|
1535
|
-
self.user1.
|
|
1536
|
+
self.user1.mfa_sms_enabled = True
|
|
1536
1537
|
self.user1.otp_secret_token = pyotp.random_base32()
|
|
1537
1538
|
self.user1.save()
|
|
1538
1539
|
with patch(
|
|
@@ -1551,7 +1552,7 @@ class MFALoginTest(TestAssertMixin, TransactionTestCase):
|
|
|
1551
1552
|
def test_login_forced_mfa_fallback_to_email(self):
|
|
1552
1553
|
"""MFA forced with no backend configured:
|
|
1553
1554
|
falls back to email challenge."""
|
|
1554
|
-
# No otp_secret_token,
|
|
1555
|
+
# No otp_secret_token, mfa_sms_enabled=False, mfa_email_enabled=False
|
|
1555
1556
|
response = self.do_login()
|
|
1556
1557
|
self.assertRC(response, 200)
|
|
1557
1558
|
self.assertJE(response, 'need_otp', True)
|
|
@@ -1573,7 +1574,7 @@ class MFALoginTest(TestAssertMixin, TransactionTestCase):
|
|
|
1573
1574
|
@override_settings(PFX_MFA_BACKENDS=['authenticator', 'email'])
|
|
1574
1575
|
def test_mfa_challenge_email_success(self):
|
|
1575
1576
|
"""POST /auth/mfa/challenge with email sends a new code."""
|
|
1576
|
-
self.user1.
|
|
1577
|
+
self.user1.mfa_email_enabled = True
|
|
1577
1578
|
self.user1.otp_secret_token = pyotp.random_base32()
|
|
1578
1579
|
self.user1.save()
|
|
1579
1580
|
otp_token = self._get_otp_token()
|
|
@@ -1590,7 +1591,7 @@ class MFALoginTest(TestAssertMixin, TransactionTestCase):
|
|
|
1590
1591
|
"""POST /auth/mfa/challenge: email allowed even without
|
|
1591
1592
|
email_enabled when MFA forced."""
|
|
1592
1593
|
otp_token = self._get_otp_token()
|
|
1593
|
-
# user1 has
|
|
1594
|
+
# user1 has mfa_email_enabled=False,
|
|
1594
1595
|
# but PFX_MFA_FORCE=True => forced_email is True
|
|
1595
1596
|
response = self.client.post('/api/auth/mfa/challenge', {
|
|
1596
1597
|
'token': otp_token,
|
|
@@ -1641,7 +1642,7 @@ class MFALoginTest(TestAssertMixin, TransactionTestCase):
|
|
|
1641
1642
|
@override_settings(PFX_MFA_BACKENDS=['sms'])
|
|
1642
1643
|
def test_mfa_challenge_sms_success(self):
|
|
1643
1644
|
"""POST /auth/mfa/challenge with sms calls send_mfa_sms_challenge."""
|
|
1644
|
-
self.user1.
|
|
1645
|
+
self.user1.mfa_sms_enabled = True
|
|
1645
1646
|
self.user1.otp_secret_token = pyotp.random_base32()
|
|
1646
1647
|
self.user1.save()
|
|
1647
1648
|
sms_path = (
|