django-pfx 1.7.6.dev33__tar.gz → 1.7.6.dev37__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 (330) hide show
  1. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/PKG-INFO +1 -1
  2. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/django_pfx.egg-info/PKG-INFO +1 -1
  3. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/django_pfx.egg-info/SOURCES.txt +1 -0
  4. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/django_pfx.egg-info/scm_file_list.json +1 -0
  5. django_pfx-1.7.6.dev37/django_pfx.egg-info/scm_version.json +8 -0
  6. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/fields/media_field.py +13 -7
  7. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/test.py +76 -2
  8. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/requirements.txt +2 -0
  9. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/basic_api_test.py +66 -7
  10. django_pfx-1.7.6.dev37/tests/tests/parallel_scenarios.py +89 -0
  11. django_pfx-1.7.6.dev37/tests/tests/test_test_runner.py +234 -0
  12. django_pfx-1.7.6.dev33/django_pfx.egg-info/scm_version.json +0 -8
  13. django_pfx-1.7.6.dev33/tests/tests/test_test_runner.py +0 -117
  14. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/.gitignore +0 -0
  15. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/.gitlab-ci.yml +0 -0
  16. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/.pre-commit-config.yaml +0 -0
  17. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/LICENSE +0 -0
  18. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/MANIFEST.in +0 -0
  19. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/README.md +0 -0
  20. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/django_pfx.egg-info/dependency_links.txt +0 -0
  21. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/django_pfx.egg-info/requires.txt +0 -0
  22. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/django_pfx.egg-info/top_level.txt +0 -0
  23. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/doc/Makefile +0 -0
  24. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/doc/conf.py +0 -0
  25. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/doc/index.rst +0 -0
  26. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/doc/source/api.views.rst +0 -0
  27. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/doc/source/authentication.md +0 -0
  28. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/doc/source/decorator.md +0 -0
  29. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/doc/source/generate_openapi.md +0 -0
  30. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/doc/source/getting_started.md +0 -0
  31. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/doc/source/internationalisation.md +0 -0
  32. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/doc/source/model.md +0 -0
  33. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/doc/source/pfx_views.md +0 -0
  34. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/doc/source/profiling.md +0 -0
  35. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/doc/source/settings.md +0 -0
  36. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/doc/source/testing.md +0 -0
  37. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/doc/source/user_jobs.md +0 -0
  38. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/img/pfx.png +0 -0
  39. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/img/pfx.svg +0 -0
  40. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/make_messages +0 -0
  41. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/manage.py +0 -0
  42. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/__init__.py +0 -0
  43. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/emails/__init__.py +0 -0
  44. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/emails/apps.py +0 -0
  45. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/emails/email_backend/__init__.py +0 -0
  46. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/emails/email_backend/memory_email_backend.py +0 -0
  47. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/emails/email_backend/q_mailjet_email_backend.py +0 -0
  48. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/emails/exceptions.py +0 -0
  49. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/emails/locale/fr/LC_MESSAGES/django.mo +0 -0
  50. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/emails/locale/fr/LC_MESSAGES/django.po +0 -0
  51. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/emails/mailjet/__init__.py +0 -0
  52. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/emails/mailjet/mailjet.py +0 -0
  53. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/emails/mailjet/message_result.py +0 -0
  54. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/emails/migrations/0001_initial.py +0 -0
  55. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/emails/migrations/0002_alter_email_headers.py +0 -0
  56. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/emails/migrations/0003_alter_email_options.py +0 -0
  57. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/emails/migrations/0004_alter_email_mail_from.py +0 -0
  58. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/emails/migrations/0005_lowercase_verbose_names.py +0 -0
  59. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/emails/migrations/0006_alter_email_attachments.py +0 -0
  60. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/emails/migrations/__init__.py +0 -0
  61. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/emails/models/__init__.py +0 -0
  62. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/emails/models/email.py +0 -0
  63. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/emails/providers/__init__.py +0 -0
  64. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/emails/providers/email_django_q_executor.py +0 -0
  65. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/emails/shortcuts.py +0 -0
  66. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/emails/tasks.py +0 -0
  67. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/emails/templates/emails/base_template.html +0 -0
  68. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/emails/templates/emails/base_template.txt +0 -0
  69. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/emails/templates/emails/base_template_subject.txt +0 -0
  70. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/emails/test.py +0 -0
  71. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/emails/urls.py +0 -0
  72. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/emails/views/__init__.py +0 -0
  73. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/emails/views/email_rest_view.py +0 -0
  74. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/__init__.py +0 -0
  75. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/apidoc/__init__.py +0 -0
  76. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/apidoc/parameters.py +0 -0
  77. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/apidoc/schema.py +0 -0
  78. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/apidoc/tags.py +0 -0
  79. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/apps.py +0 -0
  80. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/decorator/__init__.py +0 -0
  81. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/decorator/rest.py +0 -0
  82. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/default_settings.py +0 -0
  83. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/exceptions.py +0 -0
  84. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/fields/__init__.py +0 -0
  85. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/fields/decimal_field.py +0 -0
  86. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/fields/minutes_duration_field.py +0 -0
  87. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/fields/rich_text_field.py +0 -0
  88. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/http/__init__.py +0 -0
  89. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/http/json_response.py +0 -0
  90. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.mo +0 -0
  91. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/locale/fr/LC_MESSAGES/django.po +0 -0
  92. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/management/__init__.py +0 -0
  93. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/management/commands/__init__.py +0 -0
  94. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/management/commands/makeapidoc.py +0 -0
  95. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/management/commands/profile.py +0 -0
  96. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/mfa/__init__.py +0 -0
  97. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/mfa/authenticator.py +0 -0
  98. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/mfa/base.py +0 -0
  99. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/mfa/email.py +0 -0
  100. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/mfa/legacy.py +0 -0
  101. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/mfa/sms.py +0 -0
  102. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/middleware/__init__.py +0 -0
  103. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/middleware/authentication.py +0 -0
  104. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/middleware/locale.py +0 -0
  105. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/middleware/profiling.py +0 -0
  106. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/middleware/request_too_big_middleware.py +0 -0
  107. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/middleware/ws_cookie_authentication_middleware.py +0 -0
  108. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/migrations/0001_initial.py +0 -0
  109. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/migrations/0002_pfxpermissionsuser.py +0 -0
  110. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/migrations/0003_delete_pfxpermissionsuser.py +0 -0
  111. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/migrations/0004_alter_loginban_failed_counter_and_more.py +0 -0
  112. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/migrations/0005_mfadevice_mfadevice_mfa_device_unique_backend.py +0 -0
  113. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/migrations/0006_alter_pfxuser_managers.py +0 -0
  114. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/migrations/0007_alter_pfxuser_options.py +0 -0
  115. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/migrations/0008_lowercase_verbose_names.py +0 -0
  116. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/migrations/__init__.py +0 -0
  117. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/migrations/operations/__init__.py +0 -0
  118. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/migrations/operations/permissions.py +0 -0
  119. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/models/__init__.py +0 -0
  120. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/models/abstract_pfx_base_user.py +0 -0
  121. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/models/attachment_mixin.py +0 -0
  122. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/models/cache_mixins.py +0 -0
  123. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/models/login_ban.py +0 -0
  124. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/models/mfa_device.py +0 -0
  125. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/models/mfa_user_mixin.py +0 -0
  126. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/models/not_null_fields.py +0 -0
  127. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/models/ordered_model_mixin.py +0 -0
  128. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/models/pfx_models.py +0 -0
  129. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/models/pfx_user.py +0 -0
  130. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/models/user_filtered_queryset_mixin.py +0 -0
  131. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/serializers/__init__.py +0 -0
  132. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/serializers/json.py +0 -0
  133. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/settings.py +0 -0
  134. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/shortcuts.py +0 -0
  135. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/sms/__init__.py +0 -0
  136. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/sms/backends/__init__.py +0 -0
  137. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/sms/backends/base.py +0 -0
  138. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/sms/backends/console.py +0 -0
  139. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/sms/backends/memory.py +0 -0
  140. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/storage/__init__.py +0 -0
  141. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/storage/exceptions.py +0 -0
  142. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/storage/local_storage.py +0 -0
  143. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/storage/s3_storage.py +0 -0
  144. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/templates/registration/otp_code_email.txt +0 -0
  145. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/templates/registration/otp_code_subject.txt +0 -0
  146. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/templates/registration/password_reset_email.txt +0 -0
  147. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/templates/registration/password_reset_subject.txt +0 -0
  148. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/templates/registration/welcome_email.txt +0 -0
  149. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/templates/registration/welcome_subject.txt +0 -0
  150. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/urls.py +0 -0
  151. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/version_check.py +0 -0
  152. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/views/__init__.py +0 -0
  153. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/views/authentication_views.py +0 -0
  154. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/views/fields.py +0 -0
  155. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/views/filters_views.py +0 -0
  156. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/views/locale_views.py +0 -0
  157. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/views/media_rest_view_mixin.py +0 -0
  158. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/views/mfa_authentication_views.py +0 -0
  159. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/views/ordered_rest_view_mixin.py +0 -0
  160. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/views/parameters/__init__.py +0 -0
  161. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/views/parameters/date_format.py +0 -0
  162. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/views/parameters/groups.py +0 -0
  163. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/views/parameters/list_count.py +0 -0
  164. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/views/parameters/list_items.py +0 -0
  165. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/views/parameters/list_mode.py +0 -0
  166. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/views/parameters/list_order.py +0 -0
  167. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/views/parameters/list_search.py +0 -0
  168. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/views/parameters/media_redirect.py +0 -0
  169. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/views/parameters/meta_fields.py +0 -0
  170. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/views/parameters/meta_filters.py +0 -0
  171. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/views/parameters/meta_orders.py +0 -0
  172. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/views/parameters/subset.py +0 -0
  173. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/views/parameters/subset_limit.py +0 -0
  174. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/views/parameters/subset_offset.py +0 -0
  175. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/views/parameters/subset_page.py +0 -0
  176. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/views/parameters/subset_page_size.py +0 -0
  177. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/views/parameters/subset_page_subset.py +0 -0
  178. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/views/rest_views.py +0 -0
  179. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/pfxcore/xml_test_runner.py +0 -0
  180. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/settings/__init__.py +0 -0
  181. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/settings/dev.py +0 -0
  182. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/userjobs/__init__.py +0 -0
  183. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/userjobs/apps.py +0 -0
  184. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/userjobs/locale/fr/LC_MESSAGES/django.mo +0 -0
  185. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/userjobs/locale/fr/LC_MESSAGES/django.po +0 -0
  186. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/userjobs/migrations/0001_initial.py +0 -0
  187. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/userjobs/migrations/0002_alter_userjob_options_alter_userjob_auto_queued_and_more.py +0 -0
  188. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/userjobs/migrations/0003_clean_scheduled_tasks.py +0 -0
  189. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/userjobs/migrations/0004_userjob_summary_url.py +0 -0
  190. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/userjobs/migrations/0005_swappable_user_job.py +0 -0
  191. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/userjobs/migrations/0006_lowercase_verbose_names.py +0 -0
  192. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/userjobs/migrations/__init__.py +0 -0
  193. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/userjobs/models/__init__.py +0 -0
  194. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/userjobs/models/user_job.py +0 -0
  195. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/userjobs/models/user_job_attachment.py +0 -0
  196. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/userjobs/providers/__init__.py +0 -0
  197. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/userjobs/providers/user_job_django_q_executor.py +0 -0
  198. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/userjobs/providers/user_job_websocket_channel_provider.py +0 -0
  199. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/userjobs/tasks.py +0 -0
  200. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/userjobs/urls.py +0 -0
  201. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/userjobs/views/__init__.py +0 -0
  202. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/userjobs/views/user_job_attachment_view.py +0 -0
  203. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/userjobs/views/user_job_view.py +0 -0
  204. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pfx/userjobs/views/user_websocket_consumer.py +0 -0
  205. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/pyproject.toml +0 -0
  206. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/serve-doc +0 -0
  207. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/setup.cfg +0 -0
  208. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/setup.py +0 -0
  209. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/__init__.py +0 -0
  210. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/apps.py +0 -0
  211. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/channel_group_send_calls.py +0 -0
  212. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/locale/fr/LC_MESSAGES/django.po +0 -0
  213. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/mfa_urls.py +0 -0
  214. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/migrations/0001_initial.py +0 -0
  215. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/migrations/__init__.py +0 -0
  216. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/models.py +0 -0
  217. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/settings/__init__.py +0 -0
  218. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/settings/ci.py +0 -0
  219. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/settings/common.py +0 -0
  220. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/settings/dev.py +0 -0
  221. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/settings/dev_custom_example.py +0 -0
  222. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/settings/dev_default.py +0 -0
  223. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/__init__.py +0 -0
  224. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/basic_api_errors.py +0 -0
  225. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/mfa_test_utils.py +0 -0
  226. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_api_doc.py +0 -0
  227. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_api_doc_search.py +0 -0
  228. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_auth_api.py +0 -0
  229. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_body_mixin.py +0 -0
  230. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_cache.py +0 -0
  231. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_client.py +0 -0
  232. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_emails_email_api.py +0 -0
  233. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_emails_email_model.py +0 -0
  234. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_emails_q_mailjet_email_backend.py +0 -0
  235. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_emails_send_email_message.py +0 -0
  236. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_emails_test_tools.py +0 -0
  237. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_fields_choices.py +0 -0
  238. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_fields_date.py +0 -0
  239. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_fields_decimal.py +0 -0
  240. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_fields_help_text.py +0 -0
  241. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_fields_minutes_duration.py +0 -0
  242. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_fields_one2many.py +0 -0
  243. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_fields_rich_text.py +0 -0
  244. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_fields_time.py +0 -0
  245. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_filters.py +0 -0
  246. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_locale_api.py +0 -0
  247. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_mfa_legacy.py +0 -0
  248. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_mfa_login_api.py +0 -0
  249. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_mfa_setup_api.py +0 -0
  250. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_ordered_rest_view_mixin.py +0 -0
  251. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_perm_tests.py +0 -0
  252. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_permissions.py +0 -0
  253. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_perms_api.py +0 -0
  254. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_post_migrate_groups_update.py +0 -0
  255. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_profiling_middleware.py +0 -0
  256. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_settings.py +0 -0
  257. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_shortcuts.py +0 -0
  258. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_timezone_middleware.py +0 -0
  259. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_tools.py +0 -0
  260. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_user_job_api.py +0 -0
  261. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_user_job_messages.py +0 -0
  262. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_user_job_model.py +0 -0
  263. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_user_queryset.py +0 -0
  264. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_user_websocket.py +0 -0
  265. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_verbose_names.py +0 -0
  266. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_version_check.py +0 -0
  267. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_view_decorators.py +0 -0
  268. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_view_fields.py +0 -0
  269. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/tests/test_ws_cookie_middleware.py +0 -0
  270. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/urls.py +0 -0
  271. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests/views.py +0 -0
  272. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_base_user/__init__.py +0 -0
  273. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_base_user/migrations/__init__.py +0 -0
  274. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_base_user/settings/__init__.py +0 -0
  275. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_base_user/settings/ci.py +0 -0
  276. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_base_user/settings/common.py +0 -0
  277. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_base_user/settings/dev.py +0 -0
  278. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_base_user/settings/dev_custom_example.py +0 -0
  279. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_base_user/settings/dev_default.py +0 -0
  280. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_base_user/tests/__init__.py +0 -0
  281. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_base_user/tests/test_api.py +0 -0
  282. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_base_user/tests/test_auth_api.py +0 -0
  283. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_base_user/urls.py +0 -0
  284. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_base_user/views.py +0 -0
  285. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_custom_user/__init__.py +0 -0
  286. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_custom_user/migrations/0001_initial.py +0 -0
  287. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_custom_user/migrations/__init__.py +0 -0
  288. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_custom_user/models.py +0 -0
  289. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_custom_user/settings/__init__.py +0 -0
  290. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_custom_user/settings/ci.py +0 -0
  291. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_custom_user/settings/common.py +0 -0
  292. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_custom_user/settings/dev.py +0 -0
  293. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_custom_user/settings/dev_custom_example.py +0 -0
  294. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_custom_user/settings/dev_default.py +0 -0
  295. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_custom_user/tests/__init__.py +0 -0
  296. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_custom_user/tests/test_api.py +0 -0
  297. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_custom_user/tests/test_auth_api.py +0 -0
  298. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_custom_user/urls.py +0 -0
  299. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_custom_user/views.py +0 -0
  300. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_custom_user_job/__init__.py +0 -0
  301. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_custom_user_job/migrations/0001_initial.py +0 -0
  302. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_custom_user_job/migrations/0002_lowercase_verbose_names.py +0 -0
  303. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_custom_user_job/migrations/__init__.py +0 -0
  304. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_custom_user_job/models.py +0 -0
  305. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_custom_user_job/providers.py +0 -0
  306. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_custom_user_job/scope.py +0 -0
  307. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_custom_user_job/settings/__init__.py +0 -0
  308. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_custom_user_job/settings/ci.py +0 -0
  309. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_custom_user_job/settings/common.py +0 -0
  310. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_custom_user_job/settings/dev.py +0 -0
  311. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_custom_user_job/settings/dev_custom_example.py +0 -0
  312. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_custom_user_job/settings/dev_default.py +0 -0
  313. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_custom_user_job/tests/__init__.py +0 -0
  314. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_custom_user_job/tests/test_user_job.py +0 -0
  315. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_custom_user_job/urls.py +0 -0
  316. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_user_email_login/__init__.py +0 -0
  317. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_user_email_login/migrations/0001_initial.py +0 -0
  318. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_user_email_login/migrations/__init__.py +0 -0
  319. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_user_email_login/models.py +0 -0
  320. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_user_email_login/settings/__init__.py +0 -0
  321. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_user_email_login/settings/ci.py +0 -0
  322. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_user_email_login/settings/common.py +0 -0
  323. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_user_email_login/settings/dev.py +0 -0
  324. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_user_email_login/settings/dev_custom_example.py +0 -0
  325. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_user_email_login/settings/dev_default.py +0 -0
  326. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_user_email_login/tests/__init__.py +0 -0
  327. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_user_email_login/tests/test_api.py +0 -0
  328. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_user_email_login/tests/test_auth_api.py +0 -0
  329. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/tests_user_email_login/urls.py +0 -0
  330. {django_pfx-1.7.6.dev33 → django_pfx-1.7.6.dev37}/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.dev33
