django-pfx 1.4.dev64__tar.gz → 1.4.dev68__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 (148) hide show
  1. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/PKG-INFO +1 -1
  2. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/django_pfx.egg-info/PKG-INFO +1 -1
  3. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/django_pfx.egg-info/SOURCES.txt +3 -0
  4. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/doc/source/authentication.md +27 -3
  5. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/manage.py +2 -1
  6. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.po +6 -6
  7. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/models/abstract_pfx_base_user.py +3 -1
  8. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/models/pfx_user.py +1 -2
  9. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/shortcuts.py +15 -0
  10. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/views/__init__.py +3 -0
  11. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/views/rest_views.py +76 -1
  12. django_pfx-1.4.dev68/tests/migrations/0001_initial.py +132 -0
  13. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/models.py +16 -57
  14. django_pfx-1.4.dev68/tests/settings/__init__.py +0 -0
  15. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/tests/__init__.py +1 -0
  16. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/tests/test_client.py +1 -16
  17. django_pfx-1.4.dev68/tests/tests/test_permissions.py +239 -0
  18. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/urls.py +2 -0
  19. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/views.py +21 -3
  20. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/.gitignore +0 -0
  21. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/.gitlab-ci.yml +0 -0
  22. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/.pre-commit-config.yaml +0 -0
  23. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/LICENSE +0 -0
  24. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/MANIFEST.in +0 -0
  25. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/README.md +0 -0
  26. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/django_pfx.egg-info/dependency_links.txt +0 -0
  27. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/django_pfx.egg-info/requires.txt +0 -0
  28. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/django_pfx.egg-info/top_level.txt +0 -0
  29. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/doc/Makefile +0 -0
  30. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/doc/conf.py +0 -0
  31. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/doc/index.rst +0 -0
  32. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/doc/source/api.views.rst +0 -0
  33. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/doc/source/decorator.md +0 -0
  34. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/doc/source/generate_openapi.md +0 -0
  35. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/doc/source/getting_started.md +0 -0
  36. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/doc/source/internationalisation.md +0 -0
  37. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/doc/source/model.md +0 -0
  38. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/doc/source/pfx_views.md +0 -0
  39. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/doc/source/profiling.md +0 -0
  40. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/doc/source/settings.md +0 -0
  41. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/doc/source/testing.md +0 -0
  42. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/img/pfx.png +0 -0
  43. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/img/pfx.svg +0 -0
  44. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/make_messages +0 -0
  45. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/__init__.py +0 -0
  46. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/__init__.py +0 -0
  47. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/apidoc/__init__.py +0 -0
  48. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/apidoc/parameters.py +0 -0
  49. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/apidoc/schema.py +0 -0
  50. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/apidoc/tags.py +0 -0
  51. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/apps.py +0 -0
  52. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/decorator/__init__.py +0 -0
  53. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/decorator/rest.py +0 -0
  54. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/default_settings.py +0 -0
  55. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/exceptions.py +0 -0
  56. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/fields.py +0 -0
  57. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/http/__init__.py +0 -0
  58. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/http/json_response.py +0 -0
  59. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.mo +0 -0
  60. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/management/__init__.py +0 -0
  61. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/management/commands/__init__.py +0 -0
  62. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/management/commands/makeapidoc.py +0 -0
  63. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/management/commands/profile.py +0 -0
  64. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/middleware/__init__.py +0 -0
  65. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/middleware/authentication.py +0 -0
  66. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/middleware/locale.py +0 -0
  67. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/middleware/profiling.py +0 -0
  68. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/migrations/0001_initial.py +0 -0
  69. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/migrations/0002_pfxpermissionsuser.py +0 -0
  70. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/migrations/0003_delete_pfxpermissionsuser.py +0 -0
  71. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/migrations/__init__.py +0 -0
  72. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/models/__init__.py +0 -0
  73. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/models/cache_mixins.py +0 -0
  74. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/models/login_ban.py +0 -0
  75. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/models/not_null_fields.py +0 -0
  76. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/models/otp_user_mixin.py +0 -0
  77. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/models/pfx_models.py +0 -0
  78. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/models/user_filtered_queryset_mixin.py +0 -0
  79. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/serializers/__init__.py +0 -0
  80. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/serializers/json.py +0 -0
  81. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/settings.py +0 -0
  82. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/storage/__init__.py +0 -0
  83. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/storage/s3_storage.py +0 -0
  84. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/templates/registration/otp_code_email.txt +0 -0
  85. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/templates/registration/otp_code_subject.txt +0 -0
  86. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/templates/registration/password_reset_email.txt +0 -0
  87. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/templates/registration/password_reset_subject.txt +0 -0
  88. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/templates/registration/welcome_email.txt +0 -0
  89. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/templates/registration/welcome_subject.txt +0 -0
  90. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/test.py +0 -0
  91. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/urls.py +0 -0
  92. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/views/authentication_views.py +0 -0
  93. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/views/fields.py +0 -0
  94. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/views/filters_views.py +0 -0
  95. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/views/locale_views.py +0 -0
  96. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/views/parameters/__init__.py +0 -0
  97. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/views/parameters/date_format.py +0 -0
  98. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/views/parameters/groups.py +0 -0
  99. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/views/parameters/list_count.py +0 -0
  100. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/views/parameters/list_items.py +0 -0
  101. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/views/parameters/list_mode.py +0 -0
  102. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/views/parameters/list_order.py +0 -0
  103. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/views/parameters/list_search.py +0 -0
  104. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/views/parameters/media_redirect.py +0 -0
  105. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/views/parameters/meta_fields.py +0 -0
  106. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/views/parameters/meta_filters.py +0 -0
  107. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/views/parameters/meta_orders.py +0 -0
  108. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/views/parameters/subset.py +0 -0
  109. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/views/parameters/subset_limit.py +0 -0
  110. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/views/parameters/subset_offset.py +0 -0
  111. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/views/parameters/subset_page.py +0 -0
  112. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/views/parameters/subset_page_size.py +0 -0
  113. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/pfxcore/views/parameters/subset_page_subset.py +0 -0
  114. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/settings/__init__.py +0 -0
  115. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pfx/settings/dev.py +0 -0
  116. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/pyproject.toml +0 -0
  117. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/requirements.txt +0 -0
  118. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/serve-doc +0 -0
  119. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/setup.cfg +0 -0
  120. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/setup.py +0 -0
  121. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/__init__.py +0 -0
  122. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/locale/fr/LC_MESSAGES/django.po +0 -0
  123. {django_pfx-1.4.dev64/tests/settings → django_pfx-1.4.dev68/tests/migrations}/__init__.py +0 -0
  124. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/settings/ci.py +0 -0
  125. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/settings/common.py +0 -0
  126. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/settings/dev.py +0 -0
  127. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/settings/dev_custom_example.py +0 -0
  128. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/settings/dev_default.py +0 -0
  129. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/tests/basic_api_errors.py +0 -0
  130. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/tests/basic_api_test.py +0 -0
  131. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/tests/test_api_doc.py +0 -0
  132. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/tests/test_api_doc_search.py +0 -0
  133. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/tests/test_auth_api.py +0 -0
  134. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/tests/test_body_mixin.py +0 -0
  135. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/tests/test_cache.py +0 -0
  136. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/tests/test_fields.py +0 -0
  137. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/tests/test_filters.py +0 -0
  138. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/tests/test_locale_api.py +0 -0
  139. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/tests/test_perm_tests.py +0 -0
  140. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/tests/test_perms_api.py +0 -0
  141. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/tests/test_profiling_middleware.py +0 -0
  142. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/tests/test_settings.py +0 -0
  143. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/tests/test_shortcuts.py +0 -0
  144. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/tests/test_timezone_middleware.py +0 -0
  145. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/tests/test_tools.py +0 -0
  146. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/tests/test_user_queryset.py +0 -0
  147. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/tests/test_view_decorators.py +0 -0
  148. {django_pfx-1.4.dev64 → django_pfx-1.4.dev68}/tests/tests/test_view_fields.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: django-pfx
