django-pfx 1.7.2.dev14__tar.gz → 1.7.2.dev16__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.
Files changed (207) hide show
  1. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/PKG-INFO +1 -1
  2. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/django_pfx.egg-info/PKG-INFO +1 -1
  3. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/django_pfx.egg-info/SOURCES.txt +1 -0
  4. django_pfx-1.7.2.dev16/pfx/pfxcore/locale/fr/LC_MESSAGES/django.mo +0 -0
  5. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.po +34 -22
  6. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/models/mfa_user_mixin.py +41 -12
  7. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/views/authentication_views.py +6 -13
  8. django_pfx-1.7.2.dev16/tests/migrations/0006_rename_otp_enabled_user_mfa_authenticator_enabled_and_more.py +29 -0
  9. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/tests/__init__.py +1 -1
  10. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/tests/test_auth_api.py +24 -23
  11. django_pfx-1.7.2.dev14/pfx/pfxcore/locale/fr/LC_MESSAGES/django.mo +0 -0
  12. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/.gitignore +0 -0
  13. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/.gitlab-ci.yml +0 -0
  14. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/.pre-commit-config.yaml +0 -0
  15. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/LICENSE +0 -0
  16. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/MANIFEST.in +0 -0
  17. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/README.md +0 -0
  18. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/django_pfx.egg-info/dependency_links.txt +0 -0
  19. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/django_pfx.egg-info/requires.txt +0 -0
  20. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/django_pfx.egg-info/top_level.txt +0 -0
  21. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/doc/Makefile +0 -0
  22. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/doc/conf.py +0 -0
  23. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/doc/index.rst +0 -0
  24. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/doc/source/api.views.rst +0 -0
  25. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/doc/source/authentication.md +0 -0
  26. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/doc/source/decorator.md +0 -0
  27. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/doc/source/generate_openapi.md +0 -0
  28. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/doc/source/getting_started.md +0 -0
  29. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/doc/source/internationalisation.md +0 -0
  30. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/doc/source/model.md +0 -0
  31. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/doc/source/pfx_views.md +0 -0
  32. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/doc/source/profiling.md +0 -0
  33. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/doc/source/settings.md +0 -0
  34. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/doc/source/testing.md +0 -0
  35. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/img/pfx.png +0 -0
  36. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/img/pfx.svg +0 -0
  37. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/make_messages +0 -0
  38. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/manage.py +0 -0
  39. {django_pfx-1.7.2.dev14/tests_custom_user/settings → django_pfx-1.7.2.dev16/pfx}/__init__.py +0 -0
  40. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/__init__.py +0 -0
  41. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/apidoc/__init__.py +0 -0
  42. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/apidoc/parameters.py +0 -0
  43. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/apidoc/schema.py +0 -0
  44. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/apidoc/tags.py +0 -0
  45. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/apps.py +0 -0
  46. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/decorator/__init__.py +0 -0
  47. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/decorator/rest.py +0 -0
  48. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/default_settings.py +0 -0
  49. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/exceptions.py +0 -0
  50. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/fields/__init__.py +0 -0
  51. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/fields/decimal_field.py +0 -0
  52. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/fields/media_field.py +0 -0
  53. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/fields/minutes_duration_field.py +0 -0
  54. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/fields/rich_text_field.py +0 -0
  55. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/http/__init__.py +0 -0
  56. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/http/json_response.py +0 -0
  57. {django_pfx-1.7.2.dev14/tests_custom_user/migrations → django_pfx-1.7.2.dev16/pfx/pfxcore/management}/__init__.py +0 -0
  58. {django_pfx-1.7.2.dev14/tests_custom_user → django_pfx-1.7.2.dev16/pfx/pfxcore/management/commands}/__init__.py +0 -0
  59. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/management/commands/makeapidoc.py +0 -0
  60. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/management/commands/profile.py +0 -0
  61. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/middleware/__init__.py +0 -0
  62. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/middleware/authentication.py +0 -0
  63. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/middleware/locale.py +0 -0
  64. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/middleware/profiling.py +0 -0
  65. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/migrations/0001_initial.py +0 -0
  66. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/migrations/0002_pfxpermissionsuser.py +0 -0
  67. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/migrations/0003_delete_pfxpermissionsuser.py +0 -0
  68. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/migrations/0004_alter_loginban_failed_counter_and_more.py +0 -0
  69. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/migrations/__init__.py +0 -0
  70. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/migrations/operations/__init__.py +0 -0
  71. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/migrations/operations/permissions.py +0 -0
  72. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/models/__init__.py +0 -0
  73. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/models/abstract_pfx_base_user.py +0 -0
  74. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/models/cache_mixins.py +0 -0
  75. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/models/login_ban.py +0 -0
  76. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/models/not_null_fields.py +0 -0
  77. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/models/ordered_model_mixin.py +0 -0
  78. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/models/pfx_models.py +0 -0
  79. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/models/pfx_user.py +0 -0
  80. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/models/user_filtered_queryset_mixin.py +0 -0
  81. {django_pfx-1.7.2.dev14/tests_base_user/settings → django_pfx-1.7.2.dev16/pfx/pfxcore/serializers}/__init__.py +0 -0
  82. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/serializers/json.py +0 -0
  83. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/settings.py +0 -0
  84. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/shortcuts.py +0 -0
  85. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/sms/__init__.py +0 -0
  86. {django_pfx-1.7.2.dev14/tests_base_user/migrations → django_pfx-1.7.2.dev16/pfx/pfxcore/sms/backends}/__init__.py +0 -0
  87. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/sms/backends/base.py +0 -0
  88. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/sms/backends/console.py +0 -0
  89. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/storage/__init__.py +0 -0
  90. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/storage/exceptions.py +0 -0
  91. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/storage/local_storage.py +0 -0
  92. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/storage/s3_storage.py +0 -0
  93. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/templates/registration/otp_code_email.txt +0 -0
  94. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/templates/registration/otp_code_subject.txt +0 -0
  95. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/templates/registration/password_reset_email.txt +0 -0
  96. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/templates/registration/password_reset_subject.txt +0 -0
  97. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/templates/registration/welcome_email.txt +0 -0
  98. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/templates/registration/welcome_subject.txt +0 -0
  99. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/test.py +0 -0
  100. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/urls.py +0 -0
  101. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/views/__init__.py +0 -0
  102. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/views/fields.py +0 -0
  103. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/views/filters_views.py +0 -0
  104. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/views/locale_views.py +0 -0
  105. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/views/media_rest_view_mixin.py +0 -0
  106. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/views/ordered_rest_view_mixin.py +0 -0
  107. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/views/parameters/__init__.py +0 -0
  108. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/views/parameters/date_format.py +0 -0
  109. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/views/parameters/groups.py +0 -0
  110. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/views/parameters/list_count.py +0 -0
  111. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/views/parameters/list_items.py +0 -0
  112. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/views/parameters/list_mode.py +0 -0
  113. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/views/parameters/list_order.py +0 -0
  114. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/views/parameters/list_search.py +0 -0
  115. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/views/parameters/media_redirect.py +0 -0
  116. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/views/parameters/meta_fields.py +0 -0
  117. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/views/parameters/meta_filters.py +0 -0
  118. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/views/parameters/meta_orders.py +0 -0
  119. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/views/parameters/subset.py +0 -0
  120. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/views/parameters/subset_limit.py +0 -0
  121. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/views/parameters/subset_offset.py +0 -0
  122. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/views/parameters/subset_page.py +0 -0
  123. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/views/parameters/subset_page_size.py +0 -0
  124. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/views/parameters/subset_page_subset.py +0 -0
  125. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/pfxcore/views/rest_views.py +0 -0
  126. {django_pfx-1.7.2.dev14/tests_base_user → django_pfx-1.7.2.dev16/pfx/settings}/__init__.py +0 -0
  127. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pfx/settings/dev.py +0 -0
  128. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/pyproject.toml +0 -0
  129. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/requirements.txt +0 -0
  130. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/serve-doc +0 -0
  131. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/setup.cfg +0 -0
  132. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/setup.py +0 -0
  133. {django_pfx-1.7.2.dev14/tests/settings → django_pfx-1.7.2.dev16/tests}/__init__.py +0 -0
  134. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/apps.py +0 -0
  135. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/locale/fr/LC_MESSAGES/django.po +0 -0
  136. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/migrations/0001_initial.py +0 -0
  137. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/migrations/0002_alter_book_cover.py +0 -0
  138. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/migrations/0003_book_local_file.py +0 -0
  139. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/migrations/0004_mfausermixin_fields.py +0 -0
  140. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/migrations/0005_mfausermixin_fields_fix.py +0 -0
  141. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/migrations/__init__.py +0 -0
  142. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/models.py +0 -0
  143. {django_pfx-1.7.2.dev14/tests → django_pfx-1.7.2.dev16/tests/settings}/__init__.py +0 -0
  144. {django_pfx-1.7.2.dev14/tests_custom_user → django_pfx-1.7.2.dev16/tests}/settings/ci.py +0 -0
  145. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/settings/common.py +0 -0
  146. {django_pfx-1.7.2.dev14/tests_custom_user → django_pfx-1.7.2.dev16/tests}/settings/dev.py +0 -0
  147. {django_pfx-1.7.2.dev14/tests_custom_user → django_pfx-1.7.2.dev16/tests}/settings/dev_custom_example.py +0 -0
  148. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/settings/dev_default.py +0 -0
  149. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/tests/basic_api_errors.py +0 -0
  150. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/tests/basic_api_test.py +0 -0
  151. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/tests/test_api_doc.py +0 -0
  152. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/tests/test_api_doc_search.py +0 -0
  153. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/tests/test_body_mixin.py +0 -0
  154. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/tests/test_cache.py +0 -0
  155. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/tests/test_client.py +0 -0
  156. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/tests/test_fields_choices.py +0 -0
  157. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/tests/test_fields_date.py +0 -0
  158. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/tests/test_fields_decimal.py +0 -0
  159. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/tests/test_fields_minutes_duration.py +0 -0
  160. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/tests/test_fields_one2many.py +0 -0
  161. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/tests/test_fields_rich_text.py +0 -0
  162. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/tests/test_filters.py +0 -0
  163. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/tests/test_locale_api.py +0 -0
  164. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/tests/test_ordered_rest_view_mixin.py +0 -0
  165. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/tests/test_perm_tests.py +0 -0
  166. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/tests/test_permissions.py +0 -0
  167. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/tests/test_perms_api.py +0 -0
  168. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/tests/test_post_migrate_groups_update.py +0 -0
  169. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/tests/test_profiling_middleware.py +0 -0
  170. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/tests/test_settings.py +0 -0
  171. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/tests/test_shortcuts.py +0 -0
  172. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/tests/test_timezone_middleware.py +0 -0
  173. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/tests/test_tools.py +0 -0
  174. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/tests/test_user_queryset.py +0 -0
  175. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/tests/test_view_decorators.py +0 -0
  176. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/tests/test_view_fields.py +0 -0
  177. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/urls.py +0 -0
  178. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests/views.py +0 -0
  179. {django_pfx-1.7.2.dev14/pfx/settings → django_pfx-1.7.2.dev16/tests_base_user}/__init__.py +0 -0
  180. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests_base_user/migrations/0001_initial.py +0 -0
  181. {django_pfx-1.7.2.dev14/pfx/pfxcore/sms/backends → django_pfx-1.7.2.dev16/tests_base_user/migrations}/__init__.py +0 -0
  182. {django_pfx-1.7.2.dev14/pfx/pfxcore/serializers → django_pfx-1.7.2.dev16/tests_base_user/settings}/__init__.py +0 -0
  183. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests_base_user/settings/ci.py +0 -0
  184. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests_base_user/settings/common.py +0 -0
  185. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests_base_user/settings/dev.py +0 -0
  186. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests_base_user/settings/dev_custom_example.py +0 -0
  187. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests_base_user/settings/dev_default.py +0 -0
  188. {django_pfx-1.7.2.dev14/tests_custom_user → django_pfx-1.7.2.dev16/tests_base_user}/tests/__init__.py +0 -0
  189. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests_base_user/tests/test_api.py +0 -0
  190. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests_base_user/tests/test_auth_api.py +0 -0
  191. {django_pfx-1.7.2.dev14/tests_custom_user → django_pfx-1.7.2.dev16/tests_base_user}/urls.py +0 -0
  192. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests_base_user/views.py +0 -0
  193. {django_pfx-1.7.2.dev14/pfx/pfxcore/management/commands → django_pfx-1.7.2.dev16/tests_custom_user}/__init__.py +0 -0
  194. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests_custom_user/migrations/0001_initial.py +0 -0
  195. {django_pfx-1.7.2.dev14/pfx/pfxcore/management → django_pfx-1.7.2.dev16/tests_custom_user/migrations}/__init__.py +0 -0
  196. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests_custom_user/models.py +0 -0
  197. {django_pfx-1.7.2.dev14/pfx → django_pfx-1.7.2.dev16/tests_custom_user/settings}/__init__.py +0 -0
  198. {django_pfx-1.7.2.dev14/tests → django_pfx-1.7.2.dev16/tests_custom_user}/settings/ci.py +0 -0
  199. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests_custom_user/settings/common.py +0 -0
  200. {django_pfx-1.7.2.dev14/tests → django_pfx-1.7.2.dev16/tests_custom_user}/settings/dev.py +0 -0
  201. {django_pfx-1.7.2.dev14/tests → django_pfx-1.7.2.dev16/tests_custom_user}/settings/dev_custom_example.py +0 -0
  202. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests_custom_user/settings/dev_default.py +0 -0
  203. {django_pfx-1.7.2.dev14/tests_base_user → django_pfx-1.7.2.dev16/tests_custom_user}/tests/__init__.py +0 -0
  204. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests_custom_user/tests/test_api.py +0 -0
  205. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/tests_custom_user/tests/test_auth_api.py +0 -0
  206. {django_pfx-1.7.2.dev14/tests_base_user → django_pfx-1.7.2.dev16/tests_custom_user}/urls.py +0 -0
  207. {django_pfx-1.7.2.dev14 → django_pfx-1.7.2.dev16}/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.2.dev14