3
+ Version: 1.7.6.dev37
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.dev33
3
+ Version: 1.7.6.dev37
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
@@ -220,6 +220,7 @@ tests/tests/__init__.py
220
220
  tests/tests/basic_api_errors.py
221
221
  tests/tests/basic_api_test.py
222
222
  tests/tests/mfa_test_utils.py
223
+ tests/tests/parallel_scenarios.py
223
224
  tests/tests/test_api_doc.py
224
225
  tests/tests/test_api_doc_search.py
225
226
  tests/tests/test_auth_api.py
@@ -210,6 +210,7 @@
210
210
  "tests/tests/basic_api_errors.py",
211
211
  "tests/tests/basic_api_test.py",
212
212
  "tests/tests/mfa_test_utils.py",
213
+ "tests/tests/parallel_scenarios.py",
213
214
  "tests/tests/test_api_doc.py",
214
215
  "tests/tests/test_api_doc_search.py",
215
216
  "tests/tests/test_auth_api.py",
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "1.7.5",
3
+ "distance": 37,
4
+ "node": "g213d2ffb86d53a7b1417f5fa4929daa928146735",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-10-20"
8
+ }
@@ -7,7 +7,7 @@ from urllib.parse import unquote_to_bytes
7
7
 
8
8
  from django.core.exceptions import ValidationError
