django-pfx 1.7.6.dev41__tar.gz → 1.7.6.dev43__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 (331) hide show
  1. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/PKG-INFO +1 -1
  2. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/django_pfx.egg-info/PKG-INFO +1 -1
  3. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/django_pfx.egg-info/SOURCES.txt +2 -0
  4. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/django_pfx.egg-info/scm_file_list.json +2 -0
  5. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/django_pfx.egg-info/scm_version.json +2 -2
  6. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/fields/media_field.py +25 -12
  7. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/test.py +16 -1
  8. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/__init__.py +2 -0
  9. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/basic_api_test.py +20 -16
  10. django_pfx-1.7.6.dev43/tests/tests/test_fields_media_local.py +295 -0
  11. django_pfx-1.7.6.dev43/tests/tests/test_fields_media_s3.py +76 -0
  12. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_table_file_helpers.py +1 -2
  13. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/.gitignore +0 -0
  14. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/.gitlab-ci.yml +0 -0
  15. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/.pre-commit-config.yaml +0 -0
  16. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/LICENSE +0 -0
  17. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/MANIFEST.in +0 -0
  18. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/README.md +0 -0
  19. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/django_pfx.egg-info/dependency_links.txt +0 -0
  20. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/django_pfx.egg-info/requires.txt +0 -0
  21. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/django_pfx.egg-info/top_level.txt +0 -0
  22. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/doc/Makefile +0 -0
  23. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/doc/conf.py +0 -0
  24. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/doc/index.rst +0 -0
  25. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/doc/source/api.views.rst +0 -0
  26. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/doc/source/authentication.md +0 -0
  27. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/doc/source/decorator.md +0 -0
  28. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/doc/source/generate_openapi.md +0 -0
  29. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/doc/source/getting_started.md +0 -0
  30. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/doc/source/internationalisation.md +0 -0
  31. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/doc/source/model.md +0 -0
  32. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/doc/source/pfx_views.md +0 -0
  33. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/doc/source/profiling.md +0 -0
  34. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/doc/source/settings.md +0 -0
  35. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/doc/source/testing.md +0 -0
  36. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/doc/source/user_jobs.md +0 -0
  37. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/img/pfx.png +0 -0
  38. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/img/pfx.svg +0 -0
  39. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/make_messages +0 -0
  40. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/manage.py +0 -0
  41. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/__init__.py +0 -0
  42. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/emails/__init__.py +0 -0
  43. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/emails/apps.py +0 -0
  44. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/emails/email_backend/__init__.py +0 -0
  45. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/emails/email_backend/memory_email_backend.py +0 -0
  46. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/emails/email_backend/q_mailjet_email_backend.py +0 -0
  47. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/emails/exceptions.py +0 -0
  48. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/emails/locale/fr/LC_MESSAGES/django.mo +0 -0
  49. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/emails/locale/fr/LC_MESSAGES/django.po +0 -0
  50. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/emails/mailjet/__init__.py +0 -0
  51. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/emails/mailjet/mailjet.py +0 -0
  52. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/emails/mailjet/message_result.py +0 -0
  53. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/emails/migrations/0001_initial.py +0 -0
  54. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/emails/migrations/0002_alter_email_headers.py +0 -0
  55. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/emails/migrations/0003_alter_email_options.py +0 -0
  56. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/emails/migrations/0004_alter_email_mail_from.py +0 -0
  57. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/emails/migrations/0005_lowercase_verbose_names.py +0 -0
  58. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/emails/migrations/0006_alter_email_attachments.py +0 -0
  59. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/emails/migrations/__init__.py +0 -0
  60. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/emails/models/__init__.py +0 -0
  61. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/emails/models/email.py +0 -0
  62. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/emails/providers/__init__.py +0 -0
  63. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/emails/providers/email_django_q_executor.py +0 -0
  64. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/emails/shortcuts.py +0 -0
  65. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/emails/tasks.py +0 -0
  66. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/emails/templates/emails/base_template.html +0 -0
  67. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/emails/templates/emails/base_template.txt +0 -0
  68. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/emails/templates/emails/base_template_subject.txt +0 -0
  69. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/emails/test.py +0 -0
  70. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/emails/urls.py +0 -0
  71. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/emails/views/__init__.py +0 -0
  72. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/emails/views/email_rest_view.py +0 -0
  73. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/__init__.py +0 -0
  74. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/apidoc/__init__.py +0 -0
  75. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/apidoc/parameters.py +0 -0
  76. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/apidoc/schema.py +0 -0
  77. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/apidoc/tags.py +0 -0
  78. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/apps.py +0 -0
  79. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/decorator/__init__.py +0 -0
  80. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/decorator/rest.py +0 -0
  81. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/default_settings.py +0 -0
  82. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/exceptions.py +0 -0
  83. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/fields/__init__.py +0 -0
  84. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/fields/decimal_field.py +0 -0
  85. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/fields/minutes_duration_field.py +0 -0
  86. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/fields/rich_text_field.py +0 -0
  87. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/http/__init__.py +0 -0
  88. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/http/json_response.py +0 -0
  89. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.mo +0 -0
  90. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.po +0 -0
  91. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/management/__init__.py +0 -0
  92. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/management/commands/__init__.py +0 -0
  93. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/management/commands/makeapidoc.py +0 -0
  94. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/management/commands/profile.py +0 -0
  95. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/mfa/__init__.py +0 -0
  96. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/mfa/authenticator.py +0 -0
  97. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/mfa/base.py +0 -0
  98. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/mfa/email.py +0 -0
  99. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/mfa/legacy.py +0 -0
  100. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/mfa/sms.py +0 -0
  101. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/middleware/__init__.py +0 -0
  102. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/middleware/authentication.py +0 -0
  103. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/middleware/locale.py +0 -0
  104. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/middleware/profiling.py +0 -0
  105. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/middleware/request_too_big_middleware.py +0 -0
  106. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/middleware/ws_cookie_authentication_middleware.py +0 -0
  107. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/migrations/0001_initial.py +0 -0
  108. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/migrations/0002_pfxpermissionsuser.py +0 -0
  109. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/migrations/0003_delete_pfxpermissionsuser.py +0 -0
  110. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/migrations/0004_alter_loginban_failed_counter_and_more.py +0 -0
  111. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/migrations/0005_mfadevice_mfadevice_mfa_device_unique_backend.py +0 -0
  112. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/migrations/0006_alter_pfxuser_managers.py +0 -0
  113. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/migrations/0007_alter_pfxuser_options.py +0 -0
  114. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/migrations/0008_lowercase_verbose_names.py +0 -0
  115. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/migrations/__init__.py +0 -0
  116. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/migrations/operations/__init__.py +0 -0
  117. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/migrations/operations/permissions.py +0 -0
  118. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/models/__init__.py +0 -0
  119. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/models/abstract_pfx_base_user.py +0 -0
  120. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/models/attachment_mixin.py +0 -0
  121. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/models/cache_mixins.py +0 -0
  122. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/models/login_ban.py +0 -0
  123. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/models/mfa_device.py +0 -0
  124. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/models/mfa_user_mixin.py +0 -0
  125. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/models/not_null_fields.py +0 -0
  126. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/models/ordered_model_mixin.py +0 -0
  127. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/models/pfx_models.py +0 -0
  128. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/models/pfx_user.py +0 -0
  129. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/models/user_filtered_queryset_mixin.py +0 -0
  130. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/serializers/__init__.py +0 -0
  131. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/serializers/json.py +0 -0
  132. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/settings.py +0 -0
  133. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/shortcuts.py +0 -0
  134. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/sms/__init__.py +0 -0
  135. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/sms/backends/__init__.py +0 -0
  136. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/sms/backends/base.py +0 -0
  137. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/sms/backends/console.py +0 -0
  138. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/sms/backends/memory.py +0 -0
  139. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/storage/__init__.py +0 -0
  140. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/storage/exceptions.py +0 -0
  141. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/storage/local_storage.py +0 -0
  142. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/storage/s3_storage.py +0 -0
  143. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/templates/registration/otp_code_email.txt +0 -0
  144. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/templates/registration/otp_code_subject.txt +0 -0
  145. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/templates/registration/password_reset_email.txt +0 -0
  146. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/templates/registration/password_reset_subject.txt +0 -0
  147. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/templates/registration/welcome_email.txt +0 -0
  148. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/templates/registration/welcome_subject.txt +0 -0
  149. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/urls.py +0 -0
  150. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/version_check.py +0 -0
  151. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/views/__init__.py +0 -0
  152. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/views/authentication_views.py +0 -0
  153. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/views/fields.py +0 -0
  154. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/views/filters_views.py +0 -0
  155. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/views/locale_views.py +0 -0
  156. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/views/media_rest_view_mixin.py +0 -0
  157. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/views/mfa_authentication_views.py +0 -0
  158. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/views/ordered_rest_view_mixin.py +0 -0
  159. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/views/parameters/__init__.py +0 -0
  160. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/views/parameters/date_format.py +0 -0
  161. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/views/parameters/groups.py +0 -0
  162. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/views/parameters/list_count.py +0 -0
  163. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/views/parameters/list_items.py +0 -0
  164. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/views/parameters/list_mode.py +0 -0
  165. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/views/parameters/list_order.py +0 -0
  166. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/views/parameters/list_search.py +0 -0
  167. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/views/parameters/media_redirect.py +0 -0
  168. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/views/parameters/meta_fields.py +0 -0
  169. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/views/parameters/meta_filters.py +0 -0
  170. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/views/parameters/meta_orders.py +0 -0
  171. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/views/parameters/subset.py +0 -0
  172. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/views/parameters/subset_limit.py +0 -0
  173. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/views/parameters/subset_offset.py +0 -0
  174. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/views/parameters/subset_page.py +0 -0
  175. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/views/parameters/subset_page_size.py +0 -0
  176. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/views/parameters/subset_page_subset.py +0 -0
  177. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/views/rest_views.py +0 -0
  178. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/pfxcore/xml_test_runner.py +0 -0
  179. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/settings/__init__.py +0 -0
  180. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/settings/dev.py +0 -0
  181. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/userjobs/__init__.py +0 -0
  182. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/userjobs/apps.py +0 -0
  183. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/userjobs/locale/fr/LC_MESSAGES/django.mo +0 -0
  184. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/userjobs/locale/fr/LC_MESSAGES/django.po +0 -0
  185. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/userjobs/migrations/0001_initial.py +0 -0
  186. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/userjobs/migrations/0002_alter_userjob_options_alter_userjob_auto_queued_and_more.py +0 -0
  187. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/userjobs/migrations/0003_clean_scheduled_tasks.py +0 -0
  188. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/userjobs/migrations/0004_userjob_summary_url.py +0 -0
  189. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/userjobs/migrations/0005_swappable_user_job.py +0 -0
  190. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/userjobs/migrations/0006_lowercase_verbose_names.py +0 -0
  191. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/userjobs/migrations/__init__.py +0 -0
  192. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/userjobs/models/__init__.py +0 -0
  193. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/userjobs/models/user_job.py +0 -0
  194. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/userjobs/models/user_job_attachment.py +0 -0
  195. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/userjobs/providers/__init__.py +0 -0
  196. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/userjobs/providers/user_job_django_q_executor.py +0 -0
  197. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/userjobs/providers/user_job_websocket_channel_provider.py +0 -0
  198. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/userjobs/tasks.py +0 -0
  199. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/userjobs/urls.py +0 -0
  200. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/userjobs/views/__init__.py +0 -0
  201. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/userjobs/views/user_job_attachment_view.py +0 -0
  202. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/userjobs/views/user_job_view.py +0 -0
  203. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pfx/userjobs/views/user_websocket_consumer.py +0 -0
  204. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/pyproject.toml +0 -0
  205. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/requirements.txt +0 -0
  206. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/serve-doc +0 -0
  207. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/setup.cfg +0 -0
  208. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/setup.py +0 -0
  209. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/__init__.py +0 -0
  210. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/apps.py +0 -0
  211. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/channel_group_send_calls.py +0 -0
  212. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/locale/fr/LC_MESSAGES/django.po +0 -0
  213. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/mfa_urls.py +0 -0
  214. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/migrations/0001_initial.py +0 -0
  215. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/migrations/__init__.py +0 -0
  216. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/models.py +0 -0
  217. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/settings/__init__.py +0 -0
  218. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/settings/ci.py +0 -0
  219. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/settings/common.py +0 -0
  220. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/settings/dev.py +0 -0
  221. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/settings/dev_custom_example.py +0 -0
  222. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/settings/dev_default.py +0 -0
  223. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/basic_api_errors.py +0 -0
  224. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/mfa_test_utils.py +0 -0
  225. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/parallel_scenarios.py +0 -0
  226. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_api_doc.py +0 -0
  227. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_api_doc_search.py +0 -0
  228. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_auth_api.py +0 -0
  229. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_body_mixin.py +0 -0
  230. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_cache.py +0 -0
  231. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_client.py +0 -0
  232. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_emails_email_api.py +0 -0
  233. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_emails_email_model.py +0 -0
  234. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_emails_q_mailjet_email_backend.py +0 -0
  235. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_emails_send_email_message.py +0 -0
  236. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_emails_test_tools.py +0 -0
  237. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_fields_choices.py +0 -0
  238. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_fields_date.py +0 -0
  239. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_fields_decimal.py +0 -0
  240. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_fields_help_text.py +0 -0
  241. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_fields_minutes_duration.py +0 -0
  242. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_fields_one2many.py +0 -0
  243. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_fields_rich_text.py +0 -0
  244. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_fields_time.py +0 -0
  245. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_filters.py +0 -0
  246. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_locale_api.py +0 -0
  247. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_mfa_legacy.py +0 -0
  248. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_mfa_login_api.py +0 -0
  249. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_mfa_setup_api.py +0 -0
  250. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_ordered_rest_view_mixin.py +0 -0
  251. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_perm_tests.py +0 -0
  252. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_permissions.py +0 -0
  253. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_perms_api.py +0 -0
  254. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_post_migrate_groups_update.py +0 -0
  255. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_profiling_middleware.py +0 -0
  256. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_settings.py +0 -0
  257. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_shortcuts.py +0 -0
  258. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_test_runner.py +0 -0
  259. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_timezone_middleware.py +0 -0
  260. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_tools.py +0 -0
  261. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_user_job_api.py +0 -0
  262. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_user_job_messages.py +0 -0
  263. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_user_job_model.py +0 -0
  264. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_user_queryset.py +0 -0
  265. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_user_websocket.py +0 -0
  266. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_verbose_names.py +0 -0
  267. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_version_check.py +0 -0
  268. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_view_decorators.py +0 -0
  269. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_view_fields.py +0 -0
  270. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/tests/test_ws_cookie_middleware.py +0 -0
  271. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/urls.py +0 -0
  272. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests/views.py +0 -0
  273. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_base_user/__init__.py +0 -0
  274. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_base_user/migrations/__init__.py +0 -0
  275. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_base_user/settings/__init__.py +0 -0
  276. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_base_user/settings/ci.py +0 -0
  277. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_base_user/settings/common.py +0 -0
  278. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_base_user/settings/dev.py +0 -0
  279. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_base_user/settings/dev_custom_example.py +0 -0
  280. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_base_user/settings/dev_default.py +0 -0
  281. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_base_user/tests/__init__.py +0 -0
  282. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_base_user/tests/test_api.py +0 -0
  283. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_base_user/tests/test_auth_api.py +0 -0
  284. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_base_user/urls.py +0 -0
  285. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_base_user/views.py +0 -0
  286. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_custom_user/__init__.py +0 -0
  287. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_custom_user/migrations/0001_initial.py +0 -0
  288. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_custom_user/migrations/__init__.py +0 -0
  289. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_custom_user/models.py +0 -0
  290. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_custom_user/settings/__init__.py +0 -0
  291. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_custom_user/settings/ci.py +0 -0
  292. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_custom_user/settings/common.py +0 -0
  293. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_custom_user/settings/dev.py +0 -0
  294. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_custom_user/settings/dev_custom_example.py +0 -0
  295. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_custom_user/settings/dev_default.py +0 -0
  296. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_custom_user/tests/__init__.py +0 -0
  297. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_custom_user/tests/test_api.py +0 -0
  298. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_custom_user/tests/test_auth_api.py +0 -0
  299. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_custom_user/urls.py +0 -0
  300. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_custom_user/views.py +0 -0
  301. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_custom_user_job/__init__.py +0 -0
  302. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_custom_user_job/migrations/0001_initial.py +0 -0
  303. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_custom_user_job/migrations/0002_lowercase_verbose_names.py +0 -0
  304. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_custom_user_job/migrations/__init__.py +0 -0
  305. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_custom_user_job/models.py +0 -0
  306. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_custom_user_job/providers.py +0 -0
  307. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_custom_user_job/scope.py +0 -0
  308. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_custom_user_job/settings/__init__.py +0 -0
  309. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_custom_user_job/settings/ci.py +0 -0
  310. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_custom_user_job/settings/common.py +0 -0
  311. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_custom_user_job/settings/dev.py +0 -0
  312. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_custom_user_job/settings/dev_custom_example.py +0 -0
  313. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_custom_user_job/settings/dev_default.py +0 -0
  314. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_custom_user_job/tests/__init__.py +0 -0
  315. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_custom_user_job/tests/test_user_job.py +0 -0
  316. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_custom_user_job/urls.py +0 -0
  317. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_user_email_login/__init__.py +0 -0
  318. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_user_email_login/migrations/0001_initial.py +0 -0
  319. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_user_email_login/migrations/__init__.py +0 -0
  320. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_user_email_login/models.py +0 -0
  321. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_user_email_login/settings/__init__.py +0 -0
  322. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_user_email_login/settings/ci.py +0 -0
  323. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_user_email_login/settings/common.py +0 -0
  324. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_user_email_login/settings/dev.py +0 -0
  325. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_user_email_login/settings/dev_custom_example.py +0 -0
  326. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_user_email_login/settings/dev_default.py +0 -0
  327. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_user_email_login/tests/__init__.py +0 -0
  328. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_user_email_login/tests/test_api.py +0 -0
  329. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_user_email_login/tests/test_auth_api.py +0 -0
  330. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_user_email_login/urls.py +0 -0
  331. {django_pfx-1.7.6.dev41 → django_pfx-1.7.6.dev43}/tests_user_email_login/views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-pfx
