django-pfx 1.4.dev110__tar.gz → 1.4.dev112__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 (194) hide show
  1. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/PKG-INFO +1 -1
  2. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/django_pfx.egg-info/PKG-INFO +1 -1
  3. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/django_pfx.egg-info/SOURCES.txt +2 -0
  4. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/decorator/rest.py +1 -0
  5. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/fields/media_field.py +24 -4
  6. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.po +2 -2
  7. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/models/pfx_models.py +4 -0
  8. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/storage/local_storage.py +3 -1
  9. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/views/fields.py +9 -1
  10. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/views/rest_views.py +9 -5
  11. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/locale/fr/LC_MESSAGES/django.po +3 -3
  12. django_pfx-1.4.dev112/tests/migrations/0002_alter_book_cover.py +21 -0
  13. django_pfx-1.4.dev112/tests/migrations/0003_book_local_file.py +21 -0
  14. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/models.py +6 -1
  15. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/tests/basic_api_test.py +57 -0
  16. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/tests/test_view_fields.py +1 -1
  17. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/views.py +1 -1
  18. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/.gitignore +0 -0
  19. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/.gitlab-ci.yml +0 -0
  20. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/.pre-commit-config.yaml +0 -0
  21. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/LICENSE +0 -0
  22. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/MANIFEST.in +0 -0
  23. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/README.md +0 -0
  24. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/django_pfx.egg-info/dependency_links.txt +0 -0
  25. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/django_pfx.egg-info/requires.txt +0 -0
  26. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/django_pfx.egg-info/top_level.txt +0 -0
  27. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/doc/Makefile +0 -0
  28. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/doc/conf.py +0 -0
  29. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/doc/index.rst +0 -0
  30. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/doc/source/api.views.rst +0 -0
  31. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/doc/source/authentication.md +0 -0
  32. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/doc/source/decorator.md +0 -0
  33. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/doc/source/generate_openapi.md +0 -0
  34. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/doc/source/getting_started.md +0 -0
  35. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/doc/source/internationalisation.md +0 -0
  36. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/doc/source/model.md +0 -0
  37. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/doc/source/pfx_views.md +0 -0
  38. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/doc/source/profiling.md +0 -0
  39. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/doc/source/settings.md +0 -0
  40. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/doc/source/testing.md +0 -0
  41. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/img/pfx.png +0 -0
  42. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/img/pfx.svg +0 -0
  43. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/make_messages +0 -0
  44. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/manage.py +0 -0
  45. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/__init__.py +0 -0
  46. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/__init__.py +0 -0
  47. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/apidoc/__init__.py +0 -0
  48. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/apidoc/parameters.py +0 -0
  49. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/apidoc/schema.py +0 -0
  50. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/apidoc/tags.py +0 -0
  51. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/apps.py +0 -0
  52. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/decorator/__init__.py +0 -0
  53. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/default_settings.py +0 -0
  54. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/exceptions.py +0 -0
  55. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/fields/__init__.py +0 -0
  56. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/fields/minutes_duration_field.py +0 -0
  57. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/fields/nh3_field.py +0 -0
  58. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/http/__init__.py +0 -0
  59. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/http/json_response.py +0 -0
  60. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.mo +0 -0
  61. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/management/__init__.py +0 -0
  62. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/management/commands/__init__.py +0 -0
  63. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/management/commands/makeapidoc.py +0 -0
  64. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/management/commands/profile.py +0 -0
  65. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/middleware/__init__.py +0 -0
  66. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/middleware/authentication.py +0 -0
  67. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/middleware/locale.py +0 -0
  68. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/middleware/profiling.py +0 -0
  69. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/migrations/0001_initial.py +0 -0
  70. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/migrations/0002_pfxpermissionsuser.py +0 -0
  71. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/migrations/0003_delete_pfxpermissionsuser.py +0 -0
  72. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/migrations/__init__.py +0 -0
  73. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/migrations/operations/__init__.py +0 -0
  74. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/migrations/operations/permissions.py +0 -0
  75. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/models/__init__.py +0 -0
  76. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/models/abstract_pfx_base_user.py +0 -0
  77. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/models/cache_mixins.py +0 -0
  78. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/models/login_ban.py +0 -0
  79. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/models/not_null_fields.py +0 -0
  80. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/models/ordered_model_mixin.py +0 -0
  81. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/models/otp_user_mixin.py +0 -0
  82. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/models/pfx_user.py +0 -0
  83. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/models/user_filtered_queryset_mixin.py +0 -0
  84. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/serializers/__init__.py +0 -0
  85. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/serializers/json.py +0 -0
  86. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/settings.py +0 -0
  87. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/shortcuts.py +0 -0
  88. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/storage/__init__.py +0 -0
  89. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/storage/exceptions.py +0 -0
  90. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/storage/s3_storage.py +0 -0
  91. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/templates/registration/otp_code_email.txt +0 -0
  92. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/templates/registration/otp_code_subject.txt +0 -0
  93. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/templates/registration/password_reset_email.txt +0 -0
  94. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/templates/registration/password_reset_subject.txt +0 -0
  95. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/templates/registration/welcome_email.txt +0 -0
  96. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/templates/registration/welcome_subject.txt +0 -0
  97. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/test.py +0 -0
  98. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/urls.py +0 -0
  99. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/views/__init__.py +0 -0
  100. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/views/authentication_views.py +0 -0
  101. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/views/filters_views.py +0 -0
  102. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/views/locale_views.py +0 -0
  103. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/views/media_rest_view_mixin.py +0 -0
  104. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/views/ordered_rest_view_mixin.py +0 -0
  105. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/views/parameters/__init__.py +0 -0
  106. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/views/parameters/date_format.py +0 -0
  107. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/views/parameters/groups.py +0 -0
  108. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/views/parameters/list_count.py +0 -0
  109. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/views/parameters/list_items.py +0 -0
  110. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/views/parameters/list_mode.py +0 -0
  111. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/views/parameters/list_order.py +0 -0
  112. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/views/parameters/list_search.py +0 -0
  113. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/views/parameters/media_redirect.py +0 -0
  114. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/views/parameters/meta_fields.py +0 -0
  115. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/views/parameters/meta_filters.py +0 -0
  116. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/views/parameters/meta_orders.py +0 -0
  117. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/views/parameters/subset.py +0 -0
  118. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/views/parameters/subset_limit.py +0 -0
  119. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/views/parameters/subset_offset.py +0 -0
  120. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/views/parameters/subset_page.py +0 -0
  121. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/views/parameters/subset_page_size.py +0 -0
  122. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/pfxcore/views/parameters/subset_page_subset.py +0 -0
  123. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/settings/__init__.py +0 -0
  124. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pfx/settings/dev.py +0 -0
  125. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/pyproject.toml +0 -0
  126. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/requirements.txt +0 -0
  127. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/serve-doc +0 -0
  128. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/setup.cfg +0 -0
  129. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/setup.py +0 -0
  130. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/__init__.py +0 -0
  131. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/apps.py +0 -0
  132. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/migrations/0001_initial.py +0 -0
  133. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/migrations/__init__.py +0 -0
  134. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/settings/__init__.py +0 -0
  135. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/settings/ci.py +0 -0
  136. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/settings/common.py +0 -0
  137. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/settings/dev.py +0 -0
  138. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/settings/dev_custom_example.py +0 -0
  139. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/settings/dev_default.py +0 -0
  140. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/tests/__init__.py +0 -0
  141. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/tests/basic_api_errors.py +0 -0
  142. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/tests/test_api_doc.py +0 -0
  143. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/tests/test_api_doc_search.py +0 -0
  144. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/tests/test_auth_api.py +0 -0
  145. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/tests/test_body_mixin.py +0 -0
  146. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/tests/test_cache.py +0 -0
  147. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/tests/test_client.py +0 -0
  148. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/tests/test_fields_choices.py +0 -0
  149. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/tests/test_fields_minutes_duration.py +0 -0
  150. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/tests/test_fields_nh3.py +0 -0
  151. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/tests/test_filters.py +0 -0
  152. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/tests/test_locale_api.py +0 -0
  153. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/tests/test_ordered_rest_view_mixin.py +0 -0
  154. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/tests/test_perm_tests.py +0 -0
  155. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/tests/test_permissions.py +0 -0
  156. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/tests/test_perms_api.py +0 -0
  157. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/tests/test_post_migrate_groups_update.py +0 -0
  158. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/tests/test_profiling_middleware.py +0 -0
  159. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/tests/test_settings.py +0 -0
  160. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/tests/test_shortcuts.py +0 -0
  161. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/tests/test_timezone_middleware.py +0 -0
  162. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/tests/test_tools.py +0 -0
  163. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/tests/test_user_queryset.py +0 -0
  164. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/tests/test_view_decorators.py +0 -0
  165. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests/urls.py +0 -0
  166. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests_base_user/__init__.py +0 -0
  167. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests_base_user/migrations/0001_initial.py +0 -0
  168. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests_base_user/migrations/__init__.py +0 -0
  169. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests_base_user/settings/__init__.py +0 -0
  170. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests_base_user/settings/ci.py +0 -0
  171. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests_base_user/settings/common.py +0 -0
  172. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests_base_user/settings/dev.py +0 -0
  173. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests_base_user/settings/dev_custom_example.py +0 -0
  174. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests_base_user/settings/dev_default.py +0 -0
  175. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests_base_user/tests/__init__.py +0 -0
  176. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests_base_user/tests/test_api.py +0 -0
  177. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests_base_user/tests/test_auth_api.py +0 -0
  178. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests_base_user/urls.py +0 -0
  179. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests_base_user/views.py +0 -0
  180. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests_custom_user/__init__.py +0 -0
  181. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests_custom_user/migrations/0001_initial.py +0 -0
  182. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests_custom_user/migrations/__init__.py +0 -0
  183. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests_custom_user/models.py +0 -0
  184. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests_custom_user/settings/__init__.py +0 -0
  185. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests_custom_user/settings/ci.py +0 -0
  186. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests_custom_user/settings/common.py +0 -0
  187. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests_custom_user/settings/dev.py +0 -0
  188. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests_custom_user/settings/dev_custom_example.py +0 -0
  189. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests_custom_user/settings/dev_default.py +0 -0
  190. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests_custom_user/tests/__init__.py +0 -0
  191. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests_custom_user/tests/test_api.py +0 -0
  192. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests_custom_user/tests/test_auth_api.py +0 -0
  193. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests_custom_user/urls.py +0 -0
  194. {django_pfx-1.4.dev110 → django_pfx-1.4.dev112}/tests_custom_user/views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-pfx