9
9
  from django.db import models
10
- from django.db.models.signals import post_delete, post_init
10
+ from django.db.models.signals import post_delete, post_init, post_save
11
11
  from django.dispatch import receiver
12
12
 
13
13
  from pfx.pfxcore.shortcuts import settings
@@ -80,7 +80,6 @@ class MediaField(models.JSONField):
80
80
  self.auto_delete = auto_delete
81
81
  self.allowed_keys = DEFAULT_KEYS.union(extra_keys or set())
82
82
  self.convert_heic = convert_heic
83
- self._db_value = None
84
83
 
85
84
  # Inject default validator if empty
86
85
  kwargs.setdefault("validators", [validate_media_json])
@@ -113,8 +112,9 @@ class MediaField(models.JSONField):
113
112
  else:
114
113
  b64 = None
115
114
 
116
- if self._db_value and (b64 or not value):
117
- after_save.append(lambda: self.delete(self._db_value))
115
+ db_value = obj._media_db_values.get(self.name)
116
+ if db_value and (b64 or not value):
117
+ after_save.append(lambda: self.delete(db_value))
118
118
  setattr(obj, self.name, None)
119
119
  if b64:
120
120
  try:
@@ -160,11 +160,17 @@ class MediaField(models.JSONField):
160
160
  return self.storage.delete(value)