3
- Version: 1.7.6.dev41
3
+ Version: 1.7.6.dev43
4
4
  Summary: Django PFX is a toolkit designed to streamline the development of RESTful APIs using the Django framework.
5
5
  Author: Hervé Martinet
6
6
  Author-email: herve.martinet@gmail.com
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-pfx
3
- Version: 1.7.6.dev41
3
+ Version: 1.7.6.dev43
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
@@ -236,6 +236,8 @@ tests/tests/test_fields_choices.py
236
236
  tests/tests/test_fields_date.py
237
237
  tests/tests/test_fields_decimal.py
238
238
  tests/tests/test_fields_help_text.py
239
+ tests/tests/test_fields_media_local.py
240
+ tests/tests/test_fields_media_s3.py
239
241
  tests/tests/test_fields_minutes_duration.py
240
242
  tests/tests/test_fields_one2many.py
241
243
  tests/tests/test_fields_rich_text.py
@@ -226,6 +226,8 @@
226
226
  "tests/tests/test_fields_date.py",
227
227
  "tests/tests/test_fields_decimal.py",
228
228
  "tests/tests/test_fields_help_text.py",
229
+ "tests/tests/test_fields_media_local.py",
230
+ "tests/tests/test_fields_media_s3.py",
229
231
  "tests/tests/test_fields_minutes_duration.py",
