django-pfx 1.4.dev66__tar.gz → 1.4.dev70__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 (177) hide show
  1. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/.gitlab-ci.yml +6 -3
  2. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/.pre-commit-config.yaml +7 -0
  3. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/PKG-INFO +1 -1
  4. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/django_pfx.egg-info/PKG-INFO +1 -1
  5. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/django_pfx.egg-info/SOURCES.txt +33 -1
  6. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/manage.py +15 -5
  7. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.po +24 -16
  8. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/models/abstract_pfx_base_user.py +10 -0
  9. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/models/pfx_user.py +2 -2
  10. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/shortcuts.py +15 -0
  11. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/views/__init__.py +3 -0
  12. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/views/authentication_views.py +4 -9
  13. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/views/rest_views.py +76 -1
  14. django_pfx-1.4.dev70/tests/migrations/0001_initial.py +119 -0
  15. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/models.py +3 -57
  16. django_pfx-1.4.dev70/tests/settings/__init__.py +0 -0
  17. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/tests/__init__.py +1 -0
  18. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/tests/test_client.py +8 -2
  19. django_pfx-1.4.dev70/tests/tests/test_permissions.py +239 -0
  20. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/urls.py +2 -0
  21. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/views.py +21 -3
  22. django_pfx-1.4.dev70/tests_base_user/__init__.py +0 -0
  23. django_pfx-1.4.dev70/tests_base_user/migrations/0001_initial.py +13 -0
  24. django_pfx-1.4.dev70/tests_base_user/migrations/__init__.py +0 -0
  25. django_pfx-1.4.dev70/tests_base_user/settings/__init__.py +0 -0
  26. django_pfx-1.4.dev70/tests_base_user/settings/ci.py +18 -0
  27. django_pfx-1.4.dev70/tests_base_user/settings/common.py +80 -0
  28. django_pfx-1.4.dev70/tests_base_user/settings/dev.py +13 -0
  29. django_pfx-1.4.dev70/tests_base_user/settings/dev_custom_example.py +28 -0
  30. django_pfx-1.4.dev70/tests_base_user/settings/dev_default.py +12 -0
  31. django_pfx-1.4.dev70/tests_base_user/tests/__init__.py +2 -0
  32. django_pfx-1.4.dev70/tests_base_user/tests/test_api.py +32 -0
  33. django_pfx-1.4.dev70/tests_base_user/tests/test_auth_api.py +59 -0
  34. django_pfx-1.4.dev70/tests_base_user/urls.py +13 -0
  35. django_pfx-1.4.dev70/tests_base_user/views.py +13 -0
  36. django_pfx-1.4.dev70/tests_custom_user/__init__.py +0 -0
  37. django_pfx-1.4.dev70/tests_custom_user/migrations/0001_initial.py +31 -0
  38. django_pfx-1.4.dev70/tests_custom_user/migrations/__init__.py +0 -0
  39. django_pfx-1.4.dev70/tests_custom_user/models.py +18 -0
  40. django_pfx-1.4.dev70/tests_custom_user/settings/__init__.py +0 -0
  41. django_pfx-1.4.dev70/tests_custom_user/settings/ci.py +18 -0
  42. django_pfx-1.4.dev70/tests_custom_user/settings/common.py +80 -0
  43. django_pfx-1.4.dev70/tests_custom_user/settings/dev.py +13 -0
  44. django_pfx-1.4.dev70/tests_custom_user/settings/dev_custom_example.py +28 -0
  45. django_pfx-1.4.dev70/tests_custom_user/settings/dev_default.py +12 -0
  46. django_pfx-1.4.dev70/tests_custom_user/tests/__init__.py +2 -0
  47. django_pfx-1.4.dev70/tests_custom_user/tests/test_api.py +24 -0
  48. django_pfx-1.4.dev70/tests_custom_user/tests/test_auth_api.py +57 -0
  49. django_pfx-1.4.dev70/tests_custom_user/urls.py +13 -0
  50. django_pfx-1.4.dev70/tests_custom_user/views.py +13 -0
  51. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/.gitignore +0 -0
  52. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/LICENSE +0 -0
  53. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/MANIFEST.in +0 -0
  54. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/README.md +0 -0
  55. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/django_pfx.egg-info/dependency_links.txt +0 -0
  56. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/django_pfx.egg-info/requires.txt +0 -0
  57. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/django_pfx.egg-info/top_level.txt +0 -0
  58. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/doc/Makefile +0 -0
  59. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/doc/conf.py +0 -0
  60. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/doc/index.rst +0 -0
  61. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/doc/source/api.views.rst +0 -0
  62. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/doc/source/authentication.md +0 -0
  63. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/doc/source/decorator.md +0 -0
  64. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/doc/source/generate_openapi.md +0 -0
  65. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/doc/source/getting_started.md +0 -0
  66. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/doc/source/internationalisation.md +0 -0
  67. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/doc/source/model.md +0 -0
  68. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/doc/source/pfx_views.md +0 -0
  69. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/doc/source/profiling.md +0 -0
  70. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/doc/source/settings.md +0 -0
  71. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/doc/source/testing.md +0 -0
  72. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/img/pfx.png +0 -0
  73. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/img/pfx.svg +0 -0
  74. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/make_messages +0 -0
  75. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/__init__.py +0 -0
  76. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/__init__.py +0 -0
  77. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/apidoc/__init__.py +0 -0
  78. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/apidoc/parameters.py +0 -0
  79. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/apidoc/schema.py +0 -0
  80. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/apidoc/tags.py +0 -0
  81. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/apps.py +0 -0
  82. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/decorator/__init__.py +0 -0
  83. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/decorator/rest.py +0 -0
  84. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/default_settings.py +0 -0
  85. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/exceptions.py +0 -0
  86. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/fields.py +0 -0
  87. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/http/__init__.py +0 -0
  88. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/http/json_response.py +0 -0
  89. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.mo +0 -0
  90. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/management/__init__.py +0 -0
  91. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/management/commands/__init__.py +0 -0
  92. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/management/commands/makeapidoc.py +0 -0
  93. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/management/commands/profile.py +0 -0
  94. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/middleware/__init__.py +0 -0
  95. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/middleware/authentication.py +0 -0
  96. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/middleware/locale.py +0 -0
  97. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/middleware/profiling.py +0 -0
  98. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/migrations/0001_initial.py +0 -0
  99. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/migrations/0002_pfxpermissionsuser.py +0 -0
  100. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/migrations/0003_delete_pfxpermissionsuser.py +0 -0
  101. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/migrations/__init__.py +0 -0
  102. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/models/__init__.py +0 -0
  103. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/models/cache_mixins.py +0 -0
  104. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/models/login_ban.py +0 -0
  105. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/models/not_null_fields.py +0 -0
  106. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/models/otp_user_mixin.py +0 -0
  107. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/models/pfx_models.py +0 -0
  108. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/models/user_filtered_queryset_mixin.py +0 -0
  109. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/serializers/__init__.py +0 -0
  110. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/serializers/json.py +0 -0
  111. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/settings.py +0 -0
  112. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/storage/__init__.py +0 -0
  113. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/storage/s3_storage.py +0 -0
  114. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/templates/registration/otp_code_email.txt +0 -0
  115. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/templates/registration/otp_code_subject.txt +0 -0
  116. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/templates/registration/password_reset_email.txt +0 -0
  117. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/templates/registration/password_reset_subject.txt +0 -0
  118. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/templates/registration/welcome_email.txt +0 -0
  119. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/templates/registration/welcome_subject.txt +0 -0
  120. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/test.py +0 -0
  121. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/urls.py +0 -0
  122. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/views/fields.py +0 -0
  123. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/views/filters_views.py +0 -0
  124. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/views/locale_views.py +0 -0
  125. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/views/parameters/__init__.py +0 -0
  126. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/views/parameters/date_format.py +0 -0
  127. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/views/parameters/groups.py +0 -0
  128. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/views/parameters/list_count.py +0 -0
  129. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/views/parameters/list_items.py +0 -0
  130. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/views/parameters/list_mode.py +0 -0
  131. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/views/parameters/list_order.py +0 -0
  132. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/views/parameters/list_search.py +0 -0
  133. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/views/parameters/media_redirect.py +0 -0
  134. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/views/parameters/meta_fields.py +0 -0
  135. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/views/parameters/meta_filters.py +0 -0
  136. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/views/parameters/meta_orders.py +0 -0
  137. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/views/parameters/subset.py +0 -0
  138. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/views/parameters/subset_limit.py +0 -0
  139. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/views/parameters/subset_offset.py +0 -0
  140. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/views/parameters/subset_page.py +0 -0
  141. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/views/parameters/subset_page_size.py +0 -0
  142. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/pfxcore/views/parameters/subset_page_subset.py +0 -0
  143. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/settings/__init__.py +0 -0
  144. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pfx/settings/dev.py +0 -0
  145. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/pyproject.toml +0 -0
  146. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/requirements.txt +0 -0
  147. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/serve-doc +0 -0
  148. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/setup.cfg +0 -0
  149. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/setup.py +0 -0
  150. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/__init__.py +0 -0
  151. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/locale/fr/LC_MESSAGES/django.po +0 -0
  152. {django_pfx-1.4.dev66/tests/settings → django_pfx-1.4.dev70/tests/migrations}/__init__.py +0 -0
  153. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/settings/ci.py +0 -0
  154. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/settings/common.py +0 -0
  155. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/settings/dev.py +0 -0
  156. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/settings/dev_custom_example.py +0 -0
  157. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/settings/dev_default.py +0 -0
  158. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/tests/basic_api_errors.py +0 -0
  159. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/tests/basic_api_test.py +0 -0
  160. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/tests/test_api_doc.py +0 -0
  161. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/tests/test_api_doc_search.py +0 -0
  162. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/tests/test_auth_api.py +0 -0
  163. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/tests/test_body_mixin.py +0 -0
  164. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/tests/test_cache.py +0 -0
  165. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/tests/test_fields.py +0 -0
  166. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/tests/test_filters.py +0 -0
  167. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/tests/test_locale_api.py +0 -0
  168. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/tests/test_perm_tests.py +0 -0
  169. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/tests/test_perms_api.py +0 -0
  170. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/tests/test_profiling_middleware.py +0 -0
  171. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/tests/test_settings.py +0 -0
  172. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/tests/test_shortcuts.py +0 -0
  173. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/tests/test_timezone_middleware.py +0 -0
  174. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/tests/test_tools.py +0 -0
  175. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/tests/test_user_queryset.py +0 -0
  176. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/tests/test_view_decorators.py +0 -0
  177. {django_pfx-1.4.dev66 → django_pfx-1.4.dev70}/tests/tests/test_view_fields.py +0 -0