3
- Version: 1.4.dev110
3
+ Version: 1.4.dev112
4
4
  Summary: Django PFX is a toolkit designed to streamline the development of RESTful APIs using the Django framework.
5
5
  Author: Hervé Martinet
6
6
  Author-email: herve.martinet@gmail.com
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-pfx
3
- Version: 1.4.dev110
3
+ Version: 1.4.dev112
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
@@ -126,6 +126,8 @@ tests/urls.py
126
126
  tests/views.py
127
127
  tests/locale/fr/LC_MESSAGES/django.po
128
128
  tests/migrations/0001_initial.py
129
+ tests/migrations/0002_alter_book_cover.py
130
+ tests/migrations/0003_book_local_file.py
129
131
  tests/migrations/__init__.py
130
132
  tests/settings/__init__.py
131
133
  tests/settings/ci.py
@@ -65,6 +65,7 @@ def rest_property(string=None, type="CharField"):
65
65
  def rest_view(path):
66
66
  def decorator(cls):
67
67
  cls.rest_view_path[cls] = path
68
+ cls._rest_view_path = path
68
69
  return cls
69
70
  return decorator
70
71
 
@@ -1,5 +1,6 @@
1
1
  import logging
2
2
  from importlib import import_module
3
+ from urllib.request import urlopen
3
4
 