230
232
  "tests/tests/test_fields_one2many.py",
231
233
  "tests/tests/test_fields_rich_text.py",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "tag": "1.7.5",
3
- "distance": 41,
4
- "node": "g32f5225014879db5d0f59213b87db537af6b5a94",
3
+ "distance": 43,
4
+ "node": "g06cda0545eb88c42c2152cffc096975b4f14e7e3",
5
5
  "dirty": false,
6
6
  "branch": "HEAD",
7
7
  "node_date": "2026-10-27"
@@ -6,8 +6,8 @@ from importlib import import_module
6
6
  from urllib.parse import unquote_to_bytes
7
7
 
8
8
  from django.core.exceptions import ValidationError
9
- from django.db import models
10
- from django.db.models.signals import post_delete, post_init, post_save
9
+ from django.db import models, transaction
10
+ from django.db.models.signals import post_init, post_save, pre_delete
11
11
  from django.dispatch import receiver
12
12
 
13
13
  from pfx.pfxcore.shortcuts import settings
@@ -104,6 +104,12 @@ class MediaField(models.JSONField):
104
104
  setattr(obj, self.name, self.upload(obj, file, name))
105
105
  obj.save(update_fields={self.name})
106
106
 
107
+ def delete_file(value):
108
+ transaction.on_commit(
109
+ lambda value=value: self.delete(value),
110
+ robust=True,
111
+ )
112
+
107
113
  after_save = []