3
- Version: 1.4.dev64
3
+ Version: 1.4.dev68
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.dev64
3
+ Version: 1.4.dev68
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
@@ -9,20 +9,44 @@ including password validation and hashing.
9
9
  ## User Model
10
10
 
11
11
  You have the option to use the standard Django User with {class}`pfx.pfxcore.models.PFXUser`
12
- (which is a {class}`django.contrib.auth.models.User` with PFX required mixins),
13
- but you may prefer to use your own model. To do this, create your own user class.
12
+ (which is a {class}`django.contrib.auth.models.User` with PFX required mixins):
13
+
14
+ ```python
15
+ AUTH_USER_MODEL = 'pfxcore.PFXUser'
16
+ ```
17
+
18
+ But you may prefer to use your own model. To do this, create your own user class.
19
+
20
+ You have 2 options:
14
21
 
15
22
  ```python
16
23
  from pfx.pfxcore.models import AbstractPFXBaseUser
17
24
 
18
25
  class MyUser(AbstractPFXBaseUser):
26
+ # Equivalent of django.contrib.auth.models.AbstractBaseUser for PFX.
27
+ #
28
+ # Minimal user, you have to manage the USERNAME_FIELD by yourself,
29
+ # and you have to add django.contrib.auth.models.PermissionsMixin
30
+ # if you want to use the permission system.
19
31
  pass
20
32
  ```