161
161
 
162
162
 
163
+ # Kept on the instance: the field is shared by every instance of the model.
163
164
  @receiver(post_init)
164
- def post_init_media(sender, instance, **kwargs):
165
+ @receiver(post_save)
166
+ def store_media_db_values(sender, instance, update_fields=None, **kwargs):
165
167
  for field in sender._meta.fields:
166
- if isinstance(field, MediaField):
167
- field._db_value = field.value_from_object(instance)
168
+ if isinstance(field, MediaField) and (
169
+ 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] = (
173
+ field.value_from_object(instance))
168
174
 
169
175
 
170
176
  @receiver(post_delete)
@@ -1,6 +1,8 @@
1
+ import copy
1
2
  import json
2
3
  import logging
3
4
  import re
5
+ import traceback
4
6
  from functools import reduce
5
7
  from http.cookies import SimpleCookie
6
8
  from json import JSONDecodeError
@@ -9,7 +11,12 @@ from django.apps import apps
9
11
  from django.db import transaction
10
12
  from django.test import override_settings
11
13
  from django.test.client import Client as DjangoClient
12
- from django.test.runner import DiscoverRunner, ParallelTestSuite
14
+ from django.test.runner import (
15
+ DiscoverRunner,
16
+ ParallelTestSuite,
17
+ RemoteTestResult,
18
+ RemoteTestRunner,
19
+ )
13
20
  from django.test.runner import _init_worker as django_init_worker
14
21
  from django.utils.module_loading import import_string
15
22
 
@@ -351,8 +358,72 @@ def _init_worker(*args, **kwargs):
351
358
  override_settings(**get_test_backends()).enable()
352
359
 
353
360
 