4
5
  from django.db import models
5
6
  from django.db.models.signals import post_delete
@@ -27,15 +28,31 @@ class MediaField(models.JSONField):
27
28
  self.storage = storage or get_storage_class(settings.STORAGE_DEFAULT)
28
29
  self.auto_delete = auto_delete
29
30
  super().__init__(
30
- *args, max_length=max_length,
31
- default=kwargs.pop('default', dict),
32
- blank=kwargs.pop('blank', True),
33
- **kwargs)
31
+ *args, max_length=max_length, **kwargs)
34
32
 
35
33
  @staticmethod
36
34
  def get_default_key(obj, filename):
37
35
  return f"{type(obj).__name__}/{obj.pk}/{filename}"
38
36
 
37
+ def clean(self, value, obj):
38
+ def save_file(obj, file, name):
39
+ setattr(obj, self.name, self.upload(obj, file, name))
40
+ obj.save(update_fields={self.name})
41
+
42
+ if isinstance(value, dict):
43
+ toDelete = value.pop('toDelete', None)
44
+ b64 = value.pop('base64', None)
45
+ name = value.get('name')
46
+ if toDelete:
47
+ obj._after_save.append(lambda: self.delete(toDelete))
48
+ value = None
49
+ if b64:
50
+ with urlopen(b64) as response:
51
+ file = response.read()
52
+ value = dict(key=self.get_key(obj, file))
53
+ obj._after_save.append(lambda: save_file(obj, file, name))
54
+ return super().clean(value, obj)
55
+
39
56
  def to_python(self, value):