3
+ Version: 1.7.2.dev16
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.2.dev14
3
+ Version: 1.7.2.dev16
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
@@ -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-22 09:37+0200\n"
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:37
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:40
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:43
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:50 views/authentication_views.py:718
131
- msgid "OTP enabled"
132
- msgstr "OTP activé"
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:214
258
+ #: views/authentication_views.py:209
251
259
  msgid "Successful login"
252
260
  msgstr "Connexion réussie"
253
261
 
254
- #: views/authentication_views.py:391
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:402
266
+ #: views/authentication_views.py:397
259
267
  msgid "Code sent."
260
268
  msgstr "Code envoyé."
261
269
 
262
- #: views/authentication_views.py:465 views/authentication_views.py:635
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:470
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:473 views/authentication_views.py:644
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:564
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:566
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:678
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:719 views/authentication_views.py:755
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:754
302
+ #: views/authentication_views.py:747
291
303
  msgid "OTP disabled"
292
304
  msgstr "OTP désactivé"
293
305
 
294
- #: views/authentication_views.py:1015
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:1089
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
- sms_enabled = models.BooleanField(_("SMS MFA enabled"), default=False)
35
+ mfa_sms_enabled = models.BooleanField(_("SMS MFA enabled"), default=False)
36
36
  #: Email MFA enabled.
