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