40
57
  return super().to_python(self.storage.to_python(value))
41
58
 
@@ -52,6 +69,9 @@ class MediaField(models.JSONField):
52
69
  key = self.get_key(obj, filename)
53
70
  return self.to_python(self.storage.upload(key, file, **kwargs))
54
71
 
72
+ def delete(self, value):
73
+ return self.storage.delete(value)
74
+
55
75
 
56
76
  @receiver(post_delete)
57
77
  def post_delete_media(sender, instance, **kwargs):
@@ -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-04-01 13:31+0200\n"
10
+ "POT-Creation-Date: 2025-04-08 13:56+0200\n"
11
11
  "PO-Revision-Date: 2021-06-22 23:31+0200\n"
12
12
  "Last-Translator: \n"
13
13
  "Language-Team: \n"
@@ -297,7 +297,7 @@ msgstr "{model} {obj} créé."
297
297
  msgid "{model} {obj} updated."
298
298
  msgstr "{model} {obj} modifié."
299
299
 
300
- #: views/rest_views.py:1165
300
+ #: views/rest_views.py:1169
301
301
  #, python-brace-format
302
302
  msgid "{model} {obj} deleted."
303
303
  msgstr "{model} {obj} supprimé."
@@ -26,6 +26,10 @@ class JSONReprMixin():
26
26
  url = None
27
27
  apidoc = {}
28
28
 
29
+ def __init__(self, *args, **kw):
30
+ super().__init__(*args, **kw)
31
+ self._after_save = []
32
+
29
33
  def get_url(self):
30
34
  return self.url
31
35
 
@@ -40,7 +40,7 @@ class LocalStorage:
40
40
  }
41
41
 
42
42
  def delete(self, value):
43
- if 'key' not in value:
43
+ if not value or 'key' not in value:
44
44
  return value # pragma: no cover
45
45
  path = Path(settings.STORAGE_LOCAL_ROOT, value['key'])
46
46
  path.unlink(missing_ok=True)
@@ -48,6 +48,8 @@ class LocalStorage:
48
48
  path = path.parent
49
49
  if path == Path(settings.STORAGE_LOCAL_ROOT):
50
50
  break
51
+ if not path.exists():
52
+ break
51
53
  if any(path.iterdir()):
52
54
  break
53
55
  try:
@@ -43,6 +43,7 @@ class FieldType:
43
43
 
44
44
  MODEL_FIELD_BINDING = [
45
45
  (pfx_fields.MinutesDurationField, MinutesDurationField),
46
+ (pfx_fields.MediaField, MediaField),
46
47
  (models.BooleanField, BooleanField),
47
48
  (models.IntegerField, IntegerField),
48
49
  (models.FloatField, FloatField),
@@ -143,10 +144,17 @@ class ViewField:
143
144
  return self.json_repr(value)
144
145
  return value.json_repr()
145
146
 
146
- def to_json(self, obj):
147
+ def to_json(self, view, obj):
147
148
  value = self.get_value(obj)
148
149
  if self.field_type == FieldType.MinutesDurationField:
149
150
  return pfx_fields.MinutesDurationField.to_json(value)
151
+ if self.field_type == FieldType.MediaField:
152
+ if value:
153
+ api_url = view._rest_view_path
154
+ value['url'] = f'{api_url}/{obj.pk}/{self.name}'
155
+ return value
156
+ else:
157
+ return None
150
158
  if self.field_type == FieldType.ModelObject:
151
159
  return self._json_repr(value)
152
160
  if self.field_type == FieldType.ModelObjectList:
@@ -291,7 +291,7 @@ class ModelResponseMixin(ModelMixin):
291
291
  :rtype: :class:`JsonResponse`
292
292
  """
293
293
  return JsonResponse(self.serialize_object(o, **{
294
- _f.alias: _f.to_json(o)
294
+ _f.alias: _f.to_json(self, o)
295
295
  for _f in self.get_fields().values()}, meta=meta))
296
296
 
297
297
  def validate(self, obj, rel_data=None, created=False, **kwargs):
@@ -339,11 +339,15 @@ class ModelResponseMixin(ModelMixin):
339
339
  :returns: The JSON response
340
340
  :rtype: :class:`JsonResponse`
341
341
  """
342
- if rel_data:
342
+ if rel_data or obj._after_save:
343
343
  with transaction.atomic():
344
344
  obj.save()
345
- for k, v in rel_data.items():
346
- getattr(obj, k).set(v)
345
+ if rel_data:
346
+ for k, v in rel_data.items():
347
+ getattr(obj, k).set(v)
348
+ for callback in obj._after_save:
349
+ callback()
350
+ obj._after_save = []
347
351
  else:
348
352
  obj.save()
349
353
  obj = self.get_object(pk=obj.pk)
@@ -754,7 +758,7 @@ class ListRestViewMixin(ModelResponseMixin):
754
758
  *self.get_list_fields_prefetch_related())
755
759
  for o in qs:
756
760
  yield self.serialize_object(o, **{
757
- _f.alias: _f.to_json(o)
761
+ _f.alias: _f.to_json(self, o)
758
762
  for _f in self.get_list_fields().values()})
759
763
 
760
764
  def get_short_list_result(self, qs):
@@ -8,7 +8,7 @@ msgid ""
8
8
  msgstr ""
9
9
  "Project-Id-Version: PACKAGE VERSION\n"
10
10
  "Report-Msgid-Bugs-To: \n"
11
- "POT-Creation-Date: 2025-02-17 12:19+0100\n"
11
+ "POT-Creation-Date: 2025-04-08 13:56+0200\n"
12
12
  "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
13
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
14
  "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -18,11 +18,11 @@ msgstr ""
18
18
  "Content-Transfer-Encoding: 8bit\n"
19
19
  "Plural-Forms: nplurals=2; plural=(n > 1);\n"
20
20
 