361
+ class UnpicklableTestError(Exception):
362
+ """An error a parallel worker could not send back, as text."""
363
+
364
+
365
+ class UnpicklableTestFailure(UnpicklableTestError, AssertionError):
366
+ """A failure a parallel worker could not send back, as text."""
367
+
368
+
369
+ class _Repr(str):
370
+ def __repr__(self):
371
+ return str(self)
372
+
373
+
374
+ class PicklableRemoteTestResult(RemoteTestResult):
375
+ """Send every outcome of a parallel worker back to the parent.
376
+
377
+ Django aborts the whole run, without the traceback, on an exception it
378
+ cannot pickle (one holding a request, a lock, a local class…). Such an
379
+ exception is sent as its formatted traceback instead, and the run goes
380
+ on."""
381
+
382
+ def _picklable(self, err):
383
+ try:
384
+ self._confirm_picklable(err)
385
+ except Exception:
386
+ cls = (
387
+ UnpicklableTestFailure if issubclass(err[0], AssertionError)
388
+ else UnpicklableTestError)
389
+ return cls, cls(''.join(traceback.format_exception(*err))), None
390
+ return err
391
+
392
+ def _picklable_subtest(self, test, subtest):
393
+ # The subtest holds the running test case, which can become
394
+ # unpicklable after the failure: a fresh one is always sent.
395
+ subtest = copy.copy(subtest)
396
+ subtest.test_case = type(test)(test._testMethodName)
397
+ try:
398
+ self._confirm_picklable(subtest)
399
+ except Exception:
400
+ subtest.params = {
401
+ k: _Repr(repr(v)) for k, v in subtest.params.items()}
402
+ return subtest
403
+
404
+ def addError(self, test, err):
405
+ super().addError(test, self._picklable(err))
406
+
407
+ def addFailure(self, test, err):
408
+ super().addFailure(test, self._picklable(err))
409
+
410
+ def addExpectedFailure(self, test, err):
411
+ super().addExpectedFailure(test, self._picklable(err))
412
+
413
+ def addSubTest(self, test, subtest, err):
414
+ if err is not None:
415
+ err = self._picklable(err)
416
+ subtest = self._picklable_subtest(test, subtest)
417
+ super().addSubTest(test, subtest, err)
418
+
419
+
420
+ class PicklableRemoteTestRunner(RemoteTestRunner):
421
+ resultclass = PicklableRemoteTestResult
422
+
423
+
354
424
  class MemoryBackendsParallelTestSuite(ParallelTestSuite):
355
425
  init_worker = _init_worker
426
+ runner_class = PicklableRemoteTestRunner
356
427
 
357
428
 
358
429
  class MemoryBackendsRunnerMixin:
@@ -371,7 +442,10 @@ class MemoryBackendsRunnerMixin:
371
442
  validated, which ``locmem`` never does.
372
443
 
373
444
  A test class decorated with ``override_settings`` keeps its own
374
- backends."""
445
+ backends.
446
+
447
+ With ``--parallel``, the workers also send back the errors Django
448
+ cannot pickle (see :class:`PicklableRemoteTestResult`)."""
375
449
 
376
450
  parallel_test_suite = MemoryBackendsParallelTestSuite
377
451
 
@@ -4,6 +4,8 @@ pre-commit
4
4
  flake8
5
5
  isort
6
6
  coverage
7
+ # Pickles the tracebacks of the --parallel workers.
8
+ tblib
7
9
  psycopg
8
10
  Babel
9
11
  pyjwt>=2.1
@@ -847,6 +847,64 @@ class BasicAPITest(TestAssertMixin, TestCase):
847
847
  self.assertJE(response, 'local_file', None)
848
848
  self.assertFalse(Path(f'/tmp/django-pfx-filestore/{key2}').exists())
849
849
 