37
- email_enabled = models.BooleanField(_("Email MFA enabled"), default=False)
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
- otp_enabled = models.BooleanField(
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
- # --- deprecated alias ---
49
-
50
- @rest_property(_("OTP enabled"), "BooleanField")
51
- def is_otp(self):
52
- return self.otp_enabled
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.otp_enabled = True
107
+ self.mfa_authenticator_enabled = True
81
108
  self.save(update_fields=[
82
- 'otp_secret_token', 'otp_secret_token_tmp', 'otp_enabled'])
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.otp_enabled = False
93
- self.save(update_fields=['otp_secret_token', 'otp_enabled'])
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' and user.otp_enabled:
256
+ if (backend_id == 'authenticator'
257
+ and user.mfa_authenticator_enabled):
263
258
  result.append(backend_id)
264
- elif backend_id == 'sms' and user.sms_enabled:
259
+ elif backend_id == 'sms' and user.mfa_sms_enabled:
265
260
  result.append(backend_id)
266
- elif backend_id == 'email' and user.email_enabled:
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 settings.PFX_MFA_FORCE
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.otp_enabled:
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()
@@ -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
@@ -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.otp_enabled = True
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 test_otp_enable(self):
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.is_otp, False)
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.is_otp, True)
982
+ self.assertEqual(self.user1.is_mfa, True)
983
983
 
984
- def test_otp_enable_bad_code(self):
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.is_otp, True)
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.is_otp, False)
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.is_otp', True)
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.email_enabled = True
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.email_enabled = True
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.email_enabled = True
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.sms_enabled = True
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, sms_enabled=False, email_enabled=False
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.email_enabled = True
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 email_enabled=False,
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.sms_enabled = True
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 = (