django-pfx 1.4.dev100__tar.gz → 1.4.dev104__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 (189) hide show
  1. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/PKG-INFO +1 -1
  2. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/django_pfx.egg-info/PKG-INFO +1 -1
  3. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/django_pfx.egg-info/SOURCES.txt +3 -0
  4. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/default_settings.py +5 -0
  5. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/fields.py +13 -4
  6. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.po +11 -11
  7. django_pfx-1.4.dev104/pfx/pfxcore/storage/__init__.py +3 -0
  8. django_pfx-1.4.dev104/pfx/pfxcore/storage/exceptions.py +3 -0
  9. django_pfx-1.4.dev104/pfx/pfxcore/storage/local_storage.py +56 -0
  10. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/storage/s3_storage.py +7 -6
  11. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/views/__init__.py +2 -2
  12. django_pfx-1.4.dev104/pfx/pfxcore/views/media_rest_view_mixin.py +171 -0
  13. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/views/rest_views.py +1 -135
  14. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/settings/common.py +2 -0
  15. django_pfx-1.4.dev100/pfx/pfxcore/storage/__init__.py +0 -3
  16. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/.gitignore +0 -0
  17. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/.gitlab-ci.yml +0 -0
  18. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/.pre-commit-config.yaml +0 -0
  19. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/LICENSE +0 -0
  20. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/MANIFEST.in +0 -0
  21. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/README.md +0 -0
  22. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/django_pfx.egg-info/dependency_links.txt +0 -0
  23. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/django_pfx.egg-info/requires.txt +0 -0
  24. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/django_pfx.egg-info/top_level.txt +0 -0
  25. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/doc/Makefile +0 -0
  26. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/doc/conf.py +0 -0
  27. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/doc/index.rst +0 -0
  28. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/doc/source/api.views.rst +0 -0
  29. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/doc/source/authentication.md +0 -0
  30. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/doc/source/decorator.md +0 -0
  31. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/doc/source/generate_openapi.md +0 -0
  32. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/doc/source/getting_started.md +0 -0
  33. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/doc/source/internationalisation.md +0 -0
  34. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/doc/source/model.md +0 -0
  35. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/doc/source/pfx_views.md +0 -0
  36. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/doc/source/profiling.md +0 -0
  37. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/doc/source/settings.md +0 -0
  38. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/doc/source/testing.md +0 -0
  39. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/img/pfx.png +0 -0
  40. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/img/pfx.svg +0 -0
  41. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/make_messages +0 -0
  42. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/manage.py +0 -0
  43. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/__init__.py +0 -0
  44. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/__init__.py +0 -0
  45. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/apidoc/__init__.py +0 -0
  46. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/apidoc/parameters.py +0 -0
  47. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/apidoc/schema.py +0 -0
  48. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/apidoc/tags.py +0 -0
  49. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/apps.py +0 -0
  50. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/decorator/__init__.py +0 -0
  51. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/decorator/rest.py +0 -0
  52. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/exceptions.py +0 -0
  53. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/http/__init__.py +0 -0
  54. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/http/json_response.py +0 -0
  55. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.mo +0 -0
  56. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/management/__init__.py +0 -0
  57. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/management/commands/__init__.py +0 -0
  58. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/management/commands/makeapidoc.py +0 -0
  59. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/management/commands/profile.py +0 -0
  60. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/middleware/__init__.py +0 -0
  61. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/middleware/authentication.py +0 -0
  62. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/middleware/locale.py +0 -0
  63. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/middleware/profiling.py +0 -0
  64. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/migrations/0001_initial.py +0 -0
  65. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/migrations/0002_pfxpermissionsuser.py +0 -0
  66. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/migrations/0003_delete_pfxpermissionsuser.py +0 -0
  67. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/migrations/__init__.py +0 -0
  68. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/migrations/operations/__init__.py +0 -0
  69. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/migrations/operations/permissions.py +0 -0
  70. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/models/__init__.py +0 -0
  71. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/models/abstract_pfx_base_user.py +0 -0
  72. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/models/cache_mixins.py +0 -0
  73. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/models/login_ban.py +0 -0
  74. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/models/not_null_fields.py +0 -0
  75. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/models/ordered_model_mixin.py +0 -0
  76. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/models/otp_user_mixin.py +0 -0
  77. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/models/pfx_models.py +0 -0
  78. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/models/pfx_user.py +0 -0
  79. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/models/user_filtered_queryset_mixin.py +0 -0
  80. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/serializers/__init__.py +0 -0
  81. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/serializers/json.py +0 -0
  82. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/settings.py +0 -0
  83. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/shortcuts.py +0 -0
  84. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/templates/registration/otp_code_email.txt +0 -0
  85. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/templates/registration/otp_code_subject.txt +0 -0
  86. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/templates/registration/password_reset_email.txt +0 -0
  87. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/templates/registration/password_reset_subject.txt +0 -0
  88. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/templates/registration/welcome_email.txt +0 -0
  89. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/templates/registration/welcome_subject.txt +0 -0
  90. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/test.py +0 -0
  91. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/urls.py +0 -0
  92. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/views/authentication_views.py +0 -0
  93. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/views/fields.py +0 -0
  94. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/views/filters_views.py +0 -0
  95. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/views/locale_views.py +0 -0
  96. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/views/ordered_rest_view_mixin.py +0 -0
  97. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/views/parameters/__init__.py +0 -0
  98. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/views/parameters/date_format.py +0 -0
  99. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/views/parameters/groups.py +0 -0
  100. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/views/parameters/list_count.py +0 -0
  101. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/views/parameters/list_items.py +0 -0
  102. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/views/parameters/list_mode.py +0 -0
  103. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/views/parameters/list_order.py +0 -0
  104. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/views/parameters/list_search.py +0 -0
  105. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/views/parameters/media_redirect.py +0 -0
  106. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/views/parameters/meta_fields.py +0 -0
  107. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/views/parameters/meta_filters.py +0 -0
  108. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/views/parameters/meta_orders.py +0 -0
  109. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/views/parameters/subset.py +0 -0
  110. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/views/parameters/subset_limit.py +0 -0
  111. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/views/parameters/subset_offset.py +0 -0
  112. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/views/parameters/subset_page.py +0 -0
  113. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/views/parameters/subset_page_size.py +0 -0
  114. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/pfxcore/views/parameters/subset_page_subset.py +0 -0
  115. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/settings/__init__.py +0 -0
  116. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pfx/settings/dev.py +0 -0
  117. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/pyproject.toml +0 -0
  118. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/requirements.txt +0 -0
  119. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/serve-doc +0 -0
  120. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/setup.cfg +0 -0
  121. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/setup.py +0 -0
  122. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/__init__.py +0 -0
  123. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/apps.py +0 -0
  124. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/locale/fr/LC_MESSAGES/django.po +0 -0
  125. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/migrations/0001_initial.py +0 -0
  126. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/migrations/__init__.py +0 -0
  127. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/models.py +0 -0
  128. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/settings/__init__.py +0 -0
  129. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/settings/ci.py +0 -0
  130. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/settings/dev.py +0 -0
  131. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/settings/dev_custom_example.py +0 -0
  132. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/settings/dev_default.py +0 -0
  133. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/tests/__init__.py +0 -0
  134. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/tests/basic_api_errors.py +0 -0
  135. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/tests/basic_api_test.py +0 -0
  136. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/tests/test_api_doc.py +0 -0
  137. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/tests/test_api_doc_search.py +0 -0
  138. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/tests/test_auth_api.py +0 -0
  139. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/tests/test_body_mixin.py +0 -0
  140. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/tests/test_cache.py +0 -0
  141. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/tests/test_client.py +0 -0
  142. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/tests/test_fields_choices.py +0 -0
  143. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/tests/test_fields_minutes_duration.py +0 -0
  144. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/tests/test_filters.py +0 -0
  145. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/tests/test_locale_api.py +0 -0
  146. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/tests/test_ordered_rest_view_mixin.py +0 -0
  147. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/tests/test_perm_tests.py +0 -0
  148. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/tests/test_permissions.py +0 -0
  149. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/tests/test_perms_api.py +0 -0
  150. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/tests/test_post_migrate_groups_update.py +0 -0
  151. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/tests/test_profiling_middleware.py +0 -0
  152. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/tests/test_settings.py +0 -0
  153. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/tests/test_shortcuts.py +0 -0
  154. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/tests/test_timezone_middleware.py +0 -0
  155. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/tests/test_tools.py +0 -0
  156. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/tests/test_user_queryset.py +0 -0
  157. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/tests/test_view_decorators.py +0 -0
  158. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/tests/test_view_fields.py +0 -0
  159. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/urls.py +0 -0
  160. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests/views.py +0 -0
  161. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests_base_user/__init__.py +0 -0
  162. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests_base_user/migrations/0001_initial.py +0 -0
  163. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests_base_user/migrations/__init__.py +0 -0
  164. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests_base_user/settings/__init__.py +0 -0
  165. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests_base_user/settings/ci.py +0 -0
  166. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests_base_user/settings/common.py +0 -0
  167. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests_base_user/settings/dev.py +0 -0
  168. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests_base_user/settings/dev_custom_example.py +0 -0
  169. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests_base_user/settings/dev_default.py +0 -0
  170. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests_base_user/tests/__init__.py +0 -0
  171. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests_base_user/tests/test_api.py +0 -0
  172. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests_base_user/tests/test_auth_api.py +0 -0
  173. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests_base_user/urls.py +0 -0
  174. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests_base_user/views.py +0 -0
  175. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests_custom_user/__init__.py +0 -0
  176. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests_custom_user/migrations/0001_initial.py +0 -0
  177. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests_custom_user/migrations/__init__.py +0 -0
  178. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests_custom_user/models.py +0 -0
  179. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests_custom_user/settings/__init__.py +0 -0
  180. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests_custom_user/settings/ci.py +0 -0
  181. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests_custom_user/settings/common.py +0 -0
  182. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests_custom_user/settings/dev.py +0 -0
  183. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests_custom_user/settings/dev_custom_example.py +0 -0
  184. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests_custom_user/settings/dev_default.py +0 -0
  185. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests_custom_user/tests/__init__.py +0 -0
  186. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests_custom_user/tests/test_api.py +0 -0
  187. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests_custom_user/tests/test_auth_api.py +0 -0
  188. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests_custom_user/urls.py +0 -0
  189. {django_pfx-1.4.dev100 → django_pfx-1.4.dev104}/tests_custom_user/views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: django-pfx
3
- Version: 1.4.dev100
3
+ Version: 1.4.dev104
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.dev100
3
+ Version: 1.4.dev104
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
@@ -79,6 +79,8 @@ pfx/pfxcore/models/user_filtered_queryset_mixin.py
79
79
  pfx/pfxcore/serializers/__init__.py
80
80
  pfx/pfxcore/serializers/json.py
81
81
  pfx/pfxcore/storage/__init__.py
82
+ pfx/pfxcore/storage/exceptions.py
83
+ pfx/pfxcore/storage/local_storage.py
82
84
  pfx/pfxcore/storage/s3_storage.py
83
85
  pfx/pfxcore/templates/registration/otp_code_email.txt
84
86
  pfx/pfxcore/templates/registration/otp_code_subject.txt
@@ -91,6 +93,7 @@ pfx/pfxcore/views/authentication_views.py
91
93
  pfx/pfxcore/views/fields.py
92
94
  pfx/pfxcore/views/filters_views.py
93
95
  pfx/pfxcore/views/locale_views.py
96
+ pfx/pfxcore/views/media_rest_view_mixin.py
94
97
  pfx/pfxcore/views/ordered_rest_view_mixin.py
95
98
  pfx/pfxcore/views/rest_views.py
96
99
  pfx/pfxcore/views/parameters/__init__.py
@@ -21,6 +21,8 @@ PFX_OPENAPI_TEMPLATE = {}
21
21
  PFX_PROFILING_SQL = ""
22
22
  PFX_PROFILING_CPROFILE = ""
23
23
 
24
+ STORAGE_DEFAULT = None
25
+
24
26
  STORAGE_S3_AWS_REGION = None
25
27
  STORAGE_S3_AWS_ACCESS_KEY = None
26
28
  STORAGE_S3_AWS_SECRET_KEY = None
@@ -29,6 +31,9 @@ STORAGE_S3_AWS_S3_BUCKET = None
29
31
  STORAGE_S3_AWS_PUT_URL_EXPIRE = None
30
32
  STORAGE_S3_AWS_GET_URL_EXPIRE = None
31
33
 
34
+ STORAGE_LOCAL_ROOT = None
35
+ STORAGE_LOCAL_X_ACCEL_REDIRECT = False
36
+
32
37
  PFX_TEST_MODE = False
33
38
 
34
39
  PFX_AUTH_GROUPS_CREATE_ONLY = False
@@ -1,6 +1,7 @@
1
1
  import logging
2
2
  import re
3
3
  from datetime import timedelta
4
+ from importlib import import_module
4
5
 
5
6
  from django.core.exceptions import ValidationError
6
7
  from django.db import models
@@ -8,17 +9,26 @@ from django.db.models.signals import post_delete
8
9
  from django.dispatch import receiver
9
10
  from django.utils.translation import gettext_lazy as _
10
11
 
11
- from pfx.pfxcore.storage import DefaultStorage
12
+ from pfx.pfxcore.shortcuts import settings
12
13
 
13
14
  logger = logging.getLogger(__name__)
14
15
 
15
16
 