850
+ def _create_book_with_file(self, name):
851
+ response = self.client.post('/api/books', dict(
852
+ name=name,
853
+ author=self.author1.pk,
854
+ pub_date='2000-01-01',
855
+ local_file=dict(
856
+ name='test.txt',
857
+ base64='data:text/plain;charset=utf-8;'
858
+ 'base64,VGVzdCBjb250ZW50')))
859
+ self.assertRC(response, 200)
860
+ return (
861
+ self.get_val(response, 'pk'),
862
+ self.get_val(response, 'local_file.key'))
863
+
864
+ def _replace_file(self, book):
865
+ book.local_file = dict(
866
+ name='new.txt',
867
+ 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()
872
+ return book.local_file['key']
873
+
874
+ def test_replace_file_after_loading_another_object(self):
875
+ """Replacing a file deletes the old file of that object only.
876
+
877
+ The original value used to live on the field, shared by every
878
+ instance of the model: loading a second book overwrote it, and
879
+ replacing the first book's file deleted the second book's file
880
+ while leaving the first one's old file orphaned.
881
+ """
882
+ pk1, key1 = self._create_book_with_file("Book 1")
883
+ pk2, key2 = self._create_book_with_file("Book 2")
884
+
885
+ book1 = Book.objects.get(pk=pk1)
886
+ Book.objects.get(pk=pk2)
887
+ self._replace_file(book1)
888
+
889
+ self.assertFalse(Path(f'/tmp/django-pfx-filestore/{key1}').exists())
890
+ self.assertTrue(Path(f'/tmp/django-pfx-filestore/{key2}').exists())
891
+
892
+ def test_replace_file_twice_on_same_instance(self):
893
+ """The original value follows each save, so a second replacement
894
+ on the same instance deletes the file of the first one rather
895
+ than the long-gone file it was loaded with."""
896
+ pk, key = self._create_book_with_file("Book")
897
+
898
+ book = Book.objects.get(pk=pk)
899
+ new_key = self._replace_file(book)
900
+ self.assertTrue(
901
+ Path(f'/tmp/django-pfx-filestore/{new_key}').exists())
902
+ self._replace_file(book)
903
+
904
+ self.assertFalse(Path(f'/tmp/django-pfx-filestore/{key}').exists())
905
+ self.assertFalse(
906
+ Path(f'/tmp/django-pfx-filestore/{new_key}').exists())
907
+
850
908
  def test_create_file_rejects_non_data_uri(self):
851
909
  """A media upload only accepts a ``data:`` URI.
852
910
 
@@ -1332,19 +1390,20 @@ class BasicAPITest(TestAssertMixin, TestCase):
1332
1390
  STORAGE_S3_AWS_PUT_URL_EXPIRE=300)
1333
1391
  @patch("boto3.client", MagicMock(return_value=MockBoto3Client()))
1334
1392
  def test_media_field(self):
1393
+ key = f"Book/{self.author1_book1.pk}/cover.png"
1335
1394
  response = self.client.get(
1336
1395
  f'/api/books/{self.author1_book1.pk}/cover/upload-url/'
1337
1396
  'cover.png?content-type=image/png')
1338
1397
  self.assertRC(response, 200)
1339
- self.assertJE(response, 'file.key', "Book/1/cover.png")
1398
+ self.assertJE(response, 'file.key', key)
1340
1399
  self.assertJE(response, 'file.name', "cover.png")
1341
1400
  self.assertJE(
1342
- response, 'url', "http://dragonfly.fake/Book/1/cover.png")
1401
+ response, 'url', f"http://dragonfly.fake/{key}")
1343
1402
 
1344
1403
  response = self.client.put(f'/api/books/{self.author1_book1.pk}', dict(
1345
1404
  cover=response.json_content['file']))
1346
1405
  self.assertRC(response, 200)
1347
- self.assertJE(response, 'cover.key', "Book/1/cover.png")
1406
+ self.assertJE(response, 'cover.key', key)
1348
1407
  self.assertJE(response, 'cover.name', "cover.png")
1349
1408
  self.assertJE(response, 'cover.content-length', 1000)
1350
1409
  self.assertJE(response, 'cover.content-type', "image/png")
@@ -1353,18 +1412,18 @@ class BasicAPITest(TestAssertMixin, TestCase):
1353
1412
  f'/api/books/{self.author1_book1.pk}/cover')
1354
1413
  self.assertRC(response, 200)
1355
1414
  self.assertJE(
1356
- response, 'url', "http://dragonfly.fake/Book/1/cover.png")
1415
+ response, 'url', f"http://dragonfly.fake/{key}")
1357
1416
 
1358
1417
  response = self.client.get(
1359
1418
  f'/api/books/{self.author1_book1.pk}/cover?redirect=false')
1360
1419
  self.assertRC(response, 200)
1361
1420
  self.assertJE(
1362
- response, 'url', "http://dragonfly.fake/Book/1/cover.png")
1421
+ response, 'url', f"http://dragonfly.fake/{key}")
1363
1422
 
1364
1423
  response = self.client.get(
1365
1424
  f'/api/books/{self.author1_book1.pk}/cover?redirect=true')
1366
1425
  self.assertRedirects(
1367
- response, "http://dragonfly.fake/Book/1/cover.png",
1426
+ response, f"http://dragonfly.fake/{key}",
1368
1427
  fetch_redirect_response=False)
1369
1428
 
1370
1429
  with patch.object(
@@ -1374,7 +1433,7 @@ class BasicAPITest(TestAssertMixin, TestCase):
1374
1433
  f'/api/books/{self.author1_book1.pk}')
1375
1434
  mock_delete.assert_called_with(
1376
1435
  Bucket='dragonfly.fake',
1377
- Key=f'Book/{self.author1_book1.pk}/cover.png')
1436
+ Key=key)
1378
1437
 
1379
1438
  def test_annotate_meta_service(self):
1380
1439
  response = self.client.get('/api/authors-annotate/meta')
@@ -0,0 +1,89 @@
1
+ """Test cases that fail in ways a parallel worker cannot pickle.
2
+
3
+ Run by test_test_runner.py, not by the discovery (the name does not start
4
+ with ``test``). They live at module level as real test classes do: the
5
+ parent process rebuilds a test case from its class."""
6
+ import threading
7
+ import unittest
8
+
9
+
10
+ class Unpicklable(Exception):
11
+ def __init__(self):
12
+ super().__init__("holds a lock")
13
+ self.lock = threading.Lock()
14
+
15
+
16
+ class KeywordOnly(Exception):
17
+ """Pickles, but cannot be rebuilt from its args."""
18
+
19
+ def __init__(self, *, code):
20
+ super().__init__(f"code {code}")
21
+ self.code = code
22
+
23
+
24
+ class UnpicklableAssertion(AssertionError):
25
+ def __init__(self):
26
+ super().__init__("assertion holding a lock")
27
+ self.lock = threading.Lock()
28
+
29
+
30
+ class Scenarios(unittest.TestCase):
31
+
32
+ def test_plain_failure(self):
33
+ self.assertEqual(1, 2)
34
+
35
+ def test_lock_in_the_exception(self):
36
+ raise Unpicklable()
37
+
38
+ def test_unpicklable_cause(self):
39
+ try:
40
+ raise Unpicklable()
41
+ except Unpicklable as e:
42
+ raise ValueError("wrapped") from e
43
+
44
+ def test_local_class(self):
45
+ class Local(Exception):
46
+ pass
47
+ raise Local("local class")
48
+
49
+ def test_keyword_only_init(self):
50
+ raise KeywordOnly(code=42)
51
+
52
+ def test_unpicklable_assertion(self):
53
+ raise UnpicklableAssertion()
54
+
55
+ @unittest.expectedFailure
56
+ def test_expected_failure(self):
57
+ raise Unpicklable()
58
+
59
+ def test_subtest_then_a_lock(self):
60
+ with self.subTest(i=1):
61
+ self.assertEqual(1, 5)
62
+ self.lock = threading.Lock()
63
+
64
+ def test_subtest_with_a_lock_param(self):
65
+ with self.subTest(lock=threading.Lock()):
66
+ self.fail("with a lock as param")
67
+
68
+ def test_subtest_unpicklable_error(self):
69
+ with self.subTest(n=1):
70
+ raise Unpicklable()
71
+
72
+
73
+ class TearDownError(unittest.TestCase):
74
+
75
+ def tearDown(self):
76
+ raise Unpicklable()
77
+
78
+ def test_ok(self):
79
+ pass
80
+
81
+
82
+ class SetUpClassError(unittest.TestCase):
83
+
84
+ @classmethod
85
+ def setUpClass(cls):
86
+ raise Unpicklable()
87
+
88
+ def test_never_runs(self):
89
+ pass
@@ -0,0 +1,234 @@
1
+ import io
2
+ import pickle
3
+ import re
4
+ import unittest
5
+ from contextlib import redirect_stdout
6
+ from unittest.mock import patch
7
+
8
+ from django.conf import settings
9
+ from django.test import SimpleTestCase, override_settings
10
+ from django.test.runner import RemoteTestRunner
11
+ from django.test.utils import setup_test_environment, teardown_test_environment
12
+
13
+ from pfx.pfxcore.settings import settings as pfx_settings
14
+ from pfx.pfxcore.sms import get_sms_backend
15
+ from pfx.pfxcore.sms.backends.memory import MemorySMSBackend
16
+ from pfx.pfxcore.test import (
17
+ MemoryBackendsParallelTestSuite,
18
+ MemoryBackendsRunnerMixin,
19
+ PicklableRemoteTestResult,
20
+ PicklableRemoteTestRunner,
21
+ SMSTestMixin,
22
+ TestRunner,
23
+ _init_worker,
24
+ get_test_backends,
25
+ )
26
+
27
+ from . import parallel_scenarios
28
+
29
+ MEMORY_EMAIL = 'pfx.emails.email_backend.MemoryEmailBackend'
30
+ MEMORY_SMS = 'pfx.pfxcore.sms.backends.memory.MemorySMSBackend'
31
+
32
+
33
+ class TestMemoryBackendsRunner(SimpleTestCase):
34
+
35
+ def test_the_runner_replaces_the_backends(self):
36
+ """The runner swaps the backends for the whole run, and puts the
37
+ original ones back at the end, locmem restored by Django included.
38
+
39
+ The test environment of the current run is torn down first, as
40
+ Django refuses to set it up twice, and restored afterwards."""
41
+ teardown_test_environment()
42
+ try:
43
+ email = settings.EMAIL_BACKEND
44
+ sms = pfx_settings.PFX_SMS_BACKEND
45
+ runner = TestRunner()
46
+ runner.setup_test_environment()
47
+ try:
48
+ self.assertEqual(settings.EMAIL_BACKEND, MEMORY_EMAIL)
49
+ self.assertEqual(pfx_settings.PFX_SMS_BACKEND, MEMORY_SMS)
50
+ finally:
51
+ runner.teardown_test_environment()
52
+ self.assertEqual(settings.EMAIL_BACKEND, email)
53
+ self.assertEqual(pfx_settings.PFX_SMS_BACKEND, sms)
54
+ finally:
55
+ setup_test_environment(debug=False)
56
+
57
+ def test_the_parallel_workers_get_the_backends(self):
58
+ """A spawned worker sets up the test environment again, locmem
59
+ included: the suite of the runner uses a worker init that puts the
60
+ backends back."""
61
+ self.assertIs(
62
+ TestRunner.parallel_test_suite, MemoryBackendsParallelTestSuite)
63
+ self.assertIs(
64
+ MemoryBackendsParallelTestSuite.init_worker, _init_worker)
65
+
66
+ def test_the_backends_are_configurable(self):
67
+ self.assertEqual(get_test_backends(), dict(
68
+ EMAIL_BACKEND=MEMORY_EMAIL, PFX_SMS_BACKEND=MEMORY_SMS))
69
+ with override_settings(
70
+ PFX_TEST_EMAIL_BACKEND='my.EmailBackend',
71
+ PFX_TEST_SMS_BACKEND='my.SMSBackend'):
72
+ self.assertEqual(get_test_backends(), dict(
73
+ EMAIL_BACKEND='my.EmailBackend',
74
+ PFX_SMS_BACKEND='my.SMSBackend'))
75
+
76
+ def test_the_email_backend_needs_pfx_emails(self):
77
+ """Without pfx.emails, there is no email model to fill: locmem
78
+ stays."""
79
+ with patch('pfx.pfxcore.test.apps.is_installed', return_value=False):
80
+ self.assertEqual(
81
+ get_test_backends(), dict(PFX_SMS_BACKEND=MEMORY_SMS))
82
+
83
+
84
+ def run_in_worker(runner_class, *tests):
85
+ """Run the tests as a parallel worker does, send its events through
86
+ pickle as multiprocessing does, and replay them in a parent result.
87
+
88
+ The tests are listed before the run: the suite drops them as they
89
+ run."""
90
+ suite = unittest.TestSuite(tests)
91
+ listed = list(suite)
92
+ events = pickle.loads(pickle.dumps(runner_class().run(suite).events))
93
+ parent = unittest.TestResult()
94
+ for event in events:
95
+ MemoryBackendsParallelTestSuite([], 1).handle_event(
96
+ parent, listed, event)
97
+ return parent
98
+
99
+
100
+ def outcomes(results):
101
+ """{test id without its module: last line of the traceback}
102
+
103
+ The ``[<object object at 0x…>]`` of a subtest without message is left
104
+ out: unittest compares its message to a sentinel object, which does not
105
+ survive pickling, in Django as here."""
106
+ prefix = f'{parallel_scenarios.__name__}.'
107
+ return {
108
+ re.sub(r' \[<object object at 0x\w+>\]', '', test.id())
109
+ .removeprefix(prefix): text.strip().splitlines()[-1]
110
+ for test, text in results}
111
+
112
+
113
+ class TestPicklableRemoteTestResult(SimpleTestCase):
114
+
115
+ def test_the_runners_use_it(self):
116
+ self.assertIs(
117
+ MemoryBackendsParallelTestSuite.runner_class,
118
+ PicklableRemoteTestRunner)
119
+
120
+ def test_django_aborts_on_an_unpicklable_error(self):
121
+ """Why this result exists: the Django one lets the pickling error
122
+ rise, which aborts the whole run in the parent process."""
123
+ with redirect_stdout(io.StringIO()), self.assertRaises(TypeError):
124
+ run_in_worker(RemoteTestRunner, parallel_scenarios.Scenarios(
125
+ 'test_lock_in_the_exception'))
126
+
127
+ def test_every_outcome_is_sent_back(self):
128
+ """Each way of failing Django cannot pickle reaches the parent,
129
+ under its test, as an error or a failure as it was raised, with
130
+ the original exception at the end of the traceback."""
131
+ names = unittest.TestLoader().getTestCaseNames(
132
+ parallel_scenarios.Scenarios)
133
+ parent = run_in_worker(
134
+ PicklableRemoteTestRunner,
135
+ *map(parallel_scenarios.Scenarios, names),
136
+ parallel_scenarios.TearDownError('test_ok'))
137
+ module = parallel_scenarios.__name__
138
+ lock = f'{module}.Unpicklable: holds a lock'
139
+ self.assertEqual(outcomes(parent.errors), {
140
+ 'Scenarios.test_keyword_only_init':
141
+ f'{module}.KeywordOnly: code 42',
142
+ 'Scenarios.test_local_class':
143
+ f'{module}.Scenarios.test_local_class.<locals>.Local: '
144
+ 'local class',
145
+ 'Scenarios.test_lock_in_the_exception': lock,
146
+ 'Scenarios.test_subtest_unpicklable_error (n=1)': lock,
147
+ 'Scenarios.test_unpicklable_cause': 'ValueError: wrapped',
148
+ 'TearDownError.test_ok': lock,
149
+ })
150
+ [cause] = [
151
+ text for test, text in parent.errors
152
+ if test.id().endswith('test_unpicklable_cause')]
153
+ self.assertIn(lock, cause)
154
+ failures = outcomes(parent.failures)
155
+ # The lock of the subtest params is sent as its repr.
156
+ [lock_param] = [
157
+ k for k in failures if k.startswith(
158
+ 'Scenarios.test_subtest_with_a_lock_param (lock=<unlocked')]
159
+ self.assertEqual(failures, {
160
+ 'Scenarios.test_plain_failure': 'AssertionError: 1 != 2',
161
+ 'Scenarios.test_subtest_then_a_lock (i=1)':
162
+ 'AssertionError: 1 != 5',
163
+ lock_param: 'AssertionError: with a lock as param',
164
+ 'Scenarios.test_unpicklable_assertion':
165
+ f'{module}.UnpicklableAssertion: assertion holding a lock',
166
+ })
167
+ self.assertEqual(
168
+ list(outcomes(parent.expectedFailures)),
169
+ ['Scenarios.test_expected_failure'])
170
+
171
+ def test_a_class_fixture_error_is_sent_back(self):
172
+ """Django sends each class in its own subsuite: a setUpClass error
173
+ comes before the first test, and is reported under the class."""
174
+ parent = run_in_worker(
175
+ PicklableRemoteTestRunner,
176
+ parallel_scenarios.SetUpClassError('test_never_runs'))
177
+ module = parallel_scenarios.__name__
178
+ self.assertEqual(outcomes(parent.errors), {
179
+ f'setUpClass ({module}.SetUpClassError)':
180
+ f'{module}.Unpicklable: holds a lock'})
181
+
182
+ def test_a_picklable_error_is_sent_as_is(self):
183
+ """What pickles goes through untouched, as with Django.
184
+
185
+ Checked without a traceback: a traceback only pickles with tblib,
186
+ and without it every error is sent as text (where Django aborts
187
+ the run and asks for tblib)."""
188
+ err = (AssertionError, AssertionError('1 != 2'), None)
189
+ self.assertIs(PicklableRemoteTestResult()._picklable(err), err)
190
+
191
+
192
+ @override_settings(PFX_SMS_BACKEND=MEMORY_SMS)
193
+ class TestSMSTestMixin(SMSTestMixin, SimpleTestCase):
194
+
195
+ def test_the_sms_are_collected(self):
196
+ get_sms_backend().send_sms('+41791234567', 'Code: 123456')
197
+ self.assertEqual(
198
+ self.sms_outbox(), [('+41791234567', 'Code: 123456')])
199
+ self.clear_sms_outbox()
200
+ self.assertEqual(self.sms_outbox(), [])
201
+
202
+ def test_the_outbox_is_emptied_before_each_test(self):
203
+ """An SMS left by a previous test is gone when the next starts."""
204
+ class ATest(SMSTestMixin, SimpleTestCase):
205
+ def test(self):
206
+ self.outbox_at_start = self.sms_outbox()
207
+
208
+ MemorySMSBackend.outbox.append(('+41791234567', 'Left over'))
209
+ test = ATest('test')
210
+ result = unittest.TestResult()
211
+ test(result)
212
+ self.assertTrue(result.wasSuccessful(), result.errors)
213
+ self.assertEqual(test.outbox_at_start, [])
214
+
215
+ @override_settings(
216
+ PFX_SMS_BACKEND='pfx.pfxcore.sms.backends.console.ConsoleSMSBackend')
217
+ def test_the_helpers_refuse_another_backend(self):
218
+ with self.assertRaises(AssertionError):
219
+ self.sms_outbox()
220
+
221
+
222
+ class TestXMLTestRunner(SimpleTestCase):
223
+
224
+ def test_the_xml_runner_replaces_the_backends(self):
225
+ """The XML variant is the same runner: same backends, same parallel
226
+ workers, and the XML reports."""
227
+ from xmlrunner.extra.djangotestrunner import XMLTestRunner as Base
228
+
229
+ from pfx.pfxcore.xml_test_runner import XMLTestRunner
230
+
231
+ self.assertTrue(issubclass(XMLTestRunner, Base))
232
+ self.assertTrue(issubclass(XMLTestRunner, MemoryBackendsRunnerMixin))
233
+ self.assertIs(
234
+ XMLTestRunner.parallel_test_suite, MemoryBackendsParallelTestSuite)
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "1.7.5",
3
- "distance": 33,
4
- "node": "gcc4ff94bcc60ebba66f3ffe5034d8ffcdd15cdde",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-10-14"
8
- }