21
- #: models.py:68
21
+ #: models.py:69
22
22
  msgid "First Name"
23
23
  msgstr "Prénom"
24
24
 
25
- #: models.py:69
25
+ #: models.py:70
26
26
  msgid "Last Name"
27
27
  msgstr "Nom"
28
28
 
@@ -0,0 +1,21 @@
1
+ # Generated by Django 4.2.17 on 2025-04-08 05:19
2
+ # flake8: noqa
3
+
4
+ from django.db import migrations
5
+
6
+ import pfx.pfxcore.fields.media_field
7
+
8
+
9
+ class Migration(migrations.Migration):
10
+
11
+ dependencies = [
12
+ ('tests', '0001_initial'),
13
+ ]
14
+
15
+ operations = [
16
+ migrations.AlterField(
17
+ model_name='book',
18
+ name='cover',
19
+ field=pfx.pfxcore.fields.media_field.MediaField(blank=True, max_length=255, null=True, verbose_name='Cover'),
20
+ ),
21
+ ]
@@ -0,0 +1,21 @@
1
+ # Generated by Django 4.2.17 on 2025-04-08 06:36
2
+ # flake8: noqa
3
+
4
+ from django.db import migrations
5
+
6
+ import pfx.pfxcore.fields.media_field
7
+
8
+
9
+ class Migration(migrations.Migration):
10
+
11
+ dependencies = [
12
+ ('tests', '0002_alter_book_cover'),
13
+ ]
14
+
15
+ operations = [
16
+ migrations.AddField(
17
+ model_name='book',
18
+ name='local_file',
19
+ field=pfx.pfxcore.fields.media_field.MediaField(blank=True, max_length=255, null=True, verbose_name='Cover'),
20
+ ),
21
+ ]
@@ -18,6 +18,7 @@ from pfx.pfxcore.models import (
18
18
  UniqueConstraint,
19
19
  UserFilteredQuerySetMixin,
20
20
  )
21
+ from pfx.pfxcore.storage import LocalStorage
21
22
 
22
23
 
23
24
  class User(CacheableMixin, OtpUserMixin, AbstractPFXUser):
@@ -145,7 +146,11 @@ class Book(CacheDependsMixin, PFXModelMixin, models.Model):
145
146
  rating = models.FloatField("Rating", null=True, blank=True)
146
147
  reference = models.CharField(
147
148
  "Reference", max_length=30, null=True, blank=True)
148
- cover = MediaField("Cover", auto_delete=True)
149
+ cover = MediaField(
150
+ "Cover", auto_delete=True, blank=True, null=True)
151
+ local_file = MediaField(
152
+ "Cover", auto_delete=True, blank=True, null=True,
153
+ storage=LocalStorage())
149
154
  read_time = MinutesDurationField("Read Time", null=True, blank=True)
150
155
 
151
156
  class Meta:
@@ -1,4 +1,5 @@
1
1
  from datetime import date
2
+ from pathlib import Path
2
3
  from unittest.mock import MagicMock, patch
3
4
 
4
5
  from django.db import connection
@@ -8,6 +9,9 @@ from pfx.pfxcore.test import APIClient, MockBoto3Client, TestAssertMixin
8
9
  from tests.models import Author, Book, BookType
9
10
 
10
11
 
12
+ @override_settings(
13
+ STORAGE_LOCAL_ROOT='/tmp/django-pfx-filestore',
14
+ STORAGE_LOCAL_X_ACCEL_REDIRECT=False)
11
15
  class BasicAPITest(TestAssertMixin, TestCase):
12
16
 
13
17
  def setUp(self):
@@ -704,6 +708,59 @@ class BasicAPITest(TestAssertMixin, TestCase):
704
708
  self.assertJE(response, 'types.@0.pk', self.type_sf.pk)
705
709
  self.assertJE(response, 'types.@1.pk', self.type_fantastique.pk)
706
710
 