@@ -33,19 +33,22 @@ quality check:
33
33
  - isort . -c
34
34
  - ./make_messages
35
35
  - git diff --exit-code # exit if messages changed
36
- - python manage.py makemigrations --check --dry-run --setting='pfx.settings.dev'
36
+ - python manage.py makemigrations --check --dry-run
37
+ - python manage.py makemigrations tests_base_user --check --dry-run
38
+ - python manage.py makemigrations tests_custom_user --check --dry-run
37
39
 
38
40
  test:
39
41
  variables:
40
42
  POSTGRES_DB: ci
41
43
  POSTGRES_USER: postgres
42
44
  POSTGRES_PASSWORD: postgres
43
- DJANGO_SETTINGS_MODULE: 'tests.settings.ci'
44
45
  script:
45
46
  - apt-get update
46
47
  - apt-get install -y gettext
47
48
  - python manage.py compilemessages -i venv
48
- - coverage run --source='.' manage.py test
49
+ - coverage run -a --source='.' manage.py test
50
+ - coverage run -a --source='.' manage.py test tests_base_user
51
+ - coverage run -a --source='.' manage.py test tests_custom_user
49
52
  - coverage report
50
53
  - coverage xml
51
54
  - coverage html
@@ -20,3 +20,10 @@ repos:
20
20
  entry: isort