21
33
 
34
+ ```python
35
+ from pfx.pfxcore.models import AbstractPFXUser
36
+
37
+ class MyUser(AbstractPFXUser):
38
+ # Equivalent of django.contrib.auth.models.AbstractUser for PFX.
39
+ #
40
+ # This is the same as using pfxcore.PFXUser, but you can add your
41
+ # custom fields.
42
+ pass
43
+
44
+ ```
45
+
22
46
  Then, define the model in your settings:
23
47
 
24
48
  ```python
25
- AUTH_USER_MODEL = "myapp.MyUser"
49
+ AUTH_USER_MODEL = 'myapp.MyUser'
26
50
  ```
27
51
 
28
52
  ## Authentication Modes
@@ -21,7 +21,8 @@ def main():
21
21
  cmd = len(sys.argv) > 1 and sys.argv[1]
22
22
  os.environ.setdefault(
23
23
  "DJANGO_SETTINGS_MODULE",
24
- cmd == 'test' and "tests.settings.dev" or "pfx.settings.dev")
24
+ cmd in ('test', 'makemigrations') and
25
+ "tests.settings.dev" or "pfx.settings.dev")
25
26
  try:
26
27
  from django.core.management import execute_from_command_line
27
28
  except ImportError as exc:
@@ -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 14:22+0100\n"
11
11
  "PO-Revision-Date: 2021-06-22 23:31+0200\n"
12
12
  "Last-Translator: \n"
13
13
  "Language-Team: \n"
@@ -259,27 +259,27 @@ msgstr "Un nouveau code d'authentification a été envoyé par e-mail."
259
259
  msgid "Invalid value for {filter} filter"
260
260
  msgstr "Valeur invalide pour le filtre {filter}"
261
261
 
262
- #: views/rest_views.py:235
262
+ #: views/rest_views.py:242
263
263
  #, python-brace-format
264
264
  msgid "{obj} cannot be deleted because it is referenced by other objects."
265
265
  msgstr ""
266
266
  "{obj} ne peut pas être supprimé car il est référencé par d’autres objets."
267
267
 
268
- #: views/rest_views.py:329
268
+ #: views/rest_views.py:342
269
269
  #, python-brace-format
270
270
  msgid "{model} {obj} created."
271
271
  msgstr "{model} {obj} créé."
272
272
 
273
- #: views/rest_views.py:330
273
+ #: views/rest_views.py:343
274
274
  #, python-brace-format
275
275
  msgid "{model} {obj} updated."
276
276
  msgstr "{model} {obj} modifié."
277
277
 
278
- #: views/rest_views.py:1093
278
+ #: views/rest_views.py:1141
279
279
  #, python-brace-format
280
280
  msgid "{model} {obj} deleted."
281
281
  msgstr "{model} {obj} supprimé."
282
282
 
283
- #: views/rest_views.py:1185 views/rest_views.py:1225
283
+ #: views/rest_views.py:1240 views/rest_views.py:1280
284
284
  msgid "Unexpected storage error"
285
285
  msgstr "Erreur de stockage inattendue"
@@ -1,7 +1,9 @@
1
1
  from django.contrib.auth.models import AbstractBaseUser, AbstractUser
2
2
 
3
+ from .pfx_models import PFXModelMixin
3
4
 
4
- class AbstractPFXBaseUser(AbstractBaseUser):
5
+
6
+ class AbstractPFXBaseUser(PFXModelMixin, AbstractBaseUser):
5
7
  """The base abstract user for PFX."""
6
8
 
7
9
  class Meta:
@@ -1,10 +1,9 @@
1
1
  from django.contrib.auth.models import AbstractUser
2
2
 
3
3
  from .abstract_pfx_base_user import AbstractPFXUser
4
- from .pfx_models import PFXModelMixin
5
4
 
6
5
 
7
- class PFXUser(PFXModelMixin, AbstractPFXUser):
6
+ class PFXUser(AbstractPFXUser):
8
7
  """The Django User with PFX mixins.
9
8
  """
10
9
 
@@ -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
  )
@@ -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,132 @@
1
+ # Generated by Django 4.2.17 on 2025-01-21 04:24
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='TestModel',
61
+ fields=[
62
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
63
+ ('name', models.CharField(max_length=30, verbose_name='Name')),
64
+ ],
65
+ options={
66
+ 'verbose_name': 'TestModel',
67
+ 'verbose_name_plural': 'TestModels',
68
+ 'ordering': ['name', 'pk'],
69
+ },
70
+ bases=(pfx.pfxcore.models.pfx_models.JSONReprMixin, models.Model),
71
+ ),
72
+ migrations.CreateModel(
73
+ name='Book',
74
+ fields=[
75
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
76
+ ('name', models.CharField(max_length=100, verbose_name='Name')),
77
+ ('pub_date', models.DateField(verbose_name='Pub Date')),
78
+ ('created_at', models.DateField(auto_now_add=True, verbose_name='Created at')),
79
+ ('pages', models.IntegerField(blank=True, null=True, verbose_name='Pages')),
80
+ ('rating', models.FloatField(blank=True, null=True, verbose_name='Rating')),
81
+ ('reference', models.CharField(blank=True, max_length=30, null=True, verbose_name='Reference')),
82
+ ('cover', pfx.pfxcore.fields.MediaField(blank=True, default=dict, max_length=255, verbose_name='Cover')),
83
+ ('read_time', pfx.pfxcore.fields.MinutesDurationField(blank=True, null=True, verbose_name='Read Time')),
84
+ ('author', models.ForeignKey(on_delete=django.db.models.deletion.RESTRICT, related_name='books', to='tests.author', verbose_name='Author')),
85
+ ('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')),
86
+ ],
87
+ options={
88
+ 'verbose_name': 'Book',
89
+ 'verbose_name_plural': 'Books',
90
+ },
91
+ bases=(pfx.pfxcore.models.cache_mixins.CacheDependsMixin, pfx.pfxcore.models.pfx_models.PFXModelMixin, models.Model),
92
+ ),
93
+ migrations.AddField(
94
+ model_name='author',
95
+ name='types',
96
+ field=models.ManyToManyField(related_name='authors', to='tests.booktype', verbose_name='Types'),
97
+ ),
98
+ migrations.CreateModel(
99
+ name='User',
100
+ fields=[
101
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
102
+ ('password', models.CharField(max_length=128, verbose_name='password')),
103
+ ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
104
+ ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
105
+ ('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')),
106
+ ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
107
+ ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
108
+ ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
109
+ ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
110
+ ('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')),
111
+ ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
112
+ ('otp_secret_token', models.CharField(blank=True, max_length=32, null=True, unique=True, verbose_name='OTP secret token')),
113
+ ('otp_secret_token_tmp', models.CharField(blank=True, max_length=32, null=True, verbose_name='Temporary OTP secret token')),
114
+ ('hotp_count', models.IntegerField(default=0, verbose_name='HOTP count')),
115
+ ('hotp_expiry', models.DateTimeField(default=django.utils.timezone.now, verbose_name='HOTP expiry')),
116
+ ('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')),
117
+ ('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')),
118
+ ],
119
+ options={
120
+ 'verbose_name': 'user',
121
+ 'verbose_name_plural': 'users',
122
+ },
123
+ bases=(pfx.pfxcore.models.cache_mixins.CacheableMixin, pfx.pfxcore.models.pfx_models.PFXModelMixin, models.Model),
124
+ managers=[
125
+ ('objects', django.contrib.auth.models.UserManager()),
126
+ ],
127
+ ),
128
+ migrations.AddConstraint(
129
+ model_name='book',
130
+ constraint=pfx.pfxcore.models.pfx_models.UniqueConstraint(fields=('author', 'name'), message='%(name)s already exists for %(author)s', name='book_unique_author_and_name'),
131
+ ),
132
+ ]
@@ -1,4 +1,3 @@
1
- from django.contrib.auth.base_user import BaseUserManager
2
1
  from django.core.mail import send_mail
3
2
  from django.db import models
4
3
  from django.db.models import Q
@@ -8,7 +7,7 @@ from django.utils.translation import gettext_lazy as _
8
7
  from pfx.pfxcore.decorator import rest_property
9
8
  from pfx.pfxcore.fields import MediaField, MinutesDurationField