17
+ def get_storage_class(class_path):
18
+ ps = class_path.split('.')
19
+ return getattr(import_module('.'.join(ps[:-1])), ps[-1])()
20
+
21
+
16
22
  class MediaField(models.JSONField):
17
23
  def __init__(
18
24
  self, *args, max_length=255, get_key=None, storage=None,
19
25
  auto_delete=False, **kwargs):
20
26
  self.get_key = get_key or self.get_default_key
21
- self.storage = storage or DefaultStorage()
27
+ if not storage and not settings.STORAGE_DEFAULT:
28
+ raise Exception(
29
+ "Missing storage. You have to set a storage "
30
+ "class on the field or define STORAGE_DEFAULT settings.")
31
+ self.storage = storage or get_storage_class(settings.STORAGE_DEFAULT)
22
32
  self.auto_delete = auto_delete
23
33
  super().__init__(
24
34
  *args, max_length=max_length,
@@ -44,8 +54,7 @@ class MediaField(models.JSONField):
44
54
 
45
55
  def upload(self, obj, file, filename, **kwargs):
46
56
  key = self.get_key(obj, filename)
47
- self.storage.upload(key, file, **kwargs)
48
- return self.to_python(dict(key=key))
57
+ return self.to_python(self.storage.upload(key, file, **kwargs))
49
58
 
50
59
 
51
60
  @receiver(post_delete)
@@ -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: 2025-02-17 15:16+0100\n"
10
+ "POT-Creation-Date: 2025-03-05 17:31+0100\n"
11
11
  "PO-Revision-Date: 2021-06-22 23:31+0200\n"
12
12
  "Last-Translator: \n"
13
13
  "Language-Team: \n"
@@ -47,11 +47,11 @@ msgstr "Interdit"
47
47
  msgid "Resource not found"
48
48
  msgstr "Ressource non trouvée"
49
49
 
50
- #: fields.py:77
50
+ #: fields.py:86
51
51
  msgid "Invalid value."
52
52
  msgstr "Valeur invalide."
53
53
 
54
- #: fields.py:92
54
+ #: fields.py:101
55
55
  msgid ""
56
56
  "Invalid format, it can be a number in hours, “1:05”, “:05”, “1h 5m”, “1.5h” "
57
57
  "or “30m”."
@@ -267,6 +267,10 @@ msgstr "Un nouveau code d'authentification a été envoyé par e-mail."
267
267
  msgid "Invalid value for {filter} filter"
268
268
  msgstr "Valeur invalide pour le filtre {filter}"
269
269
 
270
+ #: views/media_rest_view_mixin.py:95 views/media_rest_view_mixin.py:143
271
+ msgid "Unexpected storage error"
272
+ msgstr "Erreur de stockage inattendue"
273
+
270
274
  #: views/ordered_rest_view_mixin.py:32
271
275
  #, python-brace-format
272
276
  msgid "object parameter is mandatory for move={move}."
@@ -277,27 +281,23 @@ msgstr "Le paramètre object est requis pour move={move}."
277
281
  msgid "object {pk} does not exists in this move context."
278
282
  msgstr "object {pk} n'existe pas dans ce contexte de déplacement."
279
283
 
280
- #: views/rest_views.py:246
284
+ #: views/rest_views.py:239
281
285
  #, python-brace-format
282
286
  msgid "{obj} cannot be deleted because it is referenced by other objects."
283
287
  msgstr ""
284
288
  "{obj} ne peut pas être supprimé car il est référencé par d’autres objets."
285
289
 
286
- #: views/rest_views.py:331
290
+ #: views/rest_views.py:324
287
291
  #, python-brace-format
288
292
  msgid "{model} {obj} created."
289
293
  msgstr "{model} {obj} créé."
290
294
 
291
- #: views/rest_views.py:332
295
+ #: views/rest_views.py:325
292
296
  #, python-brace-format
293
297
  msgid "{model} {obj} updated."
294
298
  msgstr "{model} {obj} modifié."
295
299
 
296
- #: views/rest_views.py:1177
300
+ #: views/rest_views.py:1170
297
301
  #, python-brace-format
298
302
  msgid "{model} {obj} deleted."
299
303
  msgstr "{model} {obj} supprimé."
300
-
301
- #: views/rest_views.py:1276 views/rest_views.py:1316
302
- msgid "Unexpected storage error"
303
- msgstr "Erreur de stockage inattendue"
@@ -0,0 +1,3 @@
1
+ from .exceptions import StorageException
2
+ from .local_storage import LocalStorage
3
+ from .s3_storage import S3Storage
@@ -0,0 +1,3 @@
1
+
2
+ class StorageException(Exception):
3
+ pass
@@ -0,0 +1,56 @@
1
+ import hashlib
2
+ import logging
3
+ from pathlib import Path
4
+
5
+ from django.http import FileResponse
6
+
7
+ from pfx.pfxcore.shortcuts import settings
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class LocalStorage:
13
+ direct = True
14
+
15
+ def to_python(self, value):
16
+ return value
17
+
18
+ def get_url(self, request, key):
19
+ return Path(settings.STORAGE_LOCAL_ROOT, key)
20
+
21
+ def upload(self, key, file, **kwargs):
22
+ key_list = key.split('/')
23
+ filename = key_list[-1]
24
+ relative_path = Path(*key_list[:-1])
25
+ dirname = Path(settings.STORAGE_LOCAL_ROOT, relative_path)
26
+ dirname.mkdir(parents=True, exist_ok=True)
27
+ ext = ''.join(filename.partition('.')[-2:])
28
+ hashname = f'{hashlib.sha1(file).hexdigest()}{ext}'
29
+ final_key = str(Path(relative_path, hashname))
30
+ with open(Path(dirname, hashname), 'wb') as f:
31
+ f.write(file)
32
+ with open(Path(dirname, hashname), 'rb') as f:
33
+ response = FileResponse(
34
+ f, as_attachment=True, filename=filename)
35
+ return {
36
+ 'key': final_key,
37
+ 'name': filename,
38
+ 'content-length': response.get('Content-Length'),
39
+ 'content-type': response.get('Content-Type'),
40
+ }
41
+
42
+ def delete(self, value):
43
+ if 'key' not in value:
44
+ return value # pragma: no cover
45
+ path = Path(settings.STORAGE_LOCAL_ROOT, value['key'])
46
+ path.unlink(missing_ok=True)
47
+ while True:
48
+ path = path.parent
49
+ if path == Path(settings.STORAGE_LOCAL_ROOT):
50
+ break
51
+ if any(path.iterdir()):
52
+ break
53
+ try:
54
+ path.rmdir()
55
+ except FileNotFoundError:
56
+ pass
@@ -6,14 +6,14 @@ from botocore.exceptions import ClientError
6
6
 
7
7
  from pfx.pfxcore.shortcuts import settings
8
8
 
9
- logger = logging.getLogger(__name__)
10
-
9
+ from .exceptions import StorageException
11
10
 
12
- class StorageException(Exception):
13
- pass
11
+ logger = logging.getLogger(__name__)
14
12
 
15
13
 
16
14
  class S3Storage:
15
+ direct = False
16
+
17
17
  @staticmethod
18
18
  def s3_client():
19
19
  return boto3.client(
@@ -60,15 +60,16 @@ class S3Storage:
60
60
  def upload(self, key, file, **kwargs):
61
61
  try:
62
62
  if isinstance(file, IOBase):
63
- return self.s3_client().upload_fileobj(
63
+ self.s3_client().upload_fileobj(
64
64
  file, settings.STORAGE_S3_AWS_S3_BUCKET, key,
65
65
  ExtraArgs=kwargs)
66
66
  else:
67
- return self.s3_client().upload_file(
67
+ self.s3_client().upload_file(
68
68
  file, settings.STORAGE_S3_AWS_S3_BUCKET, key,
69
69
  ExtraArgs=kwargs)
70
70
  except ClientError:
71
71
  raise StorageException
72
+ return dict(key=key)
72
73
 
73
74
  def delete(self, value):
74
75
  if 'key' not in value:
@@ -8,6 +8,7 @@ from .authentication_views import (
8
8
  from .fields import VF, FieldType, ViewField, ViewModelField
9
9
  from .filters_views import Filter, FilterGroup, ModelFilter
10
10
  from .locale_views import LocaleRestView
11
+ from .media_rest_view_mixin import MediaPermsRestViewMixin, MediaRestViewMixin
11
12
  from .ordered_rest_view_mixin import OrderedRestViewMixin
12
13
  from .rest_views import (
13
14
  BaseRestView,
@@ -20,8 +21,6 @@ from .rest_views import (
20
21
  DetailRestViewMixin,
21
22
  ListPermsRestViewMixin,
22
23
  ListRestViewMixin,
23
- MediaPermsRestViewMixin,
24
- MediaRestViewMixin,
25
24
  ModelBodyMixin,
26
25
  ModelMixin,
27
26
  ModelResponseMixin,
@@ -30,6 +29,7 @@ from .rest_views import (
30
29
  SecuredRestViewMixin,
31
30
  SlugDetailRestViewMixin,
32
31
  SlugPermsDetailRestViewMixin,
32
+ UpdatePermsRestViewMixin,
33
33
  UpdateRestViewMixin,
34
34
  resource_not_found,
35
35
  )
@@ -0,0 +1,171 @@
1
+ import logging
2
+
3
+ from django.core.exceptions import FieldDoesNotExist
4
+ from django.http import FileResponse, HttpResponse
5
+ from django.shortcuts import redirect
6
+ from django.utils.translation import gettext_lazy as _
7
+
8
+ from pfx.pfxcore.decorator import rest_api
9
+ from pfx.pfxcore.exceptions import APIError, NotFoundError
10
+ from pfx.pfxcore.fields import MediaField
11
+ from pfx.pfxcore.http import JsonResponse
12
+ from pfx.pfxcore.shortcuts import get_bool, settings
13
+ from pfx.pfxcore.storage.s3_storage import StorageException
14
+
15
+ from . import parameters
16
+ from .rest_views import ModelMixin
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class MediaRestViewMixin(ModelMixin):
22
+ """Extension mixin to manage media fields."""
23
+
24
+ def _get_model_field(self, field):
25
+ try:
26
+ model_field = self.model._meta.get_field(field)
27
+ if not isinstance(model_field, MediaField):
28
+ raise NotFoundError # pragma: no cover
29
+ except FieldDoesNotExist: # pragma: no cover
30
+ raise NotFoundError
31
+ return model_field
32
+
33
+ @rest_api(
34
+ "/<int:pk>/<str:field>/upload-url/<str:filename>", method="get",
35
+ priority_doc=-8)
36
+ def field_media_upload_url(self, pk, field, filename, *args, **kwargs):
37
+ """Entrypoint for
38
+ :code:`GET /<int:pk>/<str:field>/upload-url/<str:filename>` route.
39
+
40
+ Get the upload URL for a media file.
41
+
42
+ :param pk: The object pk
43
+ :param field: The field name
44
+ :param filename: The file name
45
+ :returns: The JSON response
46
+ :rtype: :class:`JsonResponse`
47
+ ---
48
+ get:
49
+ summary: Get upload URL
50
+ description: |
51
+ Get upload URL for a `MediaField` field.
52
+
53
+ You can upload a file ont the received URL. When the upload
54
+ query is done, you have to confirm the process with an
55
+ update request (`PUT`) on {model}. The body of this request
56
+ must contain the name of the `MediaField` with the contents
57
+ of the `file` value in the response of this request.
58
+
59
+ 1. `GET /<int:pk>/<str:field>/upload-url/<str:filename>`
60
+ → `data`
61
+ 2. `PUT data.url aFile Content-Type: aFile.type`
62
+ 3. `PUT /<int:pk> {{field: data.file}}`
63
+ parameters extras:
64
+ pk: The {model} pk.
65
+ field: The {model} field name. Must be the name of
66
+ a `MediaField` field.
67
+ filename: The desired filename.
68
+ responses:
69
+ 200:
70
+ description: The upload URL
71
+ content:
72
+ application/json:
73
+ schema:
74
+ properties:
75
+ url:
76
+ type: string
77
+ format: uri
78
+ file:
79
+ type: object
80
+ properties:
81
+ name:
82
+ type: string
83
+ key:
84
+ type: string
85
+ """
86
+ obj = self.get_object(pk=pk)
87
+ mediaField = self._get_model_field(field)
88
+ if mediaField.storage.direct:
89
+ raise APIError("Unavailable for direct storage")
90
+ try:
91
+ res = self._get_model_field(field).get_upload_url(
92
+ self.request, obj, filename)
93
+ except StorageException as e: # pragma: no cover
94
+ logger.exception(e)
95
+ raise APIError(_("Unexpected storage error", status=500))
96
+ return JsonResponse(res)
97
+
98
+ @rest_api(
99
+ "/<int:pk>/<str:field>", method="get",
100
+ parameters=[parameters.MediaRedirect], priority_doc=-9)
101
+ def field_media_get(self, pk, field, *args, **kwargs):
102
+ """Entrypoint for :code:`GET /<int:pk>/<str:field>` route.
103
+
104
+ Get the download URL for a media file.
105
+
106
+ :param pk: The object pk
107
+ :param field: The field name
108
+ :returns: The JSON response
109
+ :rtype: :class:`JsonResponse`
110
+ ---
111
+ get:
112
+ summary: Get {model} file
113
+ description: Get the URL for a media field file.
114
+ parameters extras:
115
+ pk: the {model} pk
116
+ field: the {model} field name
117
+ responses:
118
+ 200:
119
+ description: |
120
+ The file stream if storage is direct,
121
+ otherwise the file URL, only if `redirect` is `false`.
122
+ content:
123
+ application/json:
124
+ schema:
125
+ properties:
126
+ url:
127
+ type: string
128
+ format: uri
129
+ application/octet-stream:
130
+ schema:
131
+ type: file
132
+ 302:
133
+ description: |
134
+ The redirect, for undirect storage
135
+ if `redirect` is `true`.
136
+ """
137
+ obj = self.get_object(pk=pk)
138
+ mediaField = self._get_model_field(field)
139
+ try:
140
+ url = mediaField.get_url(self.request, obj)
141
+ except StorageException as e: # pragma: no cover
142
+ logger.exception(e)
143
+ raise APIError(_("Unexpected storage error", status=500))
144
+
145
+ if mediaField.storage.direct:
146
+ filename = getattr(obj, field).get('name')
147
+ if settings.STORAGE_LOCAL_X_ACCEL_REDIRECT:
148
+ response = HttpResponse()
149
+ response["Content-Disposition"] = (
150
+ f"attachment; filename={filename}")
151
+ response['X-Accel-Redirect'] = (
152
+ f"/filestore/{getattr(obj, field).get('key')}")
153
+ return response
154
+ response = FileResponse(
155
+ open(mediaField.get_url(self.request, obj), 'rb'),
156
+ as_attachment=True, filename=getattr(obj, field).get('name'))
157
+ return response
158
+
159
+ if get_bool(self.request.GET, 'redirect'):
160
+ return redirect(url)
161
+ return JsonResponse(dict(url=url))
162
+
163
+
164
+ class MediaPermsRestViewMixin(MediaRestViewMixin):
165
+ """Extension mixin to check permissions."""
166
+
167
+ def field_media_upload_url_perm(self, *args, **kwargs):
168
+ return self.request.user.has_perm(*self.get_model_perms('change'))
169
+
170
+ def field_media_get_perm(self, *args, **kwargs):
171
+ return self.request.user.has_perm(*self.get_model_perms('view'))
@@ -5,15 +5,10 @@ from json import JSONDecodeError
5
5
 
6
6
  from django.conf import settings
7
7
  from django.core.cache import cache
8
- from django.core.exceptions import (
9
- FieldDoesNotExist,
10
- FieldError,
11
- ValidationError,
12
- )
8
+ from django.core.exceptions import FieldError, ValidationError
13
9
  from django.db import IntegrityError, transaction
14
10
  from django.db.models import ForeignKey, Model, Q
15
11
  from django.db.models.fields import AutoFieldMixin
16
- from django.shortcuts import redirect
17
12
  from django.urls import path
18
13
  from django.utils.translation import gettext_lazy as _
19
14
  from django.views import View
@@ -31,7 +26,6 @@ from pfx.pfxcore.exceptions import (
31
26
  NotFoundError,
32
27
  UnauthorizedError,
33
28
  )
34
- from pfx.pfxcore.fields import MediaField
35
29
  from pfx.pfxcore.http import JsonResponse
36
30
  from pfx.pfxcore.models import JSONReprMixin, UserFilteredQuerySetMixin
37
31
  from pfx.pfxcore.shortcuts import (
@@ -42,7 +36,6 @@ from pfx.pfxcore.shortcuts import (
42
36
  get_object,
43
37
  model_permissions,
44
38
  )
45
- from pfx.pfxcore.storage.s3_storage import StorageException
46
39
 
47
40
  from . import parameters
48
41
  from .fields import VF
@@ -1202,133 +1195,6 @@ class DeletePermsRestViewMixin(DeleteRestViewMixin):
1202
1195
  return self.request.user.has_perm(*self.get_model_perms('delete'))
1203
1196
 
1204
1197
 
1205
- class MediaRestViewMixin(ModelMixin):
1206
- """Extension mixin to manage media fields."""
1207
-
1208
- def _get_model_field(self, field):
1209
- try:
1210
- model_field = self.model._meta.get_field(field)
1211
- if not isinstance(model_field, MediaField):
1212
- raise NotFoundError # pragma: no cover
1213
- except FieldDoesNotExist: # pragma: no cover
1214
- raise NotFoundError
1215
- return model_field
1216
-
1217
- @rest_api(
1218
- "/<int:pk>/<str:field>/upload-url/<str:filename>", method="get",
1219
- priority_doc=-8)
1220
- def field_media_upload_url(self, pk, field, filename, *args, **kwargs):
1221
- """Entrypoint for
1222
- :code:`GET /<int:pk>/<str:field>/upload-url/<str:filename>` route.
1223
-
1224
- Get the upload URL for a media file.
1225
-
1226
- :param pk: The object pk
1227
- :param field: The field name
1228
- :param filename: The file name
1229
- :returns: The JSON response
1230
- :rtype: :class:`JsonResponse`
1231
- ---
1232
- get:
1233
- summary: Get upload URL
1234
- description: |
1235
- Get upload URL for a `MediaField` field.
1236
-
1237
- You can upload a file ont the received URL. When the upload
1238
- query is done, you have to confirm the process with an
1239
- update request (`PUT`) on {model}. The body of this request
1240
- must contain the name of the `MediaField` with the contents
1241
- of the `file` value in the response of this request.
1242
-
1243
- 1. `GET /<int:pk>/<str:field>/upload-url/<str:filename>`
1244
- → `data`
1245
- 2. `PUT data.url aFile Content-Type: aFile.type`
1246
- 3. `PUT /<int:pk> {{field: data.file}}`
1247
- parameters extras:
1248
- pk: The {model} pk.
1249
- field: The {model} field name. Must be the name of
1250
- a `MediaField` field.
1251
- filename: The desired filename.
1252
- responses:
1253
- 200:
1254
- description: The upload URL
1255
- content:
1256
- application/json:
1257
- schema:
1258
- properties:
1259
- url:
1260
- type: string
1261
- format: uri
1262
- file:
1263
- type: object
1264
- properties:
1265
- name:
1266
- type: string
1267
- key:
1268
- type: string
1269
- """
1270
- obj = self.get_object(pk=pk)
1271
- try:
1272
- res = self._get_model_field(field).get_upload_url(
1273
- self.request, obj, filename)
1274
- except StorageException as e: # pragma: no cover
1275
- logger.exception(e)
1276
- raise APIError(_("Unexpected storage error", status=500))
1277
- return JsonResponse(res)
1278
-
1279
- @rest_api(
1280
- "/<int:pk>/<str:field>", method="get",
1281
- parameters=[parameters.MediaRedirect], priority_doc=-9)
1282
- def field_media_get(self, pk, field, *args, **kwargs):
1283
- """Entrypoint for :code:`GET /<int:pk>/<str:field>` route.
1284
-
1285
- Get the download URL for a media file.
1286
-
1287
- :param pk: The object pk
1288
- :param field: The field name
1289
- :returns: The JSON response
1290
- :rtype: :class:`JsonResponse`
1291
- ---
1292
- get:
1293
- summary: Get {model} file
1294
- description: Get the URL for a media field file.
1295
- parameters extras:
1296
- pk: the {model} pk
1297
- field: the {model} field name
1298
- responses:
1299
- 200:
1300
- description: The file URL, if `redirect` is `false`.
1301
- content:
1302
- application/json:
1303
- schema:
1304
- properties:
1305
- url:
1306
- type: string
1307
- format: uri
1308
- 302:
1309
- description: The redirect, if `redirect` is `true`.
1310
- """
1311
- obj = self.get_object(pk=pk)
1312
- try:
1313
- url = self._get_model_field(field).get_url(self.request, obj)
1314
- except StorageException as e: # pragma: no cover
1315
- logger.exception(e)
1316
- raise APIError(_("Unexpected storage error", status=500))
1317
- if get_bool(self.request.GET, 'redirect'):
1318
- return redirect(url)
1319
- return JsonResponse(dict(url=url))
1320
-
1321
-
1322
- class MediaPermsRestViewMixin(MediaRestViewMixin):
1323
- """Extension mixin to check permissions."""
1324
-
1325
- def field_media_upload_url_perm(self, *args, **kwargs):
1326
- return self.request.user.has_perm(*self.get_model_perms('change'))
1327
-
1328
- def field_media_get_perm(self, *args, **kwargs):
1329
- return self.request.user.has_perm(*self.get_model_perms('view'))
1330
-
1331
-
1332
1198
  class SecuredRestViewMixin(View):
1333
1199
  """A view mixin to manage service permissions.
1334
1200
 
@@ -45,6 +45,8 @@ PFX_RESET_PASSWORD_URL = (
45
45
  'http://localhost:8000/test?token={token}&uidb64={uidb64}')
46
46
  PFX_SITE_NAME = 'Books Demo'
47
47
 
48
+ STORAGE_DEFAULT = 'pfx.pfxcore.storage.S3Storage'
49
+
48
50
  # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
49
51
  BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
50
52
 
@@ -1,3 +0,0 @@
1
- from .s3_storage import S3Storage
2
-
3
- DefaultStorage = S3Storage
File without changes