21
21
  language: system
22
22
  types: [python]
23
+ - repo: local
24
+ hooks:
25
+ - id: make-messages
26
+ name: make messages
27
+ entry: ./make_messages
28
+ language: system
29
+ types: [python]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: django-pfx
3
- Version: 1.4.dev66
3
+ Version: 1.4.dev70
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.2
2
2
  Name: django-pfx
3
- Version: 1.4.dev66
3
+ Version: 1.4.dev70
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
@@ -114,6 +114,8 @@ tests/models.py
114
114
  tests/urls.py
115
115
  tests/views.py
116
116
  tests/locale/fr/LC_MESSAGES/django.po
117
+ tests/migrations/0001_initial.py
118
+ tests/migrations/__init__.py
117
119
  tests/settings/__init__.py
118
120
  tests/settings/ci.py
119
121
  tests/settings/common.py
@@ -133,6 +135,7 @@ tests/tests/test_fields.py
133
135
  tests/tests/test_filters.py
134
136
  tests/tests/test_locale_api.py
135
137
  tests/tests/test_perm_tests.py
138
+ tests/tests/test_permissions.py
136
139
  tests/tests/test_perms_api.py
137
140
  tests/tests/test_profiling_middleware.py
138
141
  tests/tests/test_settings.py
@@ -141,4 +144,33 @@ tests/tests/test_timezone_middleware.py
141
144
  tests/tests/test_tools.py
142
145
  tests/tests/test_user_queryset.py
143
146
  tests/tests/test_view_decorators.py
144
- tests/tests/test_view_fields.py
147
+ tests/tests/test_view_fields.py
148
+ tests_base_user/__init__.py
149
+ tests_base_user/urls.py
150
+ tests_base_user/views.py
151
+ tests_base_user/migrations/0001_initial.py
152
+ tests_base_user/migrations/__init__.py
153
+ tests_base_user/settings/__init__.py
154
+ tests_base_user/settings/ci.py
155
+ tests_base_user/settings/common.py
156
+ tests_base_user/settings/dev.py
157
+ tests_base_user/settings/dev_custom_example.py
158
+ tests_base_user/settings/dev_default.py
159
+ tests_base_user/tests/__init__.py
160
+ tests_base_user/tests/test_api.py
161
+ tests_base_user/tests/test_auth_api.py
162
+ tests_custom_user/__init__.py
163
+ tests_custom_user/models.py
164
+ tests_custom_user/urls.py
165
+ tests_custom_user/views.py
166
+ tests_custom_user/migrations/0001_initial.py
167
+ tests_custom_user/migrations/__init__.py
168
+ tests_custom_user/settings/__init__.py
169
+ tests_custom_user/settings/ci.py
170
+ tests_custom_user/settings/common.py
171
+ tests_custom_user/settings/dev.py
172
+ tests_custom_user/settings/dev_custom_example.py
173
+ tests_custom_user/settings/dev_default.py
174
+ tests_custom_user/tests/__init__.py
175
+ tests_custom_user/tests/test_api.py
176
+ tests_custom_user/tests/test_auth_api.py
@@ -18,10 +18,20 @@ writer.MIGRATION_HEADER_TEMPLATE = """\
18
18
 
19
19
  def main():
20
20
  """Run administrative tasks."""
21
- cmd = len(sys.argv) > 1 and sys.argv[1]
22
- os.environ.setdefault(
23
- "DJANGO_SETTINGS_MODULE",
24
- cmd == 'test' and "tests.settings.dev" or "pfx.settings.dev")
21
+ mode = os.environ.get('CI', '').lower() == 'true' and 'ci' or 'dev'
22
+ argv = [a for a in sys.argv if not a.startswith('--')]
23
+ options = [a for a in sys.argv if a.startswith('--')]
24
+ settings = f"pfx.settings.dev"
25
+ cmd = len(argv) > 1 and argv[1]
26
+ if cmd == 'test':
27
+ app = len(argv) > 2 and argv[2] or 'tests'
28
+ if len(argv) <= 2:
29
+ argv.append('tests')
30
+ settings = f"{app.split('.')[0]}.settings.{mode}"
31
+ elif cmd == 'makemigrations':
32
+ app = len(argv) > 2 and argv[2] or 'pfx'
33
+ settings = f"{app}.settings.{app == 'pfx' and 'dev' or 'common'}"
34
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", settings)
25
35
  try:
26
36
  from django.core.management import execute_from_command_line
27
37
  except ImportError as exc:
@@ -30,7 +40,7 @@ def main():
30
40
  "available on your PYTHONPATH environment variable? Did you "
31
41
  "forget to activate a virtual environment?"
32
42
  ) from exc
33
- execute_from_command_line(sys.argv)
43
+ execute_from_command_line([*argv, *options])
34
44
 
35
45
 
36
46
  if __name__ == '__main__':
@@ -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: 2024-12-11 12:51+0100\n"
10
+ "POT-Creation-Date: 2025-01-21 18:35+0100\n"
11
11
  "PO-Revision-Date: 2021-06-22 23:31+0200\n"
12
12
  "Last-Translator: \n"
13
13
  "Language-Team: \n"
@@ -59,6 +59,14 @@ msgstr ""
59
59
  "Format non valide, il peut s’agir d’un nombre en heures, « 1:05 », « :05 », "
60
60
  "« 1h 5m », « 1.5h » ou « 30m »."
61
61
 
62
+ #: models/abstract_pfx_base_user.py:35
63
+ msgid "user"
64
+ msgstr ""
65
+
66
+ #: models/abstract_pfx_base_user.py:36
67
+ msgid "users"
68
+ msgstr ""
69
+
62
70
  #: models/login_ban.py:45
63
71
  msgid "Username"
64
72
  msgstr "Nom d’utilisateur"
@@ -95,7 +103,7 @@ msgstr "Compte HOTP"
95
103
  msgid "HOTP expiry"
96
104
  msgstr "Expiration HOTP"
97
105
 
98
- #: models/otp_user_mixin.py:29 views/authentication_views.py:496
106
+ #: models/otp_user_mixin.py:29 views/authentication_views.py:491
99
107
  msgid "OTP enabled"
100
108
  msgstr "OTP activé"
101
109
 
@@ -209,7 +217,7 @@ msgstr ""
209
217
  msgid "Successful login"
210
218
  msgstr "Connexion réussie"
211
219
 
212
- #: views/authentication_views.py:243 views/authentication_views.py:413
220
+ #: views/authentication_views.py:243 views/authentication_views.py:408
213
221
  msgid "password updated successfully"
214
222
  msgstr "le mot de passe a été mis à jour avec succès"
215
223
 
@@ -217,31 +225,31 @@ msgstr "le mot de passe a été mis à jour avec succès"
217
225
  msgid "Incorrect password"
218
226
  msgstr "Mot de passe incorrect"
219
227
 
220
- #: views/authentication_views.py:251 views/authentication_views.py:422
228
+ #: views/authentication_views.py:251 views/authentication_views.py:417
221
229
  msgid "Empty password is not allowed"
222
230
  msgstr "Un mot de passe vide n’est pas autorisé"
223
231
 
224
- #: views/authentication_views.py:347
232
+ #: views/authentication_views.py:342
225
233
  msgid "User and token are valid"
226
234
  msgstr "L'utilisateur et le token sont valides"
227
235
 
228
- #: views/authentication_views.py:349
236
+ #: views/authentication_views.py:344
229
237
  msgid "User or token is invalid"
230
238
  msgstr "L'utilisateur ou le token est invalide"
231
239
 
232
- #: views/authentication_views.py:456
240
+ #: views/authentication_views.py:451
233
241
  msgid "OTP is already enabled"
234
242
  msgstr "OTP est déjà activé"
235
243
 
236
- #: views/authentication_views.py:497 views/authentication_views.py:533
244
+ #: views/authentication_views.py:492 views/authentication_views.py:528
237
245
  msgid "Invalid code"
238
246
  msgstr "Code invalide"
239
247
 
240
- #: views/authentication_views.py:532
248
+ #: views/authentication_views.py:527
241
249
  msgid "OTP disabled"
242
250
  msgstr "OTP désactivé"
243
251
 
244
- #: views/authentication_views.py:792
252
+ #: views/authentication_views.py:787
245
253
  msgid ""
246
254
  "If the email address you entered is correct, you will receive an email from "
247
255
  "us with instructions to reset your password."
@@ -250,7 +258,7 @@ msgstr ""
250
258
  "un courrier électronique de notre part contenant des instructions pour "
251
259
  "réinitialiser votre mot de passe."
252
260
 
253
- #: views/authentication_views.py:866
261
+ #: views/authentication_views.py:861
254
262
  msgid "A new authentication code has been sent by email."
255
263
  msgstr "Un nouveau code d'authentification a été envoyé par e-mail."
256
264
 
@@ -259,27 +267,27 @@ msgstr "Un nouveau code d'authentification a été envoyé par e-mail."
259
267
  msgid "Invalid value for {filter} filter"
260
268
  msgstr "Valeur invalide pour le filtre {filter}"
261
269
 
262
- #: views/rest_views.py:235
270
+ #: views/rest_views.py:242
263
271
  #, python-brace-format
264
272
  msgid "{obj} cannot be deleted because it is referenced by other objects."
265
273
  msgstr ""
266
274
  "{obj} ne peut pas être supprimé car il est référencé par d’autres objets."
267
275
 
268
- #: views/rest_views.py:329
276
+ #: views/rest_views.py:342
269
277
  #, python-brace-format
270
278
  msgid "{model} {obj} created."
271
279
  msgstr "{model} {obj} créé."
272
280
 
273
- #: views/rest_views.py:330
281
+ #: views/rest_views.py:343
274
282
  #, python-brace-format
275
283
  msgid "{model} {obj} updated."
276
284
  msgstr "{model} {obj} modifié."
277
285
 
278
- #: views/rest_views.py:1093
286
+ #: views/rest_views.py:1141
279
287
  #, python-brace-format
280
288
  msgid "{model} {obj} deleted."
281
289
  msgstr "{model} {obj} supprimé."
282
290
 
283
- #: views/rest_views.py:1185 views/rest_views.py:1225
291
+ #: views/rest_views.py:1240 views/rest_views.py:1280
284
292
  msgid "Unexpected storage error"
285
293
  msgstr "Erreur de stockage inattendue"
@@ -1,4 +1,5 @@
1
1
  from django.contrib.auth.models import AbstractBaseUser, AbstractUser
2
+ from django.utils.translation import gettext_lazy as _
2
3
 
3
4
  from .pfx_models import PFXModelMixin
4
5
 
@@ -9,6 +10,13 @@ class AbstractPFXBaseUser(PFXModelMixin, AbstractBaseUser):
9
10
  class Meta:
10
11
  abstract = True
11
12
 
13
+ def auth_json_repr(self):
14
+ return self.json_repr()
15
+
16
+ @classmethod
17
+ def auth_json_repr_schema(cls):
18
+ return cls.json_repr_schema()
19
+
12
20
  def get_user_jwt_signature_key(self):
13
21
  """
14
22
  Return a user secret to sign JWT token.
@@ -24,4 +32,6 @@ class AbstractPFXUser(AbstractUser, AbstractPFXBaseUser):
24
32
  """The base abstract user for PFX with permissions mixin."""
25
33
 
26
34
  class Meta:
35
+ verbose_name = _("user")
36
+ verbose_name_plural = _("users")
27
37
  abstract = True
@@ -1,4 +1,4 @@
1
- from django.contrib.auth.models import AbstractUser
1
+ # from django.contrib.auth.models import AbstractUser
2
2
 
3
3
  from .abstract_pfx_base_user import AbstractPFXUser
4
4
 
@@ -7,5 +7,5 @@ class PFXUser(AbstractPFXUser):
7
7
  """The Django User with PFX mixins.
8
8
  """
9
9
 
10
- class Meta(AbstractUser.Meta):
10
+ class Meta(AbstractPFXUser.Meta):
11
11
  swappable = "AUTH_USER_MODEL"
@@ -121,3 +121,18 @@ def register_views(*views):
121
121
 
122
122
  def class_key(cls, *args):
123
123
  return f"{cls.__module__}.{cls.__name__}{''.join(f'.{a}' for a in args)}"
124
+
125
+
126
+ def permissions(*perms):
127
+ from django.contrib.auth.models import Permission
128
+ pks = set()
129
+ for perm in perms:
130
+ app_label, codename = perm.split('.')
131
+ pks.add(Permission.objects.get(
132
+ codename=codename, content_type__app_label=app_label).pk)
133
+ return Permission.objects.filter(pk__in=pks)
134
+
135
+
136
+ def model_permissions(model, *actions):
137
+ meta = model._meta
138
+ return {f"{meta.app_label}.{a}_{meta.model_name}" for a in actions}
@@ -15,13 +15,16 @@ from .rest_views import (
15
15
  DeleteRestViewMixin,
16
16
  DetailRestViewMixin,
17
17
  ListRestViewMixin,
18
+ MediaPermsRestViewMixin,
18
19
  MediaRestViewMixin,
19
20
  ModelBodyMixin,
20
21
  ModelMixin,
21
22
  ModelResponseMixin,
23
+ PermsRestView,
22
24
  RestView,
23
25
  SecuredRestViewMixin,
24
26
  SlugDetailRestViewMixin,
27
+ SlugPermsDetailRestViewMixin,
25
28
  UpdateRestViewMixin,
26
29
  resource_not_found,
27
30
  )
