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