711
+ def test_create_file(self):
712
+ response = self.client.post('/api/books', dict(
713
+ name="Test Book",
714
+ author=self.author1.pk,
715
+ pub_date='2000-01-01',
716
+ local_file=dict(
717
+ name='test.txt',
718
+ base64='data:text/plain;charset=utf-8;'
719
+ 'base64,VGVzdCBjb250ZW50')))
720
+ book_pk = self.get_val(response, 'pk')
721
+ self.assertRC(response, 200)
722
+ self.assertJE(response, 'local_file.content-length', '12')
723
+ self.assertJE(response, 'local_file.content-type', 'text/plain')
724
+ self.assertJE(
725
+ response, 'local_file.key',
726
+ f"Book/{book_pk}/bca20547e94049e1ffea27223581c567022a5774.txt")
727
+ self.assertJE(response, 'local_file.name', 'test.txt')
728
+ self.assertJE(
729
+ response, 'local_file.url', f"/books/{book_pk}/local_file")
730
+ self.assertTrue(Path(
731
+ f'/tmp/django-pfx-filestore/Book/{book_pk}/'
732
+ 'bca20547e94049e1ffea27223581c567022a5774.txt').exists())
733
+
734
+ # Replace file
735
+ old_file = self.get_val(response, 'local_file')
736
+ response = self.client.put(f'/api/books/{book_pk}', dict(
737
+ local_file=dict(
738
+ name='test2.txt',
739
+ base64='data:text/plain;charset=utf-8;'
740
+ 'base64,TmV3IGNvbnRlbnQ=',
741
+ toDelete=old_file)))
742
+ self.assertRC(response, 200)
743
+ self.assertJE(
744
+ response, 'local_file.key',
745
+ f"Book/{book_pk}/af9f06b2b3b1546ac44f4a02994d0ef09e074b91.txt")
746
+ self.assertJE(response, 'local_file.name', 'test2.txt')
747
+ self.assertFalse(Path(
748
+ f'/tmp/django-pfx-filestore/Book/{book_pk}/'
749
+ 'bca20547e94049e1ffea27223581c567022a5774.txt').exists())
750
+ self.assertTrue(Path(
751
+ f'/tmp/django-pfx-filestore/Book/{book_pk}/'
752
+ 'af9f06b2b3b1546ac44f4a02994d0ef09e074b91.txt').exists())
753
+
754
+ # Delete file
755
+ old_file = self.get_val(response, 'local_file')
756
+ response = self.client.put(f'/api/books/{book_pk}', dict(
757
+ local_file=dict(toDelete=old_file)))
758
+ self.assertRC(response, 200)
759
+ self.assertJE(response, 'local_file', None)
760
+ self.assertFalse(Path(
761
+ f'/tmp/django-pfx-filestore/Book/{book_pk}/'
762
+ 'af9f06b2b3b1546ac44f4a02994d0ef09e074b91.txt').exists())
763
+
707
764
  def test_create_null_values(self):
708
765
  response = self.client.post(
709
766
  '/api/books', dict(
@@ -88,7 +88,7 @@ class ViewFieldTest(TestAssertMixin, TestCase):
88
88
  FieldType.MinutesDurationField)
89
89
  self.assertEqual(
90
90
  FieldType.from_model_field(pfx_fields.MediaField),
91
- FieldType.JsonObject)
91
+ FieldType.MediaField)
92
92
 
93
93
  def test_field_by_name(self):
94
94
  field = ViewField.from_name(Book, 'name')
@@ -218,7 +218,7 @@ class BookRestViewMixin():
218
218
  model = Book
219
219
  fields = [
220
220
  'name', 'author', 'pub_date', VF('created_at', readonly=True),
221
- 'type', 'cover', 'pages', 'rating', 'author__last_name',
221
+ 'type', 'cover', 'local_file', 'pages', 'rating', 'author__last_name',
222
222
  'read_time']
223
223
  filters = [
224
224
  FilterGroup('custom', _("Custom"), [
File without changes