@@ -20,7 +20,7 @@ from django.views.decorators.debug import sensitive_post_parameters
20
20
 
21
21
  import jwt
22
22
 
23
- from pfx.pfxcore.apidoc import ModelSchema, Schema, Tag
23
+ from pfx.pfxcore.apidoc import Schema, Tag
24
24
  from pfx.pfxcore.decorator import rest_api, rest_doc, rest_view
25
25
  from pfx.pfxcore.exceptions import (
26
26
  AuthenticationError,
@@ -286,12 +286,7 @@ class AuthenticationView(
286
286
  Can be overridden to customize result.
287
287
 
288
288
  :param user: The user"""
289
- info = {
290
- 'username': user.get_username(),
291
- 'first_name': user.first_name,
292
- 'last_name': user.last_name,
293
- 'email': user.email
294
- }
289
+ info = user.auth_json_repr()
295
290
  if isinstance(user, OtpUserMixin):
296
291
  info.update(is_otp=user.is_otp)
297
292
  return info
@@ -301,8 +296,8 @@ class AuthenticationView(
301
296
  """Get user representation schema.
302
297
 
303
298
  Can be overridden to customize result."""
304
- return ModelSchema(UserModel, cls._process_fields([
305
- 'username', 'first_name', 'last_name', 'email']))
299
+ return Schema(
300
+ 'auth_user', "User", **UserModel.auth_json_repr_schema())
306
301
 
307
302
  @method_decorator(sensitive_post_parameters())
308
303
  @method_decorator(never_cache)
@@ -34,7 +34,14 @@ from pfx.pfxcore.exceptions import (
34
34
  from pfx.pfxcore.fields import MediaField
35
35
  from pfx.pfxcore.http import JsonResponse
36
36
  from pfx.pfxcore.models import JSONReprMixin, UserFilteredQuerySetMixin
37
- from pfx.pfxcore.shortcuts import class_key, f, get_bool, get_int, get_object
37
+ from pfx.pfxcore.shortcuts import (
38
+ class_key,
39
+ f,
40
+ get_bool,
41
+ get_int,
42
+ get_object,
43
+ model_permissions,
44
+ )
38
45
  from pfx.pfxcore.storage.s3_storage import StorageException
39
46
 
40
47
  from . import parameters
@@ -242,6 +249,12 @@ class ModelMixin():
242
249
  return cls.tags or [
243
250
  Tag(str(cls.model._meta.verbose_name))]
244
251
 
252
+ @classmethod
253
+ def get_model_perms(cls, actions):
254
+ """Get the model permission name for the action.
255
+ """
256
+ return model_permissions(cls.model, actions)
257
+
245
258
 
246
259
  class ModelResponseMixin(ModelMixin):
247
260
  """Extension of :class:`ModelMixin` to manage object responses."""
@@ -894,6 +907,13 @@ class ListRestViewMixin(ModelResponseMixin):
894
907
  cls.meta_list_schema, cls.model_list_schema]
895
908
 
896
909
 
910
+ class ListPermsRestViewMixin(ListRestViewMixin):
911
+ """Extension mixin to check permissions."""
912
+
913
+ def get_list_perm(self, *args, **kwargs):
914
+ return self.request.user.has_perm(*self.get_model_perms('view'))
915
+
916
+
897
917
  class DetailRestViewMixin(ModelResponseMixin):
898
918
  """Extension mixin to add a get detail route."""
899
919
 
@@ -917,6 +937,13 @@ class DetailRestViewMixin(ModelResponseMixin):
917
937
  return self.response(obj)
918
938
 
919
939
 
940
+ class DetailPermsRestViewMixin(DetailRestViewMixin):
941
+ """Extension mixin to check permissions."""
942
+
943
+ def get_perm(self, *args, **kwargs):
944
+ return self.request.user.has_perm(*self.get_model_perms('view'))
945
+
946
+
920
947
  class SlugDetailRestViewMixin(ModelResponseMixin):
921
948
  """Extension mixin to add a get detail by slug route."""
922
949
 
@@ -944,6 +971,13 @@ class SlugDetailRestViewMixin(ModelResponseMixin):
944
971
  return self.response(obj)
945
972
 
946
973
 
974
+ class SlugPermsDetailRestViewMixin(SlugDetailRestViewMixin):
975
+ """Extension mixin to check permissions."""
976
+
977
+ def get_by_slug_perm(self, *args, **kwargs):
978
+ return self.request.user.has_perm(*self.get_model_perms('view'))
979
+
980
+
947
981
  class CreateRestViewMixin(ModelBodyMixin, ModelResponseMixin):
948
982
  """Extension mixin to add create route."""
949
983
 
@@ -1013,6 +1047,13 @@ class CreateRestViewMixin(ModelBodyMixin, ModelResponseMixin):
1013
1047
  return self._post(*args, **kwargs)
1014
1048
 
1015
1049
 
1050
+ class CreatePermsRestViewMixin(CreateRestViewMixin):
1051
+ """Extension mixin to check permissions."""
1052
+
1053
+ def post_perm(self, *args, **kwargs):
1054
+ return self.request.user.has_perm(*self.get_model_perms('add'))
1055
+
1056
+
1016
1057
  class UpdateRestViewMixin(ModelBodyMixin, ModelResponseMixin):
1017
1058
  """Extension mixin to add create route."""
1018
1059
 
@@ -1068,6 +1109,13 @@ class UpdateRestViewMixin(ModelBodyMixin, ModelResponseMixin):
1068
1109
  return self._put(id, *args, **kwargs)
1069
1110
 
1070
1111
 
1112
+ class UpdatePermsRestViewMixin(UpdateRestViewMixin):
1113
+ """Extension mixin to check permissions."""
1114
+
1115
+ def put_perm(self, *args, **kwargs):
1116
+ return self.request.user.has_perm(*self.get_model_perms('change'))
1117
+
1118
+
1071
1119
  class DeleteRestViewMixin(ModelMixin):
1072
1120
  """Extension mixin to add delete route."""
1073
1121
 
@@ -1111,6 +1159,13 @@ class DeleteRestViewMixin(ModelMixin):
1111
1159
  return self._delete(id, *args, **kwargs)
1112
1160
 
1113
1161
 
1162
+ class DeletePermsRestViewMixin(DeleteRestViewMixin):
1163
+ """Extension mixin to check permissions."""
1164
+
1165
+ def delete_perm(self, *args, **kwargs):
1166
+ return self.request.user.has_perm(*self.get_model_perms('delete'))
1167
+
1168
+
1114
1169
  class MediaRestViewMixin(ModelMixin):
1115
1170
  """Extension mixin to manage media fields."""
1116
1171
 
@@ -1228,6 +1283,16 @@ class MediaRestViewMixin(ModelMixin):
1228
1283
  return JsonResponse(dict(url=url))
1229
1284
 
1230
1285
 
1286
+ class MediaPermsRestViewMixin(MediaRestViewMixin):
1287
+ """Extension mixin to check permissions."""
1288
+
1289
+ def field_media_upload_url_perm(self, *args, **kwargs):
1290
+ return self.request.user.has_perm(*self.get_model_perms('change'))
1291
+
1292
+ def field_media_get_perm(self, *args, **kwargs):
1293
+ return self.request.user.has_perm(*self.get_model_perms('view'))
1294
+
1295
+
1231
1296
  class SecuredRestViewMixin(View):
1232
1297
  """A view mixin to manage service permissions.
1233
1298
 
@@ -1409,3 +1474,13 @@ class RestView(
1409
1474
  DeleteRestViewMixin,
1410
1475
  BaseRestView):
1411
1476
  pass
1477
+
1478
+
1479
+ class PermsRestView(
1480
+ ListPermsRestViewMixin,
1481
+ DetailPermsRestViewMixin,
1482
+ CreatePermsRestViewMixin,
1483
+ UpdatePermsRestViewMixin,
1484
+ DeletePermsRestViewMixin,
1485
+ BaseRestView):
1486
+ pass
@@ -0,0 +1,119 @@
1
+ # Generated by Django 4.2.17 on 2025-01-21 08:32
2
+ # flake8: noqa
3
+
4
+ import django.contrib.auth.models
5
+ import django.contrib.auth.validators
6
+ import django.db.models.deletion
7
+ import django.utils.timezone
8
+ from django.db import migrations, models
9
+
10
+ import pfx.pfxcore.fields
11
+ import pfx.pfxcore.models.cache_mixins
12
+ import pfx.pfxcore.models.not_null_fields
13
+ import pfx.pfxcore.models.pfx_models
14
+
15
+
16
+ class Migration(migrations.Migration):
17
+
18
+ initial = True
19
+
20
+ dependencies = [
21
+ ('auth', '0012_alter_user_first_name_max_length'),
22
+ ]
23
+
24
+ operations = [
25
+ migrations.CreateModel(
26
+ name='Author',
27
+ fields=[
28
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
29
+ ('first_name', models.CharField(max_length=30, verbose_name='First Name')),
30
+ ('last_name', models.CharField(max_length=30, verbose_name='Last Name')),
31
+ ('slug', models.SlugField(unique=True, verbose_name='Slug')),
32
+ ('gender', models.CharField(choices=[('male', 'Male'), ('female', 'Female')], default='male', max_length=10, verbose_name='Gender')),
33
+ ('science_fiction', models.BooleanField(default=False, verbose_name='Science Fiction')),
34
+ ('created_at', models.DateField(auto_now_add=True, verbose_name='Created at')),
35
+ ('create_comment', pfx.pfxcore.models.not_null_fields.NotNullCharField(blank=True, max_length=255, verbose_name='Create comment')),
36
+ ('update_comment', pfx.pfxcore.models.not_null_fields.NotNullCharField(blank=True, max_length=255, verbose_name='Update comment')),
37
+ ('website', pfx.pfxcore.models.not_null_fields.NotNullURLField(blank=True, max_length=255, verbose_name='Website')),
38
+ ],
39
+ options={
40
+ 'verbose_name': 'Author',
41
+ 'verbose_name_plural': 'Authors',
42
+ 'ordering': ['last_name', 'first_name', 'pk'],
43
+ },
44
+ bases=(pfx.pfxcore.models.cache_mixins.CacheableMixin, pfx.pfxcore.models.pfx_models.JSONReprMixin, models.Model),
45
+ ),
46
+ migrations.CreateModel(
47
+ name='BookType',
48
+ fields=[
49
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
50
+ ('name', models.CharField(max_length=30, verbose_name='Name')),
51
+ ('slug', models.SlugField(verbose_name='Slug')),
52
+ ],
53
+ options={
54
+ 'verbose_name': 'Book Type',
55
+ 'verbose_name_plural': 'Book Types',
56
+ },
57
+ bases=(pfx.pfxcore.models.cache_mixins.CacheDependsMixin, pfx.pfxcore.models.pfx_models.PFXModelMixin, models.Model),
58
+ ),
59
+ migrations.CreateModel(
60
+ name='Book',
61
+ fields=[
62
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
63
+ ('name', models.CharField(max_length=100, verbose_name='Name')),
64
+ ('pub_date', models.DateField(verbose_name='Pub Date')),
65
+ ('created_at', models.DateField(auto_now_add=True, verbose_name='Created at')),
66
+ ('pages', models.IntegerField(blank=True, null=True, verbose_name='Pages')),
67
+ ('rating', models.FloatField(blank=True, null=True, verbose_name='Rating')),
68
+ ('reference', models.CharField(blank=True, max_length=30, null=True, verbose_name='Reference')),
69
+ ('cover', pfx.pfxcore.fields.MediaField(blank=True, default=dict, max_length=255, verbose_name='Cover')),
70
+ ('read_time', pfx.pfxcore.fields.MinutesDurationField(blank=True, null=True, verbose_name='Read Time')),
71
+ ('author', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='books', to='tests.author', verbose_name='Author')),
72
+ ('type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='books', to='tests.booktype', verbose_name='Book Type')),
73
+ ],
74
+ options={
75
+ 'verbose_name': 'Book',
76
+ 'verbose_name_plural': 'Books',
77
+ },
78
+ bases=(pfx.pfxcore.models.cache_mixins.CacheDependsMixin, pfx.pfxcore.models.pfx_models.PFXModelMixin, models.Model),
79
+ ),
80
+ migrations.AddField(
81
+ model_name='author',
82
+ name='types',
83
+ field=models.ManyToManyField(related_name='authors', to='tests.booktype', verbose_name='Types'),
84
+ ),
85
+ migrations.CreateModel(
86
+ name='User',
87
+ fields=[
88
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
89
+ ('password', models.CharField(max_length=128, verbose_name='password')),
90
+ ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
91
+ ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
92
+ ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
93
+ ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
94
+ ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
95
+ ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
96
+ ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
97
+ ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
98
+ ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
99
+ ('otp_secret_token', models.CharField(blank=True, max_length=32, null=True, unique=True, verbose_name='OTP secret token')),
100
+ ('otp_secret_token_tmp', models.CharField(blank=True, max_length=32, null=True, verbose_name='Temporary OTP secret token')),
101
+ ('hotp_count', models.IntegerField(default=0, verbose_name='HOTP count')),
102
+ ('hotp_expiry', models.DateTimeField(default=django.utils.timezone.now, verbose_name='HOTP expiry')),
103
+ ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
104
+ ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
105
+ ],
106
+ options={
107
+ 'verbose_name': 'user',
108
+ 'verbose_name_plural': 'users',
109
+ },
110
+ bases=(pfx.pfxcore.models.cache_mixins.CacheableMixin, pfx.pfxcore.models.pfx_models.PFXModelMixin, models.Model),
111
+ managers=[
112
+ ('objects', django.contrib.auth.models.UserManager()),
113
+ ],
114
+ ),
115
+ migrations.AddConstraint(
116
+ model_name='book',
117
+ constraint=pfx.pfxcore.models.pfx_models.UniqueConstraint(fields=('author', 'name'), message='%(name)s already exists for %(author)s', name='book_unique_author_and_name'),
118
+ ),
119
+ ]