108
114
  value = self.value_from_object(obj)
109
115
  if isinstance(value, dict):
@@ -112,10 +118,10 @@ class MediaField(models.JSONField):
112
118
  else:
113
119
  b64 = None
114
120
 
115
- db_value = obj._media_db_values.get(self.name)
121
+ db_value = getattr(obj, f'_db_{self.name}')
116
122
  if db_value and (b64 or not value):
117
- after_save.append(lambda: self.delete(db_value))
118
123
  setattr(obj, self.name, None)
124
+ after_save.append(lambda: delete_file(db_value))
119
125
  if b64:
120
126
  try:
121
127
  file = decode_data_uri(b64)
@@ -160,21 +166,28 @@ class MediaField(models.JSONField):
160
166
  return self.storage.delete(value)
161
167
 
162
168
 
163
- # Kept on the instance: the field is shared by every instance of the model.
164
169
  @receiver(post_init)
165
170
  @receiver(post_save)
166
- def store_media_db_values(sender, instance, update_fields=None, **kwargs):
171
+ def init_db_value(sender, instance, update_fields=None, **kwargs):
172
+ """Set the current db value in _db_{field_name} to track changes.
173
+
174
+ _db_{field_name} is the standard naming convention in PFX to track
175
+ current db value (normally done in `__init__()`).
176
+ """
167
177
  for field in sender._meta.fields:
168
178
  if isinstance(field, MediaField) and (
169
179
  update_fields is None or field.name in update_fields):