10
9
  from pfx.pfxcore.models import (
11
- AbstractPFXBaseUser,
10
+ AbstractPFXUser,
12
11
  CacheableMixin,
13
12
  CacheDependsMixin,
14
13
  JSONReprMixin,
@@ -21,61 +20,8 @@ from pfx.pfxcore.models import (
21
20
  )
22
21
 
23
22
 
24
- class UserManager(BaseUserManager):
25
- def create_user(
26
- self, username, email, password, first_name, last_name,
27
- is_superuser=False):
28
- """
29
- Creates and saves a User with the given email and password.
30
- """
31
- if not email:
32
- raise ValueError('Users must have an email address')
33
-
34
- user = self.model(
35
- email=self.normalize_email(email),
36
- username=username,
37
- first_name=first_name,
38
- last_name=last_name,
39
- is_superuser=is_superuser
40
- )
41
-
42
- user.set_password(password)
43
- user.save(using=self._db)
44
- return user
45
-
46
- def create_superuser(
47
- self, username, email, password, first_name, last_name):
48
- return self.create_user(
49
- username, email, password, first_name, last_name,
50
- is_superuser=True)
51
-
52
-
53
- class User(CacheableMixin, JSONReprMixin, OtpUserMixin, AbstractPFXBaseUser):
54
- username = models.CharField(
55
- 'username',
56
- max_length=150,
57
- unique=True,
58
- error_messages={
59
- 'unique': "A user with that username already exists.",
60
- },
61
- )
62
- first_name = models.CharField('first name', max_length=150, blank=True)
63
- last_name = models.CharField('last name', max_length=150, blank=True)
64
- email = models.EmailField('email address', blank=True)
65
- is_active = models.BooleanField(
66
- 'active',
67
- default=True,
68
- )
69
- is_superuser = models.BooleanField(
70
- 'is_superuser',
71
- default=False,
72
- )
73
-
74
- EMAIL_FIELD = 'email'
75
- USERNAME_FIELD = 'username'
76
- REQUIRED_FIELDS = ['email']
77
-
78
- objects = UserManager()
23
+ class User(CacheableMixin, OtpUserMixin, AbstractPFXUser):
24
+ """Default user for tests."""
79
25
 
80
26
  class Meta:
81
27
  verbose_name = 'user'
@@ -220,3 +166,16 @@ class Book(CacheDependsMixin, PFXModelMixin, models.Model):
220
166
  type: string
221
167
  """
222
168
  return super().json_repr(author_name=str(self.author), **values)
169
+
170
+
171
+ class TestModel(JSONReprMixin, models.Model):
172
+
173
+ name = models.CharField("Name", max_length=30)
174
+
175
+ class Meta:
176
+ verbose_name = "TestModel"
177
+ verbose_name_plural = "TestModels"
178
+ ordering = ['name', 'pk']
179
+
180
+ def __str__(self):
181
+ return self.name
File without changes
@@ -10,6 +10,7 @@ from .test_fields import FieldsTest
10
10
  from .test_filters import FiltersTest
11
11
  from .test_locale_api import LocaleAPITest
12
12
  from .test_perm_tests import PermTestsTest
13
+ from .test_permissions import TestPermissions
13
14
  from .test_perms_api import PermsAPITest
14
15
  from .test_profiling_middleware import ProfilingMiddlewareTest
15
16
  from .test_settings import TestSettings
@@ -1,4 +1,3 @@
1
- from django.db import models
2
1
  from django.http import HttpResponse
3
2
  from django.test import TestCase
4
3
  from django.test.utils import override_settings
@@ -7,23 +6,9 @@ from django.urls import include, path
7
6
  from pfx.pfxcore import register_views
8
7
  from pfx.pfxcore.decorator import rest_api, rest_view
9
8
  from pfx.pfxcore.http import JsonResponse
10
- from pfx.pfxcore.models import JSONReprMixin
11
9
  from pfx.pfxcore.test import APIClient, TestAssertMixin
12
10
  from pfx.pfxcore.views import RestView
13
- from tests.models import User
14
-
15
-
16
- class TestModel(JSONReprMixin, models.Model):
17
-
18
- name = models.CharField("Name", max_length=30)
19
-
20
- class Meta:
21
- verbose_name = "TestModel"
22
- verbose_name_plural = "TestModels"
23
- ordering = ['name', 'pk']
24
-
25
- def __str__(self):
26
- return self.name
11
+ from tests.models import TestModel, User
27
12
 
28
13
 
29
14
  @rest_view("/test")