170
- if not hasattr(instance, '_media_db_values'):
171
- instance._media_db_values = {}
172
- instance._media_db_values[field.name] = (
180
+ setattr(
181
+ instance, f'_db_{field.name}',
173
182
  field.value_from_object(instance))
174
183
 
175
184
 
176
- @receiver(post_delete)
177
- def post_delete_media(sender, instance, **kwargs):
185
+ @receiver(pre_delete)
186
+ def pre_delete_media(sender, instance, **kwargs):
178
187
  for field in sender._meta.fields:
179
188
  if isinstance(field, MediaField) and field.auto_delete:
180
- field.storage.delete(field.value_from_object(instance))
189
+ value = field.value_from_object(instance)
190
+ transaction.on_commit(
191
+ lambda field=field, value=value: field.storage.delete(value),
192
+ robust=True,
193
+ )
@@ -1,3 +1,4 @@
1
+ import base64
1
2
  import copy
2
3
  import csv
3
4
  import io
@@ -218,7 +219,6 @@ class APIClient(DjangoClient):
218
219
 
219
220
 
220
221
  class TestAssertMixin:
221
-
222
222
  # assert response status code
223
223
  def assertRC(self, response, code, msg=None):
224
224
  msg = '\n'.join([msg or '', response.formatted()])
@@ -291,6 +291,21 @@ class TestAssertMixin:
291
291
  return self.assertNotIn(
292
292
  element, self.get_val(src, key, msg=msg), msg=msg)
293
293
 
294
+ def as_data_url(self, content="Test content"):
295
+ if isinstance(content, str):
296
+ content = content.encode()
297
+ b64 = base64.b64encode(content).decode('ascii')
298
+ return f"data:text/plain;charset=utf-8;base64,{b64}"
299
+
300
+ @classmethod
301
+ def upload_file(
302
+ cls, obj, field='file', file_name='test.txt',
303
+ content="A test file content.", **kw):
304
+ if isinstance(content, str):
305
+ content = content.encode()
306
+ return obj.__class__._meta.get_field(field).upload(
307
+ obj, content, file_name)
308
+
294
309
 
295
310
  class TestPermsAssertMixin(TestAssertMixin):
296
311
  USER_TESTS = {}
@@ -17,6 +17,8 @@ from .test_emails_test_tools import (
17
17
  from .test_fields_choices import TestFieldsChoices
18
18
  from .test_fields_date import TestFieldsDate
19
19
  from .test_fields_decimal import TestFieldsDecimal
20
+ from .test_fields_media_local import TestFieldsMediaLocal
21
+ from .test_fields_media_s3 import TestFieldsMediaS3
20
22
  from .test_fields_minutes_duration import TestFieldsMinutesDuration
21
23
  from .test_fields_one2many import TestFieldsOne2Many
22
24
  from .test_fields_rich_text import TestFieldsRichText
@@ -828,11 +828,12 @@ class BasicAPITest(TestAssertMixin, TestCase):
828
828
  self.assertTrue(Path(f'/tmp/django-pfx-filestore/{key1}').exists())
829
829
 
830
830
  # Replace file
831
- response = self.client.put(f'/api/books/{book_pk}', dict(
832
- local_file=dict(
833
- name='test2.txt',
834
- base64='data:text/plain;charset=utf-8;'
835
- 'base64,TmV3IGNvbnRlbnQ=')))
831
+ with self.captureOnCommitCallbacks(execute=True):
832
+ response = self.client.put(f'/api/books/{book_pk}', dict(
833
+ local_file=dict(
834
+ name='test2.txt',
835
+ base64='data:text/plain;charset=utf-8;'
836
+ 'base64,TmV3IGNvbnRlbnQ=')))
836
837
  self.assertRC(response, 200)
837
838
  self.assertNJE(response, 'local_file.key', key1)
838
839
  self.assertJE(response, 'local_file.name', 'test2.txt')
@@ -841,8 +842,9 @@ class BasicAPITest(TestAssertMixin, TestCase):
841
842
  self.assertTrue(Path(f'/tmp/django-pfx-filestore/{key2}').exists())
842
843
 
843
844
  # Delete file
844
- response = self.client.put(f'/api/books/{book_pk}', dict(
845
- local_file=None))
845
+ with self.captureOnCommitCallbacks(execute=True):
846
+ response = self.client.put(f'/api/books/{book_pk}', dict(
847
+ local_file=None))
846
848
  self.assertRC(response, 200)
847
849
  self.assertJE(response, 'local_file', None)
848
850
  self.assertFalse(Path(f'/tmp/django-pfx-filestore/{key2}').exists())
@@ -865,10 +867,11 @@ class BasicAPITest(TestAssertMixin, TestCase):
865
867
  book.local_file = dict(
866
868
  name='new.txt',
867
869
  base64='data:text/plain;charset=utf-8;base64,TmV3IGNvbnRlbnQ=')
868
- funcs = Book._meta.get_field('local_file').media_pre_save(book)
869
- book.save()
870
- for func in funcs:
871
- func()
870
+ with self.captureOnCommitCallbacks(execute=True):
871
+ funcs = Book._meta.get_field('local_file').media_pre_save(book)
872
+ book.save()
873
+ for func in funcs:
874
+ func()
872
875
  return book.local_file['key']
873
876
 
874
877
  def test_replace_file_after_loading_another_object(self):
@@ -1426,14 +1429,15 @@ class BasicAPITest(TestAssertMixin, TestCase):
1426
1429
  response, f"http://dragonfly.fake/{key}",
1427
1430
  fetch_redirect_response=False)
1428
1431
 
1429
- with patch.object(
1432
+ with (patch.object(
1430
1433
  MockBoto3Client, 'delete_object',
1431
- return_value=None) as mock_delete:
1434
+ return_value=None) as mock_delete,
1435
+ self.captureOnCommitCallbacks(execute=True)):
1432
1436
  response = self.client.delete(
1433
1437
  f'/api/books/{self.author1_book1.pk}')
1434
- mock_delete.assert_called_with(
1435
- Bucket='dragonfly.fake',
1436
- Key=key)
1438
+ mock_delete.assert_called_with(
1439
+ Bucket='dragonfly.fake',
1440
+ Key=key)
1437
1441
 
1438
1442
  def test_annotate_meta_service(self):
1439
1443
  response = self.client.get('/api/authors-annotate/meta')
@@ -0,0 +1,295 @@
1
+ from pathlib import Path
2
+ from unittest.mock import patch
3
+
4
+ from django.conf import settings
5
+ from django.db import connection, models, transaction
6
+ from django.test import TestCase, override_settings
7
+ from django.urls import include, path
8
+
9
+ from pfx.pfxcore import register_views
10
+ from pfx.pfxcore.decorator import rest_view
11
+ from pfx.pfxcore.exceptions import APIError
12
+ from pfx.pfxcore.fields import MediaField
13
+ from pfx.pfxcore.models import JSONReprMixin
14
+ from pfx.pfxcore.storage import LocalStorage
15
+ from pfx.pfxcore.test import APIClient, TestAssertMixin
16
+ from pfx.pfxcore.views import MediaRestViewMixin, RestView
17
+ from tests.views import FakeViewMixin
18
+
19
+
20
+ class TestLocalMediaModel(JSONReprMixin, models.Model):
21
+ file = MediaField(
22
+ auto_delete=True, blank=True, null=True, storage=LocalStorage())
23
+
24
+ class Meta:
25
+ verbose_name = "TestLocalModel"
26
+ verbose_name_plural = "TestLocalModels"
27
+ ordering = ['pk']
28
+
29
+
30
+ @rest_view("/test")
31
+ class TestLocalMediaRestView(FakeViewMixin, MediaRestViewMixin, RestView):
32
+ default_public = True
33
+ model = TestLocalMediaModel
34
+
35
+
36
+ @rest_view("/test-failed")
37
+ class TestLocalMediaFailedRestView(
38
+ FakeViewMixin, MediaRestViewMixin, RestView):
39
+ default_public = True
40
+ model = TestLocalMediaModel
41
+
42
+ def is_valid(self, obj, created=False, rel_data=None):
43
+ with transaction.atomic():
44
+ res = super().is_valid(obj, created, rel_data)
45
+ raise APIError("Error")
46
+ return res
47
+
48
+ def delete_object(self, obj):
49
+ with transaction.atomic():
50
+ super().delete_object(obj)
51
+ raise APIError("Error")
52
+
53
+
54
+ urlpatterns = [
55
+ path('api/', include(register_views(TestLocalMediaRestView))),
56
+ path('api/', include(register_views(TestLocalMediaFailedRestView))),
57
+ path('api/', include('pfx.pfxcore.urls'))
58
+ ]
59
+
60
+
61
+ @override_settings(
62
+ ROOT_URLCONF=__name__,
63
+ STORAGE_LOCAL_ROOT='/tmp/django-pfx-filestore',
64
+ STORAGE_LOCAL_X_ACCEL_REDIRECT=False)
65
+ class TestFieldsMediaLocal(TestAssertMixin, TestCase):
66
+
67
+ def setUp(self):
68
+ self.client = APIClient(default_locale='en')
69
+
70
+ @classmethod
71
+ def setUpTestData(cls):
72
+ with connection.schema_editor() as schema_editor:
73
+ schema_editor.create_model(TestLocalMediaModel)
74
+
75
+ @staticmethod
76
+ def pathlib_error(method):
77
+ return patch(
78
+ f'pathlib.Path.{method}', side_effect=OSError('Disk error'))
79
+
80
+ @property
81
+ def filestore(self):
82
+ return Path(settings.STORAGE_LOCAL_ROOT)
83
+
84
+ def count_files(self):
85
+ return sum(
86
+ 1 for path in self.filestore.rglob("*")
87
+ if path.is_file())
88
+
89
+ def test_get(self):
90
+ obj = TestLocalMediaModel.objects.create()
91
+ obj.file = self.upload_file(
92
+ obj, 'file', 'test.txt', "Test content")
93
+ obj.full_clean()
94
+ obj.save()
95
+
96
+ response = self.client.get(f'/api/test/{obj.pk}')
97
+ self.assertRC(response, 200)
98
+ self.assertJE(response, 'pk', obj.pk)
99
+ self.assertJE(response, 'file.content-length', '12')
100
+ self.assertJE(response, 'file.content-type', 'text/plain')
101
+ self.assertJEExists(response, 'file.key')
102
+ self.assertJE(response, 'file.name', 'test.txt')
103
+ self.assertJE(
104
+ response, 'file.url', f"/test/{obj.pk}/file")
105
+ key = self.get_val(response, 'file.key')
106
+ path = Path(f'/tmp/django-pfx-filestore/{key}')
107
+ self.assertTrue(path.exists())
108
+ with open(path, 'r') as f:
109
+ self.assertEqual(f.read(), "Test content")
110
+
111
+ def test_create(self):
112
+ response = self.client.post('/api/test', dict(
113
+ file=dict(
114
+ name='test.txt',
115
+ base64=self.as_data_url("Test content"))))
116
+ self.assertRC(response, 200)
117
+ obj_pk = self.get_val(response, 'pk')
118
+ self.assertJE(response, 'file.content-length', '12')
119
+ self.assertJE(response, 'file.content-type', 'text/plain')
120
+ self.assertJEExists(response, 'file.key')
121
+ self.assertJE(response, 'file.name', 'test.txt')
122
+ self.assertJE(
123
+ response, 'file.url', f"/test/{obj_pk}/file")
124
+ key = self.get_val(response, 'file.key')
125
+ path = self.filestore / key
126
+ self.assertTrue(path.exists())
127
+ with open(path, 'r') as f:
128
+ self.assertEqual(f.read(), "Test content")
129
+
130
+ def test_create_failed_after_save(self):
131
+ nb_files = self.count_files()
132
+
133
+ response = self.client.post('/api/test-failed', dict(
134
+ file=dict(
135
+ name='test.txt',
136
+ base64=self.as_data_url("Test content"))))
137
+ self.assertRC(response, 400)
138
+ self.assertEqual(TestLocalMediaModel.objects.count(), 0)
139
+
140
+ # If an error occurs after _save(), an orphan file exists.
141
+ self.assertEqual(self.count_files(), nb_files + 1)
142
+
143
+ def test_create_failed_disk(self):
144
+ nb_files = self.count_files()
145
+
146
+ with (
147
+ self.assertLogs(level='ERROR'),
148
+ self.pathlib_error('mkdir') as mock_mkdir):
149
+ response = self.client.post('/api/test', dict(
150
+ file=dict(
151
+ name='test.txt',
152
+ base64=self.as_data_url("Test content"))))
153
+ self.assertRC(response, 500)
154
+ # If the file can't be created, the objet is not saved.
155
+ self.assertEqual(TestLocalMediaModel.objects.count(), 0)
156
+
157
+ self.assertEqual(self.count_files(), nb_files)
158
+ mock_mkdir.assert_called_once()
159
+
160
+ def test_update(self):
161
+ obj = TestLocalMediaModel.objects.create()
162
+ obj.file = self.upload_file(
163
+ obj, 'file', 'test.txt', "Test content")
164
+ obj.full_clean()
165
+ obj.save()
166
+ path1 = self.filestore / obj.file['key']
167
+ self.assertTrue(path1.exists())
168
+
169
+ with self.captureOnCommitCallbacks(execute=True):
170
+ response = self.client.put(f'/api/test/{obj.pk}', dict(
171
+ file=dict(
172
+ name='test2.txt',
173
+ base64=self.as_data_url("Test updated"))))
174
+ self.assertRC(response, 200)
175
+ self.assertJE(response, 'pk', obj.pk)
176
+ self.assertJE(response, 'file.content-length', '12')
177
+ self.assertJE(response, 'file.content-type', 'text/plain')
178
+ self.assertJEExists(response, 'file.key')
179
+ self.assertJE(response, 'file.name', 'test2.txt')
180
+ self.assertJE(
181
+ response, 'file.url', f"/test/{obj.pk}/file")
182
+ key = self.get_val(response, 'file.key')
183
+ path2 = self.filestore / key
184
+ self.assertFalse(path1.exists())
185
+ self.assertTrue(path2.exists())
186
+ with open(path2, 'r') as f:
187
+ self.assertEqual(f.read(), "Test updated")
188
+
189
+ def test_update_failed_after_save(self):
190
+ obj = TestLocalMediaModel.objects.create()
191
+ obj.file = self.upload_file(
192
+ obj, 'file', 'test.txt', "Test content")
193
+ obj.full_clean()
194
+ obj.save()
195
+ key1 = obj.file['key']
196
+ self.assertTrue((self.filestore / key1).exists())
197
+ nb_files = self.count_files()
198
+
199
+ with self.captureOnCommitCallbacks(execute=True):
200
+ response = self.client.put(f'/api/test-failed/{obj.pk}', dict(
201
+ file=dict(
202
+ name='test2.txt',
203
+ base64=self.as_data_url("Test updated"))))
204
+ self.assertRC(response, 400)
205
+ obj.refresh_from_db()
206
+ self.assertEqual(obj.file['name'], 'test.txt')
207
+ self.assertEqual(obj.file['key'], key1)
208
+
209
+ self.assertTrue((self.filestore / key1).exists())
210
+ # If an error occurs after _save(), an orphan file exists.
211
+ self.assertEqual(self.count_files(), nb_files + 1)
212
+
213
+ def test_update_failed_disk(self):
214
+ obj = TestLocalMediaModel.objects.create()
215
+ obj.file = self.upload_file(
216
+ obj, 'file', 'test.txt', "Test content")
217
+ obj.full_clean()
218
+ obj.save()
219
+ key1 = obj.file['key']
220
+ self.assertTrue((self.filestore / key1).exists())
221
+ nb_files = self.count_files()
222
+
223
+ with (
224
+ self.assertLogs(level='ERROR'),
225
+ self.pathlib_error('mkdir') as mock_mkdir,
226
+ self.captureOnCommitCallbacks(execute=True)):
227
+ response = self.client.put(f'/api/test/{obj.pk}', dict(
228
+ file=dict(
229
+ name='test2.txt',
230
+ base64=self.as_data_url("Test updated"))))
231
+ self.assertRC(response, 500)
232
+
233
+ # If the file can't be created, the objet is not saved.
234
+ obj.refresh_from_db()
235
+ self.assertEqual(obj.file['name'], 'test.txt')
236
+ self.assertEqual(obj.file['key'], key1)
237
+
238
+ self.assertTrue((self.filestore / key1).exists())
239
+
240
+ self.assertEqual(self.count_files(), nb_files)
241
+ mock_mkdir.assert_called_once()
242
+
243
+ def test_delete(self):
244
+ obj = TestLocalMediaModel.objects.create()
245
+ obj.file = self.upload_file(
246
+ obj, 'file', 'test.txt', "Test content")
247
+ obj.full_clean()
248
+ obj.save()
249
+ path = self.filestore / obj.file['key']
250
+ self.assertTrue(path.exists())
251
+
252
+ with self.captureOnCommitCallbacks(execute=True):
253
+ response = self.client.delete(f'/api/test/{obj.pk}')
254
+ self.assertRC(response, 200)
255
+ with self.assertRaises(TestLocalMediaModel.DoesNotExist):
256
+ obj.refresh_from_db()
257
+ self.assertFalse(path.exists())
258
+
259
+ def test_delete_failed_after_save(self):
260
+ obj = TestLocalMediaModel.objects.create()
261
+ obj.file = self.upload_file(
262
+ obj, 'file', 'test.txt', "Test content")
263
+ obj.full_clean()
264
+ obj.save()
265
+ path = self.filestore / obj.file['key']
266
+ self.assertTrue(path.exists())
267
+
268
+ with self.captureOnCommitCallbacks(execute=True):
269
+ response = self.client.delete(f'/api/test-failed/{obj.pk}')
270
+ self.assertRC(response, 400)
271
+ obj.refresh_from_db()
272
+ self.assertTrue(path.exists())
273
+
274
+ def test_delete_failed_disk(self):
275
+ obj = TestLocalMediaModel.objects.create()
276
+ obj.file = self.upload_file(
277
+ obj, 'file', 'test.txt', "Test content")
278
+ obj.full_clean()
279
+ obj.save()
280
+ path = self.filestore / obj.file['key']
281
+ self.assertTrue(path.exists())
282
+
283
+ with (
284
+ self.assertLogs(level='ERROR'),
285
+ self.pathlib_error('unlink') as mock_unlink,
286
+ self.captureOnCommitCallbacks(execute=True)):
287
+ response = self.client.delete(f'/api/test/{obj.pk}')
288
+
289
+ self.assertRC(response, 200)
290
+ with self.assertRaises(TestLocalMediaModel.DoesNotExist):
291
+ obj.refresh_from_db()
292
+
293
+ # File exists (orphan)
294
+ self.assertTrue(path.exists())
295
+ mock_unlink.assert_called_once()
@@ -0,0 +1,76 @@
1
+ from unittest.mock import MagicMock, patch
2
+
3
+ from django.db import connection, models, transaction
4
+ from django.test import TestCase, override_settings
5
+ from django.urls import include, path
6
+
7
+ from pfx.pfxcore import register_views
8
+ from pfx.pfxcore.decorator import rest_view
9
+ from pfx.pfxcore.exceptions import APIError
10
+ from pfx.pfxcore.fields import MediaField
11
+ from pfx.pfxcore.models import JSONReprMixin
12
+ from pfx.pfxcore.storage import S3Storage
13
+ from pfx.pfxcore.test import APIClient, MockBoto3Client, TestAssertMixin
14
+ from pfx.pfxcore.views import MediaRestViewMixin, RestView
15
+ from tests.views import FakeViewMixin
16
+
17
+
18
+ class TestS3MediaModel(JSONReprMixin, models.Model):
19
+ file = MediaField(
20
+ auto_delete=True, blank=True, null=True, storage=S3Storage())
21
+
22
+ class Meta:
23
+ verbose_name = "TestS3Model"
24
+ verbose_name_plural = "TestS3Models"
25
+ ordering = ['pk']
26
+
27
+
28
+ @rest_view("/test")
29
+ class TestS3MediaRestView(FakeViewMixin, RestView):
30
+ default_public = True
31
+ model = TestS3MediaModel
32
+
33
+
34
+ @rest_view("/test-failed")
35
+ class TestS3MediaFailedRestView(
36
+ FakeViewMixin, MediaRestViewMixin, RestView):
37
+ default_public = True
38
+ model = TestS3MediaModel
39
+
40
+ def is_valid(self, obj, created=False, rel_data=None):
41
+ with transaction.atomic():
42
+ res = super().is_valid(obj, created, rel_data)
43
+ raise APIError("Error")
44
+ return res
45
+
46
+ def delete_object(self, obj):
47
+ with transaction.atomic():
48
+ super().delete_object(obj)
49
+ raise APIError("Error")
50
+
51
+
52
+ urlpatterns = [
53
+ path('api/', include(register_views(TestS3MediaRestView))),
54
+ path('api/', include(register_views(TestS3MediaFailedRestView))),
55
+ path('api/', include('pfx.pfxcore.urls'))
56
+ ]
57
+
58
+
59
+ @override_settings(
60
+ ROOT_URLCONF=__name__,
61
+ STORAGE_S3_AWS_REGION="fake-region",
62
+ STORAGE_S3_AWS_ACCESS_KEY="FAKE",
63
+ STORAGE_S3_AWS_SECRET_KEY="FAKE-SECRET",
64
+ STORAGE_S3_AWS_S3_BUCKET="dragonfly.fake",
65
+ STORAGE_S3_AWS_GET_URL_EXPIRE=300,
66
+ STORAGE_S3_AWS_PUT_URL_EXPIRE=300)
67
+ @patch("boto3.client", MagicMock(return_value=MockBoto3Client()))
68
+ class TestFieldsMediaS3(TestAssertMixin, TestCase):
69
+
70
+ def setUp(self):
71
+ self.client = APIClient(default_locale='en')
72
+
73
+ @classmethod
74
+ def setUpTestData(cls):
75
+ with connection.schema_editor() as schema_editor:
76
+ schema_editor.create_model(TestS3MediaModel)
@@ -8,8 +8,6 @@ from pathlib import Path
8
8
  from django.http import HttpResponse, StreamingHttpResponse
9
9
  from django.test import SimpleTestCase, override_settings
10
10
 
11
- from openpyxl import Workbook
12
-
13
11
  from pfx.pfxcore.test import CSVFileHelper, XLSXFileHelper
14
12
 
15
13
  CSV = "Name,Age\nAlice,30\nBob,\n"
@@ -17,6 +15,7 @@ CSV = "Name,Age\nAlice,30\nBob,\n"
17
15
 
18
16
  def xlsx(*sheets):
19
17
  """An Excel file of `sheets`, (title, rows) pairs."""
18
+ from openpyxl import Workbook
20
19
  workbook = Workbook()
21
20
  workbook.remove(workbook.active)
22
21
  for title, rows in sheets: